/* Write the format number and maximum number of files per directory to a new format file in PATH, overwriting a previously existing file. Use POOL for temporary allocation. This implementation is largely stolen from libsvn_fs_fs/fs_fs.c. */ static svn_error_t * write_format(const char *path, int format, int max_files_per_dir, apr_pool_t *pool) { const char *contents; path = svn_path_join(path, "format", pool); if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) { if (max_files_per_dir) contents = apr_psprintf(pool, "%d\n" "layout sharded %d\n", format, max_files_per_dir); else contents = apr_psprintf(pool, "%d\n" "layout linear", format); } else { contents = apr_psprintf(pool, "%d\n", format); } { const char *path_tmp; SVN_ERR(svn_io_write_unique(&path_tmp, svn_path_dirname(path, pool), contents, strlen(contents), svn_io_file_del_none, pool)); #ifdef WIN32 /* make the destination writable, but only on Windows, because Windows does not let us replace read-only files. */ SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool)); #endif /* WIN32 */ /* rename the temp file as the real destination */ SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); } /* And set the perms to make it read only */ return svn_io_set_file_read_only(path, FALSE, pool); }
svn_error_t * svn_wc__write_old_wcprops(const char *path, apr_hash_t *prophash, svn_node_kind_t kind, apr_pool_t *scratch_pool) { apr_pool_t *pool = scratch_pool; const char *parent_dir; const char *base_name; svn_stream_t *stream; const char *temp_dir_path; const char *temp_prop_path; const char *prop_path; int wc_format_version; if (kind == svn_node_dir) parent_dir = path; else svn_path_split(path, &parent_dir, &base_name, pool); /* At this point, we know we need to open a file in the admin area of parent_dir. First check that parent_dir is a working copy: */ SVN_ERR(svn_wc_check_wc(parent_dir, &wc_format_version, pool)); if (wc_format_version == 0) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("'%s' is not a working copy"), svn_path_local_style(parent_dir, pool)); /* Write to a temp file, then rename into place. */ temp_dir_path = svn_wc__adm_child(parent_dir, SVN_WC__ADM_TMP, pool); SVN_ERR(svn_stream_open_unique(&stream, &temp_prop_path, temp_dir_path, svn_io_file_del_none, pool, pool)); SVN_ERR_W(svn_hash_write2(prophash, stream, SVN_HASH_TERMINATOR, pool), apr_psprintf(pool, _("Cannot write property hash for '%s'"), svn_path_local_style(path, pool))); svn_stream_close(stream); /* Close file, then do an atomic "move". */ SVN_ERR(svn_wc__prop_path(&prop_path, path, kind, svn_wc__props_wcprop, pool)); SVN_ERR(svn_io_file_rename(temp_prop_path, prop_path, pool)); return svn_io_set_file_read_only(prop_path, FALSE, pool); }
/* Write the format number and maximum number of files per directory to a new format file in PATH, overwriting a previously existing file. Use POOL for temporary allocation. (This implementation is largely stolen from libsvn_fs_fs/fs_fs.c.) */ static svn_error_t * write_format(const char *path, int format, int max_files_per_dir, apr_pool_t *pool) { const char *contents; path = svn_dirent_join(path, "format", pool); if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) { if (max_files_per_dir) contents = apr_psprintf(pool, "%d\n" "layout sharded %d\n", format, max_files_per_dir); else contents = apr_psprintf(pool, "%d\n" "layout linear", format); } else { contents = apr_psprintf(pool, "%d\n", format); } { const char *path_tmp; SVN_ERR(svn_io_write_unique(&path_tmp, svn_dirent_dirname(path, pool), contents, strlen(contents), svn_io_file_del_none, pool)); /* rename the temp file as the real destination */ SVN_ERR(svn_io_file_rename(path_tmp, path, pool)); } /* And set the perms to make it read only */ return svn_io_set_file_read_only(path, FALSE, pool); }
svn_error_t * svn_wc__close_adm_stream(svn_stream_t *stream, const char *temp_file_path, const char *path, const char *fname, apr_pool_t *scratch_pool) { const char *tmp_path = extend_with_adm_name(path, NULL, TRUE, scratch_pool, fname, NULL); const char *dst_path = extend_with_adm_name(path, NULL, FALSE, scratch_pool, fname, NULL); /* ### eventually, just use the parameter rather than compute tmp_path */ SVN_ERR_ASSERT(strcmp(temp_file_path, tmp_path) == 0); SVN_ERR(svn_stream_close(stream)); /* Put the completed file into its intended location. */ SVN_ERR(svn_io_file_rename(tmp_path, dst_path, scratch_pool)); return svn_io_set_file_read_only(dst_path, FALSE, scratch_pool); }
/* Rename a tmp text-base file to its real text-base name. The file had better already be closed. */ svn_error_t * svn_wc__sync_text_base(const char *path, apr_pool_t *pool) { const char *parent_path; const char *base_name; const char *tmp_path; const char *base_path; svn_path_split(path, &parent_path, &base_name, pool); /* Extend tmp name. */ tmp_path = extend_with_adm_name(parent_path, SVN_WC__BASE_EXT, TRUE, pool, SVN_WC__ADM_TEXT_BASE, base_name, NULL); /* Extend real name. */ base_path = extend_with_adm_name(parent_path, SVN_WC__BASE_EXT, FALSE, pool, SVN_WC__ADM_TEXT_BASE, base_name, NULL); /* Rename. */ SVN_ERR(svn_io_file_rename(tmp_path, base_path, pool)); return svn_io_set_file_read_only(base_path, FALSE, pool); }
/* Make an unversioned copy of the versioned file at FROM_ABSPATH. Copy it * to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * Expand the file's keywords according to the source file's 'svn:keywords' * property, if present. If copying a locally modified working version, * append 'M' to the revision number and use '(local)' for the author. * * Translate the file's line endings according to the source file's * 'svn:eol-style' property, if present. If NATIVE_EOL is not NULL, use it * in place of the native EOL style. Throw an error if the source file has * inconsistent line endings and EOL translation is attempted. * * Set the destination file's modification time to the source file's * modification time if copying the working version and the working version * is locally modified; otherwise set it to the versioned file's last * changed time. * * Set the destination file's 'executable' flag according to the source * file's 'svn:executable' property. */ static svn_error_t * copy_one_versioned_file(const char *from_abspath, const char *to_abspath, svn_client_ctx_t *ctx, const svn_opt_revision_t *revision, const char *native_eol, svn_boolean_t ignore_keywords, apr_pool_t *scratch_pool) { apr_hash_t *kw = NULL; svn_subst_eol_style_t style; apr_hash_t *props; svn_string_t *eol_style, *keywords, *executable, *special; const char *eol = NULL; svn_boolean_t local_mod = FALSE; apr_time_t tm; svn_stream_t *source; svn_stream_t *dst_stream; const char *dst_tmp; svn_error_t *err; svn_boolean_t is_deleted; svn_wc_context_t *wc_ctx = ctx->wc_ctx; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, from_abspath, scratch_pool)); /* Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind == svn_opt_revision_working && is_deleted) return SVN_NO_ERROR; if (revision->kind != svn_opt_revision_working) { /* Only export 'added' files when the revision is WORKING. This is not WORKING, so skip the 'added' files, since they didn't exist in the BASE revision and don't have an associated text-base. 'replaced' files are technically the same as 'added' files. ### TODO: Handle replaced nodes properly. ### svn_opt_revision_base refers to the "new" ### base of the node. That means, if a node is locally ### replaced, export skips this node, as if it was locally ### added, because svn_opt_revision_base refers to the base ### of the added node, not to the node that was deleted. ### In contrast, when the node is copied-here or moved-here, ### the copy/move source's content will be exported. ### It is currently not possible to export the revert-base ### when a node is locally replaced. We need a new ### svn_opt_revision_ enum value for proper distinction ### between revert-base and commit-base. Copied-/moved-here nodes have a base, so export both added and replaced files when they involve a copy-/move-here. We get all this for free from evaluating SOURCE == NULL: */ SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (source == NULL) return SVN_NO_ERROR; SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } else { svn_wc_status3_t *status; /* ### hmm. this isn't always a specialfile. this will simply open ### the file readonly if it is a regular file. */ SVN_ERR(svn_subst_read_specialfile(&source, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_status3(&status, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (status->text_status != svn_wc_status_normal) local_mod = TRUE; } /* We can early-exit if we're creating a special file. */ special = apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING); if (special != NULL) { /* Create the destination as a special file, and copy the source details into the destination stream. */ SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath, scratch_pool, scratch_pool)); return svn_error_trace( svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool)); } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); executable = apr_hash_get(props, SVN_PROP_EXECUTABLE, APR_HASH_KEY_STRING); if (eol_style) SVN_ERR(get_eol_style(&style, &eol, eol_style->data, native_eol)); if (local_mod) { /* Use the modified time from the working copy of the file */ SVN_ERR(svn_io_file_affected_time(&tm, from_abspath, scratch_pool)); } else { SVN_ERR(svn_wc__node_get_changed_info(NULL, &tm, NULL, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } if (keywords) { svn_revnum_t changed_rev; const char *suffix; const char *url; const char *author; SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, NULL, &author, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (local_mod) { /* For locally modified files, we'll append an 'M' to the revision number, and set the author to "(local)" since we can't always determine the current user's username */ suffix = "M"; author = _("(local)"); } else { suffix = ""; } SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, apr_psprintf(scratch_pool, "%ld%s", changed_rev, suffix), url, tm, author, scratch_pool)); } /* For atomicity, we translate to a tmp file and then rename the tmp file over the real destination. */ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, svn_dirent_dirname(to_abspath, scratch_pool), svn_io_file_del_none, scratch_pool, scratch_pool)); /* If some translation is needed, then wrap the output stream (this is more efficient than wrapping the input). */ if (eol || (kw && (apr_hash_count(kw) > 0))) dst_stream = svn_subst_stream_translated(dst_stream, eol, FALSE /* repair */, kw, ! ignore_keywords /* expand */, scratch_pool); /* ###: use cancel func/baton in place of NULL/NULL below. */ err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool); if (!err && executable) err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool); if (!err) err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool); if (err) return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE, scratch_pool)); /* Now that dst_tmp contains the translated data, do the atomic rename. */ SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool)); if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath, svn_wc_notify_update_add, scratch_pool); notify->kind = svn_node_file; (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } return SVN_NO_ERROR; }
/* Remove the directory at LOCAL_ABSPATH from revision control, and do the * same to any revision controlled directories underneath LOCAL_ABSPATH * (including directories not referred to by parent svn administrative areas); * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a * unique name in the same parent directory. * * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control. * * Use SCRATCH_POOL for all temporary allocation. */ static svn_error_t * relegate_dir_external(svn_wc_context_t *wc_ctx, const char *wri_abspath, const char *local_abspath, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath, FALSE, scratch_pool, scratch_pool)); err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE, cancel_func, cancel_baton, scratch_pool); if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) { const char *parent_dir; const char *dirname; const char *new_path; svn_error_clear(err); err = SVN_NO_ERROR; svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool); /* Reserve the new dir name. */ SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path, parent_dir, dirname, ".OLD", svn_io_file_del_none, scratch_pool, scratch_pool)); /* Sigh... We must fall ever so slightly from grace. Ideally, there would be no window, however brief, when we don't have a reservation on the new name. Unfortunately, at least in the Unix (Linux?) version of apr_file_rename(), you can't rename a directory over a file, because it's just calling stdio rename(), which says: ENOTDIR A component used as a directory in oldpath or newpath path is not, in fact, a directory. Or, oldpath is a directory, and newpath exists but is not a directory So instead, we get the name, then remove the file (ugh), then rename the directory, hoping that nobody has gotten that name in the meantime -- which would never happen in real life, so no big deal. */ /* Do our best, but no biggy if it fails. The rename will fail. */ svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool)); /* Rename. If this is still a working copy we should use the working copy rename function (to release open handles) */ err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) { svn_error_clear(err); /* And if it is no longer a working copy, we should just rename it */ err = svn_io_file_rename(local_abspath, new_path, scratch_pool); } /* ### TODO: We should notify the user about the rename */ if (notify_func) { svn_wc_notify_t *notify; notify = svn_wc_create_notify(err ? local_abspath : new_path, svn_wc_notify_left_local_modifications, scratch_pool); notify->kind = svn_node_dir; notify->err = err; notify_func(notify_baton, notify, scratch_pool); } } return svn_error_trace(err); }
/* Write to DIGEST_PATH a representation of CHILDREN (which may be empty, if the versioned path in FS represented by DIGEST_PATH has no children) and LOCK (which may be NULL if that versioned path is lock itself locked). Set the permissions of DIGEST_PATH to those of PERMS_REFERENCE. Use POOL for all allocations. */ static svn_error_t * write_digest_file(apr_hash_t *children, svn_lock_t *lock, const char *fs_path, const char *digest_path, const char *perms_reference, apr_pool_t *pool) { svn_error_t *err = SVN_NO_ERROR; svn_stream_t *stream; apr_hash_index_t *hi; apr_hash_t *hash = apr_hash_make(pool); const char *tmp_path; SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR, pool), fs_path, pool)); SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool), fs_path, pool)); if (lock) { const char *creation_date = NULL, *expiration_date = NULL; if (lock->creation_date) creation_date = svn_time_to_cstring(lock->creation_date, pool); if (lock->expiration_date) expiration_date = svn_time_to_cstring(lock->expiration_date, pool); hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1, lock->path, APR_HASH_KEY_STRING, pool); hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1, lock->token, APR_HASH_KEY_STRING, pool); hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1, lock->owner, APR_HASH_KEY_STRING, pool); hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1, lock->comment, APR_HASH_KEY_STRING, pool); hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1, lock->is_dav_comment ? "1" : "0", 1, pool); hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1, creation_date, APR_HASH_KEY_STRING, pool); hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1, expiration_date, APR_HASH_KEY_STRING, pool); } if (apr_hash_count(children)) { svn_stringbuf_t *children_list = svn_stringbuf_create("", pool); for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi)) { svn_stringbuf_appendbytes(children_list, svn__apr_hash_index_key(hi), svn__apr_hash_index_klen(hi)); svn_stringbuf_appendbyte(children_list, '\n'); } hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1, children_list->data, children_list->len, pool); } SVN_ERR(svn_stream_open_unique(&stream, &tmp_path, svn_dirent_dirname(digest_path, pool), svn_io_file_del_none, pool, pool)); if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool))) { svn_error_clear(svn_stream_close(stream)); return svn_error_createf(err->apr_err, err, _("Cannot write lock/entries hashfile '%s'"), svn_dirent_local_style(tmp_path, pool)); } SVN_ERR(svn_stream_close(stream)); SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool)); SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool)); return SVN_NO_ERROR; }
dav_error * dav_svn__store_activity(const dav_svn_repos *repos, const char *activity_id, const char *txn_name) { const char *final_path, *tmp_path, *activity_contents; svn_error_t *err; apr_file_t *activity_file; /* Create activities directory if it does not yet exist. */ err = svn_io_make_dir_recursively(repos->activities_db, repos->pool); if (err != NULL) return dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, "could not initialize activity db.", repos->pool); final_path = activity_pathname(repos, activity_id); err = svn_io_open_unique_file2(&activity_file, &tmp_path, final_path, ".tmp", svn_io_file_del_none, repos->pool); if (err) { svn_error_t *serr = svn_error_quick_wrap(err, "Can't open activity db"); return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not open files.", repos->pool); } activity_contents = apr_psprintf(repos->pool, "%s\n%s\n", txn_name, activity_id); err = svn_io_file_write_full(activity_file, activity_contents, strlen(activity_contents), NULL, repos->pool); if (err) { svn_error_t *serr = svn_error_quick_wrap(err, "Can't write to activity db"); /* Try to remove the tmp file, but we already have an error... */ svn_error_clear(svn_io_file_close(activity_file, repos->pool)); svn_error_clear(svn_io_remove_file(tmp_path, repos->pool)); return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "could not write files.", repos->pool); } err = svn_io_file_close(activity_file, repos->pool); if (err) { svn_error_clear(svn_io_remove_file(tmp_path, repos->pool)); return dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, "could not close files.", repos->pool); } err = svn_io_file_rename(tmp_path, final_path, repos->pool); if (err) { svn_error_clear(svn_io_remove_file(tmp_path, repos->pool)); return dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, "could not replace files.", repos->pool); } return NULL; }
svn_error_t * svn_wc__move2(svn_wc_context_t *wc_ctx, const char *src_abspath, const char *dst_abspath, svn_boolean_t metadata_only, svn_boolean_t allow_mixed_revisions, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { svn_wc__db_t *db = wc_ctx->db; svn_boolean_t move_degraded_to_copy = FALSE; svn_node_kind_t kind; svn_boolean_t conflicted; /* Verify that we have the required write locks. */ SVN_ERR(svn_wc__write_check(wc_ctx->db, svn_dirent_dirname(src_abspath, scratch_pool), scratch_pool)); SVN_ERR(svn_wc__write_check(wc_ctx->db, svn_dirent_dirname(dst_abspath, scratch_pool), scratch_pool)); SVN_ERR(copy_or_move(&move_degraded_to_copy, wc_ctx, src_abspath, dst_abspath, TRUE /* metadata_only */, TRUE /* is_move */, allow_mixed_revisions, cancel_func, cancel_baton, notify_func, notify_baton, scratch_pool)); /* An interrupt at this point will leave the new copy marked as moved-here but the source has not yet been deleted or marked as moved-to. */ /* Should we be using a workqueue for this move? It's not clear. What should happen if the copy above is interrupted? The user may want to abort the move and a workqueue might interfere with that. BH: On Windows it is not unlikely to encounter an access denied on this line. Installing the move in the workqueue via the copy_or_move might make it hard to recover from that situation, while the DB is still in a valid state. So be careful when switching this over to the workqueue. */ if (!metadata_only) SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool)); SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, NULL, NULL, NULL, NULL, NULL, NULL, db, src_abspath, scratch_pool, scratch_pool)); if (kind == svn_node_dir) SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath, scratch_pool)); if (conflicted) SVN_ERR(remove_node_conflict_markers(db, src_abspath, dst_abspath, scratch_pool)); SVN_ERR(svn_wc__db_op_delete(db, src_abspath, move_degraded_to_copy ? NULL : dst_abspath, TRUE /* delete_dir_externals */, NULL /* conflict */, NULL /* work_items */, cancel_func, cancel_baton, notify_func, notify_baton, scratch_pool)); return SVN_NO_ERROR; }