]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/hotcopy.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / hotcopy.c
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. */
43 static svn_error_t *
44 hotcopy_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  */
98 static svn_error_t *
99 entry_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. */
120 static svn_error_t *
121 hotcopy_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  * Use SCRATCH_POOL for temporary allocations. */
247 static svn_error_t *
248 hotcopy_copy_shard_file(svn_boolean_t *skipped_p,
249                         const char *src_subdir,
250                         const char *dst_subdir,
251                         svn_revnum_t rev,
252                         int max_files_per_dir,
253                         apr_pool_t *scratch_pool)
254 {
255   const char *src_subdir_shard = src_subdir,
256              *dst_subdir_shard = dst_subdir;
257
258   const char *shard = apr_psprintf(scratch_pool, "%ld",
259                                    rev / max_files_per_dir);
260   src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
261   dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
262
263   if (rev % max_files_per_dir == 0)
264     {
265       SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
266       SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
267                                 scratch_pool));
268     }
269
270   SVN_ERR(hotcopy_io_dir_file_copy(skipped_p,
271                                    src_subdir_shard, dst_subdir_shard,
272                                    apr_psprintf(scratch_pool, "%ld", rev),
273                                    scratch_pool));
274   return SVN_NO_ERROR;
275 }
276
277
278 /* Copy a packed shard containing revision REV, and which contains
279  * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
280  * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
281  * Do not re-copy data which already exists in DST_FS.
282  * Set *SKIPPED_P to FALSE only if at least one part of the shard
283  * was copied, do not change the value in *SKIPPED_P otherwise.
284  * SKIPPED_P may be NULL if not required.
285  * Use SCRATCH_POOL for temporary allocations. */
286 static svn_error_t *
287 hotcopy_copy_packed_shard(svn_boolean_t *skipped_p,
288                           svn_revnum_t *dst_min_unpacked_rev,
289                           svn_fs_t *src_fs,
290                           svn_fs_t *dst_fs,
291                           svn_revnum_t rev,
292                           int max_files_per_dir,
293                           apr_pool_t *scratch_pool)
294 {
295   const char *src_subdir;
296   const char *dst_subdir;
297   const char *packed_shard;
298   const char *src_subdir_packed_shard;
299   svn_revnum_t revprop_rev;
300   apr_pool_t *iterpool;
301   svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
302
303   /* Copy the packed shard. */
304   src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
305   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
306   packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
307                               rev / max_files_per_dir);
308   src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
309                                             scratch_pool);
310   SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p, src_subdir_packed_shard,
311                                           dst_subdir, packed_shard,
312                                           TRUE /* copy_perms */,
313                                           NULL /* cancel_func */, NULL,
314                                           scratch_pool));
315
316   /* Copy revprops belonging to revisions in this pack. */
317   src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
318   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
319
320   if (src_ffd->min_unpacked_rev < rev + max_files_per_dir)
321     {
322       /* copy unpacked revprops rev by rev */
323       iterpool = svn_pool_create(scratch_pool);
324       for (revprop_rev = rev;
325            revprop_rev < rev + max_files_per_dir;
326            revprop_rev++)
327         {
328           svn_pool_clear(iterpool);
329
330           SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
331                                           revprop_rev, max_files_per_dir,
332                                           iterpool));
333         }
334       svn_pool_destroy(iterpool);
335     }
336   else
337     {
338       /* revprop for revision 0 will never be packed */
339       if (rev == 0)
340         SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
341                                         0, max_files_per_dir,
342                                         scratch_pool));
343
344       /* packed revprops folder */
345       packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
346                                   rev / max_files_per_dir);
347       src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
348                                                 scratch_pool);
349       SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
350                                               src_subdir_packed_shard,
351                                               dst_subdir, packed_shard,
352                                               TRUE /* copy_perms */,
353                                               NULL /* cancel_func */, NULL,
354                                               scratch_pool));
355     }
356
357   /* If necessary, update the min-unpacked rev file in the hotcopy. */
358   if (*dst_min_unpacked_rev < rev + max_files_per_dir)
359     {
360       *dst_min_unpacked_rev = rev + max_files_per_dir;
361       SVN_ERR(svn_fs_x__write_min_unpacked_rev(dst_fs,
362                                                *dst_min_unpacked_rev,
363                                                scratch_pool));
364     }
365
366   return SVN_NO_ERROR;
367 }
368
369 /* Remove file PATH, if it exists - even if it is read-only.
370  * Use SCRATCH_POOL for temporary allocations. */
371 static svn_error_t *
372 hotcopy_remove_file(const char *path,
373                     apr_pool_t *scratch_pool)
374 {
375   /* Make the rev file writable and remove it. */
376   SVN_ERR(svn_io_set_file_read_write(path, TRUE, scratch_pool));
377   SVN_ERR(svn_io_remove_file2(path, TRUE, scratch_pool));
378
379   return SVN_NO_ERROR;
380 }
381
382
383 /* Remove revision or revprop files between START_REV (inclusive) and
384  * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
385  * sharding as per MAX_FILES_PER_DIR.
386  * Use SCRATCH_POOL for temporary allocations. */
387 static svn_error_t *
388 hotcopy_remove_files(svn_fs_t *dst_fs,
389                      const char *dst_subdir,
390                      svn_revnum_t start_rev,
391                      svn_revnum_t end_rev,
392                      int max_files_per_dir,
393                      apr_pool_t *scratch_pool)
394 {
395   const char *shard;
396   const char *dst_subdir_shard;
397   svn_revnum_t rev;
398   apr_pool_t *iterpool;
399
400   /* Pre-compute paths for initial shard. */
401   shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
402   dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
403
404   iterpool = svn_pool_create(scratch_pool);
405   for (rev = start_rev; rev < end_rev; rev++)
406     {
407       svn_pool_clear(iterpool);
408
409       /* If necessary, update paths for shard. */
410       if (rev != start_rev && rev % max_files_per_dir == 0)
411         {
412           shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
413           dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
414         }
415
416       /* remove files for REV */
417       SVN_ERR(hotcopy_remove_file(svn_dirent_join(dst_subdir_shard,
418                                                   apr_psprintf(iterpool,
419                                                                "%ld", rev),
420                                                   iterpool),
421                                   iterpool));
422     }
423
424   svn_pool_destroy(iterpool);
425
426   return SVN_NO_ERROR;
427 }
428
429 /* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
430  * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
431  * Use SCRATCH_POOL for temporary allocations. */
432 static svn_error_t *
433 hotcopy_remove_rev_files(svn_fs_t *dst_fs,
434                          svn_revnum_t start_rev,
435                          svn_revnum_t end_rev,
436                          int max_files_per_dir,
437                          apr_pool_t *scratch_pool)
438 {
439   SVN_ERR_ASSERT(start_rev <= end_rev);
440   SVN_ERR(hotcopy_remove_files(dst_fs,
441                                svn_dirent_join(dst_fs->path,
442                                                PATH_REVS_DIR,
443                                                scratch_pool),
444                                start_rev, end_rev,
445                                max_files_per_dir, scratch_pool));
446
447   return SVN_NO_ERROR;
448 }
449
450 /* Remove revision properties between START_REV (inclusive) and END_REV
451  * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
452  * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
453  * not be deleted. */
454 static svn_error_t *
455 hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
456                              svn_revnum_t start_rev,
457                              svn_revnum_t end_rev,
458                              int max_files_per_dir,
459                              apr_pool_t *scratch_pool)
460 {
461   SVN_ERR_ASSERT(start_rev <= end_rev);
462
463   /* don't delete rev 0 props */
464   SVN_ERR(hotcopy_remove_files(dst_fs,
465                                svn_dirent_join(dst_fs->path,
466                                                PATH_REVPROPS_DIR,
467                                                scratch_pool),
468                                start_rev ? start_rev : 1, end_rev,
469                                max_files_per_dir, scratch_pool));
470
471   return SVN_NO_ERROR;
472 }
473
474 /* Verify that DST_FS is a suitable destination for an incremental
475  * hotcopy from SRC_FS. */
476 static svn_error_t *
477 hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
478                                         svn_fs_t *dst_fs)
479 {
480   svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
481   svn_fs_x__data_t *dst_ffd = dst_fs->fsap_data;
482
483   /* We only support incremental hotcopy between the same format. */
484   if (src_ffd->format != dst_ffd->format)
485     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
486       _("The FSX format (%d) of the hotcopy source does not match the "
487         "FSX format (%d) of the hotcopy destination; please upgrade "
488         "both repositories to the same format"),
489       src_ffd->format, dst_ffd->format);
490
491   /* Make sure the UUID of source and destination match up.
492    * We don't want to copy over a different repository. */
493   if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
494     return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
495                             _("The UUID of the hotcopy source does "
496                               "not match the UUID of the hotcopy "
497                               "destination"));
498
499   /* Also require same shard size. */
500   if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
501     return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
502                             _("The sharding layout configuration "
503                               "of the hotcopy source does not match "
504                               "the sharding layout configuration of "
505                               "the hotcopy destination"));
506   return SVN_NO_ERROR;
507 }
508
509 /* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
510  * CANCEL_FUNC and CANCEL_BATON do the usual thing.
511  * Use SCRATCH_POOL for temporary allocations.
512  */
513 static svn_error_t *
514 remove_folder(const char *path,
515               svn_cancel_func_t cancel_func,
516               void *cancel_baton,
517               apr_pool_t *scratch_pool)
518 {
519   svn_error_t *err = svn_io_remove_dir2(path, TRUE,
520                                         cancel_func, cancel_baton,
521                                         scratch_pool);
522
523   if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
524     {
525       svn_error_clear(err);
526       err = SVN_NO_ERROR;
527     }
528
529   return svn_error_trace(err);
530 }
531
532 /* Copy the revision and revprop files (possibly sharded / packed) from
533  * SRC_FS to DST_FS.  Do not re-copy data which already exists in DST_FS.
534  * When copying packed or unpacked shards, checkpoint the result in DST_FS
535  * for every shard by updating the 'current' file if necessary.  Assume
536  * the >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT filesystem format without
537  * global next-ID counters.  Indicate progress via the optional NOTIFY_FUNC
538  * callback using NOTIFY_BATON.  Use SCRATCH_POOL for temporary allocations.
539  */
540 static svn_error_t *
541 hotcopy_revisions(svn_fs_t *src_fs,
542                   svn_fs_t *dst_fs,
543                   svn_revnum_t src_youngest,
544                   svn_revnum_t dst_youngest,
545                   svn_boolean_t incremental,
546                   const char *src_revs_dir,
547                   const char *dst_revs_dir,
548                   const char *src_revprops_dir,
549                   const char *dst_revprops_dir,
550                   svn_fs_hotcopy_notify_t notify_func,
551                   void* notify_baton,
552                   svn_cancel_func_t cancel_func,
553                   void* cancel_baton,
554                   apr_pool_t *scratch_pool)
555 {
556   svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
557   int max_files_per_dir = src_ffd->max_files_per_dir;
558   svn_revnum_t src_min_unpacked_rev;
559   svn_revnum_t dst_min_unpacked_rev;
560   svn_revnum_t rev;
561   apr_pool_t *iterpool;
562
563   /* Copy the min unpacked rev, and read its value. */
564   SVN_ERR(svn_fs_x__read_min_unpacked_rev(&src_min_unpacked_rev, src_fs,
565                                           scratch_pool));
566   SVN_ERR(svn_fs_x__read_min_unpacked_rev(&dst_min_unpacked_rev, dst_fs,
567                                           scratch_pool));
568
569   /* We only support packs coming from the hotcopy source.
570     * The destination should not be packed independently from
571     * the source. This also catches the case where users accidentally
572     * swap the source and destination arguments. */
573   if (src_min_unpacked_rev < dst_min_unpacked_rev)
574     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
575                              _("The hotcopy destination already contains "
576                                "more packed revisions (%lu) than the "
577                                "hotcopy source contains (%lu)"),
578                              dst_min_unpacked_rev - 1,
579                              src_min_unpacked_rev - 1);
580
581   SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
582                                PATH_MIN_UNPACKED_REV, scratch_pool));
583
584   if (cancel_func)
585     SVN_ERR(cancel_func(cancel_baton));
586
587   /*
588    * Copy the necessary rev files.
589    */
590
591   iterpool = svn_pool_create(scratch_pool);
592   /* First, copy packed shards. */
593   for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
594     {
595       svn_boolean_t skipped = TRUE;
596       svn_revnum_t pack_end_rev;
597
598       svn_pool_clear(iterpool);
599
600       if (cancel_func)
601         SVN_ERR(cancel_func(cancel_baton));
602
603       /* Copy the packed shard. */
604       SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev,
605                                         src_fs, dst_fs,
606                                         rev, max_files_per_dir,
607                                         iterpool));
608
609       pack_end_rev = rev + max_files_per_dir - 1;
610
611       /* Whenever this pack did not previously exist in the destination,
612        * update 'current' to the most recent packed rev (so readers can see
613        * new revisions which arrived in this pack). */
614       if (pack_end_rev > dst_youngest)
615         {
616           SVN_ERR(svn_fs_x__write_current(dst_fs, pack_end_rev, iterpool));
617         }
618
619       /* When notifying about packed shards, make things simpler by either
620        * reporting a full revision range, i.e [pack start, pack end] or
621        * reporting nothing. There is one case when this approach might not
622        * be exact (incremental hotcopy with a pack replacing last unpacked
623        * revisions), but generally this is good enough. */
624       if (notify_func && !skipped)
625         notify_func(notify_baton, rev, pack_end_rev, iterpool);
626
627       /* Remove revision files which are now packed. */
628       if (incremental)
629         {
630           SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
631                                            rev + max_files_per_dir,
632                                            max_files_per_dir, iterpool));
633           SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
634                                                rev + max_files_per_dir,
635                                                max_files_per_dir,
636                                                iterpool));
637         }
638
639       /* Now that all revisions have moved into the pack, the original
640        * rev dir can be removed. */
641       SVN_ERR(remove_folder(svn_fs_x__path_rev_shard(dst_fs, rev, iterpool),
642                             cancel_func, cancel_baton, iterpool));
643       if (rev > 0)
644         SVN_ERR(remove_folder(svn_fs_x__path_revprops_shard(dst_fs, rev,
645                                                             iterpool),
646                               cancel_func, cancel_baton, iterpool));
647     }
648
649   if (cancel_func)
650     SVN_ERR(cancel_func(cancel_baton));
651
652   SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
653   SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
654
655   /* Now, copy pairs of non-packed revisions and revprop files.
656    * If necessary, update 'current' after copying all files from a shard. */
657   for (; rev <= src_youngest; rev++)
658     {
659       svn_boolean_t skipped = TRUE;
660
661       svn_pool_clear(iterpool);
662
663       if (cancel_func)
664         SVN_ERR(cancel_func(cancel_baton));
665
666       /* Copying non-packed revisions is racy in case the source repository is
667        * being packed concurrently with this hotcopy operation.  With the pack
668        * lock, however, the race is impossible, because hotcopy and pack
669        * operations block each other.
670        *
671        * We assume that all revisions coming after 'min-unpacked-rev' really
672        * are unpacked and that's not necessarily true with concurrent packing.
673        * Don't try to be smart in this edge case, because handling it properly
674        * might require copying *everything* from the start. Just abort the
675        * hotcopy with an ENOENT (revision file moved to a pack, so it is no
676        * longer where we expect it to be). */
677
678       /* Copy the rev file. */
679       SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revs_dir, dst_revs_dir,
680                                       rev, max_files_per_dir,
681                                       iterpool));
682
683       /* Copy the revprop file. */
684       SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revprops_dir,
685                                       dst_revprops_dir,
686                                       rev, max_files_per_dir,
687                                       iterpool));
688
689       /* Whenever this revision did not previously exist in the destination,
690        * checkpoint the progress via 'current' (do that once per full shard
691        * in order not to slow things down). */
692       if (rev > dst_youngest)
693         {
694           if (max_files_per_dir && (rev % max_files_per_dir == 0))
695             {
696               SVN_ERR(svn_fs_x__write_current(dst_fs, rev, iterpool));
697             }
698         }
699
700       if (notify_func && !skipped)
701         notify_func(notify_baton, rev, rev, iterpool);
702     }
703   svn_pool_destroy(iterpool);
704
705   /* We assume that all revisions were copied now, i.e. we didn't exit the
706    * above loop early. 'rev' was last incremented during exit of the loop. */
707   SVN_ERR_ASSERT(rev == src_youngest + 1);
708
709   return SVN_NO_ERROR;
710 }
711
712 /* Baton for hotcopy_body(). */
713 typedef struct hotcopy_body_baton_t {
714   svn_fs_t *src_fs;
715   svn_fs_t *dst_fs;
716   svn_boolean_t incremental;
717   svn_fs_hotcopy_notify_t notify_func;
718   void *notify_baton;
719   svn_cancel_func_t cancel_func;
720   void *cancel_baton;
721 } hotcopy_body_baton_t;
722
723 /* Perform a hotcopy, either normal or incremental.
724  *
725  * Normal hotcopy assumes that the destination exists as an empty
726  * directory. It behaves like an incremental hotcopy except that
727  * none of the copied files already exist in the destination.
728  *
729  * An incremental hotcopy copies only changed or new files to the destination,
730  * and removes files from the destination no longer present in the source.
731  * While the incremental hotcopy is running, readers should still be able
732  * to access the destintation repository without error and should not see
733  * revisions currently in progress of being copied. Readers are able to see
734  * new fully copied revisions even if the entire incremental hotcopy procedure
735  * has not yet completed.
736  *
737  * Writers are blocked out completely during the entire incremental hotcopy
738  * process to ensure consistency. This function assumes that the repository
739  * write-lock is held.
740  */
741 static svn_error_t *
742 hotcopy_body(void *baton,
743              apr_pool_t *scratch_pool)
744 {
745   hotcopy_body_baton_t *hbb = baton;
746   svn_fs_t *src_fs = hbb->src_fs;
747   svn_fs_t *dst_fs = hbb->dst_fs;
748   svn_boolean_t incremental = hbb->incremental;
749   svn_fs_hotcopy_notify_t notify_func = hbb->notify_func;
750   void* notify_baton = hbb->notify_baton;
751   svn_cancel_func_t cancel_func = hbb->cancel_func;
752   void* cancel_baton = hbb->cancel_baton;
753   svn_revnum_t src_youngest;
754   svn_revnum_t dst_youngest;
755   const char *src_revprops_dir;
756   const char *dst_revprops_dir;
757   const char *src_revs_dir;
758   const char *dst_revs_dir;
759   const char *src_subdir;
760   const char *dst_subdir;
761   svn_node_kind_t kind;
762
763   /* Try to copy the config.
764    *
765    * ### We try copying the config file before doing anything else,
766    * ### because higher layers will abort the hotcopy if we throw
767    * ### an error from this function, and that renders the hotcopy
768    * ### unusable anyway. */
769   SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
770                                scratch_pool));
771
772   if (cancel_func)
773     SVN_ERR(cancel_func(cancel_baton));
774
775   /* Find the youngest revision in the source and destination.
776    * We only support hotcopies from sources with an equal or greater amount
777    * of revisions than the destination.
778    * This also catches the case where users accidentally swap the
779    * source and destination arguments. */
780   SVN_ERR(svn_fs_x__read_current(&src_youngest, src_fs, scratch_pool));
781   if (incremental)
782     {
783       SVN_ERR(svn_fs_x__youngest_rev(&dst_youngest, dst_fs, scratch_pool));
784       if (src_youngest < dst_youngest)
785         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
786                  _("The hotcopy destination already contains more revisions "
787                    "(%lu) than the hotcopy source contains (%lu); are source "
788                    "and destination swapped?"),
789                    dst_youngest, src_youngest);
790     }
791   else
792     dst_youngest = 0;
793
794   src_revs_dir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
795   dst_revs_dir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
796   src_revprops_dir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR,
797                                      scratch_pool);
798   dst_revprops_dir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR,
799                                      scratch_pool);
800
801   /* Ensure that the required folders exist in the destination
802    * before actually copying the revisions and revprops. */
803   SVN_ERR(svn_io_make_dir_recursively(dst_revs_dir, scratch_pool));
804   SVN_ERR(svn_io_make_dir_recursively(dst_revprops_dir, scratch_pool));
805
806   if (cancel_func)
807     SVN_ERR(cancel_func(cancel_baton));
808
809   /* Split the logic for new and old FS formats. The latter is much simpler
810    * due to the absense of sharding and packing. However, it requires special
811    * care when updating the 'current' file (which contains not just the
812    * revision number, but also the next-ID counters). */
813   SVN_ERR(hotcopy_revisions(src_fs, dst_fs, src_youngest, dst_youngest,
814                             incremental, src_revs_dir, dst_revs_dir,
815                             src_revprops_dir, dst_revprops_dir,
816                             notify_func, notify_baton,
817                             cancel_func, cancel_baton, scratch_pool));
818   SVN_ERR(svn_fs_x__write_current(dst_fs, src_youngest, scratch_pool));
819
820   /* Replace the locks tree.
821    * This is racy in case readers are currently trying to list locks in
822    * the destination. However, we need to get rid of stale locks.
823    * This is the simplest way of doing this, so we accept this small race. */
824   dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, scratch_pool);
825   SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
826                              scratch_pool));
827   src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, scratch_pool);
828   SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
829   if (kind == svn_node_dir)
830     SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
831                                         PATH_LOCKS_DIR, TRUE,
832                                         cancel_func, cancel_baton,
833                                         scratch_pool));
834
835   /* Now copy the node-origins cache tree. */
836   src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR,
837                                scratch_pool);
838   SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
839   if (kind == svn_node_dir)
840     SVN_ERR(hotcopy_io_copy_dir_recursively(NULL, src_subdir, dst_fs->path,
841                                             PATH_NODE_ORIGINS_DIR, TRUE,
842                                             cancel_func, cancel_baton,
843                                             scratch_pool));
844
845   /*
846    * NB: Data copied below is only read by writers, not readers.
847    *     Writers are still locked out at this point.
848    */
849
850   /* Copy the rep cache and then remove entries for revisions
851    * younger than the destination's youngest revision. */
852   src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, scratch_pool);
853   dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, scratch_pool);
854   SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
855   if (kind == svn_node_file)
856     {
857       /* Copy the rep cache and then remove entries for revisions
858        * that did not make it into the destination. */
859       SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, scratch_pool));
860       SVN_ERR(svn_fs_x__del_rep_reference(dst_fs, src_youngest,
861                                           scratch_pool));
862     }
863
864   /* Copy the txn-current file. */
865   SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
866                                 PATH_TXN_CURRENT, scratch_pool));
867
868   /* If a revprop generation file exists in the source filesystem,
869    * reset it to zero (since this is on a different path, it will not
870    * overlap with data already in cache).  Also, clean up stale files
871    * used for the named atomics implementation. */
872   SVN_ERR(svn_fs_x__reset_revprop_generation_file(dst_fs, scratch_pool));
873
874   return SVN_NO_ERROR;
875 }
876
877 /* Wrapper around hotcopy_body taking out all necessary source repository
878  * locks.
879  */
880 static svn_error_t *
881 hotcopy_locking_src_body(void *baton,
882                          apr_pool_t *scratch_pool)
883 {
884   hotcopy_body_baton_t *hbb = baton;
885
886   return svn_error_trace(svn_fs_x__with_pack_lock(hbb->src_fs, hotcopy_body,
887                                                   baton, scratch_pool));
888 }
889
890 /* Create an empty filesystem at DST_FS at DST_PATH with the same
891  * configuration as SRC_FS (uuid, format, and other parameters).
892  * After creation DST_FS has no revisions, not even revision zero. */
893 static svn_error_t *
894 hotcopy_create_empty_dest(svn_fs_t *src_fs,
895                           svn_fs_t *dst_fs,
896                           const char *dst_path,
897                           apr_pool_t *scratch_pool)
898 {
899   svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
900
901   /* Create the DST_FS repository with the same layout as SRC_FS. */
902   SVN_ERR(svn_fs_x__create_file_tree(dst_fs, dst_path, src_ffd->format,
903                                      src_ffd->max_files_per_dir,
904                                      scratch_pool));
905
906   /* Copy the UUID.  Hotcopy destination receives a new instance ID, but
907    * has the same filesystem UUID as the source. */
908   SVN_ERR(svn_fs_x__set_uuid(dst_fs, src_fs->uuid, NULL, scratch_pool));
909
910   /* Remove revision 0 contents.  Otherwise, it may not get overwritten
911    * due to having a newer timestamp. */
912   SVN_ERR(hotcopy_remove_file(svn_fs_x__path_rev(dst_fs, 0, scratch_pool),
913                               scratch_pool));
914   SVN_ERR(hotcopy_remove_file(svn_fs_x__path_revprops(dst_fs, 0,
915                                                       scratch_pool),
916                               scratch_pool));
917
918   /* This filesystem is ready.  Stamp it with a format number.  Fail if
919    * the 'format' file should already exist. */
920   SVN_ERR(svn_fs_x__write_format(dst_fs, FALSE, scratch_pool));
921
922   return SVN_NO_ERROR;
923 }
924
925 svn_error_t *
926 svn_fs_x__hotcopy_prepare_target(svn_fs_t *src_fs,
927                                  svn_fs_t *dst_fs,
928                                  const char *dst_path,
929                                  svn_boolean_t incremental,
930                                  apr_pool_t *scratch_pool)
931 {
932   if (incremental)
933     {
934       const char *dst_format_abspath;
935       svn_node_kind_t dst_format_kind;
936
937       /* Check destination format to be sure we know how to incrementally
938        * hotcopy to the destination FS. */
939       dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT,
940                                            scratch_pool);
941       SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind,
942                                 scratch_pool));
943       if (dst_format_kind == svn_node_none)
944         {
945           /* Destination doesn't exist yet. Perform a normal hotcopy to a
946            * empty destination using the same configuration as the source. */
947           SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path,
948                                             scratch_pool));
949         }
950       else
951         {
952           /* Check the existing repository. */
953           SVN_ERR(svn_fs_x__open(dst_fs, dst_path, scratch_pool));
954           SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs));
955         }
956     }
957   else
958     {
959       /* Start out with an empty destination using the same configuration
960        * as the source. */
961       SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path,
962                                         scratch_pool));
963     }
964
965   return SVN_NO_ERROR;
966 }
967
968 svn_error_t *
969 svn_fs_x__hotcopy(svn_fs_t *src_fs,
970                   svn_fs_t *dst_fs,
971                   svn_boolean_t incremental,
972                   svn_fs_hotcopy_notify_t notify_func,
973                   void *notify_baton,
974                   svn_cancel_func_t cancel_func,
975                   void *cancel_baton,
976                   apr_pool_t *scratch_pool)
977 {
978   hotcopy_body_baton_t hbb;
979
980   hbb.src_fs = src_fs;
981   hbb.dst_fs = dst_fs;
982   hbb.incremental = incremental;
983   hbb.notify_func = notify_func;
984   hbb.notify_baton = notify_baton;
985   hbb.cancel_func = cancel_func;
986   hbb.cancel_baton = cancel_baton;
987   SVN_ERR(svn_fs_x__with_all_locks(dst_fs, hotcopy_locking_src_body, &hbb,
988                                    scratch_pool));
989
990   return SVN_NO_ERROR;
991 }