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;
123 /* Parse the hooks-env file (if any). */
124 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
127 /* Begin the transaction, ask for the fs to do on-the-fly lock checks.
128 We fetch its name, too, so the start-commit hook can use it. */
129 SVN_ERR(svn_fs_begin_txn2(&txn, repos->fs, rev,
130 SVN_FS_TXN_CHECK_LOCKS, pool));
131 err = svn_fs_txn_name(&txn_name, txn, pool);
133 return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
135 /* We pass the revision properties to the filesystem by adding them
136 as properties on the txn. Later, when we commit the txn, these
137 properties will be copied into the newly created revision. */
138 revprops = svn_prop_hash_to_array(revprop_table, pool);
139 err = svn_repos_fs_change_txn_props(txn, revprops, pool);
141 return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
143 /* Run start-commit hooks. */
144 err = svn_repos__hooks_start_commit(repos, hooks_env,
145 author ? author->data : NULL,
146 repos->client_capabilities, txn_name,
149 return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
151 /* We have API promise that *TXN_P is unaffected on faulure. */
158 svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
165 apr_hash_t *revprop_table = apr_hash_make(pool);
167 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
168 svn_string_create(author, pool));
170 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
171 svn_string_create(log_msg, pool));
172 return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
177 /*** Property wrappers ***/
180 svn_repos__validate_prop(const char *name,
181 const svn_string_t *value,
184 svn_prop_kind_t kind = svn_property_kind2(name);
186 /* Allow deleting any property, even a property we don't allow to set. */
190 /* Disallow setting non-regular properties. */
191 if (kind != svn_prop_regular_kind)
192 return svn_error_createf
193 (SVN_ERR_REPOS_BAD_ARGS, NULL,
194 _("Storage of non-regular property '%s' is disallowed through the "
195 "repository interface, and could indicate a bug in your client"),
198 /* Validate "svn:" properties. */
199 if (svn_prop_is_svn_prop(name) && value != NULL)
201 /* Validate that translated props (e.g., svn:log) are UTF-8 with
202 * LF line endings. */
203 if (svn_prop_needs_translation(name))
205 if (!svn_utf__is_valid(value->data, value->len))
207 return svn_error_createf
208 (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
209 _("Cannot accept '%s' property because it is not encoded in "
213 /* Disallow inconsistent line ending style, by simply looking for
214 * carriage return characters ('\r'). */
215 if (strchr(value->data, '\r') != NULL)
217 return svn_error_createf
218 (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
219 _("Cannot accept non-LF line endings in '%s' property"),
224 /* "svn:date" should be a valid date. */
225 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
230 err = svn_time_from_cstring(&temp, value->data, pool);
232 return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE,
241 /* Verify the mergeinfo property value VALUE and return an error if it
242 * is invalid. The PATH on which that property is set is used for error
243 * messages only. Use SCRATCH_POOL for temporary allocations. */
245 verify_mergeinfo(const svn_string_t *value,
247 apr_pool_t *scratch_pool)
250 svn_mergeinfo_t mergeinfo;
252 /* It's okay to delete svn:mergeinfo. */
256 /* Mergeinfo is UTF-8 encoded so the number of bytes returned by strlen()
257 * should match VALUE->LEN. Prevents trailing garbage in the property. */
258 if (strlen(value->data) != value->len)
259 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
260 _("Commit rejected because mergeinfo on '%s' "
261 "contains unexpected string terminator"),
264 err = svn_mergeinfo_parse(&mergeinfo, value->data, scratch_pool);
266 return svn_error_createf(err->apr_err, err,
267 _("Commit rejected because mergeinfo on '%s' "
268 "is syntactically invalid"),
275 svn_repos_fs_change_node_prop(svn_fs_root_t *root,
278 const svn_string_t *value,
281 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
282 SVN_ERR(verify_mergeinfo(value, path, pool));
284 /* Validate the property, then call the wrapped function. */
285 SVN_ERR(svn_repos__validate_prop(name, value, pool));
286 return svn_fs_change_node_prop(root, path, name, value, pool);
291 svn_repos_fs_change_txn_props(svn_fs_txn_t *txn,
292 const apr_array_header_t *txnprops,
297 for (i = 0; i < txnprops->nelts; i++)
299 svn_prop_t *prop = &APR_ARRAY_IDX(txnprops, i, svn_prop_t);
300 SVN_ERR(svn_repos__validate_prop(prop->name, prop->value, pool));
303 return svn_fs_change_txn_props(txn, txnprops, pool);
308 svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn,
310 const svn_string_t *value,
313 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
318 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
320 return svn_repos_fs_change_txn_props(txn, props, pool);
325 svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
329 const svn_string_t *const *old_value_p,
330 const svn_string_t *new_value,
331 svn_boolean_t use_pre_revprop_change_hook,
332 svn_boolean_t use_post_revprop_change_hook,
333 svn_repos_authz_func_t authz_read_func,
334 void *authz_read_baton,
337 svn_repos_revision_access_level_t readability;
339 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
340 authz_read_func, authz_read_baton,
343 if (readability == svn_repos_revision_access_full)
345 const svn_string_t *old_value;
347 apr_hash_t *hooks_env;
349 SVN_ERR(svn_repos__validate_prop(name, new_value, pool));
351 /* Fetch OLD_VALUE for svn_fs_change_rev_prop2(). */
354 old_value = *old_value_p;
358 /* Get OLD_VALUE anyway, in order for ACTION and OLD_VALUE arguments
359 * to the hooks to be accurate. */
360 svn_string_t *old_value2;
362 SVN_ERR(svn_fs_revision_prop(&old_value2, repos->fs, rev, name, pool));
363 old_value = old_value2;
366 /* Prepare ACTION. */
369 else if (! old_value)
374 /* Parse the hooks-env file (if any, and if to be used). */
375 if (use_pre_revprop_change_hook || use_post_revprop_change_hook)
376 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
379 /* ### currently not passing the old_value to hooks */
380 if (use_pre_revprop_change_hook)
381 SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, hooks_env, rev,
382 author, name, new_value,
385 SVN_ERR(svn_fs_change_rev_prop2(repos->fs, rev, name,
386 &old_value, new_value, pool));
388 if (use_post_revprop_change_hook)
389 SVN_ERR(svn_repos__hooks_post_revprop_change(repos, hooks_env, rev,
390 author, name, old_value,
393 else /* rev is either unreadable or only partially readable */
395 return svn_error_createf
396 (SVN_ERR_AUTHZ_UNREADABLE, NULL,
397 _("Write denied: not authorized to read all of revision %ld"), rev);
405 svn_repos_fs_revision_prop(svn_string_t **value_p,
408 const char *propname,
409 svn_repos_authz_func_t authz_read_func,
410 void *authz_read_baton,
413 svn_repos_revision_access_level_t readability;
415 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
416 authz_read_func, authz_read_baton,
419 if (readability == svn_repos_revision_access_none)
421 /* Property? What property? */
424 else if (readability == svn_repos_revision_access_partial)
426 /* Only svn:author and svn:date are fetchable. */
427 if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) != 0)
428 && (strcmp(propname, SVN_PROP_REVISION_DATE) != 0))
432 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs,
433 rev, propname, pool));
435 else /* wholly readable revision */
437 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, rev, propname, pool));
446 svn_repos_fs_revision_proplist(apr_hash_t **table_p,
449 svn_repos_authz_func_t authz_read_func,
450 void *authz_read_baton,
453 svn_repos_revision_access_level_t readability;
455 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
456 authz_read_func, authz_read_baton,
459 if (readability == svn_repos_revision_access_none)
461 /* Return an empty hash. */
462 *table_p = apr_hash_make(pool);
464 else if (readability == svn_repos_revision_access_partial)
469 /* Produce two property hashtables, both in POOL. */
470 SVN_ERR(svn_fs_revision_proplist(&tmphash, repos->fs, rev, pool));
471 *table_p = apr_hash_make(pool);
473 /* If they exist, we only copy svn:author and svn:date into the
474 'real' hashtable being returned. */
475 value = svn_hash_gets(tmphash, SVN_PROP_REVISION_AUTHOR);
477 svn_hash_sets(*table_p, SVN_PROP_REVISION_AUTHOR, value);
479 value = svn_hash_gets(tmphash, SVN_PROP_REVISION_DATE);
481 svn_hash_sets(*table_p, SVN_PROP_REVISION_DATE, value);
483 else /* wholly readable revision */
485 SVN_ERR(svn_fs_revision_proplist(table_p, repos->fs, rev, pool));
492 svn_repos_fs_lock(svn_lock_t **lock,
497 svn_boolean_t is_dav_comment,
498 apr_time_t expiration_date,
499 svn_revnum_t current_rev,
500 svn_boolean_t steal_lock,
504 svn_fs_access_t *access_ctx = NULL;
505 const char *username = NULL;
506 const char *new_token;
507 apr_array_header_t *paths;
508 apr_hash_t *hooks_env;
510 /* Parse the hooks-env file (if any). */
511 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
514 /* Setup an array of paths in anticipation of the ra layers handling
515 multiple locks in one request (1.3 most likely). This is only
516 used by svn_repos__hooks_post_lock. */
517 paths = apr_array_make(pool, 1, sizeof(const char *));
518 APR_ARRAY_PUSH(paths, const char *) = path;
520 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
522 SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
525 return svn_error_createf
526 (SVN_ERR_FS_NO_USER, NULL,
527 "Cannot lock path '%s', no authenticated username available.", path);
529 /* Run pre-lock hook. This could throw error, preventing
530 svn_fs_lock() from happening. */
531 SVN_ERR(svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
532 username, comment, steal_lock, pool));
537 SVN_ERR(svn_fs_lock(lock, repos->fs, path, token, comment, is_dav_comment,
538 expiration_date, current_rev, steal_lock, pool));
540 /* Run post-lock hook. */
541 if ((err = svn_repos__hooks_post_lock(repos, hooks_env,
542 paths, username, pool)))
543 return svn_error_create
544 (SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
545 "Lock succeeded, but post-lock hook failed");
552 svn_repos_fs_unlock(svn_repos_t *repos,
555 svn_boolean_t break_lock,
559 svn_fs_access_t *access_ctx = NULL;
560 const char *username = NULL;
561 apr_array_header_t *paths;
562 apr_hash_t *hooks_env;
564 /* Parse the hooks-env file (if any). */
565 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
568 /* Setup an array of paths in anticipation of the ra layers handling
569 multiple locks in one request (1.3 most likely). This is only
570 used by svn_repos__hooks_post_lock. */
571 paths = apr_array_make(pool, 1, sizeof(const char *));
572 APR_ARRAY_PUSH(paths, const char *) = path;
574 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
576 SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
578 if (! break_lock && ! username)
579 return svn_error_createf
580 (SVN_ERR_FS_NO_USER, NULL,
581 _("Cannot unlock path '%s', no authenticated username available"),
584 /* Run pre-unlock hook. This could throw error, preventing
585 svn_fs_unlock() from happening. */
586 SVN_ERR(svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
590 SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
592 /* Run post-unlock hook. */
593 if ((err = svn_repos__hooks_post_unlock(repos, hooks_env, paths,
595 return svn_error_create
596 (SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
597 _("Unlock succeeded, but post-unlock hook failed"));
603 struct get_locks_baton_t
606 svn_fs_root_t *head_root;
607 svn_repos_authz_func_t authz_read_func;
608 void *authz_read_baton;
613 /* This implements the svn_fs_get_locks_callback_t interface. */
615 get_locks_callback(void *baton,
619 struct get_locks_baton_t *b = baton;
620 svn_boolean_t readable = TRUE;
621 apr_pool_t *hash_pool = apr_hash_pool_get(b->locks);
623 /* If there's auth to deal with, deal with it. */
624 if (b->authz_read_func)
625 SVN_ERR(b->authz_read_func(&readable, b->head_root, lock->path,
626 b->authz_read_baton, pool));
628 /* If we can read this lock path, add the lock to the return hash. */
630 svn_hash_sets(b->locks, apr_pstrdup(hash_pool, lock->path),
631 svn_lock_dup(lock, hash_pool));
638 svn_repos_fs_get_locks2(apr_hash_t **locks,
642 svn_repos_authz_func_t authz_read_func,
643 void *authz_read_baton,
646 apr_hash_t *all_locks = apr_hash_make(pool);
647 svn_revnum_t head_rev;
648 struct get_locks_baton_t baton;
650 SVN_ERR_ASSERT((depth == svn_depth_empty) ||
651 (depth == svn_depth_files) ||
652 (depth == svn_depth_immediates) ||
653 (depth == svn_depth_infinity));
655 SVN_ERR(svn_fs_youngest_rev(&head_rev, repos->fs, pool));
657 /* Populate our callback baton. */
658 baton.fs = repos->fs;
659 baton.locks = all_locks;
660 baton.authz_read_func = authz_read_func;
661 baton.authz_read_baton = authz_read_baton;
662 SVN_ERR(svn_fs_revision_root(&(baton.head_root), repos->fs,
665 /* Get all the locks. */
666 SVN_ERR(svn_fs_get_locks2(repos->fs, path, depth,
667 get_locks_callback, &baton, pool));
669 *locks = baton.locks;
675 svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
677 const apr_array_header_t *paths,
679 svn_mergeinfo_inheritance_t inherit,
680 svn_boolean_t include_descendants,
681 svn_repos_authz_func_t authz_read_func,
682 void *authz_read_baton,
685 /* Here we cast away 'const', but won't try to write through this pointer
686 * without first allocating a new array. */
687 apr_array_header_t *readable_paths = (apr_array_header_t *) paths;
689 apr_pool_t *iterpool = svn_pool_create(pool);
691 if (!SVN_IS_VALID_REVNUM(rev))
692 SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, pool));
693 SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, pool));
695 /* Filter out unreadable paths before divining merge tracking info. */
700 for (i = 0; i < paths->nelts; i++)
702 svn_boolean_t readable;
703 const char *path = APR_ARRAY_IDX(paths, i, char *);
704 svn_pool_clear(iterpool);
705 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton,
707 if (readable && readable_paths != paths)
708 APR_ARRAY_PUSH(readable_paths, const char *) = path;
709 else if (!readable && readable_paths == paths)
711 /* Requested paths differ from readable paths. Fork
712 list of readable paths from requested paths. */
714 readable_paths = apr_array_make(pool, paths->nelts - 1,
716 for (j = 0; j < i; j++)
718 path = APR_ARRAY_IDX(paths, j, char *);
719 APR_ARRAY_PUSH(readable_paths, const char *) = path;
725 /* We consciously do not perform authz checks on the paths returned
726 in *MERGEINFO, avoiding massive authz overhead which would allow
727 us to protect the name of where a change was merged from, but not
728 the change itself. */
729 /* ### TODO(reint): ... but how about descendant merged-to paths? */
730 if (readable_paths->nelts > 0)
731 SVN_ERR(svn_fs_get_mergeinfo2(mergeinfo, root, readable_paths, inherit,
732 include_descendants, TRUE, pool, pool));
734 *mergeinfo = apr_hash_make(pool);
736 svn_pool_destroy(iterpool);
740 struct pack_notify_baton
742 svn_repos_notify_func_t notify_func;
746 /* Implements svn_fs_pack_notify_t. */
748 pack_notify_func(void *baton,
750 svn_fs_pack_notify_action_t pack_action,
753 struct pack_notify_baton *pnb = baton;
754 svn_repos_notify_t *notify;
756 /* Simple conversion works for these values. */
757 SVN_ERR_ASSERT(pack_action >= svn_fs_pack_notify_start
758 && pack_action <= svn_fs_pack_notify_end_revprop);
760 notify = svn_repos_notify_create(pack_action
761 + svn_repos_notify_pack_shard_start
762 - svn_fs_pack_notify_start,
764 notify->shard = shard;
765 pnb->notify_func(pnb->notify_baton, notify, pool);
771 svn_repos_fs_pack2(svn_repos_t *repos,
772 svn_repos_notify_func_t notify_func,
774 svn_cancel_func_t cancel_func,
778 struct pack_notify_baton pnb;
780 pnb.notify_func = notify_func;
781 pnb.notify_baton = notify_baton;
783 return svn_fs_pack(repos->db_path,
784 notify_func ? pack_notify_func : NULL,
785 notify_func ? &pnb : NULL,
786 cancel_func, cancel_baton, pool);
790 svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p,
793 const char *propname,
794 svn_repos_authz_func_t authz_read_func,
795 void *authz_read_baton,
796 apr_pool_t *result_pool,
797 apr_pool_t *scratch_pool)
799 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
800 apr_array_header_t *inherited_props;
801 const char *parent_path = path;
803 inherited_props = apr_array_make(result_pool, 1,
804 sizeof(svn_prop_inherited_item_t *));
805 while (!(parent_path[0] == '/' && parent_path[1] == '\0'))
807 svn_boolean_t allowed = TRUE;
808 apr_hash_t *parent_properties = NULL;
810 svn_pool_clear(iterpool);
811 parent_path = svn_fspath__dirname(parent_path, scratch_pool);
814 SVN_ERR(authz_read_func(&allowed, root, parent_path,
815 authz_read_baton, iterpool));
820 svn_string_t *propval;
822 SVN_ERR(svn_fs_node_prop(&propval, root, parent_path, propname,
826 parent_properties = apr_hash_make(result_pool);
827 svn_hash_sets(parent_properties, propname, propval);
832 SVN_ERR(svn_fs_node_proplist(&parent_properties, root,
833 parent_path, result_pool));
836 if (parent_properties && apr_hash_count(parent_properties))
838 svn_prop_inherited_item_t *i_props =
839 apr_pcalloc(result_pool, sizeof(*i_props));
840 i_props->path_or_url =
841 apr_pstrdup(result_pool, parent_path + 1);
842 i_props->prop_hash = parent_properties;
843 /* Build the output array in depth-first order. */
844 svn_sort__array_insert(&i_props, inherited_props, 0);
849 svn_pool_destroy(iterpool);
851 *inherited_props_p = inherited_props;
856 * vim:ts=4:sw=2:expandtab:tw=80:fo=tcroq
857 * vim:isk=a-z,A-Z,48-57,_,.,-,>
858 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0