]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/config_file.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_repos / config_file.c
1 /*
2  * config_file.c :  efficiently read config files from disk or repo
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 \f
26
27 #include "svn_checksum.h"
28 #include "svn_path.h"
29 #include "svn_pools.h"
30
31 #include "private/svn_subr_private.h"
32 #include "private/svn_repos_private.h"
33 #include "private/svn_config_private.h"
34
35 #include "config_file.h"
36
37 #include "svn_private_config.h"
38
39 \f
40
41 struct config_access_t
42 {
43   /* The last repository that we found the requested URL in.  May be NULL. */
44   svn_repos_t *repos;
45
46   /* Owning pool of this structure and is private to this structure.
47    * All objects with the lifetime of this access object will be allocated
48    * from this pool. */
49   apr_pool_t *pool;
50 };
51
52 \f
53
54 /* A stream object that gives access to a representation's content but
55  * delays accessing the repository data until the stream is first used.
56  * IOW, the stream object is cheap as long as it is not accessed.
57  */
58 typedef struct presentation_stream_baton_t
59 {
60   svn_fs_root_t *root;
61   const char *fs_path;
62   apr_pool_t *pool;
63   svn_stream_t *inner;
64 } presentation_stream_baton_t;
65
66 static svn_error_t *
67 auto_open_inner_stream(presentation_stream_baton_t *b)
68 {
69   if (!b->inner)
70     {
71       svn_filesize_t length;
72       svn_stream_t *stream;
73       svn_stringbuf_t *contents;
74
75       SVN_ERR(svn_fs_file_length(&length, b->root, b->fs_path, b->pool));
76       SVN_ERR(svn_fs_file_contents(&stream, b->root, b->fs_path, b->pool));
77       SVN_ERR(svn_stringbuf_from_stream(&contents, stream,
78                                         (apr_size_t)length, b->pool));
79       b->inner = svn_stream_from_stringbuf(contents, b->pool);
80     }
81
82   return SVN_NO_ERROR;
83 }
84
85 static svn_error_t *
86 read_handler_rep(void *baton, char *buffer, apr_size_t *len)
87 {
88   presentation_stream_baton_t *b = baton;
89   SVN_ERR(auto_open_inner_stream(b));
90
91   return svn_error_trace(svn_stream_read2(b->inner, buffer, len));
92 }
93
94 static svn_error_t *
95 mark_handler_rep(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
96 {
97   presentation_stream_baton_t *b = baton;
98   SVN_ERR(auto_open_inner_stream(b));
99
100   return svn_error_trace(svn_stream_mark(b->inner, mark, pool));
101 }
102
103 static svn_error_t *
104 seek_handler_rep(void *baton, const svn_stream_mark_t *mark)
105 {
106   presentation_stream_baton_t *b = baton;
107   SVN_ERR(auto_open_inner_stream(b));
108
109   return svn_error_trace(svn_stream_seek(b->inner, mark));
110 }
111
112 static svn_error_t *
113 skip_handler_rep(void *baton, apr_size_t len)
114 {
115   presentation_stream_baton_t *b = baton;
116   SVN_ERR(auto_open_inner_stream(b));
117
118   return svn_error_trace(svn_stream_skip(b->inner, len));
119 }
120
121 static svn_error_t *
122 data_available_handler_rep(void *baton, svn_boolean_t *data_available)
123 {
124   presentation_stream_baton_t *b = baton;
125   SVN_ERR(auto_open_inner_stream(b));
126
127   return svn_error_trace(svn_stream_data_available(b->inner, data_available));
128 }
129
130 static svn_error_t *
131 readline_handler_rep(void *baton,
132                         svn_stringbuf_t **stringbuf,
133                         const char *eol,
134                         svn_boolean_t *eof,
135                         apr_pool_t *pool)
136 {
137   presentation_stream_baton_t *b = baton;
138   SVN_ERR(auto_open_inner_stream(b));
139
140   return svn_error_trace(svn_stream_readline(b->inner, stringbuf, eol, eof,
141                                              pool));
142 }
143
144 /* Return a lazy access stream for FS_PATH under ROOT, allocated in POOL. */
145 static svn_stream_t *
146 representation_stream(svn_fs_root_t *root,
147                       const char *fs_path,
148                       apr_pool_t *pool)
149 {
150   svn_stream_t *stream;
151   presentation_stream_baton_t *baton;
152
153   baton = apr_pcalloc(pool, sizeof(*baton));
154   baton->root = root;
155   baton->fs_path = fs_path;
156   baton->pool = pool;
157
158   stream = svn_stream_create(baton, pool);
159   svn_stream_set_read2(stream, read_handler_rep, read_handler_rep);
160   svn_stream_set_mark(stream, mark_handler_rep);
161   svn_stream_set_seek(stream, seek_handler_rep);
162   svn_stream_set_skip(stream, skip_handler_rep);
163   svn_stream_set_data_available(stream, data_available_handler_rep);
164   svn_stream_set_readline(stream, readline_handler_rep);
165   return stream;
166 }
167
168 /* Handle the case of a file PATH / url pointing to anything that is either
169  * not a file or does not exist at all.   The case is given by NODE_KIND.
170  *
171  * If MUST_EXIST is not set and the file does not exist at all, return a
172  * default *STREAM and *CHECKSUM allocated in the context of ACCESS, or an
173  * error otherwise.
174  */
175 static svn_error_t *
176 handle_missing_file(svn_stream_t **stream,
177                     svn_checksum_t **checksum,
178                     config_access_t *access,
179                     const char *path,
180                     svn_boolean_t must_exist,
181                     svn_node_kind_t node_kind)
182 {
183   if (node_kind == svn_node_none && !must_exist)
184     {
185       *stream = svn_stream_empty(access->pool);
186       SVN_ERR(svn_checksum(checksum, svn_checksum_md5, "", 0, access->pool));
187     }
188   else if (node_kind != svn_node_file)
189     {
190       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
191                                "'%s' is not a file", path);
192     }
193
194   return SVN_NO_ERROR;
195 }
196
197 /* Open the in-repository file at URL, return its content checksum in
198  * *CHECKSUM and the content itself through *STREAM.  Allocate those with
199  * the lifetime of ACCESS and use SCRATCH_POOL for temporaries.
200  *
201  * Error out when the file does not exist but MUST_EXIST is set.
202  */
203 static svn_error_t *
204 get_repos_config(svn_stream_t **stream,
205                  svn_checksum_t **checksum,
206                  config_access_t *access,
207                  const char *url,
208                  svn_boolean_t must_exist,
209                  apr_pool_t *scratch_pool)
210 {
211   svn_fs_t *fs;
212   svn_fs_root_t *root;
213   svn_revnum_t youngest_rev;
214   svn_node_kind_t node_kind;
215   const char *dirent;
216   const char *fs_path;
217   const char *repos_root_dirent;
218
219   SVN_ERR(svn_uri_get_dirent_from_file_url(&dirent, url, access->pool));
220
221   /* Maybe we can use the repos hint instance instead of creating a
222    * new one. */
223   if (access->repos)
224     {
225       repos_root_dirent = svn_repos_path(access->repos, scratch_pool);
226       if (!svn_dirent_is_absolute(repos_root_dirent))
227         SVN_ERR(svn_dirent_get_absolute(&repos_root_dirent,
228                                         repos_root_dirent,
229                                         scratch_pool));
230
231       if (!svn_dirent_is_ancestor(repos_root_dirent, dirent))
232         access->repos = NULL;
233     }
234
235   /* Open repos if no suitable repos is available. */
236   if (!access->repos)
237     {
238       /* Search for a repository in the full path. */
239       repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
240
241       /* Attempt to open a repository at repos_root_dirent. */
242       SVN_ERR(svn_repos_open3(&access->repos, repos_root_dirent, NULL,
243                               access->pool, scratch_pool));
244     }
245
246   fs_path = &dirent[strlen(repos_root_dirent)];
247
248   /* Get the filesystem. */
249   fs = svn_repos_fs(access->repos);
250
251   /* Find HEAD and the revision root */
252   SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
253   SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, access->pool));
254
255   /* Special case: non-existent paths may be handled as "empty" contents. */
256   SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
257   if (node_kind != svn_node_file)
258     return svn_error_trace(handle_missing_file(stream, checksum, access,
259                                                url, must_exist, node_kind));
260
261   /* Fetch checksum and see whether we already have a matching config */
262   SVN_ERR(svn_fs_file_checksum(checksum, svn_checksum_md5, root, fs_path,
263                                TRUE, access->pool));
264
265   *stream = representation_stream(root, fs_path, access->pool);
266
267   return SVN_NO_ERROR;
268 }
269
270 /* Open the file at PATH, return its content checksum in CHECKSUM and the
271  * content itself through *STREAM.  Allocate those with the lifetime of
272  * ACCESS.
273  */
274 static svn_error_t *
275 get_file_config(svn_stream_t **stream,
276                 svn_checksum_t **checksum,
277                 config_access_t *access,
278                 const char *path,
279                 svn_boolean_t must_exist,
280                 apr_pool_t *scratch_pool)
281 {
282   svn_stringbuf_t *contents;
283   svn_node_kind_t node_kind;
284
285   /* Special case: non-existent paths may be handled as "empty" contents. */
286   SVN_ERR(svn_io_check_path(path, &node_kind, scratch_pool));
287   if (node_kind != svn_node_file)
288     return svn_error_trace(handle_missing_file(stream, checksum, access,
289                                                path, must_exist, node_kind));
290
291   /* Now, we should be able to read the file. */
292   SVN_ERR(svn_stringbuf_from_file2(&contents, path, access->pool));
293
294   /* calculate MD5 over the whole file contents */
295   SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
296                        contents->data, contents->len, access->pool));
297   *stream = svn_stream_from_stringbuf(contents, access->pool);
298
299   return SVN_NO_ERROR;
300 }
301
302 /* Read the configuration from path, URL or registry sub-tree PATH, return
303  * its content checksum in CHECKSUM and the content itself through *STREAM.
304  * Allocate those with the lifetime of ACCESS.
305  */
306 static svn_error_t *
307 get_generic_config(svn_stream_t **stream,
308                    svn_checksum_t **checksum,
309                    config_access_t *access,
310                    const char *path,
311                    svn_boolean_t must_exist,
312                    apr_pool_t *scratch_pool)
313 {
314   svn_stringbuf_t *contents = svn_stringbuf_create_empty(access->pool);
315   svn_config_t *config;
316
317   /* Read the configuration and serialize it into CONTENTS.
318    * That copy can then be processed by the authz parser etc. */
319   SVN_ERR(svn_config_read3(&config, path, must_exist, TRUE, TRUE,
320                            scratch_pool));
321   SVN_ERR(svn_config__write(svn_stream_from_stringbuf(contents, scratch_pool),
322                             config, scratch_pool));
323
324   /* calculate MD5 over the whole file contents */
325   SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
326                        contents->data, contents->len, access->pool));
327   *stream = svn_stream_from_stringbuf(contents, access->pool);
328
329   return SVN_NO_ERROR;
330 }
331
332 config_access_t *
333 svn_repos__create_config_access(svn_repos_t *repos_hint,
334                                 apr_pool_t *result_pool)
335 {
336   apr_pool_t *pool = svn_pool_create(result_pool);
337   config_access_t *result = apr_pcalloc(pool, sizeof(*result));
338
339   result->repos = repos_hint;
340   result->pool = pool;
341
342   return result;
343 }
344
345 void 
346 svn_repos__destroy_config_access(config_access_t *access)
347 {
348   svn_pool_destroy(access->pool);
349 }
350
351 svn_error_t *
352 svn_repos__get_config(svn_stream_t **stream,
353                       svn_checksum_t **checksum,
354                       config_access_t *access,
355                       const char *path,
356                       svn_boolean_t must_exist,
357                       apr_pool_t *scratch_pool)
358 {
359   svn_error_t *err;
360   /* Directly access the config data. */
361   if (svn_path_is_url(path))
362     err = get_repos_config(stream, checksum, access, path, must_exist,
363                            scratch_pool);
364   else
365     err = get_file_config(stream, checksum, access, path, must_exist,
366                           scratch_pool);
367
368   /* Fallback to indirect access using the generic config file parser.
369    * This is mainly used for registry support under Win32. */
370   if (err)
371     {
372       svn_error_t *err2 = get_generic_config(stream, checksum, access, path,
373                                              must_exist, scratch_pool);
374       if (err2)
375         {
376           svn_error_clear(err2);
377         }
378       else
379         {
380           svn_error_clear(err);
381           err = SVN_NO_ERROR;
382         }
383     }
384
385   return svn_error_trace(err);
386 }