1 /* fs-wrap.c --- filesystem interface wrappers.
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
28 #include "svn_pools.h"
29 #include "svn_error.h"
32 #include "svn_props.h"
33 #include "svn_repos.h"
35 #include "svn_sorts.h"
37 #include "svn_private_config.h"
38 #include "private/svn_repos_private.h"
39 #include "private/svn_utf_private.h"
40 #include "private/svn_fspath.h"
43 /*** Commit wrappers ***/
46 svn_repos_fs_commit_txn(const char **conflict_p,
48 svn_revnum_t *new_rev,
52 svn_error_t *err, *err2;
57 apr_hash_t *hooks_env;
59 *new_rev = SVN_INVALID_REVNUM;
61 /* Parse the hooks-env file (if any). */
62 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
65 /* Run pre-commit hooks. */
66 SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
67 SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool));
69 /* Remove any ephemeral transaction properties. */
70 SVN_ERR(svn_fs_txn_proplist(&props, txn, pool));
71 iterpool = svn_pool_create(pool);
72 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
75 apr_hash_this(hi, &key, NULL, NULL);
77 svn_pool_clear(iterpool);
79 if (strncmp(key, SVN_PROP_TXN_PREFIX,
80 (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
82 SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool));
85 svn_pool_destroy(iterpool);
88 err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
89 if (! SVN_IS_VALID_REVNUM(*new_rev))
92 /* Run post-commit hooks. */
93 if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env,
94 *new_rev, txn_name, pool)))
96 err2 = svn_error_create
97 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2,
98 _("Commit succeeded, but post-commit hook failed"));
101 return svn_error_compose_create(err, err2);
106 /*** Transaction creation wrappers. ***/
110 svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
113 apr_hash_t *revprop_table,
116 apr_array_header_t *revprops;
117 const char *txn_name;
118 svn_string_t *author = svn_hash_gets(revprop_table, SVN_PROP_REVISION_AUTHOR);
119 apr_hash_t *hooks_env;
121 /* Parse the hooks-env file (if any). */
122 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
125 /* Begin the transaction, ask for the fs to do on-the-fly lock checks.
126 We fetch its name, too, so the start-commit hook can use it. */
127 SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
128 SVN_FS_TXN_CHECK_LOCKS, pool));
129 SVN_ERR(svn_fs_txn_name(&txn_name, *txn_p, pool));
131 /* We pass the revision properties to the filesystem by adding them
132 as properties on the txn. Later, when we commit the txn, these
133 properties will be copied into the newly created revision. */
134 revprops = svn_prop_hash_to_array(revprop_table, pool);
135 SVN_ERR(svn_repos_fs_change_txn_props(*txn_p, revprops, pool));
137 /* Run start-commit hooks. */
138 SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
139 author ? author->data : NULL,
140 repos->client_capabilities, txn_name,
147 svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
154 apr_hash_t *revprop_table = apr_hash_make(pool);
156 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
157 svn_string_create(author, pool));
159 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
160 svn_string_create(log_msg, pool));
161 return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
166 /*** Property wrappers ***/
169 svn_repos__validate_prop(const char *name,
170 const svn_string_t *value,
173 svn_prop_kind_t kind = svn_property_kind2(name);
175 /* Allow deleting any property, even a property we don't allow to set. */
179 /* Disallow setting non-regular properties. */
180 if (kind != svn_prop_regular_kind)
181 return svn_error_createf
182 (SVN_ERR_REPOS_BAD_ARGS, NULL,
183 _("Storage of non-regular property '%s' is disallowed through the "
184 "repository interface, and could indicate a bug in your client"),
187 /* Validate "svn:" properties. */
188 if (svn_prop_is_svn_prop(name) && value != NULL)
190 /* Validate that translated props (e.g., svn:log) are UTF-8 with
191 * LF line endings. */
192 if (svn_prop_needs_translation(name))
194 if (!svn_utf__is_valid(value->data, value->len))
196 return svn_error_createf
197 (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
198 _("Cannot accept '%s' property because it is not encoded in "
202 /* Disallow inconsistent line ending style, by simply looking for
203 * carriage return characters ('\r'). */
204 if (strchr(value->data, '\r') != NULL)
206 return svn_error_createf
207 (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
208 _("Cannot accept non-LF line endings in '%s' property"),
213 /* "svn:date" should be a valid date. */
214 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
219 err = svn_time_from_cstring(&temp, value->data, pool);
221 return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE,
230 /* Verify the mergeinfo property value VALUE and return an error if it
231 * is invalid. The PATH on which that property is set is used for error
232 * messages only. Use SCRATCH_POOL for temporary allocations. */
234 verify_mergeinfo(const svn_string_t *value,
236 apr_pool_t *scratch_pool)
239 svn_mergeinfo_t mergeinfo;
241 /* It's okay to delete svn:mergeinfo. */
245 /* Mergeinfo is UTF-8 encoded so the number of bytes returned by strlen()
246 * should match VALUE->LEN. Prevents trailing garbage in the property. */
247 if (strlen(value->data) != value->len)
248 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
249 _("Commit rejected because mergeinfo on '%s' "
250 "contains unexpected string terminator"),
253 err = svn_mergeinfo_parse(&mergeinfo, value->data, scratch_pool);
255 return svn_error_createf(err->apr_err, err,
256 _("Commit rejected because mergeinfo on '%s' "
257 "is syntactically invalid"),
264 svn_repos_fs_change_node_prop(svn_fs_root_t *root,
267 const svn_string_t *value,
270 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
271 SVN_ERR(verify_mergeinfo(value, path, pool));
273 /* Validate the property, then call the wrapped function. */
274 SVN_ERR(svn_repos__validate_prop(name, value, pool));
275 return svn_fs_change_node_prop(root, path, name, value, pool);
280 svn_repos_fs_change_txn_props(svn_fs_txn_t *txn,
281 const apr_array_header_t *txnprops,
286 for (i = 0; i < txnprops->nelts; i++)
288 svn_prop_t *prop = &APR_ARRAY_IDX(txnprops, i, svn_prop_t);
289 SVN_ERR(svn_repos__validate_prop(prop->name, prop->value, pool));
292 return svn_fs_change_txn_props(txn, txnprops, pool);
297 svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn,
299 const svn_string_t *value,
302 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
307 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
309 return svn_repos_fs_change_txn_props(txn, props, pool);
314 svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
318 const svn_string_t *const *old_value_p,
319 const svn_string_t *new_value,
320 svn_boolean_t use_pre_revprop_change_hook,
321 svn_boolean_t use_post_revprop_change_hook,
322 svn_repos_authz_func_t authz_read_func,
323 void *authz_read_baton,
326 svn_repos_revision_access_level_t readability;
328 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
329 authz_read_func, authz_read_baton,
332 if (readability == svn_repos_revision_access_full)
334 const svn_string_t *old_value;
336 apr_hash_t *hooks_env;
338 SVN_ERR(svn_repos__validate_prop(name, new_value, pool));
340 /* Fetch OLD_VALUE for svn_fs_change_rev_prop2(). */
343 old_value = *old_value_p;
347 /* Get OLD_VALUE anyway, in order for ACTION and OLD_VALUE arguments
348 * to the hooks to be accurate. */
349 svn_string_t *old_value2;
351 SVN_ERR(svn_fs_revision_prop(&old_value2, repos->fs, rev, name, pool));
352 old_value = old_value2;
355 /* Prepare ACTION. */
358 else if (! old_value)
363 /* Parse the hooks-env file (if any, and if to be used). */
364 if (use_pre_revprop_change_hook || use_post_revprop_change_hook)
365 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
368 /* ### currently not passing the old_value to hooks */
369 if (use_pre_revprop_change_hook)
370 SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, hooks_env, rev,
371 author, name, new_value,
374 SVN_ERR(svn_fs_change_rev_prop2(repos->fs, rev, name,
375 &old_value, new_value, pool));
377 if (use_post_revprop_change_hook)
378 SVN_ERR(svn_repos__hooks_post_revprop_change(repos, hooks_env, rev,
379 author, name, old_value,
382 else /* rev is either unreadable or only partially readable */
384 return svn_error_createf
385 (SVN_ERR_AUTHZ_UNREADABLE, NULL,
386 _("Write denied: not authorized to read all of revision %ld"), rev);
394 svn_repos_fs_revision_prop(svn_string_t **value_p,
397 const char *propname,
398 svn_repos_authz_func_t authz_read_func,
399 void *authz_read_baton,
402 svn_repos_revision_access_level_t readability;
404 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
405 authz_read_func, authz_read_baton,
408 if (readability == svn_repos_revision_access_none)
410 /* Property? What property? */
413 else if (readability == svn_repos_revision_access_partial)
415 /* Only svn:author and svn:date are fetchable. */
416 if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) != 0)
417 && (strcmp(propname, SVN_PROP_REVISION_DATE) != 0))
421 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs,
422 rev, propname, pool));
424 else /* wholly readable revision */
426 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, rev, propname, pool));
435 svn_repos_fs_revision_proplist(apr_hash_t **table_p,
438 svn_repos_authz_func_t authz_read_func,
439 void *authz_read_baton,
442 svn_repos_revision_access_level_t readability;
444 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
445 authz_read_func, authz_read_baton,
448 if (readability == svn_repos_revision_access_none)
450 /* Return an empty hash. */
451 *table_p = apr_hash_make(pool);
453 else if (readability == svn_repos_revision_access_partial)
458 /* Produce two property hashtables, both in POOL. */
459 SVN_ERR(svn_fs_revision_proplist(&tmphash, repos->fs, rev, pool));
460 *table_p = apr_hash_make(pool);
462 /* If they exist, we only copy svn:author and svn:date into the
463 'real' hashtable being returned. */
464 value = svn_hash_gets(tmphash, SVN_PROP_REVISION_AUTHOR);
466 svn_hash_sets(*table_p, SVN_PROP_REVISION_AUTHOR, value);
468 value = svn_hash_gets(tmphash, SVN_PROP_REVISION_DATE);
470 svn_hash_sets(*table_p, SVN_PROP_REVISION_DATE, value);
472 else /* wholly readable revision */
474 SVN_ERR(svn_fs_revision_proplist(table_p, repos->fs, rev, pool));
481 svn_repos_fs_lock(svn_lock_t **lock,
486 svn_boolean_t is_dav_comment,
487 apr_time_t expiration_date,
488 svn_revnum_t current_rev,
489 svn_boolean_t steal_lock,
493 svn_fs_access_t *access_ctx = NULL;
494 const char *username = NULL;
495 const char *new_token;
496 apr_array_header_t *paths;
497 apr_hash_t *hooks_env;
499 /* Parse the hooks-env file (if any). */
500 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
503 /* Setup an array of paths in anticipation of the ra layers handling
504 multiple locks in one request (1.3 most likely). This is only
505 used by svn_repos__hooks_post_lock. */
506 paths = apr_array_make(pool, 1, sizeof(const char *));
507 APR_ARRAY_PUSH(paths, const char *) = path;
509 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
511 SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
514 return svn_error_createf
515 (SVN_ERR_FS_NO_USER, NULL,
516 "Cannot lock path '%s', no authenticated username available.", path);
518 /* Run pre-lock hook. This could throw error, preventing
519 svn_fs_lock() from happening. */
520 SVN_ERR(svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
521 username, comment, steal_lock, pool));
526 SVN_ERR(svn_fs_lock(lock, repos->fs, path, token, comment, is_dav_comment,
527 expiration_date, current_rev, steal_lock, pool));
529 /* Run post-lock hook. */
530 if ((err = svn_repos__hooks_post_lock(repos, hooks_env,
531 paths, username, pool)))
532 return svn_error_create
533 (SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
534 "Lock succeeded, but post-lock hook failed");
541 svn_repos_fs_unlock(svn_repos_t *repos,
544 svn_boolean_t break_lock,
548 svn_fs_access_t *access_ctx = NULL;
549 const char *username = NULL;
550 apr_array_header_t *paths;
551 apr_hash_t *hooks_env;
553 /* Parse the hooks-env file (if any). */
554 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
557 /* Setup an array of paths in anticipation of the ra layers handling
558 multiple locks in one request (1.3 most likely). This is only
559 used by svn_repos__hooks_post_lock. */
560 paths = apr_array_make(pool, 1, sizeof(const char *));
561 APR_ARRAY_PUSH(paths, const char *) = path;
563 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
565 SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
567 if (! break_lock && ! username)
568 return svn_error_createf
569 (SVN_ERR_FS_NO_USER, NULL,
570 _("Cannot unlock path '%s', no authenticated username available"),
573 /* Run pre-unlock hook. This could throw error, preventing
574 svn_fs_unlock() from happening. */
575 SVN_ERR(svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
579 SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
581 /* Run post-unlock hook. */
582 if ((err = svn_repos__hooks_post_unlock(repos, hooks_env, paths,
584 return svn_error_create
585 (SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
586 _("Unlock succeeded, but post-unlock hook failed"));
592 struct get_locks_baton_t
595 svn_fs_root_t *head_root;
596 svn_repos_authz_func_t authz_read_func;
597 void *authz_read_baton;
602 /* This implements the svn_fs_get_locks_callback_t interface. */
604 get_locks_callback(void *baton,
608 struct get_locks_baton_t *b = baton;
609 svn_boolean_t readable = TRUE;
610 apr_pool_t *hash_pool = apr_hash_pool_get(b->locks);
612 /* If there's auth to deal with, deal with it. */
613 if (b->authz_read_func)
614 SVN_ERR(b->authz_read_func(&readable, b->head_root, lock->path,
615 b->authz_read_baton, pool));
617 /* If we can read this lock path, add the lock to the return hash. */
619 svn_hash_sets(b->locks, apr_pstrdup(hash_pool, lock->path),
620 svn_lock_dup(lock, hash_pool));
627 svn_repos_fs_get_locks2(apr_hash_t **locks,
631 svn_repos_authz_func_t authz_read_func,
632 void *authz_read_baton,
635 apr_hash_t *all_locks = apr_hash_make(pool);
636 svn_revnum_t head_rev;
637 struct get_locks_baton_t baton;
639 SVN_ERR_ASSERT((depth == svn_depth_empty) ||
640 (depth == svn_depth_files) ||
641 (depth == svn_depth_immediates) ||
642 (depth == svn_depth_infinity));
644 SVN_ERR(svn_fs_youngest_rev(&head_rev, repos->fs, pool));
646 /* Populate our callback baton. */
647 baton.fs = repos->fs;
648 baton.locks = all_locks;
649 baton.authz_read_func = authz_read_func;
650 baton.authz_read_baton = authz_read_baton;
651 SVN_ERR(svn_fs_revision_root(&(baton.head_root), repos->fs,
654 /* Get all the locks. */
655 SVN_ERR(svn_fs_get_locks2(repos->fs, path, depth,
656 get_locks_callback, &baton, pool));
658 *locks = baton.locks;
664 svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
666 const apr_array_header_t *paths,
668 svn_mergeinfo_inheritance_t inherit,
669 svn_boolean_t include_descendants,
670 svn_repos_authz_func_t authz_read_func,
671 void *authz_read_baton,
674 /* Here we cast away 'const', but won't try to write through this pointer
675 * without first allocating a new array. */
676 apr_array_header_t *readable_paths = (apr_array_header_t *) paths;
678 apr_pool_t *iterpool = svn_pool_create(pool);
680 if (!SVN_IS_VALID_REVNUM(rev))
681 SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, pool));
682 SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, pool));
684 /* Filter out unreadable paths before divining merge tracking info. */
689 for (i = 0; i < paths->nelts; i++)
691 svn_boolean_t readable;
692 const char *path = APR_ARRAY_IDX(paths, i, char *);
693 svn_pool_clear(iterpool);
694 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton,
696 if (readable && readable_paths != paths)
697 APR_ARRAY_PUSH(readable_paths, const char *) = path;
698 else if (!readable && readable_paths == paths)
700 /* Requested paths differ from readable paths. Fork
701 list of readable paths from requested paths. */
703 readable_paths = apr_array_make(pool, paths->nelts - 1,
705 for (j = 0; j < i; j++)
707 path = APR_ARRAY_IDX(paths, j, char *);
708 APR_ARRAY_PUSH(readable_paths, const char *) = path;
714 /* We consciously do not perform authz checks on the paths returned
715 in *MERGEINFO, avoiding massive authz overhead which would allow
716 us to protect the name of where a change was merged from, but not
717 the change itself. */
718 /* ### TODO(reint): ... but how about descendant merged-to paths? */
719 if (readable_paths->nelts > 0)
720 SVN_ERR(svn_fs_get_mergeinfo2(mergeinfo, root, readable_paths, inherit,
721 include_descendants, TRUE, pool, pool));
723 *mergeinfo = apr_hash_make(pool);
725 svn_pool_destroy(iterpool);
729 struct pack_notify_baton
731 svn_repos_notify_func_t notify_func;
735 /* Implements svn_fs_pack_notify_t. */
737 pack_notify_func(void *baton,
739 svn_fs_pack_notify_action_t pack_action,
742 struct pack_notify_baton *pnb = baton;
743 svn_repos_notify_t *notify;
745 /* Simple conversion works for these values. */
746 SVN_ERR_ASSERT(pack_action >= svn_fs_pack_notify_start
747 && pack_action <= svn_fs_pack_notify_end_revprop);
749 notify = svn_repos_notify_create(pack_action
750 + svn_repos_notify_pack_shard_start
751 - svn_fs_pack_notify_start,
753 notify->shard = shard;
754 pnb->notify_func(pnb->notify_baton, notify, pool);
760 svn_repos_fs_pack2(svn_repos_t *repos,
761 svn_repos_notify_func_t notify_func,
763 svn_cancel_func_t cancel_func,
767 struct pack_notify_baton pnb;
769 pnb.notify_func = notify_func;
770 pnb.notify_baton = notify_baton;
772 return svn_fs_pack(repos->db_path,
773 notify_func ? pack_notify_func : NULL,
774 notify_func ? &pnb : NULL,
775 cancel_func, cancel_baton, pool);
779 svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p,
782 const char *propname,
783 svn_repos_authz_func_t authz_read_func,
784 void *authz_read_baton,
785 apr_pool_t *result_pool,
786 apr_pool_t *scratch_pool)
788 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
789 apr_array_header_t *inherited_props;
790 const char *parent_path = path;
792 inherited_props = apr_array_make(result_pool, 1,
793 sizeof(svn_prop_inherited_item_t *));
794 while (!(parent_path[0] == '/' && parent_path[1] == '\0'))
796 svn_boolean_t allowed = TRUE;
797 apr_hash_t *parent_properties = NULL;
799 svn_pool_clear(iterpool);
800 parent_path = svn_fspath__dirname(parent_path, scratch_pool);
803 SVN_ERR(authz_read_func(&allowed, root, parent_path,
804 authz_read_baton, iterpool));
809 svn_string_t *propval;
811 SVN_ERR(svn_fs_node_prop(&propval, root, parent_path, propname,
815 parent_properties = apr_hash_make(result_pool);
816 svn_hash_sets(parent_properties, propname, propval);
821 SVN_ERR(svn_fs_node_proplist(&parent_properties, root,
822 parent_path, result_pool));
825 if (parent_properties && apr_hash_count(parent_properties))
827 svn_prop_inherited_item_t *i_props =
828 apr_pcalloc(result_pool, sizeof(*i_props));
829 i_props->path_or_url =
830 apr_pstrdup(result_pool, parent_path + 1);
831 i_props->prop_hash = parent_properties;
832 /* Build the output array in depth-first order. */
833 svn_sort__array_insert(&i_props, inherited_props, 0);
838 svn_pool_destroy(iterpool);
840 *inherited_props_p = inherited_props;
845 * vim:ts=4:sw=2:expandtab:tw=80:fo=tcroq
846 * vim:isk=a-z,A-Z,48-57,_,.,-,>
847 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0