fs-wrap.c revision 299742
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
44/*** Commit wrappers ***/
45
46svn_error_t *
47svn_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
125/*** Transaction creation wrappers. ***/
126
127
128svn_error_t *
129svn_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
176svn_error_t *
177svn_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
196/*** Property wrappers ***/
197
198svn_error_t *
199svn_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. */
263static svn_error_t *
264verify_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
293svn_error_t *
294svn_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
309svn_error_t *
310svn_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
326svn_error_t *
327svn_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
343svn_error_t *
344svn_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
423svn_error_t *
424svn_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
464svn_error_t *
465svn_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
510struct 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. */
523static svn_error_t *
524lock_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
541svn_error_t *
542svn_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
644struct 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. */
652static svn_error_t *
653lock_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
667svn_error_t *
668svn_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
703svn_error_t *
704svn_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
798svn_error_t *
799svn_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
826struct 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. */
837static svn_error_t *
838get_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
860svn_error_t *
861svn_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
897svn_error_t *
898svn_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
963struct 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. */
970static svn_error_t *
971pack_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
993svn_error_t *
994svn_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
1012svn_error_t *
1013svn_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
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 */
1083