1/* hotcopys.c --- FS hotcopy functionality for FSX
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#include "svn_pools.h"
23#include "svn_path.h"
24#include "svn_dirent_uri.h"
25
26#include "fs_x.h"
27#include "hotcopy.h"
28#include "util.h"
29#include "revprops.h"
30#include "rep-cache.h"
31#include "transaction.h"
32#include "recovery.h"
33
34#include "../libsvn_fs/fs-loader.h"
35
36#include "svn_private_config.h"
37
38/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
39 * the destination and do not differ in terms of kind, size, and mtime.
40 * Set *SKIPPED_P to FALSE only if the file was copied, do not change
41 * the value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not
42 * required. */
43static svn_error_t *
44hotcopy_io_dir_file_copy(svn_boolean_t *skipped_p,
45                         const char *src_path,
46                         const char *dst_path,
47                         const char *file,
48                         apr_pool_t *scratch_pool)
49{
50  const svn_io_dirent2_t *src_dirent;
51  const svn_io_dirent2_t *dst_dirent;
52  const char *src_target;
53  const char *dst_target;
54
55  /* Does the destination already exist? If not, we must copy it. */
56  dst_target = svn_dirent_join(dst_path, file, scratch_pool);
57  SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
58                              scratch_pool, scratch_pool));
59  if (dst_dirent->kind != svn_node_none)
60    {
61      /* If the destination's stat information indicates that the file
62       * is equal to the source, don't bother copying the file again. */
63      src_target = svn_dirent_join(src_path, file, scratch_pool);
64      SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
65                                  scratch_pool, scratch_pool));
66      if (src_dirent->kind == dst_dirent->kind &&
67          src_dirent->special == dst_dirent->special &&
68          src_dirent->filesize == dst_dirent->filesize &&
69          src_dirent->mtime <= dst_dirent->mtime)
70        return SVN_NO_ERROR;
71    }
72
73  if (skipped_p)
74    *skipped_p = FALSE;
75
76  return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
77                                              scratch_pool));
78}
79
80/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
81 * NAME is in the internal encoding used by APR; PARENT is in
82 * UTF-8 and in internal (not local) style.
83 *
84 * Use PARENT only for generating an error string if the conversion
85 * fails because NAME could not be represented in UTF-8.  In that
86 * case, return a two-level error in which the outer error's message
87 * mentions PARENT, but the inner error's message does not mention
88 * NAME (except possibly in hex) since NAME may not be printable.
89 * Such a compound error at least allows the user to go looking in the
90 * right directory for the problem.
91 *
92 * If there is any other error, just return that error directly.
93 *
94 * If there is any error, the effect on *NAME_P is undefined.
95 *
96 * *NAME_P and NAME may refer to the same storage.
97 */
98static svn_error_t *
99entry_name_to_utf8(const char **name_p,
100                   const char *name,
101                   const char *parent,
102                   apr_pool_t *result_pool)
103{
104  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, result_pool);
105  if (err && err->apr_err == APR_EINVAL)
106    {
107      return svn_error_createf(err->apr_err, err,
108                               _("Error converting entry "
109                                 "in directory '%s' to UTF-8"),
110                               svn_dirent_local_style(parent, result_pool));
111    }
112  return err;
113}
114
115/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
116 * exist in the destination and do not differ from the source in terms of
117 * kind, size, and mtime. Set *SKIPPED_P to FALSE only if at least one
118 * file was copied, do not change the value in *SKIPPED_P otherwise.
119 * SKIPPED_P may be NULL if not required. */
120static svn_error_t *
121hotcopy_io_copy_dir_recursively(svn_boolean_t *skipped_p,
122                                const char *src,
123                                const char *dst_parent,
124                                const char *dst_basename,
125                                svn_boolean_t copy_perms,
126                                svn_cancel_func_t cancel_func,
127                                void *cancel_baton,
128                                apr_pool_t *scratch_pool)
129{
130  svn_node_kind_t kind;
131  apr_status_t status;
132  const char *dst_path;
133  apr_dir_t *this_dir;
134  apr_finfo_t this_entry;
135  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
136
137  /* Make a subpool for recursion */
138  apr_pool_t *subpool = svn_pool_create(scratch_pool);
139
140  /* The 'dst_path' is simply dst_parent/dst_basename */
141  dst_path = svn_dirent_join(dst_parent, dst_basename, scratch_pool);
142
143  /* Sanity checks:  SRC and DST_PARENT are directories, and
144     DST_BASENAME doesn't already exist in DST_PARENT. */
145  SVN_ERR(svn_io_check_path(src, &kind, subpool));
146  if (kind != svn_node_dir)
147    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
148                             _("Source '%s' is not a directory"),
149                             svn_dirent_local_style(src, scratch_pool));
150
151  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
152  if (kind != svn_node_dir)
153    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
154                             _("Destination '%s' is not a directory"),
155                             svn_dirent_local_style(dst_parent,
156                                                    scratch_pool));
157
158  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
159
160  /* Create the new directory. */
161  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
162  SVN_ERR(svn_io_make_dir_recursively(dst_path, scratch_pool));
163
164  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
165  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
166
167  for (status = apr_dir_read(&this_entry, flags, this_dir);
168       status == APR_SUCCESS;
169       status = apr_dir_read(&this_entry, flags, this_dir))
170    {
171      if ((this_entry.name[0] == '.')
172          && ((this_entry.name[1] == '\0')
173              || ((this_entry.name[1] == '.')
174                  && (this_entry.name[2] == '\0'))))
175        {
176          continue;
177        }
178      else
179        {
180          const char *entryname_utf8;
181
182          if (cancel_func)
183            SVN_ERR(cancel_func(cancel_baton));
184
185          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
186                                     src, subpool));
187          if (this_entry.filetype == APR_REG) /* regular file */
188            {
189              SVN_ERR(hotcopy_io_dir_file_copy(skipped_p, src, dst_path,
190                                               entryname_utf8, subpool));
191            }
192          else if (this_entry.filetype == APR_LNK) /* symlink */
193            {
194              const char *src_target = svn_dirent_join(src, entryname_utf8,
195                                                       subpool);
196              const char *dst_target = svn_dirent_join(dst_path,
197                                                       entryname_utf8,
198                                                       subpool);
199              SVN_ERR(svn_io_copy_link(src_target, dst_target,
200                                       subpool));
201            }
202          else if (this_entry.filetype == APR_DIR) /* recurse */
203            {
204              const char *src_target;
205
206              /* Prevent infinite recursion by filtering off our
207                 newly created destination path. */
208              if (strcmp(src, dst_parent) == 0
209                  && strcmp(entryname_utf8, dst_basename) == 0)
210                continue;
211
212              src_target = svn_dirent_join(src, entryname_utf8, subpool);
213              SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
214                                                      src_target,
215                                                      dst_path,
216                                                      entryname_utf8,
217                                                      copy_perms,
218                                                      cancel_func,
219                                                      cancel_baton,
220                                                      subpool));
221            }
222          /* ### support other APR node types someday?? */
223
224        }
225    }
226
227  if (! (APR_STATUS_IS_ENOENT(status)))
228    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
229                              svn_dirent_local_style(src, scratch_pool));
230
231  status = apr_dir_close(this_dir);
232  if (status)
233    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
234                              svn_dirent_local_style(src, scratch_pool));
235
236  /* Free any memory used by recursion */
237  svn_pool_destroy(subpool);
238
239  return SVN_NO_ERROR;
240}
241
242/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
243 * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
244 * Set *SKIPPED_P to FALSE only if the file was copied, do not change the
245 * value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not required.
246 * If PROPS is set, copy the revprops file, otherwise copy the rev data file.
247 * Use SCRATCH_POOL for temporary allocations. */
248static svn_error_t *
249hotcopy_copy_shard_file(svn_boolean_t *skipped_p,
250                        const char *src_subdir,
251                        const char *dst_subdir,
252                        svn_revnum_t rev,
253                        int max_files_per_dir,
254                        svn_boolean_t props,
255                        apr_pool_t *scratch_pool)
256{
257  const char *src_subdir_shard = src_subdir,
258             *dst_subdir_shard = dst_subdir;
259
260  const char *shard = apr_psprintf(scratch_pool, "%ld",
261                                   rev / max_files_per_dir);
262  src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
263  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
264
265  if (rev % max_files_per_dir == 0)
266    {
267      SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
268      SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
269                                scratch_pool));
270    }
271
272  SVN_ERR(hotcopy_io_dir_file_copy(skipped_p,
273                                   src_subdir_shard, dst_subdir_shard,
274                                   apr_psprintf(scratch_pool, "%c%ld",
275                                                props ? 'p' : 'r',
276                                                rev),
277                                   scratch_pool));
278  return SVN_NO_ERROR;
279}
280
281
282/* Copy a packed shard containing revision REV, and which contains
283 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
284 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
285 * Do not re-copy data which already exists in DST_FS.
286 * Set *SKIPPED_P to FALSE only if at least one part of the shard
287 * was copied, do not change the value in *SKIPPED_P otherwise.
288 * SKIPPED_P may be NULL if not required.
289 * Use SCRATCH_POOL for temporary allocations. */
290static svn_error_t *
291hotcopy_copy_packed_shard(svn_boolean_t *skipped_p,
292                          svn_revnum_t *dst_min_unpacked_rev,
293                          svn_fs_t *src_fs,
294                          svn_fs_t *dst_fs,
295                          svn_revnum_t rev,
296                          int max_files_per_dir,
297                          apr_pool_t *scratch_pool)
298{
299  const char *src_subdir;
300  const char *dst_subdir;
301  const char *packed_shard;
302  const char *src_subdir_packed_shard;
303
304  /* Copy the packed shard. */
305  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
306  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
307  packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
308                              rev / max_files_per_dir);
309  src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
310                                            scratch_pool);
311  SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p, src_subdir_packed_shard,
312                                          dst_subdir, packed_shard,
313                                          TRUE /* copy_perms */,
314                                          NULL /* cancel_func */, NULL,
315                                          scratch_pool));
316
317  /* If necessary, update the min-unpacked rev file in the hotcopy. */
318  if (*dst_min_unpacked_rev < rev + max_files_per_dir)
319    {
320      *dst_min_unpacked_rev = rev + max_files_per_dir;
321      SVN_ERR(svn_fs_x__write_min_unpacked_rev(dst_fs,
322                                               *dst_min_unpacked_rev,
323                                               scratch_pool));
324    }
325
326  return SVN_NO_ERROR;
327}
328
329/* Remove file PATH, if it exists - even if it is read-only.
330 * Use SCRATCH_POOL for temporary allocations. */
331static svn_error_t *
332hotcopy_remove_file(const char *path,
333                    apr_pool_t *scratch_pool)
334{
335  /* Make the rev file writable and remove it. */
336  SVN_ERR(svn_io_set_file_read_write(path, TRUE, scratch_pool));
337  SVN_ERR(svn_io_remove_file2(path, TRUE, scratch_pool));
338
339  return SVN_NO_ERROR;
340}
341
342/* Verify that DST_FS is a suitable destination for an incremental
343 * hotcopy from SRC_FS. */
344static svn_error_t *
345hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
346                                        svn_fs_t *dst_fs)
347{
348  svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
349  svn_fs_x__data_t *dst_ffd = dst_fs->fsap_data;
350
351  /* We only support incremental hotcopy between the same format. */
352  if (src_ffd->format != dst_ffd->format)
353    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
354      _("The FSX format (%d) of the hotcopy source does not match the "
355        "FSX format (%d) of the hotcopy destination; please upgrade "
356        "both repositories to the same format"),
357      src_ffd->format, dst_ffd->format);
358
359  /* Make sure the UUID of source and destination match up.
360   * We don't want to copy over a different repository. */
361  if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
362    return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
363                            _("The UUID of the hotcopy source does "
364                              "not match the UUID of the hotcopy "
365                              "destination"));
366
367  /* Also require same shard size. */
368  if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
369    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
370                            _("The sharding layout configuration "
371                              "of the hotcopy source does not match "
372                              "the sharding layout configuration of "
373                              "the hotcopy destination"));
374  return SVN_NO_ERROR;
375}
376
377/* Copy the revision and revprop files (possibly sharded / packed) from
378 * SRC_FS to DST_FS.  Do not re-copy data which already exists in DST_FS.
379 * When copying packed or unpacked shards, checkpoint the result in DST_FS
380 * for every shard by updating the 'current' file if necessary.  Assume
381 * the >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT filesystem format without
382 * global next-ID counters.  Indicate progress via the optional NOTIFY_FUNC
383 * callback using NOTIFY_BATON.  Use SCRATCH_POOL for temporary allocations.
384 */
385static svn_error_t *
386hotcopy_revisions(svn_fs_t *src_fs,
387                  svn_fs_t *dst_fs,
388                  svn_revnum_t src_youngest,
389                  svn_revnum_t dst_youngest,
390                  svn_boolean_t incremental,
391                  const char *src_revs_dir,
392                  const char *dst_revs_dir,
393                  svn_fs_hotcopy_notify_t notify_func,
394                  void* notify_baton,
395                  svn_cancel_func_t cancel_func,
396                  void* cancel_baton,
397                  apr_pool_t *scratch_pool)
398{
399  svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
400  int max_files_per_dir = src_ffd->max_files_per_dir;
401  svn_revnum_t src_min_unpacked_rev;
402  svn_revnum_t dst_min_unpacked_rev;
403  svn_revnum_t rev;
404  apr_pool_t *iterpool;
405
406  /* Copy the min unpacked rev, and read its value. */
407  SVN_ERR(svn_fs_x__read_min_unpacked_rev(&src_min_unpacked_rev, src_fs,
408                                          scratch_pool));
409  SVN_ERR(svn_fs_x__read_min_unpacked_rev(&dst_min_unpacked_rev, dst_fs,
410                                          scratch_pool));
411
412  /* We only support packs coming from the hotcopy source.
413    * The destination should not be packed independently from
414    * the source. This also catches the case where users accidentally
415    * swap the source and destination arguments. */
416  if (src_min_unpacked_rev < dst_min_unpacked_rev)
417    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
418                             _("The hotcopy destination already contains "
419                               "more packed revisions (%lu) than the "
420                               "hotcopy source contains (%lu)"),
421                             dst_min_unpacked_rev - 1,
422                             src_min_unpacked_rev - 1);
423
424  SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
425                               PATH_MIN_UNPACKED_REV, scratch_pool));
426
427  if (cancel_func)
428    SVN_ERR(cancel_func(cancel_baton));
429
430  /*
431   * Copy the necessary rev files.
432   */
433
434  iterpool = svn_pool_create(scratch_pool);
435  /* First, copy packed shards. */
436  for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
437    {
438      svn_boolean_t skipped = TRUE;
439      svn_revnum_t pack_end_rev;
440
441      svn_pool_clear(iterpool);
442
443      if (cancel_func)
444        SVN_ERR(cancel_func(cancel_baton));
445
446      /* Copy the packed shard. */
447      SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev,
448                                        src_fs, dst_fs,
449                                        rev, max_files_per_dir,
450                                        iterpool));
451
452      pack_end_rev = rev + max_files_per_dir - 1;
453
454      /* Whenever this pack did not previously exist in the destination,
455       * update 'current' to the most recent packed rev (so readers can see
456       * new revisions which arrived in this pack). */
457      if (pack_end_rev > dst_youngest)
458        {
459          SVN_ERR(svn_fs_x__write_current(dst_fs, pack_end_rev, iterpool));
460        }
461
462      /* When notifying about packed shards, make things simpler by either
463       * reporting a full revision range, i.e [pack start, pack end] or
464       * reporting nothing. There is one case when this approach might not
465       * be exact (incremental hotcopy with a pack replacing last unpacked
466       * revisions), but generally this is good enough. */
467      if (notify_func && !skipped)
468        notify_func(notify_baton, rev, pack_end_rev, iterpool);
469
470      /* Now that all revisions have moved into the pack, the original
471       * rev dir can be removed. */
472      SVN_ERR(svn_io_remove_dir2(svn_fs_x__path_shard(dst_fs, rev, iterpool),
473                                 TRUE, cancel_func, cancel_baton, iterpool));
474    }
475
476  if (cancel_func)
477    SVN_ERR(cancel_func(cancel_baton));
478
479  SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
480  SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
481
482  /* Now, copy pairs of non-packed revisions and revprop files.
483   * If necessary, update 'current' after copying all files from a shard. */
484  for (; rev <= src_youngest; rev++)
485    {
486      svn_boolean_t skipped = TRUE;
487
488      svn_pool_clear(iterpool);
489
490      if (cancel_func)
491        SVN_ERR(cancel_func(cancel_baton));
492
493      /* Copying non-packed revisions is racy in case the source repository is
494       * being packed concurrently with this hotcopy operation.  With the pack
495       * lock, however, the race is impossible, because hotcopy and pack
496       * operations block each other.
497       *
498       * We assume that all revisions coming after 'min-unpacked-rev' really
499       * are unpacked and that's not necessarily true with concurrent packing.
500       * Don't try to be smart in this edge case, because handling it properly
501       * might require copying *everything* from the start. Just abort the
502       * hotcopy with an ENOENT (revision file moved to a pack, so it is no
503       * longer where we expect it to be). */
504
505      /* Copy the rev file. */
506      SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revs_dir, dst_revs_dir,
507                                      rev, max_files_per_dir, FALSE,
508                                      iterpool));
509
510      /* Copy the revprop file. */
511      SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revs_dir, dst_revs_dir,
512                                      rev, max_files_per_dir, TRUE,
513                                      iterpool));
514
515      /* Whenever this revision did not previously exist in the destination,
516       * checkpoint the progress via 'current' (do that once per full shard
517       * in order not to slow things down). */
518      if (rev > dst_youngest)
519        {
520          if (max_files_per_dir && (rev % max_files_per_dir == 0))
521            {
522              SVN_ERR(svn_fs_x__write_current(dst_fs, rev, iterpool));
523            }
524        }
525
526      if (notify_func && !skipped)
527        notify_func(notify_baton, rev, rev, iterpool);
528    }
529  svn_pool_destroy(iterpool);
530
531  /* We assume that all revisions were copied now, i.e. we didn't exit the
532   * above loop early. 'rev' was last incremented during exit of the loop. */
533  SVN_ERR_ASSERT(rev == src_youngest + 1);
534
535  return SVN_NO_ERROR;
536}
537
538/* Baton for hotcopy_body(). */
539typedef struct hotcopy_body_baton_t {
540  svn_fs_t *src_fs;
541  svn_fs_t *dst_fs;
542  svn_boolean_t incremental;
543  svn_fs_hotcopy_notify_t notify_func;
544  void *notify_baton;
545  svn_cancel_func_t cancel_func;
546  void *cancel_baton;
547} hotcopy_body_baton_t;
548
549/* Perform a hotcopy, either normal or incremental.
550 *
551 * Normal hotcopy assumes that the destination exists as an empty
552 * directory. It behaves like an incremental hotcopy except that
553 * none of the copied files already exist in the destination.
554 *
555 * An incremental hotcopy copies only changed or new files to the destination,
556 * and removes files from the destination no longer present in the source.
557 * While the incremental hotcopy is running, readers should still be able
558 * to access the destination repository without error and should not see
559 * revisions currently in progress of being copied. Readers are able to see
560 * new fully copied revisions even if the entire incremental hotcopy procedure
561 * has not yet completed.
562 *
563 * Writers are blocked out completely during the entire incremental hotcopy
564 * process to ensure consistency. This function assumes that the repository
565 * write-lock is held.
566 */
567static svn_error_t *
568hotcopy_body(void *baton,
569             apr_pool_t *scratch_pool)
570{
571  hotcopy_body_baton_t *hbb = baton;
572  svn_fs_t *src_fs = hbb->src_fs;
573  svn_fs_t *dst_fs = hbb->dst_fs;
574  svn_boolean_t incremental = hbb->incremental;
575  svn_fs_hotcopy_notify_t notify_func = hbb->notify_func;
576  void* notify_baton = hbb->notify_baton;
577  svn_cancel_func_t cancel_func = hbb->cancel_func;
578  void* cancel_baton = hbb->cancel_baton;
579  svn_revnum_t src_youngest;
580  svn_revnum_t dst_youngest;
581  const char *src_revs_dir;
582  const char *dst_revs_dir;
583  const char *src_subdir;
584  const char *dst_subdir;
585  svn_node_kind_t kind;
586
587  /* Try to copy the config.
588   *
589   * ### We try copying the config file before doing anything else,
590   * ### because higher layers will abort the hotcopy if we throw
591   * ### an error from this function, and that renders the hotcopy
592   * ### unusable anyway. */
593  SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
594                               scratch_pool));
595
596  if (cancel_func)
597    SVN_ERR(cancel_func(cancel_baton));
598
599  /* Find the youngest revision in the source and destination.
600   * We only support hotcopies from sources with an equal or greater amount
601   * of revisions than the destination.
602   * This also catches the case where users accidentally swap the
603   * source and destination arguments. */
604  SVN_ERR(svn_fs_x__read_current(&src_youngest, src_fs, scratch_pool));
605  if (incremental)
606    {
607      SVN_ERR(svn_fs_x__youngest_rev(&dst_youngest, dst_fs, scratch_pool));
608      if (src_youngest < dst_youngest)
609        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
610                 _("The hotcopy destination already contains more revisions "
611                   "(%lu) than the hotcopy source contains (%lu); are source "
612                   "and destination swapped?"),
613                   dst_youngest, src_youngest);
614    }
615  else
616    dst_youngest = 0;
617
618  src_revs_dir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
619  dst_revs_dir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
620
621  /* Ensure that the required folders exist in the destination
622   * before actually copying the revisions and revprops. */
623  SVN_ERR(svn_io_make_dir_recursively(dst_revs_dir, scratch_pool));
624  if (cancel_func)
625    SVN_ERR(cancel_func(cancel_baton));
626
627  /* Split the logic for new and old FS formats. The latter is much simpler
628   * due to the absense of sharding and packing. However, it requires special
629   * care when updating the 'current' file (which contains not just the
630   * revision number, but also the next-ID counters). */
631  SVN_ERR(hotcopy_revisions(src_fs, dst_fs, src_youngest, dst_youngest,
632                            incremental, src_revs_dir, dst_revs_dir,
633                            notify_func, notify_baton,
634                            cancel_func, cancel_baton, scratch_pool));
635  SVN_ERR(svn_fs_x__write_current(dst_fs, src_youngest, scratch_pool));
636
637  /* Replace the locks tree.
638   * This is racy in case readers are currently trying to list locks in
639   * the destination. However, we need to get rid of stale locks.
640   * This is the simplest way of doing this, so we accept this small race. */
641  dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, scratch_pool);
642  SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
643                             scratch_pool));
644  src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, scratch_pool);
645  SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
646  if (kind == svn_node_dir)
647    SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
648                                        PATH_LOCKS_DIR, TRUE,
649                                        cancel_func, cancel_baton,
650                                        scratch_pool));
651
652  /*
653   * NB: Data copied below is only read by writers, not readers.
654   *     Writers are still locked out at this point.
655   */
656
657  /* Copy the rep cache and then remove entries for revisions
658   * younger than the destination's youngest revision. */
659  src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, scratch_pool);
660  dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, scratch_pool);
661  SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
662  if (kind == svn_node_file)
663    {
664      /* Copy the rep cache and then remove entries for revisions
665       * that did not make it into the destination. */
666      SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, scratch_pool));
667
668      /* The source might have r/o flags set on it - which would be
669         carried over to the copy. */
670      SVN_ERR(svn_io_set_file_read_write(dst_subdir, FALSE, scratch_pool));
671      SVN_ERR(svn_fs_x__del_rep_reference(dst_fs, src_youngest,
672                                          scratch_pool));
673    }
674
675  /* Copy the txn-current file. */
676  SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
677                                PATH_TXN_CURRENT, scratch_pool));
678
679  /* If a revprop generation file exists in the source filesystem,
680   * reset it to zero (since this is on a different path, it will not
681   * overlap with data already in cache).  Also, clean up stale files
682   * used for the named atomics implementation. */
683  SVN_ERR(svn_fs_x__reset_revprop_generation_file(dst_fs, scratch_pool));
684
685  /* Hotcopied FS is complete. Stamp it with a format file. */
686  SVN_ERR(svn_fs_x__write_format(dst_fs, TRUE, scratch_pool));
687
688  return SVN_NO_ERROR;
689}
690
691svn_error_t *
692svn_fs_x__hotcopy(svn_fs_t *src_fs,
693                  svn_fs_t *dst_fs,
694                  const char *src_path,
695                  const char *dst_path,
696                  svn_boolean_t incremental,
697                  svn_fs_hotcopy_notify_t notify_func,
698                  void *notify_baton,
699                  svn_cancel_func_t cancel_func,
700                  void *cancel_baton,
701                  svn_mutex__t *common_pool_lock,
702                  apr_pool_t *scratch_pool,
703                  apr_pool_t *common_pool)
704{
705  hotcopy_body_baton_t hbb;
706
707  if (cancel_func)
708    SVN_ERR(cancel_func(cancel_baton));
709
710  SVN_ERR(svn_fs_x__open(src_fs, src_path, scratch_pool));
711
712  if (incremental)
713    {
714      const char *dst_format_abspath;
715      svn_node_kind_t dst_format_kind;
716
717      /* Check destination format to be sure we know how to incrementally
718       * hotcopy to the destination FS. */
719      dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT,
720                                           scratch_pool);
721      SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind,
722                                scratch_pool));
723      if (dst_format_kind == svn_node_none)
724        {
725          /* No destination?  Fallback to a non-incremental hotcopy. */
726          incremental = FALSE;
727        }
728    }
729
730  if (incremental)
731    {
732      /* Check the existing repository. */
733      SVN_ERR(svn_fs_x__open(dst_fs, dst_path, scratch_pool));
734      SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs));
735
736      SVN_ERR(svn_fs_x__initialize_shared_data(dst_fs, common_pool_lock,
737                                               scratch_pool, common_pool));
738      SVN_ERR(svn_fs_x__initialize_caches(dst_fs, scratch_pool));
739    }
740  else
741    {
742      /* Start out with an empty destination using the same configuration
743       * as the source. */
744      svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
745
746      /* Create the DST_FS repository with the same layout as SRC_FS. */
747      SVN_ERR(svn_fs_x__create_file_tree(dst_fs, dst_path, src_ffd->format,
748                                         src_ffd->max_files_per_dir,
749                                         scratch_pool));
750
751      /* Copy the UUID.  Hotcopy destination receives a new instance ID, but
752       * has the same filesystem UUID as the source. */
753      SVN_ERR(svn_fs_x__set_uuid(dst_fs, src_fs->uuid, NULL, TRUE,
754                                 scratch_pool));
755
756      /* Remove revision 0 contents.  Otherwise, it may not get overwritten
757       * due to having a newer timestamp. */
758      SVN_ERR(hotcopy_remove_file(svn_fs_x__path_rev(dst_fs, 0,
759                                                     scratch_pool),
760                                  scratch_pool));
761      SVN_ERR(hotcopy_remove_file(svn_fs_x__path_revprops(dst_fs, 0,
762                                                          scratch_pool),
763                                  scratch_pool));
764
765      SVN_ERR(svn_fs_x__initialize_shared_data(dst_fs, common_pool_lock,
766                                               scratch_pool, common_pool));
767      SVN_ERR(svn_fs_x__initialize_caches(dst_fs, scratch_pool));
768    }
769
770  if (cancel_func)
771    SVN_ERR(cancel_func(cancel_baton));
772
773  hbb.src_fs = src_fs;
774  hbb.dst_fs = dst_fs;
775  hbb.incremental = incremental;
776  hbb.notify_func = notify_func;
777  hbb.notify_baton = notify_baton;
778  hbb.cancel_func = cancel_func;
779  hbb.cancel_baton = cancel_baton;
780
781  /* Lock the destination in the incremental mode.  For a non-incremental
782   * hotcopy, don't take any locks.  In that case the destination cannot be
783   * opened until the hotcopy finishes, and we don't have to worry about
784   * concurrency. */
785  if (incremental)
786    SVN_ERR(svn_fs_x__with_all_locks(dst_fs, hotcopy_body, &hbb,
787                                     scratch_pool));
788  else
789    SVN_ERR(hotcopy_body(&hbb, scratch_pool));
790
791  return SVN_NO_ERROR;
792}
793