]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_repos/fs-wrap.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_repos / fs-wrap.c
1 /* fs-wrap.c --- filesystem interface wrappers.
2  *
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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <stdio.h>
24 #include <string.h>
25 #include <ctype.h>
26
27 #include "svn_hash.h"
28 #include "svn_pools.h"
29 #include "svn_error.h"
30 #include "svn_fs.h"
31 #include "svn_path.h"
32 #include "svn_props.h"
33 #include "svn_repos.h"
34 #include "svn_time.h"
35 #include "svn_sorts.h"
36 #include "repos.h"
37 #include "svn_private_config.h"
38 #include "private/svn_repos_private.h"
39 #include "private/svn_sorts_private.h"
40 #include "private/svn_utf_private.h"
41 #include "private/svn_fspath.h"
42
43 \f
44 /*** Commit wrappers ***/
45
46 svn_error_t *
47 svn_repos_fs_commit_txn(const char **conflict_p,
48                         svn_repos_t *repos,
49                         svn_revnum_t *new_rev,
50                         svn_fs_txn_t *txn,
51                         apr_pool_t *pool)
52 {
53   svn_error_t *err, *err2;
54   const char *txn_name;
55   apr_hash_t *props;
56   apr_pool_t *iterpool;
57   apr_hash_index_t *hi;
58   apr_hash_t *hooks_env;
59
60   *new_rev = SVN_INVALID_REVNUM;
61
62   /* Parse the hooks-env file (if any). */
63   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
64                                      pool, pool));
65
66   /* Run pre-commit hooks. */
67   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
68   SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool));
69
70   /* Remove any ephemeral transaction properties.  If the commit fails
71      we will attempt to restore the properties but if that fails, or
72      the process is killed, the properties will be lost. */
73   SVN_ERR(svn_fs_txn_proplist(&props, txn, pool));
74   iterpool = svn_pool_create(pool);
75   for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
76     {
77       const char *key = apr_hash_this_key(hi);
78
79       svn_pool_clear(iterpool);
80
81       if (strncmp(key, SVN_PROP_TXN_PREFIX,
82                   (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
83         {
84           SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool));
85         }
86     }
87   svn_pool_destroy(iterpool);
88
89   /* Commit. */
90   err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
91   if (! SVN_IS_VALID_REVNUM(*new_rev))
92     {
93       /* The commit failed, try to restore the ephemeral properties. */
94       iterpool = svn_pool_create(pool);
95       for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
96         {
97           const char *key = apr_hash_this_key(hi);
98           svn_string_t *val = apr_hash_this_val(hi);
99
100           svn_pool_clear(iterpool);
101
102           if (strncmp(key, SVN_PROP_TXN_PREFIX,
103                       (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
104             svn_error_clear(svn_fs_change_txn_prop(txn, key, val, iterpool));
105         }
106       svn_pool_destroy(iterpool);
107
108       return err;
109     }
110
111   /* Run post-commit hooks. */
112   if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env,
113                                            *new_rev, txn_name, pool)))
114     {
115       err2 = svn_error_create
116                (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2,
117                 _("Commit succeeded, but post-commit hook failed"));
118     }
119
120   return svn_error_compose_create(err, err2);
121 }
122
123
124 \f
125 /*** Transaction creation wrappers. ***/
126
127
128 svn_error_t *
129 svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
130                                    svn_repos_t *repos,
131                                    svn_revnum_t rev,
132                                    apr_hash_t *revprop_table,
133                                    apr_pool_t *pool)
134 {
135   apr_array_header_t *revprops;
136   const char *txn_name;
137   svn_string_t *author = svn_hash_gets(revprop_table, SVN_PROP_REVISION_AUTHOR);
138   apr_hash_t *hooks_env;
139   svn_error_t *err;
140   svn_fs_txn_t *txn;
141
142   /* Parse the hooks-env file (if any). */
143   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
144                                      pool, pool));
145
146   /* Begin the transaction, ask for the fs to do on-the-fly lock checks.
147      We fetch its name, too, so the start-commit hook can use it.  */
148   SVN_ERR(svn_fs_begin_txn2(&txn, repos->fs, rev,
149                             SVN_FS_TXN_CHECK_LOCKS, pool));
150   err = svn_fs_txn_name(&txn_name, txn, pool);
151   if (err)
152     return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
153
154   /* We pass the revision properties to the filesystem by adding them
155      as properties on the txn.  Later, when we commit the txn, these
156      properties will be copied into the newly created revision. */
157   revprops = svn_prop_hash_to_array(revprop_table, pool);
158   err = svn_repos_fs_change_txn_props(txn, revprops, pool);
159   if (err)
160     return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
161
162   /* Run start-commit hooks. */
163   err = svn_repos__hooks_start_commit(repos, hooks_env,
164                                       author ? author->data : NULL,
165                                       repos->client_capabilities, txn_name,
166                                       pool);
167   if (err)
168     return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
169
170   /* We have API promise that *TXN_P is unaffected on failure. */
171   *txn_p = txn;
172   return SVN_NO_ERROR;
173 }
174
175
176 svn_error_t *
177 svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
178                                   svn_repos_t *repos,
179                                   svn_revnum_t rev,
180                                   const char *author,
181                                   const char *log_msg,
182                                   apr_pool_t *pool)
183 {
184   apr_hash_t *revprop_table = apr_hash_make(pool);
185   if (author)
186     svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
187                   svn_string_create(author, pool));
188   if (log_msg)
189     svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
190                   svn_string_create(log_msg, pool));
191   return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
192                                             pool);
193 }
194
195 \f
196 /*** Property wrappers ***/
197
198 svn_error_t *
199 svn_repos__validate_prop(const char *name,
200                          const svn_string_t *value,
201                          apr_pool_t *pool)
202 {
203   svn_prop_kind_t kind = svn_property_kind2(name);
204
205   /* Allow deleting any property, even a property we don't allow to set. */
206   if (value == NULL)
207     return SVN_NO_ERROR;
208
209   /* Disallow setting non-regular properties. */
210   if (kind != svn_prop_regular_kind)
211     return svn_error_createf
212       (SVN_ERR_REPOS_BAD_ARGS, NULL,
213        _("Storage of non-regular property '%s' is disallowed through the "
214          "repository interface, and could indicate a bug in your client"),
215        name);
216
217   /* Validate "svn:" properties. */
218   if (svn_prop_is_svn_prop(name) && value != NULL)
219     {
220       /* Validate that translated props (e.g., svn:log) are UTF-8 with
221        * LF line endings. */
222       if (svn_prop_needs_translation(name))
223         {
224           if (!svn_utf__is_valid(value->data, value->len))
225             {
226               return svn_error_createf
227                 (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
228                  _("Cannot accept '%s' property because it is not encoded in "
229                    "UTF-8"), name);
230             }
231
232           /* Disallow inconsistent line ending style, by simply looking for
233            * carriage return characters ('\r'). */
234           if (strchr(value->data, '\r') != NULL)
235             {
236               return svn_error_createf
237                 (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
238                  _("Cannot accept non-LF line endings in '%s' property"),
239                    name);
240             }
241         }
242
243       /* "svn:date" should be a valid date. */
244       if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
245         {
246           apr_time_t temp;
247           svn_error_t *err;
248
249           err = svn_time_from_cstring(&temp, value->data, pool);
250           if (err)
251             return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE,
252                                     err, NULL);
253         }
254     }
255
256   return SVN_NO_ERROR;
257 }
258
259
260 /* Verify the mergeinfo property value VALUE and return an error if it
261  * is invalid. The PATH on which that property is set is used for error
262  * messages only.  Use SCRATCH_POOL for temporary allocations. */
263 static svn_error_t *
264 verify_mergeinfo(const svn_string_t *value,
265                  const char *path,
266                  apr_pool_t *scratch_pool)
267 {
268   svn_error_t *err;
269   svn_mergeinfo_t mergeinfo;
270
271   /* It's okay to delete svn:mergeinfo. */
272   if (value == NULL)
273     return SVN_NO_ERROR;
274
275   /* Mergeinfo is UTF-8 encoded so the number of bytes returned by strlen()
276    * should match VALUE->LEN. Prevents trailing garbage in the property. */
277   if (strlen(value->data) != value->len)
278     return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
279                              _("Commit rejected because mergeinfo on '%s' "
280                                "contains unexpected string terminator"),
281                              path);
282
283   err = svn_mergeinfo_parse(&mergeinfo, value->data, scratch_pool);
284   if (err)
285     return svn_error_createf(err->apr_err, err,
286                              _("Commit rejected because mergeinfo on '%s' "
287                                "is syntactically invalid"),
288                              path);
289   return SVN_NO_ERROR;
290 }
291
292
293 svn_error_t *
294 svn_repos_fs_change_node_prop(svn_fs_root_t *root,
295                               const char *path,
296                               const char *name,
297                               const svn_string_t *value,
298                               apr_pool_t *pool)
299 {
300   if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
301     SVN_ERR(verify_mergeinfo(value, path, pool));
302
303   /* Validate the property, then call the wrapped function. */
304   SVN_ERR(svn_repos__validate_prop(name, value, pool));
305   return svn_fs_change_node_prop(root, path, name, value, pool);
306 }
307
308
309 svn_error_t *
310 svn_repos_fs_change_txn_props(svn_fs_txn_t *txn,
311                               const apr_array_header_t *txnprops,
312                               apr_pool_t *pool)
313 {
314   int i;
315
316   for (i = 0; i < txnprops->nelts; i++)
317     {
318       svn_prop_t *prop = &APR_ARRAY_IDX(txnprops, i, svn_prop_t);
319       SVN_ERR(svn_repos__validate_prop(prop->name, prop->value, pool));
320     }
321
322   return svn_fs_change_txn_props(txn, txnprops, pool);
323 }
324
325
326 svn_error_t *
327 svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn,
328                              const char *name,
329                              const svn_string_t *value,
330                              apr_pool_t *pool)
331 {
332   apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
333   svn_prop_t prop;
334
335   prop.name = name;
336   prop.value = value;
337   APR_ARRAY_PUSH(props, svn_prop_t) = prop;
338
339   return svn_repos_fs_change_txn_props(txn, props, pool);
340 }
341
342
343 svn_error_t *
344 svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
345                               svn_revnum_t rev,
346                               const char *author,
347                               const char *name,
348                               const svn_string_t *const *old_value_p,
349                               const svn_string_t *new_value,
350                               svn_boolean_t use_pre_revprop_change_hook,
351                               svn_boolean_t use_post_revprop_change_hook,
352                               svn_repos_authz_func_t authz_read_func,
353                               void *authz_read_baton,
354                               apr_pool_t *pool)
355 {
356   svn_repos_revision_access_level_t readability;
357
358   SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
359                                           authz_read_func, authz_read_baton,
360                                           pool));
361
362   if (readability == svn_repos_revision_access_full)
363     {
364       const svn_string_t *old_value;
365       char action;
366       apr_hash_t *hooks_env;
367
368       SVN_ERR(svn_repos__validate_prop(name, new_value, pool));
369
370       /* Fetch OLD_VALUE for svn_fs_change_rev_prop2(). */
371       if (old_value_p)
372         {
373           old_value = *old_value_p;
374         }
375       else
376         {
377           /* Get OLD_VALUE anyway, in order for ACTION and OLD_VALUE arguments
378            * to the hooks to be accurate. */
379           svn_string_t *old_value2;
380
381           SVN_ERR(svn_fs_revision_prop(&old_value2, repos->fs, rev, name, pool));
382           old_value = old_value2;
383         }
384
385       /* Prepare ACTION. */
386       if (! new_value)
387         action = 'D';
388       else if (! old_value)
389         action = 'A';
390       else
391         action = 'M';
392
393       /* Parse the hooks-env file (if any, and if to be used). */
394       if (use_pre_revprop_change_hook || use_post_revprop_change_hook)
395         SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
396                                            pool, pool));
397
398       /* ### currently not passing the old_value to hooks */
399       if (use_pre_revprop_change_hook)
400         SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, hooks_env, rev,
401                                                     author, name, new_value,
402                                                     action, pool));
403
404       SVN_ERR(svn_fs_change_rev_prop2(repos->fs, rev, name,
405                                       &old_value, new_value, pool));
406
407       if (use_post_revprop_change_hook)
408         SVN_ERR(svn_repos__hooks_post_revprop_change(repos, hooks_env, rev,
409                                                      author, name, old_value,
410                                                      action, pool));
411     }
412   else  /* rev is either unreadable or only partially readable */
413     {
414       return svn_error_createf
415         (SVN_ERR_AUTHZ_UNREADABLE, NULL,
416          _("Write denied:  not authorized to read all of revision %ld"), rev);
417     }
418
419   return SVN_NO_ERROR;
420 }
421
422
423 svn_error_t *
424 svn_repos_fs_revision_prop(svn_string_t **value_p,
425                            svn_repos_t *repos,
426                            svn_revnum_t rev,
427                            const char *propname,
428                            svn_repos_authz_func_t authz_read_func,
429                            void *authz_read_baton,
430                            apr_pool_t *pool)
431 {
432   svn_repos_revision_access_level_t readability;
433
434   SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
435                                           authz_read_func, authz_read_baton,
436                                           pool));
437
438   if (readability == svn_repos_revision_access_none)
439     {
440       /* Property?  What property? */
441       *value_p = NULL;
442     }
443   else if (readability == svn_repos_revision_access_partial)
444     {
445       /* Only svn:author and svn:date are fetchable. */
446       if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) != 0)
447           && (strcmp(propname, SVN_PROP_REVISION_DATE) != 0))
448         *value_p = NULL;
449
450       else
451         SVN_ERR(svn_fs_revision_prop(value_p, repos->fs,
452                                      rev, propname, pool));
453     }
454   else /* wholly readable revision */
455     {
456       SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, rev, propname, pool));
457     }
458
459   return SVN_NO_ERROR;
460 }
461
462
463
464 svn_error_t *
465 svn_repos_fs_revision_proplist(apr_hash_t **table_p,
466                                svn_repos_t *repos,
467                                svn_revnum_t rev,
468                                svn_repos_authz_func_t authz_read_func,
469                                void *authz_read_baton,
470                                apr_pool_t *pool)
471 {
472   svn_repos_revision_access_level_t readability;
473
474   SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
475                                           authz_read_func, authz_read_baton,
476                                           pool));
477
478   if (readability == svn_repos_revision_access_none)
479     {
480       /* Return an empty hash. */
481       *table_p = apr_hash_make(pool);
482     }
483   else if (readability == svn_repos_revision_access_partial)
484     {
485       apr_hash_t *tmphash;
486       svn_string_t *value;
487
488       /* Produce two property hashtables, both in POOL. */
489       SVN_ERR(svn_fs_revision_proplist(&tmphash, repos->fs, rev, pool));
490       *table_p = apr_hash_make(pool);
491
492       /* If they exist, we only copy svn:author and svn:date into the
493          'real' hashtable being returned. */
494       value = svn_hash_gets(tmphash, SVN_PROP_REVISION_AUTHOR);
495       if (value)
496         svn_hash_sets(*table_p, SVN_PROP_REVISION_AUTHOR, value);
497
498       value = svn_hash_gets(tmphash, SVN_PROP_REVISION_DATE);
499       if (value)
500         svn_hash_sets(*table_p, SVN_PROP_REVISION_DATE, value);
501     }
502   else /* wholly readable revision */
503     {
504       SVN_ERR(svn_fs_revision_proplist(table_p, repos->fs, rev, pool));
505     }
506
507   return SVN_NO_ERROR;
508 }
509
510 struct lock_many_baton_t {
511   svn_boolean_t need_lock;
512   apr_array_header_t *paths;
513   svn_fs_lock_callback_t lock_callback;
514   void *lock_baton;
515   svn_error_t *cb_err;
516   apr_pool_t *pool;
517 };
518
519 /* Implements svn_fs_lock_callback_t.  Used by svn_repos_fs_lock_many
520    and svn_repos_fs_unlock_many to record the paths for use by post-
521    hooks, forward to the supplied callback and record any callback
522    error. */
523 static svn_error_t *
524 lock_many_cb(void *lock_baton,
525              const char *path,
526              const svn_lock_t *lock,
527              svn_error_t *fs_err,
528              apr_pool_t *pool)
529 {
530   struct lock_many_baton_t *b = lock_baton;
531
532   if (!b->cb_err && b->lock_callback)
533     b->cb_err = b->lock_callback(b->lock_baton, path, lock, fs_err, pool);
534
535   if ((b->need_lock && lock) || (!b->need_lock && !fs_err))
536     APR_ARRAY_PUSH(b->paths, const char *) = apr_pstrdup(b->pool, path);
537
538   return SVN_NO_ERROR;
539 }
540
541 svn_error_t *
542 svn_repos_fs_lock_many(svn_repos_t *repos,
543                        apr_hash_t *targets,
544                        const char *comment,
545                        svn_boolean_t is_dav_comment,
546                        apr_time_t expiration_date,
547                        svn_boolean_t steal_lock,
548                        svn_fs_lock_callback_t lock_callback,
549                        void *lock_baton,
550                        apr_pool_t *result_pool,
551                        apr_pool_t *scratch_pool)
552 {
553   svn_error_t *err, *cb_err = SVN_NO_ERROR;
554   svn_fs_access_t *access_ctx = NULL;
555   const char *username = NULL;
556   apr_hash_t *hooks_env;
557   apr_hash_t *pre_targets = apr_hash_make(scratch_pool);
558   apr_hash_index_t *hi;
559   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
560   struct lock_many_baton_t baton;
561
562   if (!apr_hash_count(targets))
563     return SVN_NO_ERROR;
564
565   /* Parse the hooks-env file (if any). */
566   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
567                                      scratch_pool, scratch_pool));
568
569   SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
570   if (access_ctx)
571     SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
572
573   if (! username)
574     return svn_error_create
575       (SVN_ERR_FS_NO_USER, NULL,
576        "Cannot lock path, no authenticated username available.");
577
578   /* Run pre-lock hook.  This could throw error, preventing
579      svn_fs_lock2() from happening for that path. */
580   for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
581     {
582       const char *new_token;
583       svn_fs_lock_target_t *target;
584       const char *path = apr_hash_this_key(hi);
585
586       svn_pool_clear(iterpool);
587
588       err = svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
589                                       username, comment, steal_lock, iterpool);
590       if (err)
591         {
592           if (!cb_err && lock_callback)
593             cb_err = lock_callback(lock_baton, path, NULL, err, iterpool);
594           svn_error_clear(err);
595
596           continue;
597         }
598
599       target = apr_hash_this_val(hi);
600       if (*new_token)
601         svn_fs_lock_target_set_token(target, new_token);
602       svn_hash_sets(pre_targets, path, target);
603     }
604
605   if (!apr_hash_count(pre_targets))
606     return svn_error_trace(cb_err);
607
608   baton.need_lock = TRUE;
609   baton.paths = apr_array_make(scratch_pool, apr_hash_count(pre_targets),
610                                sizeof(const char *));
611   baton.lock_callback = lock_callback;
612   baton.lock_baton = lock_baton;
613   baton.cb_err = cb_err;
614   baton.pool = scratch_pool;
615
616   err = svn_fs_lock_many(repos->fs, pre_targets, comment,
617                          is_dav_comment, expiration_date, steal_lock,
618                          lock_many_cb, &baton, result_pool, iterpool);
619
620   /* If there are locks run the post-lock even if there is an error. */
621   if (baton.paths->nelts)
622     {
623       svn_error_t *perr = svn_repos__hooks_post_lock(repos, hooks_env,
624                                                      baton.paths, username,
625                                                      iterpool);
626       if (perr)
627         {
628           perr = svn_error_create(SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, perr,
629                             _("Locking succeeded, but post-lock hook failed"));
630           err = svn_error_compose_create(err, perr);
631         }
632     }
633
634   svn_pool_destroy(iterpool);
635
636   if (err && cb_err)
637     svn_error_compose(err, cb_err);
638   else if (!err)
639     err = cb_err;
640
641   return svn_error_trace(err);
642 }
643
644 struct lock_baton_t {
645   const svn_lock_t *lock;
646   svn_error_t *fs_err;
647 };
648
649 /* Implements svn_fs_lock_callback_t.  Used by svn_repos_fs_lock and
650    svn_repos_fs_unlock to record the lock and error from
651    svn_repos_fs_lock_many and svn_repos_fs_unlock_many. */
652 static svn_error_t *
653 lock_cb(void *lock_baton,
654         const char *path,
655         const svn_lock_t *lock,
656         svn_error_t *fs_err,
657         apr_pool_t *pool)
658 {
659   struct lock_baton_t *b = lock_baton;
660
661   b->lock = lock;
662   b->fs_err = svn_error_dup(fs_err);
663
664   return SVN_NO_ERROR;
665 }
666
667 svn_error_t *
668 svn_repos_fs_lock(svn_lock_t **lock,
669                   svn_repos_t *repos,
670                   const char *path,
671                   const char *token,
672                   const char *comment,
673                   svn_boolean_t is_dav_comment,
674                   apr_time_t expiration_date,
675                   svn_revnum_t current_rev,
676                   svn_boolean_t steal_lock,
677                   apr_pool_t *pool)
678 {
679   apr_hash_t *targets = apr_hash_make(pool);
680   svn_fs_lock_target_t *target = svn_fs_lock_target_create(token, current_rev,
681                                                            pool);
682   svn_error_t *err;
683   struct lock_baton_t baton = {0};
684
685   svn_hash_sets(targets, path, target);
686
687   err = svn_repos_fs_lock_many(repos, targets, comment, is_dav_comment,
688                                expiration_date, steal_lock, lock_cb, &baton,
689                                pool, pool);
690
691   if (baton.lock)
692     *lock = (svn_lock_t*)baton.lock;
693
694   if (err && baton.fs_err)
695     svn_error_compose(err, baton.fs_err);
696   else if (!err)
697     err = baton.fs_err;
698
699   return svn_error_trace(err);
700 }
701
702
703 svn_error_t *
704 svn_repos_fs_unlock_many(svn_repos_t *repos,
705                          apr_hash_t *targets,
706                          svn_boolean_t break_lock,
707                          svn_fs_lock_callback_t lock_callback,
708                          void *lock_baton,
709                          apr_pool_t *result_pool,
710                          apr_pool_t *scratch_pool)
711 {
712   svn_error_t *err, *cb_err = SVN_NO_ERROR;
713   svn_fs_access_t *access_ctx = NULL;
714   const char *username = NULL;
715   apr_hash_t *hooks_env;
716   apr_hash_t *pre_targets = apr_hash_make(scratch_pool);
717   apr_hash_index_t *hi;
718   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
719   struct lock_many_baton_t baton;
720
721   if (!apr_hash_count(targets))
722     return SVN_NO_ERROR;
723
724   /* Parse the hooks-env file (if any). */
725   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
726                                      scratch_pool, scratch_pool));
727
728   SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
729   if (access_ctx)
730     SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
731
732   if (! break_lock && ! username)
733     return svn_error_create
734       (SVN_ERR_FS_NO_USER, NULL,
735        _("Cannot unlock, no authenticated username available"));
736
737   /* Run pre-unlock hook.  This could throw error, preventing
738      svn_fs_unlock_many() from happening for that path. */
739   for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
740     {
741       const char *path = apr_hash_this_key(hi);
742       const char *token = apr_hash_this_val(hi);
743
744       svn_pool_clear(iterpool);
745
746       err = svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
747                                         break_lock, iterpool);
748       if (err)
749         {
750           if (!cb_err && lock_callback)
751             cb_err = lock_callback(lock_baton, path, NULL, err, iterpool);
752           svn_error_clear(err);
753
754           continue;
755         }
756
757       svn_hash_sets(pre_targets, path, token);
758     }
759
760   if (!apr_hash_count(pre_targets))
761     return svn_error_trace(cb_err);
762
763   baton.need_lock = FALSE;
764   baton.paths = apr_array_make(scratch_pool, apr_hash_count(pre_targets),
765                                sizeof(const char *));
766   baton.lock_callback = lock_callback;
767   baton.lock_baton = lock_baton;
768   baton.cb_err = cb_err;
769   baton.pool = scratch_pool;
770
771   err = svn_fs_unlock_many(repos->fs, pre_targets, break_lock,
772                            lock_many_cb, &baton, result_pool, iterpool);
773
774   /* If there are 'unlocks' run the post-unlock even if there is an error. */
775   if (baton.paths->nelts)
776     {
777       svn_error_t *perr = svn_repos__hooks_post_unlock(repos, hooks_env,
778                                                        baton.paths,
779                                                        username, iterpool);
780       if (perr)
781         {
782           perr = svn_error_create(SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, perr,
783                            _("Unlock succeeded, but post-unlock hook failed"));
784           err = svn_error_compose_create(err, perr);
785         }
786     }
787
788   svn_pool_destroy(iterpool);
789
790   if (err && cb_err)
791     svn_error_compose(err, cb_err);
792   else if (!err)
793     err = cb_err;
794
795   return svn_error_trace(err);
796 }
797
798 svn_error_t *
799 svn_repos_fs_unlock(svn_repos_t *repos,
800                     const char *path,
801                     const char *token,
802                     svn_boolean_t break_lock,
803                     apr_pool_t *pool)
804 {
805   apr_hash_t *targets = apr_hash_make(pool);
806   svn_error_t *err;
807   struct lock_baton_t baton = {0};
808
809   if (!token)
810     token = "";
811
812   svn_hash_sets(targets, path, token);
813
814   err = svn_repos_fs_unlock_many(repos, targets, break_lock, lock_cb, &baton,
815                                  pool, pool);
816
817   if (err && baton.fs_err)
818     svn_error_compose(err, baton.fs_err);
819   else if (!err)
820     err = baton.fs_err;
821
822   return svn_error_trace(err);
823 }
824
825
826 struct get_locks_baton_t
827 {
828   svn_fs_t *fs;
829   svn_fs_root_t *head_root;
830   svn_repos_authz_func_t authz_read_func;
831   void *authz_read_baton;
832   apr_hash_t *locks;
833 };
834
835
836 /* This implements the svn_fs_get_locks_callback_t interface. */
837 static svn_error_t *
838 get_locks_callback(void *baton,
839                    svn_lock_t *lock,
840                    apr_pool_t *pool)
841 {
842   struct get_locks_baton_t *b = baton;
843   svn_boolean_t readable = TRUE;
844   apr_pool_t *hash_pool = apr_hash_pool_get(b->locks);
845
846   /* If there's auth to deal with, deal with it. */
847   if (b->authz_read_func)
848     SVN_ERR(b->authz_read_func(&readable, b->head_root, lock->path,
849                                b->authz_read_baton, pool));
850
851   /* If we can read this lock path, add the lock to the return hash. */
852   if (readable)
853     svn_hash_sets(b->locks, apr_pstrdup(hash_pool, lock->path),
854                   svn_lock_dup(lock, hash_pool));
855
856   return SVN_NO_ERROR;
857 }
858
859
860 svn_error_t *
861 svn_repos_fs_get_locks2(apr_hash_t **locks,
862                         svn_repos_t *repos,
863                         const char *path,
864                         svn_depth_t depth,
865                         svn_repos_authz_func_t authz_read_func,
866                         void *authz_read_baton,
867                         apr_pool_t *pool)
868 {
869   apr_hash_t *all_locks = apr_hash_make(pool);
870   svn_revnum_t head_rev;
871   struct get_locks_baton_t baton;
872
873   SVN_ERR_ASSERT((depth == svn_depth_empty) ||
874                  (depth == svn_depth_files) ||
875                  (depth == svn_depth_immediates) ||
876                  (depth == svn_depth_infinity));
877
878   SVN_ERR(svn_fs_youngest_rev(&head_rev, repos->fs, pool));
879
880   /* Populate our callback baton. */
881   baton.fs = repos->fs;
882   baton.locks = all_locks;
883   baton.authz_read_func = authz_read_func;
884   baton.authz_read_baton = authz_read_baton;
885   SVN_ERR(svn_fs_revision_root(&(baton.head_root), repos->fs,
886                                head_rev, pool));
887
888   /* Get all the locks. */
889   SVN_ERR(svn_fs_get_locks2(repos->fs, path, depth,
890                             get_locks_callback, &baton, pool));
891
892   *locks = baton.locks;
893   return SVN_NO_ERROR;
894 }
895
896
897 svn_error_t *
898 svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
899                            svn_repos_t *repos,
900                            const apr_array_header_t *paths,
901                            svn_revnum_t rev,
902                            svn_mergeinfo_inheritance_t inherit,
903                            svn_boolean_t include_descendants,
904                            svn_repos_authz_func_t authz_read_func,
905                            void *authz_read_baton,
906                            apr_pool_t *pool)
907 {
908   /* Here we cast away 'const', but won't try to write through this pointer
909    * without first allocating a new array. */
910   apr_array_header_t *readable_paths = (apr_array_header_t *) paths;
911   svn_fs_root_t *root;
912   apr_pool_t *iterpool = svn_pool_create(pool);
913
914   if (!SVN_IS_VALID_REVNUM(rev))
915     SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, pool));
916   SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, pool));
917
918   /* Filter out unreadable paths before divining merge tracking info. */
919   if (authz_read_func)
920     {
921       int i;
922
923       for (i = 0; i < paths->nelts; i++)
924         {
925           svn_boolean_t readable;
926           const char *path = APR_ARRAY_IDX(paths, i, char *);
927           svn_pool_clear(iterpool);
928           SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton,
929                                   iterpool));
930           if (readable && readable_paths != paths)
931             APR_ARRAY_PUSH(readable_paths, const char *) = path;
932           else if (!readable && readable_paths == paths)
933             {
934               /* Requested paths differ from readable paths.  Fork
935                  list of readable paths from requested paths. */
936               int j;
937               readable_paths = apr_array_make(pool, paths->nelts - 1,
938                                               sizeof(char *));
939               for (j = 0; j < i; j++)
940                 {
941                   path = APR_ARRAY_IDX(paths, j, char *);
942                   APR_ARRAY_PUSH(readable_paths, const char *) = path;
943                 }
944             }
945         }
946     }
947
948   /* We consciously do not perform authz checks on the paths returned
949      in *MERGEINFO, avoiding massive authz overhead which would allow
950      us to protect the name of where a change was merged from, but not
951      the change itself. */
952   /* ### TODO(reint): ... but how about descendant merged-to paths? */
953   if (readable_paths->nelts > 0)
954     SVN_ERR(svn_fs_get_mergeinfo2(mergeinfo, root, readable_paths, inherit,
955                                   include_descendants, TRUE, pool, pool));
956   else
957     *mergeinfo = apr_hash_make(pool);
958
959   svn_pool_destroy(iterpool);
960   return SVN_NO_ERROR;
961 }
962
963 struct pack_notify_baton
964 {
965   svn_repos_notify_func_t notify_func;
966   void *notify_baton;
967 };
968
969 /* Implements svn_fs_pack_notify_t. */
970 static svn_error_t *
971 pack_notify_func(void *baton,
972                  apr_int64_t shard,
973                  svn_fs_pack_notify_action_t pack_action,
974                  apr_pool_t *pool)
975 {
976   struct pack_notify_baton *pnb = baton;
977   svn_repos_notify_t *notify;
978
979   /* Simple conversion works for these values. */
980   SVN_ERR_ASSERT(pack_action >= svn_fs_pack_notify_start
981                  && pack_action <= svn_fs_pack_notify_end_revprop);
982
983   notify = svn_repos_notify_create(pack_action
984                                    + svn_repos_notify_pack_shard_start
985                                    - svn_fs_pack_notify_start,
986                                    pool);
987   notify->shard = shard;
988   pnb->notify_func(pnb->notify_baton, notify, pool);
989
990   return SVN_NO_ERROR;
991 }
992
993 svn_error_t *
994 svn_repos_fs_pack2(svn_repos_t *repos,
995                    svn_repos_notify_func_t notify_func,
996                    void *notify_baton,
997                    svn_cancel_func_t cancel_func,
998                    void *cancel_baton,
999                    apr_pool_t *pool)
1000 {
1001   struct pack_notify_baton pnb;
1002
1003   pnb.notify_func = notify_func;
1004   pnb.notify_baton = notify_baton;
1005
1006   return svn_fs_pack(repos->db_path,
1007                      notify_func ? pack_notify_func : NULL,
1008                      notify_func ? &pnb : NULL,
1009                      cancel_func, cancel_baton, pool);
1010 }
1011
1012 svn_error_t *
1013 svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p,
1014                                  svn_fs_root_t *root,
1015                                  const char *path,
1016                                  const char *propname,
1017                                  svn_repos_authz_func_t authz_read_func,
1018                                  void *authz_read_baton,
1019                                  apr_pool_t *result_pool,
1020                                  apr_pool_t *scratch_pool)
1021 {
1022   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1023   apr_array_header_t *inherited_props;
1024   const char *parent_path = path;
1025
1026   inherited_props = apr_array_make(result_pool, 1,
1027                                    sizeof(svn_prop_inherited_item_t *));
1028   while (!(parent_path[0] == '/' && parent_path[1] == '\0'))
1029     {
1030       svn_boolean_t allowed = TRUE;
1031       apr_hash_t *parent_properties = NULL;
1032
1033       svn_pool_clear(iterpool);
1034       parent_path = svn_fspath__dirname(parent_path, scratch_pool);
1035
1036       if (authz_read_func)
1037         SVN_ERR(authz_read_func(&allowed, root, parent_path,
1038                                 authz_read_baton, iterpool));
1039       if (allowed)
1040         {
1041           if (propname)
1042             {
1043               svn_string_t *propval;
1044
1045               SVN_ERR(svn_fs_node_prop(&propval, root, parent_path, propname,
1046                                        result_pool));
1047               if (propval)
1048                 {
1049                   parent_properties = apr_hash_make(result_pool);
1050                   svn_hash_sets(parent_properties, propname, propval);
1051                 }
1052             }
1053           else
1054             {
1055               SVN_ERR(svn_fs_node_proplist(&parent_properties, root,
1056                                            parent_path, result_pool));
1057             }
1058
1059           if (parent_properties && apr_hash_count(parent_properties))
1060             {
1061               svn_prop_inherited_item_t *i_props =
1062                 apr_pcalloc(result_pool, sizeof(*i_props));
1063               i_props->path_or_url =
1064                 apr_pstrdup(result_pool, parent_path + 1);
1065               i_props->prop_hash = parent_properties;
1066               /* Build the output array in depth-first order. */
1067               svn_sort__array_insert(inherited_props, &i_props, 0);
1068             }
1069         }
1070     }
1071
1072   svn_pool_destroy(iterpool);
1073
1074   *inherited_props_p = inherited_props;
1075   return SVN_NO_ERROR;
1076 }
1077 \f
1078 /*
1079  * vim:ts=4:sw=2:expandtab:tw=80:fo=tcroq
1080  * vim:isk=a-z,A-Z,48-57,_,.,-,>
1081  * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0
1082  */