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