blob: f182d9d54a5c428c57855a821462501db95880a7 [file] [log] [blame]
/*
* 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-2002 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_hash.h>
#include <apr_file_io.h>
#include <apr_time.h>
#include <apr_strings.h>
#include "svn_types.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_hash.h"
#include "svn_io.h"
#include "svn_path.h"
#include "svn_wc.h"
#include "wc.h"
#include "adm_files.h"
#include "entries.h"
/*** File names in the adm area. ***/
static const char *
adm_subdir (void)
{
return SVN_WC_ADM_DIR_NAME;
}
svn_stringbuf_t *
svn_wc__adm_subdir (apr_pool_t *pool)
{
return svn_stringbuf_create (adm_subdir (), pool);
}
/* Extend PATH to the name of something in PATH's administrative area.
* Returns the number of path components added to PATH.
*
* 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.
*
* Important: chances are you will want to call chop_admin_name() to
* restore PATH to its original value before exiting anything that
* calls this. If you exit, say by returning an error, before calling
* chop_admin_name(), then PATH will still be in its extended state.
*
* So, the safest recipe is for callers of extend_with_adm_name() to
* always have exactly one return statement, occurring *after* an
* unconditional call to chop_admin_name(). */
static int
v_extend_with_adm_name (svn_stringbuf_t *path,
const char *extension,
svn_boolean_t use_tmp,
apr_pool_t *pool,
va_list ap)
{
const char *this;
int components_added = 0;
/* Tack on the administrative subdirectory. */
svn_path_add_component_nts (path, adm_subdir ());
components_added++;
/* If this is a tmp file, name it into the tmp area. */
if (use_tmp)
{
svn_path_add_component_nts (path, SVN_WC__ADM_TMP);
components_added++;
}
/* Tack on everything else. */
while ((this = va_arg (ap, const char *)) != NULL)
{
if (this[0] == '\0')
continue;
svn_path_add_component_nts (path, this);
components_added++;
}
if (extension)
svn_stringbuf_appendcstr (path, extension);
return components_added;
}
/* See v_extend_with_adm_name() for details. */
static int
extend_with_adm_name (svn_stringbuf_t *path,
const char *extension,
svn_boolean_t use_tmp,
apr_pool_t *pool,
...)
{
va_list ap;
int components_added;
va_start (ap, pool);
components_added = v_extend_with_adm_name (path,
extension,
use_tmp,
pool,
ap);
va_end (ap);
return components_added;
}
svn_stringbuf_t *
svn_wc__adm_path (svn_stringbuf_t *path,
svn_boolean_t tmp,
apr_pool_t *pool,
...)
{
svn_stringbuf_t *newpath = svn_stringbuf_dup (path, pool);
va_list ap;
va_start (ap, pool);
v_extend_with_adm_name (newpath, NULL, tmp, pool, ap);
va_end (ap);
return newpath;
}
svn_boolean_t
svn_wc__adm_path_exists (svn_stringbuf_t *path,
svn_boolean_t tmp,
apr_pool_t *pool,
...)
{
enum svn_node_kind kind;
svn_stringbuf_t *newpath = svn_stringbuf_dup (path, pool);
va_list ap;
va_start (ap, pool);
v_extend_with_adm_name (newpath, NULL, tmp, pool, ap);
va_end (ap);
svn_io_check_path (newpath->data, &kind, pool);
if (kind == svn_node_none)
return FALSE;
else
return TRUE;
}
/* Restore PATH to what it was before a call to
* extend_with_adm_name(), by lopping off NUM_COMPONENTS
* components.
*/
static void
chop_admin_name (svn_stringbuf_t *path, int num_components)
{
while (num_components-- > 0)
svn_path_remove_component (path);
}
/*** 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_stringbuf_t *path,
const char *thing,
int type,
apr_fileperms_t perms,
svn_boolean_t tmp,
apr_pool_t *pool)
{
svn_error_t *err = NULL;
apr_file_t *f = NULL;
apr_status_t apr_err = 0;
int components_added;
components_added = extend_with_adm_name (path, NULL, tmp, pool, thing, NULL);
if (type == svn_node_file)
{
apr_err = apr_file_open (&f, path->data,
(APR_WRITE | APR_CREATE | APR_EXCL),
perms,
pool);
if (apr_err)
err = svn_error_create (apr_err, 0, NULL, pool, path->data);
else
{
/* Creation succeeded, so close immediately. */
apr_err = apr_file_close (f);
if (apr_err)
err = svn_error_create (apr_err, 0, NULL, pool, path->data);
}
}
else if (type == svn_node_dir)
{
apr_err = apr_dir_make (path->data, perms, pool);
if (apr_err)
err = svn_error_create (apr_err, 0, NULL, pool, path->data);
}
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, 0, NULL, pool, "svn_wc__make_admin_thing: bad type indicator");
}
/* Restore path to its original state no matter what. */
chop_admin_name (path, components_added);
return err;
}
/* Copy SRC to DST if SRC exists, else create DST empty. */
static svn_error_t *
maybe_copy_file (svn_stringbuf_t *src, svn_stringbuf_t *dst, apr_pool_t *pool)
{
enum svn_node_kind kind;
svn_error_t *err;
apr_status_t apr_err;
/* First test if SRC exists. */
err = svn_io_check_path (src->data, &kind, pool);
if (err)
return err;
else if (kind == svn_node_none)
{
/* SRC doesn't exist, create DST empty. */
apr_file_t *f = NULL;
apr_err = apr_file_open (&f,
dst->data,
(APR_WRITE | APR_CREATE),
APR_OS_DEFAULT,
pool);
if (apr_err)
return svn_error_create (apr_err, 0, NULL, pool, dst->data);
else
{
apr_err = apr_file_close (f);
if (apr_err)
return svn_error_create (apr_err, 0, NULL, pool, dst->data);
else
return SVN_NO_ERROR;
}
}
else /* SRC exists, so copy it to DST. */
{
err = svn_io_copy_file (src->data, dst->data, FALSE, pool);
if (err)
return err;
}
return SVN_NO_ERROR;
}
/*** Syncing files in the adm area. ***/
static svn_error_t *
sync_adm_file (svn_stringbuf_t *path,
const char *extension,
apr_pool_t *pool,
...)
{
/* Some code duplication with close_adm_file() seems unavoidable,
given how C va_lists work. */
svn_stringbuf_t *tmp_path = svn_stringbuf_dup (path, pool);
apr_status_t apr_err;
int components_added;
va_list ap;
/* Extend real name. */
va_start (ap, pool);
components_added = v_extend_with_adm_name (path, extension, 0, pool, ap);
va_end (ap);
/* Extend tmp name. */
va_start (ap, pool);
v_extend_with_adm_name (tmp_path, extension, 1, pool, ap);
va_end (ap);
/* Remove read-only flag on destination. */
SVN_ERR (svn_io_set_file_read_write (path->data, TRUE, pool));
/* Rename. */
apr_err = apr_file_rename (tmp_path->data, path->data, pool);
if (APR_STATUS_IS_SUCCESS (apr_err))
SVN_ERR (svn_io_set_file_read_only (path->data, FALSE, pool));
/* Unconditionally restore path. */
chop_admin_name (path, components_added);
if (apr_err)
return svn_error_createf (apr_err, 0, NULL, pool,
"error renaming %s to %s",
tmp_path->data, path->data);
else
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 (svn_stringbuf_t *path, apr_pool_t *pool)
{
svn_stringbuf_t *newpath, *basename;
svn_path_split (path, &newpath, &basename, pool);
return sync_adm_file (newpath,
SVN_WC__BASE_EXT,
pool,
SVN_WC__ADM_TEXT_BASE,
basename->data,
NULL);
}
svn_stringbuf_t *
svn_wc__text_base_path (const svn_stringbuf_t *path,
svn_boolean_t tmp,
apr_pool_t *pool)
{
svn_stringbuf_t *newpath, *basename;
svn_path_split (path, &newpath, &basename, pool);
extend_with_adm_name (newpath,
SVN_WC__BASE_EXT,
0,
pool,
tmp ? SVN_WC__ADM_TMP : "",
SVN_WC__ADM_TEXT_BASE,
basename->data,
NULL);
return newpath;
}
static svn_error_t *
prop_path_internal (svn_stringbuf_t **prop_path,
const svn_stringbuf_t *path,
svn_boolean_t base,
svn_boolean_t tmp,
apr_pool_t *pool)
{
svn_error_t *err;
enum svn_node_kind kind;
svn_boolean_t is_wc;
svn_stringbuf_t *entry_name;
err = svn_io_check_path (path->data, &kind, pool);
if (err)
return err;
/* kff todo: some factorization can be done on most callers of
svn_wc_check_wc()? */
is_wc = FALSE;
entry_name = NULL;
if (kind == svn_node_dir)
{
err = svn_wc_check_wc (path, &is_wc, pool);
if (err)
return err;
}
if (is_wc) /* It's not only a dir, it's a working copy dir */
{
*prop_path = svn_stringbuf_dup (path, pool);
extend_with_adm_name
(*prop_path,
NULL,
0,
pool,
tmp ? SVN_WC__ADM_TMP : "",
base ? SVN_WC__ADM_DIR_PROP_BASE : SVN_WC__ADM_DIR_PROPS,
NULL);
}
else /* It's either a file, or a non-wc dir (i.e., maybe an ex-file) */
{
svn_path_split (path, prop_path, &entry_name, pool);
if (svn_path_is_empty (*prop_path))
svn_stringbuf_set (*prop_path, ".");
err = svn_wc_check_wc (*prop_path, &is_wc, pool);
if (err)
return err;
else if (! is_wc)
return svn_error_createf
(SVN_ERR_WC_OBSTRUCTED_UPDATE, 0, NULL, pool,
"svn_wc__prop_path: %s is not a working copy directory",
(*prop_path)->data);
extend_with_adm_name (*prop_path,
base ? SVN_WC__BASE_EXT : NULL,
0,
pool,
tmp ? SVN_WC__ADM_TMP : "",
base ? SVN_WC__ADM_PROP_BASE : SVN_WC__ADM_PROPS,
entry_name->data,
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 (svn_stringbuf_t **wcprop_path,
const svn_stringbuf_t *path,
svn_boolean_t tmp,
apr_pool_t *pool)
{
svn_error_t *err;
enum svn_node_kind kind;
svn_boolean_t is_wc;
svn_stringbuf_t *entry_name;
err = svn_io_check_path (path->data, &kind, pool);
if (err)
return err;
/* kff todo: some factorization can be done on most callers of
svn_wc_check_wc()? */
is_wc = FALSE;
entry_name = NULL;
if (kind == svn_node_dir)
{
err = svn_wc_check_wc (path, &is_wc, pool);
if (err)
return err;
}
if (is_wc) /* It's not only a dir, it's a working copy dir */
{
*wcprop_path = svn_stringbuf_dup (path, pool);
extend_with_adm_name
(*wcprop_path,
NULL,
0,
pool,
tmp ? SVN_WC__ADM_TMP : "",
SVN_WC__ADM_DIR_WCPROPS,
NULL);
}
else /* It's either a file, or a non-wc dir (i.e., maybe an ex-file) */
{
svn_path_split (path, wcprop_path, &entry_name, pool);
err = svn_wc_check_wc (*wcprop_path, &is_wc, pool);
if (err)
return err;
else if (! is_wc)
return svn_error_createf
(SVN_ERR_WC_OBSTRUCTED_UPDATE, 0, NULL, pool,
"wcprop_path: %s is not a working copy directory",
(*wcprop_path)->data);
extend_with_adm_name (*wcprop_path,
NULL,
0,
pool,
tmp ? SVN_WC__ADM_TMP : "",
SVN_WC__ADM_WCPROPS,
entry_name->data,
NULL);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__prop_path (svn_stringbuf_t **prop_path,
const svn_stringbuf_t *path,
svn_boolean_t tmp,
apr_pool_t *pool)
{
return prop_path_internal (prop_path, path, FALSE, tmp, pool);
}
svn_error_t *
svn_wc__prop_base_path (svn_stringbuf_t **prop_path,
const svn_stringbuf_t *path,
svn_boolean_t tmp,
apr_pool_t *pool)
{
return prop_path_internal (prop_path, path, TRUE, tmp, pool);
}
/*** Opening and closing files in the adm area. ***/
/* Open a file somewhere in the adm area for directory PATH.
* First, the adm subdir is appended as a path component, then each of
* the varargs (they are char *'s) is appended as a path component,
* and the resulting file opened.
*
* If FLAGS indicates writing, then the file is opened in the adm tmp
* area, whence it must be renamed, either by passing the sync flag to
* close_adm_file() or with an explicit call to sync_adm_file().
*/
static svn_error_t *
open_adm_file (apr_file_t **handle,
svn_stringbuf_t *path,
const char *extension,
apr_int32_t flags,
apr_pool_t *pool,
...)
{
svn_error_t *err = NULL;
apr_status_t apr_err = 0;
int components_added;
va_list ap;
/* If we're writing, always do it to a tmp file. */
if (flags & APR_WRITE)
{
if (flags & APR_APPEND)
{
svn_stringbuf_t *opath, *tmp_path; /* just keep it all local */
opath = svn_stringbuf_dup (path, pool);
tmp_path = svn_stringbuf_dup (path, pool);
va_start (ap, pool);
v_extend_with_adm_name (opath, extension, 0, pool, ap);
va_end (ap);
va_start (ap, pool);
v_extend_with_adm_name (tmp_path, extension, 1, pool, ap);
va_end (ap);
/* Copy the original thing to the tmp location. */
err = maybe_copy_file (opath, tmp_path, pool);
if (err)
return err;
}
/* Extend with tmp name. */
va_start (ap, pool);
components_added
= v_extend_with_adm_name (path, extension, 1, pool, ap);
va_end (ap);
}
else
{
/* Extend with regular adm name. */
va_start (ap, pool);
components_added
= v_extend_with_adm_name (path, extension, 0, pool, ap);
va_end (ap);
}
apr_err = apr_file_open (handle, path->data, flags, APR_OS_DEFAULT, pool);
if (apr_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;
err = svn_error_create (apr_err, 0, NULL, pool, path->data);
}
/* Restore path to its original state no matter what. */
chop_admin_name (path, components_added);
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,
svn_stringbuf_t *path,
const char *extension,
svn_boolean_t sync,
apr_pool_t *pool,
...)
{
apr_status_t apr_err = 0;
int components_added;
va_list ap;
/* Get the full name of the thing we want. */
va_start (ap, pool);
components_added = v_extend_with_adm_name (path, extension, sync, pool, ap);
va_end (ap);
apr_err = apr_file_close (fp);
/* Restore path to its original state no matter what. */
chop_admin_name (path, components_added);
if (apr_err)
return svn_error_create (apr_err, 0, NULL, pool, path->data);
/* If we were writing, then it was to a tmp file, which will have to
be renamed after closing. */
if (sync)
{
/* Some code duplication with sync_adm_file() seems unavoidable,
given how C va_lists work. */
svn_stringbuf_t *tmp_path = svn_stringbuf_dup (path, pool);
/* Extend real name. */
va_start (ap, pool);
components_added = v_extend_with_adm_name (path, extension,
0, pool, ap);
va_end (ap);
/* Extend tmp name. */
va_start (ap, pool);
v_extend_with_adm_name (tmp_path, extension, 1, pool, ap);
va_end (ap);
/* Remove read-only flag on destination. */
SVN_ERR (svn_io_set_file_read_write (path->data, TRUE, pool));
/* Rename. */
apr_err = apr_file_rename (tmp_path->data, path->data, pool);
if (APR_STATUS_IS_SUCCESS(apr_err))
SVN_ERR (svn_io_set_file_read_only (path->data, FALSE, pool));
/* Unconditionally restore path. */
chop_admin_name (path, components_added);
if (apr_err)
return svn_error_createf (apr_err, 0, NULL, pool,
"error renaming %s to %s",
tmp_path->data, path->data);
else
return SVN_NO_ERROR;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__open_adm_file (apr_file_t **handle,
const svn_stringbuf_t *path,
const char *fname,
apr_int32_t flags,
apr_pool_t *pool)
{
*handle = NULL; /* satisfy APR's bizarre requirement */
/* ### We are casting away const here, and open_adm_file will modify
path. (It will append things, possibly requiring memory allocation,
before returning it will chop of the appended stuff, but the string
retains the allocated memory.) */
return open_adm_file (handle, (svn_stringbuf_t *) path, NULL,
flags, pool, fname, NULL);
}
svn_error_t *
svn_wc__close_adm_file (apr_file_t *fp,
const svn_stringbuf_t *path,
const char *fname,
int sync,
apr_pool_t *pool)
{
/* ### We are casting away const here, and open_adm_file will modify
path. (It will append things, possibly requiring memory allocation,
before returning it will chop of the appended stuff, but the string
retains the allocated memory.) */
return close_adm_file (fp, (svn_stringbuf_t *) path, NULL,
sync, pool, fname, NULL);
}
svn_error_t *
svn_wc__remove_adm_file (svn_stringbuf_t *path, apr_pool_t *pool, ...)
{
svn_error_t *err = NULL;
apr_status_t apr_err = 0;
int components_added;
va_list ap;
va_start (ap, pool);
components_added = v_extend_with_adm_name (path, NULL, 0, pool, ap);
va_end (ap);
/* Remove read-only flag on path. */
SVN_ERR(svn_io_set_file_read_write (path->data, FALSE, pool));
apr_err = apr_file_remove (path->data, pool);
if (apr_err)
err = svn_error_create (apr_err, 0, NULL, pool, path->data);
/* Restore path to its original state no matter what. */
chop_admin_name (path, components_added);
return err;
}
svn_stringbuf_t *
svn_wc__empty_file_path (const svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_stringbuf_t *empty_file_path = svn_stringbuf_dup (path, pool);
svn_path_remove_component (empty_file_path);
extend_with_adm_name (empty_file_path, NULL, 0, pool, SVN_WC__ADM_EMPTY_FILE,
NULL);
return empty_file_path;
}
svn_error_t *
svn_wc__open_empty_file (apr_file_t **handle,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_stringbuf_t *newpath;
svn_path_split (path, &newpath, NULL, pool);
return open_adm_file (handle, newpath, NULL, APR_READ, pool,
SVN_WC__ADM_EMPTY_FILE, NULL);
}
svn_error_t *
svn_wc__close_empty_file (apr_file_t *fp,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
svn_stringbuf_t *newpath, *basename;
svn_path_split (path, &newpath, &basename, pool);
return close_adm_file (fp, newpath, NULL, 0, pool,
SVN_WC__ADM_EMPTY_FILE, NULL);
}
svn_error_t *
svn_wc__open_text_base (apr_file_t **handle,
svn_stringbuf_t *path,
apr_int32_t flags,
apr_pool_t *pool)
{
svn_stringbuf_t *newpath, *basename;
svn_path_split (path, &newpath, &basename, pool);
return open_adm_file (handle, newpath, SVN_WC__BASE_EXT, flags, pool,
SVN_WC__ADM_TEXT_BASE, basename->data, NULL);
}
svn_error_t *
svn_wc__close_text_base (apr_file_t *fp,
svn_stringbuf_t *path,
int write,
apr_pool_t *pool)
{
svn_stringbuf_t *newpath, *basename;
svn_path_split (path, &newpath, &basename, pool);
return close_adm_file (fp, newpath, SVN_WC__BASE_EXT, write, pool,
SVN_WC__ADM_TEXT_BASE, basename->data, NULL);
}
svn_error_t *
svn_wc__open_auth_file (apr_file_t **handle,
svn_stringbuf_t *path,
svn_stringbuf_t *auth_filename,
apr_int32_t flags,
apr_pool_t *pool)
{
return open_adm_file (handle, path, NULL, flags, pool,
SVN_WC__ADM_AUTH_DIR, auth_filename->data, NULL);
}
svn_error_t *
svn_wc__close_auth_file (apr_file_t *handle,
svn_stringbuf_t *path,
svn_stringbuf_t *file,
int sync,
apr_pool_t *pool)
{
return close_adm_file (handle, path, NULL, sync, pool,
SVN_WC__ADM_AUTH_DIR, file->data, NULL);
}
svn_error_t *
svn_wc__open_props (apr_file_t **handle,
svn_stringbuf_t *path,
apr_int32_t flags,
svn_boolean_t base,
svn_boolean_t wcprops,
apr_pool_t *pool)
{
svn_stringbuf_t *parent_dir, *basename;
enum svn_node_kind kind;
/* Check if path is a file or a dir. */
SVN_ERR (svn_io_check_path (path->data, &kind, pool));
/* If file, split the path. */
if (kind == svn_node_file)
svn_path_split (path, &parent_dir, &basename, 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, 0, NULL, pool,
"open_props: no such thing as 'base' wcprops!");
else if (base)
{
if (kind == svn_node_dir)
return open_adm_file (handle, parent_dir, NULL, flags, pool,
SVN_WC__ADM_DIR_PROP_BASE, NULL);
else
return open_adm_file (handle, parent_dir, SVN_WC__BASE_EXT, flags,
pool, SVN_WC__ADM_PROP_BASE, basename->data,
NULL);
}
else if (wcprops)
{
if (kind == svn_node_dir)
return open_adm_file (handle, parent_dir, NULL, flags, pool,
SVN_WC__ADM_DIR_WCPROPS, NULL);
else
return open_adm_file (handle, parent_dir, NULL, flags,
pool, SVN_WC__ADM_WCPROPS, basename->data,
NULL);
}
else /* plain old property file */
{
if (kind == svn_node_dir)
return open_adm_file (handle, parent_dir, NULL, flags, pool,
SVN_WC__ADM_DIR_PROPS, NULL);
else
return open_adm_file (handle, parent_dir, NULL, flags,
pool, SVN_WC__ADM_PROPS, basename->data,
NULL);
}
}
svn_error_t *
svn_wc__close_props (apr_file_t *fp,
svn_stringbuf_t *path,
svn_boolean_t base,
svn_boolean_t wcprops,
int sync,
apr_pool_t *pool)
{
svn_stringbuf_t *parent_dir, *basename;
enum svn_node_kind kind;
/* Check if path is a file or a dir. */
SVN_ERR (svn_io_check_path (path->data, &kind, pool));
/* If file, split the path. */
if (kind == svn_node_file)
svn_path_split (path, &parent_dir, &basename, 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, 0, NULL, pool,
"close_props: no such thing as 'base' wcprops!");
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, basename->data, 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, NULL, sync, pool,
SVN_WC__ADM_WCPROPS, basename->data, 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, NULL, sync, pool,
SVN_WC__ADM_PROPS, basename->data, NULL);
}
}
svn_error_t *
svn_wc__sync_props (svn_stringbuf_t *path,
svn_boolean_t base,
svn_boolean_t wcprops,
apr_pool_t *pool)
{
svn_stringbuf_t *parent_dir, *basename;
enum svn_node_kind kind;
/* Check if path is a file or a dir. */
SVN_ERR (svn_io_check_path (path->data, &kind, pool));
/* If file, split the path. */
if (kind == svn_node_file)
svn_path_split (path, &parent_dir, &basename, 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, 0, NULL, pool,
"close_props: no such thing as 'base' wcprops!");
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, basename->data, 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, basename->data, 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, NULL, pool,
SVN_WC__ADM_PROPS, basename->data, NULL);
}
}
/*** Checking for and creating administrative subdirs. ***/
/* Set *EXISTS to non-zero iff there's an adm area for PATH, and it
* matches URL and REVISION.
* ### todo: The url/rev match is not currently implemented.
*
* If an error occurs, just return the error and don't touch *EXISTS.
*/
static svn_error_t *
check_adm_exists (int *exists,
svn_stringbuf_t *path,
svn_stringbuf_t *url,
svn_revnum_t revision,
apr_pool_t *pool)
{
svn_error_t *err = NULL;
enum svn_node_kind kind;
int dir_exists = 0;
apr_file_t *f = NULL;
int components_added;
/** Step 1: check that the directory exists. **/
components_added = extend_with_adm_name (path, NULL, 0, pool, NULL);
err = svn_io_check_path (path->data, &kind, pool);
if (!err)
{
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. */
err = svn_error_create (APR_ENOTDIR, 0, NULL, pool, path->data);
}
else if (kind == svn_node_none)
{
dir_exists = 0;
}
else /* must be a dir. */
{
assert (kind == svn_node_dir);
dir_exists = 1;
}
}
/* Restore path to its original state. */
chop_admin_name (path, components_added);
/** Step 1. If no adm directory, then we're done. */
if (err)
return err;
else if (! dir_exists)
{
*exists = 0;
return SVN_NO_ERROR;
}
/** The directory exists, but is it a valid working copy yet?
Try step 2: checking that SVN_WC__ADM_README exists. **/
err = svn_wc__open_adm_file (&f, path, SVN_WC__ADM_README, APR_READ, pool);
if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
return err;
else if (err)
{
svn_error_clear_all (err);
*exists = 0;
}
else
*exists = 1;
err = svn_wc__close_adm_file (f, path, SVN_WC__ADM_README, 0, pool);
if (err)
return err;
/** kff todo:
Step 3: now check that repos and ancestry are correct **/
return SVN_NO_ERROR;
}
static svn_error_t *
make_empty_adm (svn_stringbuf_t *path, apr_pool_t *pool)
{
svn_error_t *err = NULL;
apr_status_t apr_err;
int components_added;
components_added = extend_with_adm_name (path, NULL, 0, pool, NULL);
apr_err = apr_dir_make (path->data, APR_OS_DEFAULT, pool);
if (apr_err)
err = svn_error_create (apr_err, 0, NULL, pool, path->data);
chop_admin_name (path, components_added);
return err;
}
/* Init an adm file with some contents.
Don't call this until a tmp area exists in adm. */
static svn_error_t *
init_adm_file (svn_stringbuf_t *path,
const char *thing,
svn_stringbuf_t *contents,
apr_pool_t *pool)
{
svn_error_t *err;
apr_status_t apr_err;
apr_file_t *f = NULL;
apr_size_t written = 0;
err = svn_wc__open_adm_file (&f, path, thing, APR_WRITE | APR_CREATE, pool);
if (err)
return err;
apr_err = apr_file_write_full (f, contents->data, contents->len, &written);
err = svn_wc__close_adm_file (f, path, thing, 1, pool);
if (err)
return err;
if (apr_err)
err = svn_error_create (apr_err, 0, NULL, pool, path->data);
return err;
}
static svn_error_t *
init_adm_tmp_area (svn_stringbuf_t *path,
apr_pool_t *pool)
{
/* Default perms */
apr_fileperms_t perms = APR_OS_DEFAULT;
/* SVN_WC__ADM_TMP */
SVN_ERR (svn_wc__make_adm_thing (path, 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 (path, 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 (path, 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 (path, SVN_WC__ADM_PROPS,
svn_node_dir, perms, 1, pool));
/* SVN_WC__ADM_TMP/SVN_WC__ADM_WCPROPS */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_WCPROPS,
svn_node_dir, perms, 1, pool));
/* SVN_WC__ADM_TMP/SVN_WC__ADM_AUTH_DIR */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_AUTH_DIR,
svn_node_dir,
(APR_UREAD | APR_UWRITE | APR_UEXECUTE),
1, pool));
return SVN_NO_ERROR;
}
/* Set up a new adm area for PATH, with URL as the ancestor url.
The adm area starts out locked; remember to unlock it when done. */
static svn_error_t *
init_adm (svn_stringbuf_t *path,
svn_stringbuf_t *url,
apr_pool_t *pool)
{
/* Default perms */
apr_fileperms_t perms = APR_OS_DEFAULT;
/* Initial contents for certain adm files. */
const char *format_contents = "1\n";
const char *readme_contents =
"This is a Subversion working copy administrative directory.\n"
"Visit http://subversion.tigris.org/ for more information.\n";
/* First, make an empty administrative area. */
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_lock (path, 0, pool));
/** Make subdirectories. ***/
/* SVN_WC__ADM_TEXT_BASE */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_TEXT_BASE,
svn_node_dir, perms, 0, pool));
/* SVN_WC__ADM_PROP_BASE */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_PROP_BASE,
svn_node_dir, perms, 0, pool));
/* SVN_WC__ADM_PROPS */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_PROPS,
svn_node_dir, perms, 0, pool));
/* SVN_WC__ADM_WCPROPS */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_WCPROPS,
svn_node_dir, perms, 0, pool));
/* SVN_WC__ADM_AUTH_DIR */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_AUTH_DIR,
svn_node_dir,
(APR_UREAD | APR_UWRITE | APR_UEXECUTE),
0, pool));
/** Init the tmp area. ***/
SVN_ERR (init_adm_tmp_area (path, pool));
/** Initialize each administrative file. */
/* SVN_WC__ADM_FORMAT */
SVN_ERR (init_adm_file (path, SVN_WC__ADM_FORMAT,
svn_stringbuf_create (format_contents, pool), pool));
/* SVN_WC__ADM_ENTRIES */
SVN_ERR (svn_wc__entries_init (path, url, pool));
/* SVN_WC__ADM_EMPTY_FILE exists because sometimes an readable, empty
file is required (in the repository diff for example). Creating such a
file temporarily, only to delete it again, would appear to be less
efficient than just having one around. It doesn't take up much space
after all. */
SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_EMPTY_FILE, svn_node_file,
APR_UREAD, 0, pool));
/* THIS FILE MUST BE CREATED LAST:
After this exists, the dir is considered complete. */
SVN_ERR (init_adm_file (path, SVN_WC__ADM_README,
svn_stringbuf_create (readme_contents, pool),
pool));
/* Now unlock it. It's now a valid working copy directory, that
just happens to be at revision 0. */
SVN_ERR (svn_wc_unlock (path, pool));
/* Else no problems, we're outta here. */
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__ensure_adm (svn_stringbuf_t *path,
svn_stringbuf_t *url,
svn_revnum_t revision,
apr_pool_t *pool)
{
int exists_already;
SVN_ERR (check_adm_exists (&exists_already, path, url, revision, pool));
return (exists_already ? SVN_NO_ERROR : init_adm (path, url, pool));
}
svn_error_t *
svn_wc__adm_destroy (svn_stringbuf_t *path, apr_pool_t *pool)
{
/* Try to lock the admin directory, hoping that this function will
eject an error if we're already locked (which is fine, cause if
it is already locked, we certainly don't want to blow it away. */
SVN_ERR (svn_wc_lock (path, 0, pool));
/* Well, I think the coast is clear for blowing away this directory
(which should also remove the lock file we created above) */
{
apr_status_t apr_err;
svn_stringbuf_t *adm_path = svn_stringbuf_dup (path, pool);
svn_path_add_component (adm_path, svn_wc__adm_subdir (pool));
apr_err = apr_dir_remove_recursively (adm_path->data, pool);
if (apr_err)
return svn_error_createf
(apr_err, 0, NULL, pool,
"error removing administrative directory for %s",
path->data);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__adm_cleanup_tmp_area (svn_stringbuf_t *path, apr_pool_t *pool)
{
svn_boolean_t was_locked;
svn_stringbuf_t *tmp_path;
apr_status_t apr_err;
/* Lock the admin area if it's not already locked. */
SVN_ERR (svn_wc_locked (&was_locked, path, pool));
if (! was_locked)
SVN_ERR (svn_wc_lock (path, 0, pool));
/* Get the path to the tmp area, and blow it away. */
tmp_path = svn_stringbuf_dup (path, pool);
extend_with_adm_name (tmp_path, NULL, 0, pool, SVN_WC__ADM_TMP, NULL);
apr_err = apr_dir_remove_recursively (tmp_path->data, pool);
if (apr_err)
return svn_error_createf
(apr_err, 0, NULL, pool,
"error removing tmp area in administrative directory for %s",
path->data);
/* Now, rebuild the tmp area. */
SVN_ERR (init_adm_tmp_area (path, pool));
/* Unlock the admin area if it wasn't locked when we entered this
function. */
if (! was_locked)
SVN_ERR (svn_wc_unlock (path, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_create_tmp_file (apr_file_t **fp,
svn_stringbuf_t *path,
svn_boolean_t delete_on_close,
apr_pool_t *pool)
{
svn_stringbuf_t *truepath = svn_stringbuf_dup (path, pool);
svn_stringbuf_t *ignored_filename;
/* Tack on the administrative subdirectory. */
svn_path_add_component_nts (truepath, adm_subdir());
/* Tack on the temp area. */
svn_path_add_component_nts (truepath, SVN_WC__ADM_TMP);
/* Tack on a made-up filename. */
svn_path_add_component_nts (truepath, "tempfile");
/* Open a unique file; use APR_DELONCLOSE. */
SVN_ERR (svn_io_open_unique_file (fp, &ignored_filename,
truepath->data, ".tmp",
delete_on_close, pool));
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/