blob: 374f32810c23dc7b32707650ecd4546f19ca86a4 [file] [log] [blame]
/*
* lock.c: routines for locking working copy subdirectories.
*
* ====================================================================
* Copyright (c) 2000-2008 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 <apr_pools.h>
#include <apr_time.h>
#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_sorts.h"
#include "svn_types.h"
#include "wc.h"
#include "adm_files.h"
#include "lock.h"
#include "questions.h"
#include "props.h"
#include "log.h"
#include "entries.h"
#include "wc_db.h"
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
typedef struct
{
/* Handle to the administrative database. */
svn_wc__db_t *db;
/* SET is a hash of svn_wc_adm_access_t* keyed on char* representing the
path to directories that are open. */
apr_hash_t *set;
} svn_wc__adm_shared_t;
struct svn_wc_adm_access_t
{
/* The working copy format version number for the directory */
int wc_format;
/* PATH to directory which contains the administrative area */
const char *path;
enum svn_wc__adm_access_type {
/* SVN_WC__ADM_ACCESS_UNLOCKED indicates no lock is held allowing
read-only access */
svn_wc__adm_access_unlocked,
/* SVN_WC__ADM_ACCESS_WRITE_LOCK indicates that a write lock is held
allowing read-write access */
svn_wc__adm_access_write_lock,
/* SVN_WC__ADM_ACCESS_CLOSED indicates that the baton has been
closed. */
svn_wc__adm_access_closed
} type;
/* LOCK_EXISTS is set TRUE when the write lock exists */
svn_boolean_t lock_exists;
/* SHARED contains state that is shared among all associated
access batons. */
svn_wc__adm_shared_t *shared;
/* SET_OWNER is TRUE if SET is allocated from this access baton */
svn_boolean_t set_owner;
/* ENTRIES_HIDDEN is all cached entries including those in
state deleted or state absent. It may be NULL. */
apr_hash_t *entries_all;
/* A hash mapping const char * entry names to hashes of wcprops.
These hashes map const char * names to svn_string_t * values.
NULL of the wcprops hasn't been read into memory.
### Since there are typically just one or two wcprops per entry,
### we could use a more compact way of storing them. */
apr_hash_t *wcprops;
/* POOL is used to allocate cached items, they need to persist for the
lifetime of this access baton */
apr_pool_t *pool;
};
/* This is a placeholder used in the set hash to represent missing
directories. Only its address is important, it contains no useful
data. */
static const svn_wc_adm_access_t missing;
static svn_error_t *
do_close(svn_wc_adm_access_t *adm_access, svn_boolean_t preserve_lock,
svn_boolean_t recurse, apr_pool_t *scratch_pool);
static void join_batons(svn_wc__adm_shared_t *dst_shared,
svn_wc_adm_access_t *t_access,
apr_pool_t *pool);
/* Defining this conditional will result in a client that will refuse to
upgrade working copies. This can be useful if you want to avoid
problems caused by accidentally running a development version of SVN
on a working copy that you typically use with an older version. */
#ifndef SVN_DISABLE_WC_UPGRADE
/* Write, to LOG_ACCUM, log entries to convert an old WC that did not have
propcaching into a WC that uses propcaching. Do this conversion for
the directory of ADM_ACCESS and its file children. Use POOL for
temporary allocations. */
static svn_error_t *
introduce_propcaching(svn_stringbuf_t *log_accum,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
apr_hash_t *entries;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create(pool);
SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
/* Reinstall the properties for each file and this dir; subdirs are handled
when they're opened. */
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
{
void *val;
const svn_wc_entry_t *entry;
const char *entrypath;
apr_hash_t *base_props, *props;
apr_hash_this(hi, NULL, NULL, &val);
entry = val;
if (entry->kind != svn_node_file
&& strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) != 0)
continue;
svn_pool_clear(subpool);
entrypath = svn_dirent_join(adm_access->path, entry->name, subpool);
SVN_ERR(svn_wc__load_props(&base_props, &props, NULL, adm_access,
entrypath, subpool));
SVN_ERR(svn_wc__install_props(&log_accum, adm_access, entrypath,
base_props, props, TRUE, subpool));
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
/* Write, to LOG_ACCUM, commands to convert a WC that has wcprops in individual
files to use one wcprops file per directory.
Do this for ADM_ACCESS and its file children, using POOL for temporary
allocations. */
static svn_error_t *
convert_wcprops(svn_stringbuf_t *log_accum,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
apr_hash_t *entries;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create(pool);
SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
/* Walk over the entries, adding a modify-wcprop command for each wcprop.
Note that the modifications happen in memory and are just written once
at the end of the log execution, so this isn't as inefficient as it
might sound. */
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
{
void *val;
const svn_wc_entry_t *entry;
apr_hash_t *wcprops;
apr_hash_index_t *hj;
const char *full_path;
apr_hash_this(hi, NULL, NULL, &val);
entry = val;
full_path = svn_dirent_join(adm_access->path, entry->name, pool);
if (entry->kind != svn_node_file
&& strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) != 0)
continue;
svn_pool_clear(subpool);
SVN_ERR(svn_wc__wcprop_list(&wcprops, entry->name, adm_access, subpool));
/* Create a subsubpool for the inner loop...
No, just kidding. There are typically just one or two wcprops
per entry... */
for (hj = apr_hash_first(subpool, wcprops); hj; hj = apr_hash_next(hj))
{
const void *key2;
void *val2;
const char *propname;
svn_string_t *propval;
apr_hash_this(hj, &key2, NULL, &val2);
propname = key2;
propval = val2;
SVN_ERR(svn_wc__loggy_modify_wcprop(&log_accum, adm_access,
full_path, propname,
propval->data,
subpool));
}
}
return SVN_NO_ERROR;
}
/* Maybe upgrade the working copy directory represented by ADM_ACCESS
to the latest 'SVN_WC__VERSION'. ADM_ACCESS must contain a write
lock. Use POOL for all temporary allocation.
Not all upgrade paths are necessarily supported. For example,
upgrading a version 1 working copy results in an error.
Sometimes the format file can contain "0" while the administrative
directory is being constructed; calling this on a format 0 working
copy has no effect and returns no error. */
static svn_error_t *
maybe_upgrade_format(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
{
SVN_ERR(svn_wc__check_format(adm_access->wc_format,
adm_access->path,
pool));
/* We can upgrade all formats that are accepted by
svn_wc__check_format. */
if (adm_access->wc_format < SVN_WC__VERSION)
{
svn_boolean_t cleanup_required;
svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
/* Don't try to mess with the WC if there are old log files left. */
SVN_ERR(svn_wc__adm_is_cleanup_required(&cleanup_required,
adm_access, pool));
if (cleanup_required)
return SVN_NO_ERROR;
/* First, loggily upgrade the format file. */
SVN_ERR(svn_wc__loggy_upgrade_format(&log_accum, SVN_WC__VERSION, pool));
/* Possibly convert an old WC that doesn't use propcaching. */
if (adm_access->wc_format <= SVN_WC__NO_PROPCACHING_VERSION)
SVN_ERR(introduce_propcaching(log_accum, adm_access, pool));
/* If the WC uses one file per entry for wcprops, give back some inodes
to the poor user. */
if (adm_access->wc_format <= SVN_WC__WCPROPS_MANY_FILES_VERSION)
SVN_ERR(convert_wcprops(log_accum, adm_access, pool));
SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
if (adm_access->wc_format <= SVN_WC__WCPROPS_MANY_FILES_VERSION)
{
/* Remove wcprops directory, dir-props, README.txt and empty-file
files.
We just silently ignore errors, because keeping these files is
not catastrophic. */
svn_error_clear(svn_io_remove_dir2(
svn_wc__adm_child(adm_access->path, SVN_WC__ADM_WCPROPS, pool),
FALSE, NULL, NULL, pool));
svn_error_clear(svn_io_remove_file(
svn_wc__adm_child(adm_access->path, SVN_WC__ADM_DIR_WCPROPS,
pool),
pool));
svn_error_clear(svn_io_remove_file(
svn_wc__adm_child(adm_access->path, SVN_WC__ADM_EMPTY_FILE, pool),
pool));
svn_error_clear(svn_io_remove_file(
svn_wc__adm_child(adm_access->path, SVN_WC__ADM_README, pool),
pool));
}
SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
}
return SVN_NO_ERROR;
}
#else
/* Alternate version of the above for use when working copy upgrades
are disabled. Return an error if the working copy described by
ADM_ACCESS is not at the latest 'SVN_WC__VERSION'. Use POOL for all
temporary allocation. */
static svn_error_t *
maybe_upgrade_format(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
{
SVN_ERR(svn_wc__check_format(adm_access->wc_format,
adm_access->path,
pool));
if (adm_access->wc_format != SVN_WC__VERSION)
{
return svn_error_createf(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
"Would upgrade working copy '%s' from old "
"format (%d) to current format (%d), "
"but automatic upgrade has been disabled",
svn_path_local_style(adm_access->path, pool),
adm_access->wc_format, SVN_WC__VERSION);
}
return SVN_NO_ERROR;
}
#endif
/* Create a physical lock file in the admin directory for ADM_ACCESS.
Note: most callers of this function determine the wc_format for the
lock soon afterwards. We recommend calling maybe_upgrade_format()
as soon as you have the wc_format for a lock, since that's a good
opportunity to drag old working directories into the modern era. */
static svn_error_t *
create_lock(const char *path, apr_pool_t *pool)
{
const char *lock_path = svn_wc__adm_child(path, SVN_WC__ADM_LOCK, pool);
svn_error_t *err;
apr_file_t *file;
err = svn_io_file_open(&file, lock_path,
APR_WRITE | APR_CREATE | APR_EXCL,
APR_OS_DEFAULT,
pool);
if (err == NULL)
return svn_io_file_close(file, pool);
if (APR_STATUS_IS_EEXIST(err->apr_err))
{
svn_error_clear(err);
return svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
_("Working copy '%s' locked"),
svn_path_local_style(path, pool));
}
return err;
}
/* An APR pool cleanup handler. This handles access batons that have not
been closed when their pool gets destroyed. The physical locks
associated with such batons remain in the working copy if they are
protecting a log file. */
static apr_status_t
pool_cleanup(void *p)
{
svn_wc_adm_access_t *lock = p;
svn_boolean_t cleanup;
svn_error_t *err;
if (lock->type == svn_wc__adm_access_closed)
return SVN_NO_ERROR;
err = svn_wc__adm_is_cleanup_required(&cleanup, lock, lock->pool);
if (!err)
err = do_close(lock, cleanup, TRUE, lock->pool);
/* ### Is this the correct way to handle the error? */
if (err)
{
apr_status_t apr_err = err->apr_err;
svn_error_clear(err);
return apr_err;
}
else
return APR_SUCCESS;
}
/* An APR pool cleanup handler. This is a child handler, it removes the
main pool handler. */
static apr_status_t
pool_cleanup_child(void *p)
{
svn_wc_adm_access_t *lock = p;
apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup);
return APR_SUCCESS;
}
/* Allocate from POOL, initialise and return an access baton. TYPE and PATH
are used to initialise the baton. */
static svn_error_t *
adm_access_alloc(svn_wc_adm_access_t **adm_access,
enum svn_wc__adm_access_type type,
const char *path,
apr_pool_t *pool)
{
svn_wc_adm_access_t *lock = apr_palloc(pool, sizeof(*lock));
lock->type = type;
lock->entries_all = NULL;
lock->wcprops = NULL;
lock->wc_format = 0;
lock->shared = NULL;
lock->lock_exists = FALSE;
lock->set_owner = FALSE;
lock->path = apr_pstrdup(pool, path);
lock->pool = pool;
*adm_access = lock;
if (type == svn_wc__adm_access_write_lock)
{
SVN_ERR(create_lock(path, pool));
lock->lock_exists = TRUE;
}
else
lock->lock_exists = FALSE;
return SVN_NO_ERROR;
}
/* Add LOCK into the SHARED set, for the specified PATH. The lock will
be updated to refer to SHARED, since it is now part of that set. */
static void
add_to_shared(const char *path,
svn_wc_adm_access_t *lock,
svn_wc__adm_shared_t *shared)
{
apr_hash_set(shared->set, path, APR_HASH_KEY_STRING, lock);
if (lock != &missing)
lock->shared = shared;
}
static void
adm_ensure_set(svn_wc_adm_access_t *adm_access)
{
if (adm_access->shared == NULL)
adm_access->shared = apr_pcalloc(adm_access->pool,
sizeof(*adm_access->shared));
if (adm_access->shared->set == NULL)
{
adm_access->set_owner = TRUE;
adm_access->shared->set = apr_hash_make(adm_access->pool);
add_to_shared(adm_access->path, adm_access, adm_access->shared);
}
}
static svn_error_t *
probe(const char **dir,
const char *path,
int *wc_format,
apr_pool_t *pool)
{
svn_node_kind_t kind;
SVN_ERR(svn_io_check_path(path, &kind, pool));
if (kind == svn_node_dir)
SVN_ERR(svn_wc_check_wc(path, wc_format, pool));
else
*wc_format = 0;
/* a "version" of 0 means a non-wc directory */
if (kind != svn_node_dir || *wc_format == 0)
{
/* Passing a path ending in "." or ".." to svn_path_dirname() is
probably always a bad idea; certainly it is in this case.
Unfortunately, svn_path_dirname()'s current signature can't
return an error, so we have to insert the protection in this
caller, as making the larger API change would be very
destabilizing right now (just before 1.0). See issue #1617. */
const char *base_name = svn_dirent_basename(path, pool);
if ((strcmp(base_name, "..") == 0)
|| (strcmp(base_name, ".") == 0))
{
return svn_error_createf
(SVN_ERR_WC_BAD_PATH, NULL,
_("Path '%s' ends in '%s', "
"which is unsupported for this operation"),
svn_path_local_style(path, pool), base_name);
}
*dir = svn_dirent_dirname(path, pool);
}
else
*dir = path;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__adm_steal_write_lock(svn_wc_adm_access_t **adm_access,
const char *path,
apr_pool_t *pool)
{
svn_error_t *err;
svn_wc_adm_access_t *lock;
err = adm_access_alloc(&lock, svn_wc__adm_access_write_lock, path, pool);
if (err)
{
if (err->apr_err == SVN_ERR_WC_LOCKED)
{
svn_error_clear(err); /* Steal existing lock */
lock->lock_exists = TRUE; /* Seriously. We have the lock. */
}
else
return err;
}
/* We have a write lock. If the working copy has an old
format, this is the time to upgrade it. */
SVN_ERR(svn_wc_check_wc(path, &lock->wc_format, pool));
SVN_ERR(maybe_upgrade_format(lock, pool));
*adm_access = lock;
return SVN_NO_ERROR;
}
/* This is essentially the guts of svn_wc_adm_open3.
*
* If the working copy is already locked, return SVN_ERR_WC_LOCKED; if
* it is not a versioned directory, return SVN_ERR_WC_NOT_DIRECTORY.
*/
static svn_error_t *
do_open(svn_wc_adm_access_t **adm_access,
const char *path,
svn_boolean_t write_lock,
int levels_to_lock,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
svn_wc_adm_access_t *lock;
int wc_format;
svn_error_t *err;
apr_pool_t *subpool = svn_pool_create(pool);
err = svn_wc_check_wc(path, &wc_format, subpool);
if (wc_format == 0 || (err && APR_STATUS_IS_ENOENT(err->apr_err)))
{
return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, err,
_("'%s' is not a working copy"),
svn_path_local_style(path, pool));
}
svn_error_clear(err);
/* Need to create a new lock */
SVN_ERR(adm_access_alloc(&lock,
write_lock
? svn_wc__adm_access_write_lock
: svn_wc__adm_access_unlocked,
path, pool));
lock->wc_format = wc_format;
if (write_lock)
SVN_ERR(maybe_upgrade_format(lock, subpool));
if (levels_to_lock != 0)
{
apr_hash_t *entries;
apr_hash_index_t *hi;
/* Reduce levels_to_lock since we are about to recurse */
if (levels_to_lock > 0)
levels_to_lock--;
SVN_ERR(svn_wc_entries_read(&entries, lock, FALSE, subpool));
if (apr_hash_count(entries) > 0)
{
/* All the batons will accumulate on <lock>. */
adm_ensure_set(lock);
}
/* Open the tree */
for (hi = apr_hash_first(subpool, entries); hi; hi = apr_hash_next(hi))
{
void *val;
const svn_wc_entry_t *entry;
svn_wc_adm_access_t *entry_access;
const char *entry_path;
/* See if someone wants to cancel this operation. */
if (cancel_func)
{
err = cancel_func(cancel_baton);
if (err)
{
svn_error_clear(svn_wc_adm_close2(lock, subpool));
svn_pool_destroy(subpool);
return err;
}
}
apr_hash_this(hi, NULL, NULL, &val);
entry = val;
if (entry->kind != svn_node_dir
|| ! strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
continue;
/* Also skip the excluded subdir. */
if (entry->depth == svn_depth_exclude)
continue;
entry_path = svn_dirent_join(path, entry->name, subpool);
/* Don't use the subpool pool here, the lock needs to persist */
err = do_open(&entry_access, entry_path, write_lock,
levels_to_lock, cancel_func, cancel_baton,
lock->pool);
if (err == NULL)
join_batons(lock->shared, entry_access, subpool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_NOT_DIRECTORY)
{
/* See comment above regarding this assignment. */
svn_error_clear(svn_wc_adm_close2(lock, subpool));
svn_pool_destroy(subpool);
return err;
}
/* It's missing or obstructed, so store a placeholder */
svn_error_clear(err);
add_to_shared(apr_pstrdup(lock->pool, entry_path),
(svn_wc_adm_access_t *)&missing,
lock->shared);
}
/* ### what is the comment below all about? */
/* ### Perhaps we should verify that the parent and child agree
### about the URL of the child? */
}
}
/* It's important that the cleanup handler is registered *after* at least
one UTF8 conversion has been done, since such a conversion may create
the apr_xlate_t object in the pool, and that object must be around
when the cleanup handler runs. If the apr_xlate_t cleanup handler
were to run *before* the access baton cleanup handler, then the access
baton's handler won't work. */
apr_pool_cleanup_register(lock->pool, lock, pool_cleanup,
pool_cleanup_child);
*adm_access = lock;
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_adm_open3(svn_wc_adm_access_t **adm_access,
svn_wc_adm_access_t *associated,
const char *path,
svn_boolean_t write_lock,
int levels_to_lock,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
svn_error_t *err;
/* Make sure that ASSOCIATED has a set of access batons, so that we can
glom a reference to self into it. */
if (associated)
{
svn_wc_adm_access_t *lock;
adm_ensure_set(associated);
lock = apr_hash_get(associated->shared->set, path, APR_HASH_KEY_STRING);
if (lock && lock != &missing)
/* Already locked. The reason we don't return the existing baton
here is that the user is supposed to know whether a directory is
locked: if it's not locked call svn_wc_adm_open, if it is locked
call svn_wc_adm_retrieve. */
return svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
_("Working copy '%s' locked"),
svn_path_local_style(path, pool));
}
err = do_open(adm_access, path,
write_lock, levels_to_lock, cancel_func, cancel_baton, pool);
if (err == NULL && associated != NULL)
join_batons(associated->shared, *adm_access, pool);
return err;
}
svn_error_t *
svn_wc__adm_pre_open(svn_wc_adm_access_t **adm_access,
const char *path,
apr_pool_t *pool)
{
svn_wc_adm_access_t *lock;
SVN_ERR(adm_access_alloc(&lock, svn_wc__adm_access_write_lock, path, pool));
apr_pool_cleanup_register(lock->pool, lock, pool_cleanup,
pool_cleanup_child);
*adm_access = lock;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access,
svn_wc_adm_access_t *associated,
const char *path,
svn_boolean_t write_lock,
int levels_to_lock,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
svn_error_t *err;
const char *dir;
int wc_format;
SVN_ERR(probe(&dir, path, &wc_format, pool));
/* If we moved up a directory, then the path is not a directory, or it
is not under version control. In either case, the notion of
levels_to_lock does not apply to the provided path. Disable it so
that we don't end up trying to lock more than we need. */
if (dir != path)
levels_to_lock = 0;
err = svn_wc_adm_open3(adm_access, associated, dir, write_lock,
levels_to_lock, cancel_func, cancel_baton, pool);
if (err)
{
svn_error_t *err2;
/* If we got an error on the parent dir, that means we failed to
get an access baton for the child in the first place. And if
the reason we couldn't get the child access baton is that the
child is not a versioned directory, then return an error
about the child, not the parent. */
svn_node_kind_t child_kind;
if ((err2 = svn_io_check_path(path, &child_kind, pool)))
{
svn_error_compose(err, err2);
return err;
}
if ((dir != path)
&& (child_kind == svn_node_dir)
&& (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY))
{
svn_error_clear(err);
return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
_("'%s' is not a working copy"),
svn_path_local_style(path, pool));
}
else
{
return err;
}
}
if (wc_format && ! (*adm_access)->wc_format)
(*adm_access)->wc_format = wc_format;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__adm_retrieve_internal(svn_wc_adm_access_t **adm_access,
svn_wc_adm_access_t *associated,
const char *path,
apr_pool_t *pool)
{
if (associated->shared && associated->shared->set)
*adm_access = apr_hash_get(associated->shared->set,
path, APR_HASH_KEY_STRING);
else if (! strcmp(associated->path, path))
*adm_access = associated;
else
*adm_access = NULL;
if (*adm_access == &missing)
*adm_access = NULL;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access,
svn_wc_adm_access_t *associated,
const char *path,
apr_pool_t *pool)
{
SVN_ERR(svn_wc__adm_retrieve_internal(adm_access, associated, path, pool));
/* Most of the code expects access batons to exist, so returning an error
generally makes the calling code simpler as it doesn't need to check
for NULL batons. */
if (! *adm_access)
{
const char *wcpath;
const svn_wc_entry_t *subdir_entry;
svn_node_kind_t wckind;
svn_node_kind_t kind;
svn_error_t *err;
err = svn_wc_entry(&subdir_entry, path, associated, TRUE, pool);
/* If we can't get an entry here, we are in pretty bad shape,
and will have to fall back to using just regular old paths to
see what's going on. */
if (err)
{
svn_error_clear(err);
subdir_entry = NULL;
}
err = svn_io_check_path(path, &kind, pool);
/* If we can't check the path, we can't make a good error
message. */
if (err)
{
return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, err,
_("Unable to check path existence for '%s'"),
svn_path_local_style(path, pool));
}
if (subdir_entry)
{
if (subdir_entry->kind == svn_node_dir
&& kind == svn_node_file)
{
const char *err_msg = apr_psprintf
(pool, _("Expected '%s' to be a directory but found a file"),
svn_path_local_style(path, pool));
return svn_error_create(SVN_ERR_WC_NOT_LOCKED,
svn_error_create
(SVN_ERR_WC_NOT_DIRECTORY, NULL,
err_msg),
err_msg);
}
else if (subdir_entry->kind == svn_node_file
&& kind == svn_node_dir)
{
const char *err_msg = apr_psprintf
(pool, _("Expected '%s' to be a file but found a directory"),
svn_path_local_style(path, pool));
return svn_error_create(SVN_ERR_WC_NOT_LOCKED,
svn_error_create(SVN_ERR_WC_NOT_FILE,
NULL, err_msg),
err_msg);
}
}
wcpath = svn_wc__adm_child(path, NULL, pool);
err = svn_io_check_path(wcpath, &wckind, pool);
/* If we can't check the path, we can't make a good error
message. */
if (err)
{
return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, err,
_("Unable to check path existence for '%s'"),
svn_path_local_style(wcpath, pool));
}
if (kind == svn_node_none)
{
const char *err_msg = apr_psprintf(pool,
_("Directory '%s' is missing"),
svn_path_local_style(path, pool));
return svn_error_create(SVN_ERR_WC_NOT_LOCKED,
svn_error_create(SVN_ERR_WC_PATH_NOT_FOUND,
NULL, err_msg),
err_msg);
}
else if (kind == svn_node_dir && wckind == svn_node_none)
return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
_("Directory '%s' containing working copy admin area is missing"),
svn_path_local_style(wcpath, pool));
else if (kind == svn_node_dir && wckind == svn_node_dir)
return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
_("Unable to lock '%s'"),
svn_path_local_style(path, pool));
/* If all else fails, return our useless generic error. */
return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
_("Working copy '%s' is not locked"),
svn_path_local_style(path, pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access,
svn_wc_adm_access_t *associated,
const char *path,
apr_pool_t *pool)
{
const char *dir;
const svn_wc_entry_t *entry;
int wc_format;
svn_error_t *err;
SVN_ERR(svn_wc_entry(&entry, path, associated, TRUE, pool));
if (! entry)
/* Not a versioned item, probe it */
SVN_ERR(probe(&dir, path, &wc_format, pool));
else if (entry->kind != svn_node_dir)
dir = svn_dirent_dirname(path, pool);
else
dir = path;
err = svn_wc_adm_retrieve(adm_access, associated, dir, pool);
if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
{
/* We'll receive a NOT LOCKED error for various reasons,
including the reason we'll actually want to test for:
The path is a versioned directory, but missing, in which case
we want its parent's adm_access (which holds minimal data
on the child) */
svn_error_clear(err);
SVN_ERR(probe(&dir, path, &wc_format, pool));
SVN_ERR(svn_wc_adm_retrieve(adm_access, associated, dir, pool));
}
else
return err;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_adm_probe_try2(svn_wc_adm_access_t **adm_access,
svn_wc_adm_access_t *associated,
const char *path,
svn_boolean_t write_lock,
int levels_to_lock,
apr_pool_t *pool)
{
return svn_wc_adm_probe_try3(adm_access, associated, path, write_lock,
levels_to_lock, NULL, NULL, pool);
}
svn_error_t *
svn_wc_adm_probe_try3(svn_wc_adm_access_t **adm_access,
svn_wc_adm_access_t *associated,
const char *path,
svn_boolean_t write_lock,
int levels_to_lock,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
svn_error_t *err;
err = svn_wc_adm_probe_retrieve(adm_access, associated, path, pool);
/* SVN_ERR_WC_NOT_LOCKED would mean there was no access baton for
path in associated, in which case we want to open an access
baton and add it to associated. */
if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
{
svn_error_clear(err);
err = svn_wc_adm_probe_open3(adm_access, associated,
path, write_lock, levels_to_lock,
cancel_func, cancel_baton,
svn_wc_adm_access_pool(associated));
/* If the path is not a versioned directory, we just return a
null access baton with no error. Note that of the errors we
do report, the most important (and probably most likely) is
SVN_ERR_WC_LOCKED. That error would mean that someone else
has this area locked, and we definitely want to bail in that
case. */
if (err && (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY))
{
svn_error_clear(err);
*adm_access = NULL;
err = NULL;
}
}
return err;
}
/* A helper for svn_wc_adm_open_anchor. Add all the access batons in the
T_ACCESS set, including T_ACCESS, into the DST_SHARED set. */
static void join_batons(svn_wc__adm_shared_t *dst_shared,
svn_wc_adm_access_t *t_access,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
if (t_access->shared == NULL || t_access->shared->set == NULL)
{
add_to_shared(t_access->path, t_access, dst_shared);
return;
}
for (hi = apr_hash_first(pool, t_access->shared->set);
hi;
hi = apr_hash_next(hi))
{
const void *key;
void *val;
apr_hash_this(hi, &key, NULL, &val);
add_to_shared(key, val, dst_shared);
}
t_access->set_owner = FALSE;
}
svn_error_t *
svn_wc_adm_open_anchor(svn_wc_adm_access_t **anchor_access,
svn_wc_adm_access_t **target_access,
const char **target,
const char *path,
svn_boolean_t write_lock,
int levels_to_lock,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
const char *base_name = svn_dirent_basename(path, pool);
if (svn_path_is_empty(path)
|| svn_dirent_is_root(path, strlen(path))
|| ! strcmp(base_name, ".."))
{
SVN_ERR(do_open(anchor_access, path, write_lock, levels_to_lock,
cancel_func, cancel_baton, pool));
*target_access = *anchor_access;
*target = "";
}
else
{
svn_error_t *err;
svn_wc_adm_access_t *p_access = NULL;
svn_wc_adm_access_t *t_access = NULL;
const char *parent = svn_dirent_dirname(path, pool);
svn_error_t *p_access_err = SVN_NO_ERROR;
/* Try to open parent of PATH to setup P_ACCESS */
err = do_open(&p_access, parent, write_lock, 0,
cancel_func, cancel_baton, pool);
if (err)
{
if (err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
{
svn_error_clear(err);
p_access = NULL;
}
else if (write_lock && (err->apr_err == SVN_ERR_WC_LOCKED
|| APR_STATUS_IS_EACCES(err->apr_err)))
{
/* If P_ACCESS isn't to be returned then a read-only baton
will do for now, but keep the error in case we need it. */
svn_error_t *err2 = do_open(&p_access, parent, FALSE, 0,
cancel_func, cancel_baton, pool);
if (err2)
{
svn_error_clear(err2);
return err;
}
p_access_err = err;
}
else
return err;
}
/* Try to open PATH to setup T_ACCESS */
err = do_open(&t_access, path, write_lock, levels_to_lock,
cancel_func, cancel_baton, pool);
if (err)
{
if (! p_access || err->apr_err != SVN_ERR_WC_NOT_DIRECTORY)
{
if (p_access)
svn_error_clear(svn_wc_adm_close2(p_access, pool));
svn_error_clear(p_access_err);
return err;
}
svn_error_clear(err);
t_access = NULL;
}
/* At this stage might have P_ACCESS, T_ACCESS or both */
/* Check for switched or disjoint P_ACCESS and T_ACCESS */
if (p_access && t_access)
{
const svn_wc_entry_t *t_entry, *p_entry, *t_entry_in_p;
err = svn_wc_entry(&t_entry_in_p, path, p_access, FALSE, pool);
if (! err)
err = svn_wc_entry(&t_entry, path, t_access, FALSE, pool);
if (! err)
err = svn_wc_entry(&p_entry, parent, p_access, FALSE, pool);
if (err)
{
svn_error_clear(p_access_err);
svn_error_clear(svn_wc_adm_close2(p_access, pool));
svn_error_clear(svn_wc_adm_close2(t_access, pool));
return err;
}
/* Disjoint won't have PATH in P_ACCESS, switched will have
incompatible URLs */
if (! t_entry_in_p
||
(p_entry->url && t_entry->url
&& (strcmp(svn_uri_dirname(t_entry->url, pool), p_entry->url)
|| strcmp(svn_path_uri_encode(base_name, pool),
svn_uri_basename(t_entry->url, pool)))))
{
/* Switched or disjoint, so drop P_ACCESS */
err = svn_wc_adm_close2(p_access, pool);
if (err)
{
svn_error_clear(p_access_err);
svn_error_clear(svn_wc_adm_close2(t_access, pool));
return err;
}
p_access = NULL;
}
}
if (p_access)
{
if (p_access_err)
{
/* Need P_ACCESS, so the read-only temporary won't do */
if (t_access)
svn_error_clear(svn_wc_adm_close2(t_access, pool));
svn_error_clear(svn_wc_adm_close2(p_access, pool));
return p_access_err;
}
else if (t_access)
{
adm_ensure_set(p_access);
join_batons(p_access->shared, t_access, pool);
}
}
svn_error_clear(p_access_err);
if (! t_access)
{
const svn_wc_entry_t *t_entry;
err = svn_wc_entry(&t_entry, path, p_access, FALSE, pool);
if (err)
{
svn_error_clear(svn_wc_adm_close2(p_access, pool));
return err;
}
if (t_entry && t_entry->kind == svn_node_dir)
{
adm_ensure_set(p_access);
add_to_shared(apr_pstrdup(p_access->pool, path),
(svn_wc_adm_access_t *)&missing,
p_access->shared);
}
}
*anchor_access = p_access ? p_access : t_access;
*target_access = t_access ? t_access : p_access;
if (! p_access)
*target = "";
else
*target = base_name;
}
return SVN_NO_ERROR;
}
/* Does the work of closing the access baton ADM_ACCESS. Any physical
locks are removed from the working copy if PRESERVE_LOCK is FALSE, or
are left if PRESERVE_LOCK is TRUE. Any associated access batons that
are direct descendants will also be closed.
### FIXME: If the set has a "hole", say it contains locks for the
### directories A, A/B, A/B/C/X but not A/B/C then closing A/B will not
### reach A/B/C/X .
*/
static svn_error_t *
do_close(svn_wc_adm_access_t *adm_access,
svn_boolean_t preserve_lock,
svn_boolean_t recurse,
apr_pool_t *scratch_pool)
{
if (adm_access->type == svn_wc__adm_access_closed)
return SVN_NO_ERROR;
/* Close descendant batons */
if (recurse && adm_access->shared && adm_access->shared->set)
{
int i;
apr_array_header_t *children
= svn_sort__hash(adm_access->shared->set,
svn_sort_compare_items_as_paths,
scratch_pool);
/* Go backwards through the list to close children before their
parents. */
for (i = children->nelts - 1; i >= 0; --i)
{
svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
svn_sort__item_t);
const char *path = item->key;
svn_wc_adm_access_t *child = item->value;
if (child == &missing)
{
/* We don't close the missing entry, but get rid of it from
the set. */
apr_hash_set(adm_access->shared->set,
path, APR_HASH_KEY_STRING, NULL);
continue;
}
if (! svn_dirent_is_ancestor(adm_access->path, path)
|| strcmp(adm_access->path, path) == 0)
continue;
SVN_ERR(do_close(child, preserve_lock, FALSE, scratch_pool));
}
}
/* Physically unlock if required */
if (adm_access->type == svn_wc__adm_access_write_lock)
{
if (adm_access->lock_exists && ! preserve_lock)
{
/* Remove the physical lock in the admin directory for
PATH. It is acceptable for the administrative area to
have disappeared, such as when the directory is removed
from the working copy. It is an error for the lock to
have disappeared if the administrative area still exists. */
svn_error_t *err = svn_wc__remove_adm_file(adm_access,
SVN_WC__ADM_LOCK,
scratch_pool);
if (err)
{
if (svn_wc__adm_area_exists(adm_access, scratch_pool))
return err;
svn_error_clear(err);
}
adm_access->lock_exists = FALSE;
}
}
/* Reset to prevent further use of the lock. */
adm_access->type = svn_wc__adm_access_closed;
/* Detach from set */
if (adm_access->shared && adm_access->shared->set)
{
apr_hash_set(adm_access->shared->set,
adm_access->path, APR_HASH_KEY_STRING,
NULL);
SVN_ERR_ASSERT(! adm_access->set_owner
|| apr_hash_count(adm_access->shared->set) == 0);
}
/* Close the underlying wc_db. */
if (adm_access->set_owner)
{
SVN_ERR_ASSERT(apr_hash_count(adm_access->shared->set) == 0);
if (adm_access->shared->db)
SVN_ERR(svn_wc__db_close(adm_access->shared->db, scratch_pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_adm_close2(svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool)
{
return do_close(adm_access, FALSE, TRUE, scratch_pool);
}
svn_boolean_t
svn_wc_adm_locked(const svn_wc_adm_access_t *adm_access)
{
return adm_access->type == svn_wc__adm_access_write_lock;
}
svn_error_t *
svn_wc__adm_write_check(const svn_wc_adm_access_t *adm_access,
apr_pool_t *scratch_pool)
{
if (adm_access->type == svn_wc__adm_access_write_lock)
{
if (adm_access->lock_exists)
{
/* Check physical lock still exists and hasn't been stolen. This
really is paranoia, I have only ever seen one report of this
triggering (from someone using the 0.25 release) and that was
never reproduced. The check accesses the physical filesystem
so it is expensive, but it only runs when we are going to
modify the admin area. If it ever proves to be a bottleneck
the physical check could be removed, just leaving the logical
check. */
svn_boolean_t locked;
SVN_ERR(svn_wc_locked(&locked, adm_access->path, scratch_pool));
if (! locked)
return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
_("Write-lock stolen in '%s'"),
svn_path_local_style(adm_access->path,
scratch_pool));
}
}
else
{
return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
_("No write-lock in '%s'"),
svn_path_local_style(adm_access->path,
scratch_pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_locked(svn_boolean_t *locked, const char *path, apr_pool_t *pool)
{
svn_node_kind_t kind;
const char *lockfile = svn_wc__adm_child(path, SVN_WC__ADM_LOCK, pool);
SVN_ERR(svn_io_check_path(lockfile, &kind, pool));
if (kind == svn_node_file)
*locked = TRUE;
else if (kind == svn_node_none)
*locked = FALSE;
else
return svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
_("Lock file '%s' is not a regular file"),
svn_path_local_style(lockfile, pool));
return SVN_NO_ERROR;
}
const char *
svn_wc_adm_access_path(const svn_wc_adm_access_t *adm_access)
{
return adm_access->path;
}
apr_pool_t *
svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access)
{
return adm_access->pool;
}
svn_error_t *
svn_wc__adm_is_cleanup_required(svn_boolean_t *cleanup,
const svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
if (adm_access->type == svn_wc__adm_access_write_lock)
{
svn_node_kind_t kind;
const char *log_path = svn_wc__adm_child(adm_access->path,
SVN_WC__ADM_LOG, pool);
/* The presence of a log file demands cleanup */
SVN_ERR(svn_io_check_path(log_path, &kind, pool));
*cleanup = (kind == svn_node_file);
}
else
*cleanup = FALSE;
return SVN_NO_ERROR;
}
void
svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access,
apr_hash_t *entries)
{
adm_access->entries_all = entries;
}
apr_hash_t *
svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
return adm_access->entries_all;
}
void
svn_wc__adm_access_set_wcprops(svn_wc_adm_access_t *adm_access,
apr_hash_t *wcprops)
{
adm_access->wcprops = wcprops;
}
apr_hash_t *
svn_wc__adm_access_wcprops(const svn_wc_adm_access_t *adm_access)
{
return adm_access->wcprops;
}
int
svn_wc__adm_wc_format(const svn_wc_adm_access_t *adm_access)
{
return adm_access->wc_format;
}
void
svn_wc__adm_set_wc_format(svn_wc_adm_access_t *adm_access,
int format)
{
adm_access->wc_format = format;
}
svn_error_t *
svn_wc__adm_get_db(svn_wc__db_t **db, svn_wc_adm_access_t *adm_access,
apr_pool_t *scratch_pool)
{
adm_ensure_set(adm_access);
if (adm_access->shared->db == NULL)
{
const char *abspath;
svn_wc__db_openmode_t mode;
/* ### need to determine mode based on callers' needs. */
mode = svn_wc__db_openmode_default;
SVN_ERR(svn_dirent_get_absolute(&abspath, adm_access->path,
scratch_pool));
SVN_ERR(svn_wc__db_open(&adm_access->shared->db,
mode,
abspath,
NULL /* ### need the config */,
adm_access->pool, scratch_pool));
}
*db = adm_access->shared->db;
return SVN_NO_ERROR;
}
svn_boolean_t
svn_wc__adm_missing(const svn_wc_adm_access_t *adm_access,
const char *path)
{
if (adm_access->shared
&& adm_access->shared->set
&& apr_hash_get(adm_access->shared->set,
path, APR_HASH_KEY_STRING) == &missing)
return TRUE;
return FALSE;
}
/* Extend the scope of the svn_wc_adm_access_t * passed in as WALK_BATON
for its entire WC tree. An implementation of
svn_wc_entry_callbacks2_t's found_entry() API. */
static svn_error_t *
extend_lock_found_entry(const char *path,
const svn_wc_entry_t *entry,
void *walk_baton,
apr_pool_t *pool)
{
/* If PATH is a directory, and it's not already locked, lock it all
the way down to its leaf nodes. */
if (entry->kind == svn_node_dir &&
strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) != 0)
{
svn_wc_adm_access_t *anchor_access = walk_baton, *adm_access;
svn_boolean_t write_lock =
(anchor_access->type == svn_wc__adm_access_write_lock);
svn_error_t *err = svn_wc_adm_probe_try3(&adm_access, anchor_access,
path, write_lock, -1,
NULL, NULL, pool);
if (err)
{
if (err->apr_err == SVN_ERR_WC_LOCKED)
/* Good! The directory is *already* locked... */
svn_error_clear(err);
else
return err;
}
}
return SVN_NO_ERROR;
}
/* WC entry walker callbacks for svn_wc__adm_extend_lock_to_tree(). */
static const svn_wc_entry_callbacks2_t extend_lock_walker =
{
extend_lock_found_entry,
svn_wc__walker_default_error_handler
};
svn_error_t *
svn_wc__adm_extend_lock_to_tree(svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
return svn_wc_walk_entries3(adm_access->path, adm_access,
&extend_lock_walker, adm_access,
svn_depth_infinity, FALSE, NULL, NULL, pool);
}