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
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
40
41struct 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
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 */
58typedef 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
66static svn_error_t *
67auto_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
85static svn_error_t *
86read_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
94static svn_error_t *
95mark_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
103static svn_error_t *
104seek_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
112static svn_error_t *
113skip_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
121static svn_error_t *
122data_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
130static svn_error_t *
131readline_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. */
145static svn_stream_t *
146representation_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 */
175static svn_error_t *
176handle_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 */
203static svn_error_t *
204get_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      if (repos_root_dirent == NULL)
241        return svn_error_trace(handle_missing_file(stream, checksum, access,
242                                                   url, must_exist,
243                                                   svn_node_none));
244
245      /* Attempt to open a repository at repos_root_dirent. */
246      SVN_ERR(svn_repos_open3(&access->repos, repos_root_dirent, NULL,
247                              access->pool, scratch_pool));
248    }
249
250  fs_path = &dirent[strlen(repos_root_dirent)];
251
252  /* Get the filesystem. */
253  fs = svn_repos_fs(access->repos);
254
255  /* Find HEAD and the revision root */
256  SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
257  SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, access->pool));
258
259  /* Special case: non-existent paths may be handled as "empty" contents. */
260  SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
261  if (node_kind != svn_node_file)
262    return svn_error_trace(handle_missing_file(stream, checksum, access,
263                                               url, must_exist, node_kind));
264
265  /* Fetch checksum and see whether we already have a matching config */
266  SVN_ERR(svn_fs_file_checksum(checksum, svn_checksum_md5, root, fs_path,
267                               TRUE, access->pool));
268
269  *stream = representation_stream(root, fs_path, access->pool);
270
271  return SVN_NO_ERROR;
272}
273
274/* Open the file at PATH, return its content checksum in CHECKSUM and the
275 * content itself through *STREAM.  Allocate those with the lifetime of
276 * ACCESS.
277 */
278static svn_error_t *
279get_file_config(svn_stream_t **stream,
280                svn_checksum_t **checksum,
281                config_access_t *access,
282                const char *path,
283                svn_boolean_t must_exist,
284                apr_pool_t *scratch_pool)
285{
286  svn_stringbuf_t *contents;
287  svn_node_kind_t node_kind;
288
289  /* Special case: non-existent paths may be handled as "empty" contents. */
290  SVN_ERR(svn_io_check_path(path, &node_kind, scratch_pool));
291  if (node_kind != svn_node_file)
292    return svn_error_trace(handle_missing_file(stream, checksum, access,
293                                               path, must_exist, node_kind));
294
295  /* Now, we should be able to read the file. */
296  SVN_ERR(svn_stringbuf_from_file2(&contents, path, access->pool));
297
298  /* calculate MD5 over the whole file contents */
299  SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
300                       contents->data, contents->len, access->pool));
301  *stream = svn_stream_from_stringbuf(contents, access->pool);
302
303  return SVN_NO_ERROR;
304}
305
306/* Read the configuration from path, URL or registry sub-tree PATH, return
307 * its content checksum in CHECKSUM and the content itself through *STREAM.
308 * Allocate those with the lifetime of ACCESS.
309 */
310static svn_error_t *
311get_generic_config(svn_stream_t **stream,
312                   svn_checksum_t **checksum,
313                   config_access_t *access,
314                   const char *path,
315                   svn_boolean_t must_exist,
316                   apr_pool_t *scratch_pool)
317{
318  svn_stringbuf_t *contents = svn_stringbuf_create_empty(access->pool);
319  svn_config_t *config;
320
321  /* Read the configuration and serialize it into CONTENTS.
322   * That copy can then be processed by the authz parser etc. */
323  SVN_ERR(svn_config_read3(&config, path, must_exist, TRUE, TRUE,
324                           scratch_pool));
325  SVN_ERR(svn_config__write(svn_stream_from_stringbuf(contents, scratch_pool),
326                            config, scratch_pool));
327
328  /* calculate MD5 over the whole file contents */
329  SVN_ERR(svn_checksum(checksum, svn_checksum_md5,
330                       contents->data, contents->len, access->pool));
331  *stream = svn_stream_from_stringbuf(contents, access->pool);
332
333  return SVN_NO_ERROR;
334}
335
336config_access_t *
337svn_repos__create_config_access(svn_repos_t *repos_hint,
338                                apr_pool_t *result_pool)
339{
340  apr_pool_t *pool = svn_pool_create(result_pool);
341  config_access_t *result = apr_pcalloc(pool, sizeof(*result));
342
343  result->repos = repos_hint;
344  result->pool = pool;
345
346  return result;
347}
348
349void
350svn_repos__destroy_config_access(config_access_t *access)
351{
352  svn_pool_destroy(access->pool);
353}
354
355svn_error_t *
356svn_repos__get_config(svn_stream_t **stream,
357                      svn_checksum_t **checksum,
358                      config_access_t *access,
359                      const char *path,
360                      svn_boolean_t must_exist,
361                      apr_pool_t *scratch_pool)
362{
363  svn_error_t *err;
364  /* Directly access the config data. */
365  if (svn_path_is_url(path))
366    err = get_repos_config(stream, checksum, access, path, must_exist,
367                           scratch_pool);
368  else
369    err = get_file_config(stream, checksum, access, path, must_exist,
370                          scratch_pool);
371
372  /* Fallback to indirect access using the generic config file parser.
373   * This is mainly used for registry support under Win32. */
374  if (err)
375    {
376      svn_error_t *err2 = get_generic_config(stream, checksum, access, path,
377                                             must_exist, scratch_pool);
378      if (err2)
379        {
380          svn_error_clear(err2);
381        }
382      else
383        {
384          svn_error_clear(err);
385          err = SVN_NO_ERROR;
386        }
387    }
388
389  return svn_error_trace(err);
390}
391