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