1251881Speter/* repos.c : repository creation; shared and exclusive repository locking
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter#include <apr_pools.h>
24251881Speter#include <apr_file_io.h>
25251881Speter
26251881Speter#include "svn_pools.h"
27251881Speter#include "svn_error.h"
28251881Speter#include "svn_dirent_uri.h"
29251881Speter#include "svn_path.h"
30251881Speter#include "svn_utf.h"
31251881Speter#include "svn_time.h"
32251881Speter#include "svn_fs.h"
33251881Speter#include "svn_ra.h"  /* for SVN_RA_CAPABILITY_* */
34251881Speter#include "svn_repos.h"
35251881Speter#include "svn_hash.h"
36251881Speter#include "svn_version.h"
37251881Speter#include "svn_config.h"
38251881Speter
39251881Speter#include "private/svn_repos_private.h"
40251881Speter#include "private/svn_subr_private.h"
41251881Speter#include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
42251881Speter
43251881Speter#include "repos.h"
44251881Speter
45251881Speter/* Used to terminate lines in large multi-line string literals. */
46251881Speter#define NL APR_EOL_STR
47251881Speter
48251881Speter
49251881Speter/* Path accessor functions. */
50251881Speter
51251881Speter
52251881Speterconst char *
53251881Spetersvn_repos_path(svn_repos_t *repos, apr_pool_t *pool)
54251881Speter{
55251881Speter  return apr_pstrdup(pool, repos->path);
56251881Speter}
57251881Speter
58251881Speter
59251881Speterconst char *
60251881Spetersvn_repos_db_env(svn_repos_t *repos, apr_pool_t *pool)
61251881Speter{
62251881Speter  return apr_pstrdup(pool, repos->db_path);
63251881Speter}
64251881Speter
65251881Speter
66251881Speterconst char *
67251881Spetersvn_repos_conf_dir(svn_repos_t *repos, apr_pool_t *pool)
68251881Speter{
69251881Speter  return apr_pstrdup(pool, repos->conf_path);
70251881Speter}
71251881Speter
72251881Speter
73251881Speterconst char *
74251881Spetersvn_repos_svnserve_conf(svn_repos_t *repos, apr_pool_t *pool)
75251881Speter{
76251881Speter  return svn_dirent_join(repos->conf_path, SVN_REPOS__CONF_SVNSERVE_CONF, pool);
77251881Speter}
78251881Speter
79251881Speter
80251881Speterconst char *
81251881Spetersvn_repos_lock_dir(svn_repos_t *repos, apr_pool_t *pool)
82251881Speter{
83251881Speter  return apr_pstrdup(pool, repos->lock_path);
84251881Speter}
85251881Speter
86251881Speter
87251881Speterconst char *
88251881Spetersvn_repos_db_lockfile(svn_repos_t *repos, apr_pool_t *pool)
89251881Speter{
90251881Speter  return svn_dirent_join(repos->lock_path, SVN_REPOS__DB_LOCKFILE, pool);
91251881Speter}
92251881Speter
93251881Speter
94251881Speterconst char *
95251881Spetersvn_repos_db_logs_lockfile(svn_repos_t *repos, apr_pool_t *pool)
96251881Speter{
97251881Speter  return svn_dirent_join(repos->lock_path, SVN_REPOS__DB_LOGS_LOCKFILE, pool);
98251881Speter}
99251881Speter
100251881Speterconst char *
101251881Spetersvn_repos_hook_dir(svn_repos_t *repos, apr_pool_t *pool)
102251881Speter{
103251881Speter  return apr_pstrdup(pool, repos->hook_path);
104251881Speter}
105251881Speter
106251881Speter
107251881Speterconst char *
108251881Spetersvn_repos_start_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
109251881Speter{
110251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_START_COMMIT, pool);
111251881Speter}
112251881Speter
113251881Speter
114251881Speterconst char *
115251881Spetersvn_repos_pre_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
116251881Speter{
117251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_COMMIT, pool);
118251881Speter}
119251881Speter
120251881Speter
121251881Speterconst char *
122251881Spetersvn_repos_pre_lock_hook(svn_repos_t *repos, apr_pool_t *pool)
123251881Speter{
124251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_LOCK, pool);
125251881Speter}
126251881Speter
127251881Speter
128251881Speterconst char *
129251881Spetersvn_repos_pre_unlock_hook(svn_repos_t *repos, apr_pool_t *pool)
130251881Speter{
131251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_UNLOCK, pool);
132251881Speter}
133251881Speter
134251881Speterconst char *
135251881Spetersvn_repos_post_lock_hook(svn_repos_t *repos, apr_pool_t *pool)
136251881Speter{
137251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_LOCK, pool);
138251881Speter}
139251881Speter
140251881Speter
141251881Speterconst char *
142251881Spetersvn_repos_post_unlock_hook(svn_repos_t *repos, apr_pool_t *pool)
143251881Speter{
144251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_UNLOCK, pool);
145251881Speter}
146251881Speter
147251881Speter
148251881Speterconst char *
149251881Spetersvn_repos_post_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
150251881Speter{
151251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_COMMIT, pool);
152251881Speter}
153251881Speter
154251881Speter
155251881Speterconst char *
156251881Spetersvn_repos_pre_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool)
157251881Speter{
158251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE,
159251881Speter                       pool);
160251881Speter}
161251881Speter
162251881Speter
163251881Speterconst char *
164251881Spetersvn_repos_post_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool)
165251881Speter{
166251881Speter  return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_REVPROP_CHANGE,
167251881Speter                       pool);
168251881Speter}
169251881Speter
170251881Speterstatic svn_error_t *
171251881Spetercreate_repos_dir(const char *path, apr_pool_t *pool)
172251881Speter{
173251881Speter  svn_error_t *err;
174251881Speter
175251881Speter  err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
176251881Speter  if (err && (APR_STATUS_IS_EEXIST(err->apr_err)))
177251881Speter    {
178251881Speter      svn_boolean_t is_empty;
179251881Speter
180251881Speter      svn_error_clear(err);
181251881Speter
182251881Speter      SVN_ERR(svn_io_dir_empty(&is_empty, path, pool));
183251881Speter
184251881Speter      if (is_empty)
185251881Speter        err = NULL;
186251881Speter      else
187251881Speter        err = svn_error_createf(SVN_ERR_DIR_NOT_EMPTY, 0,
188251881Speter                                _("'%s' exists and is non-empty"),
189251881Speter                                svn_dirent_local_style(path, pool));
190251881Speter    }
191251881Speter
192251881Speter  return svn_error_trace(err);
193251881Speter}
194251881Speter
195251881Speterstatic const char * bdb_lock_file_contents =
196251881Speter  "DB lock file, representing locks on the versioned filesystem."            NL
197251881Speter  ""                                                                         NL
198251881Speter  "All accessors -- both readers and writers -- of the repository's"         NL
199251881Speter  "Berkeley DB environment take out shared locks on this file, and"          NL
200251881Speter  "each accessor removes its lock when done.  If and when the DB"            NL
201251881Speter  "recovery procedure is run, the recovery code takes out an"                NL
202251881Speter  "exclusive lock on this file, so we can be sure no one else is"            NL
203251881Speter  "using the DB during the recovery."                                        NL
204251881Speter  ""                                                                         NL
205251881Speter  "You should never have to edit or remove this file."                       NL;
206251881Speter
207251881Speterstatic const char * bdb_logs_lock_file_contents =
208251881Speter  "DB logs lock file, representing locks on the versioned filesystem logs."  NL
209251881Speter  ""                                                                         NL
210251881Speter  "All log manipulators of the repository's Berkeley DB environment"         NL
211251881Speter  "take out exclusive locks on this file to ensure that only one"            NL
212251881Speter  "accessor manipulates the logs at a time."                                 NL
213251881Speter  ""                                                                         NL
214251881Speter  "You should never have to edit or remove this file."                       NL;
215251881Speter
216251881Speterstatic const char * pre12_compat_unneeded_file_contents =
217251881Speter  "This file is not used by Subversion 1.3.x or later."                      NL
218251881Speter  "However, its existence is required for compatibility with"                NL
219251881Speter  "Subversion 1.2.x or earlier."                                             NL;
220251881Speter
221251881Speter/* Create the DB logs lockfile. */
222251881Speterstatic svn_error_t *
223251881Spetercreate_db_logs_lock(svn_repos_t *repos, apr_pool_t *pool) {
224251881Speter  const char *contents;
225251881Speter  const char *lockfile_path;
226251881Speter
227251881Speter  lockfile_path = svn_repos_db_logs_lockfile(repos, pool);
228251881Speter  if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
229251881Speter    contents = bdb_logs_lock_file_contents;
230251881Speter  else
231251881Speter    contents = pre12_compat_unneeded_file_contents;
232251881Speter
233251881Speter  SVN_ERR_W(svn_io_file_create(lockfile_path, contents, pool),
234251881Speter            _("Creating db logs lock file"));
235251881Speter
236251881Speter  return SVN_NO_ERROR;
237251881Speter}
238251881Speter
239251881Speter/* Create the DB lockfile. */
240251881Speterstatic svn_error_t *
241251881Spetercreate_db_lock(svn_repos_t *repos, apr_pool_t *pool) {
242251881Speter  const char *contents;
243251881Speter  const char *lockfile_path;
244251881Speter
245251881Speter  lockfile_path = svn_repos_db_lockfile(repos, pool);
246251881Speter  if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
247251881Speter    contents = bdb_lock_file_contents;
248251881Speter  else
249251881Speter    contents = pre12_compat_unneeded_file_contents;
250251881Speter
251251881Speter  SVN_ERR_W(svn_io_file_create(lockfile_path, contents, pool),
252251881Speter            _("Creating db lock file"));
253251881Speter
254251881Speter  return SVN_NO_ERROR;
255251881Speter}
256251881Speter
257251881Speterstatic svn_error_t *
258251881Spetercreate_locks(svn_repos_t *repos, apr_pool_t *pool)
259251881Speter{
260251881Speter  /* Create the locks directory. */
261251881Speter  SVN_ERR_W(create_repos_dir(repos->lock_path, pool),
262251881Speter            _("Creating lock dir"));
263251881Speter
264251881Speter  SVN_ERR(create_db_lock(repos, pool));
265251881Speter  return create_db_logs_lock(repos, pool);
266251881Speter}
267251881Speter
268251881Speter
269251881Speter#define HOOKS_ENVIRONMENT_TEXT                                                \
270251881Speter  "# The hook program typically does not inherit the environment of"       NL \
271251881Speter  "# its parent process.  For example, a common problem is for the"        NL \
272251881Speter  "# PATH environment variable to not be set to its usual value, so"       NL \
273251881Speter  "# that subprograms fail to launch unless invoked via absolute path."    NL \
274251881Speter  "# If you're having unexpected problems with a hook program, the"        NL \
275251881Speter  "# culprit may be unusual (or missing) environment variables."           NL
276251881Speter
277251881Speter#define PREWRITTEN_HOOKS_TEXT                                                 \
278251881Speter  "# For more examples and pre-written hooks, see those in"                NL \
279251881Speter  "# the Subversion repository at"                                         NL \
280251881Speter  "# http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ and"        NL \
281251881Speter  "# http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/"          NL
282251881Speter
283251881Speter
284251881Speterstatic svn_error_t *
285251881Spetercreate_hooks(svn_repos_t *repos, apr_pool_t *pool)
286251881Speter{
287251881Speter  const char *this_path, *contents;
288251881Speter
289251881Speter  /* Create the hook directory. */
290251881Speter  SVN_ERR_W(create_repos_dir(repos->hook_path, pool),
291251881Speter            _("Creating hook directory"));
292251881Speter
293251881Speter  /*** Write a default template for each standard hook file. */
294251881Speter
295251881Speter  /* Start-commit hook. */
296251881Speter  {
297251881Speter    this_path = apr_psprintf(pool, "%s%s",
298251881Speter                             svn_repos_start_commit_hook(repos, pool),
299251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
300251881Speter
301251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_START_COMMIT
302251881Speter
303251881Speter    contents =
304251881Speter"#!/bin/sh"                                                                  NL
305251881Speter""                                                                           NL
306251881Speter"# START-COMMIT HOOK"                                                        NL
307251881Speter"#"                                                                          NL
308251881Speter"# The start-commit hook is invoked immediately after a Subversion txn is"   NL
309251881Speter"# created and populated with initial revprops in the process of doing a"    NL
310251881Speter"# commit. Subversion runs this hook by invoking a program (script, "        NL
311251881Speter"# executable, binary, etc.) named '"SCRIPT_NAME"' (for which this file"     NL
312251881Speter"# is a template) with the following ordered arguments:"                     NL
313251881Speter"#"                                                                          NL
314251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
315251881Speter"#   [2] USER         (the authenticated user attempting to commit)"         NL
316251881Speter"#   [3] CAPABILITIES (a colon-separated list of capabilities reported"      NL
317251881Speter"#                     by the client; see note below)"                       NL
318251881Speter"#   [4] TXN-NAME     (the name of the commit txn just created)"             NL
319251881Speter"#"                                                                          NL
320251881Speter"# Note: The CAPABILITIES parameter is new in Subversion 1.5, and 1.5"       NL
321251881Speter"# clients will typically report at least the \""                            \
322251881Speter   SVN_RA_CAPABILITY_MERGEINFO "\" capability."                              NL
323251881Speter"# If there are other capabilities, then the list is colon-separated,"       NL
324251881Speter"# e.g.: \"" SVN_RA_CAPABILITY_MERGEINFO ":some-other-capability\" "         \
325251881Speter  "(the order is undefined)."                                                NL
326251881Speter"#"                                                                          NL
327251881Speter"# Note: The TXN-NAME parameter is new in Subversion 1.8.  Prior to version" NL
328251881Speter"# 1.8, the start-commit hook was invoked before the commit txn was even"    NL
329251881Speter"# created, so the ability to inspect the commit txn and its metadata from"  NL
330251881Speter"# within the start-commit hook was not possible."                           NL
331251881Speter"# "                                                                         NL
332251881Speter"# The list is self-reported by the client.  Therefore, you should not"      NL
333251881Speter"# make security assumptions based on the capabilities list, nor should"     NL
334251881Speter"# you assume that clients reliably report every capability they have."      NL
335251881Speter"#"                                                                          NL
336251881Speter"# The working directory for this hook program's invocation is undefined,"   NL
337251881Speter"# so the program should set one explicitly if it cares."                    NL
338251881Speter"#"                                                                          NL
339251881Speter"# If the hook program exits with success, the commit continues; but"        NL
340251881Speter"# if it exits with failure (non-zero), the commit is stopped before"        NL
341251881Speter"# a Subversion txn is created, and STDERR is returned to the client."       NL
342251881Speter"#"                                                                          NL
343251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
344251881Speter"# invoke other programs to do the real work, though it may do the"          NL
345251881Speter"# work itself too."                                                         NL
346251881Speter"#"                                                                          NL
347251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
348251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
349251881Speter"# have filesystem-level permission to access the repository."               NL
350251881Speter"#"                                                                          NL
351251881Speter"# On a Windows system, you should name the hook program"                    NL
352251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
353251881Speter"# but the basic idea is the same."                                          NL
354251881Speter"# "                                                                         NL
355251881SpeterHOOKS_ENVIRONMENT_TEXT
356251881Speter"# "                                                                         NL
357251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter."          NL
358251881SpeterPREWRITTEN_HOOKS_TEXT
359251881Speter""                                                                           NL
360251881Speter""                                                                           NL
361251881Speter"REPOS=\"$1\""                                                               NL
362251881Speter"USER=\"$2\""                                                                NL
363251881Speter""                                                                           NL
364251881Speter"commit-allower.pl --repository \"$REPOS\" --user \"$USER\" || exit 1"       NL
365251881Speter"special-auth-check.py --user \"$USER\" --auth-level 3 || exit 1"            NL
366251881Speter""                                                                           NL
367251881Speter"# All checks passed, so allow the commit."                                  NL
368251881Speter"exit 0"                                                                     NL;
369251881Speter
370251881Speter#undef SCRIPT_NAME
371251881Speter
372251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
373251881Speter              _("Creating start-commit hook"));
374251881Speter
375251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
376251881Speter  }  /* end start-commit hook */
377251881Speter
378251881Speter  /* Pre-commit hook. */
379251881Speter  {
380251881Speter    this_path = apr_psprintf(pool, "%s%s",
381251881Speter                             svn_repos_pre_commit_hook(repos, pool),
382251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
383251881Speter
384251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_COMMIT
385251881Speter
386251881Speter    contents =
387251881Speter"#!/bin/sh"                                                                  NL
388251881Speter""                                                                           NL
389251881Speter"# PRE-COMMIT HOOK"                                                          NL
390251881Speter"#"                                                                          NL
391251881Speter"# The pre-commit hook is invoked before a Subversion txn is"                NL
392251881Speter"# committed.  Subversion runs this hook by invoking a program"              NL
393251881Speter"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which"      NL
394251881Speter"# this file is a template), with the following ordered arguments:"          NL
395251881Speter"#"                                                                          NL
396251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
397251881Speter"#   [2] TXN-NAME     (the name of the txn about to be committed)"           NL
398251881Speter"#"                                                                          NL
399251881Speter"#   [STDIN] LOCK-TOKENS ** the lock tokens are passed via STDIN."           NL
400251881Speter"#"                                                                          NL
401251881Speter"#   If STDIN contains the line \"LOCK-TOKENS:\\n\" (the \"\\n\" denotes a"  NL
402251881Speter"#   single newline), the lines following it are the lock tokens for"        NL
403251881Speter"#   this commit.  The end of the list is marked by a line containing"       NL
404251881Speter"#   only a newline character."                                              NL
405251881Speter"#"                                                                          NL
406251881Speter"#   Each lock token line consists of a URI-escaped path, followed"          NL
407251881Speter"#   by the separator character '|', followed by the lock token string,"     NL
408251881Speter"#   followed by a newline."                                                 NL
409251881Speter"#"                                                                          NL
410251881Speter"# The default working directory for the invocation is undefined, so"        NL
411251881Speter"# the program should set one explicitly if it cares."                       NL
412251881Speter"#"                                                                          NL
413251881Speter"# If the hook program exits with success, the txn is committed; but"        NL
414251881Speter"# if it exits with failure (non-zero), the txn is aborted, no commit"       NL
415251881Speter"# takes place, and STDERR is returned to the client.   The hook"            NL
416251881Speter"# program can use the 'svnlook' utility to help it examine the txn."        NL
417251881Speter"#"                                                                          NL
418251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
419251881Speter"# invoke other programs to do the real work, though it may do the"          NL
420251881Speter"# work itself too."                                                         NL
421251881Speter"#"                                                                          NL
422251881Speter"#   ***  NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT  ***"       NL
423251881Speter"#   ***  FOR REVISION PROPERTIES (like svn:log or svn:author).   ***"       NL
424251881Speter"#"                                                                          NL
425251881Speter"#   This is why we recommend using the read-only 'svnlook' utility."        NL
426251881Speter"#   In the future, Subversion may enforce the rule that pre-commit"         NL
427251881Speter"#   hooks should not modify the versioned data in txns, or else come"       NL
428251881Speter"#   up with a mechanism to make it safe to do so (by informing the"         NL
429251881Speter"#   committing client of the changes).  However, right now neither"         NL
430251881Speter"#   mechanism is implemented, so hook writers just have to be careful."     NL
431251881Speter"#"                                                                          NL
432251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
433251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
434251881Speter"# have filesystem-level permission to access the repository."               NL
435251881Speter"#"                                                                          NL
436251881Speter"# On a Windows system, you should name the hook program"                    NL
437251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
438251881Speter"# but the basic idea is the same."                                          NL
439251881Speter"#"                                                                          NL
440251881SpeterHOOKS_ENVIRONMENT_TEXT
441251881Speter"# "                                                                         NL
442251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter."          NL
443251881SpeterPREWRITTEN_HOOKS_TEXT
444251881Speter""                                                                           NL
445251881Speter""                                                                           NL
446251881Speter"REPOS=\"$1\""                                                               NL
447251881Speter"TXN=\"$2\""                                                                 NL
448251881Speter""                                                                           NL
449251881Speter"# Make sure that the log message contains some text."                       NL
450251881Speter"SVNLOOK=" SVN_BINDIR "/svnlook"                                             NL
451251881Speter"$SVNLOOK log -t \"$TXN\" \"$REPOS\" | \\"                                   NL
452251881Speter"   grep \"[a-zA-Z0-9]\" > /dev/null || exit 1"                              NL
453251881Speter""                                                                           NL
454251881Speter"# Check that the author of this commit has the rights to perform"           NL
455251881Speter"# the commit on the files and directories being modified."                  NL
456251881Speter"commit-access-control.pl \"$REPOS\" \"$TXN\" commit-access-control.cfg || exit 1"
457251881Speter                                                                             NL
458251881Speter""                                                                           NL
459251881Speter"# All checks passed, so allow the commit."                                  NL
460251881Speter"exit 0"                                                                     NL;
461251881Speter
462251881Speter#undef SCRIPT_NAME
463251881Speter
464251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
465251881Speter              _("Creating pre-commit hook"));
466251881Speter
467251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
468251881Speter  }  /* end pre-commit hook */
469251881Speter
470251881Speter
471251881Speter  /* Pre-revprop-change hook. */
472251881Speter  {
473251881Speter    this_path = apr_psprintf(pool, "%s%s",
474251881Speter                             svn_repos_pre_revprop_change_hook(repos, pool),
475251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
476251881Speter
477251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_REVPROP_CHANGE
478251881Speter
479251881Speter    contents =
480251881Speter"#!/bin/sh"                                                                  NL
481251881Speter""                                                                           NL
482251881Speter"# PRE-REVPROP-CHANGE HOOK"                                                  NL
483251881Speter"#"                                                                          NL
484251881Speter"# The pre-revprop-change hook is invoked before a revision property"        NL
485251881Speter"# is added, modified or deleted.  Subversion runs this hook by invoking"    NL
486251881Speter"# a program (script, executable, binary, etc.) named '"SCRIPT_NAME"'"       NL
487251881Speter"# (for which this file is a template), with the following ordered"          NL
488251881Speter"# arguments:"                                                               NL
489251881Speter"#"                                                                          NL
490251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
491251881Speter"#   [2] REV          (the revision being tweaked)"                          NL
492251881Speter"#   [3] USER         (the username of the person tweaking the property)"    NL
493251881Speter"#   [4] PROPNAME     (the property being set on the revision)"              NL
494251881Speter"#   [5] ACTION       (the property is being 'A'dded, 'M'odified, or 'D'eleted)"
495251881Speter                                                                             NL
496251881Speter"#"                                                                          NL
497251881Speter"#   [STDIN] PROPVAL  ** the new property value is passed via STDIN."        NL
498251881Speter"#"                                                                          NL
499251881Speter"# If the hook program exits with success, the propchange happens; but"      NL
500251881Speter"# if it exits with failure (non-zero), the propchange doesn't happen."      NL
501251881Speter"# The hook program can use the 'svnlook' utility to examine the "           NL
502251881Speter"# existing value of the revision property."                                 NL
503251881Speter"#"                                                                          NL
504251881Speter"# WARNING: unlike other hooks, this hook MUST exist for revision"           NL
505251881Speter"# properties to be changed.  If the hook does not exist, Subversion "       NL
506251881Speter"# will behave as if the hook were present, but failed.  The reason"         NL
507251881Speter"# for this is that revision properties are UNVERSIONED, meaning that"       NL
508251881Speter"# a successful propchange is destructive;  the old value is gone"           NL
509251881Speter"# forever.  We recommend the hook back up the old value somewhere."         NL
510251881Speter"#"                                                                          NL
511251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
512251881Speter"# invoke other programs to do the real work, though it may do the"          NL
513251881Speter"# work itself too."                                                         NL
514251881Speter"#"                                                                          NL
515251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
516251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
517251881Speter"# have filesystem-level permission to access the repository."               NL
518251881Speter"#"                                                                          NL
519251881Speter"# On a Windows system, you should name the hook program"                    NL
520251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
521251881Speter"# but the basic idea is the same."                                          NL
522251881Speter"#"                                                                          NL
523251881SpeterHOOKS_ENVIRONMENT_TEXT
524251881Speter"# "                                                                         NL
525251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter."          NL
526251881SpeterPREWRITTEN_HOOKS_TEXT
527251881Speter""                                                                           NL
528251881Speter""                                                                           NL
529251881Speter"REPOS=\"$1\""                                                               NL
530251881Speter"REV=\"$2\""                                                                 NL
531251881Speter"USER=\"$3\""                                                                NL
532251881Speter"PROPNAME=\"$4\""                                                            NL
533251881Speter"ACTION=\"$5\""                                                              NL
534251881Speter""                                                                           NL
535251881Speter"if [ \"$ACTION\" = \"M\" -a \"$PROPNAME\" = \"svn:log\" ]; then exit 0; fi" NL
536251881Speter""                                                                           NL
537251881Speter"echo \"Changing revision properties other than svn:log is prohibited\" >&2" NL
538251881Speter"exit 1"                                                                     NL;
539251881Speter
540251881Speter#undef SCRIPT_NAME
541251881Speter
542251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
543251881Speter              _("Creating pre-revprop-change hook"));
544251881Speter
545251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
546251881Speter  }  /* end pre-revprop-change hook */
547251881Speter
548251881Speter
549251881Speter  /* Pre-lock hook. */
550251881Speter  {
551251881Speter    this_path = apr_psprintf(pool, "%s%s",
552251881Speter                             svn_repos_pre_lock_hook(repos, pool),
553251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
554251881Speter
555251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_LOCK
556251881Speter
557251881Speter    contents =
558251881Speter"#!/bin/sh"                                                                  NL
559251881Speter""                                                                           NL
560251881Speter"# PRE-LOCK HOOK"                                                            NL
561251881Speter"#"                                                                          NL
562251881Speter"# The pre-lock hook is invoked before an exclusive lock is"                 NL
563251881Speter"# created.  Subversion runs this hook by invoking a program "               NL
564251881Speter"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which"      NL
565251881Speter"# this file is a template), with the following ordered arguments:"          NL
566251881Speter"#"                                                                          NL
567251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
568251881Speter"#   [2] PATH         (the path in the repository about to be locked)"       NL
569251881Speter"#   [3] USER         (the user creating the lock)"                          NL
570251881Speter"#   [4] COMMENT      (the comment of the lock)"                             NL
571251881Speter"#   [5] STEAL-LOCK   (1 if the user is trying to steal the lock, else 0)"   NL
572251881Speter"#"                                                                          NL
573251881Speter"# If the hook program outputs anything on stdout, the output string will"   NL
574251881Speter"# be used as the lock token for this lock operation.  If you choose to use" NL
575251881Speter"# this feature, you must guarantee the tokens generated are unique across"  NL
576251881Speter"# the repository each time."                                                NL
577251881Speter"#"                                                                          NL
578251881Speter"# The default working directory for the invocation is undefined, so"        NL
579251881Speter"# the program should set one explicitly if it cares."                       NL
580251881Speter"#"                                                                          NL
581251881Speter"# If the hook program exits with success, the lock is created; but"         NL
582251881Speter"# if it exits with failure (non-zero), the lock action is aborted"          NL
583251881Speter"# and STDERR is returned to the client."                                    NL
584251881Speter""                                                                           NL
585251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
586251881Speter"# invoke other programs to do the real work, though it may do the"          NL
587251881Speter"# work itself too."                                                         NL
588251881Speter"#"                                                                          NL
589251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
590251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
591251881Speter"# have filesystem-level permission to access the repository."               NL
592251881Speter"#"                                                                          NL
593251881Speter"# On a Windows system, you should name the hook program"                    NL
594251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
595251881Speter"# but the basic idea is the same."                                          NL
596251881Speter"#"                                                                          NL
597251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter:"          NL
598251881Speter""                                                                           NL
599251881Speter"REPOS=\"$1\""                                                               NL
600251881Speter"PATH=\"$2\""                                                                NL
601251881Speter"USER=\"$3\""                                                                NL
602251881Speter"COMMENT=\"$4\""                                                             NL
603251881Speter"STEAL=\"$5\""                                                               NL
604251881Speter""                                                                           NL
605251881Speter"# If a lock exists and is owned by a different person, don't allow it"      NL
606251881Speter"# to be stolen (e.g., with 'svn lock --force ...')."                        NL
607251881Speter""                                                                           NL
608251881Speter"# (Maybe this script could send email to the lock owner?)"                  NL
609251881Speter"SVNLOOK=" SVN_BINDIR "/svnlook"                                             NL
610251881Speter"GREP=/bin/grep"                                                             NL
611251881Speter"SED=/bin/sed"                                                               NL
612251881Speter""                                                                           NL
613251881Speter"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\"                        NL
614251881Speter"            $GREP '^Owner: ' | $SED 's/Owner: //'`"                         NL
615251881Speter""                                                                           NL
616251881Speter"# If we get no result from svnlook, there's no lock, allow the lock to"     NL
617251881Speter"# happen:"                                                                  NL
618251881Speter"if [ \"$LOCK_OWNER\" = \"\" ]; then"                                        NL
619251881Speter"  exit 0"                                                                   NL
620251881Speter"fi"                                                                         NL
621251881Speter""                                                                           NL
622251881Speter"# If the person locking matches the lock's owner, allow the lock to"        NL
623251881Speter"# happen:"                                                                  NL
624251881Speter"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then"                                   NL
625251881Speter"  exit 0"                                                                   NL
626251881Speter"fi"                                                                         NL
627251881Speter""                                                                           NL
628251881Speter"# Otherwise, we've got an owner mismatch, so return failure:"               NL
629251881Speter"echo \"Error: $PATH already locked by ${LOCK_OWNER}.\" 1>&2"                NL
630251881Speter"exit 1"                                                                     NL;
631251881Speter
632251881Speter#undef SCRIPT_NAME
633251881Speter
634251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
635251881Speter              "Creating pre-lock hook");
636251881Speter
637251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
638251881Speter  }  /* end pre-lock hook */
639251881Speter
640251881Speter
641251881Speter  /* Pre-unlock hook. */
642251881Speter  {
643251881Speter    this_path = apr_psprintf(pool, "%s%s",
644251881Speter                             svn_repos_pre_unlock_hook(repos, pool),
645251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
646251881Speter
647251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_UNLOCK
648251881Speter
649251881Speter    contents =
650251881Speter"#!/bin/sh"                                                                  NL
651251881Speter""                                                                           NL
652251881Speter"# PRE-UNLOCK HOOK"                                                          NL
653251881Speter"#"                                                                          NL
654251881Speter"# The pre-unlock hook is invoked before an exclusive lock is"               NL
655251881Speter"# destroyed.  Subversion runs this hook by invoking a program "             NL
656251881Speter"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which"      NL
657251881Speter"# this file is a template), with the following ordered arguments:"          NL
658251881Speter"#"                                                                          NL
659251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
660251881Speter"#   [2] PATH         (the path in the repository about to be unlocked)"     NL
661251881Speter"#   [3] USER         (the user destroying the lock)"                        NL
662251881Speter"#   [4] TOKEN        (the lock token to be destroyed)"                      NL
663251881Speter"#   [5] BREAK-UNLOCK (1 if the user is breaking the lock, else 0)"          NL
664251881Speter"#"                                                                          NL
665251881Speter"# The default working directory for the invocation is undefined, so"        NL
666251881Speter"# the program should set one explicitly if it cares."                       NL
667251881Speter"#"                                                                          NL
668251881Speter"# If the hook program exits with success, the lock is destroyed; but"       NL
669251881Speter"# if it exits with failure (non-zero), the unlock action is aborted"        NL
670251881Speter"# and STDERR is returned to the client."                                    NL
671251881Speter""                                                                           NL
672251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
673251881Speter"# invoke other programs to do the real work, though it may do the"          NL
674251881Speter"# work itself too."                                                         NL
675251881Speter"#"                                                                          NL
676251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
677251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
678251881Speter"# have filesystem-level permission to access the repository."               NL
679251881Speter"#"                                                                          NL
680251881Speter"# On a Windows system, you should name the hook program"                    NL
681251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
682251881Speter"# but the basic idea is the same."                                          NL
683251881Speter"#"                                                                          NL
684251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter:"          NL
685251881Speter""                                                                           NL
686251881Speter"REPOS=\"$1\""                                                               NL
687251881Speter"PATH=\"$2\""                                                                NL
688251881Speter"USER=\"$3\""                                                                NL
689251881Speter"TOKEN=\"$4\""                                                               NL
690251881Speter"BREAK=\"$5\""                                                               NL
691251881Speter""                                                                           NL
692251881Speter"# If a lock is owned by a different person, don't allow it be broken."      NL
693251881Speter"# (Maybe this script could send email to the lock owner?)"                  NL
694251881Speter""                                                                           NL
695251881Speter"SVNLOOK=" SVN_BINDIR "/svnlook"                                             NL
696251881Speter"GREP=/bin/grep"                                                             NL
697251881Speter"SED=/bin/sed"                                                               NL
698251881Speter""                                                                           NL
699251881Speter"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\"                        NL
700251881Speter"            $GREP '^Owner: ' | $SED 's/Owner: //'`"                         NL
701251881Speter""                                                                           NL
702251881Speter"# If we get no result from svnlook, there's no lock, return success:"       NL
703251881Speter"if [ \"$LOCK_OWNER\" = \"\" ]; then"                                        NL
704251881Speter"  exit 0"                                                                   NL
705251881Speter"fi"                                                                         NL
706251881Speter""                                                                           NL
707251881Speter"# If the person unlocking matches the lock's owner, return success:"        NL
708251881Speter"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then"                                   NL
709251881Speter"  exit 0"                                                                   NL
710251881Speter"fi"                                                                         NL
711251881Speter""                                                                           NL
712251881Speter"# Otherwise, we've got an owner mismatch, so return failure:"               NL
713251881Speter"echo \"Error: $PATH locked by ${LOCK_OWNER}.\" 1>&2"                        NL
714251881Speter"exit 1"                                                                     NL;
715251881Speter
716251881Speter#undef SCRIPT_NAME
717251881Speter
718251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
719251881Speter              "Creating pre-unlock hook");
720251881Speter
721251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
722251881Speter  }  /* end pre-unlock hook */
723251881Speter
724251881Speter
725251881Speter
726251881Speter  /* Post-commit hook. */
727251881Speter  {
728251881Speter    this_path = apr_psprintf(pool, "%s%s",
729251881Speter                             svn_repos_post_commit_hook(repos, pool),
730251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
731251881Speter
732251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_COMMIT
733251881Speter
734251881Speter    contents =
735251881Speter"#!/bin/sh"                                                                  NL
736251881Speter""                                                                           NL
737251881Speter"# POST-COMMIT HOOK"                                                         NL
738251881Speter"#"                                                                          NL
739251881Speter"# The post-commit hook is invoked after a commit.  Subversion runs"         NL
740251881Speter"# this hook by invoking a program (script, executable, binary, etc.)"       NL
741251881Speter"# named '"SCRIPT_NAME"' (for which this file is a template) with the "      NL
742251881Speter"# following ordered arguments:"                                             NL
743251881Speter"#"                                                                          NL
744251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
745251881Speter"#   [2] REV          (the number of the revision just committed)"           NL
746251881Speter"#   [3] TXN-NAME     (the name of the transaction that has become REV)"     NL
747251881Speter"#"                                                                          NL
748251881Speter"# The default working directory for the invocation is undefined, so"        NL
749251881Speter"# the program should set one explicitly if it cares."                       NL
750251881Speter"#"                                                                          NL
751251881Speter"# Because the commit has already completed and cannot be undone,"           NL
752251881Speter"# the exit code of the hook program is ignored.  The hook program"          NL
753251881Speter"# can use the 'svnlook' utility to help it examine the"                     NL
754251881Speter"# newly-committed tree."                                                    NL
755251881Speter"#"                                                                          NL
756251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
757251881Speter"# invoke other programs to do the real work, though it may do the"          NL
758251881Speter"# work itself too."                                                         NL
759251881Speter"#"                                                                          NL
760251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
761251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
762251881Speter"# have filesystem-level permission to access the repository."               NL
763251881Speter"#"                                                                          NL
764251881Speter"# On a Windows system, you should name the hook program"                    NL
765251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
766251881Speter"# but the basic idea is the same."                                          NL
767251881Speter"# "                                                                         NL
768251881SpeterHOOKS_ENVIRONMENT_TEXT
769251881Speter"# "                                                                         NL
770251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter."          NL
771251881SpeterPREWRITTEN_HOOKS_TEXT
772251881Speter""                                                                           NL
773251881Speter""                                                                           NL
774251881Speter"REPOS=\"$1\""                                                               NL
775251881Speter"REV=\"$2\""                                                                 NL
776251881Speter"TXN_NAME=\"$3\""                                                            NL
777251881Speter                                                                             NL
778251881Speter"mailer.py commit \"$REPOS\" \"$REV\" /path/to/mailer.conf"                  NL;
779251881Speter
780251881Speter#undef SCRIPT_NAME
781251881Speter
782251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
783251881Speter              _("Creating post-commit hook"));
784251881Speter
785251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
786251881Speter  } /* end post-commit hook */
787251881Speter
788251881Speter
789251881Speter  /* Post-lock hook. */
790251881Speter  {
791251881Speter    this_path = apr_psprintf(pool, "%s%s",
792251881Speter                             svn_repos_post_lock_hook(repos, pool),
793251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
794251881Speter
795251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_LOCK
796251881Speter
797251881Speter    contents =
798251881Speter"#!/bin/sh"                                                                  NL
799251881Speter""                                                                           NL
800251881Speter"# POST-LOCK HOOK"                                                           NL
801251881Speter"#"                                                                          NL
802251881Speter"# The post-lock hook is run after a path is locked.  Subversion runs"       NL
803251881Speter"# this hook by invoking a program (script, executable, binary, etc.)"       NL
804251881Speter"# named '"SCRIPT_NAME"' (for which this file is a template) with the "      NL
805251881Speter"# following ordered arguments:"                                             NL
806251881Speter"#"                                                                          NL
807251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
808251881Speter"#   [2] USER         (the user who created the lock)"                       NL
809251881Speter"#"                                                                          NL
810251881Speter"# The paths that were just locked are passed to the hook via STDIN (as"     NL
811251881Speter"# of Subversion 1.2, only one path is passed per invocation, but the"       NL
812251881Speter"# plan is to pass all locked paths at once, so the hook program"            NL
813251881Speter"# should be written accordingly)."                                          NL
814251881Speter"#"                                                                          NL
815251881Speter"# The default working directory for the invocation is undefined, so"        NL
816251881Speter"# the program should set one explicitly if it cares."                       NL
817251881Speter"#"                                                                          NL
818251881Speter"# Because the lock has already been created and cannot be undone,"          NL
819251881Speter"# the exit code of the hook program is ignored.  The hook program"          NL
820251881Speter"# can use the 'svnlook' utility to help it examine the"                     NL
821251881Speter"# newly-created lock."                                                      NL
822251881Speter"#"                                                                          NL
823251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
824251881Speter"# invoke other programs to do the real work, though it may do the"          NL
825251881Speter"# work itself too."                                                         NL
826251881Speter"#"                                                                          NL
827251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
828251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
829251881Speter"# have filesystem-level permission to access the repository."               NL
830251881Speter"#"                                                                          NL
831251881Speter"# On a Windows system, you should name the hook program"                    NL
832251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
833251881Speter"# but the basic idea is the same."                                          NL
834251881Speter"# "                                                                         NL
835251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter:"          NL
836251881Speter""                                                                           NL
837251881Speter"REPOS=\"$1\""                                                               NL
838251881Speter"USER=\"$2\""                                                                NL
839251881Speter""                                                                           NL
840251881Speter"# Send email to interested parties, let them know a lock was created:"      NL
841251881Speter"mailer.py lock \"$REPOS\" \"$USER\" /path/to/mailer.conf"                   NL;
842251881Speter
843251881Speter#undef SCRIPT_NAME
844251881Speter
845251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
846251881Speter              "Creating post-lock hook");
847251881Speter
848251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
849251881Speter  } /* end post-lock hook */
850251881Speter
851251881Speter
852251881Speter  /* Post-unlock hook. */
853251881Speter  {
854251881Speter    this_path = apr_psprintf(pool, "%s%s",
855251881Speter                             svn_repos_post_unlock_hook(repos, pool),
856251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
857251881Speter
858251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_UNLOCK
859251881Speter
860251881Speter    contents =
861251881Speter"#!/bin/sh"                                                                  NL
862251881Speter""                                                                           NL
863251881Speter"# POST-UNLOCK HOOK"                                                         NL
864251881Speter"#"                                                                          NL
865251881Speter"# The post-unlock hook runs after a path is unlocked.  Subversion runs"     NL
866251881Speter"# this hook by invoking a program (script, executable, binary, etc.)"       NL
867251881Speter"# named '"SCRIPT_NAME"' (for which this file is a template) with the "      NL
868251881Speter"# following ordered arguments:"                                             NL
869251881Speter"#"                                                                          NL
870251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
871251881Speter"#   [2] USER         (the user who destroyed the lock)"                     NL
872251881Speter"#"                                                                          NL
873251881Speter"# The paths that were just unlocked are passed to the hook via STDIN"       NL
874251881Speter"# (as of Subversion 1.2, only one path is passed per invocation, but"       NL
875251881Speter"# the plan is to pass all unlocked paths at once, so the hook program"      NL
876251881Speter"# should be written accordingly)."                                          NL
877251881Speter"#"                                                                          NL
878251881Speter"# The default working directory for the invocation is undefined, so"        NL
879251881Speter"# the program should set one explicitly if it cares."                       NL
880251881Speter"#"                                                                          NL
881251881Speter"# Because the lock has already been destroyed and cannot be undone,"        NL
882251881Speter"# the exit code of the hook program is ignored."                            NL
883251881Speter"#"                                                                          NL
884251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
885251881Speter"# invoke other programs to do the real work, though it may do the"          NL
886251881Speter"# work itself too."                                                         NL
887251881Speter"#"                                                                          NL
888251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
889251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
890251881Speter"# have filesystem-level permission to access the repository."               NL
891251881Speter"#"                                                                          NL
892251881Speter"# On a Windows system, you should name the hook program"                    NL
893251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
894251881Speter"# but the basic idea is the same."                                          NL
895251881Speter"# "                                                                         NL
896251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter:"          NL
897251881Speter""                                                                           NL
898251881Speter"REPOS=\"$1\""                                                               NL
899251881Speter"USER=\"$2\""                                                                NL
900251881Speter""                                                                           NL
901251881Speter"# Send email to interested parties, let them know a lock was removed:"      NL
902251881Speter"mailer.py unlock \"$REPOS\" \"$USER\" /path/to/mailer.conf"                 NL;
903251881Speter
904251881Speter#undef SCRIPT_NAME
905251881Speter
906251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
907251881Speter              "Creating post-unlock hook");
908251881Speter
909251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
910251881Speter  } /* end post-unlock hook */
911251881Speter
912251881Speter
913251881Speter  /* Post-revprop-change hook. */
914251881Speter  {
915251881Speter    this_path = apr_psprintf(pool, "%s%s",
916251881Speter                             svn_repos_post_revprop_change_hook(repos, pool),
917251881Speter                             SVN_REPOS__HOOK_DESC_EXT);
918251881Speter
919251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_REVPROP_CHANGE
920251881Speter
921251881Speter    contents =
922251881Speter"#!/bin/sh"                                                                  NL
923251881Speter""                                                                           NL
924251881Speter"# POST-REVPROP-CHANGE HOOK"                                                 NL
925251881Speter"#"                                                                          NL
926251881Speter"# The post-revprop-change hook is invoked after a revision property"        NL
927251881Speter"# has been added, modified or deleted.  Subversion runs this hook by"       NL
928251881Speter"# invoking a program (script, executable, binary, etc.) named"              NL
929251881Speter"# '"SCRIPT_NAME"' (for which this file is a template), with the"            NL
930251881Speter"# following ordered arguments:"                                             NL
931251881Speter"#"                                                                          NL
932251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
933251881Speter"#   [2] REV          (the revision that was tweaked)"                       NL
934251881Speter"#   [3] USER         (the username of the person tweaking the property)"    NL
935251881Speter"#   [4] PROPNAME     (the property that was changed)"                       NL
936251881Speter"#   [5] ACTION       (the property was 'A'dded, 'M'odified, or 'D'eleted)"  NL
937251881Speter"#"                                                                          NL
938251881Speter"#   [STDIN] PROPVAL  ** the old property value is passed via STDIN."        NL
939251881Speter"#"                                                                          NL
940251881Speter"# Because the propchange has already completed and cannot be undone,"       NL
941251881Speter"# the exit code of the hook program is ignored.  The hook program"          NL
942251881Speter"# can use the 'svnlook' utility to help it examine the"                     NL
943251881Speter"# new property value."                                                      NL
944251881Speter"#"                                                                          NL
945251881Speter"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
946251881Speter"# invoke other programs to do the real work, though it may do the"          NL
947251881Speter"# work itself too."                                                         NL
948251881Speter"#"                                                                          NL
949251881Speter"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will"     NL
950251881Speter"# invoke it (typically the user httpd runs as), and that user must"         NL
951251881Speter"# have filesystem-level permission to access the repository."               NL
952251881Speter"#"                                                                          NL
953251881Speter"# On a Windows system, you should name the hook program"                    NL
954251881Speter"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe',"                              NL
955251881Speter"# but the basic idea is the same."                                          NL
956251881Speter"# "                                                                         NL
957251881SpeterHOOKS_ENVIRONMENT_TEXT
958251881Speter"# "                                                                         NL
959251881Speter"# Here is an example hook script, for a Unix /bin/sh interpreter."          NL
960251881SpeterPREWRITTEN_HOOKS_TEXT
961251881Speter""                                                                           NL
962251881Speter""                                                                           NL
963251881Speter"REPOS=\"$1\""                                                               NL
964251881Speter"REV=\"$2\""                                                                 NL
965251881Speter"USER=\"$3\""                                                                NL
966251881Speter"PROPNAME=\"$4\""                                                            NL
967251881Speter"ACTION=\"$5\""                                                              NL
968251881Speter""                                                                           NL
969251881Speter"mailer.py propchange2 \"$REPOS\" \"$REV\" \"$USER\" \"$PROPNAME\" "
970251881Speter"\"$ACTION\" /path/to/mailer.conf"                                           NL;
971251881Speter
972251881Speter#undef SCRIPT_NAME
973251881Speter
974251881Speter    SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
975251881Speter              _("Creating post-revprop-change hook"));
976251881Speter
977251881Speter    SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
978251881Speter  } /* end post-revprop-change hook */
979251881Speter
980251881Speter  return SVN_NO_ERROR;
981251881Speter}
982251881Speter
983251881Speterstatic svn_error_t *
984251881Spetercreate_conf(svn_repos_t *repos, apr_pool_t *pool)
985251881Speter{
986251881Speter  SVN_ERR_W(create_repos_dir(repos->conf_path, pool),
987251881Speter            _("Creating conf directory"));
988251881Speter
989251881Speter  /* Write a default template for svnserve.conf. */
990251881Speter  {
991251881Speter    static const char * const svnserve_conf_contents =
992251881Speter"### This file controls the configuration of the svnserve daemon, if you"    NL
993251881Speter"### use it to allow access to this repository.  (If you only allow"         NL
994251881Speter"### access through http: and/or file: URLs, then this file is"              NL
995251881Speter"### irrelevant.)"                                                           NL
996251881Speter""                                                                           NL
997251881Speter"### Visit http://subversion.apache.org/ for more information."              NL
998251881Speter""                                                                           NL
999251881Speter"[general]"                                                                  NL
1000251881Speter"### The anon-access and auth-access options control access to the"          NL
1001251881Speter"### repository for unauthenticated (a.k.a. anonymous) users and"            NL
1002251881Speter"### authenticated users, respectively."                                     NL
1003251881Speter"### Valid values are \"write\", \"read\", and \"none\"."                    NL
1004251881Speter"### Setting the value to \"none\" prohibits both reading and writing;"      NL
1005251881Speter"### \"read\" allows read-only access, and \"write\" allows complete "       NL
1006251881Speter"### read/write access to the repository."                                   NL
1007251881Speter"### The sample settings below are the defaults and specify that anonymous"  NL
1008251881Speter"### users have read-only access to the repository, while authenticated"     NL
1009251881Speter"### users have read and write access to the repository."                    NL
1010251881Speter"# anon-access = read"                                                       NL
1011251881Speter"# auth-access = write"                                                      NL
1012251881Speter"### The password-db option controls the location of the password"           NL
1013251881Speter"### database file.  Unless you specify a path starting with a /,"           NL
1014251881Speter"### the file's location is relative to the directory containing"            NL
1015251881Speter"### this configuration file."                                               NL
1016251881Speter"### If SASL is enabled (see below), this file will NOT be used."            NL
1017251881Speter"### Uncomment the line below to use the default password file."             NL
1018251881Speter"# password-db = passwd"                                                     NL
1019251881Speter"### The authz-db option controls the location of the authorization"         NL
1020251881Speter"### rules for path-based access control.  Unless you specify a path"        NL
1021251881Speter"### starting with a /, the file's location is relative to the"              NL
1022251881Speter"### directory containing this file.  The specified path may be a"           NL
1023251881Speter"### repository relative URL (^/) or an absolute file:// URL to a text"      NL
1024251881Speter"### file in a Subversion repository.  If you don't specify an authz-db,"    NL
1025251881Speter"### no path-based access control is done."                                  NL
1026251881Speter"### Uncomment the line below to use the default authorization file."        NL
1027251881Speter"# authz-db = " SVN_REPOS__CONF_AUTHZ                                        NL
1028251881Speter"### The groups-db option controls the location of the groups file."         NL
1029251881Speter"### Unless you specify a path starting with a /, the file's location is"    NL
1030251881Speter"### relative to the directory containing this file.  The specified path"    NL
1031251881Speter"### may be a repository relative URL (^/) or an absolute file:// URL to a"  NL
1032251881Speter"### text file in a Subversion repository."                                  NL
1033251881Speter"# groups-db = " SVN_REPOS__CONF_GROUPS                                      NL
1034251881Speter"### This option specifies the authentication realm of the repository."      NL
1035251881Speter"### If two repositories have the same authentication realm, they should"    NL
1036251881Speter"### have the same password database, and vice versa.  The default realm"    NL
1037251881Speter"### is repository's uuid."                                                  NL
1038251881Speter"# realm = My First Repository"                                              NL
1039251881Speter"### The force-username-case option causes svnserve to case-normalize"       NL
1040251881Speter"### usernames before comparing them against the authorization rules in the" NL
1041251881Speter"### authz-db file configured above.  Valid values are \"upper\" (to upper-" NL
1042251881Speter"### case the usernames), \"lower\" (to lowercase the usernames), and"       NL
1043251881Speter"### \"none\" (to compare usernames as-is without case conversion, which"    NL
1044251881Speter"### is the default behavior)."                                              NL
1045251881Speter"# force-username-case = none"                                               NL
1046251881Speter"### The hooks-env options specifies a path to the hook script environment " NL
1047251881Speter"### configuration file. This option overrides the per-repository default"   NL
1048251881Speter"### and can be used to configure the hook script environment for multiple " NL
1049251881Speter"### repositories in a single file, if an absolute path is specified."       NL
1050251881Speter"### Unless you specify an absolute path, the file's location is relative"   NL
1051251881Speter"### to the directory containing this file."                                 NL
1052251881Speter"# hooks-env = " SVN_REPOS__CONF_HOOKS_ENV                                   NL
1053251881Speter""                                                                           NL
1054251881Speter"[sasl]"                                                                     NL
1055251881Speter"### This option specifies whether you want to use the Cyrus SASL"           NL
1056251881Speter"### library for authentication. Default is false."                          NL
1057251881Speter"### This section will be ignored if svnserve is not built with Cyrus"       NL
1058251881Speter"### SASL support; to check, run 'svnserve --version' and look for a line"   NL
1059251881Speter"### reading 'Cyrus SASL authentication is available.'"                      NL
1060251881Speter"# use-sasl = true"                                                          NL
1061251881Speter"### These options specify the desired strength of the security layer"       NL
1062251881Speter"### that you want SASL to provide. 0 means no encryption, 1 means"          NL
1063251881Speter"### integrity-checking only, values larger than 1 are correlated"           NL
1064251881Speter"### to the effective key length for encryption (e.g. 128 means 128-bit"     NL
1065251881Speter"### encryption). The values below are the defaults."                        NL
1066251881Speter"# min-encryption = 0"                                                       NL
1067251881Speter"# max-encryption = 256"                                                     NL;
1068251881Speter
1069251881Speter    SVN_ERR_W(svn_io_file_create(svn_repos_svnserve_conf(repos, pool),
1070251881Speter                                 svnserve_conf_contents, pool),
1071251881Speter              _("Creating svnserve.conf file"));
1072251881Speter  }
1073251881Speter
1074251881Speter  {
1075251881Speter    static const char * const passwd_contents =
1076251881Speter"### This file is an example password file for svnserve."                    NL
1077251881Speter"### Its format is similar to that of svnserve.conf. As shown in the"        NL
1078251881Speter"### example below it contains one section labelled [users]."                NL
1079251881Speter"### The name and password for each user follow, one account per line."      NL
1080251881Speter""                                                                           NL
1081251881Speter"[users]"                                                                    NL
1082251881Speter"# harry = harryssecret"                                                     NL
1083251881Speter"# sally = sallyssecret"                                                     NL;
1084251881Speter
1085251881Speter    SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
1086251881Speter                                                 SVN_REPOS__CONF_PASSWD,
1087251881Speter                                                 pool),
1088251881Speter                                 passwd_contents, pool),
1089251881Speter              _("Creating passwd file"));
1090251881Speter  }
1091251881Speter
1092251881Speter  {
1093251881Speter    static const char * const authz_contents =
1094251881Speter"### This file is an example authorization file for svnserve."               NL
1095251881Speter"### Its format is identical to that of mod_authz_svn authorization"         NL
1096251881Speter"### files."                                                                 NL
1097251881Speter"### As shown below each section defines authorizations for the path and"    NL
1098251881Speter"### (optional) repository specified by the section name."                   NL
1099251881Speter"### The authorizations follow. An authorization line can refer to:"         NL
1100251881Speter"###  - a single user,"                                                      NL
1101251881Speter"###  - a group of users defined in a special [groups] section,"             NL
1102251881Speter"###  - an alias defined in a special [aliases] section,"                    NL
1103251881Speter"###  - all authenticated users, using the '$authenticated' token,"          NL
1104251881Speter"###  - only anonymous users, using the '$anonymous' token,"                 NL
1105251881Speter"###  - anyone, using the '*' wildcard."                                     NL
1106251881Speter"###"                                                                        NL
1107251881Speter"### A match can be inverted by prefixing the rule with '~'. Rules can"      NL
1108251881Speter"### grant read ('r') access, read-write ('rw') access, or no access"        NL
1109251881Speter"### ('')."                                                                  NL
1110251881Speter""                                                                           NL
1111251881Speter"[aliases]"                                                                  NL
1112251881Speter"# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average"  NL
1113251881Speter""                                                                           NL
1114251881Speter"[groups]"                                                                   NL
1115251881Speter"# harry_and_sally = harry,sally"                                            NL
1116251881Speter"# harry_sally_and_joe = harry,sally,&joe"                                   NL
1117251881Speter""                                                                           NL
1118251881Speter"# [/foo/bar]"                                                               NL
1119251881Speter"# harry = rw"                                                               NL
1120251881Speter"# &joe = r"                                                                 NL
1121251881Speter"# * ="                                                                      NL
1122251881Speter""                                                                           NL
1123251881Speter"# [repository:/baz/fuz]"                                                    NL
1124251881Speter"# @harry_and_sally = rw"                                                    NL
1125251881Speter"# * = r"                                                                    NL;
1126251881Speter
1127251881Speter    SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
1128251881Speter                                                 SVN_REPOS__CONF_AUTHZ,
1129251881Speter                                                 pool),
1130251881Speter                                 authz_contents, pool),
1131251881Speter              _("Creating authz file"));
1132251881Speter  }
1133251881Speter
1134251881Speter  {
1135251881Speter    static const char * const hooks_env_contents =
1136251881Speter"### This file is an example hook script environment configuration file."    NL
1137251881Speter"### Hook scripts run in an empty environment by default."                   NL
1138251881Speter"### As shown below each section defines environment variables for a"        NL
1139251881Speter"### particular hook script. The [default] section defines environment"      NL
1140251881Speter"### variables for all hook scripts, unless overridden by a hook-specific"   NL
1141251881Speter"### section."                                                               NL
1142251881Speter""                                                                           NL
1143251881Speter"### This example configures a UTF-8 locale for all hook scripts, so that "  NL
1144251881Speter"### special characters, such as umlauts, may be printed to stderr."         NL
1145251881Speter"### If UTF-8 is used with a mod_dav_svn server, the SVNUseUTF8 option must" NL
1146251881Speter"### also be set to 'yes' in httpd.conf."                                    NL
1147251881Speter"### With svnserve, the LANG environment variable of the svnserve process"   NL
1148251881Speter"### must be set to the same value as given here."                           NL
1149251881Speter"[default]"                                                                  NL
1150251881Speter"LANG = en_US.UTF-8"                                                         NL
1151251881Speter""                                                                           NL
1152251881Speter"### This sets the PATH environment variable for the pre-commit hook."       NL
1153251881Speter"[pre-commit]"                                                               NL
1154251881Speter"PATH = /usr/local/bin:/usr/bin:/usr/sbin"                                   NL;
1155251881Speter
1156251881Speter    SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
1157251881Speter                                                 SVN_REPOS__CONF_HOOKS_ENV \
1158251881Speter                                                 SVN_REPOS__HOOK_DESC_EXT,
1159251881Speter                                                 pool),
1160251881Speter                                 hooks_env_contents, pool),
1161251881Speter              _("Creating hooks-env file"));
1162251881Speter  }
1163251881Speter
1164251881Speter  return SVN_NO_ERROR;
1165251881Speter}
1166251881Speter
1167251881Spetersvn_error_t *
1168251881Spetersvn_repos_hooks_setenv(svn_repos_t *repos,
1169251881Speter                       const char *hooks_env_path,
1170251881Speter                       apr_pool_t *scratch_pool)
1171251881Speter{
1172251881Speter  if (hooks_env_path == NULL)
1173251881Speter    repos->hooks_env_path = svn_dirent_join(repos->conf_path,
1174251881Speter                                            SVN_REPOS__CONF_HOOKS_ENV,
1175251881Speter                                            repos->pool);
1176251881Speter  else if (!svn_dirent_is_absolute(hooks_env_path))
1177251881Speter    repos->hooks_env_path = svn_dirent_join(repos->conf_path,
1178251881Speter                                            hooks_env_path,
1179251881Speter                                            repos->pool);
1180251881Speter  else
1181251881Speter    repos->hooks_env_path = apr_pstrdup(repos->pool, hooks_env_path);
1182251881Speter
1183251881Speter  return SVN_NO_ERROR;
1184251881Speter}
1185251881Speter
1186251881Speter/* Allocate and return a new svn_repos_t * object, initializing the
1187251881Speter   directory pathname members based on PATH, and initializing the
1188251881Speter   REPOSITORY_CAPABILITIES member.
1189251881Speter   The members FS, FORMAT, and FS_TYPE are *not* initialized (they are null),
1190251881Speter   and it is the caller's responsibility to fill them in if needed.  */
1191251881Speterstatic svn_repos_t *
1192251881Spetercreate_svn_repos_t(const char *path, apr_pool_t *pool)
1193251881Speter{
1194251881Speter  svn_repos_t *repos = apr_pcalloc(pool, sizeof(*repos));
1195251881Speter
1196251881Speter  repos->path = apr_pstrdup(pool, path);
1197251881Speter  repos->db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
1198251881Speter  repos->conf_path = svn_dirent_join(path, SVN_REPOS__CONF_DIR, pool);
1199251881Speter  repos->hook_path = svn_dirent_join(path, SVN_REPOS__HOOK_DIR, pool);
1200251881Speter  repos->lock_path = svn_dirent_join(path, SVN_REPOS__LOCK_DIR, pool);
1201251881Speter  repos->hooks_env_path = NULL;
1202251881Speter  repos->repository_capabilities = apr_hash_make(pool);
1203251881Speter  repos->pool = pool;
1204251881Speter
1205251881Speter  return repos;
1206251881Speter}
1207251881Speter
1208251881Speter
1209251881Speterstatic svn_error_t *
1210251881Spetercreate_repos_structure(svn_repos_t *repos,
1211251881Speter                       const char *path,
1212251881Speter                       apr_hash_t *fs_config,
1213251881Speter                       apr_pool_t *pool)
1214251881Speter{
1215251881Speter  /* Create the top-level repository directory. */
1216251881Speter  SVN_ERR_W(create_repos_dir(path, pool),
1217251881Speter            _("Could not create top-level directory"));
1218251881Speter
1219251881Speter  /* Create the DAV sandbox directory if pre-1.4 or pre-1.5-compatible. */
1220251881Speter  if (fs_config
1221251881Speter      && (svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)
1222251881Speter          || svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)))
1223251881Speter    {
1224251881Speter      const char *dav_path = svn_dirent_join(repos->path,
1225251881Speter                                             SVN_REPOS__DAV_DIR, pool);
1226251881Speter      SVN_ERR_W(create_repos_dir(dav_path, pool),
1227251881Speter                _("Creating DAV sandbox dir"));
1228251881Speter    }
1229251881Speter
1230251881Speter  /* Create the lock directory.  */
1231251881Speter  SVN_ERR(create_locks(repos, pool));
1232251881Speter
1233251881Speter  /* Create the hooks directory.  */
1234251881Speter  SVN_ERR(create_hooks(repos, pool));
1235251881Speter
1236251881Speter  /* Create the conf directory.  */
1237251881Speter  SVN_ERR(create_conf(repos, pool));
1238251881Speter
1239251881Speter  /* Write the top-level README file. */
1240251881Speter  {
1241251881Speter    const char * const readme_header =
1242251881Speter      "This is a Subversion repository; use the 'svnadmin' and 'svnlook' "   NL
1243251881Speter      "tools to examine it.  Do not add, delete, or modify files here "      NL
1244251881Speter      "unless you know how to avoid corrupting the repository."              NL
1245251881Speter      ""                                                                     NL;
1246251881Speter    const char * const readme_bdb_insert =
1247251881Speter      "The directory \"" SVN_REPOS__DB_DIR "\" contains a Berkeley DB environment."  NL
1248251881Speter      "you may need to tweak the values in \"" SVN_REPOS__DB_DIR "/DB_CONFIG\" to match the"  NL
1249251881Speter      "requirements of your site."                                           NL
1250251881Speter      ""                                                                     NL;
1251251881Speter    const char * const readme_footer =
1252251881Speter      "Visit http://subversion.apache.org/ for more information."            NL;
1253251881Speter    apr_file_t *f;
1254251881Speter    apr_size_t written;
1255251881Speter
1256251881Speter    SVN_ERR(svn_io_file_open(&f,
1257251881Speter                             svn_dirent_join(path, SVN_REPOS__README, pool),
1258251881Speter                             (APR_WRITE | APR_CREATE | APR_EXCL),
1259251881Speter                             APR_OS_DEFAULT, pool));
1260251881Speter
1261251881Speter    SVN_ERR(svn_io_file_write_full(f, readme_header, strlen(readme_header),
1262251881Speter                                   &written, pool));
1263251881Speter    if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
1264251881Speter      SVN_ERR(svn_io_file_write_full(f, readme_bdb_insert,
1265251881Speter                                     strlen(readme_bdb_insert),
1266251881Speter                                     &written, pool));
1267251881Speter    SVN_ERR(svn_io_file_write_full(f, readme_footer, strlen(readme_footer),
1268251881Speter                                   &written, pool));
1269251881Speter
1270251881Speter    return svn_io_file_close(f, pool);
1271251881Speter  }
1272251881Speter}
1273251881Speter
1274251881Speter
1275251881Speter/* There is, at present, nothing within the direct responsibility
1276251881Speter   of libsvn_repos which requires locking.  For historical compatibility
1277251881Speter   reasons, the BDB libsvn_fs backend does not do its own locking, expecting
1278251881Speter   libsvn_repos to do the locking for it.  Here we take care of that
1279251881Speter   backend-specific requirement.
1280251881Speter   The kind of lock is controlled by EXCLUSIVE and NONBLOCKING.
1281251881Speter   The lock is scoped to POOL.  */
1282251881Speterstatic svn_error_t *
1283251881Speterlock_repos(svn_repos_t *repos,
1284251881Speter           svn_boolean_t exclusive,
1285251881Speter           svn_boolean_t nonblocking,
1286251881Speter           apr_pool_t *pool)
1287251881Speter{
1288251881Speter  if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
1289251881Speter    {
1290251881Speter      svn_error_t *err;
1291251881Speter      const char *lockfile_path = svn_repos_db_lockfile(repos, pool);
1292251881Speter
1293251881Speter      err = svn_io_file_lock2(lockfile_path, exclusive, nonblocking, pool);
1294251881Speter      if (err != NULL && APR_STATUS_IS_EAGAIN(err->apr_err))
1295251881Speter        return svn_error_trace(err);
1296251881Speter      SVN_ERR_W(err, _("Error opening db lockfile"));
1297251881Speter    }
1298251881Speter  return SVN_NO_ERROR;
1299251881Speter}
1300251881Speter
1301251881Speter
1302251881Spetersvn_error_t *
1303251881Spetersvn_repos_create(svn_repos_t **repos_p,
1304251881Speter                 const char *path,
1305251881Speter                 const char *unused_1,
1306251881Speter                 const char *unused_2,
1307251881Speter                 apr_hash_t *config,
1308251881Speter                 apr_hash_t *fs_config,
1309251881Speter                 apr_pool_t *pool)
1310251881Speter{
1311251881Speter  svn_repos_t *repos;
1312251881Speter  svn_error_t *err;
1313251881Speter  const char *root_path;
1314251881Speter  const char *local_abspath;
1315251881Speter
1316251881Speter  /* Allocate a repository object, filling in the format we will create. */
1317251881Speter  repos = create_svn_repos_t(path, pool);
1318251881Speter  repos->format = SVN_REPOS__FORMAT_NUMBER;
1319251881Speter
1320251881Speter  /* Discover the type of the filesystem we are about to create. */
1321251881Speter  repos->fs_type = svn_hash__get_cstring(fs_config, SVN_FS_CONFIG_FS_TYPE,
1322251881Speter                                         DEFAULT_FS_TYPE);
1323251881Speter  if (svn_hash__get_bool(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, FALSE))
1324251881Speter    repos->format = SVN_REPOS__FORMAT_NUMBER_LEGACY;
1325251881Speter
1326251881Speter  /* Don't create a repository inside another repository. */
1327251881Speter  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
1328251881Speter  root_path = svn_repos_find_root_path(local_abspath, pool);
1329251881Speter  if (root_path != NULL)
1330251881Speter    {
1331251881Speter      if (strcmp(root_path, local_abspath) == 0)
1332251881Speter        return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1333251881Speter                                 _("'%s' is an existing repository"),
1334251881Speter                                 svn_dirent_local_style(root_path, pool));
1335251881Speter      else
1336251881Speter        return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1337251881Speter                                 _("'%s' is a subdirectory of an existing "
1338251881Speter                                   "repository " "rooted at '%s'"),
1339251881Speter                                 svn_dirent_local_style(local_abspath, pool),
1340251881Speter                                 svn_dirent_local_style(root_path, pool));
1341251881Speter    }
1342251881Speter
1343251881Speter  /* Create the various files and subdirectories for the repository. */
1344251881Speter  SVN_ERR_W(create_repos_structure(repos, path, fs_config, pool),
1345251881Speter            _("Repository creation failed"));
1346251881Speter
1347251881Speter  /* Lock if needed. */
1348251881Speter  SVN_ERR(lock_repos(repos, FALSE, FALSE, pool));
1349251881Speter
1350251881Speter  /* Create an environment for the filesystem. */
1351251881Speter  if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config, pool)))
1352251881Speter    {
1353251881Speter      /* If there was an error making the filesytem, e.g. unknown/supported
1354251881Speter       * filesystem type.  Clean up after ourselves.  Yes this is safe because
1355251881Speter       * create_repos_structure will fail if the path existed before we started
1356251881Speter       * so we can't accidentally remove a directory that previously existed.
1357251881Speter       */
1358251881Speter
1359251881Speter      return svn_error_trace(
1360251881Speter                   svn_error_compose_create(
1361251881Speter                        err,
1362251881Speter                        svn_io_remove_dir2(path, FALSE, NULL, NULL, pool)));
1363251881Speter    }
1364251881Speter
1365251881Speter  /* This repository is ready.  Stamp it with a format number. */
1366251881Speter  SVN_ERR(svn_io_write_version_file
1367251881Speter          (svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
1368251881Speter           repos->format, pool));
1369251881Speter
1370251881Speter  *repos_p = repos;
1371251881Speter  return SVN_NO_ERROR;
1372251881Speter}
1373251881Speter
1374251881Speter
1375251881Speter/* Check if @a path is the root of a repository by checking if the
1376251881Speter * path contains the expected files and directories.  Return TRUE
1377251881Speter * on errors (which would be permission errors, probably) so that
1378251881Speter * we the user will see them after we try to open the repository
1379251881Speter * for real.  */
1380251881Speterstatic svn_boolean_t
1381251881Spetercheck_repos_path(const char *path,
1382251881Speter                 apr_pool_t *pool)
1383251881Speter{
1384251881Speter  svn_node_kind_t kind;
1385251881Speter  svn_error_t *err;
1386251881Speter
1387251881Speter  err = svn_io_check_path(svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
1388251881Speter                          &kind, pool);
1389251881Speter  if (err)
1390251881Speter    {
1391251881Speter      svn_error_clear(err);
1392251881Speter      return TRUE;
1393251881Speter    }
1394251881Speter  if (kind != svn_node_file)
1395251881Speter    return FALSE;
1396251881Speter
1397251881Speter  /* Check the db/ subdir, but allow it to be a symlink (Subversion
1398251881Speter     works just fine if it's a symlink). */
1399251881Speter  err = svn_io_check_resolved_path
1400251881Speter    (svn_dirent_join(path, SVN_REPOS__DB_DIR, pool), &kind, pool);
1401251881Speter  if (err)
1402251881Speter    {
1403251881Speter      svn_error_clear(err);
1404251881Speter      return TRUE;
1405251881Speter    }
1406251881Speter  if (kind != svn_node_dir)
1407251881Speter    return FALSE;
1408251881Speter
1409251881Speter  return TRUE;
1410251881Speter}
1411251881Speter
1412251881Speter
1413251881Speter/* Verify that REPOS's format is suitable.
1414251881Speter   Use POOL for temporary allocation. */
1415251881Speterstatic svn_error_t *
1416251881Spetercheck_repos_format(svn_repos_t *repos,
1417251881Speter                   apr_pool_t *pool)
1418251881Speter{
1419251881Speter  int format;
1420251881Speter  const char *format_path;
1421251881Speter
1422251881Speter  format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, pool);
1423251881Speter  SVN_ERR(svn_io_read_version_file(&format, format_path, pool));
1424251881Speter
1425251881Speter  if (format != SVN_REPOS__FORMAT_NUMBER &&
1426251881Speter      format != SVN_REPOS__FORMAT_NUMBER_LEGACY)
1427251881Speter    {
1428251881Speter      return svn_error_createf
1429251881Speter        (SVN_ERR_REPOS_UNSUPPORTED_VERSION, NULL,
1430251881Speter         _("Expected repository format '%d' or '%d'; found format '%d'"),
1431251881Speter         SVN_REPOS__FORMAT_NUMBER_LEGACY, SVN_REPOS__FORMAT_NUMBER,
1432251881Speter         format);
1433251881Speter    }
1434251881Speter
1435251881Speter  repos->format = format;
1436251881Speter
1437251881Speter  return SVN_NO_ERROR;
1438251881Speter}
1439251881Speter
1440251881Speter
1441251881Speter/* Set *REPOS_P to a repository at PATH which has been opened.
1442251881Speter   See lock_repos() above regarding EXCLUSIVE and NONBLOCKING.
1443251881Speter   OPEN_FS indicates whether the Subversion filesystem should be opened,
1444251881Speter   the handle being placed into repos->fs.
1445251881Speter   Do all allocation in POOL.  */
1446251881Speterstatic svn_error_t *
1447251881Speterget_repos(svn_repos_t **repos_p,
1448251881Speter          const char *path,
1449251881Speter          svn_boolean_t exclusive,
1450251881Speter          svn_boolean_t nonblocking,
1451251881Speter          svn_boolean_t open_fs,
1452251881Speter          apr_hash_t *fs_config,
1453251881Speter          apr_pool_t *pool)
1454251881Speter{
1455251881Speter  svn_repos_t *repos;
1456251881Speter
1457251881Speter  /* Allocate a repository object. */
1458251881Speter  repos = create_svn_repos_t(path, pool);
1459251881Speter
1460251881Speter  /* Verify the validity of our repository format. */
1461251881Speter  SVN_ERR(check_repos_format(repos, pool));
1462251881Speter
1463251881Speter  /* Discover the FS type. */
1464251881Speter  SVN_ERR(svn_fs_type(&repos->fs_type, repos->db_path, pool));
1465251881Speter
1466251881Speter  /* Lock if needed. */
1467251881Speter  SVN_ERR(lock_repos(repos, exclusive, nonblocking, pool));
1468251881Speter
1469251881Speter  /* Open up the filesystem only after obtaining the lock. */
1470251881Speter  if (open_fs)
1471251881Speter    SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, fs_config, pool));
1472251881Speter
1473251881Speter#ifdef SVN_DEBUG_CRASH_AT_REPOS_OPEN
1474251881Speter  /* If $PATH/config/debug-abort exists, crash the server here.
1475251881Speter     This debugging feature can be used to test client recovery
1476251881Speter     when the server crashes.
1477251881Speter
1478251881Speter     See: Issue #4274 */
1479251881Speter  {
1480251881Speter    svn_node_kind_t kind;
1481251881Speter    svn_error_t *err = svn_io_check_path(
1482251881Speter        svn_dirent_join(repos->conf_path, "debug-abort", pool),
1483251881Speter        &kind, pool);
1484251881Speter    svn_error_clear(err);
1485251881Speter    if (!err && kind == svn_node_file)
1486251881Speter      SVN_ERR_MALFUNCTION_NO_RETURN();
1487251881Speter  }
1488251881Speter#endif /* SVN_DEBUG_CRASH_AT_REPOS_OPEN */
1489251881Speter
1490251881Speter  *repos_p = repos;
1491251881Speter  return SVN_NO_ERROR;
1492251881Speter}
1493251881Speter
1494251881Speter
1495251881Speter
1496251881Speterconst char *
1497251881Spetersvn_repos_find_root_path(const char *path,
1498251881Speter                         apr_pool_t *pool)
1499251881Speter{
1500251881Speter  const char *candidate = path;
1501251881Speter  const char *decoded;
1502251881Speter  svn_error_t *err;
1503251881Speter
1504251881Speter  while (1)
1505251881Speter    {
1506251881Speter      /* Try to decode the path, so we don't fail if it contains characters
1507251881Speter         that aren't supported by the OS filesystem.  The subversion fs
1508251881Speter         isn't restricted by the OS filesystem character set. */
1509251881Speter      err = svn_path_cstring_from_utf8(&decoded, candidate, pool);
1510251881Speter      if (!err && check_repos_path(candidate, pool))
1511251881Speter        break;
1512251881Speter      svn_error_clear(err);
1513251881Speter
1514251881Speter      if (svn_path_is_empty(candidate) ||
1515251881Speter          svn_dirent_is_root(candidate, strlen(candidate)))
1516251881Speter        return NULL;
1517251881Speter
1518251881Speter      candidate = svn_dirent_dirname(candidate, pool);
1519251881Speter    }
1520251881Speter
1521251881Speter  return candidate;
1522251881Speter}
1523251881Speter
1524251881Speter
1525251881Spetersvn_error_t *
1526251881Spetersvn_repos_open2(svn_repos_t **repos_p,
1527251881Speter                const char *path,
1528251881Speter                apr_hash_t *fs_config,
1529251881Speter                apr_pool_t *pool)
1530251881Speter{
1531251881Speter  /* Fetch a repository object initialized with a shared read/write
1532251881Speter     lock on the database. */
1533251881Speter
1534251881Speter  return get_repos(repos_p, path, FALSE, FALSE, TRUE, fs_config, pool);
1535251881Speter}
1536251881Speter
1537251881Speter
1538251881Spetersvn_error_t *
1539251881Spetersvn_repos_upgrade2(const char *path,
1540251881Speter                   svn_boolean_t nonblocking,
1541251881Speter                   svn_repos_notify_func_t notify_func,
1542251881Speter                   void *notify_baton,
1543251881Speter                   apr_pool_t *pool)
1544251881Speter{
1545251881Speter  svn_repos_t *repos;
1546251881Speter  const char *format_path;
1547251881Speter  int format;
1548251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1549251881Speter
1550251881Speter  /* Fetch a repository object; for the Berkeley DB backend, it is
1551251881Speter     initialized with an EXCLUSIVE lock on the database.  This will at
1552251881Speter     least prevent others from trying to read or write to it while we
1553251881Speter     run recovery. (Other backends should do their own locking; see
1554251881Speter     lock_repos.) */
1555251881Speter  SVN_ERR(get_repos(&repos, path, TRUE, nonblocking, FALSE, NULL, subpool));
1556251881Speter
1557251881Speter  if (notify_func)
1558251881Speter    {
1559251881Speter      /* We notify *twice* here, because there are two different logistical
1560251881Speter         actions occuring. */
1561251881Speter      svn_repos_notify_t *notify = svn_repos_notify_create(
1562251881Speter                                    svn_repos_notify_mutex_acquired, subpool);
1563251881Speter      notify_func(notify_baton, notify, subpool);
1564251881Speter
1565251881Speter      notify->action = svn_repos_notify_upgrade_start;
1566251881Speter      notify_func(notify_baton, notify, subpool);
1567251881Speter    }
1568251881Speter
1569251881Speter  /* Try to overwrite with its own contents.  We do this only to
1570251881Speter     verify that we can, because we don't want to actually bump the
1571251881Speter     format of the repository until our underlying filesystem claims
1572251881Speter     to have been upgraded correctly. */
1573251881Speter  format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, subpool);
1574251881Speter  SVN_ERR(svn_io_read_version_file(&format, format_path, subpool));
1575251881Speter  SVN_ERR(svn_io_write_version_file(format_path, format, subpool));
1576251881Speter
1577251881Speter  /* Try to upgrade the filesystem. */
1578251881Speter  SVN_ERR(svn_fs_upgrade(repos->db_path, subpool));
1579251881Speter
1580251881Speter  /* Now overwrite our format file with the latest version. */
1581251881Speter  SVN_ERR(svn_io_write_version_file(format_path, SVN_REPOS__FORMAT_NUMBER,
1582251881Speter                                    subpool));
1583251881Speter
1584251881Speter  /* Close shop and free the subpool, to release the exclusive lock. */
1585251881Speter  svn_pool_destroy(subpool);
1586251881Speter
1587251881Speter  return SVN_NO_ERROR;
1588251881Speter}
1589251881Speter
1590251881Speter
1591251881Spetersvn_error_t *
1592251881Spetersvn_repos_delete(const char *path,
1593251881Speter                 apr_pool_t *pool)
1594251881Speter{
1595251881Speter  const char *db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
1596251881Speter
1597251881Speter  /* Delete the filesystem environment... */
1598251881Speter  SVN_ERR(svn_fs_delete_fs(db_path, pool));
1599251881Speter
1600251881Speter  /* ...then blow away everything else.  */
1601251881Speter  return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
1602251881Speter}
1603251881Speter
1604251881Speter
1605251881Speter/* Repository supports the capability. */
1606251881Speterstatic const char *capability_yes = "yes";
1607251881Speter/* Repository does not support the capability. */
1608251881Speterstatic const char *capability_no = "no";
1609251881Speter
1610251881Spetersvn_error_t *
1611251881Spetersvn_repos_has_capability(svn_repos_t *repos,
1612251881Speter                         svn_boolean_t *has,
1613251881Speter                         const char *capability,
1614251881Speter                         apr_pool_t *pool)
1615251881Speter{
1616251881Speter  const char *val = svn_hash_gets(repos->repository_capabilities, capability);
1617251881Speter
1618251881Speter  if (val == capability_yes)
1619251881Speter    {
1620251881Speter      *has = TRUE;
1621251881Speter    }
1622251881Speter  else if (val == capability_no)
1623251881Speter    {
1624251881Speter      *has = FALSE;
1625251881Speter    }
1626251881Speter  /* Else don't know, so investigate. */
1627251881Speter  else if (strcmp(capability, SVN_REPOS_CAPABILITY_MERGEINFO) == 0)
1628251881Speter    {
1629251881Speter      svn_error_t *err;
1630251881Speter      svn_fs_root_t *root;
1631251881Speter      svn_mergeinfo_catalog_t ignored;
1632251881Speter      apr_array_header_t *paths = apr_array_make(pool, 1,
1633251881Speter                                                 sizeof(char *));
1634251881Speter
1635251881Speter      SVN_ERR(svn_fs_revision_root(&root, repos->fs, 0, pool));
1636251881Speter      APR_ARRAY_PUSH(paths, const char *) = "";
1637251881Speter      err = svn_fs_get_mergeinfo2(&ignored, root, paths, FALSE, FALSE,
1638251881Speter                                  TRUE, pool, pool);
1639251881Speter
1640251881Speter      if (err)
1641251881Speter        {
1642251881Speter          if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1643251881Speter            {
1644251881Speter              svn_error_clear(err);
1645251881Speter              svn_hash_sets(repos->repository_capabilities,
1646251881Speter                            SVN_REPOS_CAPABILITY_MERGEINFO, capability_no);
1647251881Speter              *has = FALSE;
1648251881Speter            }
1649251881Speter          else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
1650251881Speter            {
1651251881Speter              /* Mergeinfo requests use relative paths, and anyway we're
1652251881Speter                 in r0, so we're likely to get this error -- but it
1653251881Speter                 means the repository supports mergeinfo! */
1654251881Speter              svn_error_clear(err);
1655251881Speter              svn_hash_sets(repos->repository_capabilities,
1656251881Speter                            SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
1657251881Speter              *has = TRUE;
1658251881Speter            }
1659251881Speter          else
1660251881Speter            {
1661251881Speter              return svn_error_trace(err);
1662251881Speter            }
1663251881Speter        }
1664251881Speter      else
1665251881Speter        {
1666251881Speter          svn_hash_sets(repos->repository_capabilities,
1667251881Speter                        SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
1668251881Speter          *has = TRUE;
1669251881Speter        }
1670251881Speter    }
1671251881Speter  else
1672251881Speter    {
1673251881Speter      return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, 0,
1674251881Speter                               _("unknown capability '%s'"), capability);
1675251881Speter    }
1676251881Speter
1677251881Speter  return SVN_NO_ERROR;
1678251881Speter}
1679251881Speter
1680251881Spetersvn_fs_t *
1681251881Spetersvn_repos_fs(svn_repos_t *repos)
1682251881Speter{
1683251881Speter  if (! repos)
1684251881Speter    return NULL;
1685251881Speter  return repos->fs;
1686251881Speter}
1687251881Speter
1688251881Speter
1689251881Speter/* For historical reasons, for the Berkeley DB backend, this code uses
1690251881Speter * repository locking, which is motivated by the need to support the
1691251881Speter * Berkeley DB error DB_RUN_RECOVERY.  (FSFS takes care of locking
1692251881Speter * itself, inside its implementation of svn_fs_recover.)  Here's how
1693251881Speter * it works:
1694251881Speter *
1695251881Speter * Every accessor of a repository's database takes out a shared lock
1696251881Speter * on the repository -- both readers and writers get shared locks, and
1697251881Speter * there can be an unlimited number of shared locks simultaneously.
1698251881Speter *
1699251881Speter * Sometimes, a db access returns the error DB_RUN_RECOVERY.  When
1700251881Speter * this happens, we need to run svn_fs_recover() on the db
1701251881Speter * with no other accessors present.  So we take out an exclusive lock
1702251881Speter * on the repository.  From the moment we request the exclusive lock,
1703251881Speter * no more shared locks are granted, and when the last shared lock
1704251881Speter * disappears, the exclusive lock is granted.  As soon as we get it,
1705251881Speter * we can run recovery.
1706251881Speter *
1707251881Speter * We assume that once any berkeley call returns DB_RUN_RECOVERY, they
1708251881Speter * all do, until recovery is run.
1709251881Speter */
1710251881Speter
1711251881Spetersvn_error_t *
1712251881Spetersvn_repos_recover4(const char *path,
1713251881Speter                   svn_boolean_t nonblocking,
1714251881Speter                   svn_repos_notify_func_t notify_func,
1715251881Speter                   void *notify_baton,
1716251881Speter                   svn_cancel_func_t cancel_func,
1717251881Speter                   void * cancel_baton,
1718251881Speter                   apr_pool_t *pool)
1719251881Speter{
1720251881Speter  svn_repos_t *repos;
1721251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1722251881Speter
1723251881Speter  /* Fetch a repository object; for the Berkeley DB backend, it is
1724251881Speter     initialized with an EXCLUSIVE lock on the database.  This will at
1725251881Speter     least prevent others from trying to read or write to it while we
1726251881Speter     run recovery. (Other backends should do their own locking; see
1727251881Speter     lock_repos.) */
1728251881Speter  SVN_ERR(get_repos(&repos, path, TRUE, nonblocking,
1729251881Speter                    FALSE,    /* don't try to open the db yet. */
1730251881Speter                    NULL,
1731251881Speter                    subpool));
1732251881Speter
1733251881Speter  if (notify_func)
1734251881Speter    {
1735251881Speter      /* We notify *twice* here, because there are two different logistical
1736251881Speter         actions occuring. */
1737251881Speter      svn_repos_notify_t *notify = svn_repos_notify_create(
1738251881Speter                                    svn_repos_notify_mutex_acquired, subpool);
1739251881Speter      notify_func(notify_baton, notify, subpool);
1740251881Speter
1741251881Speter      notify->action = svn_repos_notify_recover_start;
1742251881Speter      notify_func(notify_baton, notify, subpool);
1743251881Speter    }
1744251881Speter
1745251881Speter  /* Recover the database to a consistent state. */
1746251881Speter  SVN_ERR(svn_fs_recover(repos->db_path, cancel_func, cancel_baton, subpool));
1747251881Speter
1748251881Speter  /* Close shop and free the subpool, to release the exclusive lock. */
1749251881Speter  svn_pool_destroy(subpool);
1750251881Speter
1751251881Speter  return SVN_NO_ERROR;
1752251881Speter}
1753251881Speter
1754251881Speterstruct freeze_baton_t {
1755251881Speter  apr_array_header_t *paths;
1756251881Speter  int counter;
1757251881Speter  svn_repos_freeze_func_t freeze_func;
1758251881Speter  void *freeze_baton;
1759251881Speter};
1760251881Speter
1761251881Speterstatic svn_error_t *
1762251881Spetermulti_freeze(void *baton,
1763251881Speter             apr_pool_t *pool)
1764251881Speter{
1765251881Speter  struct freeze_baton_t *fb = baton;
1766251881Speter
1767251881Speter  if (fb->counter == fb->paths->nelts)
1768251881Speter    {
1769251881Speter      SVN_ERR(fb->freeze_func(fb->freeze_baton, pool));
1770251881Speter      return SVN_NO_ERROR;
1771251881Speter    }
1772251881Speter  else
1773251881Speter    {
1774251881Speter      /* Using a subpool as the only way to unlock the repos lock used
1775251881Speter         by BDB is to clear the pool used to take the lock. */
1776251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1777251881Speter      const char *path = APR_ARRAY_IDX(fb->paths, fb->counter, const char *);
1778251881Speter      svn_repos_t *repos;
1779251881Speter
1780251881Speter      ++fb->counter;
1781251881Speter
1782251881Speter      SVN_ERR(get_repos(&repos, path,
1783251881Speter                        TRUE  /* exclusive (only applies to BDB) */,
1784251881Speter                        FALSE /* non-blocking */,
1785251881Speter                        FALSE /* open-fs */,
1786251881Speter                        NULL, subpool));
1787251881Speter
1788251881Speter
1789251881Speter      if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
1790251881Speter        {
1791251881Speter          svn_error_t *err = multi_freeze(fb, subpool);
1792251881Speter
1793251881Speter          svn_pool_destroy(subpool);
1794251881Speter
1795251881Speter          return err;
1796251881Speter        }
1797251881Speter      else
1798251881Speter        {
1799251881Speter          SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, NULL, subpool));
1800251881Speter          SVN_ERR(svn_fs_freeze(svn_repos_fs(repos), multi_freeze, fb,
1801251881Speter                                subpool));
1802251881Speter        }
1803251881Speter
1804251881Speter      svn_pool_destroy(subpool);
1805251881Speter    }
1806251881Speter
1807251881Speter  return SVN_NO_ERROR;
1808251881Speter}
1809251881Speter
1810251881Speter/* For BDB we fall back on BDB's repos layer lock which means that the
1811251881Speter   repository is unreadable while frozen.
1812251881Speter
1813251881Speter   For FSFS we delegate to the FS layer which uses the FSFS write-lock
1814251881Speter   and an SQLite reserved lock which means the repository is readable
1815251881Speter   while frozen. */
1816251881Spetersvn_error_t *
1817251881Spetersvn_repos_freeze(apr_array_header_t *paths,
1818251881Speter                 svn_repos_freeze_func_t freeze_func,
1819251881Speter                 void *freeze_baton,
1820251881Speter                 apr_pool_t *pool)
1821251881Speter{
1822251881Speter  struct freeze_baton_t fb;
1823251881Speter
1824251881Speter  fb.paths = paths;
1825251881Speter  fb.counter = 0;
1826251881Speter  fb.freeze_func = freeze_func;
1827251881Speter  fb.freeze_baton = freeze_baton;
1828251881Speter
1829251881Speter  SVN_ERR(multi_freeze(&fb, pool));
1830251881Speter
1831251881Speter  return SVN_NO_ERROR;
1832251881Speter}
1833251881Speter
1834251881Spetersvn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
1835251881Speter                                   const char *path,
1836251881Speter                                   svn_boolean_t only_unused,
1837251881Speter                                   apr_pool_t *pool)
1838251881Speter{
1839251881Speter  svn_repos_t *repos;
1840251881Speter  int i;
1841251881Speter
1842251881Speter  SVN_ERR(get_repos(&repos, path,
1843251881Speter                    FALSE, FALSE,
1844251881Speter                    FALSE,     /* Do not open fs. */
1845251881Speter                    NULL,
1846251881Speter                    pool));
1847251881Speter
1848251881Speter  SVN_ERR(svn_fs_berkeley_logfiles(logfiles,
1849251881Speter                                   svn_repos_db_env(repos, pool),
1850251881Speter                                   only_unused,
1851251881Speter                                   pool));
1852251881Speter
1853251881Speter  /* Loop, printing log files. */
1854251881Speter  for (i = 0; i < (*logfiles)->nelts; i++)
1855251881Speter    {
1856251881Speter      const char ** log_file = &(APR_ARRAY_IDX(*logfiles, i, const char *));
1857251881Speter      *log_file = svn_dirent_join(SVN_REPOS__DB_DIR, *log_file, pool);
1858251881Speter    }
1859251881Speter
1860251881Speter  return SVN_NO_ERROR;
1861251881Speter}
1862251881Speter
1863251881Speter/* Baton for hotcopy_structure(). */
1864251881Speterstruct hotcopy_ctx_t {
1865251881Speter  const char *dest;     /* target location to construct */
1866251881Speter  size_t src_len; /* len of the source path*/
1867251881Speter
1868251881Speter  /* As in svn_repos_hotcopy2() */
1869251881Speter  svn_boolean_t incremental;
1870251881Speter  svn_cancel_func_t cancel_func;
1871251881Speter  void *cancel_baton;
1872251881Speter};
1873251881Speter
1874251881Speter/* Copy the repository structure of PATH to BATON->DEST, with exception of
1875251881Speter * @c SVN_REPOS__DB_DIR, @c SVN_REPOS__LOCK_DIR and @c SVN_REPOS__FORMAT;
1876251881Speter * those directories and files are handled separately.
1877251881Speter *
1878251881Speter * BATON is a (struct hotcopy_ctx_t *).  BATON->SRC_LEN is the length
1879251881Speter * of PATH.
1880251881Speter *
1881251881Speter * Implements svn_io_walk_func_t.
1882251881Speter */
1883251881Speterstatic svn_error_t *hotcopy_structure(void *baton,
1884251881Speter                                      const char *path,
1885251881Speter                                      const apr_finfo_t *finfo,
1886251881Speter                                      apr_pool_t *pool)
1887251881Speter{
1888251881Speter  const struct hotcopy_ctx_t *ctx = baton;
1889251881Speter  const char *sub_path;
1890251881Speter  const char *target;
1891251881Speter
1892251881Speter  if (ctx->cancel_func)
1893251881Speter    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1894251881Speter
1895251881Speter  if (strlen(path) == ctx->src_len)
1896251881Speter    {
1897251881Speter      sub_path = "";
1898251881Speter    }
1899251881Speter  else
1900251881Speter    {
1901251881Speter      sub_path = &path[ctx->src_len+1];
1902251881Speter
1903251881Speter      /* Check if we are inside db directory and if so skip it */
1904251881Speter      if (svn_path_compare_paths
1905251881Speter          (svn_dirent_get_longest_ancestor(SVN_REPOS__DB_DIR, sub_path, pool),
1906251881Speter           SVN_REPOS__DB_DIR) == 0)
1907251881Speter        return SVN_NO_ERROR;
1908251881Speter
1909251881Speter      if (svn_path_compare_paths
1910251881Speter          (svn_dirent_get_longest_ancestor(SVN_REPOS__LOCK_DIR, sub_path,
1911251881Speter                                           pool),
1912251881Speter           SVN_REPOS__LOCK_DIR) == 0)
1913251881Speter        return SVN_NO_ERROR;
1914251881Speter
1915251881Speter      if (svn_path_compare_paths
1916251881Speter          (svn_dirent_get_longest_ancestor(SVN_REPOS__FORMAT, sub_path, pool),
1917251881Speter           SVN_REPOS__FORMAT) == 0)
1918251881Speter        return SVN_NO_ERROR;
1919251881Speter    }
1920251881Speter
1921251881Speter  target = svn_dirent_join(ctx->dest, sub_path, pool);
1922251881Speter
1923251881Speter  if (finfo->filetype == APR_DIR)
1924251881Speter    {
1925251881Speter      svn_error_t *err;
1926251881Speter
1927251881Speter      err = create_repos_dir(target, pool);
1928251881Speter      if (ctx->incremental && err && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
1929251881Speter        {
1930251881Speter          svn_error_clear(err);
1931251881Speter          err = SVN_NO_ERROR;
1932251881Speter        }
1933251881Speter      return svn_error_trace(err);
1934251881Speter    }
1935251881Speter  else if (finfo->filetype == APR_REG)
1936251881Speter    return svn_io_copy_file(path, target, TRUE, pool);
1937251881Speter  else if (finfo->filetype == APR_LNK)
1938251881Speter    return svn_io_copy_link(path, target, pool);
1939251881Speter  else
1940251881Speter    return SVN_NO_ERROR;
1941251881Speter}
1942251881Speter
1943251881Speter
1944251881Speter/** Obtain a lock on db logs lock file. Create one if it does not exist.
1945251881Speter */
1946251881Speterstatic svn_error_t *
1947251881Speterlock_db_logs_file(svn_repos_t *repos,
1948251881Speter                  svn_boolean_t exclusive,
1949251881Speter                  apr_pool_t *pool)
1950251881Speter{
1951251881Speter  const char * lock_file = svn_repos_db_logs_lockfile(repos, pool);
1952251881Speter
1953251881Speter  /* Try to create a lock file, in case if it is missing. As in case of the
1954251881Speter     repositories created before hotcopy functionality.  */
1955251881Speter  svn_error_clear(create_db_logs_lock(repos, pool));
1956251881Speter
1957251881Speter  return svn_io_file_lock2(lock_file, exclusive, FALSE, pool);
1958251881Speter}
1959251881Speter
1960251881Speter
1961251881Speter/* Make a copy of a repository with hot backup of fs. */
1962251881Spetersvn_error_t *
1963251881Spetersvn_repos_hotcopy2(const char *src_path,
1964251881Speter                   const char *dst_path,
1965251881Speter                   svn_boolean_t clean_logs,
1966251881Speter                   svn_boolean_t incremental,
1967251881Speter                   svn_cancel_func_t cancel_func,
1968251881Speter                   void *cancel_baton,
1969251881Speter                   apr_pool_t *pool)
1970251881Speter{
1971251881Speter  svn_repos_t *src_repos;
1972251881Speter  svn_repos_t *dst_repos;
1973251881Speter  struct hotcopy_ctx_t hotcopy_context;
1974251881Speter  svn_error_t *err;
1975251881Speter  const char *src_abspath;
1976251881Speter  const char *dst_abspath;
1977251881Speter
1978251881Speter  SVN_ERR(svn_dirent_get_absolute(&src_abspath, src_path, pool));
1979251881Speter  SVN_ERR(svn_dirent_get_absolute(&dst_abspath, dst_path, pool));
1980251881Speter  if (strcmp(src_abspath, dst_abspath) == 0)
1981251881Speter    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1982251881Speter                             _("Hotcopy source and destination are equal"));
1983251881Speter
1984251881Speter  /* Try to open original repository */
1985251881Speter  SVN_ERR(get_repos(&src_repos, src_abspath,
1986251881Speter                    FALSE, FALSE,
1987251881Speter                    FALSE,    /* don't try to open the db yet. */
1988251881Speter                    NULL,
1989251881Speter                    pool));
1990251881Speter
1991251881Speter  /* If we are going to clean logs, then get an exclusive lock on
1992251881Speter     db-logs.lock, to ensure that no one else will work with logs.
1993251881Speter
1994251881Speter     If we are just copying, then get a shared lock to ensure that
1995251881Speter     no one else will clean logs while we copying them */
1996251881Speter
1997251881Speter  SVN_ERR(lock_db_logs_file(src_repos, clean_logs, pool));
1998251881Speter
1999251881Speter  /* Copy the repository to a new path, with exception of
2000251881Speter     specially handled directories */
2001251881Speter
2002251881Speter  hotcopy_context.dest = dst_abspath;
2003251881Speter  hotcopy_context.src_len = strlen(src_abspath);
2004251881Speter  hotcopy_context.incremental = incremental;
2005251881Speter  hotcopy_context.cancel_func = cancel_func;
2006251881Speter  hotcopy_context.cancel_baton = cancel_baton;
2007251881Speter  SVN_ERR(svn_io_dir_walk2(src_abspath,
2008251881Speter                           0,
2009251881Speter                           hotcopy_structure,
2010251881Speter                           &hotcopy_context,
2011251881Speter                           pool));
2012251881Speter
2013251881Speter  /* Prepare dst_repos object so that we may create locks,
2014251881Speter     so that we may open repository */
2015251881Speter
2016251881Speter  dst_repos = create_svn_repos_t(dst_abspath, pool);
2017251881Speter  dst_repos->fs_type = src_repos->fs_type;
2018251881Speter  dst_repos->format = src_repos->format;
2019251881Speter
2020251881Speter  err = create_locks(dst_repos, pool);
2021251881Speter  if (err)
2022251881Speter    {
2023251881Speter      if (incremental && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
2024251881Speter        svn_error_clear(err);
2025251881Speter      else
2026251881Speter        return svn_error_trace(err);
2027251881Speter    }
2028251881Speter
2029251881Speter  err = svn_io_dir_make_sgid(dst_repos->db_path, APR_OS_DEFAULT, pool);
2030251881Speter  if (err)
2031251881Speter    {
2032251881Speter      if (incremental && APR_STATUS_IS_EEXIST(err->apr_err))
2033251881Speter        svn_error_clear(err);
2034251881Speter      else
2035251881Speter        return svn_error_trace(err);
2036251881Speter    }
2037251881Speter
2038251881Speter  /* Exclusively lock the new repository.
2039251881Speter     No one should be accessing it at the moment */
2040251881Speter  SVN_ERR(lock_repos(dst_repos, TRUE, FALSE, pool));
2041251881Speter
2042251881Speter  SVN_ERR(svn_fs_hotcopy2(src_repos->db_path, dst_repos->db_path,
2043251881Speter                          clean_logs, incremental,
2044251881Speter                          cancel_func, cancel_baton, pool));
2045251881Speter
2046251881Speter  /* Destination repository is ready.  Stamp it with a format number. */
2047251881Speter  return svn_io_write_version_file
2048251881Speter          (svn_dirent_join(dst_repos->path, SVN_REPOS__FORMAT, pool),
2049251881Speter           dst_repos->format, pool);
2050251881Speter}
2051251881Speter
2052251881Spetersvn_error_t *
2053251881Spetersvn_repos_hotcopy(const char *src_path,
2054251881Speter                  const char *dst_path,
2055251881Speter                  svn_boolean_t clean_logs,
2056251881Speter                  apr_pool_t *pool)
2057251881Speter{
2058251881Speter  return svn_error_trace(svn_repos_hotcopy2(src_path, dst_path, clean_logs,
2059251881Speter                                            FALSE, NULL, NULL, pool));
2060251881Speter}
2061251881Speter
2062251881Speter/* Return the library version number. */
2063251881Speterconst svn_version_t *
2064251881Spetersvn_repos_version(void)
2065251881Speter{
2066251881Speter  SVN_VERSION_BODY;
2067251881Speter}
2068251881Speter
2069251881Speter
2070251881Speter
2071251881Spetersvn_error_t *
2072251881Spetersvn_repos_stat(svn_dirent_t **dirent,
2073251881Speter               svn_fs_root_t *root,
2074251881Speter               const char *path,
2075251881Speter               apr_pool_t *pool)
2076251881Speter{
2077251881Speter  svn_node_kind_t kind;
2078251881Speter  svn_dirent_t *ent;
2079251881Speter  const char *datestring;
2080251881Speter  apr_hash_t *prophash;
2081251881Speter
2082251881Speter  SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
2083251881Speter
2084251881Speter  if (kind == svn_node_none)
2085251881Speter    {
2086251881Speter      *dirent = NULL;
2087251881Speter      return SVN_NO_ERROR;
2088251881Speter    }
2089251881Speter
2090251881Speter  ent = svn_dirent_create(pool);
2091251881Speter  ent->kind = kind;
2092251881Speter
2093251881Speter  if (kind == svn_node_file)
2094251881Speter    SVN_ERR(svn_fs_file_length(&(ent->size), root, path, pool));
2095251881Speter
2096251881Speter  SVN_ERR(svn_fs_node_proplist(&prophash, root, path, pool));
2097251881Speter  if (apr_hash_count(prophash) > 0)
2098251881Speter    ent->has_props = TRUE;
2099251881Speter
2100251881Speter  SVN_ERR(svn_repos_get_committed_info(&(ent->created_rev),
2101251881Speter                                       &datestring,
2102251881Speter                                       &(ent->last_author),
2103251881Speter                                       root, path, pool));
2104251881Speter  if (datestring)
2105251881Speter    SVN_ERR(svn_time_from_cstring(&(ent->time), datestring, pool));
2106251881Speter
2107251881Speter  *dirent = ent;
2108251881Speter  return SVN_NO_ERROR;
2109251881Speter}
2110251881Speter
2111251881Spetersvn_error_t *
2112251881Spetersvn_repos_remember_client_capabilities(svn_repos_t *repos,
2113251881Speter                                       const apr_array_header_t *capabilities)
2114251881Speter{
2115251881Speter  repos->client_capabilities = capabilities;
2116251881Speter  return SVN_NO_ERROR;
2117251881Speter}
2118251881Speter
2119251881Spetersvn_error_t *
2120251881Spetersvn_repos__fs_type(const char **fs_type,
2121251881Speter                   const char *repos_path,
2122251881Speter                   apr_pool_t *pool)
2123251881Speter{
2124251881Speter  svn_repos_t repos;
2125251881Speter  repos.path = (char*)repos_path;
2126251881Speter
2127251881Speter  SVN_ERR(check_repos_format(&repos, pool));
2128251881Speter
2129251881Speter  return svn_fs_type(fs_type,
2130251881Speter                     svn_dirent_join(repos_path, SVN_REPOS__DB_DIR, pool),
2131251881Speter                     pool);
2132251881Speter}
2133