]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/hotcopy.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_fs / hotcopy.c
1 /* hotcopy.c --- FS hotcopy functionality for FSFS
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_fs.h"
27 #include "hotcopy.h"
28 #include "util.h"
29 #include "recovery.h"
30 #include "revprops.h"
31 #include "rep-cache.h"
32
33 #include "../libsvn_fs/fs-loader.h"
34
35 #include "svn_private_config.h"
36
37 /* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
38  * the destination and do not differ in terms of kind, size, and mtime.
39  * Set *SKIPPED_P to FALSE only if the file was copied, do not change
40  * the value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not
41  * required. */
42 static svn_error_t *
43 hotcopy_io_dir_file_copy(svn_boolean_t *skipped_p,
44                          const char *src_path,
45                          const char *dst_path,
46                          const char *file,
47                          apr_pool_t *scratch_pool)
48 {
49   const svn_io_dirent2_t *src_dirent;
50   const svn_io_dirent2_t *dst_dirent;
51   const char *src_target;
52   const char *dst_target;
53
54   /* Does the destination already exist? If not, we must copy it. */
55   dst_target = svn_dirent_join(dst_path, file, scratch_pool);
56   SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
57                               scratch_pool, scratch_pool));
58   if (dst_dirent->kind != svn_node_none)
59     {
60       /* If the destination's stat information indicates that the file
61        * is equal to the source, don't bother copying the file again. */
62       src_target = svn_dirent_join(src_path, file, scratch_pool);
63       SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
64                                   scratch_pool, scratch_pool));
65       if (src_dirent->kind == dst_dirent->kind &&
66           src_dirent->special == dst_dirent->special &&
67           src_dirent->filesize == dst_dirent->filesize &&
68           src_dirent->mtime <= dst_dirent->mtime)
69         return SVN_NO_ERROR;
70     }
71
72   if (skipped_p)
73     *skipped_p = FALSE;
74
75   return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
76                                               scratch_pool));
77 }
78
79 /* Set *NAME_P to the UTF-8 representation of directory entry NAME.
80  * NAME is in the internal encoding used by APR; PARENT is in
81  * UTF-8 and in internal (not local) style.
82  *
83  * Use PARENT only for generating an error string if the conversion
84  * fails because NAME could not be represented in UTF-8.  In that
85  * case, return a two-level error in which the outer error's message
86  * mentions PARENT, but the inner error's message does not mention
87  * NAME (except possibly in hex) since NAME may not be printable.
88  * Such a compound error at least allows the user to go looking in the
89  * right directory for the problem.
90  *
91  * If there is any other error, just return that error directly.
92  *
93  * If there is any error, the effect on *NAME_P is undefined.
94  *
95  * *NAME_P and NAME may refer to the same storage.
96  */
97 static svn_error_t *
98 entry_name_to_utf8(const char **name_p,
99                    const char *name,
100                    const char *parent,
101                    apr_pool_t *pool)
102 {
103   svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
104   if (err && err->apr_err == APR_EINVAL)
105     {
106       return svn_error_createf(err->apr_err, err,
107                                _("Error converting entry "
108                                  "in directory '%s' to UTF-8"),
109                                svn_dirent_local_style(parent, pool));
110     }
111   return err;
112 }
113
114 /* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
115  * exist in the destination and do not differ from the source in terms of
116  * kind, size, and mtime. Set *SKIPPED_P to FALSE only if at least one
117  * file was copied, do not change the value in *SKIPPED_P otherwise.
118  * SKIPPED_P may be NULL if not required. */
119 static svn_error_t *
120 hotcopy_io_copy_dir_recursively(svn_boolean_t *skipped_p,
121                                 const char *src,
122                                 const char *dst_parent,
123                                 const char *dst_basename,
124                                 svn_boolean_t copy_perms,
125                                 svn_cancel_func_t cancel_func,
126                                 void *cancel_baton,
127                                 apr_pool_t *pool)
128 {
129   svn_node_kind_t kind;
130   apr_status_t status;
131   const char *dst_path;
132   apr_dir_t *this_dir;
133   apr_finfo_t this_entry;
134   apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
135
136   /* Make a subpool for recursion */
137   apr_pool_t *subpool = svn_pool_create(pool);
138
139   /* The 'dst_path' is simply dst_parent/dst_basename */
140   dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
141
142   /* Sanity checks:  SRC and DST_PARENT are directories, and
143      DST_BASENAME doesn't already exist in DST_PARENT. */
144   SVN_ERR(svn_io_check_path(src, &kind, subpool));
145   if (kind != svn_node_dir)
146     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
147                              _("Source '%s' is not a directory"),
148                              svn_dirent_local_style(src, pool));
149
150   SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
151   if (kind != svn_node_dir)
152     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
153                              _("Destination '%s' is not a directory"),
154                              svn_dirent_local_style(dst_parent, pool));
155
156   SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
157
158   /* Create the new directory. */
159   /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
160   SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
161
162   /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
163   SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
164
165   for (status = apr_dir_read(&this_entry, flags, this_dir);
166        status == APR_SUCCESS;
167        status = apr_dir_read(&this_entry, flags, this_dir))
168     {
169       if ((this_entry.name[0] == '.')
170           && ((this_entry.name[1] == '\0')
171               || ((this_entry.name[1] == '.')
172                   && (this_entry.name[2] == '\0'))))
173         {
174           continue;
175         }
176       else
177         {
178           const char *entryname_utf8;
179
180           if (cancel_func)
181             SVN_ERR(cancel_func(cancel_baton));
182
183           SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
184                                      src, subpool));
185           if (this_entry.filetype == APR_REG) /* regular file */
186             {
187               SVN_ERR(hotcopy_io_dir_file_copy(skipped_p, src, dst_path,
188                                                entryname_utf8, subpool));
189             }
190           else if (this_entry.filetype == APR_LNK) /* symlink */
191             {
192               const char *src_target = svn_dirent_join(src, entryname_utf8,
193                                                        subpool);
194               const char *dst_target = svn_dirent_join(dst_path,
195                                                        entryname_utf8,
196                                                        subpool);
197               SVN_ERR(svn_io_copy_link(src_target, dst_target,
198                                        subpool));
199             }
200           else if (this_entry.filetype == APR_DIR) /* recurse */
201             {
202               const char *src_target;
203
204               /* Prevent infinite recursion by filtering off our
205                  newly created destination path. */
206               if (strcmp(src, dst_parent) == 0
207                   && strcmp(entryname_utf8, dst_basename) == 0)
208                 continue;
209
210               src_target = svn_dirent_join(src, entryname_utf8, subpool);
211               SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
212                                                       src_target,
213                                                       dst_path,
214                                                       entryname_utf8,
215                                                       copy_perms,
216                                                       cancel_func,
217                                                       cancel_baton,
218                                                       subpool));
219             }
220           /* ### support other APR node types someday?? */
221
222         }
223     }
224
225   if (! (APR_STATUS_IS_ENOENT(status)))
226     return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
227                               svn_dirent_local_style(src, pool));
228
229   status = apr_dir_close(this_dir);
230   if (status)
231     return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
232                               svn_dirent_local_style(src, pool));
233
234   /* Free any memory used by recursion */
235   svn_pool_destroy(subpool);
236
237   return SVN_NO_ERROR;
238 }
239
240 /* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
241  * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
242  * Set *SKIPPED_P to FALSE only if the file was copied, do not change the
243  * value in *SKIPPED_P otherwise. SKIPPED_P may be NULL if not required.
244  * Use SCRATCH_POOL for temporary allocations. */
245 static svn_error_t *
246 hotcopy_copy_shard_file(svn_boolean_t *skipped_p,
247                         const char *src_subdir,
248                         const char *dst_subdir,
249                         svn_revnum_t rev,
250                         int max_files_per_dir,
251                         apr_pool_t *scratch_pool)
252 {
253   const char *src_subdir_shard = src_subdir,
254              *dst_subdir_shard = dst_subdir;
255
256   if (max_files_per_dir)
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
271   SVN_ERR(hotcopy_io_dir_file_copy(skipped_p,
272                                    src_subdir_shard, dst_subdir_shard,
273                                    apr_psprintf(scratch_pool, "%ld", rev),
274                                    scratch_pool));
275
276   return SVN_NO_ERROR;
277 }
278
279
280 /* Copy a packed shard containing revision REV, and which contains
281  * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
282  * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
283  * Do not re-copy data which already exists in DST_FS.
284  * Set *SKIPPED_P to FALSE only if at least one part of the shard
285  * was copied, do not change the value in *SKIPPED_P otherwise.
286  * SKIPPED_P may be NULL if not required.
287  * Use SCRATCH_POOL for temporary allocations. */
288 static svn_error_t *
289 hotcopy_copy_packed_shard(svn_boolean_t *skipped_p,
290                           svn_revnum_t *dst_min_unpacked_rev,
291                           svn_fs_t *src_fs,
292                           svn_fs_t *dst_fs,
293                           svn_revnum_t rev,
294                           int max_files_per_dir,
295                           apr_pool_t *scratch_pool)
296 {
297   const char *src_subdir;
298   const char *dst_subdir;
299   const char *packed_shard;
300   const char *src_subdir_packed_shard;
301   svn_revnum_t revprop_rev;
302   apr_pool_t *iterpool;
303   fs_fs_data_t *src_ffd = src_fs->fsap_data;
304
305   /* Copy the packed shard. */
306   src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
307   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
308   packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
309                               rev / max_files_per_dir);
310   src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
311                                             scratch_pool);
312   SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p, src_subdir_packed_shard,
313                                           dst_subdir, packed_shard,
314                                           TRUE /* copy_perms */,
315                                           NULL /* cancel_func */, NULL,
316                                           scratch_pool));
317
318   /* Copy revprops belonging to revisions in this pack. */
319   src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
320   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
321
322   if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
323       || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
324     {
325       /* copy unpacked revprops rev by rev */
326       iterpool = svn_pool_create(scratch_pool);
327       for (revprop_rev = rev;
328            revprop_rev < rev + max_files_per_dir;
329            revprop_rev++)
330         {
331           svn_pool_clear(iterpool);
332
333           SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
334                                           revprop_rev, max_files_per_dir,
335                                           iterpool));
336         }
337       svn_pool_destroy(iterpool);
338     }
339   else
340     {
341       /* revprop for revision 0 will never be packed */
342       if (rev == 0)
343         SVN_ERR(hotcopy_copy_shard_file(skipped_p, src_subdir, dst_subdir,
344                                         0, max_files_per_dir,
345                                         scratch_pool));
346
347       /* packed revprops folder */
348       packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
349                                   rev / max_files_per_dir);
350       src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
351                                                 scratch_pool);
352       SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p,
353                                               src_subdir_packed_shard,
354                                               dst_subdir, packed_shard,
355                                               TRUE /* copy_perms */,
356                                               NULL /* cancel_func */, NULL,
357                                               scratch_pool));
358     }
359
360   /* If necessary, update the min-unpacked rev file in the hotcopy. */
361   if (*dst_min_unpacked_rev < rev + max_files_per_dir)
362     {
363       *dst_min_unpacked_rev = rev + max_files_per_dir;
364       SVN_ERR(svn_fs_fs__write_min_unpacked_rev(dst_fs,
365                                                 *dst_min_unpacked_rev,
366                                                 scratch_pool));
367     }
368
369   return SVN_NO_ERROR;
370 }
371
372 /* Remove file PATH, if it exists - even if it is read-only.
373  * Use POOL for temporary allocations. */
374 static svn_error_t *
375 hotcopy_remove_file(const char *path,
376                     apr_pool_t *pool)
377 {
378   /* Make the rev file writable and remove it. */
379   SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
380   SVN_ERR(svn_io_remove_file2(path, TRUE, pool));
381
382   return SVN_NO_ERROR;
383 }
384
385
386 /* Remove revision or revprop files between START_REV (inclusive) and
387  * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
388  * sharding as per MAX_FILES_PER_DIR.
389  * Use SCRATCH_POOL for temporary allocations. */
390 static svn_error_t *
391 hotcopy_remove_files(svn_fs_t *dst_fs,
392                      const char *dst_subdir,
393                      svn_revnum_t start_rev,
394                      svn_revnum_t end_rev,
395                      int max_files_per_dir,
396                      apr_pool_t *scratch_pool)
397 {
398   const char *shard;
399   const char *dst_subdir_shard;
400   svn_revnum_t rev;
401   apr_pool_t *iterpool;
402
403   /* Pre-compute paths for initial shard. */
404   shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
405   dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
406
407   iterpool = svn_pool_create(scratch_pool);
408   for (rev = start_rev; rev < end_rev; rev++)
409     {
410       svn_pool_clear(iterpool);
411
412       /* If necessary, update paths for shard. */
413       if (rev != start_rev && rev % max_files_per_dir == 0)
414         {
415           shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
416           dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
417         }
418
419       /* remove files for REV */
420       SVN_ERR(hotcopy_remove_file(svn_dirent_join(dst_subdir_shard,
421                                                   apr_psprintf(iterpool,
422                                                                "%ld", rev),
423                                                   iterpool),
424                                   iterpool));
425     }
426
427   svn_pool_destroy(iterpool);
428
429   return SVN_NO_ERROR;
430 }
431
432 /* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
433  * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
434  * Use SCRATCH_POOL for temporary allocations. */
435 static svn_error_t *
436 hotcopy_remove_rev_files(svn_fs_t *dst_fs,
437                          svn_revnum_t start_rev,
438                          svn_revnum_t end_rev,
439                          int max_files_per_dir,
440                          apr_pool_t *scratch_pool)
441 {
442   SVN_ERR_ASSERT(start_rev <= end_rev);
443   SVN_ERR(hotcopy_remove_files(dst_fs,
444                                svn_dirent_join(dst_fs->path,
445                                                PATH_REVS_DIR,
446                                                scratch_pool),
447                                start_rev, end_rev,
448                                max_files_per_dir, scratch_pool));
449
450   return SVN_NO_ERROR;
451 }
452
453 /* Remove revision properties between START_REV (inclusive) and END_REV
454  * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
455  * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
456  * not be deleted. */
457 static svn_error_t *
458 hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
459                              svn_revnum_t start_rev,
460                              svn_revnum_t end_rev,
461                              int max_files_per_dir,
462                              apr_pool_t *scratch_pool)
463 {
464   SVN_ERR_ASSERT(start_rev <= end_rev);
465
466   /* don't delete rev 0 props */
467   SVN_ERR(hotcopy_remove_files(dst_fs,
468                                svn_dirent_join(dst_fs->path,
469                                                PATH_REVPROPS_DIR,
470                                                scratch_pool),
471                                start_rev ? start_rev : 1, end_rev,
472                                max_files_per_dir, scratch_pool));
473
474   return SVN_NO_ERROR;
475 }
476
477 /* Verify that DST_FS is a suitable destination for an incremental
478  * hotcopy from SRC_FS. */
479 static svn_error_t *
480 hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
481                                         svn_fs_t *dst_fs,
482                                         apr_pool_t *pool)
483 {
484   fs_fs_data_t *src_ffd = src_fs->fsap_data;
485   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
486
487   /* We only support incremental hotcopy between the same format. */
488   if (src_ffd->format != dst_ffd->format)
489     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
490       _("The FSFS format (%d) of the hotcopy source does not match the "
491         "FSFS format (%d) of the hotcopy destination; please upgrade "
492         "both repositories to the same format"),
493       src_ffd->format, dst_ffd->format);
494
495   /* Make sure the UUID of source and destination match up.
496    * We don't want to copy over a different repository. */
497   if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
498     return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
499                             _("The UUID of the hotcopy source does "
500                               "not match the UUID of the hotcopy "
501                               "destination"));
502
503   /* Also require same shard size. */
504   if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
505     return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
506                             _("The sharding layout configuration "
507                               "of the hotcopy source does not match "
508                               "the sharding layout configuration of "
509                               "the hotcopy destination"));
510   return SVN_NO_ERROR;
511 }
512
513 /* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
514  * CANCEL_FUNC and CANCEL_BATON do the usual thing.
515  * Use POOL for temporary allocations.
516  */
517 static svn_error_t *
518 remove_folder(const char *path,
519               svn_cancel_func_t cancel_func,
520               void *cancel_baton,
521               apr_pool_t *pool)
522 {
523   svn_error_t *err = svn_io_remove_dir2(path, TRUE,
524                                         cancel_func, cancel_baton, pool);
525
526   if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
527     {
528       svn_error_clear(err);
529       err = SVN_NO_ERROR;
530     }
531
532   return svn_error_trace(err);
533 }
534
535 /* Copy the revision and revprop files (possibly sharded / packed) from
536  * SRC_FS to DST_FS.  Do not re-copy data which already exists in DST_FS.
537  * When copying packed or unpacked shards, checkpoint the result in DST_FS
538  * for every shard by updating the 'current' file if necessary.  Assume
539  * the >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT filesystem format without
540  * global next-ID counters.  Indicate progress via the optional NOTIFY_FUNC
541  * callback using NOTIFY_BATON.  Use POOL for temporary allocations.
542  */
543 static svn_error_t *
544 hotcopy_revisions(svn_fs_t *src_fs,
545                   svn_fs_t *dst_fs,
546                   svn_revnum_t src_youngest,
547                   svn_revnum_t dst_youngest,
548                   svn_boolean_t incremental,
549                   const char *src_revs_dir,
550                   const char *dst_revs_dir,
551                   const char *src_revprops_dir,
552                   const char *dst_revprops_dir,
553                   svn_fs_hotcopy_notify_t notify_func,
554                   void* notify_baton,
555                   svn_cancel_func_t cancel_func,
556                   void* cancel_baton,
557                   apr_pool_t *pool)
558 {
559   fs_fs_data_t *src_ffd = src_fs->fsap_data;
560   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
561   int max_files_per_dir = src_ffd->max_files_per_dir;
562   svn_revnum_t src_min_unpacked_rev;
563   svn_revnum_t dst_min_unpacked_rev;
564   svn_revnum_t rev;
565   apr_pool_t *iterpool;
566
567   /* Copy the min unpacked rev, and read its value. */
568   if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
569     {
570       SVN_ERR(svn_fs_fs__read_min_unpacked_rev(&src_min_unpacked_rev,
571                                                src_fs, pool));
572       SVN_ERR(svn_fs_fs__read_min_unpacked_rev(&dst_min_unpacked_rev,
573                                                dst_fs, pool));
574
575       /* We only support packs coming from the hotcopy source.
576        * The destination should not be packed independently from
577        * the source. This also catches the case where users accidentally
578        * swap the source and destination arguments. */
579       if (src_min_unpacked_rev < dst_min_unpacked_rev)
580         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
581                                  _("The hotcopy destination already contains "
582                                    "more packed revisions (%lu) than the "
583                                    "hotcopy source contains (%lu)"),
584                                    dst_min_unpacked_rev - 1,
585                                    src_min_unpacked_rev - 1);
586
587       SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
588                                    PATH_MIN_UNPACKED_REV, pool));
589     }
590   else
591     {
592       src_min_unpacked_rev = 0;
593       dst_min_unpacked_rev = 0;
594     }
595
596   if (cancel_func)
597     SVN_ERR(cancel_func(cancel_baton));
598
599   /*
600    * Copy the necessary rev files.
601    */
602
603   iterpool = svn_pool_create(pool);
604   /* First, copy packed shards. */
605   for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
606     {
607       svn_boolean_t skipped = TRUE;
608       svn_revnum_t pack_end_rev;
609
610       svn_pool_clear(iterpool);
611
612       if (cancel_func)
613         SVN_ERR(cancel_func(cancel_baton));
614
615       /* Copy the packed shard. */
616       SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev,
617                                         src_fs, dst_fs,
618                                         rev, max_files_per_dir,
619                                         iterpool));
620
621       pack_end_rev = rev + max_files_per_dir - 1;
622
623       /* Whenever this pack did not previously exist in the destination,
624        * update 'current' to the most recent packed rev (so readers can see
625        * new revisions which arrived in this pack). */
626       if (pack_end_rev > dst_youngest)
627         {
628           SVN_ERR(svn_fs_fs__write_current(dst_fs, pack_end_rev, 0, 0,
629                                            iterpool));
630         }
631
632       /* When notifying about packed shards, make things simpler by either
633        * reporting a full revision range, i.e [pack start, pack end] or
634        * reporting nothing. There is one case when this approach might not
635        * be exact (incremental hotcopy with a pack replacing last unpacked
636        * revisions), but generally this is good enough. */
637       if (notify_func && !skipped)
638         notify_func(notify_baton, rev, pack_end_rev, iterpool);
639
640       /* Remove revision files which are now packed. */
641       if (incremental)
642         {
643           SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
644                                            rev + max_files_per_dir,
645                                            max_files_per_dir, iterpool));
646           if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
647             SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
648                                                  rev + max_files_per_dir,
649                                                  max_files_per_dir,
650                                                  iterpool));
651         }
652
653       /* Now that all revisions have moved into the pack, the original
654        * rev dir can be removed. */
655       SVN_ERR(remove_folder(svn_fs_fs__path_rev_shard(dst_fs, rev, iterpool),
656                             cancel_func, cancel_baton, iterpool));
657       if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
658         SVN_ERR(remove_folder(svn_fs_fs__path_revprops_shard(dst_fs, rev,
659                                                              iterpool),
660                               cancel_func, cancel_baton, iterpool));
661     }
662
663   if (cancel_func)
664     SVN_ERR(cancel_func(cancel_baton));
665
666   SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
667   SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
668
669   /* Now, copy pairs of non-packed revisions and revprop files.
670    * If necessary, update 'current' after copying all files from a shard. */
671   for (; rev <= src_youngest; rev++)
672     {
673       svn_boolean_t skipped = TRUE;
674
675       svn_pool_clear(iterpool);
676
677       if (cancel_func)
678         SVN_ERR(cancel_func(cancel_baton));
679
680       /* Copying non-packed revisions is racy in case the source repository is
681        * being packed concurrently with this hotcopy operation. The race can
682        * happen with FS formats prior to SVN_FS_FS__MIN_PACK_LOCK_FORMAT that
683        * support packed revisions. With the pack lock, however, the race is
684        * impossible, because hotcopy and pack operations block each other.
685        *
686        * We assume that all revisions coming after 'min-unpacked-rev' really
687        * are unpacked and that's not necessarily true with concurrent packing.
688        * Don't try to be smart in this edge case, because handling it properly
689        * might require copying *everything* from the start. Just abort the
690        * hotcopy with an ENOENT (revision file moved to a pack, so it is no
691        * longer where we expect it to be). */
692
693       /* Copy the rev file. */
694       SVN_ERR(hotcopy_copy_shard_file(&skipped,
695                                       src_revs_dir, dst_revs_dir, rev,
696                                       max_files_per_dir,
697                                       iterpool));
698       /* Copy the revprop file. */
699       SVN_ERR(hotcopy_copy_shard_file(&skipped,
700                                       src_revprops_dir, dst_revprops_dir,
701                                       rev, max_files_per_dir,
702                                       iterpool));
703
704       /* Whenever this revision did not previously exist in the destination,
705        * checkpoint the progress via 'current' (do that once per full shard
706        * in order not to slow things down). */
707       if (rev > dst_youngest)
708         {
709           if (max_files_per_dir && (rev % max_files_per_dir == 0))
710             {
711               SVN_ERR(svn_fs_fs__write_current(dst_fs, rev, 0, 0,
712                                                iterpool));
713             }
714         }
715
716       if (notify_func && !skipped)
717         notify_func(notify_baton, rev, rev, iterpool);
718     }
719   svn_pool_destroy(iterpool);
720
721   /* We assume that all revisions were copied now, i.e. we didn't exit the
722    * above loop early. 'rev' was last incremented during exit of the loop. */
723   SVN_ERR_ASSERT(rev == src_youngest + 1);
724
725   return SVN_NO_ERROR;
726 }
727
728 /* Shortcut for the revision and revprop copying for old (1 or 2) format
729  * filesystems without sharding and packing.  Copy the non-sharded revision
730  * and revprop files from SRC_FS to DST_FS.  Do not re-copy data which
731  * already exists in DST_FS.  Do not somehow checkpoint the results in
732  * the 'current' file in DST_FS.  Indicate progress via the optional
733  * NOTIFY_FUNC callback using NOTIFY_BATON.  Use POOL for temporary
734  * allocations.  Also see hotcopy_revisions().
735  */
736 static svn_error_t *
737 hotcopy_revisions_old(svn_fs_t *src_fs,
738                       svn_fs_t *dst_fs,
739                       svn_revnum_t src_youngest,
740                       const char *src_revs_dir,
741                       const char *dst_revs_dir,
742                       const char *src_revprops_dir,
743                       const char *dst_revprops_dir,
744                       svn_fs_hotcopy_notify_t notify_func,
745                       void* notify_baton,
746                       svn_cancel_func_t cancel_func,
747                       void* cancel_baton,
748                       apr_pool_t *pool)
749 {
750   apr_pool_t *iterpool = svn_pool_create(pool);
751   svn_revnum_t rev;
752
753   for (rev = 0; rev <= src_youngest; rev++)
754     {
755       svn_boolean_t skipped = TRUE;
756
757       svn_pool_clear(iterpool);
758
759       if (cancel_func)
760         SVN_ERR(cancel_func(cancel_baton));
761
762       SVN_ERR(hotcopy_io_dir_file_copy(&skipped, src_revs_dir, dst_revs_dir,
763                                        apr_psprintf(iterpool, "%ld", rev),
764                                        iterpool));
765       SVN_ERR(hotcopy_io_dir_file_copy(&skipped, src_revprops_dir,
766                                        dst_revprops_dir,
767                                        apr_psprintf(iterpool, "%ld", rev),
768                                        iterpool));
769
770       if (notify_func && !skipped)
771         notify_func(notify_baton, rev, rev, iterpool);
772     }
773     svn_pool_destroy(iterpool);
774
775     return SVN_NO_ERROR;
776 }
777
778 /* Baton for hotcopy_body(). */
779 struct hotcopy_body_baton {
780   svn_fs_t *src_fs;
781   svn_fs_t *dst_fs;
782   svn_boolean_t incremental;
783   svn_fs_hotcopy_notify_t notify_func;
784   void *notify_baton;
785   svn_cancel_func_t cancel_func;
786   void *cancel_baton;
787 };
788
789 /* Perform a hotcopy, either normal or incremental.
790  *
791  * Normal hotcopy assumes that the destination exists as an empty
792  * directory. It behaves like an incremental hotcopy except that
793  * none of the copied files already exist in the destination.
794  *
795  * An incremental hotcopy copies only changed or new files to the destination,
796  * and removes files from the destination no longer present in the source.
797  * While the incremental hotcopy is running, readers should still be able
798  * to access the destintation repository without error and should not see
799  * revisions currently in progress of being copied. Readers are able to see
800  * new fully copied revisions even if the entire incremental hotcopy procedure
801  * has not yet completed.
802  *
803  * Writers are blocked out completely during the entire incremental hotcopy
804  * process to ensure consistency. This function assumes that the repository
805  * write-lock is held.
806  */
807 static svn_error_t *
808 hotcopy_body(void *baton, apr_pool_t *pool)
809 {
810   struct hotcopy_body_baton *hbb = baton;
811   svn_fs_t *src_fs = hbb->src_fs;
812   fs_fs_data_t *src_ffd = src_fs->fsap_data;
813   svn_fs_t *dst_fs = hbb->dst_fs;
814   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
815   svn_boolean_t incremental = hbb->incremental;
816   svn_fs_hotcopy_notify_t notify_func = hbb->notify_func;
817   void* notify_baton = hbb->notify_baton;
818   svn_cancel_func_t cancel_func = hbb->cancel_func;
819   void* cancel_baton = hbb->cancel_baton;
820   svn_revnum_t src_youngest;
821   apr_uint64_t src_next_node_id;
822   apr_uint64_t src_next_copy_id;
823   svn_revnum_t dst_youngest;
824   const char *src_revprops_dir;
825   const char *dst_revprops_dir;
826   const char *src_revs_dir;
827   const char *dst_revs_dir;
828   const char *src_subdir;
829   const char *dst_subdir;
830   svn_node_kind_t kind;
831
832   /* Try to copy the config.
833    *
834    * ### We try copying the config file before doing anything else,
835    * ### because higher layers will abort the hotcopy if we throw
836    * ### an error from this function, and that renders the hotcopy
837    * ### unusable anyway. */
838   if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
839     {
840       svn_error_t *err;
841
842       err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
843                                  pool);
844       if (err)
845         {
846           if (APR_STATUS_IS_ENOENT(err->apr_err))
847             {
848               /* 1.6.0 to 1.6.11 did not copy the configuration file during
849                * hotcopy. So if we're hotcopying a repository which has been
850                * created as a hotcopy itself, it's possible that fsfs.conf
851                * does not exist. Ask the user to re-create it.
852                *
853                * ### It would be nice to make this a non-fatal error,
854                * ### but this function does not get an svn_fs_t object
855                * ### so we have no way of just printing a warning via
856                * ### the fs->warning() callback. */
857
858               const char *src_abspath;
859               const char *dst_abspath;
860               const char *config_relpath;
861               svn_error_t *err2;
862
863               config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
864               err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
865               if (err2)
866                 return svn_error_trace(svn_error_compose_create(err, err2));
867               err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
868               if (err2)
869                 return svn_error_trace(svn_error_compose_create(err, err2));
870
871               /* ### hack: strip off the 'db/' directory from paths so
872                * ### they make sense to the user */
873               src_abspath = svn_dirent_dirname(src_abspath, pool);
874               dst_abspath = svn_dirent_dirname(dst_abspath, pool);
875
876               return svn_error_quick_wrapf(err,
877                                  _("Failed to create hotcopy at '%s'. "
878                                    "The file '%s' is missing from the source "
879                                    "repository. Please create this file, for "
880                                    "instance by running 'svnadmin upgrade %s'"),
881                                  dst_abspath, config_relpath, src_abspath);
882             }
883           else
884             return svn_error_trace(err);
885         }
886     }
887
888   if (cancel_func)
889     SVN_ERR(cancel_func(cancel_baton));
890
891   /* Find the youngest revision in the source and destination.
892    * We only support hotcopies from sources with an equal or greater amount
893    * of revisions than the destination.
894    * This also catches the case where users accidentally swap the
895    * source and destination arguments. */
896   SVN_ERR(svn_fs_fs__read_current(&src_youngest, &src_next_node_id,
897                                   &src_next_copy_id, src_fs, pool));
898   if (incremental)
899     {
900       SVN_ERR(svn_fs_fs__youngest_rev(&dst_youngest, dst_fs, pool));
901       if (src_youngest < dst_youngest)
902         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
903                  _("The hotcopy destination already contains more revisions "
904                    "(%lu) than the hotcopy source contains (%lu); are source "
905                    "and destination swapped?"),
906                    dst_youngest, src_youngest);
907     }
908   else
909     dst_youngest = 0;
910
911   src_revs_dir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
912   dst_revs_dir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
913   src_revprops_dir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
914   dst_revprops_dir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
915
916   /* Ensure that the required folders exist in the destination
917    * before actually copying the revisions and revprops. */
918   SVN_ERR(svn_io_make_dir_recursively(dst_revs_dir, pool));
919   SVN_ERR(svn_io_make_dir_recursively(dst_revprops_dir, pool));
920
921   if (cancel_func)
922     SVN_ERR(cancel_func(cancel_baton));
923
924   /* Split the logic for new and old FS formats. The latter is much simpler
925    * due to the absense of sharding and packing. However, it requires special
926    * care when updating the 'current' file (which contains not just the
927    * revision number, but also the next-ID counters). */
928   if (src_ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
929     {
930       SVN_ERR(hotcopy_revisions(src_fs, dst_fs, src_youngest, dst_youngest,
931                                 incremental, src_revs_dir, dst_revs_dir,
932                                 src_revprops_dir, dst_revprops_dir,
933                                 notify_func, notify_baton,
934                                 cancel_func, cancel_baton, pool));
935       SVN_ERR(svn_fs_fs__write_current(dst_fs, src_youngest, 0, 0, pool));
936     }
937   else
938     {
939       SVN_ERR(hotcopy_revisions_old(src_fs, dst_fs, src_youngest,
940                                     src_revs_dir, dst_revs_dir,
941                                     src_revprops_dir, dst_revprops_dir,
942                                     notify_func, notify_baton,
943                                     cancel_func, cancel_baton, pool));
944       SVN_ERR(svn_fs_fs__write_current(dst_fs, src_youngest, src_next_node_id,
945                                        src_next_copy_id, pool));
946     }
947
948   /* Replace the locks tree.
949    * This is racy in case readers are currently trying to list locks in
950    * the destination. However, we need to get rid of stale locks.
951    * This is the simplest way of doing this, so we accept this small race. */
952   dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
953   SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
954                              pool));
955   src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
956   SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
957   if (kind == svn_node_dir)
958     SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
959                                         PATH_LOCKS_DIR, TRUE,
960                                         cancel_func, cancel_baton, pool));
961
962   /* Now copy the node-origins cache tree. */
963   src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
964   SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
965   if (kind == svn_node_dir)
966     SVN_ERR(hotcopy_io_copy_dir_recursively(NULL, src_subdir, dst_fs->path,
967                                             PATH_NODE_ORIGINS_DIR, TRUE,
968                                             cancel_func, cancel_baton, pool));
969
970   /*
971    * NB: Data copied below is only read by writers, not readers.
972    *     Writers are still locked out at this point.
973    */
974
975   if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
976     {
977       /* Copy the rep cache and then remove entries for revisions
978        * that did not make it into the destination. */
979       src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
980       dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
981       SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
982       if (kind == svn_node_file)
983         {
984           SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
985
986           /* The source might have r/o flags set on it - which would be
987              carried over to the copy. */
988           SVN_ERR(svn_io_set_file_read_write(dst_subdir, FALSE, pool));
989           SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, src_youngest, pool));
990         }
991     }
992
993   /* Copy the txn-current file. */
994   if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
995     SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
996                                  PATH_TXN_CURRENT, pool));
997
998   return SVN_NO_ERROR;
999 }
1000
1001 /* Create an empty filesystem at DST_FS at DST_PATH with the same
1002  * configuration as SRC_FS (uuid, format, and other parameters).
1003  * After creation DST_FS has no revisions, not even revision zero. */
1004 static svn_error_t *
1005 hotcopy_create_empty_dest(svn_fs_t *src_fs,
1006                           svn_fs_t *dst_fs,
1007                           const char *dst_path,
1008                           apr_pool_t *pool)
1009 {
1010   fs_fs_data_t *src_ffd = src_fs->fsap_data;
1011
1012   /* Create the DST_FS repository with the same layout as SRC_FS. */
1013   SVN_ERR(svn_fs_fs__create_file_tree(dst_fs, dst_path, src_ffd->format,
1014                                       src_ffd->max_files_per_dir,
1015                                       src_ffd->use_log_addressing,
1016                                       pool));
1017
1018   /* Copy the UUID.  Hotcopy destination receives a new instance ID, but
1019    * has the same filesystem UUID as the source. */
1020   SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, NULL, pool));
1021
1022   /* Remove revision 0 contents.  Otherwise, it may not get overwritten
1023    * due to having a newer timestamp. */
1024   SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_rev(dst_fs, 0, pool), pool));
1025   SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_revprops(dst_fs, 0, pool),
1026                               pool));
1027
1028   /* This filesystem is ready.  Stamp it with a format number.  Fail if
1029    * the 'format' file should already exist. */
1030   SVN_ERR(svn_fs_fs__write_format(dst_fs, FALSE, pool));
1031
1032   return SVN_NO_ERROR;
1033 }
1034
1035 svn_error_t *
1036 svn_fs_fs__hotcopy_prepare_target(svn_fs_t *src_fs,
1037                                   svn_fs_t *dst_fs,
1038                                   const char *dst_path,
1039                                   svn_boolean_t incremental,
1040                                   apr_pool_t *pool)
1041 {
1042   if (incremental)
1043     {
1044       const char *dst_format_abspath;
1045       svn_node_kind_t dst_format_kind;
1046
1047       /* Check destination format to be sure we know how to incrementally
1048        * hotcopy to the destination FS. */
1049       dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
1050       SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
1051       if (dst_format_kind == svn_node_none)
1052         {
1053           /* Destination doesn't exist yet. Perform a normal hotcopy to a
1054            * empty destination using the same configuration as the source. */
1055           SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
1056         }
1057       else
1058         {
1059           /* Check the existing repository. */
1060           SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
1061           SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
1062                                                           pool));
1063         }
1064     }
1065   else
1066     {
1067       /* Start out with an empty destination using the same configuration
1068        * as the source. */
1069       SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
1070     }
1071
1072   return SVN_NO_ERROR;
1073 }
1074
1075 svn_error_t *
1076 svn_fs_fs__hotcopy(svn_fs_t *src_fs,
1077                    svn_fs_t *dst_fs,
1078                    svn_boolean_t incremental,
1079                    svn_fs_hotcopy_notify_t notify_func,
1080                    void *notify_baton,
1081                    svn_cancel_func_t cancel_func,
1082                    void *cancel_baton,
1083                    apr_pool_t *pool)
1084 {
1085   struct hotcopy_body_baton hbb;
1086
1087   hbb.src_fs = src_fs;
1088   hbb.dst_fs = dst_fs;
1089   hbb.incremental = incremental;
1090   hbb.notify_func = notify_func;
1091   hbb.notify_baton = notify_baton;
1092   hbb.cancel_func = cancel_func;
1093   hbb.cancel_baton = cancel_baton;
1094   SVN_ERR(svn_fs_fs__with_all_locks(dst_fs, hotcopy_body, &hbb, pool));
1095
1096   return SVN_NO_ERROR;
1097 }