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                                                \
270299742Sdim  "# The hook program runs in an empty environment, unless the server is"  NL \
271299742Sdim  "# explicitly configured otherwise.  For example, a common problem is for" NL \
272299742Sdim  "# the 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
283299742Sdim#define HOOKS_QUOTE_ARGUMENTS_TEXT                                            \
284299742Sdim  "# CAUTION:"                                                             NL \
285299742Sdim  "# For security reasons, you MUST always properly quote arguments when"  NL \
286299742Sdim  "# you use them, as those arguments could contain whitespace or other"   NL \
287299742Sdim  "# problematic characters. Additionally, you should delimit the list"    NL \
288299742Sdim  "# of options with \"--\" before passing the arguments, so malicious"    NL \
289299742Sdim  "# clients cannot bootleg unexpected options to the commands your"       NL \
290299742Sdim  "# script aims to execute."                                              NL \
291299742Sdim  "# For similar reasons, you should also add a trailing @ to URLs which"  NL \
292299742Sdim  "# are passed to SVN commands accepting URLs with peg revisions."        NL
293251881Speter
294299742Sdim/* Return template text for a hook script named SCRIPT_NAME. Include
295299742Sdim * DESCRIPTION and SCRIPT in the template text.
296299742Sdim */
297299742Sdimstatic const char *
298299742Sdimhook_template_text(const char *script_name,
299299742Sdim                   const char *description,
300299742Sdim                   const char *script,
301299742Sdim                   apr_pool_t *result_pool)
302299742Sdim{
303299742Sdim  return apr_pstrcat(result_pool,
304299742Sdim"#!/bin/sh"                                                                  NL
305299742Sdim""                                                                           NL,
306299742Sdim                     description,
307299742Sdim"#"                                                                          NL
308299742Sdim"# The default working directory for the invocation is undefined, so"        NL
309299742Sdim"# the program should set one explicitly if it cares."                       NL
310299742Sdim"#"                                                                          NL
311299742Sdim"# On a Unix system, the normal procedure is to have '", script_name, "'"    NL
312299742Sdim"# invoke other programs to do the real work, though it may do the"          NL
313299742Sdim"# work itself too."                                                         NL
314299742Sdim"#"                                                                          NL
315299742Sdim"# Note that '", script_name, "' must be executable by the user(s) who will" NL
316299742Sdim"# invoke it (typically the user httpd runs as), and that user must"         NL
317299742Sdim"# have filesystem-level permission to access the repository."               NL
318299742Sdim"#"                                                                          NL
319299742Sdim"# On a Windows system, you should name the hook program"                    NL
320299742Sdim"# '", script_name, ".bat' or '", script_name, ".exe',"                                                    NL
321299742Sdim"# but the basic idea is the same."                                          NL
322299742Sdim"#"                                                                          NL
323299742SdimHOOKS_ENVIRONMENT_TEXT
324299742Sdim"#"                                                                          NL
325299742SdimHOOKS_QUOTE_ARGUMENTS_TEXT
326299742Sdim"#"                                                                          NL
327299742Sdim"# Here is an example hook script, for a Unix /bin/sh interpreter."          NL
328299742SdimPREWRITTEN_HOOKS_TEXT
329299742Sdim""                                                                           NL
330299742Sdim""                                                                           NL,
331299742Sdim                     script,
332299742Sdim                     SVN_VA_NULL);
333299742Sdim}
334299742Sdim
335299742Sdim/* Write a template file for a hook script named SCRIPT_NAME (appending
336299742Sdim * '.tmpl' to that name) in REPOS. Include DESCRIPTION and SCRIPT in the
337299742Sdim * template text.
338299742Sdim */
339251881Speterstatic svn_error_t *
340299742Sdimwrite_hook_template_file(svn_repos_t *repos, const char *script_name,
341299742Sdim                         const char *description,
342299742Sdim                         const char *script,
343299742Sdim                         apr_pool_t *pool)
344299742Sdim{
345299742Sdim  const char *template_path
346299742Sdim    = svn_dirent_join(repos->hook_path,
347299742Sdim                      apr_psprintf(pool, "%s%s",
348299742Sdim                                   script_name, SVN_REPOS__HOOK_DESC_EXT),
349299742Sdim                      pool);
350299742Sdim  const char *contents
351299742Sdim    = hook_template_text(script_name, description, script, pool);
352299742Sdim
353299742Sdim  SVN_ERR(svn_io_file_create(template_path, contents, pool));
354299742Sdim  SVN_ERR(svn_io_set_file_executable(template_path, TRUE, FALSE, pool));
355299742Sdim  return SVN_NO_ERROR;
356299742Sdim}
357299742Sdim
358299742Sdim/* Write the hook template files in REPOS.
359299742Sdim */
360299742Sdimstatic svn_error_t *
361251881Spetercreate_hooks(svn_repos_t *repos, apr_pool_t *pool)
362251881Speter{
363299742Sdim  const char *description, *script;
364251881Speter
365251881Speter  /* Create the hook directory. */
366251881Speter  SVN_ERR_W(create_repos_dir(repos->hook_path, pool),
367251881Speter            _("Creating hook directory"));
368251881Speter
369251881Speter  /*** Write a default template for each standard hook file. */
370251881Speter
371251881Speter  /* Start-commit hook. */
372251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_START_COMMIT
373251881Speter
374299742Sdim  description =
375251881Speter"# START-COMMIT HOOK"                                                        NL
376251881Speter"#"                                                                          NL
377251881Speter"# The start-commit hook is invoked immediately after a Subversion txn is"   NL
378251881Speter"# created and populated with initial revprops in the process of doing a"    NL
379251881Speter"# commit. Subversion runs this hook by invoking a program (script, "        NL
380251881Speter"# executable, binary, etc.) named '"SCRIPT_NAME"' (for which this file"     NL
381251881Speter"# is a template) with the following ordered arguments:"                     NL
382251881Speter"#"                                                                          NL
383251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
384251881Speter"#   [2] USER         (the authenticated user attempting to commit)"         NL
385251881Speter"#   [3] CAPABILITIES (a colon-separated list of capabilities reported"      NL
386251881Speter"#                     by the client; see note below)"                       NL
387251881Speter"#   [4] TXN-NAME     (the name of the commit txn just created)"             NL
388251881Speter"#"                                                                          NL
389251881Speter"# Note: The CAPABILITIES parameter is new in Subversion 1.5, and 1.5"       NL
390251881Speter"# clients will typically report at least the \""                            \
391251881Speter   SVN_RA_CAPABILITY_MERGEINFO "\" capability."                              NL
392251881Speter"# If there are other capabilities, then the list is colon-separated,"       NL
393251881Speter"# e.g.: \"" SVN_RA_CAPABILITY_MERGEINFO ":some-other-capability\" "         \
394251881Speter  "(the order is undefined)."                                                NL
395251881Speter"#"                                                                          NL
396251881Speter"# Note: The TXN-NAME parameter is new in Subversion 1.8.  Prior to version" NL
397251881Speter"# 1.8, the start-commit hook was invoked before the commit txn was even"    NL
398251881Speter"# created, so the ability to inspect the commit txn and its metadata from"  NL
399251881Speter"# within the start-commit hook was not possible."                           NL
400251881Speter"# "                                                                         NL
401251881Speter"# The list is self-reported by the client.  Therefore, you should not"      NL
402251881Speter"# make security assumptions based on the capabilities list, nor should"     NL
403251881Speter"# you assume that clients reliably report every capability they have."      NL
404251881Speter"#"                                                                          NL
405251881Speter"# If the hook program exits with success, the commit continues; but"        NL
406251881Speter"# if it exits with failure (non-zero), the commit is stopped before"        NL
407299742Sdim"# a Subversion txn is created, and STDERR is returned to the client."       NL;
408299742Sdim  script =
409251881Speter"REPOS=\"$1\""                                                               NL
410251881Speter"USER=\"$2\""                                                                NL
411251881Speter""                                                                           NL
412251881Speter"commit-allower.pl --repository \"$REPOS\" --user \"$USER\" || exit 1"       NL
413251881Speter"special-auth-check.py --user \"$USER\" --auth-level 3 || exit 1"            NL
414251881Speter""                                                                           NL
415251881Speter"# All checks passed, so allow the commit."                                  NL
416251881Speter"exit 0"                                                                     NL;
417251881Speter
418299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
419299742Sdim                                     description, script, pool),
420299742Sdim            _("Creating start-commit hook"));
421299742Sdim
422251881Speter#undef SCRIPT_NAME
423251881Speter
424251881Speter
425251881Speter  /* Pre-commit hook. */
426251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_COMMIT
427251881Speter
428299742Sdim  description =
429251881Speter"# PRE-COMMIT HOOK"                                                          NL
430251881Speter"#"                                                                          NL
431251881Speter"# The pre-commit hook is invoked before a Subversion txn is"                NL
432251881Speter"# committed.  Subversion runs this hook by invoking a program"              NL
433251881Speter"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which"      NL
434251881Speter"# this file is a template), with the following ordered arguments:"          NL
435251881Speter"#"                                                                          NL
436251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
437251881Speter"#   [2] TXN-NAME     (the name of the txn about to be committed)"           NL
438251881Speter"#"                                                                          NL
439251881Speter"#   [STDIN] LOCK-TOKENS ** the lock tokens are passed via STDIN."           NL
440251881Speter"#"                                                                          NL
441251881Speter"#   If STDIN contains the line \"LOCK-TOKENS:\\n\" (the \"\\n\" denotes a"  NL
442251881Speter"#   single newline), the lines following it are the lock tokens for"        NL
443251881Speter"#   this commit.  The end of the list is marked by a line containing"       NL
444251881Speter"#   only a newline character."                                              NL
445251881Speter"#"                                                                          NL
446251881Speter"#   Each lock token line consists of a URI-escaped path, followed"          NL
447251881Speter"#   by the separator character '|', followed by the lock token string,"     NL
448251881Speter"#   followed by a newline."                                                 NL
449251881Speter"#"                                                                          NL
450251881Speter"# If the hook program exits with success, the txn is committed; but"        NL
451251881Speter"# if it exits with failure (non-zero), the txn is aborted, no commit"       NL
452251881Speter"# takes place, and STDERR is returned to the client.   The hook"            NL
453251881Speter"# program can use the 'svnlook' utility to help it examine the txn."        NL
454251881Speter"#"                                                                          NL
455251881Speter"#   ***  NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT  ***"       NL
456251881Speter"#   ***  FOR REVISION PROPERTIES (like svn:log or svn:author).   ***"       NL
457251881Speter"#"                                                                          NL
458251881Speter"#   This is why we recommend using the read-only 'svnlook' utility."        NL
459251881Speter"#   In the future, Subversion may enforce the rule that pre-commit"         NL
460251881Speter"#   hooks should not modify the versioned data in txns, or else come"       NL
461251881Speter"#   up with a mechanism to make it safe to do so (by informing the"         NL
462251881Speter"#   committing client of the changes).  However, right now neither"         NL
463299742Sdim"#   mechanism is implemented, so hook writers just have to be careful."     NL;
464299742Sdim  script =
465251881Speter"REPOS=\"$1\""                                                               NL
466251881Speter"TXN=\"$2\""                                                                 NL
467251881Speter""                                                                           NL
468251881Speter"# Make sure that the log message contains some text."                       NL
469251881Speter"SVNLOOK=" SVN_BINDIR "/svnlook"                                             NL
470251881Speter"$SVNLOOK log -t \"$TXN\" \"$REPOS\" | \\"                                   NL
471251881Speter"   grep \"[a-zA-Z0-9]\" > /dev/null || exit 1"                              NL
472251881Speter""                                                                           NL
473251881Speter"# Check that the author of this commit has the rights to perform"           NL
474251881Speter"# the commit on the files and directories being modified."                  NL
475251881Speter"commit-access-control.pl \"$REPOS\" \"$TXN\" commit-access-control.cfg || exit 1"
476251881Speter                                                                             NL
477251881Speter""                                                                           NL
478251881Speter"# All checks passed, so allow the commit."                                  NL
479251881Speter"exit 0"                                                                     NL;
480251881Speter
481299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
482299742Sdim                                     description, script, pool),
483299742Sdim            _("Creating pre-commit hook"));
484299742Sdim
485251881Speter#undef SCRIPT_NAME
486251881Speter
487251881Speter
488251881Speter  /* Pre-revprop-change hook. */
489251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_REVPROP_CHANGE
490251881Speter
491299742Sdim  description =
492251881Speter"# PRE-REVPROP-CHANGE HOOK"                                                  NL
493251881Speter"#"                                                                          NL
494251881Speter"# The pre-revprop-change hook is invoked before a revision property"        NL
495251881Speter"# is added, modified or deleted.  Subversion runs this hook by invoking"    NL
496251881Speter"# a program (script, executable, binary, etc.) named '"SCRIPT_NAME"'"       NL
497251881Speter"# (for which this file is a template), with the following ordered"          NL
498251881Speter"# arguments:"                                                               NL
499251881Speter"#"                                                                          NL
500251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
501251881Speter"#   [2] REV          (the revision being tweaked)"                          NL
502251881Speter"#   [3] USER         (the username of the person tweaking the property)"    NL
503251881Speter"#   [4] PROPNAME     (the property being set on the revision)"              NL
504251881Speter"#   [5] ACTION       (the property is being 'A'dded, 'M'odified, or 'D'eleted)"
505251881Speter                                                                             NL
506251881Speter"#"                                                                          NL
507251881Speter"#   [STDIN] PROPVAL  ** the new property value is passed via STDIN."        NL
508251881Speter"#"                                                                          NL
509251881Speter"# If the hook program exits with success, the propchange happens; but"      NL
510251881Speter"# if it exits with failure (non-zero), the propchange doesn't happen."      NL
511251881Speter"# The hook program can use the 'svnlook' utility to examine the "           NL
512251881Speter"# existing value of the revision property."                                 NL
513251881Speter"#"                                                                          NL
514251881Speter"# WARNING: unlike other hooks, this hook MUST exist for revision"           NL
515251881Speter"# properties to be changed.  If the hook does not exist, Subversion "       NL
516251881Speter"# will behave as if the hook were present, but failed.  The reason"         NL
517251881Speter"# for this is that revision properties are UNVERSIONED, meaning that"       NL
518251881Speter"# a successful propchange is destructive;  the old value is gone"           NL
519299742Sdim"# forever.  We recommend the hook back up the old value somewhere."         NL;
520299742Sdim  script =
521251881Speter"REPOS=\"$1\""                                                               NL
522251881Speter"REV=\"$2\""                                                                 NL
523251881Speter"USER=\"$3\""                                                                NL
524251881Speter"PROPNAME=\"$4\""                                                            NL
525251881Speter"ACTION=\"$5\""                                                              NL
526251881Speter""                                                                           NL
527251881Speter"if [ \"$ACTION\" = \"M\" -a \"$PROPNAME\" = \"svn:log\" ]; then exit 0; fi" NL
528251881Speter""                                                                           NL
529251881Speter"echo \"Changing revision properties other than svn:log is prohibited\" >&2" NL
530251881Speter"exit 1"                                                                     NL;
531251881Speter
532299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
533299742Sdim                                     description, script, pool),
534299742Sdim            _("Creating pre-revprop-change hook"));
535299742Sdim
536251881Speter#undef SCRIPT_NAME
537251881Speter
538251881Speter
539251881Speter  /* Pre-lock hook. */
540251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_LOCK
541251881Speter
542299742Sdim  description =
543251881Speter"# PRE-LOCK HOOK"                                                            NL
544251881Speter"#"                                                                          NL
545251881Speter"# The pre-lock hook is invoked before an exclusive lock is"                 NL
546251881Speter"# created.  Subversion runs this hook by invoking a program "               NL
547251881Speter"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which"      NL
548251881Speter"# this file is a template), with the following ordered arguments:"          NL
549251881Speter"#"                                                                          NL
550251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
551251881Speter"#   [2] PATH         (the path in the repository about to be locked)"       NL
552251881Speter"#   [3] USER         (the user creating the lock)"                          NL
553251881Speter"#   [4] COMMENT      (the comment of the lock)"                             NL
554251881Speter"#   [5] STEAL-LOCK   (1 if the user is trying to steal the lock, else 0)"   NL
555251881Speter"#"                                                                          NL
556251881Speter"# If the hook program outputs anything on stdout, the output string will"   NL
557251881Speter"# be used as the lock token for this lock operation.  If you choose to use" NL
558251881Speter"# this feature, you must guarantee the tokens generated are unique across"  NL
559251881Speter"# the repository each time."                                                NL
560251881Speter"#"                                                                          NL
561251881Speter"# If the hook program exits with success, the lock is created; but"         NL
562251881Speter"# if it exits with failure (non-zero), the lock action is aborted"          NL
563299742Sdim"# and STDERR is returned to the client."                                    NL;
564299742Sdim  script =
565251881Speter"REPOS=\"$1\""                                                               NL
566251881Speter"PATH=\"$2\""                                                                NL
567251881Speter"USER=\"$3\""                                                                NL
568251881Speter"COMMENT=\"$4\""                                                             NL
569251881Speter"STEAL=\"$5\""                                                               NL
570251881Speter""                                                                           NL
571251881Speter"# If a lock exists and is owned by a different person, don't allow it"      NL
572251881Speter"# to be stolen (e.g., with 'svn lock --force ...')."                        NL
573251881Speter""                                                                           NL
574251881Speter"# (Maybe this script could send email to the lock owner?)"                  NL
575251881Speter"SVNLOOK=" SVN_BINDIR "/svnlook"                                             NL
576251881Speter"GREP=/bin/grep"                                                             NL
577251881Speter"SED=/bin/sed"                                                               NL
578251881Speter""                                                                           NL
579251881Speter"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\"                        NL
580251881Speter"            $GREP '^Owner: ' | $SED 's/Owner: //'`"                         NL
581251881Speter""                                                                           NL
582251881Speter"# If we get no result from svnlook, there's no lock, allow the lock to"     NL
583251881Speter"# happen:"                                                                  NL
584251881Speter"if [ \"$LOCK_OWNER\" = \"\" ]; then"                                        NL
585251881Speter"  exit 0"                                                                   NL
586251881Speter"fi"                                                                         NL
587251881Speter""                                                                           NL
588251881Speter"# If the person locking matches the lock's owner, allow the lock to"        NL
589251881Speter"# happen:"                                                                  NL
590251881Speter"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then"                                   NL
591251881Speter"  exit 0"                                                                   NL
592251881Speter"fi"                                                                         NL
593251881Speter""                                                                           NL
594251881Speter"# Otherwise, we've got an owner mismatch, so return failure:"               NL
595251881Speter"echo \"Error: $PATH already locked by ${LOCK_OWNER}.\" 1>&2"                NL
596251881Speter"exit 1"                                                                     NL;
597251881Speter
598299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
599299742Sdim                                     description, script, pool),
600299742Sdim            _("Creating pre-lock hook"));
601299742Sdim
602251881Speter#undef SCRIPT_NAME
603251881Speter
604251881Speter
605251881Speter  /* Pre-unlock hook. */
606251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_UNLOCK
607251881Speter
608299742Sdim  description =
609251881Speter"# PRE-UNLOCK HOOK"                                                          NL
610251881Speter"#"                                                                          NL
611251881Speter"# The pre-unlock hook is invoked before an exclusive lock is"               NL
612251881Speter"# destroyed.  Subversion runs this hook by invoking a program "             NL
613251881Speter"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which"      NL
614251881Speter"# this file is a template), with the following ordered arguments:"          NL
615251881Speter"#"                                                                          NL
616251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
617251881Speter"#   [2] PATH         (the path in the repository about to be unlocked)"     NL
618251881Speter"#   [3] USER         (the user destroying the lock)"                        NL
619251881Speter"#   [4] TOKEN        (the lock token to be destroyed)"                      NL
620251881Speter"#   [5] BREAK-UNLOCK (1 if the user is breaking the lock, else 0)"          NL
621251881Speter"#"                                                                          NL
622251881Speter"# If the hook program exits with success, the lock is destroyed; but"       NL
623251881Speter"# if it exits with failure (non-zero), the unlock action is aborted"        NL
624299742Sdim"# and STDERR is returned to the client."                                    NL;
625299742Sdim  script =
626251881Speter"REPOS=\"$1\""                                                               NL
627251881Speter"PATH=\"$2\""                                                                NL
628251881Speter"USER=\"$3\""                                                                NL
629251881Speter"TOKEN=\"$4\""                                                               NL
630251881Speter"BREAK=\"$5\""                                                               NL
631251881Speter""                                                                           NL
632251881Speter"# If a lock is owned by a different person, don't allow it be broken."      NL
633251881Speter"# (Maybe this script could send email to the lock owner?)"                  NL
634251881Speter""                                                                           NL
635251881Speter"SVNLOOK=" SVN_BINDIR "/svnlook"                                             NL
636251881Speter"GREP=/bin/grep"                                                             NL
637251881Speter"SED=/bin/sed"                                                               NL
638251881Speter""                                                                           NL
639251881Speter"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\"                        NL
640251881Speter"            $GREP '^Owner: ' | $SED 's/Owner: //'`"                         NL
641251881Speter""                                                                           NL
642251881Speter"# If we get no result from svnlook, there's no lock, return success:"       NL
643251881Speter"if [ \"$LOCK_OWNER\" = \"\" ]; then"                                        NL
644251881Speter"  exit 0"                                                                   NL
645251881Speter"fi"                                                                         NL
646251881Speter""                                                                           NL
647251881Speter"# If the person unlocking matches the lock's owner, return success:"        NL
648251881Speter"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then"                                   NL
649251881Speter"  exit 0"                                                                   NL
650251881Speter"fi"                                                                         NL
651251881Speter""                                                                           NL
652251881Speter"# Otherwise, we've got an owner mismatch, so return failure:"               NL
653251881Speter"echo \"Error: $PATH locked by ${LOCK_OWNER}.\" 1>&2"                        NL
654251881Speter"exit 1"                                                                     NL;
655251881Speter
656299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
657299742Sdim                                     description, script, pool),
658299742Sdim            _("Creating pre-unlock hook"));
659299742Sdim
660251881Speter#undef SCRIPT_NAME
661251881Speter
662251881Speter
663251881Speter  /* Post-commit hook. */
664251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_COMMIT
665251881Speter
666299742Sdim  description =
667251881Speter"# POST-COMMIT HOOK"                                                         NL
668251881Speter"#"                                                                          NL
669251881Speter"# The post-commit hook is invoked after a commit.  Subversion runs"         NL
670251881Speter"# this hook by invoking a program (script, executable, binary, etc.)"       NL
671251881Speter"# named '"SCRIPT_NAME"' (for which this file is a template) with the "      NL
672251881Speter"# following ordered arguments:"                                             NL
673251881Speter"#"                                                                          NL
674251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
675251881Speter"#   [2] REV          (the number of the revision just committed)"           NL
676251881Speter"#   [3] TXN-NAME     (the name of the transaction that has become REV)"     NL
677251881Speter"#"                                                                          NL
678251881Speter"# Because the commit has already completed and cannot be undone,"           NL
679251881Speter"# the exit code of the hook program is ignored.  The hook program"          NL
680251881Speter"# can use the 'svnlook' utility to help it examine the"                     NL
681299742Sdim"# newly-committed tree."                                                    NL;
682299742Sdim  script =
683251881Speter"REPOS=\"$1\""                                                               NL
684251881Speter"REV=\"$2\""                                                                 NL
685251881Speter"TXN_NAME=\"$3\""                                                            NL
686251881Speter                                                                             NL
687251881Speter"mailer.py commit \"$REPOS\" \"$REV\" /path/to/mailer.conf"                  NL;
688251881Speter
689299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
690299742Sdim                                     description, script, pool),
691299742Sdim            _("Creating post-commit hook"));
692299742Sdim
693251881Speter#undef SCRIPT_NAME
694251881Speter
695251881Speter
696251881Speter  /* Post-lock hook. */
697251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_LOCK
698251881Speter
699299742Sdim  description =
700251881Speter"# POST-LOCK HOOK"                                                           NL
701251881Speter"#"                                                                          NL
702251881Speter"# The post-lock hook is run after a path is locked.  Subversion runs"       NL
703251881Speter"# this hook by invoking a program (script, executable, binary, etc.)"       NL
704251881Speter"# named '"SCRIPT_NAME"' (for which this file is a template) with the "      NL
705251881Speter"# following ordered arguments:"                                             NL
706251881Speter"#"                                                                          NL
707251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
708251881Speter"#   [2] USER         (the user who created the lock)"                       NL
709251881Speter"#"                                                                          NL
710299742Sdim"# The paths that were just locked are passed to the hook via STDIN."        NL
711251881Speter"#"                                                                          NL
712299742Sdim"# Because the locks have already been created and cannot be undone,"        NL
713251881Speter"# the exit code of the hook program is ignored.  The hook program"          NL
714299742Sdim"# can use the 'svnlook' utility to examine the paths in the repository"     NL
715309512Speter"# but since the hook is invoked asynchronously the newly-created locks"     NL
716299742Sdim"# may no longer be present."                                                NL;
717299742Sdim  script =
718251881Speter"REPOS=\"$1\""                                                               NL
719251881Speter"USER=\"$2\""                                                                NL
720251881Speter""                                                                           NL
721251881Speter"# Send email to interested parties, let them know a lock was created:"      NL
722251881Speter"mailer.py lock \"$REPOS\" \"$USER\" /path/to/mailer.conf"                   NL;
723251881Speter
724299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
725299742Sdim                                     description, script, pool),
726299742Sdim            _("Creating post-lock hook"));
727299742Sdim
728251881Speter#undef SCRIPT_NAME
729251881Speter
730251881Speter
731251881Speter  /* Post-unlock hook. */
732251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_UNLOCK
733251881Speter
734299742Sdim  description =
735251881Speter"# POST-UNLOCK HOOK"                                                         NL
736251881Speter"#"                                                                          NL
737251881Speter"# The post-unlock hook runs after a path is unlocked.  Subversion runs"     NL
738251881Speter"# this hook by invoking a program (script, executable, binary, etc.)"       NL
739251881Speter"# named '"SCRIPT_NAME"' (for which this file is a template) with the "      NL
740251881Speter"# following ordered arguments:"                                             NL
741251881Speter"#"                                                                          NL
742251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
743251881Speter"#   [2] USER         (the user who destroyed the lock)"                     NL
744251881Speter"#"                                                                          NL
745299742Sdim"# The paths that were just unlocked are passed to the hook via STDIN."      NL
746251881Speter"#"                                                                          NL
747251881Speter"# Because the lock has already been destroyed and cannot be undone,"        NL
748299742Sdim"# the exit code of the hook program is ignored."                            NL;
749299742Sdim  script =
750251881Speter"REPOS=\"$1\""                                                               NL
751251881Speter"USER=\"$2\""                                                                NL
752251881Speter""                                                                           NL
753251881Speter"# Send email to interested parties, let them know a lock was removed:"      NL
754251881Speter"mailer.py unlock \"$REPOS\" \"$USER\" /path/to/mailer.conf"                 NL;
755251881Speter
756299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
757299742Sdim                                     description, script, pool),
758299742Sdim            _("Creating post-unlock hook"));
759299742Sdim
760251881Speter#undef SCRIPT_NAME
761251881Speter
762251881Speter
763251881Speter  /* Post-revprop-change hook. */
764251881Speter#define SCRIPT_NAME SVN_REPOS__HOOK_POST_REVPROP_CHANGE
765251881Speter
766299742Sdim  description =
767251881Speter"# POST-REVPROP-CHANGE HOOK"                                                 NL
768251881Speter"#"                                                                          NL
769251881Speter"# The post-revprop-change hook is invoked after a revision property"        NL
770251881Speter"# has been added, modified or deleted.  Subversion runs this hook by"       NL
771251881Speter"# invoking a program (script, executable, binary, etc.) named"              NL
772251881Speter"# '"SCRIPT_NAME"' (for which this file is a template), with the"            NL
773251881Speter"# following ordered arguments:"                                             NL
774251881Speter"#"                                                                          NL
775251881Speter"#   [1] REPOS-PATH   (the path to this repository)"                         NL
776251881Speter"#   [2] REV          (the revision that was tweaked)"                       NL
777251881Speter"#   [3] USER         (the username of the person tweaking the property)"    NL
778251881Speter"#   [4] PROPNAME     (the property that was changed)"                       NL
779251881Speter"#   [5] ACTION       (the property was 'A'dded, 'M'odified, or 'D'eleted)"  NL
780251881Speter"#"                                                                          NL
781251881Speter"#   [STDIN] PROPVAL  ** the old property value is passed via STDIN."        NL
782251881Speter"#"                                                                          NL
783251881Speter"# Because the propchange has already completed and cannot be undone,"       NL
784251881Speter"# the exit code of the hook program is ignored.  The hook program"          NL
785251881Speter"# can use the 'svnlook' utility to help it examine the"                     NL
786299742Sdim"# new property value."                                                      NL;
787299742Sdim  script =
788251881Speter"REPOS=\"$1\""                                                               NL
789251881Speter"REV=\"$2\""                                                                 NL
790251881Speter"USER=\"$3\""                                                                NL
791251881Speter"PROPNAME=\"$4\""                                                            NL
792251881Speter"ACTION=\"$5\""                                                              NL
793251881Speter""                                                                           NL
794251881Speter"mailer.py propchange2 \"$REPOS\" \"$REV\" \"$USER\" \"$PROPNAME\" "
795251881Speter"\"$ACTION\" /path/to/mailer.conf"                                           NL;
796251881Speter
797299742Sdim  SVN_ERR_W(write_hook_template_file(repos, SCRIPT_NAME,
798299742Sdim                                     description, script, pool),
799299742Sdim            _("Creating post-revprop-change hook"));
800299742Sdim
801251881Speter#undef SCRIPT_NAME
802251881Speter
803251881Speter  return SVN_NO_ERROR;
804251881Speter}
805251881Speter
806251881Speterstatic svn_error_t *
807251881Spetercreate_conf(svn_repos_t *repos, apr_pool_t *pool)
808251881Speter{
809251881Speter  SVN_ERR_W(create_repos_dir(repos->conf_path, pool),
810251881Speter            _("Creating conf directory"));
811251881Speter
812251881Speter  /* Write a default template for svnserve.conf. */
813251881Speter  {
814251881Speter    static const char * const svnserve_conf_contents =
815251881Speter"### This file controls the configuration of the svnserve daemon, if you"    NL
816251881Speter"### use it to allow access to this repository.  (If you only allow"         NL
817251881Speter"### access through http: and/or file: URLs, then this file is"              NL
818251881Speter"### irrelevant.)"                                                           NL
819251881Speter""                                                                           NL
820251881Speter"### Visit http://subversion.apache.org/ for more information."              NL
821251881Speter""                                                                           NL
822251881Speter"[general]"                                                                  NL
823251881Speter"### The anon-access and auth-access options control access to the"          NL
824251881Speter"### repository for unauthenticated (a.k.a. anonymous) users and"            NL
825251881Speter"### authenticated users, respectively."                                     NL
826251881Speter"### Valid values are \"write\", \"read\", and \"none\"."                    NL
827251881Speter"### Setting the value to \"none\" prohibits both reading and writing;"      NL
828251881Speter"### \"read\" allows read-only access, and \"write\" allows complete "       NL
829251881Speter"### read/write access to the repository."                                   NL
830251881Speter"### The sample settings below are the defaults and specify that anonymous"  NL
831251881Speter"### users have read-only access to the repository, while authenticated"     NL
832251881Speter"### users have read and write access to the repository."                    NL
833251881Speter"# anon-access = read"                                                       NL
834251881Speter"# auth-access = write"                                                      NL
835251881Speter"### The password-db option controls the location of the password"           NL
836251881Speter"### database file.  Unless you specify a path starting with a /,"           NL
837251881Speter"### the file's location is relative to the directory containing"            NL
838251881Speter"### this configuration file."                                               NL
839251881Speter"### If SASL is enabled (see below), this file will NOT be used."            NL
840251881Speter"### Uncomment the line below to use the default password file."             NL
841251881Speter"# password-db = passwd"                                                     NL
842251881Speter"### The authz-db option controls the location of the authorization"         NL
843251881Speter"### rules for path-based access control.  Unless you specify a path"        NL
844251881Speter"### starting with a /, the file's location is relative to the"              NL
845251881Speter"### directory containing this file.  The specified path may be a"           NL
846251881Speter"### repository relative URL (^/) or an absolute file:// URL to a text"      NL
847251881Speter"### file in a Subversion repository.  If you don't specify an authz-db,"    NL
848251881Speter"### no path-based access control is done."                                  NL
849251881Speter"### Uncomment the line below to use the default authorization file."        NL
850251881Speter"# authz-db = " SVN_REPOS__CONF_AUTHZ                                        NL
851299742Sdim"### The groups-db option controls the location of the file with the"        NL
852299742Sdim"### group definitions and allows maintaining groups separately from the"    NL
853299742Sdim"### authorization rules.  The groups-db file is of the same format as the"  NL
854299742Sdim"### authz-db file and should contain a single [groups] section with the"    NL
855299742Sdim"### group definitions.  If the option is enabled, the authz-db file cannot" NL
856299742Sdim"### contain a [groups] section.  Unless you specify a path starting with"   NL
857299742Sdim"### a /, the file's location is relative to the directory containing this"  NL
858299742Sdim"### file.  The specified path may be a repository relative URL (^/) or an"  NL
859299742Sdim"### absolute file:// URL to a text file in a Subversion repository."        NL
860299742Sdim"### This option is not being used by default."                              NL
861251881Speter"# groups-db = " SVN_REPOS__CONF_GROUPS                                      NL
862251881Speter"### This option specifies the authentication realm of the repository."      NL
863251881Speter"### If two repositories have the same authentication realm, they should"    NL
864251881Speter"### have the same password database, and vice versa.  The default realm"    NL
865251881Speter"### is repository's uuid."                                                  NL
866251881Speter"# realm = My First Repository"                                              NL
867251881Speter"### The force-username-case option causes svnserve to case-normalize"       NL
868251881Speter"### usernames before comparing them against the authorization rules in the" NL
869251881Speter"### authz-db file configured above.  Valid values are \"upper\" (to upper-" NL
870251881Speter"### case the usernames), \"lower\" (to lowercase the usernames), and"       NL
871251881Speter"### \"none\" (to compare usernames as-is without case conversion, which"    NL
872251881Speter"### is the default behavior)."                                              NL
873251881Speter"# force-username-case = none"                                               NL
874251881Speter"### The hooks-env options specifies a path to the hook script environment " NL
875251881Speter"### configuration file. This option overrides the per-repository default"   NL
876251881Speter"### and can be used to configure the hook script environment for multiple " NL
877251881Speter"### repositories in a single file, if an absolute path is specified."       NL
878251881Speter"### Unless you specify an absolute path, the file's location is relative"   NL
879251881Speter"### to the directory containing this file."                                 NL
880251881Speter"# hooks-env = " SVN_REPOS__CONF_HOOKS_ENV                                   NL
881251881Speter""                                                                           NL
882251881Speter"[sasl]"                                                                     NL
883251881Speter"### This option specifies whether you want to use the Cyrus SASL"           NL
884251881Speter"### library for authentication. Default is false."                          NL
885251881Speter"### This section will be ignored if svnserve is not built with Cyrus"       NL
886251881Speter"### SASL support; to check, run 'svnserve --version' and look for a line"   NL
887251881Speter"### reading 'Cyrus SASL authentication is available.'"                      NL
888251881Speter"# use-sasl = true"                                                          NL
889251881Speter"### These options specify the desired strength of the security layer"       NL
890251881Speter"### that you want SASL to provide. 0 means no encryption, 1 means"          NL
891251881Speter"### integrity-checking only, values larger than 1 are correlated"           NL
892251881Speter"### to the effective key length for encryption (e.g. 128 means 128-bit"     NL
893251881Speter"### encryption). The values below are the defaults."                        NL
894251881Speter"# min-encryption = 0"                                                       NL
895251881Speter"# max-encryption = 256"                                                     NL;
896251881Speter
897251881Speter    SVN_ERR_W(svn_io_file_create(svn_repos_svnserve_conf(repos, pool),
898251881Speter                                 svnserve_conf_contents, pool),
899251881Speter              _("Creating svnserve.conf file"));
900251881Speter  }
901251881Speter
902251881Speter  {
903251881Speter    static const char * const passwd_contents =
904251881Speter"### This file is an example password file for svnserve."                    NL
905251881Speter"### Its format is similar to that of svnserve.conf. As shown in the"        NL
906251881Speter"### example below it contains one section labelled [users]."                NL
907251881Speter"### The name and password for each user follow, one account per line."      NL
908251881Speter""                                                                           NL
909251881Speter"[users]"                                                                    NL
910251881Speter"# harry = harryssecret"                                                     NL
911251881Speter"# sally = sallyssecret"                                                     NL;
912251881Speter
913251881Speter    SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
914251881Speter                                                 SVN_REPOS__CONF_PASSWD,
915251881Speter                                                 pool),
916251881Speter                                 passwd_contents, pool),
917251881Speter              _("Creating passwd file"));
918251881Speter  }
919251881Speter
920251881Speter  {
921251881Speter    static const char * const authz_contents =
922251881Speter"### This file is an example authorization file for svnserve."               NL
923251881Speter"### Its format is identical to that of mod_authz_svn authorization"         NL
924251881Speter"### files."                                                                 NL
925251881Speter"### As shown below each section defines authorizations for the path and"    NL
926251881Speter"### (optional) repository specified by the section name."                   NL
927251881Speter"### The authorizations follow. An authorization line can refer to:"         NL
928251881Speter"###  - a single user,"                                                      NL
929251881Speter"###  - a group of users defined in a special [groups] section,"             NL
930251881Speter"###  - an alias defined in a special [aliases] section,"                    NL
931251881Speter"###  - all authenticated users, using the '$authenticated' token,"          NL
932251881Speter"###  - only anonymous users, using the '$anonymous' token,"                 NL
933251881Speter"###  - anyone, using the '*' wildcard."                                     NL
934251881Speter"###"                                                                        NL
935251881Speter"### A match can be inverted by prefixing the rule with '~'. Rules can"      NL
936251881Speter"### grant read ('r') access, read-write ('rw') access, or no access"        NL
937251881Speter"### ('')."                                                                  NL
938251881Speter""                                                                           NL
939251881Speter"[aliases]"                                                                  NL
940251881Speter"# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average"  NL
941251881Speter""                                                                           NL
942251881Speter"[groups]"                                                                   NL
943251881Speter"# harry_and_sally = harry,sally"                                            NL
944251881Speter"# harry_sally_and_joe = harry,sally,&joe"                                   NL
945251881Speter""                                                                           NL
946251881Speter"# [/foo/bar]"                                                               NL
947251881Speter"# harry = rw"                                                               NL
948251881Speter"# &joe = r"                                                                 NL
949251881Speter"# * ="                                                                      NL
950251881Speter""                                                                           NL
951251881Speter"# [repository:/baz/fuz]"                                                    NL
952251881Speter"# @harry_and_sally = rw"                                                    NL
953251881Speter"# * = r"                                                                    NL;
954251881Speter
955251881Speter    SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
956251881Speter                                                 SVN_REPOS__CONF_AUTHZ,
957251881Speter                                                 pool),
958251881Speter                                 authz_contents, pool),
959251881Speter              _("Creating authz file"));
960251881Speter  }
961251881Speter
962251881Speter  {
963251881Speter    static const char * const hooks_env_contents =
964251881Speter"### This file is an example hook script environment configuration file."    NL
965251881Speter"### Hook scripts run in an empty environment by default."                   NL
966251881Speter"### As shown below each section defines environment variables for a"        NL
967251881Speter"### particular hook script. The [default] section defines environment"      NL
968251881Speter"### variables for all hook scripts, unless overridden by a hook-specific"   NL
969251881Speter"### section."                                                               NL
970251881Speter""                                                                           NL
971251881Speter"### This example configures a UTF-8 locale for all hook scripts, so that "  NL
972251881Speter"### special characters, such as umlauts, may be printed to stderr."         NL
973251881Speter"### If UTF-8 is used with a mod_dav_svn server, the SVNUseUTF8 option must" NL
974251881Speter"### also be set to 'yes' in httpd.conf."                                    NL
975251881Speter"### With svnserve, the LANG environment variable of the svnserve process"   NL
976251881Speter"### must be set to the same value as given here."                           NL
977251881Speter"[default]"                                                                  NL
978251881Speter"LANG = en_US.UTF-8"                                                         NL
979251881Speter""                                                                           NL
980251881Speter"### This sets the PATH environment variable for the pre-commit hook."       NL
981251881Speter"[pre-commit]"                                                               NL
982251881Speter"PATH = /usr/local/bin:/usr/bin:/usr/sbin"                                   NL;
983251881Speter
984251881Speter    SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
985251881Speter                                                 SVN_REPOS__CONF_HOOKS_ENV \
986251881Speter                                                 SVN_REPOS__HOOK_DESC_EXT,
987251881Speter                                                 pool),
988251881Speter                                 hooks_env_contents, pool),
989251881Speter              _("Creating hooks-env file"));
990251881Speter  }
991251881Speter
992251881Speter  return SVN_NO_ERROR;
993251881Speter}
994251881Speter
995251881Spetersvn_error_t *
996251881Spetersvn_repos_hooks_setenv(svn_repos_t *repos,
997251881Speter                       const char *hooks_env_path,
998251881Speter                       apr_pool_t *scratch_pool)
999251881Speter{
1000251881Speter  if (hooks_env_path == NULL)
1001251881Speter    repos->hooks_env_path = svn_dirent_join(repos->conf_path,
1002251881Speter                                            SVN_REPOS__CONF_HOOKS_ENV,
1003251881Speter                                            repos->pool);
1004251881Speter  else if (!svn_dirent_is_absolute(hooks_env_path))
1005251881Speter    repos->hooks_env_path = svn_dirent_join(repos->conf_path,
1006251881Speter                                            hooks_env_path,
1007251881Speter                                            repos->pool);
1008251881Speter  else
1009251881Speter    repos->hooks_env_path = apr_pstrdup(repos->pool, hooks_env_path);
1010251881Speter
1011251881Speter  return SVN_NO_ERROR;
1012251881Speter}
1013251881Speter
1014251881Speter/* Allocate and return a new svn_repos_t * object, initializing the
1015251881Speter   directory pathname members based on PATH, and initializing the
1016251881Speter   REPOSITORY_CAPABILITIES member.
1017251881Speter   The members FS, FORMAT, and FS_TYPE are *not* initialized (they are null),
1018251881Speter   and it is the caller's responsibility to fill them in if needed.  */
1019251881Speterstatic svn_repos_t *
1020251881Spetercreate_svn_repos_t(const char *path, apr_pool_t *pool)
1021251881Speter{
1022251881Speter  svn_repos_t *repos = apr_pcalloc(pool, sizeof(*repos));
1023251881Speter
1024251881Speter  repos->path = apr_pstrdup(pool, path);
1025251881Speter  repos->db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
1026251881Speter  repos->conf_path = svn_dirent_join(path, SVN_REPOS__CONF_DIR, pool);
1027251881Speter  repos->hook_path = svn_dirent_join(path, SVN_REPOS__HOOK_DIR, pool);
1028251881Speter  repos->lock_path = svn_dirent_join(path, SVN_REPOS__LOCK_DIR, pool);
1029251881Speter  repos->hooks_env_path = NULL;
1030251881Speter  repos->repository_capabilities = apr_hash_make(pool);
1031251881Speter  repos->pool = pool;
1032251881Speter
1033251881Speter  return repos;
1034251881Speter}
1035251881Speter
1036251881Speter
1037251881Speterstatic svn_error_t *
1038251881Spetercreate_repos_structure(svn_repos_t *repos,
1039251881Speter                       const char *path,
1040251881Speter                       apr_hash_t *fs_config,
1041251881Speter                       apr_pool_t *pool)
1042251881Speter{
1043251881Speter  /* Create the top-level repository directory. */
1044251881Speter  SVN_ERR_W(create_repos_dir(path, pool),
1045251881Speter            _("Could not create top-level directory"));
1046251881Speter
1047251881Speter  /* Create the DAV sandbox directory if pre-1.4 or pre-1.5-compatible. */
1048251881Speter  if (fs_config
1049251881Speter      && (svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)
1050251881Speter          || svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)))
1051251881Speter    {
1052251881Speter      const char *dav_path = svn_dirent_join(repos->path,
1053251881Speter                                             SVN_REPOS__DAV_DIR, pool);
1054251881Speter      SVN_ERR_W(create_repos_dir(dav_path, pool),
1055251881Speter                _("Creating DAV sandbox dir"));
1056251881Speter    }
1057251881Speter
1058251881Speter  /* Create the lock directory.  */
1059251881Speter  SVN_ERR(create_locks(repos, pool));
1060251881Speter
1061251881Speter  /* Create the hooks directory.  */
1062251881Speter  SVN_ERR(create_hooks(repos, pool));
1063251881Speter
1064251881Speter  /* Create the conf directory.  */
1065251881Speter  SVN_ERR(create_conf(repos, pool));
1066251881Speter
1067251881Speter  /* Write the top-level README file. */
1068251881Speter  {
1069251881Speter    const char * const readme_header =
1070251881Speter      "This is a Subversion repository; use the 'svnadmin' and 'svnlook' "   NL
1071251881Speter      "tools to examine it.  Do not add, delete, or modify files here "      NL
1072251881Speter      "unless you know how to avoid corrupting the repository."              NL
1073251881Speter      ""                                                                     NL;
1074251881Speter    const char * const readme_bdb_insert =
1075251881Speter      "The directory \"" SVN_REPOS__DB_DIR "\" contains a Berkeley DB environment."  NL
1076251881Speter      "you may need to tweak the values in \"" SVN_REPOS__DB_DIR "/DB_CONFIG\" to match the"  NL
1077251881Speter      "requirements of your site."                                           NL
1078251881Speter      ""                                                                     NL;
1079251881Speter    const char * const readme_footer =
1080251881Speter      "Visit http://subversion.apache.org/ for more information."            NL;
1081251881Speter    apr_file_t *f;
1082251881Speter    apr_size_t written;
1083251881Speter
1084251881Speter    SVN_ERR(svn_io_file_open(&f,
1085251881Speter                             svn_dirent_join(path, SVN_REPOS__README, pool),
1086251881Speter                             (APR_WRITE | APR_CREATE | APR_EXCL),
1087251881Speter                             APR_OS_DEFAULT, pool));
1088251881Speter
1089251881Speter    SVN_ERR(svn_io_file_write_full(f, readme_header, strlen(readme_header),
1090251881Speter                                   &written, pool));
1091251881Speter    if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
1092251881Speter      SVN_ERR(svn_io_file_write_full(f, readme_bdb_insert,
1093251881Speter                                     strlen(readme_bdb_insert),
1094251881Speter                                     &written, pool));
1095251881Speter    SVN_ERR(svn_io_file_write_full(f, readme_footer, strlen(readme_footer),
1096251881Speter                                   &written, pool));
1097251881Speter
1098251881Speter    return svn_io_file_close(f, pool);
1099251881Speter  }
1100251881Speter}
1101251881Speter
1102251881Speter
1103251881Speter/* There is, at present, nothing within the direct responsibility
1104251881Speter   of libsvn_repos which requires locking.  For historical compatibility
1105251881Speter   reasons, the BDB libsvn_fs backend does not do its own locking, expecting
1106251881Speter   libsvn_repos to do the locking for it.  Here we take care of that
1107251881Speter   backend-specific requirement.
1108251881Speter   The kind of lock is controlled by EXCLUSIVE and NONBLOCKING.
1109251881Speter   The lock is scoped to POOL.  */
1110251881Speterstatic svn_error_t *
1111251881Speterlock_repos(svn_repos_t *repos,
1112251881Speter           svn_boolean_t exclusive,
1113251881Speter           svn_boolean_t nonblocking,
1114251881Speter           apr_pool_t *pool)
1115251881Speter{
1116251881Speter  if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
1117251881Speter    {
1118251881Speter      svn_error_t *err;
1119251881Speter      const char *lockfile_path = svn_repos_db_lockfile(repos, pool);
1120251881Speter
1121251881Speter      err = svn_io_file_lock2(lockfile_path, exclusive, nonblocking, pool);
1122251881Speter      if (err != NULL && APR_STATUS_IS_EAGAIN(err->apr_err))
1123251881Speter        return svn_error_trace(err);
1124251881Speter      SVN_ERR_W(err, _("Error opening db lockfile"));
1125251881Speter    }
1126251881Speter  return SVN_NO_ERROR;
1127251881Speter}
1128251881Speter
1129251881Speter
1130251881Spetersvn_error_t *
1131251881Spetersvn_repos_create(svn_repos_t **repos_p,
1132251881Speter                 const char *path,
1133251881Speter                 const char *unused_1,
1134251881Speter                 const char *unused_2,
1135251881Speter                 apr_hash_t *config,
1136251881Speter                 apr_hash_t *fs_config,
1137299742Sdim                 apr_pool_t *result_pool)
1138251881Speter{
1139251881Speter  svn_repos_t *repos;
1140251881Speter  svn_error_t *err;
1141299742Sdim  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
1142251881Speter  const char *root_path;
1143251881Speter  const char *local_abspath;
1144251881Speter
1145251881Speter  /* Allocate a repository object, filling in the format we will create. */
1146299742Sdim  repos = create_svn_repos_t(path, result_pool);
1147251881Speter  repos->format = SVN_REPOS__FORMAT_NUMBER;
1148251881Speter
1149251881Speter  /* Discover the type of the filesystem we are about to create. */
1150251881Speter  repos->fs_type = svn_hash__get_cstring(fs_config, SVN_FS_CONFIG_FS_TYPE,
1151251881Speter                                         DEFAULT_FS_TYPE);
1152251881Speter  if (svn_hash__get_bool(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, FALSE))
1153251881Speter    repos->format = SVN_REPOS__FORMAT_NUMBER_LEGACY;
1154251881Speter
1155251881Speter  /* Don't create a repository inside another repository. */
1156299742Sdim  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
1157299742Sdim  root_path = svn_repos_find_root_path(local_abspath, scratch_pool);
1158251881Speter  if (root_path != NULL)
1159251881Speter    {
1160251881Speter      if (strcmp(root_path, local_abspath) == 0)
1161251881Speter        return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1162251881Speter                                 _("'%s' is an existing repository"),
1163299742Sdim                                 svn_dirent_local_style(root_path,
1164299742Sdim                                                        scratch_pool));
1165251881Speter      else
1166251881Speter        return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1167251881Speter                                 _("'%s' is a subdirectory of an existing "
1168251881Speter                                   "repository " "rooted at '%s'"),
1169299742Sdim                                 svn_dirent_local_style(local_abspath,
1170299742Sdim                                                        scratch_pool),
1171299742Sdim                                 svn_dirent_local_style(root_path,
1172299742Sdim                                                        scratch_pool));
1173251881Speter    }
1174251881Speter
1175251881Speter  /* Create the various files and subdirectories for the repository. */
1176299742Sdim  SVN_ERR_W(create_repos_structure(repos, path, fs_config, scratch_pool),
1177251881Speter            _("Repository creation failed"));
1178251881Speter
1179251881Speter  /* Lock if needed. */
1180299742Sdim  SVN_ERR(lock_repos(repos, FALSE, FALSE, scratch_pool));
1181251881Speter
1182251881Speter  /* Create an environment for the filesystem. */
1183299742Sdim  if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config,
1184299742Sdim                           result_pool)))
1185251881Speter    {
1186251881Speter      /* If there was an error making the filesytem, e.g. unknown/supported
1187251881Speter       * filesystem type.  Clean up after ourselves.  Yes this is safe because
1188251881Speter       * create_repos_structure will fail if the path existed before we started
1189251881Speter       * so we can't accidentally remove a directory that previously existed.
1190251881Speter       */
1191299742Sdim      svn_pool_destroy(scratch_pool); /* Release lock to allow deleting dir */
1192251881Speter
1193251881Speter      return svn_error_trace(
1194251881Speter                   svn_error_compose_create(
1195251881Speter                        err,
1196299742Sdim                        svn_io_remove_dir2(path, FALSE, NULL, NULL,
1197299742Sdim                                           result_pool)));
1198251881Speter    }
1199251881Speter
1200251881Speter  /* This repository is ready.  Stamp it with a format number. */
1201251881Speter  SVN_ERR(svn_io_write_version_file
1202299742Sdim          (svn_dirent_join(path, SVN_REPOS__FORMAT, scratch_pool),
1203299742Sdim           repos->format, scratch_pool));
1204251881Speter
1205299742Sdim  svn_pool_destroy(scratch_pool); /* Release lock */
1206299742Sdim
1207251881Speter  *repos_p = repos;
1208251881Speter  return SVN_NO_ERROR;
1209251881Speter}
1210251881Speter
1211251881Speter
1212251881Speter/* Check if @a path is the root of a repository by checking if the
1213251881Speter * path contains the expected files and directories.  Return TRUE
1214251881Speter * on errors (which would be permission errors, probably) so that
1215251881Speter * we the user will see them after we try to open the repository
1216251881Speter * for real.  */
1217251881Speterstatic svn_boolean_t
1218251881Spetercheck_repos_path(const char *path,
1219251881Speter                 apr_pool_t *pool)
1220251881Speter{
1221251881Speter  svn_node_kind_t kind;
1222251881Speter  svn_error_t *err;
1223251881Speter
1224251881Speter  err = svn_io_check_path(svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
1225251881Speter                          &kind, pool);
1226251881Speter  if (err)
1227251881Speter    {
1228251881Speter      svn_error_clear(err);
1229251881Speter      return TRUE;
1230251881Speter    }
1231251881Speter  if (kind != svn_node_file)
1232251881Speter    return FALSE;
1233251881Speter
1234251881Speter  /* Check the db/ subdir, but allow it to be a symlink (Subversion
1235251881Speter     works just fine if it's a symlink). */
1236251881Speter  err = svn_io_check_resolved_path
1237251881Speter    (svn_dirent_join(path, SVN_REPOS__DB_DIR, pool), &kind, pool);
1238251881Speter  if (err)
1239251881Speter    {
1240251881Speter      svn_error_clear(err);
1241251881Speter      return TRUE;
1242251881Speter    }
1243251881Speter  if (kind != svn_node_dir)
1244251881Speter    return FALSE;
1245251881Speter
1246251881Speter  return TRUE;
1247251881Speter}
1248251881Speter
1249251881Speter
1250251881Speter/* Verify that REPOS's format is suitable.
1251251881Speter   Use POOL for temporary allocation. */
1252251881Speterstatic svn_error_t *
1253251881Spetercheck_repos_format(svn_repos_t *repos,
1254251881Speter                   apr_pool_t *pool)
1255251881Speter{
1256251881Speter  int format;
1257251881Speter  const char *format_path;
1258251881Speter
1259251881Speter  format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, pool);
1260251881Speter  SVN_ERR(svn_io_read_version_file(&format, format_path, pool));
1261251881Speter
1262251881Speter  if (format != SVN_REPOS__FORMAT_NUMBER &&
1263251881Speter      format != SVN_REPOS__FORMAT_NUMBER_LEGACY)
1264251881Speter    {
1265251881Speter      return svn_error_createf
1266251881Speter        (SVN_ERR_REPOS_UNSUPPORTED_VERSION, NULL,
1267251881Speter         _("Expected repository format '%d' or '%d'; found format '%d'"),
1268251881Speter         SVN_REPOS__FORMAT_NUMBER_LEGACY, SVN_REPOS__FORMAT_NUMBER,
1269251881Speter         format);
1270251881Speter    }
1271251881Speter
1272251881Speter  repos->format = format;
1273251881Speter
1274251881Speter  return SVN_NO_ERROR;
1275251881Speter}
1276251881Speter
1277251881Speter
1278251881Speter/* Set *REPOS_P to a repository at PATH which has been opened.
1279251881Speter   See lock_repos() above regarding EXCLUSIVE and NONBLOCKING.
1280251881Speter   OPEN_FS indicates whether the Subversion filesystem should be opened,
1281251881Speter   the handle being placed into repos->fs.
1282251881Speter   Do all allocation in POOL.  */
1283251881Speterstatic svn_error_t *
1284251881Speterget_repos(svn_repos_t **repos_p,
1285251881Speter          const char *path,
1286251881Speter          svn_boolean_t exclusive,
1287251881Speter          svn_boolean_t nonblocking,
1288251881Speter          svn_boolean_t open_fs,
1289251881Speter          apr_hash_t *fs_config,
1290299742Sdim          apr_pool_t *result_pool,
1291299742Sdim          apr_pool_t *scratch_pool)
1292251881Speter{
1293251881Speter  svn_repos_t *repos;
1294299742Sdim  const char *fs_type;
1295251881Speter
1296251881Speter  /* Allocate a repository object. */
1297299742Sdim  repos = create_svn_repos_t(path, result_pool);
1298251881Speter
1299251881Speter  /* Verify the validity of our repository format. */
1300299742Sdim  SVN_ERR(check_repos_format(repos, scratch_pool));
1301251881Speter
1302251881Speter  /* Discover the FS type. */
1303299742Sdim  SVN_ERR(svn_fs_type(&fs_type, repos->db_path, scratch_pool));
1304299742Sdim  repos->fs_type = apr_pstrdup(result_pool, fs_type);
1305251881Speter
1306251881Speter  /* Lock if needed. */
1307299742Sdim  SVN_ERR(lock_repos(repos, exclusive, nonblocking, result_pool));
1308251881Speter
1309251881Speter  /* Open up the filesystem only after obtaining the lock. */
1310251881Speter  if (open_fs)
1311299742Sdim    SVN_ERR(svn_fs_open2(&repos->fs, repos->db_path, fs_config,
1312299742Sdim                         result_pool, scratch_pool));
1313251881Speter
1314251881Speter#ifdef SVN_DEBUG_CRASH_AT_REPOS_OPEN
1315251881Speter  /* If $PATH/config/debug-abort exists, crash the server here.
1316251881Speter     This debugging feature can be used to test client recovery
1317251881Speter     when the server crashes.
1318251881Speter
1319251881Speter     See: Issue #4274 */
1320251881Speter  {
1321251881Speter    svn_node_kind_t kind;
1322251881Speter    svn_error_t *err = svn_io_check_path(
1323299742Sdim        svn_dirent_join(repos->conf_path, "debug-abort", scratch_pool),
1324299742Sdim        &kind, scratch_pool);
1325251881Speter    svn_error_clear(err);
1326251881Speter    if (!err && kind == svn_node_file)
1327251881Speter      SVN_ERR_MALFUNCTION_NO_RETURN();
1328251881Speter  }
1329251881Speter#endif /* SVN_DEBUG_CRASH_AT_REPOS_OPEN */
1330251881Speter
1331251881Speter  *repos_p = repos;
1332251881Speter  return SVN_NO_ERROR;
1333251881Speter}
1334251881Speter
1335251881Speter
1336251881Speter
1337251881Speterconst char *
1338251881Spetersvn_repos_find_root_path(const char *path,
1339251881Speter                         apr_pool_t *pool)
1340251881Speter{
1341251881Speter  const char *candidate = path;
1342251881Speter  const char *decoded;
1343251881Speter  svn_error_t *err;
1344251881Speter
1345251881Speter  while (1)
1346251881Speter    {
1347251881Speter      /* Try to decode the path, so we don't fail if it contains characters
1348251881Speter         that aren't supported by the OS filesystem.  The subversion fs
1349251881Speter         isn't restricted by the OS filesystem character set. */
1350251881Speter      err = svn_path_cstring_from_utf8(&decoded, candidate, pool);
1351251881Speter      if (!err && check_repos_path(candidate, pool))
1352251881Speter        break;
1353251881Speter      svn_error_clear(err);
1354251881Speter
1355251881Speter      if (svn_path_is_empty(candidate) ||
1356251881Speter          svn_dirent_is_root(candidate, strlen(candidate)))
1357251881Speter        return NULL;
1358251881Speter
1359251881Speter      candidate = svn_dirent_dirname(candidate, pool);
1360251881Speter    }
1361251881Speter
1362251881Speter  return candidate;
1363251881Speter}
1364251881Speter
1365251881Spetersvn_error_t *
1366299742Sdimsvn_repos_open3(svn_repos_t **repos_p,
1367251881Speter                const char *path,
1368251881Speter                apr_hash_t *fs_config,
1369299742Sdim                apr_pool_t *result_pool,
1370299742Sdim                apr_pool_t *scratch_pool)
1371251881Speter{
1372251881Speter  /* Fetch a repository object initialized with a shared read/write
1373251881Speter     lock on the database. */
1374251881Speter
1375299742Sdim  return get_repos(repos_p, path, FALSE, FALSE, TRUE, fs_config,
1376299742Sdim                   result_pool, scratch_pool);
1377251881Speter}
1378251881Speter
1379299742Sdim/* Baton used with fs_upgrade_notify, specifying the svn_repos layer
1380299742Sdim * notification parameters.
1381299742Sdim */
1382299742Sdimstruct fs_upgrade_notify_baton_t
1383299742Sdim{
1384299742Sdim  svn_repos_notify_func_t notify_func;
1385299742Sdim  void *notify_baton;
1386299742Sdim};
1387251881Speter
1388299742Sdim/* Implements svn_fs_upgrade_notify_t as forwarding to a
1389299742Sdim * svn_repos_notify_func_t passed in a fs_upgrade_notify_baton_t* BATON.
1390299742Sdim */
1391299742Sdimstatic svn_error_t *
1392299742Sdimfs_upgrade_notify(void *baton,
1393299742Sdim                  apr_uint64_t number,
1394299742Sdim                  svn_fs_upgrade_notify_action_t action,
1395299742Sdim                  apr_pool_t *pool)
1396299742Sdim{
1397299742Sdim  struct fs_upgrade_notify_baton_t *fs_baton = baton;
1398299742Sdim
1399299742Sdim  svn_repos_notify_t *notify = svn_repos_notify_create(
1400299742Sdim                                svn_repos_notify_mutex_acquired, pool);
1401299742Sdim  switch(action)
1402299742Sdim    {
1403299742Sdim      case svn_fs_upgrade_pack_revprops:
1404299742Sdim        notify->shard = number;
1405299742Sdim        notify->action = svn_repos_notify_pack_revprops;
1406299742Sdim        break;
1407299742Sdim
1408299742Sdim      case svn_fs_upgrade_cleanup_revprops:
1409299742Sdim        notify->shard = number;
1410299742Sdim        notify->action = svn_repos_notify_cleanup_revprops;
1411299742Sdim        break;
1412299742Sdim
1413299742Sdim      case svn_fs_upgrade_format_bumped:
1414299742Sdim        notify->revision = number;
1415299742Sdim        notify->action = svn_repos_notify_format_bumped;
1416299742Sdim        break;
1417299742Sdim
1418299742Sdim      default:
1419299742Sdim        /* unknown notification */
1420299742Sdim        SVN_ERR_MALFUNCTION();
1421299742Sdim    }
1422299742Sdim
1423299742Sdim  fs_baton->notify_func(fs_baton->notify_baton, notify, pool);
1424299742Sdim
1425299742Sdim  return SVN_NO_ERROR;
1426299742Sdim}
1427299742Sdim
1428251881Spetersvn_error_t *
1429251881Spetersvn_repos_upgrade2(const char *path,
1430251881Speter                   svn_boolean_t nonblocking,
1431251881Speter                   svn_repos_notify_func_t notify_func,
1432251881Speter                   void *notify_baton,
1433251881Speter                   apr_pool_t *pool)
1434251881Speter{
1435251881Speter  svn_repos_t *repos;
1436251881Speter  const char *format_path;
1437251881Speter  int format;
1438251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1439251881Speter
1440299742Sdim  struct fs_upgrade_notify_baton_t fs_notify_baton;
1441299742Sdim  fs_notify_baton.notify_func = notify_func;
1442299742Sdim  fs_notify_baton.notify_baton = notify_baton;
1443299742Sdim
1444251881Speter  /* Fetch a repository object; for the Berkeley DB backend, it is
1445251881Speter     initialized with an EXCLUSIVE lock on the database.  This will at
1446251881Speter     least prevent others from trying to read or write to it while we
1447251881Speter     run recovery. (Other backends should do their own locking; see
1448251881Speter     lock_repos.) */
1449299742Sdim  SVN_ERR(get_repos(&repos, path, TRUE, nonblocking, FALSE, NULL, subpool,
1450299742Sdim                    subpool));
1451251881Speter
1452251881Speter  if (notify_func)
1453251881Speter    {
1454251881Speter      /* We notify *twice* here, because there are two different logistical
1455251881Speter         actions occuring. */
1456251881Speter      svn_repos_notify_t *notify = svn_repos_notify_create(
1457251881Speter                                    svn_repos_notify_mutex_acquired, subpool);
1458251881Speter      notify_func(notify_baton, notify, subpool);
1459251881Speter
1460251881Speter      notify->action = svn_repos_notify_upgrade_start;
1461251881Speter      notify_func(notify_baton, notify, subpool);
1462251881Speter    }
1463251881Speter
1464251881Speter  /* Try to overwrite with its own contents.  We do this only to
1465251881Speter     verify that we can, because we don't want to actually bump the
1466251881Speter     format of the repository until our underlying filesystem claims
1467251881Speter     to have been upgraded correctly. */
1468251881Speter  format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, subpool);
1469251881Speter  SVN_ERR(svn_io_read_version_file(&format, format_path, subpool));
1470251881Speter  SVN_ERR(svn_io_write_version_file(format_path, format, subpool));
1471251881Speter
1472251881Speter  /* Try to upgrade the filesystem. */
1473299742Sdim  SVN_ERR(svn_fs_upgrade2(repos->db_path,
1474299742Sdim                          notify_func ? fs_upgrade_notify : NULL,
1475299742Sdim                          &fs_notify_baton, NULL, NULL, subpool));
1476251881Speter
1477251881Speter  /* Now overwrite our format file with the latest version. */
1478251881Speter  SVN_ERR(svn_io_write_version_file(format_path, SVN_REPOS__FORMAT_NUMBER,
1479251881Speter                                    subpool));
1480251881Speter
1481251881Speter  /* Close shop and free the subpool, to release the exclusive lock. */
1482251881Speter  svn_pool_destroy(subpool);
1483251881Speter
1484251881Speter  return SVN_NO_ERROR;
1485251881Speter}
1486251881Speter
1487251881Speter
1488251881Spetersvn_error_t *
1489251881Spetersvn_repos_delete(const char *path,
1490251881Speter                 apr_pool_t *pool)
1491251881Speter{
1492251881Speter  const char *db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
1493251881Speter
1494251881Speter  /* Delete the filesystem environment... */
1495251881Speter  SVN_ERR(svn_fs_delete_fs(db_path, pool));
1496251881Speter
1497251881Speter  /* ...then blow away everything else.  */
1498299742Sdim  return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL, pool));
1499251881Speter}
1500251881Speter
1501251881Speter
1502251881Speter/* Repository supports the capability. */
1503251881Speterstatic const char *capability_yes = "yes";
1504251881Speter/* Repository does not support the capability. */
1505251881Speterstatic const char *capability_no = "no";
1506251881Speter
1507251881Spetersvn_error_t *
1508251881Spetersvn_repos_has_capability(svn_repos_t *repos,
1509251881Speter                         svn_boolean_t *has,
1510251881Speter                         const char *capability,
1511251881Speter                         apr_pool_t *pool)
1512251881Speter{
1513251881Speter  const char *val = svn_hash_gets(repos->repository_capabilities, capability);
1514251881Speter
1515251881Speter  if (val == capability_yes)
1516251881Speter    {
1517251881Speter      *has = TRUE;
1518251881Speter    }
1519251881Speter  else if (val == capability_no)
1520251881Speter    {
1521251881Speter      *has = FALSE;
1522251881Speter    }
1523251881Speter  /* Else don't know, so investigate. */
1524251881Speter  else if (strcmp(capability, SVN_REPOS_CAPABILITY_MERGEINFO) == 0)
1525251881Speter    {
1526251881Speter      svn_error_t *err;
1527251881Speter      svn_fs_root_t *root;
1528251881Speter      svn_mergeinfo_catalog_t ignored;
1529251881Speter      apr_array_header_t *paths = apr_array_make(pool, 1,
1530251881Speter                                                 sizeof(char *));
1531251881Speter
1532251881Speter      SVN_ERR(svn_fs_revision_root(&root, repos->fs, 0, pool));
1533251881Speter      APR_ARRAY_PUSH(paths, const char *) = "";
1534251881Speter      err = svn_fs_get_mergeinfo2(&ignored, root, paths, FALSE, FALSE,
1535251881Speter                                  TRUE, pool, pool);
1536251881Speter
1537251881Speter      if (err)
1538251881Speter        {
1539251881Speter          if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
1540251881Speter            {
1541251881Speter              svn_error_clear(err);
1542251881Speter              svn_hash_sets(repos->repository_capabilities,
1543251881Speter                            SVN_REPOS_CAPABILITY_MERGEINFO, capability_no);
1544251881Speter              *has = FALSE;
1545251881Speter            }
1546251881Speter          else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
1547251881Speter            {
1548251881Speter              /* Mergeinfo requests use relative paths, and anyway we're
1549251881Speter                 in r0, so we're likely to get this error -- but it
1550251881Speter                 means the repository supports mergeinfo! */
1551251881Speter              svn_error_clear(err);
1552251881Speter              svn_hash_sets(repos->repository_capabilities,
1553251881Speter                            SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
1554251881Speter              *has = TRUE;
1555251881Speter            }
1556251881Speter          else
1557251881Speter            {
1558251881Speter              return svn_error_trace(err);
1559251881Speter            }
1560251881Speter        }
1561251881Speter      else
1562251881Speter        {
1563251881Speter          svn_hash_sets(repos->repository_capabilities,
1564251881Speter                        SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
1565251881Speter          *has = TRUE;
1566251881Speter        }
1567251881Speter    }
1568251881Speter  else
1569251881Speter    {
1570251881Speter      return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, 0,
1571251881Speter                               _("unknown capability '%s'"), capability);
1572251881Speter    }
1573251881Speter
1574251881Speter  return SVN_NO_ERROR;
1575251881Speter}
1576251881Speter
1577299742Sdimsvn_error_t *
1578299742Sdimsvn_repos_capabilities(apr_hash_t **capabilities,
1579299742Sdim                       svn_repos_t *repos,
1580299742Sdim                       apr_pool_t *result_pool,
1581299742Sdim                       apr_pool_t *scratch_pool)
1582299742Sdim{
1583299742Sdim  static const char *const queries[] = {
1584299742Sdim    SVN_REPOS_CAPABILITY_MERGEINFO,
1585299742Sdim    NULL
1586299742Sdim  };
1587299742Sdim  const char *const *i;
1588299742Sdim
1589299742Sdim  *capabilities = apr_hash_make(result_pool);
1590299742Sdim
1591299742Sdim  for (i = queries; *i; i++)
1592299742Sdim    {
1593299742Sdim      svn_boolean_t has;
1594299742Sdim      SVN_ERR(svn_repos_has_capability(repos, &has, *i, scratch_pool));
1595299742Sdim      if (has)
1596299742Sdim        svn_hash_sets(*capabilities, *i, *i);
1597299742Sdim    }
1598299742Sdim
1599299742Sdim  return SVN_NO_ERROR;
1600299742Sdim}
1601299742Sdim
1602299742Sdimsvn_error_t *
1603299742Sdimsvn_repos_info_format(int *repos_format,
1604299742Sdim                      svn_version_t **supports_version,
1605299742Sdim                      svn_repos_t *repos,
1606299742Sdim                      apr_pool_t *result_pool,
1607299742Sdim                      apr_pool_t *scratch_pool)
1608299742Sdim{
1609299742Sdim  *repos_format = repos->format;
1610299742Sdim  *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
1611299742Sdim
1612299742Sdim  (*supports_version)->major = SVN_VER_MAJOR;
1613299742Sdim  (*supports_version)->minor = 0;
1614299742Sdim  (*supports_version)->patch = 0;
1615299742Sdim  (*supports_version)->tag = "";
1616299742Sdim
1617299742Sdim  switch (repos->format)
1618299742Sdim    {
1619299742Sdim    case SVN_REPOS__FORMAT_NUMBER_LEGACY:
1620299742Sdim      break;
1621299742Sdim    case SVN_REPOS__FORMAT_NUMBER_1_4:
1622299742Sdim      (*supports_version)->minor = 4;
1623299742Sdim      break;
1624299742Sdim#ifdef SVN_DEBUG
1625299742Sdim# if SVN_REPOS__FORMAT_NUMBER != SVN_REPOS__FORMAT_NUMBER_1_4
1626299742Sdim#  error "Need to add a 'case' statement here"
1627299742Sdim# endif
1628299742Sdim#endif
1629299742Sdim    }
1630299742Sdim
1631299742Sdim  return SVN_NO_ERROR;
1632299742Sdim}
1633299742Sdim
1634251881Spetersvn_fs_t *
1635251881Spetersvn_repos_fs(svn_repos_t *repos)
1636251881Speter{
1637251881Speter  if (! repos)
1638251881Speter    return NULL;
1639251881Speter  return repos->fs;
1640251881Speter}
1641251881Speter
1642299742Sdimconst char *
1643299742Sdimsvn_repos_fs_type(svn_repos_t *repos,
1644299742Sdim                  apr_pool_t *result_pool)
1645299742Sdim{
1646299742Sdim  return apr_pstrdup(result_pool, repos->fs_type);
1647299742Sdim}
1648251881Speter
1649251881Speter/* For historical reasons, for the Berkeley DB backend, this code uses
1650251881Speter * repository locking, which is motivated by the need to support the
1651251881Speter * Berkeley DB error DB_RUN_RECOVERY.  (FSFS takes care of locking
1652251881Speter * itself, inside its implementation of svn_fs_recover.)  Here's how
1653251881Speter * it works:
1654251881Speter *
1655251881Speter * Every accessor of a repository's database takes out a shared lock
1656251881Speter * on the repository -- both readers and writers get shared locks, and
1657251881Speter * there can be an unlimited number of shared locks simultaneously.
1658251881Speter *
1659251881Speter * Sometimes, a db access returns the error DB_RUN_RECOVERY.  When
1660251881Speter * this happens, we need to run svn_fs_recover() on the db
1661251881Speter * with no other accessors present.  So we take out an exclusive lock
1662251881Speter * on the repository.  From the moment we request the exclusive lock,
1663251881Speter * no more shared locks are granted, and when the last shared lock
1664251881Speter * disappears, the exclusive lock is granted.  As soon as we get it,
1665251881Speter * we can run recovery.
1666251881Speter *
1667251881Speter * We assume that once any berkeley call returns DB_RUN_RECOVERY, they
1668251881Speter * all do, until recovery is run.
1669251881Speter */
1670251881Speter
1671251881Spetersvn_error_t *
1672251881Spetersvn_repos_recover4(const char *path,
1673251881Speter                   svn_boolean_t nonblocking,
1674251881Speter                   svn_repos_notify_func_t notify_func,
1675251881Speter                   void *notify_baton,
1676251881Speter                   svn_cancel_func_t cancel_func,
1677251881Speter                   void * cancel_baton,
1678251881Speter                   apr_pool_t *pool)
1679251881Speter{
1680251881Speter  svn_repos_t *repos;
1681251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1682251881Speter
1683251881Speter  /* Fetch a repository object; for the Berkeley DB backend, it is
1684251881Speter     initialized with an EXCLUSIVE lock on the database.  This will at
1685251881Speter     least prevent others from trying to read or write to it while we
1686251881Speter     run recovery. (Other backends should do their own locking; see
1687251881Speter     lock_repos.) */
1688251881Speter  SVN_ERR(get_repos(&repos, path, TRUE, nonblocking,
1689251881Speter                    FALSE,    /* don't try to open the db yet. */
1690251881Speter                    NULL,
1691299742Sdim                    subpool, subpool));
1692251881Speter
1693251881Speter  if (notify_func)
1694251881Speter    {
1695251881Speter      /* We notify *twice* here, because there are two different logistical
1696251881Speter         actions occuring. */
1697251881Speter      svn_repos_notify_t *notify = svn_repos_notify_create(
1698251881Speter                                    svn_repos_notify_mutex_acquired, subpool);
1699251881Speter      notify_func(notify_baton, notify, subpool);
1700251881Speter
1701251881Speter      notify->action = svn_repos_notify_recover_start;
1702251881Speter      notify_func(notify_baton, notify, subpool);
1703251881Speter    }
1704251881Speter
1705251881Speter  /* Recover the database to a consistent state. */
1706251881Speter  SVN_ERR(svn_fs_recover(repos->db_path, cancel_func, cancel_baton, subpool));
1707251881Speter
1708251881Speter  /* Close shop and free the subpool, to release the exclusive lock. */
1709251881Speter  svn_pool_destroy(subpool);
1710251881Speter
1711251881Speter  return SVN_NO_ERROR;
1712251881Speter}
1713251881Speter
1714251881Speterstruct freeze_baton_t {
1715251881Speter  apr_array_header_t *paths;
1716251881Speter  int counter;
1717251881Speter  svn_repos_freeze_func_t freeze_func;
1718251881Speter  void *freeze_baton;
1719299742Sdim
1720299742Sdim  /* Scratch pool used for every freeze callback invocation. */
1721299742Sdim  apr_pool_t *scratch_pool;
1722251881Speter};
1723251881Speter
1724251881Speterstatic svn_error_t *
1725251881Spetermulti_freeze(void *baton,
1726251881Speter             apr_pool_t *pool)
1727251881Speter{
1728251881Speter  struct freeze_baton_t *fb = baton;
1729251881Speter
1730299742Sdim  svn_pool_clear(fb->scratch_pool);
1731251881Speter  if (fb->counter == fb->paths->nelts)
1732251881Speter    {
1733251881Speter      SVN_ERR(fb->freeze_func(fb->freeze_baton, pool));
1734251881Speter      return SVN_NO_ERROR;
1735251881Speter    }
1736251881Speter  else
1737251881Speter    {
1738251881Speter      /* Using a subpool as the only way to unlock the repos lock used
1739251881Speter         by BDB is to clear the pool used to take the lock. */
1740251881Speter      apr_pool_t *subpool = svn_pool_create(pool);
1741251881Speter      const char *path = APR_ARRAY_IDX(fb->paths, fb->counter, const char *);
1742251881Speter      svn_repos_t *repos;
1743251881Speter
1744251881Speter      ++fb->counter;
1745251881Speter
1746251881Speter      SVN_ERR(get_repos(&repos, path,
1747251881Speter                        TRUE  /* exclusive (only applies to BDB) */,
1748251881Speter                        FALSE /* non-blocking */,
1749251881Speter                        FALSE /* open-fs */,
1750299742Sdim                        NULL, subpool, fb->scratch_pool));
1751251881Speter
1752251881Speter
1753251881Speter      if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
1754251881Speter        {
1755251881Speter          svn_error_t *err = multi_freeze(fb, subpool);
1756251881Speter
1757251881Speter          svn_pool_destroy(subpool);
1758251881Speter
1759251881Speter          return err;
1760251881Speter        }
1761251881Speter      else
1762251881Speter        {
1763299742Sdim          SVN_ERR(svn_fs_open2(&repos->fs, repos->db_path, NULL, subpool,
1764299742Sdim                               fb->scratch_pool));
1765251881Speter          SVN_ERR(svn_fs_freeze(svn_repos_fs(repos), multi_freeze, fb,
1766251881Speter                                subpool));
1767251881Speter        }
1768251881Speter
1769251881Speter      svn_pool_destroy(subpool);
1770251881Speter    }
1771251881Speter
1772251881Speter  return SVN_NO_ERROR;
1773251881Speter}
1774251881Speter
1775251881Speter/* For BDB we fall back on BDB's repos layer lock which means that the
1776251881Speter   repository is unreadable while frozen.
1777251881Speter
1778251881Speter   For FSFS we delegate to the FS layer which uses the FSFS write-lock
1779251881Speter   and an SQLite reserved lock which means the repository is readable
1780251881Speter   while frozen. */
1781251881Spetersvn_error_t *
1782251881Spetersvn_repos_freeze(apr_array_header_t *paths,
1783251881Speter                 svn_repos_freeze_func_t freeze_func,
1784251881Speter                 void *freeze_baton,
1785251881Speter                 apr_pool_t *pool)
1786251881Speter{
1787251881Speter  struct freeze_baton_t fb;
1788251881Speter
1789251881Speter  fb.paths = paths;
1790251881Speter  fb.counter = 0;
1791251881Speter  fb.freeze_func = freeze_func;
1792251881Speter  fb.freeze_baton = freeze_baton;
1793299742Sdim  fb.scratch_pool = svn_pool_create(pool);
1794251881Speter
1795251881Speter  SVN_ERR(multi_freeze(&fb, pool));
1796251881Speter
1797299742Sdim  svn_pool_destroy(fb.scratch_pool);
1798251881Speter  return SVN_NO_ERROR;
1799251881Speter}
1800251881Speter
1801251881Spetersvn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
1802251881Speter                                   const char *path,
1803251881Speter                                   svn_boolean_t only_unused,
1804251881Speter                                   apr_pool_t *pool)
1805251881Speter{
1806251881Speter  svn_repos_t *repos;
1807251881Speter  int i;
1808251881Speter
1809251881Speter  SVN_ERR(get_repos(&repos, path,
1810251881Speter                    FALSE, FALSE,
1811251881Speter                    FALSE,     /* Do not open fs. */
1812251881Speter                    NULL,
1813299742Sdim                    pool, pool));
1814251881Speter
1815251881Speter  SVN_ERR(svn_fs_berkeley_logfiles(logfiles,
1816251881Speter                                   svn_repos_db_env(repos, pool),
1817251881Speter                                   only_unused,
1818251881Speter                                   pool));
1819251881Speter
1820251881Speter  /* Loop, printing log files. */
1821251881Speter  for (i = 0; i < (*logfiles)->nelts; i++)
1822251881Speter    {
1823251881Speter      const char ** log_file = &(APR_ARRAY_IDX(*logfiles, i, const char *));
1824251881Speter      *log_file = svn_dirent_join(SVN_REPOS__DB_DIR, *log_file, pool);
1825251881Speter    }
1826251881Speter
1827251881Speter  return SVN_NO_ERROR;
1828251881Speter}
1829251881Speter
1830251881Speter/* Baton for hotcopy_structure(). */
1831251881Speterstruct hotcopy_ctx_t {
1832251881Speter  const char *dest;     /* target location to construct */
1833251881Speter  size_t src_len; /* len of the source path*/
1834251881Speter
1835251881Speter  /* As in svn_repos_hotcopy2() */
1836251881Speter  svn_boolean_t incremental;
1837251881Speter  svn_cancel_func_t cancel_func;
1838251881Speter  void *cancel_baton;
1839251881Speter};
1840251881Speter
1841251881Speter/* Copy the repository structure of PATH to BATON->DEST, with exception of
1842251881Speter * @c SVN_REPOS__DB_DIR, @c SVN_REPOS__LOCK_DIR and @c SVN_REPOS__FORMAT;
1843251881Speter * those directories and files are handled separately.
1844251881Speter *
1845251881Speter * BATON is a (struct hotcopy_ctx_t *).  BATON->SRC_LEN is the length
1846251881Speter * of PATH.
1847251881Speter *
1848251881Speter * Implements svn_io_walk_func_t.
1849251881Speter */
1850251881Speterstatic svn_error_t *hotcopy_structure(void *baton,
1851251881Speter                                      const char *path,
1852251881Speter                                      const apr_finfo_t *finfo,
1853251881Speter                                      apr_pool_t *pool)
1854251881Speter{
1855251881Speter  const struct hotcopy_ctx_t *ctx = baton;
1856251881Speter  const char *sub_path;
1857251881Speter  const char *target;
1858251881Speter
1859251881Speter  if (ctx->cancel_func)
1860251881Speter    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1861251881Speter
1862251881Speter  if (strlen(path) == ctx->src_len)
1863251881Speter    {
1864251881Speter      sub_path = "";
1865251881Speter    }
1866251881Speter  else
1867251881Speter    {
1868251881Speter      sub_path = &path[ctx->src_len+1];
1869251881Speter
1870251881Speter      /* Check if we are inside db directory and if so skip it */
1871251881Speter      if (svn_path_compare_paths
1872251881Speter          (svn_dirent_get_longest_ancestor(SVN_REPOS__DB_DIR, sub_path, pool),
1873251881Speter           SVN_REPOS__DB_DIR) == 0)
1874251881Speter        return SVN_NO_ERROR;
1875251881Speter
1876251881Speter      if (svn_path_compare_paths
1877251881Speter          (svn_dirent_get_longest_ancestor(SVN_REPOS__LOCK_DIR, sub_path,
1878251881Speter                                           pool),
1879251881Speter           SVN_REPOS__LOCK_DIR) == 0)
1880251881Speter        return SVN_NO_ERROR;
1881251881Speter
1882251881Speter      if (svn_path_compare_paths
1883251881Speter          (svn_dirent_get_longest_ancestor(SVN_REPOS__FORMAT, sub_path, pool),
1884251881Speter           SVN_REPOS__FORMAT) == 0)
1885251881Speter        return SVN_NO_ERROR;
1886251881Speter    }
1887251881Speter
1888251881Speter  target = svn_dirent_join(ctx->dest, sub_path, pool);
1889251881Speter
1890251881Speter  if (finfo->filetype == APR_DIR)
1891251881Speter    {
1892251881Speter      svn_error_t *err;
1893251881Speter
1894251881Speter      err = create_repos_dir(target, pool);
1895251881Speter      if (ctx->incremental && err && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
1896251881Speter        {
1897251881Speter          svn_error_clear(err);
1898251881Speter          err = SVN_NO_ERROR;
1899251881Speter        }
1900251881Speter      return svn_error_trace(err);
1901251881Speter    }
1902251881Speter  else if (finfo->filetype == APR_REG)
1903251881Speter    return svn_io_copy_file(path, target, TRUE, pool);
1904251881Speter  else if (finfo->filetype == APR_LNK)
1905251881Speter    return svn_io_copy_link(path, target, pool);
1906251881Speter  else
1907251881Speter    return SVN_NO_ERROR;
1908251881Speter}
1909251881Speter
1910251881Speter
1911251881Speter/** Obtain a lock on db logs lock file. Create one if it does not exist.
1912251881Speter */
1913251881Speterstatic svn_error_t *
1914251881Speterlock_db_logs_file(svn_repos_t *repos,
1915251881Speter                  svn_boolean_t exclusive,
1916251881Speter                  apr_pool_t *pool)
1917251881Speter{
1918251881Speter  const char * lock_file = svn_repos_db_logs_lockfile(repos, pool);
1919251881Speter
1920251881Speter  /* Try to create a lock file, in case if it is missing. As in case of the
1921251881Speter     repositories created before hotcopy functionality.  */
1922251881Speter  svn_error_clear(create_db_logs_lock(repos, pool));
1923251881Speter
1924251881Speter  return svn_io_file_lock2(lock_file, exclusive, FALSE, pool);
1925251881Speter}
1926251881Speter
1927251881Speter
1928299742Sdim/* Baton used with fs_hotcopy_notify(), specifying the svn_repos layer
1929299742Sdim * notification parameters.
1930299742Sdim */
1931299742Sdimstruct fs_hotcopy_notify_baton_t
1932299742Sdim{
1933299742Sdim  svn_repos_notify_func_t notify_func;
1934299742Sdim  void *notify_baton;
1935299742Sdim};
1936299742Sdim
1937299742Sdim/* Implements svn_fs_hotcopy_notify_t as forwarding to a
1938299742Sdim * svn_repos_notify_func_t passed in a fs_hotcopy_notify_baton_t* BATON.
1939299742Sdim */
1940299742Sdimstatic void
1941299742Sdimfs_hotcopy_notify(void *baton,
1942299742Sdim                  svn_revnum_t start_revision,
1943299742Sdim                  svn_revnum_t end_revision,
1944299742Sdim                  apr_pool_t *pool)
1945299742Sdim{
1946299742Sdim  struct fs_hotcopy_notify_baton_t *fs_baton = baton;
1947299742Sdim  svn_repos_notify_t *notify;
1948299742Sdim
1949299742Sdim  notify = svn_repos_notify_create(svn_repos_notify_hotcopy_rev_range, pool);
1950299742Sdim  notify->start_revision = start_revision;
1951299742Sdim  notify->end_revision = end_revision;
1952299742Sdim
1953299742Sdim  fs_baton->notify_func(fs_baton->notify_baton, notify, pool);
1954299742Sdim}
1955299742Sdim
1956251881Speter/* Make a copy of a repository with hot backup of fs. */
1957251881Spetersvn_error_t *
1958299742Sdimsvn_repos_hotcopy3(const char *src_path,
1959251881Speter                   const char *dst_path,
1960251881Speter                   svn_boolean_t clean_logs,
1961251881Speter                   svn_boolean_t incremental,
1962299742Sdim                   svn_repos_notify_func_t notify_func,
1963299742Sdim                   void *notify_baton,
1964251881Speter                   svn_cancel_func_t cancel_func,
1965251881Speter                   void *cancel_baton,
1966299742Sdim                   apr_pool_t *scratch_pool)
1967251881Speter{
1968299742Sdim  svn_fs_hotcopy_notify_t fs_notify_func;
1969299742Sdim  struct fs_hotcopy_notify_baton_t fs_notify_baton;
1970299742Sdim  struct hotcopy_ctx_t hotcopy_context;
1971299742Sdim  const char *src_abspath;
1972299742Sdim  const char *dst_abspath;
1973251881Speter  svn_repos_t *src_repos;
1974251881Speter  svn_repos_t *dst_repos;
1975251881Speter  svn_error_t *err;
1976251881Speter
1977299742Sdim  SVN_ERR(svn_dirent_get_absolute(&src_abspath, src_path, scratch_pool));
1978299742Sdim  SVN_ERR(svn_dirent_get_absolute(&dst_abspath, dst_path, scratch_pool));
1979251881Speter  if (strcmp(src_abspath, dst_abspath) == 0)
1980251881Speter    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1981251881Speter                             _("Hotcopy source and destination are equal"));
1982251881Speter
1983251881Speter  /* Try to open original repository */
1984251881Speter  SVN_ERR(get_repos(&src_repos, src_abspath,
1985251881Speter                    FALSE, FALSE,
1986251881Speter                    FALSE,    /* don't try to open the db yet. */
1987251881Speter                    NULL,
1988299742Sdim                    scratch_pool, scratch_pool));
1989251881Speter
1990251881Speter  /* If we are going to clean logs, then get an exclusive lock on
1991251881Speter     db-logs.lock, to ensure that no one else will work with logs.
1992251881Speter
1993251881Speter     If we are just copying, then get a shared lock to ensure that
1994251881Speter     no one else will clean logs while we copying them */
1995251881Speter
1996299742Sdim  SVN_ERR(lock_db_logs_file(src_repos, clean_logs, scratch_pool));
1997251881Speter
1998251881Speter  /* Copy the repository to a new path, with exception of
1999251881Speter     specially handled directories */
2000251881Speter
2001251881Speter  hotcopy_context.dest = dst_abspath;
2002251881Speter  hotcopy_context.src_len = strlen(src_abspath);
2003251881Speter  hotcopy_context.incremental = incremental;
2004251881Speter  hotcopy_context.cancel_func = cancel_func;
2005251881Speter  hotcopy_context.cancel_baton = cancel_baton;
2006251881Speter  SVN_ERR(svn_io_dir_walk2(src_abspath,
2007251881Speter                           0,
2008251881Speter                           hotcopy_structure,
2009251881Speter                           &hotcopy_context,
2010299742Sdim                           scratch_pool));
2011251881Speter
2012251881Speter  /* Prepare dst_repos object so that we may create locks,
2013251881Speter     so that we may open repository */
2014251881Speter
2015299742Sdim  dst_repos = create_svn_repos_t(dst_abspath, scratch_pool);
2016251881Speter  dst_repos->fs_type = src_repos->fs_type;
2017251881Speter  dst_repos->format = src_repos->format;
2018251881Speter
2019299742Sdim  err = create_locks(dst_repos, scratch_pool);
2020251881Speter  if (err)
2021251881Speter    {
2022251881Speter      if (incremental && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
2023251881Speter        svn_error_clear(err);
2024251881Speter      else
2025251881Speter        return svn_error_trace(err);
2026251881Speter    }
2027251881Speter
2028299742Sdim  err = svn_io_dir_make_sgid(dst_repos->db_path, APR_OS_DEFAULT,
2029299742Sdim                             scratch_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 */
2040299742Sdim  SVN_ERR(lock_repos(dst_repos, TRUE, FALSE, scratch_pool));
2041251881Speter
2042299742Sdim  fs_notify_func = notify_func ? fs_hotcopy_notify : NULL;
2043299742Sdim  fs_notify_baton.notify_func = notify_func;
2044299742Sdim  fs_notify_baton.notify_baton = notify_baton;
2045299742Sdim
2046299742Sdim  SVN_ERR(svn_fs_hotcopy3(src_repos->db_path, dst_repos->db_path,
2047251881Speter                          clean_logs, incremental,
2048299742Sdim                          fs_notify_func, &fs_notify_baton,
2049299742Sdim                          cancel_func, cancel_baton, scratch_pool));
2050251881Speter
2051251881Speter  /* Destination repository is ready.  Stamp it with a format number. */
2052251881Speter  return svn_io_write_version_file
2053299742Sdim          (svn_dirent_join(dst_repos->path, SVN_REPOS__FORMAT, scratch_pool),
2054299742Sdim           dst_repos->format, scratch_pool);
2055251881Speter}
2056251881Speter
2057251881Speter/* Return the library version number. */
2058251881Speterconst svn_version_t *
2059251881Spetersvn_repos_version(void)
2060251881Speter{
2061251881Speter  SVN_VERSION_BODY;
2062251881Speter}
2063251881Speter
2064251881Speter
2065251881Speter
2066251881Spetersvn_error_t *
2067251881Spetersvn_repos_stat(svn_dirent_t **dirent,
2068251881Speter               svn_fs_root_t *root,
2069251881Speter               const char *path,
2070251881Speter               apr_pool_t *pool)
2071251881Speter{
2072251881Speter  svn_node_kind_t kind;
2073251881Speter  svn_dirent_t *ent;
2074251881Speter  const char *datestring;
2075251881Speter
2076251881Speter  SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
2077251881Speter
2078251881Speter  if (kind == svn_node_none)
2079251881Speter    {
2080251881Speter      *dirent = NULL;
2081251881Speter      return SVN_NO_ERROR;
2082251881Speter    }
2083251881Speter
2084251881Speter  ent = svn_dirent_create(pool);
2085251881Speter  ent->kind = kind;
2086251881Speter
2087251881Speter  if (kind == svn_node_file)
2088251881Speter    SVN_ERR(svn_fs_file_length(&(ent->size), root, path, pool));
2089251881Speter
2090299742Sdim  SVN_ERR(svn_fs_node_has_props(&ent->has_props, root, path, pool));
2091251881Speter
2092251881Speter  SVN_ERR(svn_repos_get_committed_info(&(ent->created_rev),
2093251881Speter                                       &datestring,
2094251881Speter                                       &(ent->last_author),
2095251881Speter                                       root, path, pool));
2096251881Speter  if (datestring)
2097251881Speter    SVN_ERR(svn_time_from_cstring(&(ent->time), datestring, pool));
2098251881Speter
2099251881Speter  *dirent = ent;
2100251881Speter  return SVN_NO_ERROR;
2101251881Speter}
2102251881Speter
2103251881Spetersvn_error_t *
2104251881Spetersvn_repos_remember_client_capabilities(svn_repos_t *repos,
2105251881Speter                                       const apr_array_header_t *capabilities)
2106251881Speter{
2107251881Speter  repos->client_capabilities = capabilities;
2108251881Speter  return SVN_NO_ERROR;
2109251881Speter}
2110251881Speter
2111251881Spetersvn_error_t *
2112251881Spetersvn_repos__fs_type(const char **fs_type,
2113251881Speter                   const char *repos_path,
2114251881Speter                   apr_pool_t *pool)
2115251881Speter{
2116251881Speter  svn_repos_t repos;
2117251881Speter  repos.path = (char*)repos_path;
2118251881Speter
2119251881Speter  SVN_ERR(check_repos_format(&repos, pool));
2120251881Speter
2121251881Speter  return svn_fs_type(fs_type,
2122251881Speter                     svn_dirent_join(repos_path, SVN_REPOS__DB_DIR, pool),
2123251881Speter                     pool);
2124251881Speter}
2125