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