summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/lock.c')
-rw-r--r--subversion/libsvn_wc/lock.c1656
1 files changed, 1656 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/lock.c b/subversion/libsvn_wc/lock.c
new file mode 100644
index 0000000..36fbb0e
--- /dev/null
+++ b/subversion/libsvn_wc/lock.c
@@ -0,0 +1,1656 @@
+/*
+ * lock.c: routines for locking working copy subdirectories.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_DEPRECATED
+
+#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_hash.h"
+#include "svn_types.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "lock.h"
+#include "props.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+
+struct svn_wc_adm_access_t
+{
+ /* PATH to directory which contains the administrative area */
+ const char *path;
+
+ /* And the absolute form of the path. */
+ const char *abspath;
+
+ /* Indicates that the baton has been closed. */
+ svn_boolean_t closed;
+
+ /* Handle to the administrative database. */
+ svn_wc__db_t *db;
+
+ /* Was the DB provided to us? If so, then we'll never close it. */
+ svn_boolean_t db_provided;
+
+ /* ENTRIES_HIDDEN is all cached entries including those in
+ state deleted or state absent. It may be NULL. */
+ apr_hash_t *entries_all;
+
+ /* 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 = { 0 };
+#define IS_MISSING(lock) ((lock) == &missing)
+
+/* ### hack for now. future functionality coming in a future revision. */
+#define svn_wc__db_is_closed(db) FALSE
+
+
+svn_error_t *
+svn_wc__internal_check_wc(int *wc_format,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t check_path,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ err = svn_wc__db_temp_get_format(wc_format, db, local_abspath, scratch_pool);
+ if (err)
+ {
+ svn_node_kind_t kind;
+
+ if (err->apr_err != SVN_ERR_WC_MISSING &&
+ err->apr_err != SVN_ERR_WC_UNSUPPORTED_FORMAT &&
+ err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* ### the stuff below seems to be redundant. get_format() probably
+ ### does all this.
+ ###
+ ### investigate all callers. DEFINITELY keep in mind the
+ ### svn_wc_check_wc() entrypoint.
+ */
+
+ /* If the format file does not exist or path not directory, then for
+ our purposes this is not a working copy, so return 0. */
+ *wc_format = 0;
+
+ /* Check path itself exists. */
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ {
+ return svn_error_createf(APR_ENOENT, NULL, _("'%s' does not exist"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (*wc_format >= SVN_WC__WC_NG_VERSION)
+ {
+ svn_wc__db_status_t db_status;
+ svn_node_kind_t db_kind;
+
+ if (check_path)
+ {
+ /* If a node is not a directory, it is not a working copy
+ directory. This allows creating new working copies as
+ a path below an existing working copy. */
+ svn_node_kind_t wc_kind;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool));
+ if (wc_kind != svn_node_dir)
+ {
+ *wc_format = 0; /* Not a directory, so not a wc-directory */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ if (db_kind != svn_node_dir)
+ {
+ /* The WC thinks there must be a file, so this is not
+ a wc-directory */
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ }
+
+ switch (db_status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ /* If there is a directory here, it is not related to the parent
+ working copy: Obstruction */
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ default:
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_check_wc2(int *wc_format,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Should we pass TRUE for check_path to find obstructions and
+ missing directories? */
+ return svn_error_trace(
+ svn_wc__internal_check_wc(wc_format, wc_ctx->db, local_abspath, FALSE,
+ scratch_pool));
+}
+
+
+/* */
+static svn_error_t *
+add_to_shared(svn_wc_adm_access_t *lock, apr_pool_t *scratch_pool)
+{
+ /* ### sometimes we replace &missing with a now-valid lock. */
+ {
+ svn_wc_adm_access_t *prior = svn_wc__db_temp_get_access(lock->db,
+ lock->abspath,
+ scratch_pool);
+ if (IS_MISSING(prior))
+ SVN_ERR(svn_wc__db_temp_close_access(lock->db, lock->abspath,
+ prior, scratch_pool));
+ }
+
+ svn_wc__db_temp_set_access(lock->db, lock->abspath, lock,
+ scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_wc_adm_access_t *
+get_from_shared(const char *abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ /* We closed the DB when it became empty. ABSPATH is not present. */
+ if (db == NULL)
+ return NULL;
+ return svn_wc__db_temp_get_access(db, abspath, scratch_pool);
+}
+
+
+/* */
+static svn_error_t *
+close_single(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t preserve_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked;
+
+ if (adm_access->closed)
+ return SVN_NO_ERROR;
+
+ /* Physically unlock if required */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
+ adm_access->abspath, TRUE,
+ scratch_pool));
+ if (locked)
+ {
+ if (!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__db_wclock_release(adm_access->db,
+ adm_access->abspath,
+ scratch_pool);
+ if (err)
+ {
+ if (svn_wc__adm_area_exists(adm_access->abspath, scratch_pool))
+ return err;
+ svn_error_clear(err);
+ }
+ }
+ }
+
+ /* Reset to prevent further use of the lock. */
+ adm_access->closed = TRUE;
+
+ /* Detach from set */
+ SVN_ERR(svn_wc__db_temp_close_access(adm_access->db, adm_access->abspath,
+ adm_access, scratch_pool));
+
+ /* Possibly close the underlying wc_db. */
+ if (!adm_access->db_provided)
+ {
+ apr_hash_t *opened = svn_wc__db_temp_get_all_access(adm_access->db,
+ scratch_pool);
+ if (apr_hash_count(opened) == 0)
+ {
+ SVN_ERR(svn_wc__db_close(adm_access->db));
+ adm_access->db = NULL;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Cleanup for a locked access baton.
+
+ This handles closing access batons when their pool gets destroyed.
+ The physical locks associated with such batons remain in the working
+ copy if they are protecting work items in the workqueue. */
+static apr_status_t
+pool_cleanup_locked(void *p)
+{
+ svn_wc_adm_access_t *lock = p;
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ if (lock->closed)
+ return APR_SUCCESS;
+
+ /* If the DB is closed, then we have a bunch of extra work to do. */
+ if (svn_wc__db_is_closed(lock->db))
+ {
+ apr_pool_t *scratch_pool;
+ svn_wc__db_t *db;
+
+ lock->closed = TRUE;
+
+ /* If there is no ADM area, then we definitely have no work items
+ or physical locks to worry about. Bail out. */
+ if (!svn_wc__adm_area_exists(lock->abspath, lock->pool))
+ return APR_SUCCESS;
+
+ /* Creating a subpool is safe within a pool cleanup, as long as
+ we're absolutely sure to destroy it before we exit this function.
+
+ We avoid using LOCK->POOL to keep the following functions from
+ hanging cleanups or subpools from it. (the cleanups *might* get
+ run, but the subpools will NOT be destroyed) */
+ scratch_pool = svn_pool_create(lock->pool);
+
+ err = svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
+ scratch_pool, scratch_pool);
+ if (!err)
+ {
+ err = svn_wc__db_wq_fetch_next(&id, &work_item, db, lock->abspath, 0,
+ scratch_pool, scratch_pool);
+ if (!err && work_item == NULL)
+ {
+ /* There is no remaining work, so we're good to remove any
+ potential "physical" lock. */
+ err = svn_wc__db_wclock_release(db, lock->abspath, scratch_pool);
+ }
+ }
+ svn_error_clear(err);
+
+ /* Closes the DB, too. */
+ svn_pool_destroy(scratch_pool);
+
+ return APR_SUCCESS;
+ }
+
+ /* ### should we create an API that just looks, but doesn't return? */
+ err = svn_wc__db_wq_fetch_next(&id, &work_item, lock->db, lock->abspath, 0,
+ lock->pool, lock->pool);
+
+ /* Close just this access baton. The pool cleanup will close the rest. */
+ if (!err)
+ err = close_single(lock,
+ work_item != NULL /* preserve_lock */,
+ lock->pool);
+
+ if (err)
+ {
+ apr_status_t apr_err = err->apr_err;
+ svn_error_clear(err);
+ return apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Cleanup for a readonly access baton. */
+static apr_status_t
+pool_cleanup_readonly(void *data)
+{
+ svn_wc_adm_access_t *lock = data;
+ svn_error_t *err;
+
+ if (lock->closed)
+ return APR_SUCCESS;
+
+ /* If the DB is closed, then we have nothing to do. There are no
+ "physical" locks to remove, and we don't care whether this baton
+ is registered with the DB. */
+ if (svn_wc__db_is_closed(lock->db))
+ return APR_SUCCESS;
+
+ /* Close this baton. No lock to preserve. Since this is part of the
+ pool cleanup, we don't need to close children -- the cleanup process
+ will close all children. */
+ err = close_single(lock, FALSE /* preserve_lock */, lock->pool);
+ if (err)
+ {
+ apr_status_t result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+
+ 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_locked);
+ apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_readonly);
+
+ return APR_SUCCESS;
+}
+
+
+/* Allocate from POOL, initialise and return an access baton. TYPE and PATH
+ are used to initialise the baton. If STEAL_LOCK, steal the lock if path
+ is already locked */
+static svn_error_t *
+adm_access_alloc(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ svn_boolean_t write_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_wc_adm_access_t *lock = apr_palloc(result_pool, sizeof(*lock));
+
+ lock->closed = FALSE;
+ lock->entries_all = NULL;
+ lock->db = db;
+ lock->db_provided = db_provided;
+ lock->path = apr_pstrdup(result_pool, path);
+ lock->pool = result_pool;
+
+ SVN_ERR(svn_dirent_get_absolute(&lock->abspath, path, result_pool));
+
+ *adm_access = lock;
+
+ if (write_lock)
+ {
+ svn_boolean_t owns_lock;
+
+ /* If the db already owns a lock, we can't add an extra lock record */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, path, FALSE,
+ scratch_pool));
+
+ /* If DB owns the lock, but when there is no access baton open for this
+ directory, old access baton based code is trying to access data that
+ was previously locked by new code. Just hand them the lock, or
+ important code paths like svn_wc_add3() will start failing */
+ if (!owns_lock
+ || svn_wc__adm_retrieve_internal2(db, lock->abspath, scratch_pool))
+ {
+ SVN_ERR(svn_wc__db_wclock_obtain(db, lock->abspath, 0, FALSE,
+ scratch_pool));
+ }
+ }
+
+ err = add_to_shared(lock, scratch_pool);
+
+ if (err)
+ return svn_error_compose_create(
+ err,
+ svn_wc__db_wclock_release(db, lock->abspath, scratch_pool));
+
+ /* ### does this utf8 thing really/still apply?? */
+ /* 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. */
+
+ /* Register an appropriate cleanup handler, based on the whether this
+ access baton is locked or not. */
+ apr_pool_cleanup_register(lock->pool, lock,
+ write_lock
+ ? pool_cleanup_locked
+ : pool_cleanup_readonly,
+ pool_cleanup_child);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+probe(svn_wc__db_t *db,
+ const char **dir,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ int wc_format = 0;
+
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ if (kind == svn_node_dir)
+ {
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__internal_check_wc(&wc_format, db, local_abspath,
+ FALSE, pool));
+ }
+
+ /* 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_dirent_dirname() is
+ probably always a bad idea; certainly it is in this case.
+ Unfortunately, svn_dirent_dirname()'s current signature can't
+ return an error, so we have to insert the protection in this
+ caller, ideally the API needs a change. 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_dirent_local_style(path, pool), base_name);
+ }
+
+ *dir = svn_dirent_dirname(path, pool);
+ }
+ else
+ *dir = path;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+open_single(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ int wc_format = 0;
+ svn_error_t *err;
+ svn_wc_adm_access_t *lock;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+ err = svn_wc__internal_check_wc(&wc_format, db, local_abspath, FALSE,
+ scratch_pool);
+ if (wc_format == 0 || (err && APR_STATUS_IS_ENOENT(err->apr_err)))
+ {
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, err,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, scratch_pool));
+ }
+ SVN_ERR(err);
+
+ /* The format version must match exactly. Note that wc_db will perform
+ an auto-upgrade if allowed. If it does *not*, then it has decided a
+ manual upgrade is required and it should have raised an error. */
+ SVN_ERR_ASSERT(wc_format == SVN_WC__VERSION);
+
+ /* Need to create a new lock */
+ SVN_ERR(adm_access_alloc(&lock, path, db, db_provided, write_lock,
+ result_pool, scratch_pool));
+
+ /* ### recurse was here */
+ *adm_access = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Retrieves the KIND of LOCAL_ABSPATH and whether its administrative data is
+ available in the working copy.
+
+ *AVAILABLE is set to TRUE when the node and its metadata are available,
+ otherwise to FALSE (due to obstruction, missing, absence, exclusion,
+ or a "not-present" child).
+
+ KIND can be NULL.
+
+ ### note: this function should go away when we move to a single
+ ### adminstrative area. */
+static svn_error_t *
+adm_available(svn_boolean_t *available,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ if (kind)
+ *kind = svn_node_unknown;
+
+ SVN_ERR(svn_wc__db_read_info(&status, kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ *available = !(status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present);
+
+ 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_WORKING_COPY.
+ */
+static svn_error_t *
+do_open(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ apr_array_header_t *rollback,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *lock;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(open_single(&lock, path, write_lock, db, db_provided,
+ result_pool, iterpool));
+
+ /* Add self to the rollback list in case of error. */
+ APR_ARRAY_PUSH(rollback, svn_wc_adm_access_t *) = lock;
+
+ if (levels_to_lock != 0)
+ {
+ const apr_array_header_t *children;
+ const char *local_abspath = svn_wc__adm_access_abspath(lock);
+ int i;
+
+ /* Reduce levels_to_lock since we are about to recurse */
+ if (levels_to_lock > 0)
+ levels_to_lock--;
+
+ SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath,
+ scratch_pool, iterpool));
+
+ /* Open the tree */
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *node_abspath;
+ svn_node_kind_t kind;
+ svn_boolean_t available;
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ node_abspath = svn_dirent_join(local_abspath, name, iterpool);
+
+ SVN_ERR(adm_available(&available,
+ &kind,
+ db,
+ node_abspath,
+ scratch_pool));
+
+ if (kind != svn_node_dir)
+ continue;
+
+ if (available)
+ {
+ const char *node_path = svn_dirent_join(path, name, iterpool);
+ svn_wc_adm_access_t *node_access;
+
+ SVN_ERR(do_open(&node_access, node_path, db, db_provided,
+ rollback, write_lock, levels_to_lock,
+ cancel_func, cancel_baton,
+ lock->pool, iterpool));
+ /* node_access has been registered in DB, so we don't need
+ to do anything with it. */
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *adm_access = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+open_all(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *rollback;
+ svn_error_t *err;
+
+ rollback = apr_array_make(pool, 10, sizeof(svn_wc_adm_access_t *));
+
+ err = do_open(adm_access, path, db, db_provided, rollback,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool, pool);
+ if (err)
+ {
+ int i;
+
+ for (i = rollback->nelts; i--; )
+ {
+ svn_wc_adm_access_t *lock = APR_ARRAY_IDX(rollback, i,
+ svn_wc_adm_access_t *);
+ SVN_ERR_ASSERT(!IS_MISSING(lock));
+
+ svn_error_clear(close_single(lock, FALSE /* preserve_lock */, pool));
+ }
+ }
+
+ return svn_error_trace(err);
+}
+
+
+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_wc__db_t *db;
+ svn_boolean_t db_provided;
+
+ /* Make sure that ASSOCIATED has a set of access batons, so that we can
+ glom a reference to self into it. */
+ if (associated)
+ {
+ const char *abspath;
+ svn_wc_adm_access_t *lock;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
+ lock = get_from_shared(abspath, associated->db, pool);
+ if (lock && !IS_MISSING(lock))
+ /* 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_dirent_local_style(path, pool));
+ db = associated->db;
+ db_provided = associated->db_provided;
+ }
+ else
+ {
+ /* Any baton creation is going to need a shared structure for holding
+ data across the entire set. The caller isn't providing one, so we
+ do it here. */
+ /* ### we could optimize around levels_to_lock==0, but much of this
+ ### is going to be simplified soon anyways. */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
+ pool, pool));
+ db_provided = FALSE;
+ }
+
+ return svn_error_trace(open_all(adm_access, path, db, db_provided,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool));
+}
+
+
+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;
+
+ if (associated == NULL)
+ {
+ svn_wc__db_t *db;
+
+ /* Ugh. Too bad about having to open a DB. */
+ SVN_ERR(svn_wc__db_open(&db,
+ NULL /* ### config */, FALSE, TRUE, pool, pool));
+ err = probe(db, &dir, path, pool);
+ svn_error_clear(svn_wc__db_close(db));
+ SVN_ERR(err);
+ }
+ else
+ {
+ SVN_ERR(probe(associated->db, &dir, path, 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_WORKING_COPY))
+ {
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_adm_access_t *
+svn_wc__adm_retrieve_internal2(svn_wc__db_t *db,
+ const char *abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *adm_access = get_from_shared(abspath, db, scratch_pool);
+
+ /* If the entry is marked as "missing", then return nothing. */
+ if (IS_MISSING(adm_access))
+ adm_access = NULL;
+
+ return adm_access;
+}
+
+
+/* SVN_DEPRECATED */
+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)
+{
+ const char *local_abspath;
+ svn_node_kind_t kind = svn_node_unknown;
+ svn_node_kind_t wckind;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ if (strcmp(associated->path, path) == 0)
+ *adm_access = associated;
+ else
+ *adm_access = svn_wc__adm_retrieve_internal2(associated->db, local_abspath,
+ pool);
+
+ /* We found what we're looking for, so bail. */
+ if (*adm_access)
+ return SVN_NO_ERROR;
+
+ /* 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. */
+ /* We are going to send a SVN_ERR_WC_NOT_LOCKED, but let's provide
+ a bit more information to our caller */
+
+ err = svn_io_check_path(path, &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_dirent_local_style(path, pool));
+ }
+
+ if (associated)
+ {
+ err = svn_wc__db_read_kind(&kind, svn_wc__adm_get_db(associated),
+ local_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */, pool);
+
+ if (err)
+ {
+ kind = svn_node_unknown;
+ svn_error_clear(err);
+ }
+ }
+
+ if (kind == svn_node_dir && wckind == svn_node_file)
+ {
+ err = svn_error_createf(
+ SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("Expected '%s' to be a directory but found a file"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ if (kind != svn_node_dir && kind != svn_node_unknown)
+ {
+ err = svn_error_createf(
+ SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("Can't retrieve an access baton for non-directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ if (kind == svn_node_unknown || wckind == svn_node_none)
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Directory '%s' is missing"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ /* 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_dirent_local_style(path, pool));
+}
+
+
+/* SVN_DEPRECATED */
+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 char *local_abspath;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(associated != NULL);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__db_read_kind(&kind, associated->db, local_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden*/,
+ pool));
+
+ if (kind == svn_node_dir)
+ dir = path;
+ else if (kind != svn_node_unknown)
+ dir = svn_dirent_dirname(path, pool);
+ else
+ /* Not a versioned item, probe it */
+ SVN_ERR(probe(associated->db, &dir, path, pool));
+
+ 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(associated->db, &dir, path, pool));
+ SVN_ERR(svn_wc_adm_retrieve(adm_access, associated, dir, pool));
+ }
+ else
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SVN_DEPRECATED */
+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_WORKING_COPY))
+ {
+ svn_error_clear(err);
+ *adm_access = NULL;
+ err = NULL;
+ }
+ }
+
+ return err;
+}
+
+
+/* */
+static svn_error_t *
+child_is_disjoint(svn_boolean_t *disjoint,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_switched;
+
+ /* Check if the parent directory knows about this node */
+ SVN_ERR(svn_wc__db_is_switched(disjoint, &is_switched, NULL,
+ db, local_abspath, scratch_pool));
+
+ if (*disjoint)
+ return SVN_NO_ERROR;
+
+ if (is_switched)
+ *disjoint = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+open_anchor(svn_wc_adm_access_t **anchor_access,
+ svn_wc_adm_access_t **target_access,
+ const char **target,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ 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);
+
+ /* Any baton creation is going to need a shared structure for holding
+ data across the entire set. The caller isn't providing one, so we
+ do it here. */
+ /* ### we could maybe skip the shared struct for levels_to_lock==0, but
+ ### given that we need DB for format detection, may as well keep this.
+ ### in any case, much of this is going to be simplified soon anyways. */
+ if (!db_provided)
+ SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ FALSE, TRUE,
+ pool, pool));
+
+ if (svn_path_is_empty(path)
+ || svn_dirent_is_root(path, strlen(path))
+ || ! strcmp(base_name, ".."))
+ {
+ SVN_ERR(open_all(anchor_access, path, db, db_provided,
+ 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);
+ const char *local_abspath;
+ svn_error_t *p_access_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Try to open parent of PATH to setup P_ACCESS */
+ err = open_single(&p_access, parent, write_lock, db, db_provided,
+ pool, pool);
+ if (err)
+ {
+ const char *abspath = svn_dirent_dirname(local_abspath, pool);
+ svn_wc_adm_access_t *existing_adm = svn_wc__db_temp_get_access(db, abspath, pool);
+
+ if (IS_MISSING(existing_adm))
+ svn_wc__db_temp_clear_access(db, abspath, pool);
+ else
+ SVN_ERR_ASSERT(existing_adm == NULL);
+
+ if (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ 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 = open_single(&p_access, parent, FALSE,
+ db, db_provided, pool, 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 = open_all(&t_access, path, db, db_provided, write_lock,
+ levels_to_lock, cancel_func, cancel_baton, pool);
+ if (err)
+ {
+ if (p_access == NULL)
+ {
+ /* Couldn't open the parent or the target. Bail out. */
+ svn_error_clear(p_access_err);
+ return svn_error_trace(err);
+ }
+
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ if (p_access)
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ svn_error_clear(p_access_err);
+ return svn_error_trace(err);
+ }
+
+ /* This directory is not under version control. Ignore it. */
+ 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)
+ {
+ svn_boolean_t disjoint;
+
+ err = child_is_disjoint(&disjoint, db, local_abspath, 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 svn_error_trace(err);
+ }
+
+ if (disjoint)
+ {
+ /* Switched or disjoint, so drop P_ACCESS. Don't close any
+ descendents, or we might blast the child. */
+ err = close_single(p_access, FALSE /* preserve_lock */, pool);
+ if (err)
+ {
+ svn_error_clear(p_access_err);
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ return svn_error_trace(err);
+ }
+ p_access = NULL;
+ }
+ }
+
+ /* We have a parent baton *and* we have an error related to opening
+ the baton. That means we have a readonly baton, but that isn't
+ going to work for us. (p_access would have been set to NULL if
+ a writable parent baton is not required) */
+ if (p_access && p_access_err)
+ {
+ if (t_access)
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ return svn_error_trace(p_access_err);
+ }
+ svn_error_clear(p_access_err);
+
+ if (! t_access)
+ {
+ svn_boolean_t available;
+ svn_node_kind_t kind;
+
+ err = adm_available(&available, &kind, db, local_abspath, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ svn_error_clear(err);
+ else if (err)
+ {
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ return svn_error_trace(err);
+ }
+ }
+
+ *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;
+}
+
+
+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)
+{
+ return svn_error_trace(open_anchor(anchor_access, target_access, target,
+ NULL, FALSE, path, write_lock,
+ levels_to_lock, cancel_func,
+ cancel_baton, pool));
+}
+
+
+/* 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.
+ */
+static svn_error_t *
+do_close(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t preserve_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *look;
+
+ if (adm_access->closed)
+ return SVN_NO_ERROR;
+
+ /* If we are part of the shared set, then close descendant batons. */
+ look = get_from_shared(adm_access->abspath, adm_access->db, scratch_pool);
+ if (look != NULL)
+ {
+ apr_hash_t *opened;
+ apr_hash_index_t *hi;
+
+ /* Gather all the opened access batons from the DB. */
+ opened = svn_wc__db_temp_get_all_access(adm_access->db, scratch_pool);
+
+ /* Close any that are descendents of this baton. */
+ for (hi = apr_hash_first(scratch_pool, opened);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *abspath = svn__apr_hash_index_key(hi);
+ svn_wc_adm_access_t *child = svn__apr_hash_index_val(hi);
+ const char *path = child->path;
+
+ if (IS_MISSING(child))
+ {
+ /* We don't close the missing entry, but get rid of it from
+ the set. */
+ svn_wc__db_temp_clear_access(adm_access->db, abspath,
+ scratch_pool);
+ continue;
+ }
+
+ if (! svn_dirent_is_ancestor(adm_access->path, path)
+ || strcmp(adm_access->path, path) == 0)
+ continue;
+
+ SVN_ERR(close_single(child, preserve_lock, scratch_pool));
+ }
+ }
+
+ return svn_error_trace(close_single(adm_access, preserve_lock,
+ scratch_pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_close2(svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(do_close(adm_access, FALSE, scratch_pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_boolean_t
+svn_wc_adm_locked(const svn_wc_adm_access_t *adm_access)
+{
+ svn_boolean_t locked;
+ apr_pool_t *subpool = svn_pool_create(adm_access->pool);
+ svn_error_t *err = svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
+ adm_access->abspath, TRUE,
+ subpool);
+ svn_pool_destroy(subpool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+ /* ### is this right? */
+ return FALSE;
+ }
+
+ return locked;
+}
+
+svn_error_t *
+svn_wc__write_check(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked;
+
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, db, local_abspath, FALSE,
+ scratch_pool));
+ if (!locked)
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
+ _("No write-lock in '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_locked2(svn_boolean_t *locked_here,
+ svn_boolean_t *locked,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (locked_here != NULL)
+ SVN_ERR(svn_wc__db_wclock_owns_lock(locked_here, wc_ctx->db, local_abspath,
+ FALSE, scratch_pool));
+ if (locked != NULL)
+ SVN_ERR(svn_wc__db_wclocked(locked, wc_ctx->db, local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SVN_DEPRECATED */
+const char *
+svn_wc_adm_access_path(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->path;
+}
+
+
+const char *
+svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->abspath;
+}
+
+
+/* SVN_DEPRECATED */
+apr_pool_t *
+svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->pool;
+}
+
+apr_pool_t *
+svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->pool;
+}
+
+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)
+{
+ /* Compile with -DSVN_DISABLE_ENTRY_CACHE to disable the in-memory
+ entry caching. As of 2010-03-18 (r924708) merge_tests 34 and 134
+ fail during "make check". */
+#ifdef SVN_DISABLE_ENTRY_CACHE
+ return NULL;
+#else
+ return adm_access->entries_all;
+#endif
+}
+
+
+svn_wc__db_t *
+svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->db;
+}
+
+svn_error_t *
+svn_wc__acquire_write_lock(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_boolean_t is_wcroot;
+ svn_boolean_t is_switched;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_wc__db_is_switched(&is_wcroot, &is_switched, &kind,
+ db, local_abspath, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ kind = svn_node_none;
+ is_wcroot = FALSE;
+ is_switched = FALSE;
+ }
+
+ if (!lock_root_abspath && kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
+ _("Can't obtain lock on non-directory '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (lock_anchor && kind == svn_node_dir)
+ {
+ if (is_wcroot)
+ lock_anchor = FALSE;
+ }
+
+ if (lock_anchor)
+ {
+ const char *parent_abspath;
+ SVN_ERR_ASSERT(lock_root_abspath != NULL);
+
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ if (! is_switched)
+ local_abspath = parent_abspath;
+ }
+ else if (kind != svn_node_none && kind != svn_node_unknown)
+ {
+ /* In the single-DB world we know parent exists */
+ local_abspath = parent_abspath;
+ }
+ else
+ {
+ /* Can't lock parents that don't exist */
+ svn_node_kind_t parent_kind;
+ err = svn_wc__db_read_kind(&parent_kind, db, parent_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool);
+ if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+ {
+ svn_error_clear(err);
+ parent_kind = svn_node_unknown;
+ }
+ else
+ SVN_ERR(err);
+
+ if (parent_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ local_abspath = parent_abspath;
+ }
+ }
+ else if (kind != svn_node_dir)
+ {
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ }
+
+ if (lock_root_abspath)
+ *lock_root_abspath = apr_pstrdup(result_pool, local_abspath);
+
+ SVN_ERR(svn_wc__db_wclock_obtain(wc_ctx->db, local_abspath,
+ -1 /* levels_to_lock (infinite) */,
+ FALSE /* steal_lock */,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__release_write_lock(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, wc_ctx->db, local_abspath,
+ 0, scratch_pool, scratch_pool));
+ if (work_item)
+ {
+ /* Do not release locks (here or below) if there is work to do. */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__db_wclock_release(wc_ctx->db, local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__call_with_write_lock(svn_wc__with_write_lock_func_t func,
+ void *baton,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err1, *err2;
+ const char *lock_root_abspath;
+
+ SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, wc_ctx, local_abspath,
+ lock_anchor, scratch_pool, scratch_pool));
+ err1 = svn_error_trace(func(baton, result_pool, scratch_pool));
+ err2 = svn_wc__release_write_lock(wc_ctx, lock_root_abspath, scratch_pool);
+ return svn_error_compose_create(err1, err2);
+}
+
+
+svn_error_t *
+svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked = FALSE;
+ const char *obtained_abspath;
+ const char *requested_abspath = local_abspath;
+
+ while (!locked)
+ {
+ const char *required_abspath;
+ const char *child;
+
+ SVN_ERR(svn_wc__acquire_write_lock(&obtained_abspath, wc_ctx,
+ requested_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ locked = TRUE;
+
+ SVN_ERR(svn_wc__required_lock_for_resolve(&required_abspath,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* It's possible for the required lock path to be an ancestor
+ of, a descendent of, or equal to, the obtained lock path. If
+ it's an ancestor we have to try again, otherwise the obtained
+ lock will do. */
+ child = svn_dirent_skip_ancestor(required_abspath, obtained_abspath);
+ if (child && child[0])
+ {
+ SVN_ERR(svn_wc__release_write_lock(wc_ctx, obtained_abspath,
+ scratch_pool));
+ locked = FALSE;
+ requested_abspath = required_abspath;
+ }
+ else
+ {
+ /* required should be a descendent of, or equal to, obtained */
+ SVN_ERR_ASSERT(!strcmp(required_abspath, obtained_abspath)
+ || svn_dirent_skip_ancestor(obtained_abspath,
+ required_abspath));
+ }
+ }
+
+ *lock_root_abspath = apr_pstrdup(result_pool, obtained_abspath);
+
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud