Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_repos / fs-wrap.c
blobdb80cdc265b2eaa736e50561ff8f270772f877bb
1 /* fs-wrap.c --- filesystem interface wrappers.
3 * ====================================================================
4 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
18 #include <stdio.h>
19 #include <string.h>
20 #include <ctype.h>
22 #include "svn_pools.h"
23 #include "svn_error.h"
24 #include "svn_fs.h"
25 #include "svn_path.h"
26 #include "svn_props.h"
27 #include "svn_repos.h"
28 #include "repos.h"
29 #include "svn_private_config.h"
32 /*** Commit wrappers ***/
34 svn_error_t *
35 svn_repos_fs_commit_txn(const char **conflict_p,
36 svn_repos_t *repos,
37 svn_revnum_t *new_rev,
38 svn_fs_txn_t *txn,
39 apr_pool_t *pool)
41 svn_error_t *err;
42 const char *txn_name;
44 /* Run pre-commit hooks. */
45 SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
46 SVN_ERR(svn_repos__hooks_pre_commit(repos, txn_name, pool));
48 /* Commit. */
49 SVN_ERR(svn_fs_commit_txn(conflict_p, new_rev, txn, pool));
51 /* Run post-commit hooks. Notice that we're wrapping the error
52 with a -specific- errorcode, so that our caller knows not to try
53 and abort the transaction. */
54 if ((err = svn_repos__hooks_post_commit(repos, *new_rev, pool)))
55 return svn_error_create
56 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
57 _("Commit succeeded, but post-commit hook failed"));
59 return SVN_NO_ERROR;
64 /*** Transaction creation wrappers. ***/
67 svn_error_t *
68 svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
69 svn_repos_t *repos,
70 svn_revnum_t rev,
71 apr_hash_t *revprop_table,
72 apr_pool_t *pool)
74 svn_string_t *author = apr_hash_get(revprop_table, SVN_PROP_REVISION_AUTHOR,
75 APR_HASH_KEY_STRING);
76 apr_array_header_t *revprops;
78 /* Run start-commit hooks. */
79 SVN_ERR(svn_repos__hooks_start_commit(repos, author ? author->data : NULL,
80 repos->client_capabilities, pool));
82 /* Begin the transaction, ask for the fs to do on-the-fly lock checks. */
83 SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
84 SVN_FS_TXN_CHECK_LOCKS, pool));
86 /* We pass the revision properties to the filesystem by adding them
87 as properties on the txn. Later, when we commit the txn, these
88 properties will be copied into the newly created revision. */
89 revprops = svn_prop_hash_to_array(revprop_table, pool);
90 return svn_repos_fs_change_txn_props(*txn_p, revprops, pool);
94 svn_error_t *
95 svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
96 svn_repos_t *repos,
97 svn_revnum_t rev,
98 const char *author,
99 const char *log_msg,
100 apr_pool_t *pool)
102 apr_hash_t *revprop_table = apr_hash_make(pool);
103 if (author)
104 apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR,
105 APR_HASH_KEY_STRING,
106 svn_string_create(author, pool));
107 if (log_msg)
108 apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG,
109 APR_HASH_KEY_STRING,
110 svn_string_create(log_msg, pool));
111 return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
112 pool);
116 svn_error_t *
117 svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p,
118 svn_repos_t *repos,
119 svn_revnum_t rev,
120 const char *author,
121 apr_pool_t *pool)
123 /* ### someday, we might run a read-hook here. */
125 /* Begin the transaction. */
126 SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev, 0, pool));
128 /* We pass the author to the filesystem by adding it as a property
129 on the txn. */
131 /* User (author). */
132 if (author)
134 svn_string_t val;
135 val.data = author;
136 val.len = strlen(author);
137 SVN_ERR(svn_fs_change_txn_prop(*txn_p, SVN_PROP_REVISION_AUTHOR,
138 &val, pool));
141 return SVN_NO_ERROR;
146 /*** Property wrappers ***/
148 /* Validate that property NAME is valid for use in a Subversion
149 repository. */
150 static svn_error_t *
151 validate_prop(const char *name)
153 svn_prop_kind_t kind = svn_property_kind(NULL, name);
154 if (kind != svn_prop_regular_kind)
155 return svn_error_createf
156 (SVN_ERR_REPOS_BAD_ARGS, NULL,
157 _("Storage of non-regular property '%s' is disallowed through the "
158 "repository interface, and could indicate a bug in your client"),
159 name);
160 return SVN_NO_ERROR;
164 svn_error_t *
165 svn_repos_fs_change_node_prop(svn_fs_root_t *root,
166 const char *path,
167 const char *name,
168 const svn_string_t *value,
169 apr_pool_t *pool)
171 /* Validate the property, then call the wrapped function. */
172 SVN_ERR(validate_prop(name));
173 return svn_fs_change_node_prop(root, path, name, value, pool);
177 svn_error_t *
178 svn_repos_fs_change_txn_props(svn_fs_txn_t *txn,
179 apr_array_header_t *txnprops,
180 apr_pool_t *pool)
182 int i;
184 for (i = 0; i < txnprops->nelts; i++)
185 SVN_ERR(validate_prop(APR_ARRAY_IDX(txnprops, i, svn_prop_t).name));
187 return svn_fs_change_txn_props(txn, txnprops, pool);
191 svn_error_t *
192 svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn,
193 const char *name,
194 const svn_string_t *value,
195 apr_pool_t *pool)
197 apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
198 svn_prop_t prop;
200 prop.name = name;
201 prop.value = value;
202 APR_ARRAY_PUSH(props, svn_prop_t) = prop;
204 return svn_repos_fs_change_txn_props(txn, props, pool);
208 svn_error_t *
209 svn_repos_fs_change_rev_prop3(svn_repos_t *repos,
210 svn_revnum_t rev,
211 const char *author,
212 const char *name,
213 const svn_string_t *new_value,
214 svn_boolean_t use_pre_revprop_change_hook,
215 svn_boolean_t use_post_revprop_change_hook,
216 svn_repos_authz_func_t authz_read_func,
217 void *authz_read_baton,
218 apr_pool_t *pool)
220 svn_string_t *old_value;
221 svn_repos_revision_access_level_t readability;
222 char action;
224 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
225 authz_read_func, authz_read_baton,
226 pool));
228 if (readability == svn_repos_revision_access_full)
230 SVN_ERR(validate_prop(name));
231 SVN_ERR(svn_fs_revision_prop(&old_value, repos->fs, rev, name, pool));
232 if (! new_value)
233 action = 'D';
234 else if (! old_value)
235 action = 'A';
236 else
237 action = 'M';
239 if (use_pre_revprop_change_hook)
240 SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, rev, author, name,
241 new_value, action, pool));
243 SVN_ERR(svn_fs_change_rev_prop(repos->fs, rev, name, new_value, pool));
245 if (use_post_revprop_change_hook)
246 SVN_ERR(svn_repos__hooks_post_revprop_change(repos, rev, author, name,
247 old_value, action, pool));
249 else /* rev is either unreadable or only partially readable */
251 return svn_error_createf
252 (SVN_ERR_AUTHZ_UNREADABLE, NULL,
253 _("Write denied: not authorized to read all of revision %ld"), rev);
256 return SVN_NO_ERROR;
260 svn_error_t *
261 svn_repos_fs_change_rev_prop2(svn_repos_t *repos,
262 svn_revnum_t rev,
263 const char *author,
264 const char *name,
265 const svn_string_t *new_value,
266 svn_repos_authz_func_t authz_read_func,
267 void *authz_read_baton,
268 apr_pool_t *pool)
270 return svn_repos_fs_change_rev_prop3(repos, rev, author, name, new_value,
271 TRUE, TRUE, authz_read_func,
272 authz_read_baton, pool);
277 svn_error_t *
278 svn_repos_fs_change_rev_prop(svn_repos_t *repos,
279 svn_revnum_t rev,
280 const char *author,
281 const char *name,
282 const svn_string_t *new_value,
283 apr_pool_t *pool)
285 return svn_repos_fs_change_rev_prop2(repos, rev, author, name, new_value,
286 NULL, NULL, pool);
291 svn_error_t *
292 svn_repos_fs_revision_prop(svn_string_t **value_p,
293 svn_repos_t *repos,
294 svn_revnum_t rev,
295 const char *propname,
296 svn_repos_authz_func_t authz_read_func,
297 void *authz_read_baton,
298 apr_pool_t *pool)
300 svn_repos_revision_access_level_t readability;
302 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
303 authz_read_func, authz_read_baton,
304 pool));
306 if (readability == svn_repos_revision_access_none)
308 /* Property? What property? */
309 *value_p = NULL;
311 else if (readability == svn_repos_revision_access_partial)
313 /* Only svn:author and svn:date are fetchable. */
314 if ((strncmp(propname, SVN_PROP_REVISION_AUTHOR,
315 strlen(SVN_PROP_REVISION_AUTHOR)) != 0)
316 && (strncmp(propname, SVN_PROP_REVISION_DATE,
317 strlen(SVN_PROP_REVISION_DATE)) != 0))
318 *value_p = NULL;
320 else
321 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs,
322 rev, propname, pool));
324 else /* wholly readable revision */
326 SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, rev, propname, pool));
329 return SVN_NO_ERROR;
334 svn_error_t *
335 svn_repos_fs_revision_proplist(apr_hash_t **table_p,
336 svn_repos_t *repos,
337 svn_revnum_t rev,
338 svn_repos_authz_func_t authz_read_func,
339 void *authz_read_baton,
340 apr_pool_t *pool)
342 svn_repos_revision_access_level_t readability;
344 SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
345 authz_read_func, authz_read_baton,
346 pool));
348 if (readability == svn_repos_revision_access_none)
350 /* Return an empty hash. */
351 *table_p = apr_hash_make(pool);
353 else if (readability == svn_repos_revision_access_partial)
355 apr_hash_t *tmphash;
356 svn_string_t *value;
358 /* Produce two property hashtables, both in POOL. */
359 SVN_ERR(svn_fs_revision_proplist(&tmphash, repos->fs, rev, pool));
360 *table_p = apr_hash_make(pool);
362 /* If they exist, we only copy svn:author and svn:date into the
363 'real' hashtable being returned. */
364 value = apr_hash_get(tmphash, SVN_PROP_REVISION_AUTHOR,
365 APR_HASH_KEY_STRING);
366 if (value)
367 apr_hash_set(*table_p, SVN_PROP_REVISION_AUTHOR,
368 APR_HASH_KEY_STRING, value);
370 value = apr_hash_get(tmphash, SVN_PROP_REVISION_DATE,
371 APR_HASH_KEY_STRING);
372 if (value)
373 apr_hash_set(*table_p, SVN_PROP_REVISION_DATE,
374 APR_HASH_KEY_STRING, value);
376 else /* wholly readable revision */
378 SVN_ERR(svn_fs_revision_proplist(table_p, repos->fs, rev, pool));
381 return SVN_NO_ERROR;
384 svn_error_t *
385 svn_repos_fs_lock(svn_lock_t **lock,
386 svn_repos_t *repos,
387 const char *path,
388 const char *token,
389 const char *comment,
390 svn_boolean_t is_dav_comment,
391 apr_time_t expiration_date,
392 svn_revnum_t current_rev,
393 svn_boolean_t steal_lock,
394 apr_pool_t *pool)
396 svn_error_t *err;
397 svn_fs_access_t *access_ctx = NULL;
398 const char *username = NULL;
399 apr_array_header_t *paths;
401 /* Setup an array of paths in anticipation of the ra layers handling
402 multiple locks in one request (1.3 most likely). This is only
403 used by svn_repos__hooks_post_lock. */
404 paths = apr_array_make(pool, 1, sizeof(const char *));
405 APR_ARRAY_PUSH(paths, const char *) = path;
407 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
408 if (access_ctx)
409 SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
411 if (! username)
412 return svn_error_createf
413 (SVN_ERR_FS_NO_USER, NULL,
414 "Cannot lock path '%s', no authenticated username available.", path);
416 /* Run pre-lock hook. This could throw error, preventing
417 svn_fs_lock() from happening. */
418 SVN_ERR(svn_repos__hooks_pre_lock(repos, path, username, pool));
420 /* Lock. */
421 SVN_ERR(svn_fs_lock(lock, repos->fs, path, token, comment, is_dav_comment,
422 expiration_date, current_rev, steal_lock, pool));
424 /* Run post-lock hook. */
425 if ((err = svn_repos__hooks_post_lock(repos, paths, username, pool)))
426 return svn_error_create
427 (SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
428 "Lock succeeded, but post-lock hook failed");
430 return SVN_NO_ERROR;
434 svn_error_t *
435 svn_repos_fs_unlock(svn_repos_t *repos,
436 const char *path,
437 const char *token,
438 svn_boolean_t break_lock,
439 apr_pool_t *pool)
441 svn_error_t *err;
442 svn_fs_access_t *access_ctx = NULL;
443 const char *username = NULL;
444 /* Setup an array of paths in anticipation of the ra layers handling
445 multiple locks in one request (1.3 most likely). This is only
446 used by svn_repos__hooks_post_lock. */
447 apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(const char *));
448 APR_ARRAY_PUSH(paths, const char *) = path;
450 SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
451 if (access_ctx)
452 SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
454 if (! break_lock && ! username)
455 return svn_error_createf
456 (SVN_ERR_FS_NO_USER, NULL,
457 _("Cannot unlock path '%s', no authenticated username available"),
458 path);
460 /* Run pre-unlock hook. This could throw error, preventing
461 svn_fs_unlock() from happening. */
462 SVN_ERR(svn_repos__hooks_pre_unlock(repos, path, username, pool));
464 /* Unlock. */
465 SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
467 /* Run post-unlock hook. */
468 if ((err = svn_repos__hooks_post_unlock(repos, paths, username, pool)))
469 return svn_error_create
470 (SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
471 _("Unlock succeeded, but post-unlock hook failed"));
473 return SVN_NO_ERROR;
477 struct get_locks_baton_t
479 svn_fs_t *fs;
480 svn_fs_root_t *head_root;
481 svn_repos_authz_func_t authz_read_func;
482 void *authz_read_baton;
483 apr_hash_t *locks;
487 /* This implements the svn_fs_get_locks_callback_t interface. */
488 static svn_error_t *
489 get_locks_callback(void *baton,
490 svn_lock_t *lock,
491 apr_pool_t *pool)
493 struct get_locks_baton_t *b = baton;
494 svn_boolean_t readable = TRUE;
495 apr_pool_t *hash_pool = apr_hash_pool_get(b->locks);
497 /* If there's auth to deal with, deal with it. */
498 if (b->authz_read_func)
500 SVN_ERR(b->authz_read_func(&readable, b->head_root, lock->path,
501 b->authz_read_baton, pool));
504 /* If we can read this lock path, add the lock to the return hash. */
505 if (readable)
506 apr_hash_set(b->locks, apr_pstrdup(hash_pool, lock->path),
507 APR_HASH_KEY_STRING, svn_lock_dup(lock, hash_pool));
509 return SVN_NO_ERROR;
513 svn_error_t *
514 svn_repos_fs_get_locks(apr_hash_t **locks,
515 svn_repos_t *repos,
516 const char *path,
517 svn_repos_authz_func_t authz_read_func,
518 void *authz_read_baton,
519 apr_pool_t *pool)
521 apr_hash_t *all_locks = apr_hash_make(pool);
522 svn_revnum_t head_rev;
523 struct get_locks_baton_t baton;
525 /* Locks are always said to apply to HEAD revision, so we'll check
526 to see if locked-paths are readable in HEAD as well. */
527 SVN_ERR(svn_fs_youngest_rev(&head_rev, repos->fs, pool));
529 /* Populate our callback baton. */
530 baton.fs = repos->fs;
531 baton.locks = all_locks;
532 baton.authz_read_func = authz_read_func;
533 baton.authz_read_baton = authz_read_baton;
534 SVN_ERR(svn_fs_revision_root(&(baton.head_root), repos->fs,
535 head_rev, pool));
537 /* Get all the locks. */
538 SVN_ERR(svn_fs_get_locks(repos->fs, path, get_locks_callback,
539 &baton, pool));
541 *locks = baton.locks;
542 return SVN_NO_ERROR;
546 svn_error_t *
547 svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
548 svn_repos_t *repos,
549 const apr_array_header_t *paths,
550 svn_revnum_t rev,
551 svn_mergeinfo_inheritance_t inherit,
552 svn_boolean_t include_descendants,
553 svn_repos_authz_func_t authz_read_func,
554 void *authz_read_baton,
555 apr_pool_t *pool)
557 apr_array_header_t *readable_paths = (apr_array_header_t *) paths;
558 svn_fs_root_t *root;
559 apr_pool_t *iterpool = svn_pool_create(pool);
560 int i;
562 if (!SVN_IS_VALID_REVNUM(rev))
563 SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, pool));
564 SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, pool));
566 /* Filter out unreadable paths before divining merge tracking info. */
567 if (authz_read_func)
569 for (i = 0; i < paths->nelts; i++)
571 svn_boolean_t readable;
572 const char *path = APR_ARRAY_IDX(paths, i, char *);
573 svn_pool_clear(iterpool);
574 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton,
575 iterpool));
576 if (readable && readable_paths != paths)
577 APR_ARRAY_PUSH(readable_paths, const char *) = path;
578 else if (!readable && readable_paths == paths)
580 /* Requested paths differ from readable paths. Fork
581 list of readable paths from requested paths. */
582 int j;
583 readable_paths = apr_array_make(pool, paths->nelts - 1,
584 sizeof(char *));
585 for (j = 0; j < i; j++)
587 path = APR_ARRAY_IDX(paths, j, char *);
588 APR_ARRAY_PUSH(readable_paths, const char *) = path;
594 /* We consciously do not perform authz checks on the paths returned
595 in *MERGEINFO, avoiding massive authz overhead which would allow
596 us to protect the name of where a change was merged from, but not
597 the change itself. */
598 /* ### TODO(reint): ... but how about descendant merged-to paths? */
599 if (readable_paths->nelts > 0)
600 SVN_ERR(svn_fs_get_mergeinfo(mergeinfo, root, readable_paths, inherit,
601 include_descendants, pool));
602 else
603 *mergeinfo = apr_hash_make(pool);
605 svn_pool_destroy(iterpool);
606 return SVN_NO_ERROR;
612 * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq
613 * vim:isk=a-z,A-Z,48-57,_,.,-,>
614 * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0