/* Copy the file or directory tree FROM_PATH to TO_PATH which must not exist * beforehand. */ svn_error_t * sbox_disk_copy(svn_test__sandbox_t *b, const char *from_path, const char *to_path) { const char *to_dir, *to_name; from_path = sbox_wc_path(b, from_path); to_path = sbox_wc_path(b, to_path); svn_dirent_split(&to_dir, &to_name, to_path, b->pool); return svn_io_copy_dir_recursively(from_path, to_dir, to_name, FALSE, NULL, NULL, b->pool); }
/* Perform a hotcopy, either normal or incremental. * * Normal hotcopy assumes that the destination exists as an empty * directory. It behaves like an incremental hotcopy except that * none of the copied files already exist in the destination. * * An incremental hotcopy copies only changed or new files to the destination, * and removes files from the destination no longer present in the source. * While the incremental hotcopy is running, readers should still be able * to access the destintation repository without error and should not see * revisions currently in progress of being copied. Readers are able to see * new fully copied revisions even if the entire incremental hotcopy procedure * has not yet completed. * * Writers are blocked out completely during the entire incremental hotcopy * process to ensure consistency. This function assumes that the repository * write-lock is held. */ static svn_error_t * hotcopy_body(void *baton, apr_pool_t *pool) { struct hotcopy_body_baton *hbb = baton; svn_fs_t *src_fs = hbb->src_fs; fs_fs_data_t *src_ffd = src_fs->fsap_data; svn_fs_t *dst_fs = hbb->dst_fs; fs_fs_data_t *dst_ffd = dst_fs->fsap_data; svn_boolean_t incremental = hbb->incremental; svn_fs_hotcopy_notify_t notify_func = hbb->notify_func; void* notify_baton = hbb->notify_baton; svn_cancel_func_t cancel_func = hbb->cancel_func; void* cancel_baton = hbb->cancel_baton; svn_revnum_t src_youngest; apr_uint64_t src_next_node_id; apr_uint64_t src_next_copy_id; svn_revnum_t dst_youngest; const char *src_revprops_dir; const char *dst_revprops_dir; const char *src_revs_dir; const char *dst_revs_dir; const char *src_subdir; const char *dst_subdir; svn_node_kind_t kind; /* Try to copy the config. * * ### We try copying the config file before doing anything else, * ### because higher layers will abort the hotcopy if we throw * ### an error from this function, and that renders the hotcopy * ### unusable anyway. */ if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE) { svn_error_t *err; err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG, pool); if (err) { if (APR_STATUS_IS_ENOENT(err->apr_err)) { /* 1.6.0 to 1.6.11 did not copy the configuration file during * hotcopy. So if we're hotcopying a repository which has been * created as a hotcopy itself, it's possible that fsfs.conf * does not exist. Ask the user to re-create it. * * ### It would be nice to make this a non-fatal error, * ### but this function does not get an svn_fs_t object * ### so we have no way of just printing a warning via * ### the fs->warning() callback. */ const char *src_abspath; const char *dst_abspath; const char *config_relpath; svn_error_t *err2; config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool); err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool); if (err2) return svn_error_trace(svn_error_compose_create(err, err2)); err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool); if (err2) return svn_error_trace(svn_error_compose_create(err, err2)); /* ### hack: strip off the 'db/' directory from paths so * ### they make sense to the user */ src_abspath = svn_dirent_dirname(src_abspath, pool); dst_abspath = svn_dirent_dirname(dst_abspath, pool); return svn_error_quick_wrapf(err, _("Failed to create hotcopy at '%s'. " "The file '%s' is missing from the source " "repository. Please create this file, for " "instance by running 'svnadmin upgrade %s'"), dst_abspath, config_relpath, src_abspath); } else return svn_error_trace(err); } } if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); /* Find the youngest revision in the source and destination. * We only support hotcopies from sources with an equal or greater amount * of revisions than the destination. * This also catches the case where users accidentally swap the * source and destination arguments. */ SVN_ERR(svn_fs_fs__read_current(&src_youngest, &src_next_node_id, &src_next_copy_id, src_fs, pool)); if (incremental) { SVN_ERR(svn_fs_fs__youngest_rev(&dst_youngest, dst_fs, pool)); if (src_youngest < dst_youngest) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("The hotcopy destination already contains more revisions " "(%lu) than the hotcopy source contains (%lu); are source " "and destination swapped?"), dst_youngest, src_youngest); } else dst_youngest = 0; src_revs_dir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool); dst_revs_dir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool); src_revprops_dir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool); dst_revprops_dir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool); /* Ensure that the required folders exist in the destination * before actually copying the revisions and revprops. */ SVN_ERR(svn_io_make_dir_recursively(dst_revs_dir, pool)); SVN_ERR(svn_io_make_dir_recursively(dst_revprops_dir, pool)); if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); /* Split the logic for new and old FS formats. The latter is much simpler * due to the absense of sharding and packing. However, it requires special * care when updating the 'current' file (which contains not just the * revision number, but also the next-ID counters). */ if (src_ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) { SVN_ERR(hotcopy_revisions(src_fs, dst_fs, src_youngest, dst_youngest, incremental, src_revs_dir, dst_revs_dir, src_revprops_dir, dst_revprops_dir, notify_func, notify_baton, cancel_func, cancel_baton, pool)); SVN_ERR(svn_fs_fs__write_current(dst_fs, src_youngest, 0, 0, pool)); } else { SVN_ERR(hotcopy_revisions_old(src_fs, dst_fs, src_youngest, src_revs_dir, dst_revs_dir, src_revprops_dir, dst_revprops_dir, notify_func, notify_baton, cancel_func, cancel_baton, pool)); SVN_ERR(svn_fs_fs__write_current(dst_fs, src_youngest, src_next_node_id, src_next_copy_id, pool)); } /* Replace the locks tree. * This is racy in case readers are currently trying to list locks in * the destination. However, we need to get rid of stale locks. * This is the simplest way of doing this, so we accept this small race. */ dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool); SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton, pool)); src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool); SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); if (kind == svn_node_dir) SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path, PATH_LOCKS_DIR, TRUE, cancel_func, cancel_baton, pool)); /* Now copy the node-origins cache tree. */ src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool); SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); if (kind == svn_node_dir) SVN_ERR(hotcopy_io_copy_dir_recursively(NULL, src_subdir, dst_fs->path, PATH_NODE_ORIGINS_DIR, TRUE, cancel_func, cancel_baton, pool)); /* * NB: Data copied below is only read by writers, not readers. * Writers are still locked out at this point. */ if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) { /* Copy the rep cache and then remove entries for revisions * that did not make it into the destination. */ src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool); dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool); SVN_ERR(svn_io_check_path(src_subdir, &kind, pool)); if (kind == svn_node_file) { SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool)); /* The source might have r/o flags set on it - which would be carried over to the copy. */ SVN_ERR(svn_io_set_file_read_write(dst_subdir, FALSE, pool)); SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, src_youngest, pool)); } } /* Copy the txn-current file. */ if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT) SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_TXN_CURRENT, pool)); return SVN_NO_ERROR; }
/* Make a copy of the filesystem node (or tree if RECURSIVE) at SRC_ABSPATH under a temporary name in the directory TMPDIR_ABSPATH and return the absolute path of the copy in *DST_ABSPATH. Return the node kind of SRC_ABSPATH in *KIND. If SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate that no copy was made. */ static svn_error_t * copy_to_tmpdir(svn_skel_t **work_item, svn_node_kind_t *kind, svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, const char *tmpdir_abspath, svn_boolean_t file_copy, svn_boolean_t unversioned, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_boolean_t is_special; svn_io_file_del_t delete_when; const char *dst_tmp_abspath; svn_node_kind_t dsk_kind; if (!kind) kind = &dsk_kind; *work_item = NULL; SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special, scratch_pool)); if (*kind == svn_node_none) { return SVN_NO_ERROR; } else if (*kind == svn_node_unknown) { return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Source '%s' is unexpected kind"), svn_dirent_local_style(src_abspath, scratch_pool)); } else if (*kind == svn_node_dir || is_special) delete_when = svn_io_file_del_on_close; else /* the default case: (*kind == svn_node_file) */ delete_when = svn_io_file_del_none; /* ### Do we need a pool cleanup to remove the copy? We can't use ### svn_io_file_del_on_pool_cleanup above because a) it won't ### handle the directory case and b) we need to be able to remove ### the cleanup before queueing the move work item. */ if (file_copy && !unversioned) { svn_boolean_t modified; /* It's faster to look for mods on the source now, as the timestamp might match, than to examine the destination later as the destination timestamp will never match. */ SVN_ERR(svn_wc__internal_file_modified_p(&modified, db, src_abspath, FALSE, scratch_pool)); if (!modified) { /* Why create a temp copy if we can just reinstall from pristine? */ SVN_ERR(svn_wc__wq_build_file_install(work_item, db, dst_abspath, NULL, FALSE, TRUE, result_pool, scratch_pool)); return SVN_NO_ERROR; } } /* Set DST_TMP_ABSPATH to a temporary unique path. If *KIND is file, leave a file there and then overwrite it; otherwise leave no node on disk at that path. In the latter case, something else might use that path before we get around to using it a moment later, but never mind. */ SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath, delete_when, scratch_pool, scratch_pool)); if (*kind == svn_node_dir) { if (file_copy) SVN_ERR(svn_io_copy_dir_recursively( src_abspath, tmpdir_abspath, svn_dirent_basename(dst_tmp_abspath, scratch_pool), TRUE, /* copy_perms */ cancel_func, cancel_baton, scratch_pool)); else SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool)); } else if (!is_special) SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath, TRUE /* copy_perms */, scratch_pool)); else SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool)); if (file_copy) { /* Remove 'read-only' from the destination file; it's a local add now. */ SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath, FALSE, scratch_pool)); } SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath, dst_tmp_abspath, dst_abspath, result_pool, scratch_pool)); return SVN_NO_ERROR; }