/*
* adm_files.c: helper routines for handling files & dirs in the
* working copy administrative area (creating,
* deleting, opening, and closing). This is the only
* code that actually knows where administrative
* information is kept.
*
* ====================================================================
* Copyright (c) 2000-2006 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <stdarg.h>
#include <assert.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_wc.h"
#include "wc.h"
#include "adm_files.h"
#include "entries.h"
#include "lock.h"
#include "svn_private_config.h"
/*** File names in the adm area. ***/
/* The default name of the WC admin directory. This name is always
checked by svn_wc_is_adm_dir. */
static const char default_adm_dir_name[] = ".svn";
/* The name that is actually used for the WC admin directory. The
commonest case where this won't be the default is in Windows
ASP.NET development environments, which choke on ".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)
{
/* This is the canonical list of administrative directory names.
FIXME:
An identical list is used in
libsvn_subr/opt.c:svn_opt_args_to_target_array2(),
but that function can't use this list, because that use would
create a circular dependency between libsvn_wc and libsvn_subr.
Make sure changes to the lists are always synchronized! */
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))
{
/* Use the pointer to the statically allocated string
constant, to avoid potential pool lifetime issues. */
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));
}
/* Return the path to something in PATH's administrative area.
*
* First, the adm subdir is appended to PATH as a component, then the
* "tmp" directory is added iff USE_TMP is set, then each of the
* varargs in AP (char *'s) is appended as a path component. The list
* must be terminated with a NULL argument.
*
* Adding an empty component results in no effect (i.e., the separator
* char is not doubled).
*
* If EXTENSION is non-null, it will be appended to the final string
* without a separator character.
*/
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;
/* Tack on the administrative subdirectory. */
path = svn_path_join(path, adm_dir_name, pool);
/* If this is a tmp file, name it into the tmp area. */
if (use_tmp)
path = svn_path_join(path, SVN_WC__ADM_TMP, pool);
/* Tack on everything else. */
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;
}
/* See v_extend_with_adm_name() for details. */
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_path(const char *path,
svn_boolean_t tmp,
apr_pool_t *pool,
...)
{
va_list ap;
va_start(ap, pool);
path = v_extend_with_adm_name(path, NULL, tmp, pool, ap);
va_end(ap);
return path;
}
svn_boolean_t
svn_wc__adm_path_exists(const char *path,
svn_boolean_t tmp,
apr_pool_t *pool,
...)
{
svn_node_kind_t kind;
svn_error_t *err;
va_list ap;
va_start(ap, pool);
path = v_extend_with_adm_name(path, NULL, tmp, pool, ap);
va_end(ap);
err = svn_io_check_path(path, &kind, pool);
if (err)
{
svn_error_clear(err);
/* Return early, since kind is undefined in this case. */
return FALSE;
}
if (kind == svn_node_none)
return FALSE;
else
return TRUE;
}
/*** Making and using files in the adm area. ***/
/* Create an empty THING in the adm area with permissions set to PERMS.
* If TMP is non-zero, then create THING in the tmp dir.
*
* Does not check if THING already exists, so be careful -- THING will
* be empty after this no matter what.
*/
svn_error_t *
svn_wc__make_adm_thing(svn_wc_adm_access_t *adm_access,
const char *thing,
svn_node_kind_t type,
apr_fileperms_t perms,
svn_boolean_t tmp,
apr_pool_t *pool)
{
svn_error_t *err = SVN_NO_ERROR;
apr_file_t *f = NULL;
const char *path;
SVN_ERR(svn_wc__adm_write_check(adm_access));
path = extend_with_adm_name(svn_wc_adm_access_path(adm_access),
NULL, tmp, pool, thing, NULL);
if (type == svn_node_file)
{
SVN_ERR(svn_io_file_open(&f, path,
(APR_WRITE | APR_CREATE | APR_EXCL),
perms,
pool));
/* Creation succeeded, so close immediately. */
SVN_ERR(svn_io_file_close(f, pool));
}
else if (type == svn_node_dir)
{
SVN_ERR(svn_io_dir_make(path, perms, pool));
}
else /* unknown type argument, wrongness */
{
/* We're only capturing this here because there wouldn't be a
segfault or other obvious indicator that something went
wrong. Even so, not sure if it's appropriate. Thoughts? */
err = svn_error_create
(0, NULL, _("Bad type indicator"));
}
return err;
}
/*** Syncing files in the adm area. ***/
static svn_error_t *
sync_adm_file(const char *path,
const char *extension,
apr_pool_t *pool,
...)
{
/* Some code duplication with close_adm_file() seems unavoidable,
given how C va_lists work. */
const char *tmp_path;
va_list ap;
/* Extend tmp name. */
va_start(ap, pool);
tmp_path = v_extend_with_adm_name(path, extension, 1, pool, ap);
va_end(ap);
/* Extend real name. */
va_start(ap, pool);
path = v_extend_with_adm_name(path, extension, 0, pool, ap);
va_end(ap);
/* Rename. */
SVN_ERR(svn_io_file_rename(tmp_path, path, pool));
SVN_ERR(svn_io_set_file_read_only(path, FALSE, pool));
return SVN_NO_ERROR;
}
/* 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, *base_name;
svn_path_split(path, &parent_path, &base_name, pool);
return sync_adm_file(parent_path,
SVN_WC__BASE_EXT,
pool,
SVN_WC__ADM_TEXT_BASE,
base_name,
NULL);
}
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,
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__REVERT_EXT,
tmp,
pool,
SVN_WC__ADM_TEXT_BASE,
base_name,
NULL);
}
/* Kind for prop_path_internal. */
typedef enum prop_path_kind_t
{
prop_path_kind_base = 0,
prop_path_kind_revert,
prop_path_kind_wcprop,
prop_path_kind_working
} prop_path_kind_t;
static svn_error_t *
prop_path_internal(const char **prop_path,
const char *path,
svn_node_kind_t kind,
prop_path_kind_t path_kind,
svn_boolean_t tmp,
apr_pool_t *pool)
{
if (kind == svn_node_dir) /* It's a working copy dir */
{
static const char * names[] = {
SVN_WC__ADM_DIR_PROP_BASE, /* prop_path_kind_base */
SVN_WC__ADM_DIR_PROP_REVERT, /* prop_path_kind_revert */
SVN_WC__ADM_DIR_WCPROPS, /* prop_path_kind_wcprop */
SVN_WC__ADM_DIR_PROPS /* prop_path_kind_working */
};
*prop_path = extend_with_adm_name
(path,
NULL,
tmp,
pool,
names[path_kind],
NULL);
}
else /* It's a file */
{
static const char * extensions[] = {
SVN_WC__BASE_EXT, /* prop_path_kind_base */
SVN_WC__REVERT_EXT, /* prop_path_kind_revert */
SVN_WC__WORK_EXT, /* prop_path_kind_wcprop */
SVN_WC__WORK_EXT /* prop_path_kind_working */
};
static const char * dirs[] = {
SVN_WC__ADM_PROP_BASE, /* prop_path_kind_base */
SVN_WC__ADM_PROP_BASE, /* prop_path_kind_revert */
SVN_WC__ADM_WCPROPS, /* prop_path_kind_wcprop */
SVN_WC__ADM_PROPS /* prop_path_kind_working */
};
const char *base_name;
svn_path_split(path, prop_path, &base_name, pool);
*prop_path = extend_with_adm_name
(*prop_path,
extensions[path_kind],
tmp,
pool,
dirs[path_kind],
base_name,
NULL);
}
return SVN_NO_ERROR;
}
/* Return a path to the 'wcprop' file for PATH, possibly in TMP area. */
svn_error_t *
svn_wc__wcprop_path(const char **wcprop_path,
const char *path,
svn_node_kind_t kind,
svn_boolean_t tmp,
apr_pool_t *pool)
{
return prop_path_internal(wcprop_path, path, kind,
prop_path_kind_wcprop, tmp, pool);
}
svn_error_t *
svn_wc__prop_path(const char **prop_path,
const char *path,
svn_node_kind_t kind,
svn_boolean_t tmp,
apr_pool_t *pool)
{
return prop_path_internal(prop_path, path, kind,
prop_path_kind_working, tmp, pool);
}
svn_error_t *
svn_wc__prop_base_path(const char **prop_path,
const char *path,
svn_node_kind_t kind,
svn_boolean_t tmp,
apr_pool_t *pool)
{
return prop_path_internal(prop_path, path, kind,
prop_path_kind_base, tmp, pool);
}
svn_error_t *
svn_wc__prop_revert_path(const char **prop_path,
const char *path,
svn_node_kind_t kind,
svn_boolean_t tmp,
apr_pool_t *pool)
{
return prop_path_internal(prop_path, path, kind,
prop_path_kind_revert, tmp, pool);
}
/*** Opening and closing files in the adm area. ***/
/* Open a file somewhere in the adm area for directory PATH.
* First, add the adm subdir as the next component of PATH, then add
* each of the varargs (they are char *'s), then add EXTENSION if it
* is non-null, then open the resulting file as *HANDLE.
*
* If FLAGS indicates writing, open the file in the adm tmp area.
* This means the file will probably need to be renamed from there,
* either by passing the sync flag to close_adm_file() later, or with
* an explicit call to sync_adm_file().
*/
static svn_error_t *
open_adm_file(apr_file_t **handle,
const char *path,
const char *extension,
apr_fileperms_t protection,
apr_int32_t flags,
apr_pool_t *pool,
...)
{
svn_error_t *err = SVN_NO_ERROR;
va_list ap;
/* If we're writing, always do it to a tmp file. */
if (flags & APR_WRITE)
{
if (flags & APR_APPEND)
{
/* We don't handle append. To do so we would need to copy the
contents into the apr_file_t once it has been opened. */
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("APR_APPEND not supported for adm files"));
}
/* Need to own the temporary file, so don't reuse an existing one. */
flags |= APR_EXCL | APR_CREATE;
/* Extend with tmp name. */
va_start(ap, pool);
path = v_extend_with_adm_name(path, extension, 1, pool, ap);
va_end(ap);
}
else
{
/* Extend with regular adm name. */
va_start(ap, pool);
path = v_extend_with_adm_name(path, extension, 0, pool, ap);
va_end(ap);
}
err = svn_io_file_open(handle, path, flags, protection, pool);
if ((flags & APR_WRITE) && err && APR_STATUS_IS_EEXIST(err->apr_err))
{
/* Exclusive open failed, delete and retry */
svn_error_clear(err);
SVN_ERR(svn_io_remove_file(path, pool));
err = svn_io_file_open(handle, path, flags, protection, pool);
}
if (err)
{
/* Oddly enough, APR will set *HANDLE even if the open failed.
You'll get a filehandle whose descriptor is -1. There must
be a reason this is useful... Anyway, we don't want the
handle. */
*handle = NULL;
/* If we receive a failure to open a file in our temporary directory,
* it may be because our temporary directories aren't created.
* Older SVN clients did not create these directories.
* 'svn cleanup' will fix this problem.
*/
if (APR_STATUS_IS_ENOENT(err->apr_err) && (flags & APR_WRITE))
{
err = svn_error_quick_wrap(err,
_("Your .svn/tmp directory may be missing or "
"corrupt; run 'svn cleanup' and try again"));
}
}
return err;
}
/* Close the file indicated by FP (PATH is passed to make error
* reporting better). If SYNC is non-zero, then the file will be
* sync'd from the adm tmp area to its permanent location, otherwise
* it will remain in the tmp area. See open_adm_file().
*/
static svn_error_t *
close_adm_file(apr_file_t *fp,
const char *path,
const char *extension,
svn_boolean_t sync,
apr_pool_t *pool,
...)
{
const char *tmp_path;
va_list ap;
/* Get the full name of the thing we're closing. */
va_start(ap, pool);
tmp_path = v_extend_with_adm_name(path, extension, sync, pool, ap);
va_end(ap);
SVN_ERR(svn_io_file_close(fp, pool));
/* If we're syncing a tmp file, it needs to be renamed after closing. */
if (sync)
{
/* Some code duplication with sync_adm_file() seems unavoidable,
given how C va_lists work. */
/* Obtain dest name. */
va_start(ap, pool);
path = v_extend_with_adm_name(path, extension, 0, pool, ap);
va_end(ap);
/* Rename. */
SVN_ERR(svn_io_file_rename(tmp_path, path, pool));
SVN_ERR(svn_io_set_file_read_only(path, FALSE, pool));
return SVN_NO_ERROR;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__open_adm_file(apr_file_t **handle,
const char *path,
const char *fname,
apr_int32_t flags,
apr_pool_t *pool)
{
return open_adm_file(handle, path, NULL, APR_OS_DEFAULT, flags, pool,
fname, NULL);
}
svn_error_t *
svn_wc__close_adm_file(apr_file_t *fp,
const char *path,
const char *fname,
int sync,
apr_pool_t *pool)
{
return close_adm_file(fp, path, NULL, sync, pool, fname, NULL);
}
svn_error_t *
svn_wc__remove_adm_file(const char *path, apr_pool_t *pool, ...)
{
va_list ap;
va_start(ap, pool);
path = v_extend_with_adm_name(path, NULL, 0, pool, ap);
va_end(ap);
SVN_ERR(svn_io_remove_file(path, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__open_text_base(apr_file_t **handle,
const char *path,
apr_int32_t flags,
apr_pool_t *pool)
{
const char *parent_path, *base_name;
svn_path_split(path, &parent_path, &base_name, pool);
return open_adm_file(handle, parent_path, SVN_WC__BASE_EXT, APR_OS_DEFAULT,
flags, pool, SVN_WC__ADM_TEXT_BASE, base_name, NULL);
}
svn_error_t *
svn_wc__open_revert_base(apr_file_t **handle,
const char *path,
apr_int32_t flags,
apr_pool_t *pool)
{
const char *parent_path, *base_name;
svn_path_split(path, &parent_path, &base_name, pool);
return open_adm_file(handle, parent_path, SVN_WC__REVERT_EXT, APR_OS_DEFAULT,
flags, pool, SVN_WC__ADM_TEXT_BASE, base_name, NULL);
}
svn_error_t *
svn_wc__close_text_base(apr_file_t *fp,
const char *path,
int write,
apr_pool_t *pool)
{
const char *parent_path, *base_name;
svn_path_split(path, &parent_path, &base_name, pool);
return close_adm_file(fp, parent_path, SVN_WC__BASE_EXT, write, pool,
SVN_WC__ADM_TEXT_BASE, base_name, NULL);
}
svn_error_t *
svn_wc__close_revert_base(apr_file_t *fp,
const char *path,
int write,
apr_pool_t *pool)
{
const char *parent_path, *base_name;
svn_path_split(path, &parent_path, &base_name, pool);
return close_adm_file(fp, parent_path, SVN_WC__REVERT_EXT, write, pool,
SVN_WC__ADM_TEXT_BASE, base_name, NULL);
}
svn_error_t *
svn_wc__open_props(apr_file_t **handle,
const char *path,
apr_int32_t flags,
svn_boolean_t base,
svn_boolean_t wcprops,
apr_pool_t *pool)
{
const char *parent_dir, *base_name;
svn_node_kind_t kind;
int wc_format_version;
SVN_ERR(svn_io_check_path(path, &kind, pool));
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));
/* Then examine the flags to know -which- kind of prop file to get. */
if (base && wcprops)
return svn_error_create(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("No such thing as 'base' "
"working copy properties!"));
else if (base)
{
if (kind == svn_node_dir)
return open_adm_file(handle, parent_dir, NULL, APR_OS_DEFAULT, flags,
pool, SVN_WC__ADM_DIR_PROP_BASE, NULL);
else
return open_adm_file(handle, parent_dir, SVN_WC__BASE_EXT,
APR_OS_DEFAULT, flags, pool,
SVN_WC__ADM_PROP_BASE, base_name, NULL);
}
else if (wcprops)
{
if (kind == svn_node_dir)
return open_adm_file(handle, parent_dir, NULL, APR_OS_DEFAULT, flags,
pool, SVN_WC__ADM_DIR_WCPROPS, NULL);
else
{
return open_adm_file
(handle, parent_dir,
SVN_WC__WORK_EXT, APR_OS_DEFAULT,
flags, pool, SVN_WC__ADM_WCPROPS, base_name, NULL);
}
}
else /* plain old property file */
{
if (kind == svn_node_dir)
return open_adm_file(handle, parent_dir, NULL, APR_OS_DEFAULT, flags,
pool, SVN_WC__ADM_DIR_PROPS, NULL);
else
{
return open_adm_file
(handle, parent_dir,
SVN_WC__WORK_EXT, APR_OS_DEFAULT,
flags, pool, SVN_WC__ADM_PROPS, base_name, NULL);
}
}
}
svn_error_t *
svn_wc__close_props(apr_file_t *fp,
const char *path,
svn_boolean_t base,
svn_boolean_t wcprops,
int sync,
apr_pool_t *pool)
{
const char *parent_dir, *base_name;
svn_node_kind_t kind;
SVN_ERR(svn_io_check_path(path, &kind, pool));
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 close a file in the admin area
of parent_dir. Since the file must be open already, we know that
parent_dir is a working copy. */
/* Then examine the flags to know -which- kind of prop file to get. */
if (base && wcprops)
return svn_error_create(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("No such thing as 'base' "
"working copy properties!"));
else if (base)
{
if (kind == svn_node_dir)
return close_adm_file(fp, parent_dir, NULL, sync, pool,
SVN_WC__ADM_DIR_PROP_BASE, NULL);
else
return close_adm_file(fp, parent_dir, SVN_WC__BASE_EXT, sync, pool,
SVN_WC__ADM_PROP_BASE, base_name, NULL);
}
else if (wcprops)
{
if (kind == svn_node_dir)
return close_adm_file(fp, parent_dir, NULL, sync, pool,
SVN_WC__ADM_DIR_WCPROPS, NULL);
else
return close_adm_file
(fp, parent_dir,
SVN_WC__WORK_EXT,
sync, pool, SVN_WC__ADM_WCPROPS, base_name, NULL);
}
else /* plain old property file */
{
if (kind == svn_node_dir)
return close_adm_file(fp, parent_dir, NULL, sync, pool,
SVN_WC__ADM_DIR_PROPS, NULL);
else
return close_adm_file
(fp, parent_dir,
SVN_WC__WORK_EXT,
sync, pool, SVN_WC__ADM_PROPS, base_name, NULL);
}
}
svn_error_t *
svn_wc__sync_props(const char *path,
svn_boolean_t base,
svn_boolean_t wcprops,
apr_pool_t *pool)
{
const char *parent_dir, *base_name;
svn_node_kind_t kind;
/* Check if path is a file or a dir. */
SVN_ERR(svn_io_check_path(path, &kind, pool));
/* If file, split the path. */
if (kind == svn_node_file)
svn_path_split(path, &parent_dir, &base_name, pool);
else
parent_dir = path;
/* At this point, we know we need to open a file in the admin area
of parent_dir. Examine the flags to know -which- kind of prop
file to get -- there are three types! */
if (base && wcprops)
return svn_error_create(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("No such thing as 'base' "
"working copy properties!"));
else if (base)
{
if (kind == svn_node_dir)
return sync_adm_file(parent_dir, NULL, pool,
SVN_WC__ADM_DIR_PROP_BASE, NULL);
else
return sync_adm_file(parent_dir, SVN_WC__BASE_EXT, pool,
SVN_WC__ADM_PROP_BASE, base_name, NULL);
}
else if (wcprops)
{
if (kind == svn_node_dir)
return sync_adm_file(parent_dir, NULL, pool,
SVN_WC__ADM_DIR_WCPROPS, NULL);
else
return sync_adm_file(parent_dir, SVN_WC__BASE_EXT, pool,
SVN_WC__ADM_WCPROPS, base_name, NULL);
}
else /* plain old property file */
{
if (kind == svn_node_dir)
return sync_adm_file(parent_dir, NULL, pool,
SVN_WC__ADM_DIR_PROPS, NULL);
else
return sync_adm_file(parent_dir, SVN_WC__WORK_EXT, pool,
SVN_WC__ADM_PROPS, base_name, NULL);
}
}
/*** Checking for and creating administrative subdirs. ***/
/* Set *EXISTS to true iff there's an adm area for PATH, and it matches URL
* and REVISION. If there's no adm area, set *EXISTS to false; if
* there's an adm area but it doesn't match URL and REVISION, then
* return error and don't touch *EXISTS.
*
* ### These semantics are totally bizarre. One wonders what the
* ### callers' real needs are. In the long term, this function
* ### should probably be unified with svn_wc_check_wc.
*/
static svn_error_t *
check_adm_exists(svn_boolean_t *exists,
const char *path,
const char *url,
svn_revnum_t revision,
apr_pool_t *pool)
{
svn_error_t *err = SVN_NO_ERROR;
svn_node_kind_t kind;
svn_boolean_t dir_exists = FALSE, wc_exists = FALSE;
const char *tmp_path;
/** Step 1: check that the directory exists. **/
tmp_path = extend_with_adm_name(path, NULL, 0, pool, NULL);
SVN_ERR(svn_io_check_path(tmp_path, &kind, pool));
if (kind != svn_node_none && kind != svn_node_dir)
{
/* If got an error other than dir non-existence, then
something's weird and we should return a genuine error. */
return svn_error_createf(APR_ENOTDIR, NULL,
_("'%s' is not a directory"),
svn_path_local_style(tmp_path, pool));
}
else if (kind == svn_node_none)
{
dir_exists = FALSE;
}
else /* must be a dir. */
{
assert(kind == svn_node_dir);
dir_exists = TRUE;
}
/** Step 1. If no adm directory, then we're done. */
if (! dir_exists)
{
*exists = FALSE;
return SVN_NO_ERROR;
}
/** The directory exists, but is it a valid working copy yet?
Try step 2: checking that we can read the format number. */
{
int wc_format;
err = svn_io_read_version_file
(&wc_format, svn_path_join(tmp_path, SVN_WC__ADM_ENTRIES, pool), pool);
/* Fall back on the format file for WCs before format 7. */
if (err)
{
svn_error_clear(err);
err = svn_io_read_version_file
(&wc_format, svn_path_join(tmp_path, SVN_WC__ADM_FORMAT, pool),
pool);
}
if (err)
{
svn_error_clear(err);
wc_exists = FALSE;
}
else
wc_exists = TRUE;
}
/** Step 3: now check that repos and ancestry are correct **/
if (wc_exists)
{
/* This is a bit odd. We have to open an access baton, which relies
on this being a working copy, in order to determine if this is a
working copy! */
svn_wc_adm_access_t *adm_access;
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, path, FALSE, 0,
NULL, NULL, pool));
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
SVN_ERR(svn_wc_adm_close(adm_access));
if (!entry)
return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
_("No entry for '%s'"),
svn_path_local_style(path, pool));
/* When the directory exists and is scheduled for deletion do not
* check the revision or the URL. The revision can be any
* arbitrary revision and the URL may differ if the add is
* being driven from a merge which will have a different URL. */
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);
/** ### comparing URLs, should they be canonicalized first? */
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);
}
}
*exists = wc_exists;
return SVN_NO_ERROR;
}
static svn_error_t *
make_empty_adm(const char *path, apr_pool_t *pool)
{
path = extend_with_adm_name(path, NULL, 0, pool, NULL);
SVN_ERR(svn_io_dir_make_hidden(path, APR_OS_DEFAULT, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
init_adm_tmp_area(svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
/* Default perms */
apr_fileperms_t perms = APR_OS_DEFAULT;
/* SVN_WC__ADM_TMP */
SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_TMP,
svn_node_dir, perms, 0, pool));
/* SVN_WC__ADM_TMP/SVN_WC__ADM_TEXT_BASE */
SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_TEXT_BASE,
svn_node_dir, perms, 1, pool));
/* SVN_WC__ADM_TMP/SVN_WC__ADM_PROP_BASE */
SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_PROP_BASE,
svn_node_dir, perms, 1, pool));
/* SVN_WC__ADM_TMP/SVN_WC__ADM_PROPS */
SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_PROPS,
svn_node_dir, perms, 1, pool));
return SVN_NO_ERROR;
}
/* Set up a new adm area for PATH, with URL as the ancestor url, and
INITIAL_REV as the starting revision. The entries file starts out
marked as 'incomplete. The adm area starts out locked; remember to
unlock it when done. */
static svn_error_t *
init_adm(const char *path,
const char *uuid,
const char *url,
const char *repos,
svn_revnum_t initial_rev,
apr_pool_t *pool)
{
svn_wc_adm_access_t *adm_access;
/* Default perms */
apr_fileperms_t perms = APR_OS_DEFAULT;
/* First, make an empty administrative area. */
SVN_ERR(make_empty_adm(path, pool));
/* Lock it immediately. Theoretically, no compliant wc library
would ever consider this an adm area until a README file were
present... but locking it is still appropriately paranoid. */
SVN_ERR(svn_wc__adm_pre_open(&adm_access, path, pool));
/** Make subdirectories. ***/
/* SVN_WC__ADM_TEXT_BASE */
SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_TEXT_BASE,
svn_node_dir, perms, 0, pool));
/* SVN_WC__ADM_PROP_BASE */
SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_PROP_BASE,
svn_node_dir, perms, 0, pool));
/* SVN_WC__ADM_PROPS */
SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_PROPS,
svn_node_dir, perms, 0, pool));
/** Init the tmp area. ***/
SVN_ERR(init_adm_tmp_area(adm_access, pool));
/** Initialize each administrative file. */
/* SVN_WC__ADM_ENTRIES */
/* THIS FILE MUST BE CREATED LAST:
After this exists, the dir is considered complete. */
SVN_ERR(svn_wc__entries_init(path, uuid, url, repos, initial_rev, pool));
/* We provide this for backwards compatibilty. Clients that don't understand
format version 7 or higher will display a nicer error message if this
file exists.
### Consider removing this in svn 1.5 or 1.6. */
SVN_ERR(svn_io_write_version_file
(extend_with_adm_name(path, NULL, FALSE, pool,
SVN_WC__ADM_FORMAT, NULL),
SVN_WC__VERSION, pool));
/* Now unlock it. It's now a valid working copy directory, that
just happens to be at revision 0. */
SVN_ERR(svn_wc_adm_close(adm_access));
/* Else no problems, we're outta here. */
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_ensure_adm2(const char *path,
const char *uuid,
const char *url,
const char *repos,
svn_revnum_t revision,
apr_pool_t *pool)
{
svn_boolean_t exists_already;
SVN_ERR(check_adm_exists(&exists_already, path, url, revision, pool));
return (exists_already ? SVN_NO_ERROR :
init_adm(path, uuid, url, repos, revision, pool));
}
svn_error_t *
svn_wc_ensure_adm(const char *path,
const char *uuid,
const char *url,
svn_revnum_t revision,
apr_pool_t *pool)
{
return svn_wc_ensure_adm2(path, uuid, url, NULL, revision, pool);
}
svn_error_t *
svn_wc__adm_destroy(svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const char *path;
SVN_ERR(svn_wc__adm_write_check(adm_access));
/* Well, the coast is clear for blowing away the administrative
directory, which also removes the lock file */
path = extend_with_adm_name(svn_wc_adm_access_path(adm_access),
NULL, FALSE, pool, NULL);
SVN_ERR(svn_io_remove_dir(path, pool));
SVN_ERR(svn_wc_adm_close(adm_access));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__adm_cleanup_tmp_area(svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
const char *tmp_path;
SVN_ERR(svn_wc__adm_write_check(adm_access));
/* Get the path to the tmp area, and blow it away. */
tmp_path = extend_with_adm_name(svn_wc_adm_access_path(adm_access),
NULL, 0, pool, SVN_WC__ADM_TMP, NULL);
SVN_ERR(svn_io_remove_dir(tmp_path, pool));
/* Now, rebuild the tmp area. */
SVN_ERR(init_adm_tmp_area(adm_access, pool));
return SVN_NO_ERROR;
}
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)
{
apr_file_t *file;
assert(fp || new_name);
/* Use a self-explanatory name for the file :-) . */
path = svn_wc__adm_path(path, TRUE, pool, "tempfile", NULL);
/* Open a unique file; use APR_DELONCLOSE. */
SVN_ERR(svn_io_open_unique_file2(&file, new_name,
path, ".tmp", delete_when, pool));
if (fp)
*fp = file;
else
SVN_ERR(svn_io_file_close(file, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_create_tmp_file(apr_file_t **fp,
const char *path,
svn_boolean_t delete_on_close,
apr_pool_t *pool)
{
return svn_wc_create_tmp_file2(fp, NULL, path,
delete_on_close
? svn_io_file_del_on_close
: svn_io_file_del_none,
pool);
}
syntax highlighted by Code2HTML, v. 0.9.1