summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_repos/repos.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/repos.c')
-rw-r--r--subversion/libsvn_repos/repos.c2132
1 files changed, 2132 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
new file mode 100644
index 0000000..9f10c06
--- /dev/null
+++ b/subversion/libsvn_repos/repos.c
@@ -0,0 +1,2132 @@
+/* repos.c : repository creation; shared and exclusive repository locking
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_time.h"
+#include "svn_fs.h"
+#include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
+#include "svn_repos.h"
+#include "svn_hash.h"
+#include "svn_version.h"
+#include "svn_config.h"
+
+#include "private/svn_repos_private.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
+
+#include "repos.h"
+
+/* Used to terminate lines in large multi-line string literals. */
+#define NL APR_EOL_STR
+
+
+/* Path accessor functions. */
+
+
+const char *
+svn_repos_path(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->path);
+}
+
+
+const char *
+svn_repos_db_env(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->db_path);
+}
+
+
+const char *
+svn_repos_conf_dir(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->conf_path);
+}
+
+
+const char *
+svn_repos_svnserve_conf(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->conf_path, SVN_REPOS__CONF_SVNSERVE_CONF, pool);
+}
+
+
+const char *
+svn_repos_lock_dir(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->lock_path);
+}
+
+
+const char *
+svn_repos_db_lockfile(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->lock_path, SVN_REPOS__DB_LOCKFILE, pool);
+}
+
+
+const char *
+svn_repos_db_logs_lockfile(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->lock_path, SVN_REPOS__DB_LOGS_LOCKFILE, pool);
+}
+
+const char *
+svn_repos_hook_dir(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->hook_path);
+}
+
+
+const char *
+svn_repos_start_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_START_COMMIT, pool);
+}
+
+
+const char *
+svn_repos_pre_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_COMMIT, pool);
+}
+
+
+const char *
+svn_repos_pre_lock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_LOCK, pool);
+}
+
+
+const char *
+svn_repos_pre_unlock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_UNLOCK, pool);
+}
+
+const char *
+svn_repos_post_lock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_LOCK, pool);
+}
+
+
+const char *
+svn_repos_post_unlock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_UNLOCK, pool);
+}
+
+
+const char *
+svn_repos_post_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_COMMIT, pool);
+}
+
+
+const char *
+svn_repos_pre_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE,
+ pool);
+}
+
+
+const char *
+svn_repos_post_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_REVPROP_CHANGE,
+ pool);
+}
+
+static svn_error_t *
+create_repos_dir(const char *path, apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
+ if (err && (APR_STATUS_IS_EEXIST(err->apr_err)))
+ {
+ svn_boolean_t is_empty;
+
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_dir_empty(&is_empty, path, pool));
+
+ if (is_empty)
+ err = NULL;
+ else
+ err = svn_error_createf(SVN_ERR_DIR_NOT_EMPTY, 0,
+ _("'%s' exists and is non-empty"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ return svn_error_trace(err);
+}
+
+static const char * bdb_lock_file_contents =
+ "DB lock file, representing locks on the versioned filesystem." NL
+ "" NL
+ "All accessors -- both readers and writers -- of the repository's" NL
+ "Berkeley DB environment take out shared locks on this file, and" NL
+ "each accessor removes its lock when done. If and when the DB" NL
+ "recovery procedure is run, the recovery code takes out an" NL
+ "exclusive lock on this file, so we can be sure no one else is" NL
+ "using the DB during the recovery." NL
+ "" NL
+ "You should never have to edit or remove this file." NL;
+
+static const char * bdb_logs_lock_file_contents =
+ "DB logs lock file, representing locks on the versioned filesystem logs." NL
+ "" NL
+ "All log manipulators of the repository's Berkeley DB environment" NL
+ "take out exclusive locks on this file to ensure that only one" NL
+ "accessor manipulates the logs at a time." NL
+ "" NL
+ "You should never have to edit or remove this file." NL;
+
+static const char * pre12_compat_unneeded_file_contents =
+ "This file is not used by Subversion 1.3.x or later." NL
+ "However, its existence is required for compatibility with" NL
+ "Subversion 1.2.x or earlier." NL;
+
+/* Create the DB logs lockfile. */
+static svn_error_t *
+create_db_logs_lock(svn_repos_t *repos, apr_pool_t *pool) {
+ const char *contents;
+ const char *lockfile_path;
+
+ lockfile_path = svn_repos_db_logs_lockfile(repos, pool);
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ contents = bdb_logs_lock_file_contents;
+ else
+ contents = pre12_compat_unneeded_file_contents;
+
+ SVN_ERR_W(svn_io_file_create(lockfile_path, contents, pool),
+ _("Creating db logs lock file"));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create the DB lockfile. */
+static svn_error_t *
+create_db_lock(svn_repos_t *repos, apr_pool_t *pool) {
+ const char *contents;
+ const char *lockfile_path;
+
+ lockfile_path = svn_repos_db_lockfile(repos, pool);
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ contents = bdb_lock_file_contents;
+ else
+ contents = pre12_compat_unneeded_file_contents;
+
+ SVN_ERR_W(svn_io_file_create(lockfile_path, contents, pool),
+ _("Creating db lock file"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_locks(svn_repos_t *repos, apr_pool_t *pool)
+{
+ /* Create the locks directory. */
+ SVN_ERR_W(create_repos_dir(repos->lock_path, pool),
+ _("Creating lock dir"));
+
+ SVN_ERR(create_db_lock(repos, pool));
+ return create_db_logs_lock(repos, pool);
+}
+
+
+#define HOOKS_ENVIRONMENT_TEXT \
+ "# The hook program typically does not inherit the environment of" NL \
+ "# its parent process. For example, a common problem is for the" NL \
+ "# PATH environment variable to not be set to its usual value, so" NL \
+ "# that subprograms fail to launch unless invoked via absolute path." NL \
+ "# If you're having unexpected problems with a hook program, the" NL \
+ "# culprit may be unusual (or missing) environment variables." NL
+
+#define PREWRITTEN_HOOKS_TEXT \
+ "# For more examples and pre-written hooks, see those in" NL \
+ "# the Subversion repository at" NL \
+ "# http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ and" NL \
+ "# http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/" NL
+
+
+static svn_error_t *
+create_hooks(svn_repos_t *repos, apr_pool_t *pool)
+{
+ const char *this_path, *contents;
+
+ /* Create the hook directory. */
+ SVN_ERR_W(create_repos_dir(repos->hook_path, pool),
+ _("Creating hook directory"));
+
+ /*** Write a default template for each standard hook file. */
+
+ /* Start-commit hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_start_commit_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_START_COMMIT
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# START-COMMIT HOOK" NL
+"#" NL
+"# The start-commit hook is invoked immediately after a Subversion txn is" NL
+"# created and populated with initial revprops in the process of doing a" NL
+"# commit. Subversion runs this hook by invoking a program (script, " NL
+"# executable, binary, etc.) named '"SCRIPT_NAME"' (for which this file" NL
+"# is a template) with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] USER (the authenticated user attempting to commit)" NL
+"# [3] CAPABILITIES (a colon-separated list of capabilities reported" NL
+"# by the client; see note below)" NL
+"# [4] TXN-NAME (the name of the commit txn just created)" NL
+"#" NL
+"# Note: The CAPABILITIES parameter is new in Subversion 1.5, and 1.5" NL
+"# clients will typically report at least the \"" \
+ SVN_RA_CAPABILITY_MERGEINFO "\" capability." NL
+"# If there are other capabilities, then the list is colon-separated," NL
+"# e.g.: \"" SVN_RA_CAPABILITY_MERGEINFO ":some-other-capability\" " \
+ "(the order is undefined)." NL
+"#" NL
+"# Note: The TXN-NAME parameter is new in Subversion 1.8. Prior to version" NL
+"# 1.8, the start-commit hook was invoked before the commit txn was even" NL
+"# created, so the ability to inspect the commit txn and its metadata from" NL
+"# within the start-commit hook was not possible." NL
+"# " NL
+"# The list is self-reported by the client. Therefore, you should not" NL
+"# make security assumptions based on the capabilities list, nor should" NL
+"# you assume that clients reliably report every capability they have." NL
+"#" NL
+"# The working directory for this hook program's invocation is undefined," NL
+"# so the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the commit continues; but" NL
+"# if it exits with failure (non-zero), the commit is stopped before" NL
+"# a Subversion txn is created, and STDERR is returned to the client." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"USER=\"$2\"" NL
+"" NL
+"commit-allower.pl --repository \"$REPOS\" --user \"$USER\" || exit 1" NL
+"special-auth-check.py --user \"$USER\" --auth-level 3 || exit 1" NL
+"" NL
+"# All checks passed, so allow the commit." NL
+"exit 0" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating start-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end start-commit hook */
+
+ /* Pre-commit hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_commit_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_COMMIT
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-COMMIT HOOK" NL
+"#" NL
+"# The pre-commit hook is invoked before a Subversion txn is" NL
+"# committed. Subversion runs this hook by invoking a program" NL
+"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which" NL
+"# this file is a template), with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] TXN-NAME (the name of the txn about to be committed)" NL
+"#" NL
+"# [STDIN] LOCK-TOKENS ** the lock tokens are passed via STDIN." NL
+"#" NL
+"# If STDIN contains the line \"LOCK-TOKENS:\\n\" (the \"\\n\" denotes a" NL
+"# single newline), the lines following it are the lock tokens for" NL
+"# this commit. The end of the list is marked by a line containing" NL
+"# only a newline character." NL
+"#" NL
+"# Each lock token line consists of a URI-escaped path, followed" NL
+"# by the separator character '|', followed by the lock token string," NL
+"# followed by a newline." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the txn is committed; but" NL
+"# if it exits with failure (non-zero), the txn is aborted, no commit" NL
+"# takes place, and STDERR is returned to the client. The hook" NL
+"# program can use the 'svnlook' utility to help it examine the txn." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# *** NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT ***" NL
+"# *** FOR REVISION PROPERTIES (like svn:log or svn:author). ***" NL
+"#" NL
+"# This is why we recommend using the read-only 'svnlook' utility." NL
+"# In the future, Subversion may enforce the rule that pre-commit" NL
+"# hooks should not modify the versioned data in txns, or else come" NL
+"# up with a mechanism to make it safe to do so (by informing the" NL
+"# committing client of the changes). However, right now neither" NL
+"# mechanism is implemented, so hook writers just have to be careful." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"TXN=\"$2\"" NL
+"" NL
+"# Make sure that the log message contains some text." NL
+"SVNLOOK=" SVN_BINDIR "/svnlook" NL
+"$SVNLOOK log -t \"$TXN\" \"$REPOS\" | \\" NL
+" grep \"[a-zA-Z0-9]\" > /dev/null || exit 1" NL
+"" NL
+"# Check that the author of this commit has the rights to perform" NL
+"# the commit on the files and directories being modified." NL
+"commit-access-control.pl \"$REPOS\" \"$TXN\" commit-access-control.cfg || exit 1"
+ NL
+"" NL
+"# All checks passed, so allow the commit." NL
+"exit 0" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating pre-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-commit hook */
+
+
+ /* Pre-revprop-change hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_revprop_change_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_REVPROP_CHANGE
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-REVPROP-CHANGE HOOK" NL
+"#" NL
+"# The pre-revprop-change hook is invoked before a revision property" NL
+"# is added, modified or deleted. Subversion runs this hook by invoking" NL
+"# a program (script, executable, binary, etc.) named '"SCRIPT_NAME"'" NL
+"# (for which this file is a template), with the following ordered" NL
+"# arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] REV (the revision being tweaked)" NL
+"# [3] USER (the username of the person tweaking the property)" NL
+"# [4] PROPNAME (the property being set on the revision)" NL
+"# [5] ACTION (the property is being 'A'dded, 'M'odified, or 'D'eleted)"
+ NL
+"#" NL
+"# [STDIN] PROPVAL ** the new property value is passed via STDIN." NL
+"#" NL
+"# If the hook program exits with success, the propchange happens; but" NL
+"# if it exits with failure (non-zero), the propchange doesn't happen." NL
+"# The hook program can use the 'svnlook' utility to examine the " NL
+"# existing value of the revision property." NL
+"#" NL
+"# WARNING: unlike other hooks, this hook MUST exist for revision" NL
+"# properties to be changed. If the hook does not exist, Subversion " NL
+"# will behave as if the hook were present, but failed. The reason" NL
+"# for this is that revision properties are UNVERSIONED, meaning that" NL
+"# a successful propchange is destructive; the old value is gone" NL
+"# forever. We recommend the hook back up the old value somewhere." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"REV=\"$2\"" NL
+"USER=\"$3\"" NL
+"PROPNAME=\"$4\"" NL
+"ACTION=\"$5\"" NL
+"" NL
+"if [ \"$ACTION\" = \"M\" -a \"$PROPNAME\" = \"svn:log\" ]; then exit 0; fi" NL
+"" NL
+"echo \"Changing revision properties other than svn:log is prohibited\" >&2" NL
+"exit 1" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating pre-revprop-change hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-revprop-change hook */
+
+
+ /* Pre-lock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_lock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_LOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-LOCK HOOK" NL
+"#" NL
+"# The pre-lock hook is invoked before an exclusive lock is" NL
+"# created. Subversion runs this hook by invoking a program " NL
+"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which" NL
+"# this file is a template), with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] PATH (the path in the repository about to be locked)" NL
+"# [3] USER (the user creating the lock)" NL
+"# [4] COMMENT (the comment of the lock)" NL
+"# [5] STEAL-LOCK (1 if the user is trying to steal the lock, else 0)" NL
+"#" NL
+"# If the hook program outputs anything on stdout, the output string will" NL
+"# be used as the lock token for this lock operation. If you choose to use" NL
+"# this feature, you must guarantee the tokens generated are unique across" NL
+"# the repository each time." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the lock is created; but" NL
+"# if it exits with failure (non-zero), the lock action is aborted" NL
+"# and STDERR is returned to the client." NL
+"" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"PATH=\"$2\"" NL
+"USER=\"$3\"" NL
+"COMMENT=\"$4\"" NL
+"STEAL=\"$5\"" NL
+"" NL
+"# If a lock exists and is owned by a different person, don't allow it" NL
+"# to be stolen (e.g., with 'svn lock --force ...')." NL
+"" NL
+"# (Maybe this script could send email to the lock owner?)" NL
+"SVNLOOK=" SVN_BINDIR "/svnlook" NL
+"GREP=/bin/grep" NL
+"SED=/bin/sed" NL
+"" NL
+"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\" NL
+" $GREP '^Owner: ' | $SED 's/Owner: //'`" NL
+"" NL
+"# If we get no result from svnlook, there's no lock, allow the lock to" NL
+"# happen:" NL
+"if [ \"$LOCK_OWNER\" = \"\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# If the person locking matches the lock's owner, allow the lock to" NL
+"# happen:" NL
+"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# Otherwise, we've got an owner mismatch, so return failure:" NL
+"echo \"Error: $PATH already locked by ${LOCK_OWNER}.\" 1>&2" NL
+"exit 1" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating pre-lock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-lock hook */
+
+
+ /* Pre-unlock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_unlock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_UNLOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-UNLOCK HOOK" NL
+"#" NL
+"# The pre-unlock hook is invoked before an exclusive lock is" NL
+"# destroyed. Subversion runs this hook by invoking a program " NL
+"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which" NL
+"# this file is a template), with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] PATH (the path in the repository about to be unlocked)" NL
+"# [3] USER (the user destroying the lock)" NL
+"# [4] TOKEN (the lock token to be destroyed)" NL
+"# [5] BREAK-UNLOCK (1 if the user is breaking the lock, else 0)" NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the lock is destroyed; but" NL
+"# if it exits with failure (non-zero), the unlock action is aborted" NL
+"# and STDERR is returned to the client." NL
+"" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"PATH=\"$2\"" NL
+"USER=\"$3\"" NL
+"TOKEN=\"$4\"" NL
+"BREAK=\"$5\"" NL
+"" NL
+"# If a lock is owned by a different person, don't allow it be broken." NL
+"# (Maybe this script could send email to the lock owner?)" NL
+"" NL
+"SVNLOOK=" SVN_BINDIR "/svnlook" NL
+"GREP=/bin/grep" NL
+"SED=/bin/sed" NL
+"" NL
+"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\" NL
+" $GREP '^Owner: ' | $SED 's/Owner: //'`" NL
+"" NL
+"# If we get no result from svnlook, there's no lock, return success:" NL
+"if [ \"$LOCK_OWNER\" = \"\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# If the person unlocking matches the lock's owner, return success:" NL
+"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# Otherwise, we've got an owner mismatch, so return failure:" NL
+"echo \"Error: $PATH locked by ${LOCK_OWNER}.\" 1>&2" NL
+"exit 1" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating pre-unlock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-unlock hook */
+
+
+
+ /* Post-commit hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_commit_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_COMMIT
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-COMMIT HOOK" NL
+"#" NL
+"# The post-commit hook is invoked after a commit. Subversion runs" NL
+"# this hook by invoking a program (script, executable, binary, etc.)" NL
+"# named '"SCRIPT_NAME"' (for which this file is a template) with the " NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] REV (the number of the revision just committed)" NL
+"# [3] TXN-NAME (the name of the transaction that has become REV)" NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# Because the commit has already completed and cannot be undone," NL
+"# the exit code of the hook program is ignored. The hook program" NL
+"# can use the 'svnlook' utility to help it examine the" NL
+"# newly-committed tree." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"REV=\"$2\"" NL
+"TXN_NAME=\"$3\"" NL
+ NL
+"mailer.py commit \"$REPOS\" \"$REV\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating post-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-commit hook */
+
+
+ /* Post-lock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_lock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_LOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-LOCK HOOK" NL
+"#" NL
+"# The post-lock hook is run after a path is locked. Subversion runs" NL
+"# this hook by invoking a program (script, executable, binary, etc.)" NL
+"# named '"SCRIPT_NAME"' (for which this file is a template) with the " NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] USER (the user who created the lock)" NL
+"#" NL
+"# The paths that were just locked are passed to the hook via STDIN (as" NL
+"# of Subversion 1.2, only one path is passed per invocation, but the" NL
+"# plan is to pass all locked paths at once, so the hook program" NL
+"# should be written accordingly)." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# Because the lock has already been created and cannot be undone," NL
+"# the exit code of the hook program is ignored. The hook program" NL
+"# can use the 'svnlook' utility to help it examine the" NL
+"# newly-created lock." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"USER=\"$2\"" NL
+"" NL
+"# Send email to interested parties, let them know a lock was created:" NL
+"mailer.py lock \"$REPOS\" \"$USER\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating post-lock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-lock hook */
+
+
+ /* Post-unlock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_unlock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_UNLOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-UNLOCK HOOK" NL
+"#" NL
+"# The post-unlock hook runs after a path is unlocked. Subversion runs" NL
+"# this hook by invoking a program (script, executable, binary, etc.)" NL
+"# named '"SCRIPT_NAME"' (for which this file is a template) with the " NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] USER (the user who destroyed the lock)" NL
+"#" NL
+"# The paths that were just unlocked are passed to the hook via STDIN" NL
+"# (as of Subversion 1.2, only one path is passed per invocation, but" NL
+"# the plan is to pass all unlocked paths at once, so the hook program" NL
+"# should be written accordingly)." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# Because the lock has already been destroyed and cannot be undone," NL
+"# the exit code of the hook program is ignored." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"USER=\"$2\"" NL
+"" NL
+"# Send email to interested parties, let them know a lock was removed:" NL
+"mailer.py unlock \"$REPOS\" \"$USER\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating post-unlock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-unlock hook */
+
+
+ /* Post-revprop-change hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_revprop_change_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_REVPROP_CHANGE
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-REVPROP-CHANGE HOOK" NL
+"#" NL
+"# The post-revprop-change hook is invoked after a revision property" NL
+"# has been added, modified or deleted. Subversion runs this hook by" NL
+"# invoking a program (script, executable, binary, etc.) named" NL
+"# '"SCRIPT_NAME"' (for which this file is a template), with the" NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] REV (the revision that was tweaked)" NL
+"# [3] USER (the username of the person tweaking the property)" NL
+"# [4] PROPNAME (the property that was changed)" NL
+"# [5] ACTION (the property was 'A'dded, 'M'odified, or 'D'eleted)" NL
+"#" NL
+"# [STDIN] PROPVAL ** the old property value is passed via STDIN." NL
+"#" NL
+"# Because the propchange has already completed and cannot be undone," NL
+"# the exit code of the hook program is ignored. The hook program" NL
+"# can use the 'svnlook' utility to help it examine the" NL
+"# new property value." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"REV=\"$2\"" NL
+"USER=\"$3\"" NL
+"PROPNAME=\"$4\"" NL
+"ACTION=\"$5\"" NL
+"" NL
+"mailer.py propchange2 \"$REPOS\" \"$REV\" \"$USER\" \"$PROPNAME\" "
+"\"$ACTION\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating post-revprop-change hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-revprop-change hook */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_conf(svn_repos_t *repos, apr_pool_t *pool)
+{
+ SVN_ERR_W(create_repos_dir(repos->conf_path, pool),
+ _("Creating conf directory"));
+
+ /* Write a default template for svnserve.conf. */
+ {
+ static const char * const svnserve_conf_contents =
+"### This file controls the configuration of the svnserve daemon, if you" NL
+"### use it to allow access to this repository. (If you only allow" NL
+"### access through http: and/or file: URLs, then this file is" NL
+"### irrelevant.)" NL
+"" NL
+"### Visit http://subversion.apache.org/ for more information." NL
+"" NL
+"[general]" NL
+"### The anon-access and auth-access options control access to the" NL
+"### repository for unauthenticated (a.k.a. anonymous) users and" NL
+"### authenticated users, respectively." NL
+"### Valid values are \"write\", \"read\", and \"none\"." NL
+"### Setting the value to \"none\" prohibits both reading and writing;" NL
+"### \"read\" allows read-only access, and \"write\" allows complete " NL
+"### read/write access to the repository." NL
+"### The sample settings below are the defaults and specify that anonymous" NL
+"### users have read-only access to the repository, while authenticated" NL
+"### users have read and write access to the repository." NL
+"# anon-access = read" NL
+"# auth-access = write" NL
+"### The password-db option controls the location of the password" NL
+"### database file. Unless you specify a path starting with a /," NL
+"### the file's location is relative to the directory containing" NL
+"### this configuration file." NL
+"### If SASL is enabled (see below), this file will NOT be used." NL
+"### Uncomment the line below to use the default password file." NL
+"# password-db = passwd" NL
+"### The authz-db option controls the location of the authorization" NL
+"### rules for path-based access control. Unless you specify a path" NL
+"### starting with a /, the file's location is relative to the" NL
+"### directory containing this file. The specified path may be a" NL
+"### repository relative URL (^/) or an absolute file:// URL to a text" NL
+"### file in a Subversion repository. If you don't specify an authz-db," NL
+"### no path-based access control is done." NL
+"### Uncomment the line below to use the default authorization file." NL
+"# authz-db = " SVN_REPOS__CONF_AUTHZ NL
+"### The groups-db option controls the location of the groups file." NL
+"### Unless you specify a path starting with a /, the file's location is" NL
+"### relative to the directory containing this file. The specified path" NL
+"### may be a repository relative URL (^/) or an absolute file:// URL to a" NL
+"### text file in a Subversion repository." NL
+"# groups-db = " SVN_REPOS__CONF_GROUPS NL
+"### This option specifies the authentication realm of the repository." NL
+"### If two repositories have the same authentication realm, they should" NL
+"### have the same password database, and vice versa. The default realm" NL
+"### is repository's uuid." NL
+"# realm = My First Repository" NL
+"### The force-username-case option causes svnserve to case-normalize" NL
+"### usernames before comparing them against the authorization rules in the" NL
+"### authz-db file configured above. Valid values are \"upper\" (to upper-" NL
+"### case the usernames), \"lower\" (to lowercase the usernames), and" NL
+"### \"none\" (to compare usernames as-is without case conversion, which" NL
+"### is the default behavior)." NL
+"# force-username-case = none" NL
+"### The hooks-env options specifies a path to the hook script environment " NL
+"### configuration file. This option overrides the per-repository default" NL
+"### and can be used to configure the hook script environment for multiple " NL
+"### repositories in a single file, if an absolute path is specified." NL
+"### Unless you specify an absolute path, the file's location is relative" NL
+"### to the directory containing this file." NL
+"# hooks-env = " SVN_REPOS__CONF_HOOKS_ENV NL
+"" NL
+"[sasl]" NL
+"### This option specifies whether you want to use the Cyrus SASL" NL
+"### library for authentication. Default is false." NL
+"### This section will be ignored if svnserve is not built with Cyrus" NL
+"### SASL support; to check, run 'svnserve --version' and look for a line" NL
+"### reading 'Cyrus SASL authentication is available.'" NL
+"# use-sasl = true" NL
+"### These options specify the desired strength of the security layer" NL
+"### that you want SASL to provide. 0 means no encryption, 1 means" NL
+"### integrity-checking only, values larger than 1 are correlated" NL
+"### to the effective key length for encryption (e.g. 128 means 128-bit" NL
+"### encryption). The values below are the defaults." NL
+"# min-encryption = 0" NL
+"# max-encryption = 256" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_repos_svnserve_conf(repos, pool),
+ svnserve_conf_contents, pool),
+ _("Creating svnserve.conf file"));
+ }
+
+ {
+ static const char * const passwd_contents =
+"### This file is an example password file for svnserve." NL
+"### Its format is similar to that of svnserve.conf. As shown in the" NL
+"### example below it contains one section labelled [users]." NL
+"### The name and password for each user follow, one account per line." NL
+"" NL
+"[users]" NL
+"# harry = harryssecret" NL
+"# sally = sallyssecret" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_PASSWD,
+ pool),
+ passwd_contents, pool),
+ _("Creating passwd file"));
+ }
+
+ {
+ static const char * const authz_contents =
+"### This file is an example authorization file for svnserve." NL
+"### Its format is identical to that of mod_authz_svn authorization" NL
+"### files." NL
+"### As shown below each section defines authorizations for the path and" NL
+"### (optional) repository specified by the section name." NL
+"### The authorizations follow. An authorization line can refer to:" NL
+"### - a single user," NL
+"### - a group of users defined in a special [groups] section," NL
+"### - an alias defined in a special [aliases] section," NL
+"### - all authenticated users, using the '$authenticated' token," NL
+"### - only anonymous users, using the '$anonymous' token," NL
+"### - anyone, using the '*' wildcard." NL
+"###" NL
+"### A match can be inverted by prefixing the rule with '~'. Rules can" NL
+"### grant read ('r') access, read-write ('rw') access, or no access" NL
+"### ('')." NL
+"" NL
+"[aliases]" NL
+"# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average" NL
+"" NL
+"[groups]" NL
+"# harry_and_sally = harry,sally" NL
+"# harry_sally_and_joe = harry,sally,&joe" NL
+"" NL
+"# [/foo/bar]" NL
+"# harry = rw" NL
+"# &joe = r" NL
+"# * =" NL
+"" NL
+"# [repository:/baz/fuz]" NL
+"# @harry_and_sally = rw" NL
+"# * = r" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_AUTHZ,
+ pool),
+ authz_contents, pool),
+ _("Creating authz file"));
+ }
+
+ {
+ static const char * const hooks_env_contents =
+"### This file is an example hook script environment configuration file." NL
+"### Hook scripts run in an empty environment by default." NL
+"### As shown below each section defines environment variables for a" NL
+"### particular hook script. The [default] section defines environment" NL
+"### variables for all hook scripts, unless overridden by a hook-specific" NL
+"### section." NL
+"" NL
+"### This example configures a UTF-8 locale for all hook scripts, so that " NL
+"### special characters, such as umlauts, may be printed to stderr." NL
+"### If UTF-8 is used with a mod_dav_svn server, the SVNUseUTF8 option must" NL
+"### also be set to 'yes' in httpd.conf." NL
+"### With svnserve, the LANG environment variable of the svnserve process" NL
+"### must be set to the same value as given here." NL
+"[default]" NL
+"LANG = en_US.UTF-8" NL
+"" NL
+"### This sets the PATH environment variable for the pre-commit hook." NL
+"[pre-commit]" NL
+"PATH = /usr/local/bin:/usr/bin:/usr/sbin" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV \
+ SVN_REPOS__HOOK_DESC_EXT,
+ pool),
+ hooks_env_contents, pool),
+ _("Creating hooks-env file"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_hooks_setenv(svn_repos_t *repos,
+ const char *hooks_env_path,
+ apr_pool_t *scratch_pool)
+{
+ if (hooks_env_path == NULL)
+ repos->hooks_env_path = svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV,
+ repos->pool);
+ else if (!svn_dirent_is_absolute(hooks_env_path))
+ repos->hooks_env_path = svn_dirent_join(repos->conf_path,
+ hooks_env_path,
+ repos->pool);
+ else
+ repos->hooks_env_path = apr_pstrdup(repos->pool, hooks_env_path);
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate and return a new svn_repos_t * object, initializing the
+ directory pathname members based on PATH, and initializing the
+ REPOSITORY_CAPABILITIES member.
+ The members FS, FORMAT, and FS_TYPE are *not* initialized (they are null),
+ and it is the caller's responsibility to fill them in if needed. */
+static svn_repos_t *
+create_svn_repos_t(const char *path, apr_pool_t *pool)
+{
+ svn_repos_t *repos = apr_pcalloc(pool, sizeof(*repos));
+
+ repos->path = apr_pstrdup(pool, path);
+ repos->db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
+ repos->conf_path = svn_dirent_join(path, SVN_REPOS__CONF_DIR, pool);
+ repos->hook_path = svn_dirent_join(path, SVN_REPOS__HOOK_DIR, pool);
+ repos->lock_path = svn_dirent_join(path, SVN_REPOS__LOCK_DIR, pool);
+ repos->hooks_env_path = NULL;
+ repos->repository_capabilities = apr_hash_make(pool);
+ repos->pool = pool;
+
+ return repos;
+}
+
+
+static svn_error_t *
+create_repos_structure(svn_repos_t *repos,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ /* Create the top-level repository directory. */
+ SVN_ERR_W(create_repos_dir(path, pool),
+ _("Could not create top-level directory"));
+
+ /* Create the DAV sandbox directory if pre-1.4 or pre-1.5-compatible. */
+ if (fs_config
+ && (svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)
+ || svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)))
+ {
+ const char *dav_path = svn_dirent_join(repos->path,
+ SVN_REPOS__DAV_DIR, pool);
+ SVN_ERR_W(create_repos_dir(dav_path, pool),
+ _("Creating DAV sandbox dir"));
+ }
+
+ /* Create the lock directory. */
+ SVN_ERR(create_locks(repos, pool));
+
+ /* Create the hooks directory. */
+ SVN_ERR(create_hooks(repos, pool));
+
+ /* Create the conf directory. */
+ SVN_ERR(create_conf(repos, pool));
+
+ /* Write the top-level README file. */
+ {
+ const char * const readme_header =
+ "This is a Subversion repository; use the 'svnadmin' and 'svnlook' " NL
+ "tools to examine it. Do not add, delete, or modify files here " NL
+ "unless you know how to avoid corrupting the repository." NL
+ "" NL;
+ const char * const readme_bdb_insert =
+ "The directory \"" SVN_REPOS__DB_DIR "\" contains a Berkeley DB environment." NL
+ "you may need to tweak the values in \"" SVN_REPOS__DB_DIR "/DB_CONFIG\" to match the" NL
+ "requirements of your site." NL
+ "" NL;
+ const char * const readme_footer =
+ "Visit http://subversion.apache.org/ for more information." NL;
+ apr_file_t *f;
+ apr_size_t written;
+
+ SVN_ERR(svn_io_file_open(&f,
+ svn_dirent_join(path, SVN_REPOS__README, pool),
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT, pool));
+
+ SVN_ERR(svn_io_file_write_full(f, readme_header, strlen(readme_header),
+ &written, pool));
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ SVN_ERR(svn_io_file_write_full(f, readme_bdb_insert,
+ strlen(readme_bdb_insert),
+ &written, pool));
+ SVN_ERR(svn_io_file_write_full(f, readme_footer, strlen(readme_footer),
+ &written, pool));
+
+ return svn_io_file_close(f, pool);
+ }
+}
+
+
+/* There is, at present, nothing within the direct responsibility
+ of libsvn_repos which requires locking. For historical compatibility
+ reasons, the BDB libsvn_fs backend does not do its own locking, expecting
+ libsvn_repos to do the locking for it. Here we take care of that
+ backend-specific requirement.
+ The kind of lock is controlled by EXCLUSIVE and NONBLOCKING.
+ The lock is scoped to POOL. */
+static svn_error_t *
+lock_repos(svn_repos_t *repos,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool)
+{
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ {
+ svn_error_t *err;
+ const char *lockfile_path = svn_repos_db_lockfile(repos, pool);
+
+ err = svn_io_file_lock2(lockfile_path, exclusive, nonblocking, pool);
+ if (err != NULL && APR_STATUS_IS_EAGAIN(err->apr_err))
+ return svn_error_trace(err);
+ SVN_ERR_W(err, _("Error opening db lockfile"));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_create(svn_repos_t **repos_p,
+ const char *path,
+ const char *unused_1,
+ const char *unused_2,
+ apr_hash_t *config,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ svn_error_t *err;
+ const char *root_path;
+ const char *local_abspath;
+
+ /* Allocate a repository object, filling in the format we will create. */
+ repos = create_svn_repos_t(path, pool);
+ repos->format = SVN_REPOS__FORMAT_NUMBER;
+
+ /* Discover the type of the filesystem we are about to create. */
+ repos->fs_type = svn_hash__get_cstring(fs_config, SVN_FS_CONFIG_FS_TYPE,
+ DEFAULT_FS_TYPE);
+ if (svn_hash__get_bool(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, FALSE))
+ repos->format = SVN_REPOS__FORMAT_NUMBER_LEGACY;
+
+ /* Don't create a repository inside another repository. */
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ root_path = svn_repos_find_root_path(local_abspath, pool);
+ if (root_path != NULL)
+ {
+ if (strcmp(root_path, local_abspath) == 0)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("'%s' is an existing repository"),
+ svn_dirent_local_style(root_path, pool));
+ else
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("'%s' is a subdirectory of an existing "
+ "repository " "rooted at '%s'"),
+ svn_dirent_local_style(local_abspath, pool),
+ svn_dirent_local_style(root_path, pool));
+ }
+
+ /* Create the various files and subdirectories for the repository. */
+ SVN_ERR_W(create_repos_structure(repos, path, fs_config, pool),
+ _("Repository creation failed"));
+
+ /* Lock if needed. */
+ SVN_ERR(lock_repos(repos, FALSE, FALSE, pool));
+
+ /* Create an environment for the filesystem. */
+ if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config, pool)))
+ {
+ /* If there was an error making the filesytem, e.g. unknown/supported
+ * filesystem type. Clean up after ourselves. Yes this is safe because
+ * create_repos_structure will fail if the path existed before we started
+ * so we can't accidentally remove a directory that previously existed.
+ */
+
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_io_remove_dir2(path, FALSE, NULL, NULL, pool)));
+ }
+
+ /* This repository is ready. Stamp it with a format number. */
+ SVN_ERR(svn_io_write_version_file
+ (svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
+ repos->format, pool));
+
+ *repos_p = repos;
+ return SVN_NO_ERROR;
+}
+
+
+/* Check if @a path is the root of a repository by checking if the
+ * path contains the expected files and directories. Return TRUE
+ * on errors (which would be permission errors, probably) so that
+ * we the user will see them after we try to open the repository
+ * for real. */
+static svn_boolean_t
+check_repos_path(const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_io_check_path(svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
+ &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return TRUE;
+ }
+ if (kind != svn_node_file)
+ return FALSE;
+
+ /* Check the db/ subdir, but allow it to be a symlink (Subversion
+ works just fine if it's a symlink). */
+ err = svn_io_check_resolved_path
+ (svn_dirent_join(path, SVN_REPOS__DB_DIR, pool), &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return TRUE;
+ }
+ if (kind != svn_node_dir)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* Verify that REPOS's format is suitable.
+ Use POOL for temporary allocation. */
+static svn_error_t *
+check_repos_format(svn_repos_t *repos,
+ apr_pool_t *pool)
+{
+ int format;
+ const char *format_path;
+
+ format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, pool);
+ SVN_ERR(svn_io_read_version_file(&format, format_path, pool));
+
+ if (format != SVN_REPOS__FORMAT_NUMBER &&
+ format != SVN_REPOS__FORMAT_NUMBER_LEGACY)
+ {
+ return svn_error_createf
+ (SVN_ERR_REPOS_UNSUPPORTED_VERSION, NULL,
+ _("Expected repository format '%d' or '%d'; found format '%d'"),
+ SVN_REPOS__FORMAT_NUMBER_LEGACY, SVN_REPOS__FORMAT_NUMBER,
+ format);
+ }
+
+ repos->format = format;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *REPOS_P to a repository at PATH which has been opened.
+ See lock_repos() above regarding EXCLUSIVE and NONBLOCKING.
+ OPEN_FS indicates whether the Subversion filesystem should be opened,
+ the handle being placed into repos->fs.
+ Do all allocation in POOL. */
+static svn_error_t *
+get_repos(svn_repos_t **repos_p,
+ const char *path,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ svn_boolean_t open_fs,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+
+ /* Allocate a repository object. */
+ repos = create_svn_repos_t(path, pool);
+
+ /* Verify the validity of our repository format. */
+ SVN_ERR(check_repos_format(repos, pool));
+
+ /* Discover the FS type. */
+ SVN_ERR(svn_fs_type(&repos->fs_type, repos->db_path, pool));
+
+ /* Lock if needed. */
+ SVN_ERR(lock_repos(repos, exclusive, nonblocking, pool));
+
+ /* Open up the filesystem only after obtaining the lock. */
+ if (open_fs)
+ SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, fs_config, pool));
+
+#ifdef SVN_DEBUG_CRASH_AT_REPOS_OPEN
+ /* If $PATH/config/debug-abort exists, crash the server here.
+ This debugging feature can be used to test client recovery
+ when the server crashes.
+
+ See: Issue #4274 */
+ {
+ svn_node_kind_t kind;
+ svn_error_t *err = svn_io_check_path(
+ svn_dirent_join(repos->conf_path, "debug-abort", pool),
+ &kind, pool);
+ svn_error_clear(err);
+ if (!err && kind == svn_node_file)
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+#endif /* SVN_DEBUG_CRASH_AT_REPOS_OPEN */
+
+ *repos_p = repos;
+ return SVN_NO_ERROR;
+}
+
+
+
+const char *
+svn_repos_find_root_path(const char *path,
+ apr_pool_t *pool)
+{
+ const char *candidate = path;
+ const char *decoded;
+ svn_error_t *err;
+
+ while (1)
+ {
+ /* Try to decode the path, so we don't fail if it contains characters
+ that aren't supported by the OS filesystem. The subversion fs
+ isn't restricted by the OS filesystem character set. */
+ err = svn_path_cstring_from_utf8(&decoded, candidate, pool);
+ if (!err && check_repos_path(candidate, pool))
+ break;
+ svn_error_clear(err);
+
+ if (svn_path_is_empty(candidate) ||
+ svn_dirent_is_root(candidate, strlen(candidate)))
+ return NULL;
+
+ candidate = svn_dirent_dirname(candidate, pool);
+ }
+
+ return candidate;
+}
+
+
+svn_error_t *
+svn_repos_open2(svn_repos_t **repos_p,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ /* Fetch a repository object initialized with a shared read/write
+ lock on the database. */
+
+ return get_repos(repos_p, path, FALSE, FALSE, TRUE, fs_config, pool);
+}
+
+
+svn_error_t *
+svn_repos_upgrade2(const char *path,
+ svn_boolean_t nonblocking,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ const char *format_path;
+ int format;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Fetch a repository object; for the Berkeley DB backend, it is
+ initialized with an EXCLUSIVE lock on the database. This will at
+ least prevent others from trying to read or write to it while we
+ run recovery. (Other backends should do their own locking; see
+ lock_repos.) */
+ SVN_ERR(get_repos(&repos, path, TRUE, nonblocking, FALSE, NULL, subpool));
+
+ if (notify_func)
+ {
+ /* We notify *twice* here, because there are two different logistical
+ actions occuring. */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_mutex_acquired, subpool);
+ notify_func(notify_baton, notify, subpool);
+
+ notify->action = svn_repos_notify_upgrade_start;
+ notify_func(notify_baton, notify, subpool);
+ }
+
+ /* Try to overwrite with its own contents. We do this only to
+ verify that we can, because we don't want to actually bump the
+ format of the repository until our underlying filesystem claims
+ to have been upgraded correctly. */
+ format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, subpool);
+ SVN_ERR(svn_io_read_version_file(&format, format_path, subpool));
+ SVN_ERR(svn_io_write_version_file(format_path, format, subpool));
+
+ /* Try to upgrade the filesystem. */
+ SVN_ERR(svn_fs_upgrade(repos->db_path, subpool));
+
+ /* Now overwrite our format file with the latest version. */
+ SVN_ERR(svn_io_write_version_file(format_path, SVN_REPOS__FORMAT_NUMBER,
+ subpool));
+
+ /* Close shop and free the subpool, to release the exclusive lock. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_delete(const char *path,
+ apr_pool_t *pool)
+{
+ const char *db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
+
+ /* Delete the filesystem environment... */
+ SVN_ERR(svn_fs_delete_fs(db_path, pool));
+
+ /* ...then blow away everything else. */
+ return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+}
+
+
+/* Repository supports the capability. */
+static const char *capability_yes = "yes";
+/* Repository does not support the capability. */
+static const char *capability_no = "no";
+
+svn_error_t *
+svn_repos_has_capability(svn_repos_t *repos,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ const char *val = svn_hash_gets(repos->repository_capabilities, capability);
+
+ if (val == capability_yes)
+ {
+ *has = TRUE;
+ }
+ else if (val == capability_no)
+ {
+ *has = FALSE;
+ }
+ /* Else don't know, so investigate. */
+ else if (strcmp(capability, SVN_REPOS_CAPABILITY_MERGEINFO) == 0)
+ {
+ svn_error_t *err;
+ svn_fs_root_t *root;
+ svn_mergeinfo_catalog_t ignored;
+ apr_array_header_t *paths = apr_array_make(pool, 1,
+ sizeof(char *));
+
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, 0, pool));
+ APR_ARRAY_PUSH(paths, const char *) = "";
+ err = svn_fs_get_mergeinfo2(&ignored, root, paths, FALSE, FALSE,
+ TRUE, pool, pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
+ {
+ svn_error_clear(err);
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_no);
+ *has = FALSE;
+ }
+ else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* Mergeinfo requests use relative paths, and anyway we're
+ in r0, so we're likely to get this error -- but it
+ means the repository supports mergeinfo! */
+ svn_error_clear(err);
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
+ *has = TRUE;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ else
+ {
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
+ *has = TRUE;
+ }
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, 0,
+ _("unknown capability '%s'"), capability);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_fs_t *
+svn_repos_fs(svn_repos_t *repos)
+{
+ if (! repos)
+ return NULL;
+ return repos->fs;
+}
+
+
+/* For historical reasons, for the Berkeley DB backend, this code uses
+ * repository locking, which is motivated by the need to support the
+ * Berkeley DB error DB_RUN_RECOVERY. (FSFS takes care of locking
+ * itself, inside its implementation of svn_fs_recover.) Here's how
+ * it works:
+ *
+ * Every accessor of a repository's database takes out a shared lock
+ * on the repository -- both readers and writers get shared locks, and
+ * there can be an unlimited number of shared locks simultaneously.
+ *
+ * Sometimes, a db access returns the error DB_RUN_RECOVERY. When
+ * this happens, we need to run svn_fs_recover() on the db
+ * with no other accessors present. So we take out an exclusive lock
+ * on the repository. From the moment we request the exclusive lock,
+ * no more shared locks are granted, and when the last shared lock
+ * disappears, the exclusive lock is granted. As soon as we get it,
+ * we can run recovery.
+ *
+ * We assume that once any berkeley call returns DB_RUN_RECOVERY, they
+ * all do, until recovery is run.
+ */
+
+svn_error_t *
+svn_repos_recover4(const char *path,
+ svn_boolean_t nonblocking,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void * cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Fetch a repository object; for the Berkeley DB backend, it is
+ initialized with an EXCLUSIVE lock on the database. This will at
+ least prevent others from trying to read or write to it while we
+ run recovery. (Other backends should do their own locking; see
+ lock_repos.) */
+ SVN_ERR(get_repos(&repos, path, TRUE, nonblocking,
+ FALSE, /* don't try to open the db yet. */
+ NULL,
+ subpool));
+
+ if (notify_func)
+ {
+ /* We notify *twice* here, because there are two different logistical
+ actions occuring. */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_mutex_acquired, subpool);
+ notify_func(notify_baton, notify, subpool);
+
+ notify->action = svn_repos_notify_recover_start;
+ notify_func(notify_baton, notify, subpool);
+ }
+
+ /* Recover the database to a consistent state. */
+ SVN_ERR(svn_fs_recover(repos->db_path, cancel_func, cancel_baton, subpool));
+
+ /* Close shop and free the subpool, to release the exclusive lock. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+struct freeze_baton_t {
+ apr_array_header_t *paths;
+ int counter;
+ svn_repos_freeze_func_t freeze_func;
+ void *freeze_baton;
+};
+
+static svn_error_t *
+multi_freeze(void *baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t *fb = baton;
+
+ if (fb->counter == fb->paths->nelts)
+ {
+ SVN_ERR(fb->freeze_func(fb->freeze_baton, pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* Using a subpool as the only way to unlock the repos lock used
+ by BDB is to clear the pool used to take the lock. */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *path = APR_ARRAY_IDX(fb->paths, fb->counter, const char *);
+ svn_repos_t *repos;
+
+ ++fb->counter;
+
+ SVN_ERR(get_repos(&repos, path,
+ TRUE /* exclusive (only applies to BDB) */,
+ FALSE /* non-blocking */,
+ FALSE /* open-fs */,
+ NULL, subpool));
+
+
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ {
+ svn_error_t *err = multi_freeze(fb, subpool);
+
+ svn_pool_destroy(subpool);
+
+ return err;
+ }
+ else
+ {
+ SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, NULL, subpool));
+ SVN_ERR(svn_fs_freeze(svn_repos_fs(repos), multi_freeze, fb,
+ subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* For BDB we fall back on BDB's repos layer lock which means that the
+ repository is unreadable while frozen.
+
+ For FSFS we delegate to the FS layer which uses the FSFS write-lock
+ and an SQLite reserved lock which means the repository is readable
+ while frozen. */
+svn_error_t *
+svn_repos_freeze(apr_array_header_t *paths,
+ svn_repos_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t fb;
+
+ fb.paths = paths;
+ fb.counter = 0;
+ fb.freeze_func = freeze_func;
+ fb.freeze_baton = freeze_baton;
+
+ SVN_ERR(multi_freeze(&fb, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ int i;
+
+ SVN_ERR(get_repos(&repos, path,
+ FALSE, FALSE,
+ FALSE, /* Do not open fs. */
+ NULL,
+ pool));
+
+ SVN_ERR(svn_fs_berkeley_logfiles(logfiles,
+ svn_repos_db_env(repos, pool),
+ only_unused,
+ pool));
+
+ /* Loop, printing log files. */
+ for (i = 0; i < (*logfiles)->nelts; i++)
+ {
+ const char ** log_file = &(APR_ARRAY_IDX(*logfiles, i, const char *));
+ *log_file = svn_dirent_join(SVN_REPOS__DB_DIR, *log_file, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for hotcopy_structure(). */
+struct hotcopy_ctx_t {
+ const char *dest; /* target location to construct */
+ size_t src_len; /* len of the source path*/
+
+ /* As in svn_repos_hotcopy2() */
+ svn_boolean_t incremental;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
+
+/* Copy the repository structure of PATH to BATON->DEST, with exception of
+ * @c SVN_REPOS__DB_DIR, @c SVN_REPOS__LOCK_DIR and @c SVN_REPOS__FORMAT;
+ * those directories and files are handled separately.
+ *
+ * BATON is a (struct hotcopy_ctx_t *). BATON->SRC_LEN is the length
+ * of PATH.
+ *
+ * Implements svn_io_walk_func_t.
+ */
+static svn_error_t *hotcopy_structure(void *baton,
+ const char *path,
+ const apr_finfo_t *finfo,
+ apr_pool_t *pool)
+{
+ const struct hotcopy_ctx_t *ctx = baton;
+ const char *sub_path;
+ const char *target;
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ if (strlen(path) == ctx->src_len)
+ {
+ sub_path = "";
+ }
+ else
+ {
+ sub_path = &path[ctx->src_len+1];
+
+ /* Check if we are inside db directory and if so skip it */
+ if (svn_path_compare_paths
+ (svn_dirent_get_longest_ancestor(SVN_REPOS__DB_DIR, sub_path, pool),
+ SVN_REPOS__DB_DIR) == 0)
+ return SVN_NO_ERROR;
+
+ if (svn_path_compare_paths
+ (svn_dirent_get_longest_ancestor(SVN_REPOS__LOCK_DIR, sub_path,
+ pool),
+ SVN_REPOS__LOCK_DIR) == 0)
+ return SVN_NO_ERROR;
+
+ if (svn_path_compare_paths
+ (svn_dirent_get_longest_ancestor(SVN_REPOS__FORMAT, sub_path, pool),
+ SVN_REPOS__FORMAT) == 0)
+ return SVN_NO_ERROR;
+ }
+
+ target = svn_dirent_join(ctx->dest, sub_path, pool);
+
+ if (finfo->filetype == APR_DIR)
+ {
+ svn_error_t *err;
+
+ err = create_repos_dir(target, pool);
+ if (ctx->incremental && err && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+ else if (finfo->filetype == APR_REG)
+ return svn_io_copy_file(path, target, TRUE, pool);
+ else if (finfo->filetype == APR_LNK)
+ return svn_io_copy_link(path, target, pool);
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/** Obtain a lock on db logs lock file. Create one if it does not exist.
+ */
+static svn_error_t *
+lock_db_logs_file(svn_repos_t *repos,
+ svn_boolean_t exclusive,
+ apr_pool_t *pool)
+{
+ const char * lock_file = svn_repos_db_logs_lockfile(repos, pool);
+
+ /* Try to create a lock file, in case if it is missing. As in case of the
+ repositories created before hotcopy functionality. */
+ svn_error_clear(create_db_logs_lock(repos, pool));
+
+ return svn_io_file_lock2(lock_file, exclusive, FALSE, pool);
+}
+
+
+/* Make a copy of a repository with hot backup of fs. */
+svn_error_t *
+svn_repos_hotcopy2(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_t *src_repos;
+ svn_repos_t *dst_repos;
+ struct hotcopy_ctx_t hotcopy_context;
+ svn_error_t *err;
+ const char *src_abspath;
+ const char *dst_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&src_abspath, src_path, pool));
+ SVN_ERR(svn_dirent_get_absolute(&dst_abspath, dst_path, pool));
+ if (strcmp(src_abspath, dst_abspath) == 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Hotcopy source and destination are equal"));
+
+ /* Try to open original repository */
+ SVN_ERR(get_repos(&src_repos, src_abspath,
+ FALSE, FALSE,
+ FALSE, /* don't try to open the db yet. */
+ NULL,
+ pool));
+
+ /* If we are going to clean logs, then get an exclusive lock on
+ db-logs.lock, to ensure that no one else will work with logs.
+
+ If we are just copying, then get a shared lock to ensure that
+ no one else will clean logs while we copying them */
+
+ SVN_ERR(lock_db_logs_file(src_repos, clean_logs, pool));
+
+ /* Copy the repository to a new path, with exception of
+ specially handled directories */
+
+ hotcopy_context.dest = dst_abspath;
+ hotcopy_context.src_len = strlen(src_abspath);
+ hotcopy_context.incremental = incremental;
+ hotcopy_context.cancel_func = cancel_func;
+ hotcopy_context.cancel_baton = cancel_baton;
+ SVN_ERR(svn_io_dir_walk2(src_abspath,
+ 0,
+ hotcopy_structure,
+ &hotcopy_context,
+ pool));
+
+ /* Prepare dst_repos object so that we may create locks,
+ so that we may open repository */
+
+ dst_repos = create_svn_repos_t(dst_abspath, pool);
+ dst_repos->fs_type = src_repos->fs_type;
+ dst_repos->format = src_repos->format;
+
+ err = create_locks(dst_repos, pool);
+ if (err)
+ {
+ if (incremental && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+
+ err = svn_io_dir_make_sgid(dst_repos->db_path, APR_OS_DEFAULT, pool);
+ if (err)
+ {
+ if (incremental && APR_STATUS_IS_EEXIST(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+
+ /* Exclusively lock the new repository.
+ No one should be accessing it at the moment */
+ SVN_ERR(lock_repos(dst_repos, TRUE, FALSE, pool));
+
+ SVN_ERR(svn_fs_hotcopy2(src_repos->db_path, dst_repos->db_path,
+ clean_logs, incremental,
+ cancel_func, cancel_baton, pool));
+
+ /* Destination repository is ready. Stamp it with a format number. */
+ return svn_io_write_version_file
+ (svn_dirent_join(dst_repos->path, SVN_REPOS__FORMAT, pool),
+ dst_repos->format, pool);
+}
+
+svn_error_t *
+svn_repos_hotcopy(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_hotcopy2(src_path, dst_path, clean_logs,
+ FALSE, NULL, NULL, pool));
+}
+
+/* Return the library version number. */
+const svn_version_t *
+svn_repos_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+
+
+svn_error_t *
+svn_repos_stat(svn_dirent_t **dirent,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_dirent_t *ent;
+ const char *datestring;
+ apr_hash_t *prophash;
+
+ SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
+
+ if (kind == svn_node_none)
+ {
+ *dirent = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ ent = svn_dirent_create(pool);
+ ent->kind = kind;
+
+ if (kind == svn_node_file)
+ SVN_ERR(svn_fs_file_length(&(ent->size), root, path, pool));
+
+ SVN_ERR(svn_fs_node_proplist(&prophash, root, path, pool));
+ if (apr_hash_count(prophash) > 0)
+ ent->has_props = TRUE;
+
+ SVN_ERR(svn_repos_get_committed_info(&(ent->created_rev),
+ &datestring,
+ &(ent->last_author),
+ root, path, pool));
+ if (datestring)
+ SVN_ERR(svn_time_from_cstring(&(ent->time), datestring, pool));
+
+ *dirent = ent;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_remember_client_capabilities(svn_repos_t *repos,
+ const apr_array_header_t *capabilities)
+{
+ repos->client_capabilities = capabilities;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__fs_type(const char **fs_type,
+ const char *repos_path,
+ apr_pool_t *pool)
+{
+ svn_repos_t repos;
+ repos.path = (char*)repos_path;
+
+ SVN_ERR(check_repos_format(&repos, pool));
+
+ return svn_fs_type(fs_type,
+ svn_dirent_join(repos_path, SVN_REPOS__DB_DIR, pool),
+ pool);
+}
OpenPOWER on IntegriCloud