#include <stdarg.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_strings.h>
#include "svn_types.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_hash.h"
#include "wc.h"
#include "adm_files.h"
#include "entries.h"
#include "lock.h"
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
static const char default_adm_dir_name[] = ".svn";
static const char *adm_dir_name = default_adm_dir_name;
svn_boolean_t
svn_wc_is_adm_dir(const char *name, apr_pool_t *pool)
{
return (0 == strcmp(name, adm_dir_name)
|| 0 == strcmp(name, default_adm_dir_name));
}
const char *
svn_wc_get_adm_dir(apr_pool_t *pool)
{
return adm_dir_name;
}
svn_error_t *
svn_wc_set_adm_dir(const char *name, apr_pool_t *pool)
{
static const char *valid_dir_names[] = {
default_adm_dir_name,
"_svn",
NULL
};
const char **dir_name;
for (dir_name = valid_dir_names; *dir_name; ++dir_name)
if (0 == strcmp(name, *dir_name))
{
adm_dir_name = *dir_name;
return SVN_NO_ERROR;
}
return svn_error_createf
(SVN_ERR_BAD_FILENAME, NULL,
_("'%s' is not a valid administrative directory name"),
svn_path_local_style(name, pool));
}
static const char *
v_extend_with_adm_name(const char *path,
const char *extension,
svn_boolean_t use_tmp,
apr_pool_t *pool,
va_list ap)
{
const char *this;
path = svn_path_join(path, adm_dir_name, pool);
if (use_tmp)
path = svn_path_join(path, SVN_WC__ADM_TMP, pool);
while ((this = va_arg(ap, const char *)) != NULL)
{
if (this[0] == '\0')
continue;
path = svn_path_join(path, this, pool);
}
if (extension)
path = apr_pstrcat(pool, path, extension, NULL);
return path;
}
static const char *
extend_with_adm_name(const char *path,
const char *extension,
svn_boolean_t use_tmp,
apr_pool_t *pool,
...)
{
va_list ap;
va_start(ap, pool);
path = v_extend_with_adm_name(path, extension, use_tmp, pool, ap);
va_end(ap);
return path;
}
const char *svn_wc__adm_child(const char *path,
const char *child,
apr_pool_t *result_pool)
{
return extend_with_adm_name(path, NULL, FALSE, result_pool, child, NULL);
}
svn_boolean_t
svn_wc__adm_area_exists(const svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const char *path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access),
NULL, pool);
svn_node_kind_t kind;
svn_error_t *err;
err = svn_io_check_path(path, &kind, pool);
if (err)
{
svn_error_clear(err);
return FALSE;
}
return kind != svn_node_none;
}
static svn_error_t *
make_adm_subdir(const char *path,
const char *subdir,
svn_boolean_t tmp,
apr_pool_t *pool)
{
const char *fullpath;
fullpath = extend_with_adm_name(path, NULL, tmp, pool, subdir, NULL);
return svn_io_dir_make(fullpath, APR_OS_DEFAULT, pool);
}
svn_error_t *
svn_wc__make_killme(svn_wc_adm_access_t *adm_access,
svn_boolean_t adm_only,
apr_pool_t *pool)
{
const char *path;
SVN_ERR(svn_wc__adm_write_check(adm_access, pool));
path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_KILLME, pool);
return svn_io_file_create(path, adm_only ? SVN_WC__KILL_ADM_ONLY : "", pool);
}
svn_error_t *
svn_wc__check_killme(svn_wc_adm_access_t *adm_access,
svn_boolean_t *exists,
svn_boolean_t *kill_adm_only,
apr_pool_t *pool)
{
const char *path;
svn_error_t *err;
svn_stringbuf_t *contents;
path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_KILLME, pool);
err = svn_stringbuf_from_file2(&contents, path, pool);
if (err)
{
if (APR_STATUS_IS_ENOENT(err->apr_err))
{
*exists = FALSE;
svn_error_clear(err);
err = SVN_NO_ERROR;
}
return err;
}
*exists = TRUE;
*kill_adm_only = strcmp(contents->data, SVN_WC__KILL_ADM_ONLY) == 0;
return SVN_NO_ERROR;
}
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);
tmp_path = extend_with_adm_name(parent_path, SVN_WC__BASE_EXT, TRUE, pool,
SVN_WC__ADM_TEXT_BASE, base_name, NULL);
base_path = extend_with_adm_name(parent_path, SVN_WC__BASE_EXT, FALSE, pool,
SVN_WC__ADM_TEXT_BASE, base_name, NULL);
SVN_ERR(svn_io_file_rename(tmp_path, base_path, pool));
return svn_io_set_file_read_only(base_path, FALSE, pool);
}
const char *
svn_wc__text_base_path(const char *path,
svn_boolean_t tmp,
apr_pool_t *pool)
{
const char *newpath, *base_name;
svn_path_split(path, &newpath, &base_name, pool);
return extend_with_adm_name(newpath,
SVN_WC__BASE_EXT,
tmp,
pool,
SVN_WC__ADM_TEXT_BASE,
base_name,
NULL);
}
const char *
svn_wc__text_revert_path(const char *path,
apr_pool_t *pool)
{
const char *newpath, *base_name;
svn_path_split(path, &newpath, &base_name, pool);
return extend_with_adm_name(newpath,
SVN_WC__REVERT_EXT,
FALSE,
pool,
SVN_WC__ADM_TEXT_BASE,
base_name,
NULL);
}
svn_error_t *
svn_wc__get_revert_contents(svn_stream_t **contents,
const char *path,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *revert_base = svn_wc__text_revert_path(path, scratch_pool);
if (revert_base == NULL)
{
*contents = NULL;
return SVN_NO_ERROR;
}
return svn_stream_open_readonly(contents, revert_base, result_pool,
scratch_pool);
}
svn_error_t *
svn_wc__prop_path(const char **prop_path,
const char *path,
svn_node_kind_t node_kind,
svn_wc__props_kind_t props_kind,
apr_pool_t *pool)
{
if (node_kind == svn_node_dir)
{
static const char * names[] = {
SVN_WC__ADM_DIR_PROP_BASE,
SVN_WC__ADM_DIR_PROP_REVERT,
SVN_WC__ADM_DIR_WCPROPS,
SVN_WC__ADM_DIR_PROPS
};
*prop_path = extend_with_adm_name
(path,
NULL,
FALSE,
pool,
names[props_kind],
NULL);
}
else
{
static const char * extensions[] = {
SVN_WC__BASE_EXT,
SVN_WC__REVERT_EXT,
SVN_WC__WORK_EXT,
SVN_WC__WORK_EXT
};
static const char * dirs[] = {
SVN_WC__ADM_PROP_BASE,
SVN_WC__ADM_PROP_BASE,
SVN_WC__ADM_WCPROPS,
SVN_WC__ADM_PROPS
};
const char *base_name;
svn_path_split(path, prop_path, &base_name, pool);
*prop_path = extend_with_adm_name
(*prop_path,
extensions[props_kind],
FALSE,
pool,
dirs[props_kind],
base_name,
NULL);
}
return SVN_NO_ERROR;
}
static svn_error_t *
open_adm_file(svn_stream_t **stream,
const char **selected_path,
const char *path,
const char *extension,
svn_boolean_t for_writing,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool,
...)
{
svn_error_t *err;
va_list ap;
if (for_writing)
{
va_start(ap, scratch_pool);
path = v_extend_with_adm_name(path, extension, TRUE, result_pool, ap);
va_end(ap);
err = svn_stream_open_writable(stream, path, result_pool, scratch_pool);
}
else
{
va_start(ap, scratch_pool);
path = v_extend_with_adm_name(path, extension, FALSE, result_pool, ap);
va_end(ap);
err = svn_stream_open_readonly(stream, path, result_pool, scratch_pool);
}
if (selected_path)
*selected_path = path;
if (for_writing && err && APR_STATUS_IS_EEXIST(err->apr_err))
{
svn_error_clear(err);
SVN_ERR(svn_io_remove_file(path, scratch_pool));
err = svn_stream_open_writable(stream, path, result_pool, scratch_pool);
}
if (for_writing && err && APR_STATUS_IS_ENOENT(err->apr_err))
{
err = svn_error_quick_wrap(err,
_("Your .svn/tmp directory may be missing or "
"corrupt; run 'svn cleanup' and try again"));
}
return err;
}
svn_error_t *
svn_wc__open_adm_writable(svn_stream_t **stream,
const char **temp_file_path,
const char *path,
const char *fname,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return open_adm_file(stream, temp_file_path, path, NULL ,
TRUE ,
result_pool, scratch_pool,
fname, NULL);
}
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);
SVN_ERR_ASSERT(strcmp(temp_file_path, tmp_path) == 0);
SVN_ERR(svn_stream_close(stream));
SVN_ERR(svn_io_file_rename(tmp_path, dst_path, scratch_pool));
return svn_io_set_file_read_only(dst_path, FALSE, scratch_pool);
}
svn_error_t *
svn_wc__remove_adm_file(const svn_wc_adm_access_t *adm_access,
const char *filename,
apr_pool_t *scratch_pool)
{
const char *path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access),
filename, scratch_pool);
return svn_io_remove_file(path, scratch_pool);
}
svn_error_t *
svn_wc__open_adm_stream(svn_stream_t **stream,
const char *path,
const char *fname,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return open_adm_file(stream,
NULL ,
path,
NULL ,
FALSE ,
result_pool, scratch_pool,
fname,
NULL);
}
svn_error_t *
svn_wc__open_writable_base(svn_stream_t **stream,
const char **temp_base_path,
const char *path,
svn_boolean_t need_revert_base,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *parent_path;
const char *base_name;
svn_path_split(path, &parent_path, &base_name, scratch_pool);
return open_adm_file(stream, temp_base_path,
parent_path,
need_revert_base
? SVN_WC__REVERT_EXT
: SVN_WC__BASE_EXT,
TRUE ,
result_pool, scratch_pool,
SVN_WC__ADM_TEXT_BASE,
base_name,
NULL);
}
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);
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));
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);
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);
}
static svn_error_t *
make_empty_adm(const char *path, apr_pool_t *pool)
{
path = svn_wc__adm_child(path, NULL, pool);
return svn_io_dir_make_hidden(path, APR_OS_DEFAULT, pool);
}
static svn_error_t *
init_adm_tmp_area(const svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const char *path;
SVN_ERR(svn_wc__adm_write_check(adm_access, pool));
path = svn_wc_adm_access_path(adm_access);
SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, FALSE, pool));
SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TEXT_BASE, TRUE, pool));
SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_PROP_BASE, TRUE, pool));
return make_adm_subdir(path, SVN_WC__ADM_PROPS, TRUE, pool);
}
static svn_error_t *
init_adm(const char *path,
const char *uuid,
const char *url,
const char *repos,
svn_revnum_t initial_rev,
svn_depth_t depth,
apr_pool_t *pool)
{
svn_wc_adm_access_t *adm_access;
SVN_ERR(make_empty_adm(path, pool));
SVN_ERR(svn_wc__adm_pre_open(&adm_access, path, pool));
SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TEXT_BASE, FALSE, pool));
SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_PROP_BASE, FALSE, pool));
SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_PROPS, FALSE, pool));
SVN_ERR(init_adm_tmp_area(adm_access, pool));
SVN_ERR(svn_wc__entries_init(path, uuid, url, repos,
initial_rev, depth, pool));
return svn_wc_adm_close2(adm_access, pool);
}
svn_error_t *
svn_wc_ensure_adm3(const char *path,
const char *uuid,
const char *url,
const char *repos,
svn_revnum_t revision,
svn_depth_t depth,
apr_pool_t *pool)
{
svn_wc_adm_access_t *adm_access;
const svn_wc_entry_t *entry;
int format;
SVN_ERR(svn_wc_check_wc(path, &format, pool));
if (format == 0)
return init_adm(path, uuid, url, repos, revision, depth, pool);
SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, path, FALSE, 0,
NULL, NULL, pool));
SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
SVN_ERR(svn_wc_adm_close2(adm_access, pool));
if (entry->schedule != svn_wc_schedule_delete)
{
if (entry->revision != revision)
return
svn_error_createf
(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("Revision %ld doesn't match existing revision %ld in '%s'"),
revision, entry->revision, path);
if (strcmp(entry->url, url) != 0)
return
svn_error_createf
(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("URL '%s' doesn't match existing URL '%s' in '%s'"),
url, entry->url, path);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__adm_destroy(svn_wc_adm_access_t *adm_access,
apr_pool_t *scratch_pool)
{
const char *path;
SVN_ERR(svn_wc__adm_write_check(adm_access, scratch_pool));
path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access), NULL,
scratch_pool);
SVN_ERR(svn_io_remove_dir2(path, FALSE, NULL, NULL, scratch_pool));
return svn_wc_adm_close2(adm_access, scratch_pool);
}
svn_error_t *
svn_wc__adm_cleanup_tmp_area(const svn_wc_adm_access_t *adm_access,
apr_pool_t *scratch_pool)
{
const char *tmp_path;
if (!svn_wc__adm_area_exists(adm_access, scratch_pool))
return SVN_NO_ERROR;
SVN_ERR(svn_wc__adm_write_check(adm_access, scratch_pool));
tmp_path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access),
SVN_WC__ADM_TMP, scratch_pool);
SVN_ERR(svn_io_remove_dir2(tmp_path, TRUE, NULL, NULL, scratch_pool));
return init_adm_tmp_area(adm_access, scratch_pool);
}
svn_error_t *
svn_wc_create_tmp_file2(apr_file_t **fp,
const char **new_name,
const char *path,
svn_io_file_del_t delete_when,
apr_pool_t *pool)
{
const char *temp_dir;
apr_file_t *file;
SVN_ERR_ASSERT(fp || new_name);
temp_dir = svn_wc__adm_child(path, SVN_WC__ADM_TMP, pool);
SVN_ERR(svn_io_open_unique_file3(&file, new_name, temp_dir,
delete_when, pool, pool));
if (fp)
*fp = file;
else
SVN_ERR(svn_io_file_close(file, pool));
return SVN_NO_ERROR;
}