1 /*
2 * svnadmin.c: Subversion server administration tool main file.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25 #include <apr_file_io.h>
26
27 #include "svn_hash.h"
28 #include "svn_pools.h"
29 #include "svn_cmdline.h"
30 #include "svn_error.h"
31 #include "svn_opt.h"
32 #include "svn_utf.h"
33 #include "svn_subst.h"
34 #include "svn_dirent_uri.h"
35 #include "svn_path.h"
36 #include "svn_config.h"
37 #include "svn_repos.h"
38 #include "svn_cache_config.h"
39 #include "svn_version.h"
40 #include "svn_props.h"
41 #include "svn_sorts.h"
42 #include "svn_time.h"
43 #include "svn_user.h"
44 #include "svn_xml.h"
45
46 #include "private/svn_cmdline_private.h"
47 #include "private/svn_opt_private.h"
48 #include "private/svn_sorts_private.h"
49 #include "private/svn_subr_private.h"
50 #include "private/svn_cmdline_private.h"
51 #include "private/svn_fspath.h"
52
53 #include "svn_private_config.h"
54
55
56 /*** Code. ***/
57
58 /* FSFS format 7's "block-read" feature performs poorly with small caches.
59 * Enable it only if caches above this threshold have been configured.
60 * The current threshold is 64MB. */
61 #define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000)
62
63 static svn_cancel_func_t check_cancel = NULL;
64
65 /* Custom filesystem warning function. */
66 static void
warning_func(void * baton,svn_error_t * err)67 warning_func(void *baton,
68 svn_error_t *err)
69 {
70 if (! err)
71 return;
72 svn_handle_warning2(stderr, err, "svnadmin: ");
73 }
74
75
76 /* Version compatibility check */
77 static svn_error_t *
check_lib_versions(void)78 check_lib_versions(void)
79 {
80 static const svn_version_checklist_t checklist[] =
81 {
82 { "svn_subr", svn_subr_version },
83 { "svn_repos", svn_repos_version },
84 { "svn_fs", svn_fs_version },
85 { "svn_delta", svn_delta_version },
86 { NULL, NULL }
87 };
88 SVN_VERSION_DEFINE(my_version);
89
90 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
91 }
92
93
94
95 /** Subcommands. **/
96
97 static svn_opt_subcommand_t
98 subcommand_crashtest,
99 subcommand_create,
100 subcommand_delrevprop,
101 subcommand_deltify,
102 subcommand_dump,
103 subcommand_dump_revprops,
104 subcommand_freeze,
105 subcommand_help,
106 subcommand_hotcopy,
107 subcommand_info,
108 subcommand_load,
109 subcommand_load_revprops,
110 subcommand_list_dblogs,
111 subcommand_list_unused_dblogs,
112 subcommand_lock,
113 subcommand_lslocks,
114 subcommand_lstxns,
115 subcommand_pack,
116 subcommand_recover,
117 subcommand_rmlocks,
118 subcommand_rmtxns,
119 subcommand_setlog,
120 subcommand_setrevprop,
121 subcommand_setuuid,
122 subcommand_unlock,
123 subcommand_upgrade,
124 subcommand_verify;
125
126 enum svnadmin__cmdline_options_t
127 {
128 svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
129 svnadmin__incremental,
130 svnadmin__keep_going,
131 svnadmin__deltas,
132 svnadmin__ignore_uuid,
133 svnadmin__force_uuid,
134 svnadmin__fs_type,
135 svnadmin__parent_dir,
136 svnadmin__bdb_txn_nosync,
137 svnadmin__bdb_log_keep,
138 svnadmin__config_dir,
139 svnadmin__bypass_hooks,
140 svnadmin__bypass_prop_validation,
141 svnadmin__ignore_dates,
142 svnadmin__use_pre_commit_hook,
143 svnadmin__use_post_commit_hook,
144 svnadmin__use_pre_revprop_change_hook,
145 svnadmin__use_post_revprop_change_hook,
146 svnadmin__clean_logs,
147 svnadmin__wait,
148 svnadmin__pre_1_4_compatible,
149 svnadmin__pre_1_5_compatible,
150 svnadmin__pre_1_6_compatible,
151 svnadmin__compatible_version,
152 svnadmin__check_normalization,
153 svnadmin__metadata_only,
154 svnadmin__no_flush_to_disk,
155 svnadmin__normalize_props,
156 svnadmin__exclude,
157 svnadmin__include,
158 svnadmin__glob
159 };
160
161 /* Option codes and descriptions.
162 *
163 * The entire list must be terminated with an entry of nulls.
164 */
165 static const apr_getopt_option_t options_table[] =
166 {
167 {"help", 'h', 0,
168 N_("show help on a subcommand")},
169
170 {NULL, '?', 0,
171 N_("show help on a subcommand")},
172
173 {"version", svnadmin__version, 0,
174 N_("show program version information")},
175
176 {"revision", 'r', 1,
177 N_("specify revision number ARG (or X:Y range)")},
178
179 {"transaction", 't', 1,
180 N_("specify transaction name ARG")},
181
182 {"incremental", svnadmin__incremental, 0,
183 N_("dump or hotcopy incrementally")},
184
185 {"deltas", svnadmin__deltas, 0,
186 N_("use deltas in dump output")},
187
188 {"bypass-hooks", svnadmin__bypass_hooks, 0,
189 N_("bypass the repository hook system")},
190
191 {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0,
192 N_("bypass property validation logic")},
193
194 {"ignore-dates", svnadmin__ignore_dates, 0,
195 N_("ignore revision datestamps found in the stream")},
196
197 {"quiet", 'q', 0,
198 N_("no progress (only errors to stderr)")},
199
200 {"ignore-uuid", svnadmin__ignore_uuid, 0,
201 N_("ignore any repos UUID found in the stream")},
202
203 {"force-uuid", svnadmin__force_uuid, 0,
204 N_("set repos UUID to that found in stream, if any")},
205
206 {"fs-type", svnadmin__fs_type, 1,
207 N_("type of repository:\n"
208 " 'fsfs' (default), 'bdb' or 'fsx'\n"
209 " CAUTION: FSX is for EXPERIMENTAL use only!")},
210
211 {"parent-dir", svnadmin__parent_dir, 1,
212 N_("load at specified directory in repository")},
213
214 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
215 N_("disable fsync at transaction commit [Berkeley DB]")},
216
217 {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
218 N_("disable automatic log file removal [Berkeley DB]")},
219
220 {"config-dir", svnadmin__config_dir, 1,
221 N_("read user configuration files from directory ARG")},
222
223 {"clean-logs", svnadmin__clean_logs, 0,
224 N_("remove redundant Berkeley DB log files\n"
225 " from source repository [Berkeley DB]")},
226
227 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
228 N_("call pre-commit hook before committing revisions")},
229
230 {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
231 N_("call post-commit hook after committing revisions")},
232
233 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
234 N_("call hook before changing revision property")},
235
236 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
237 N_("call hook after changing revision property")},
238
239 {"wait", svnadmin__wait, 0,
240 N_("wait instead of exit if the repository is in\n"
241 " use by another process")},
242
243 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
244 N_("deprecated; see --compatible-version")},
245
246 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
247 N_("deprecated; see --compatible-version")},
248
249 {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0,
250 N_("deprecated; see --compatible-version")},
251
252 {"keep-going", svnadmin__keep_going, 0,
253 N_("continue verification after detecting a corruption")},
254
255 {"memory-cache-size", 'M', 1,
256 N_("size of the extra in-memory cache in MB used to\n"
257 " minimize redundant operations. Default: 16.\n"
258 " [used for FSFS repositories only]")},
259
260 {"compatible-version", svnadmin__compatible_version, 1,
261 N_("use repository format compatible with Subversion\n"
262 " version ARG (\"1.5.5\", \"1.7\", etc.)")},
263
264 {"file", 'F', 1, N_("read repository paths from file ARG")},
265
266 {"check-normalization", svnadmin__check_normalization, 0,
267 N_("report any names within the same directory or\n"
268 " svn:mergeinfo property value that differ only\n"
269 " in character representation, but are otherwise\n"
270 " identical")},
271
272 {"metadata-only", svnadmin__metadata_only, 0,
273 N_("verify metadata only (ignored for BDB),\n"
274 " checking against external corruption in\n"
275 " Subversion 1.9+ format repositories.\n")},
276
277 {"no-flush-to-disk", svnadmin__no_flush_to_disk, 0,
278 N_("disable flushing to disk during the operation\n"
279 " (faster, but unsafe on power off)")},
280
281 {"normalize-props", svnadmin__normalize_props, 0,
282 N_("normalize property values found in the dumpstream\n"
283 " (currently, only translates non-LF line endings)")},
284
285 {"exclude", svnadmin__exclude, 1,
286 N_("filter out nodes with given prefix(es) from dump")},
287
288 {"include", svnadmin__include, 1,
289 N_("filter out nodes without given prefix(es) from dump")},
290
291 {"pattern", svnadmin__glob, 0,
292 N_("treat the path prefixes as file glob patterns.\n"
293 " Glob special characters are '*' '?' '[]' and '\\'.\n"
294 " Character '/' is not treated specially, so\n"
295 " pattern /*/foo matches paths /a/foo and /a/b/foo.") },
296
297 {NULL}
298 };
299
300
301 /* Array of available subcommands.
302 * The entire list must be terminated with an entry of nulls.
303 */
304 static const svn_opt_subcommand_desc2_t cmd_table[] =
305 {
306 {"crashtest", subcommand_crashtest, {0}, N_
307 ("usage: svnadmin crashtest REPOS_PATH\n\n"
308 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
309 "a process that crashes while holding an open repository handle.\n"),
310 {0} },
311
312 {"create", subcommand_create, {0}, N_
313 ("usage: svnadmin create REPOS_PATH\n\n"
314 "Create a new, empty repository at REPOS_PATH.\n"),
315 {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
316 svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
317 svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
318 svnadmin__pre_1_6_compatible
319 } },
320
321 {"delrevprop", subcommand_delrevprop, {0}, N_
322 ("usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n"
323 " 2. svnadmin delrevprop REPOS_PATH -t TXN NAME\n\n"
324 "1. Delete the property NAME on revision REVISION.\n\n"
325 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
326 "trigger the revision property-related hooks (for example, if you want\n"
327 "an email notification sent from your post-revprop-change hook).\n\n"
328 "NOTE: Revision properties are not versioned, so this command will\n"
329 "irreversibly destroy the previous value of the property.\n\n"
330 "2. Delete the property NAME on transaction TXN.\n"),
331 {'r', 't', svnadmin__use_pre_revprop_change_hook,
332 svnadmin__use_post_revprop_change_hook} },
333
334 {"deltify", subcommand_deltify, {0}, N_
335 ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
336 "Run over the requested revision range, performing predecessor delti-\n"
337 "fication on the paths changed in those revisions. Deltification in\n"
338 "essence compresses the repository by only storing the differences or\n"
339 "delta from the preceding revision. If no revisions are specified,\n"
340 "this will simply deltify the HEAD revision.\n"),
341 {'r', 'q', 'M'} },
342
343 {"dump", subcommand_dump, {0}, N_
344 ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
345 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
346 "portable format, sending feedback to stderr. Dump revisions\n"
347 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
348 "revision trees. If only LOWER is given, dump that one revision tree.\n"
349 "If --incremental is passed, the first revision dumped will describe\n"
350 "only the paths changed in that revision; otherwise it will describe\n"
351 "every path present in the repository as of that revision. (In either\n"
352 "case, the second and subsequent revisions, if any, describe only paths\n"
353 "changed in those revisions.)\n"
354 "\n"
355 "Using --exclude or --include gives results equivalent to authz-based\n"
356 "path exclusions. In particular, when the source of a copy is\n"
357 "excluded, the copy is transformed into an add (unlike in 'svndumpfilter').\n"),
358 {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F',
359 svnadmin__exclude, svnadmin__include, svnadmin__glob },
360 {{'F', N_("write to file ARG instead of stdout")}} },
361
362 {"dump-revprops", subcommand_dump_revprops, {0}, N_
363 ("usage: svnadmin dump-revprops REPOS_PATH [-r LOWER[:UPPER]]\n\n"
364 "Dump the revision properties of filesystem to stdout in a 'dumpfile'\n"
365 "portable format, sending feedback to stderr. Dump revisions\n"
366 "LOWER rev through UPPER rev. If no revisions are given, dump the\n"
367 "properties for all revisions. If only LOWER is given, dump the\n"
368 "properties for that one revision.\n"),
369 {'r', 'q', 'F'},
370 {{'F', N_("write to file ARG instead of stdout")}} },
371
372 {"freeze", subcommand_freeze, {0}, N_
373 ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
374 " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
375 "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
376 " Allows safe use of third-party backup tools on a live repository.\n"
377 "\n"
378 "2. Like 1 except all repositories listed in FILE are locked. The file\n"
379 " format is repository paths separated by newlines. Repositories are\n"
380 " locked in the same order as they are listed in the file.\n"),
381 {'F'},
382 {{'F', N_("read repository paths from file ARG")}} },
383
384 {"help", subcommand_help, {"?", "h"}, N_
385 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
386 "Describe the usage of this program or its subcommands.\n"),
387 {0} },
388
389 {"hotcopy", subcommand_hotcopy, {0}, N_
390 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
391 "Make a hot copy of a repository.\n"
392 "If --incremental is passed, data which already exists at the destination\n"
393 "is not copied again. Incremental mode is implemented for FSFS repositories.\n"),
394 {svnadmin__clean_logs, svnadmin__incremental, 'q'} },
395
396 {"info", subcommand_info, {0}, N_
397 ("usage: svnadmin info REPOS_PATH\n\n"
398 "Print information about the repository at REPOS_PATH.\n"),
399 {0} },
400
401 {"list-dblogs", subcommand_list_dblogs, {0}, N_
402 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
403 "List all Berkeley DB log files.\n\n"
404 "WARNING: Modifying or deleting logfiles which are still in use\n"
405 "will cause your repository to be corrupted.\n"),
406 {0} },
407
408 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
409 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
410 "List unused Berkeley DB log files.\n\n"),
411 {0} },
412
413 {"load", subcommand_load, {0}, N_
414 ("usage: svnadmin load REPOS_PATH\n\n"
415 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
416 "new revisions into the repository's filesystem. If the repository\n"
417 "was previously empty, its UUID will, by default, be changed to the\n"
418 "one specified in the stream. Progress feedback is sent to stdout.\n"
419 "If --revision is specified, limit the loaded revisions to only those\n"
420 "in the dump stream whose revision numbers match the specified range.\n"),
421 {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
422 svnadmin__ignore_dates,
423 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
424 svnadmin__parent_dir, svnadmin__normalize_props,
425 svnadmin__bypass_prop_validation, 'M',
426 svnadmin__no_flush_to_disk, 'F'},
427 {{'F', N_("read from file ARG instead of stdin")}} },
428
429 {"load-revprops", subcommand_load_revprops, {0}, N_
430 ("usage: svnadmin load-revprops REPOS_PATH\n\n"
431 "Read a 'dumpfile'-formatted stream from stdin, setting the revision\n"
432 "properties in the repository's filesystem. Revisions not found in the\n"
433 "repository will cause an error. Progress feedback is sent to stdout.\n"
434 "If --revision is specified, limit the loaded revisions to only those\n"
435 "in the dump stream whose revision numbers match the specified range.\n"),
436 {'q', 'r', svnadmin__force_uuid, svnadmin__normalize_props,
437 svnadmin__bypass_prop_validation, svnadmin__no_flush_to_disk, 'F'},
438 {{'F', N_("read from file ARG instead of stdin")}} },
439
440 {"lock", subcommand_lock, {0}, N_
441 ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
442 "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
443 "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
444 "triggering the pre-lock and post-lock hook scripts.\n"),
445 {svnadmin__bypass_hooks, 'q'} },
446
447 {"lslocks", subcommand_lslocks, {0}, N_
448 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
449 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
450 "if not provided, is the root of the repository).\n"),
451 {0} },
452
453 {"lstxns", subcommand_lstxns, {0}, N_
454 ("usage: svnadmin lstxns REPOS_PATH\n\n"
455 "Print the names of uncommitted transactions. With -rN skip the output\n"
456 "of those that have a base revision more recent than rN. Transactions\n"
457 "with base revisions much older than HEAD are likely to have been\n"
458 "abandonded and are candidates to be removed.\n"),
459 {'r'},
460 { {'r', "transaction base revision ARG"} } },
461
462 {"pack", subcommand_pack, {0}, N_
463 ("usage: svnadmin pack REPOS_PATH\n\n"
464 "Possibly compact the repository into a more efficient storage model.\n"
465 "This may not apply to all repositories, in which case, exit.\n"),
466 {'q', 'M'} },
467
468 {"recover", subcommand_recover, {0}, N_
469 ("usage: svnadmin recover REPOS_PATH\n\n"
470 "Run the recovery procedure on a repository. Do this if you've\n"
471 "been getting errors indicating that recovery ought to be run.\n"
472 "Berkeley DB recovery requires exclusive access and will\n"
473 "exit if the repository is in use by another process.\n"),
474 {svnadmin__wait} },
475
476 {"rmlocks", subcommand_rmlocks, {0}, N_
477 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
478 "Unconditionally remove lock from each LOCKED_PATH.\n"),
479 {'q'} },
480
481 {"rmtxns", subcommand_rmtxns, {0}, N_
482 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
483 "Delete the named transaction(s).\n"),
484 {'q'} },
485
486 {"setlog", subcommand_setlog, {0}, N_
487 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
488 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
489 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
490 "(for example, if you do not want an email notification sent\n"
491 "from your post-revprop-change hook, or because the modification of\n"
492 "revision properties has not been enabled in the pre-revprop-change\n"
493 "hook).\n\n"
494 "NOTE: Revision properties are not versioned, so this command will\n"
495 "overwrite the previous log message.\n"),
496 {'r', svnadmin__bypass_hooks} },
497
498 {"setrevprop", subcommand_setrevprop, {0}, N_
499 ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n"
500 " 2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n\n"
501 "1. Set the property NAME on revision REVISION to the contents of FILE.\n\n"
502 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
503 "trigger the revision property-related hooks (for example, if you want\n"
504 "an email notification sent from your post-revprop-change hook).\n\n"
505 "NOTE: Revision properties are not versioned, so this command will\n"
506 "overwrite the previous value of the property.\n\n"
507 "2. Set the property NAME on transaction TXN to the contents of FILE.\n"),
508 {'r', 't', svnadmin__use_pre_revprop_change_hook,
509 svnadmin__use_post_revprop_change_hook} },
510
511 {"setuuid", subcommand_setuuid, {0}, N_
512 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
513 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
514 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
515 "generate a brand new UUID for the repository.\n"),
516 {0} },
517
518 {"unlock", subcommand_unlock, {0}, N_
519 ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
520 "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
521 "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
522 "triggering the pre-unlock and post-unlock hook scripts.\n"),
523 {svnadmin__bypass_hooks, 'q'} },
524
525 {"upgrade", subcommand_upgrade, {0}, N_
526 ("usage: svnadmin upgrade REPOS_PATH\n\n"
527 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
528 "schema version.\n\n"
529 "This functionality is provided as a convenience for repository\n"
530 "administrators who wish to make use of new Subversion functionality\n"
531 "without having to undertake a potentially costly full repository dump\n"
532 "and load operation. As such, the upgrade performs only the minimum\n"
533 "amount of work needed to accomplish this while still maintaining the\n"
534 "integrity of the repository. It does not guarantee the most optimized\n"
535 "repository state as a dump and subsequent load would.\n"),
536 {0} },
537
538 {"verify", subcommand_verify, {0}, N_
539 ("usage: svnadmin verify REPOS_PATH\n\n"
540 "Verify the data stored in the repository.\n"),
541 {'t', 'r', 'q', svnadmin__keep_going, 'M',
542 svnadmin__check_normalization, svnadmin__metadata_only} },
543
544 { NULL, NULL, {0}, NULL, {0} }
545 };
546
547
548 /* Baton for passing option/argument state to a subcommand function. */
549 struct svnadmin_opt_state
550 {
551 const char *repository_path;
552 const char *fs_type; /* --fs-type */
553 svn_version_t *compatible_version; /* --compatible-version */
554 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
555 const char *txn_id; /* -t TXN */
556 svn_boolean_t help; /* --help or -? */
557 svn_boolean_t version; /* --version */
558 svn_boolean_t incremental; /* --incremental */
559 svn_boolean_t use_deltas; /* --deltas */
560 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
561 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
562 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
563 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
564 svn_boolean_t quiet; /* --quiet */
565 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
566 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
567 svn_boolean_t clean_logs; /* --clean-logs */
568 svn_boolean_t bypass_hooks; /* --bypass-hooks */
569 svn_boolean_t wait; /* --wait */
570 svn_boolean_t keep_going; /* --keep-going */
571 svn_boolean_t check_normalization; /* --check-normalization */
572 svn_boolean_t metadata_only; /* --metadata-only */
573 svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
574 svn_boolean_t ignore_dates; /* --ignore-dates */
575 svn_boolean_t no_flush_to_disk; /* --no-flush-to-disk */
576 svn_boolean_t normalize_props; /* --normalize_props */
577 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
578 --force-uuid */
579 apr_uint64_t memory_cache_size; /* --memory-cache-size M */
580 const char *parent_dir; /* --parent-dir */
581 const char *file; /* --file */
582 apr_array_header_t *exclude; /* --exclude */
583 apr_array_header_t *include; /* --include */
584 svn_boolean_t glob; /* --pattern */
585
586 const char *config_dir; /* Overriding Configuration Directory */
587 };
588
589
590 /* Helper to open a repository and set a warning func (so we don't
591 * SEGFAULT when libsvn_fs's default handler gets run). */
592 static svn_error_t *
open_repos(svn_repos_t ** repos,const char * path,struct svnadmin_opt_state * opt_state,apr_pool_t * pool)593 open_repos(svn_repos_t **repos,
594 const char *path,
595 struct svnadmin_opt_state *opt_state,
596 apr_pool_t *pool)
597 {
598 /* Enable the "block-read" feature (where it applies)? */
599 svn_boolean_t use_block_read
600 = svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD;
601
602 /* construct FS configuration parameters: enable caches for r/o data */
603 apr_hash_t *fs_config = apr_hash_make(pool);
604 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
605 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
606 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS, "1");
607 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
608 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
609 svn_uuid_generate(pool));
610 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
611 use_block_read ? "1" : "0");
612 svn_hash_sets(fs_config, SVN_FS_CONFIG_NO_FLUSH_TO_DISK,
613 opt_state->no_flush_to_disk ? "1" : "0");
614
615 /* now, open the requested repository */
616 SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool));
617 svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
618 return SVN_NO_ERROR;
619 }
620
621
622 /* Set *REVNUM to the revision specified by REVISION (or to
623 SVN_INVALID_REVNUM if that has the type 'unspecified'),
624 possibly making use of the YOUNGEST revision number in REPOS. */
625 static svn_error_t *
get_revnum(svn_revnum_t * revnum,const svn_opt_revision_t * revision,svn_revnum_t youngest,svn_repos_t * repos,apr_pool_t * pool)626 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
627 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
628 {
629 if (revision->kind == svn_opt_revision_number)
630 *revnum = revision->value.number;
631 else if (revision->kind == svn_opt_revision_head)
632 *revnum = youngest;
633 else if (revision->kind == svn_opt_revision_date)
634 SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
635 pool));
636 else if (revision->kind == svn_opt_revision_unspecified)
637 *revnum = SVN_INVALID_REVNUM;
638 else
639 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
640 _("Invalid revision specifier"));
641
642 if (*revnum > youngest)
643 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
644 _("Revisions must not be greater than the youngest revision (%ld)"),
645 youngest);
646
647 return SVN_NO_ERROR;
648 }
649
650 /* Set *FSPATH to an internal-style fspath parsed from ARG. */
651 static svn_error_t *
target_arg_to_fspath(const char ** fspath,const char * arg,apr_pool_t * result_pool,apr_pool_t * scratch_pool)652 target_arg_to_fspath(const char **fspath,
653 const char *arg,
654 apr_pool_t *result_pool,
655 apr_pool_t *scratch_pool)
656 {
657 /* ### Using a private API. This really shouldn't be needed. */
658 *fspath = svn_fspath__canonicalize(arg, result_pool);
659 return SVN_NO_ERROR;
660 }
661
662 /* Set *DIRENT to an internal-style, local dirent path
663 allocated from POOL and parsed from PATH. */
664 static svn_error_t *
target_arg_to_dirent(const char ** dirent,const char * path,apr_pool_t * pool)665 target_arg_to_dirent(const char **dirent,
666 const char *path,
667 apr_pool_t *pool)
668 {
669 if (svn_path_is_url(path))
670 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
671 _("Path '%s' is not a local path"), path);
672 *dirent = svn_dirent_internal_style(path, pool);
673 return SVN_NO_ERROR;
674 }
675
676 /* Parse the remaining command-line arguments from OS, returning them
677 in a new array *ARGS (allocated from POOL) and optionally verifying
678 that we got the expected number thereof. If MIN_EXPECTED is not
679 negative, return an error if the function would return fewer than
680 MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
681 error if the function would return more than MAX_EXPECTED
682 arguments.
683
684 As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
685 allow ARGS to be NULL. */
686 static svn_error_t *
parse_args(apr_array_header_t ** args,apr_getopt_t * os,int min_expected,int max_expected,apr_pool_t * pool)687 parse_args(apr_array_header_t **args,
688 apr_getopt_t *os,
689 int min_expected,
690 int max_expected,
691 apr_pool_t *pool)
692 {
693 int num_args = os ? (os->argc - os->ind) : 0;
694
695 if (min_expected || max_expected)
696 SVN_ERR_ASSERT(args);
697
698 if ((min_expected >= 0) && (num_args < min_expected))
699 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
700 _("Not enough arguments"));
701 if ((max_expected >= 0) && (num_args > max_expected))
702 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
703 _("Too many arguments"));
704 if (args)
705 {
706 *args = apr_array_make(pool, num_args, sizeof(const char *));
707
708 if (num_args)
709 while (os->ind < os->argc)
710 {
711 const char *arg;
712
713 SVN_ERR(svn_utf_cstring_to_utf8(&arg, os->argv[os->ind++], pool));
714 APR_ARRAY_PUSH(*args, const char *) = arg;
715 }
716 }
717
718 return SVN_NO_ERROR;
719 }
720
721
722 /* This implements 'svn_error_malfunction_handler_t. */
723 static svn_error_t *
crashtest_malfunction_handler(svn_boolean_t can_return,const char * file,int line,const char * expr)724 crashtest_malfunction_handler(svn_boolean_t can_return,
725 const char *file,
726 int line,
727 const char *expr)
728 {
729 abort();
730 return SVN_NO_ERROR; /* Not reached. */
731 }
732
733 /* This implements `svn_opt_subcommand_t'. */
734 static svn_error_t *
subcommand_crashtest(apr_getopt_t * os,void * baton,apr_pool_t * pool)735 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
736 {
737 struct svnadmin_opt_state *opt_state = baton;
738 svn_repos_t *repos;
739
740 (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler);
741 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
742 SVN_ERR(svn_cmdline_printf(pool,
743 _("Successfully opened repository '%s'.\n"
744 "Will now crash to simulate a crashing "
745 "server process.\n"),
746 svn_dirent_local_style(opt_state->repository_path,
747 pool)));
748 SVN_ERR_MALFUNCTION();
749
750 /* merely silence a compiler warning (this will never be executed) */
751 return SVN_NO_ERROR;
752 }
753
754 /* This implements `svn_opt_subcommand_t'. */
755 static svn_error_t *
subcommand_create(apr_getopt_t * os,void * baton,apr_pool_t * pool)756 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
757 {
758 struct svnadmin_opt_state *opt_state = baton;
759 svn_repos_t *repos;
760 apr_hash_t *fs_config = apr_hash_make(pool);
761
762 /* Expect no more arguments. */
763 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
764
765 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
766 (opt_state->bdb_txn_nosync ? "1" :"0"));
767
768 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
769 (opt_state->bdb_log_keep ? "0" :"1"));
770
771 if (opt_state->fs_type)
772 {
773 /* With 1.8 we are announcing that BDB is deprecated. No support
774 * has been removed and it will continue to work until some future
775 * date. The purpose here is to discourage people from creating
776 * new BDB repositories which they will need to dump/load into
777 * FSFS or some new FS type in the future. */
778 if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
779 {
780 SVN_ERR(svn_cmdline_fprintf(
781 stderr, pool,
782 _("%swarning:"
783 " The \"%s\" repository back-end is deprecated,"
784 " consider using \"%s\" instead.\n"),
785 "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
786 fflush(stderr);
787 }
788 svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
789 }
790
791 if (opt_state->compatible_version)
792 {
793 if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
794 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
795 if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
796 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
797 if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
798 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
799 if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
800 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
801 /* In 1.9, we figured out that we didn't have to keep extending this
802 madness indefinitely. */
803 svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION,
804 apr_psprintf(pool, "%d.%d.%d%s%s",
805 opt_state->compatible_version->major,
806 opt_state->compatible_version->minor,
807 opt_state->compatible_version->patch,
808 opt_state->compatible_version->tag
809 ? "-" : "",
810 opt_state->compatible_version->tag
811 ? opt_state->compatible_version->tag : ""));
812 }
813
814 if (opt_state->compatible_version)
815 {
816 if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
817 /* ### TODO: this NULL check hard-codes knowledge of the library's
818 default fs-type value */
819 && (opt_state->fs_type == NULL
820 || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
821 {
822 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
823 _("Repositories compatible with 1.0.x must "
824 "use --fs-type=bdb"));
825 }
826
827 if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0)
828 && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX))
829 {
830 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
831 _("Repositories compatible with 1.8.x or "
832 "earlier cannot use --fs-type=%s"),
833 SVN_FS_TYPE_FSX);
834 }
835 }
836
837 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
838 NULL, NULL, NULL, fs_config, pool));
839 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
840 return SVN_NO_ERROR;
841 }
842
843
844 /* This implements `svn_opt_subcommand_t'. */
845 static svn_error_t *
subcommand_deltify(apr_getopt_t * os,void * baton,apr_pool_t * pool)846 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
847 {
848 struct svnadmin_opt_state *opt_state = baton;
849 svn_repos_t *repos;
850 svn_fs_t *fs;
851 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
852 svn_revnum_t youngest, revision;
853 apr_pool_t *subpool = svn_pool_create(pool);
854
855 /* Expect no more arguments. */
856 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
857
858 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
859 fs = svn_repos_fs(repos);
860 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
861
862 /* Find the revision numbers at which to start and end. */
863 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
864 youngest, repos, pool));
865 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
866 youngest, repos, pool));
867
868 /* Fill in implied revisions if necessary. */
869 if (start == SVN_INVALID_REVNUM)
870 start = youngest;
871 if (end == SVN_INVALID_REVNUM)
872 end = start;
873
874 if (start > end)
875 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
876 _("First revision cannot be higher than second"));
877
878 /* Loop over the requested revision range, performing the
879 predecessor deltification on paths changed in each. */
880 for (revision = start; revision <= end; revision++)
881 {
882 svn_pool_clear(subpool);
883 SVN_ERR(check_cancel(NULL));
884 if (! opt_state->quiet)
885 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
886 revision));
887 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
888 if (! opt_state->quiet)
889 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
890 }
891 svn_pool_destroy(subpool);
892
893 return SVN_NO_ERROR;
894 }
895
896 /* Structure for errors encountered during 'svnadmin verify --keep-going'. */
897 struct verification_error
898 {
899 svn_revnum_t rev;
900 svn_error_t *err;
901 };
902
903 /* Pool cleanup function to clear an svn_error_t *. */
904 static apr_status_t
err_cleanup(void * data)905 err_cleanup(void *data)
906 {
907 svn_error_t *err = data;
908
909 svn_error_clear(err);
910
911 return APR_SUCCESS;
912 }
913
914 struct repos_verify_callback_baton
915 {
916 /* Should we continue after receiving a first verification error? */
917 svn_boolean_t keep_going;
918
919 /* List of errors encountered during 'svnadmin verify --keep-going'. */
920 apr_array_header_t *error_summary;
921
922 /* Pool for data collected during callback invocations. */
923 apr_pool_t *result_pool;
924 };
925
926 /* Implementation of svn_repos_verify_callback_t to handle errors coming
927 from svn_repos_verify_fs3(). */
928 static svn_error_t *
repos_verify_callback(void * baton,svn_revnum_t revision,svn_error_t * verify_err,apr_pool_t * scratch_pool)929 repos_verify_callback(void *baton,
930 svn_revnum_t revision,
931 svn_error_t *verify_err,
932 apr_pool_t *scratch_pool)
933 {
934 struct repos_verify_callback_baton *b = baton;
935
936 if (revision == SVN_INVALID_REVNUM)
937 {
938 SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"),
939 stderr, scratch_pool));
940 }
941 else
942 {
943 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
944 _("* Error verifying revision %ld.\n"),
945 revision));
946 }
947
948 if (b->keep_going)
949 {
950 struct verification_error *verr;
951
952 svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: ");
953
954 /* Remember the error in B->ERROR_SUMMARY. */
955 verr = apr_palloc(b->result_pool, sizeof(*verr));
956 verr->rev = revision;
957 verr->err = svn_error_dup(verify_err);
958 apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup,
959 apr_pool_cleanup_null);
960 APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr;
961
962 return SVN_NO_ERROR;
963 }
964 else
965 return svn_error_trace(svn_error_dup(verify_err));
966 }
967
968 /* Implementation of svn_repos_notify_func_t to wrap the output to a
969 response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(),
970 svn_repos_hotcopy3() and others. */
971 static void
repos_notify_handler(void * baton,const svn_repos_notify_t * notify,apr_pool_t * scratch_pool)972 repos_notify_handler(void *baton,
973 const svn_repos_notify_t *notify,
974 apr_pool_t *scratch_pool)
975 {
976 svn_stream_t *feedback_stream = baton;
977
978 switch (notify->action)
979 {
980 case svn_repos_notify_warning:
981 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
982 "WARNING 0x%04x: %s\n", notify->warning,
983 notify->warning_str));
984 return;
985
986 case svn_repos_notify_dump_rev_end:
987 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
988 _("* Dumped revision %ld.\n"),
989 notify->revision));
990 return;
991
992 case svn_repos_notify_verify_rev_end:
993 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
994 _("* Verified revision %ld.\n"),
995 notify->revision));
996 return;
997
998 case svn_repos_notify_verify_rev_structure:
999 if (notify->revision == SVN_INVALID_REVNUM)
1000 svn_error_clear(svn_stream_puts(feedback_stream,
1001 _("* Verifying repository metadata ...\n")));
1002 else
1003 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1004 _("* Verifying metadata at revision %ld ...\n"),
1005 notify->revision));
1006 return;
1007
1008 case svn_repos_notify_pack_shard_start:
1009 {
1010 const char *shardstr = apr_psprintf(scratch_pool,
1011 "%" APR_INT64_T_FMT,
1012 notify->shard);
1013 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1014 _("Packing revisions in shard %s..."),
1015 shardstr));
1016 }
1017 return;
1018
1019 case svn_repos_notify_pack_shard_end:
1020 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1021 return;
1022
1023 case svn_repos_notify_pack_shard_start_revprop:
1024 {
1025 const char *shardstr = apr_psprintf(scratch_pool,
1026 "%" APR_INT64_T_FMT,
1027 notify->shard);
1028 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1029 _("Packing revprops in shard %s..."),
1030 shardstr));
1031 }
1032 return;
1033
1034 case svn_repos_notify_pack_shard_end_revprop:
1035 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1036 return;
1037
1038 case svn_repos_notify_load_txn_committed:
1039 if (notify->old_revision == SVN_INVALID_REVNUM)
1040 {
1041 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1042 _("\n------- Committed revision %ld >>>\n\n"),
1043 notify->new_revision));
1044 }
1045 else
1046 {
1047 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1048 _("\n------- Committed new rev %ld"
1049 " (loaded from original rev %ld"
1050 ") >>>\n\n"), notify->new_revision,
1051 notify->old_revision));
1052 }
1053 return;
1054
1055 case svn_repos_notify_load_node_start:
1056 {
1057 switch (notify->node_action)
1058 {
1059 case svn_node_action_change:
1060 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1061 _(" * editing path : %s ..."),
1062 notify->path));
1063 break;
1064
1065 case svn_node_action_delete:
1066 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1067 _(" * deleting path : %s ..."),
1068 notify->path));
1069 break;
1070
1071 case svn_node_action_add:
1072 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1073 _(" * adding path : %s ..."),
1074 notify->path));
1075 break;
1076
1077 case svn_node_action_replace:
1078 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1079 _(" * replacing path : %s ..."),
1080 notify->path));
1081 break;
1082
1083 }
1084 }
1085 return;
1086
1087 case svn_repos_notify_load_node_done:
1088 svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n")));
1089 return;
1090
1091 case svn_repos_notify_load_copied_node:
1092 svn_error_clear(svn_stream_puts(feedback_stream, "COPIED..."));
1093 return;
1094
1095 case svn_repos_notify_load_txn_start:
1096 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1097 _("<<< Started new transaction, based on "
1098 "original revision %ld\n"),
1099 notify->old_revision));
1100 return;
1101
1102 case svn_repos_notify_load_skipped_rev:
1103 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1104 _("<<< Skipped original revision %ld\n"),
1105 notify->old_revision));
1106 return;
1107
1108 case svn_repos_notify_load_normalized_mergeinfo:
1109 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1110 _(" removing '\\r' from %s ..."),
1111 SVN_PROP_MERGEINFO));
1112 return;
1113
1114 case svn_repos_notify_mutex_acquired:
1115 svn_cmdline__setup_cancellation_handler();
1116 return;
1117
1118 case svn_repos_notify_recover_start:
1119 svn_error_clear(svn_stream_puts(feedback_stream,
1120 _("Repository lock acquired.\n"
1121 "Please wait; recovering the"
1122 " repository may take some time...\n")));
1123 return;
1124
1125 case svn_repos_notify_upgrade_start:
1126 svn_error_clear(svn_stream_puts(feedback_stream,
1127 _("Repository lock acquired.\n"
1128 "Please wait; upgrading the"
1129 " repository may take some time...\n")));
1130 return;
1131
1132 case svn_repos_notify_pack_revprops:
1133 {
1134 const char *shardstr = apr_psprintf(scratch_pool,
1135 "%" APR_INT64_T_FMT,
1136 notify->shard);
1137 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1138 _("Packed revision properties in shard %s\n"),
1139 shardstr));
1140 return;
1141 }
1142
1143 case svn_repos_notify_cleanup_revprops:
1144 {
1145 const char *shardstr = apr_psprintf(scratch_pool,
1146 "%" APR_INT64_T_FMT,
1147 notify->shard);
1148 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1149 _("Removed non-packed revision properties"
1150 " in shard %s\n"),
1151 shardstr));
1152 return;
1153 }
1154
1155 case svn_repos_notify_format_bumped:
1156 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1157 _("Bumped repository format to %ld\n"),
1158 notify->revision));
1159 return;
1160
1161 case svn_repos_notify_hotcopy_rev_range:
1162 if (notify->start_revision == notify->end_revision)
1163 {
1164 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1165 _("* Copied revision %ld.\n"),
1166 notify->start_revision));
1167 }
1168 else
1169 {
1170 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1171 _("* Copied revisions from %ld to %ld.\n"),
1172 notify->start_revision, notify->end_revision));
1173 }
1174 return;
1175
1176 case svn_repos_notify_pack_noop:
1177 /* For best backward compatibility, we keep silent if there were just
1178 no more shards to pack. */
1179 if (notify->shard == -1)
1180 {
1181 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1182 _("svnadmin: Warning - this repository is not sharded."
1183 " Packing has no effect.\n")));
1184 }
1185 return;
1186
1187 case svn_repos_notify_load_revprop_set:
1188 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1189 _("Properties set on revision %ld.\n"),
1190 notify->new_revision));
1191 return;
1192
1193 default:
1194 return;
1195 }
1196 }
1197
1198
1199 /* Baton for recode_write(). */
1200 struct recode_write_baton
1201 {
1202 apr_pool_t *pool;
1203 FILE *out;
1204 };
1205
1206 /* This implements the 'svn_write_fn_t' interface.
1207
1208 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
1209 console encoding, using svn_cmdline_fprintf(). DATA is a
1210 UTF8-encoded C string, therefore ignore LEN.
1211
1212 ### This recoding mechanism might want to be abstracted into
1213 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
recode_write(void * baton,const char * data,apr_size_t * len)1214 static svn_error_t *recode_write(void *baton,
1215 const char *data,
1216 apr_size_t *len)
1217 {
1218 struct recode_write_baton *rwb = baton;
1219 svn_pool_clear(rwb->pool);
1220 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
1221 }
1222
1223 /* Create a stream, to write to STD_STREAM, that uses recode_write()
1224 to perform UTF-8 to console encoding translation. */
1225 static svn_stream_t *
recode_stream_create(FILE * std_stream,apr_pool_t * pool)1226 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
1227 {
1228 struct recode_write_baton *std_stream_rwb =
1229 apr_palloc(pool, sizeof(struct recode_write_baton));
1230
1231 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
1232 std_stream_rwb->pool = svn_pool_create(pool);
1233 std_stream_rwb->out = std_stream;
1234 svn_stream_set_write(rw_stream, recode_write);
1235 return rw_stream;
1236 }
1237
1238 /* Read the min / max revision from the OPT_STATE, verify them against REPOS
1239 and return them in *LOWER and *UPPER, respectively. Use SCRATCH_POOL
1240 for temporary allocations. */
1241 static svn_error_t *
get_dump_range(svn_revnum_t * lower,svn_revnum_t * upper,svn_repos_t * repos,struct svnadmin_opt_state * opt_state,apr_pool_t * scratch_pool)1242 get_dump_range(svn_revnum_t *lower,
1243 svn_revnum_t *upper,
1244 svn_repos_t *repos,
1245 struct svnadmin_opt_state *opt_state,
1246 apr_pool_t *scratch_pool)
1247 {
1248 svn_fs_t *fs;
1249 svn_revnum_t youngest;
1250
1251 *lower = SVN_INVALID_REVNUM;
1252 *upper = SVN_INVALID_REVNUM;
1253
1254 fs = svn_repos_fs(repos);
1255 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, scratch_pool));
1256
1257 /* Find the revision numbers at which to start and end. */
1258 SVN_ERR(get_revnum(lower, &opt_state->start_revision,
1259 youngest, repos, scratch_pool));
1260 SVN_ERR(get_revnum(upper, &opt_state->end_revision,
1261 youngest, repos, scratch_pool));
1262
1263 /* Fill in implied revisions if necessary. */
1264 if (*lower == SVN_INVALID_REVNUM)
1265 {
1266 *lower = 0;
1267 *upper = youngest;
1268 }
1269 else if (*upper == SVN_INVALID_REVNUM)
1270 {
1271 *upper = *lower;
1272 }
1273
1274 if (*lower > *upper)
1275 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1276 _("First revision cannot be higher than second"));
1277
1278 return SVN_NO_ERROR;
1279 }
1280
1281 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
1282 * Return TRUE if any prefix is a prefix of PATH (matching whole path
1283 * components); FALSE otherwise.
1284 * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
1285 /* This function is a duplicate of svndumpfilter.c:ary_prefix_match(). */
1286 static svn_boolean_t
ary_prefix_match(const apr_array_header_t * pfxlist,const char * path)1287 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
1288 {
1289 int i;
1290 size_t path_len = strlen(path);
1291
1292 for (i = 0; i < pfxlist->nelts; i++)
1293 {
1294 const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
1295 size_t pfx_len = strlen(pfx);
1296
1297 if (path_len < pfx_len)
1298 continue;
1299 if (strncmp(path, pfx, pfx_len) == 0
1300 && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
1301 return TRUE;
1302 }
1303
1304 return FALSE;
1305 }
1306
1307 /* Baton for dump_filter_func(). */
1308 struct dump_filter_baton_t
1309 {
1310 apr_array_header_t *prefixes;
1311 svn_boolean_t glob;
1312 svn_boolean_t do_exclude;
1313 };
1314
1315 /* Implements svn_repos_dump_filter_func_t. */
1316 static svn_error_t *
dump_filter_func(svn_boolean_t * include,svn_fs_root_t * root,const char * path,void * baton,apr_pool_t * scratch_pool)1317 dump_filter_func(svn_boolean_t *include,
1318 svn_fs_root_t *root,
1319 const char *path,
1320 void *baton,
1321 apr_pool_t *scratch_pool)
1322 {
1323 struct dump_filter_baton_t *b = baton;
1324 const svn_boolean_t matches =
1325 (b->glob
1326 ? svn_cstring_match_glob_list(path, b->prefixes)
1327 : ary_prefix_match(b->prefixes, path));
1328
1329 *include = b->do_exclude ? !matches : matches;
1330 return SVN_NO_ERROR;
1331 }
1332
1333 /* This implements `svn_opt_subcommand_t'. */
1334 static svn_error_t *
subcommand_dump(apr_getopt_t * os,void * baton,apr_pool_t * pool)1335 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1336 {
1337 struct svnadmin_opt_state *opt_state = baton;
1338 svn_repos_t *repos;
1339 svn_stream_t *out_stream;
1340 svn_revnum_t lower, upper;
1341 svn_stream_t *feedback_stream = NULL;
1342 struct dump_filter_baton_t filter_baton = {0};
1343
1344 /* Expect no more arguments. */
1345 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1346
1347 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1348 SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1349
1350 /* Open the file or STDOUT, depending on whether -F was specified. */
1351 if (opt_state->file)
1352 {
1353 apr_file_t *file;
1354
1355 /* Overwrite existing files, same as with > redirection. */
1356 SVN_ERR(svn_io_file_open(&file, opt_state->file,
1357 APR_WRITE | APR_CREATE | APR_TRUNCATE
1358 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1359 out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1360 }
1361 else
1362 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1363
1364 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1365 if (! opt_state->quiet)
1366 feedback_stream = recode_stream_create(stderr, pool);
1367
1368 /* Initialize the filter baton. */
1369 filter_baton.glob = opt_state->glob;
1370
1371 if (opt_state->exclude && !opt_state->include)
1372 {
1373 filter_baton.prefixes = opt_state->exclude;
1374 filter_baton.do_exclude = TRUE;
1375 }
1376 else if (opt_state->include && !opt_state->exclude)
1377 {
1378 filter_baton.prefixes = opt_state->include;
1379 filter_baton.do_exclude = FALSE;
1380 }
1381 else if (opt_state->include && opt_state->exclude)
1382 {
1383 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1384 _("'--exclude' and '--include' options "
1385 "cannot be used simultaneously"));
1386 }
1387
1388 SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1389 opt_state->incremental, opt_state->use_deltas,
1390 TRUE, TRUE,
1391 !opt_state->quiet ? repos_notify_handler : NULL,
1392 feedback_stream,
1393 filter_baton.prefixes ? dump_filter_func : NULL,
1394 &filter_baton,
1395 check_cancel, NULL, pool));
1396
1397 return SVN_NO_ERROR;
1398 }
1399
1400 /* This implements `svn_opt_subcommand_t'. */
1401 static svn_error_t *
subcommand_dump_revprops(apr_getopt_t * os,void * baton,apr_pool_t * pool)1402 subcommand_dump_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1403 {
1404 struct svnadmin_opt_state *opt_state = baton;
1405 svn_repos_t *repos;
1406 svn_stream_t *out_stream;
1407 svn_revnum_t lower, upper;
1408 svn_stream_t *feedback_stream = NULL;
1409
1410 /* Expect no more arguments. */
1411 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1412
1413 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1414 SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1415
1416 /* Open the file or STDOUT, depending on whether -F was specified. */
1417 if (opt_state->file)
1418 {
1419 apr_file_t *file;
1420
1421 /* Overwrite existing files, same as with > redirection. */
1422 SVN_ERR(svn_io_file_open(&file, opt_state->file,
1423 APR_WRITE | APR_CREATE | APR_TRUNCATE
1424 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1425 out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1426 }
1427 else
1428 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1429
1430 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1431 if (! opt_state->quiet)
1432 feedback_stream = recode_stream_create(stderr, pool);
1433
1434 SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1435 FALSE, FALSE, TRUE, FALSE,
1436 !opt_state->quiet ? repos_notify_handler : NULL,
1437 feedback_stream, NULL, NULL,
1438 check_cancel, NULL, pool));
1439
1440 return SVN_NO_ERROR;
1441 }
1442
1443 struct freeze_baton_t {
1444 const char *command;
1445 const char **args;
1446 int status;
1447 };
1448
1449 /* Implements svn_repos_freeze_func_t */
1450 static svn_error_t *
freeze_body(void * baton,apr_pool_t * pool)1451 freeze_body(void *baton,
1452 apr_pool_t *pool)
1453 {
1454 struct freeze_baton_t *b = baton;
1455 apr_status_t apr_err;
1456 apr_file_t *infile, *outfile, *errfile;
1457
1458 apr_err = apr_file_open_stdin(&infile, pool);
1459 if (apr_err)
1460 return svn_error_wrap_apr(apr_err, "Can't open stdin");
1461 apr_err = apr_file_open_stdout(&outfile, pool);
1462 if (apr_err)
1463 return svn_error_wrap_apr(apr_err, "Can't open stdout");
1464 apr_err = apr_file_open_stderr(&errfile, pool);
1465 if (apr_err)
1466 return svn_error_wrap_apr(apr_err, "Can't open stderr");
1467
1468 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1469 NULL, TRUE,
1470 infile, outfile, errfile, pool));
1471
1472 return SVN_NO_ERROR;
1473 }
1474
1475 static svn_error_t *
subcommand_freeze(apr_getopt_t * os,void * baton,apr_pool_t * pool)1476 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1477 {
1478 struct svnadmin_opt_state *opt_state = baton;
1479 apr_array_header_t *paths;
1480 apr_array_header_t *args;
1481 int i;
1482 struct freeze_baton_t b;
1483
1484 SVN_ERR(parse_args(&args, os, -1, -1, pool));
1485
1486 if (!args->nelts)
1487 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1488 _("No program provided"));
1489
1490 if (!opt_state->file)
1491 {
1492 /* One repository on the command line. */
1493 paths = apr_array_make(pool, 1, sizeof(const char *));
1494 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1495 }
1496 else
1497 {
1498 svn_stringbuf_t *buf;
1499 const char *utf8;
1500 /* Read repository paths from the -F file. */
1501 SVN_ERR(svn_stringbuf_from_file2(&buf, opt_state->file, pool));
1502 SVN_ERR(svn_utf_cstring_to_utf8(&utf8, buf->data, pool));
1503 paths = svn_cstring_split(utf8, "\r\n", FALSE, pool);
1504 }
1505
1506 b.command = APR_ARRAY_IDX(args, 0, const char *);
1507 b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1508 for (i = 0; i < args->nelts; ++i)
1509 b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1510 b.args[args->nelts] = NULL;
1511
1512 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1513
1514 /* Make any non-zero status visible to the user. */
1515 if (b.status)
1516 exit(b.status);
1517
1518 return SVN_NO_ERROR;
1519 }
1520
1521
1522 /* This implements `svn_opt_subcommand_t'. */
1523 static svn_error_t *
subcommand_help(apr_getopt_t * os,void * baton,apr_pool_t * pool)1524 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1525 {
1526 struct svnadmin_opt_state *opt_state = baton;
1527 const char *header =
1528 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1529 "Subversion repository administration tool.\n"
1530 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1531 "Type 'svnadmin --version' to see the program version and FS modules.\n"
1532 "\n"
1533 "Available subcommands:\n");
1534
1535 const char *fs_desc_start
1536 = _("The following repository back-end (FS) modules are available:\n\n");
1537
1538 svn_stringbuf_t *version_footer;
1539
1540 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1541 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1542
1543 SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1544 opt_state ? opt_state->version : FALSE,
1545 opt_state ? opt_state->quiet : FALSE,
1546 /*###opt_state ? opt_state->verbose :*/ FALSE,
1547 version_footer->data,
1548 header, cmd_table, options_table, NULL, NULL,
1549 pool));
1550
1551 return SVN_NO_ERROR;
1552 }
1553
1554
1555 /* Set *REVNUM to the revision number of a numeric REV, or to
1556 SVN_INVALID_REVNUM if REV is unspecified. */
1557 static svn_error_t *
optrev_to_revnum(svn_revnum_t * revnum,const svn_opt_revision_t * opt_rev)1558 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1559 {
1560 if (opt_rev->kind == svn_opt_revision_number)
1561 {
1562 *revnum = opt_rev->value.number;
1563 if (! SVN_IS_VALID_REVNUM(*revnum))
1564 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1565 _("Invalid revision number (%ld) specified"),
1566 *revnum);
1567 }
1568 else if (opt_rev->kind == svn_opt_revision_unspecified)
1569 {
1570 *revnum = SVN_INVALID_REVNUM;
1571 }
1572 else
1573 {
1574 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1575 _("Non-numeric revision specified"));
1576 }
1577 return SVN_NO_ERROR;
1578 }
1579
1580 /* Read the min / max revision from the OPT_STATE, verify them and return
1581 them in *LOWER and *UPPER, respectively. */
1582 static svn_error_t *
get_load_range(svn_revnum_t * lower,svn_revnum_t * upper,struct svnadmin_opt_state * opt_state)1583 get_load_range(svn_revnum_t *lower,
1584 svn_revnum_t *upper,
1585 struct svnadmin_opt_state *opt_state)
1586 {
1587 /* Find the revision numbers at which to start and end. We only
1588 support a limited set of revision kinds: number and unspecified. */
1589 SVN_ERR(optrev_to_revnum(lower, &opt_state->start_revision));
1590 SVN_ERR(optrev_to_revnum(upper, &opt_state->end_revision));
1591
1592 /* Fill in implied revisions if necessary. */
1593 if ((*upper == SVN_INVALID_REVNUM) && (*lower != SVN_INVALID_REVNUM))
1594 {
1595 *upper = *lower;
1596 }
1597 else if ((*upper != SVN_INVALID_REVNUM) && (*lower == SVN_INVALID_REVNUM))
1598 {
1599 *lower = *upper;
1600 }
1601
1602 /* Ensure correct range ordering. */
1603 if (*lower > *upper)
1604 {
1605 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1606 _("First revision cannot be higher than second"));
1607 }
1608
1609 return SVN_NO_ERROR;
1610 }
1611
1612
1613 /* This implements `svn_opt_subcommand_t'. */
1614 static svn_error_t *
subcommand_load(apr_getopt_t * os,void * baton,apr_pool_t * pool)1615 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1616 {
1617 svn_error_t *err;
1618 struct svnadmin_opt_state *opt_state = baton;
1619 svn_repos_t *repos;
1620 svn_revnum_t lower, upper;
1621 svn_stream_t *in_stream;
1622 svn_stream_t *feedback_stream = NULL;
1623
1624 /* Expect no more arguments. */
1625 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1626
1627 /* Find the revision numbers at which to start and end. We only
1628 support a limited set of revision kinds: number and unspecified. */
1629 SVN_ERR(get_load_range(&lower, &upper, opt_state));
1630
1631 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1632
1633 /* Open the file or STDIN, depending on whether -F was specified. */
1634 if (opt_state->file)
1635 SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1636 pool, pool));
1637 else
1638 SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1639
1640 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1641 if (! opt_state->quiet)
1642 feedback_stream = recode_stream_create(stdout, pool);
1643
1644 err = svn_repos_load_fs6(repos, in_stream, lower, upper,
1645 opt_state->uuid_action, opt_state->parent_dir,
1646 opt_state->use_pre_commit_hook,
1647 opt_state->use_post_commit_hook,
1648 !opt_state->bypass_prop_validation,
1649 opt_state->ignore_dates,
1650 opt_state->normalize_props,
1651 opt_state->quiet ? NULL : repos_notify_handler,
1652 feedback_stream, check_cancel, NULL, pool);
1653
1654 if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1655 {
1656 return svn_error_quick_wrap(err,
1657 _("A property with invalid line ending "
1658 "found in dumpstream; consider using "
1659 "--normalize-props while loading."));
1660 }
1661 else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1662 {
1663 return svn_error_quick_wrap(err,
1664 _("Invalid property value found in "
1665 "dumpstream; consider repairing the "
1666 "source or using --bypass-prop-validation "
1667 "while loading."));
1668 }
1669
1670 return err;
1671 }
1672
1673 static svn_error_t *
subcommand_load_revprops(apr_getopt_t * os,void * baton,apr_pool_t * pool)1674 subcommand_load_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1675 {
1676 svn_error_t *err;
1677 struct svnadmin_opt_state *opt_state = baton;
1678 svn_repos_t *repos;
1679 svn_revnum_t lower, upper;
1680 svn_stream_t *in_stream;
1681
1682 svn_stream_t *feedback_stream = NULL;
1683
1684 /* Expect no more arguments. */
1685 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1686
1687 /* Find the revision numbers at which to start and end. We only
1688 support a limited set of revision kinds: number and unspecified. */
1689 SVN_ERR(get_load_range(&lower, &upper, opt_state));
1690
1691 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1692
1693 /* Open the file or STDIN, depending on whether -F was specified. */
1694 if (opt_state->file)
1695 SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1696 pool, pool));
1697 else
1698 SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1699
1700 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1701 if (! opt_state->quiet)
1702 feedback_stream = recode_stream_create(stdout, pool);
1703
1704 err = svn_repos_load_fs_revprops(repos, in_stream, lower, upper,
1705 !opt_state->bypass_prop_validation,
1706 opt_state->ignore_dates,
1707 opt_state->normalize_props,
1708 opt_state->quiet ? NULL
1709 : repos_notify_handler,
1710 feedback_stream, check_cancel, NULL, pool);
1711
1712 if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1713 {
1714 return svn_error_quick_wrap(err,
1715 _("A property with invalid line ending "
1716 "found in dumpstream; consider using "
1717 "--normalize-props while loading."));
1718 }
1719 else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1720 {
1721 return svn_error_quick_wrap(err,
1722 _("Invalid property value found in "
1723 "dumpstream; consider repairing the "
1724 "source or using --bypass-prop-validation "
1725 "while loading."));
1726 }
1727
1728 return err;
1729 }
1730
1731 /* This implements `svn_opt_subcommand_t'. */
1732 static svn_error_t *
subcommand_lstxns(apr_getopt_t * os,void * baton,apr_pool_t * pool)1733 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1734 {
1735 struct svnadmin_opt_state *opt_state = baton;
1736 svn_repos_t *repos;
1737 svn_fs_t *fs;
1738 apr_array_header_t *txns;
1739 apr_pool_t *iterpool;
1740 svn_revnum_t youngest, limit;
1741 int i;
1742
1743 /* Expect no more arguments. */
1744 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1745 if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1746 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1747 _("Revision range is not allowed"));
1748
1749 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1750 fs = svn_repos_fs(repos);
1751 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1752
1753 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1754 SVN_ERR(get_revnum(&limit, &opt_state->start_revision, youngest, repos,
1755 pool));
1756
1757 iterpool = svn_pool_create(pool);
1758 for (i = 0; i < txns->nelts; i++)
1759 {
1760 const char *name = APR_ARRAY_IDX(txns, i, const char *);
1761 svn_boolean_t show = TRUE;
1762
1763 svn_pool_clear(iterpool);
1764 if (limit != SVN_INVALID_REVNUM)
1765 {
1766 svn_fs_txn_t *txn;
1767 svn_revnum_t base;
1768
1769 SVN_ERR(svn_fs_open_txn(&txn, fs, name, iterpool));
1770 base = svn_fs_txn_base_revision(txn);
1771
1772 if (base > limit)
1773 show = FALSE;
1774 }
1775 if (show)
1776 SVN_ERR(svn_cmdline_printf(pool, "%s\n", name));
1777 }
1778 svn_pool_destroy(iterpool);
1779
1780 return SVN_NO_ERROR;
1781 }
1782
1783
1784 /* This implements `svn_opt_subcommand_t'. */
1785 static svn_error_t *
subcommand_recover(apr_getopt_t * os,void * baton,apr_pool_t * pool)1786 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1787 {
1788 svn_revnum_t youngest_rev;
1789 svn_repos_t *repos;
1790 svn_error_t *err;
1791 struct svnadmin_opt_state *opt_state = baton;
1792 svn_stream_t *feedback_stream = NULL;
1793
1794 /* Expect no more arguments. */
1795 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1796
1797 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
1798
1799 /* Restore default signal handlers until after we have acquired the
1800 * exclusive lock so that the user interrupt before we actually
1801 * touch the repository. */
1802 svn_cmdline__disable_cancellation_handler();
1803
1804 err = svn_repos_recover4(opt_state->repository_path, TRUE,
1805 repos_notify_handler, feedback_stream,
1806 check_cancel, NULL, pool);
1807 if (err)
1808 {
1809 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1810 return err;
1811 svn_error_clear(err);
1812 if (! opt_state->wait)
1813 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1814 _("Failed to get exclusive repository "
1815 "access; perhaps another process\n"
1816 "such as httpd, svnserve or svn "
1817 "has it open?"));
1818 SVN_ERR(svn_cmdline_printf(pool,
1819 _("Waiting on repository lock; perhaps"
1820 " another process has it open?\n")));
1821 SVN_ERR(svn_cmdline_fflush(stdout));
1822 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1823 repos_notify_handler, feedback_stream,
1824 check_cancel, NULL, pool));
1825 }
1826
1827 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1828
1829 /* Since db transactions may have been replayed, it's nice to tell
1830 people what the latest revision is. It also proves that the
1831 recovery actually worked. */
1832 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1833 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1834 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1835 youngest_rev));
1836
1837 return SVN_NO_ERROR;
1838 }
1839
1840
1841 /* This implements `svn_opt_subcommand_t'. */
1842 static svn_error_t *
list_dblogs(apr_getopt_t * os,void * baton,svn_boolean_t only_unused,apr_pool_t * pool)1843 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1844 apr_pool_t *pool)
1845 {
1846 struct svnadmin_opt_state *opt_state = baton;
1847 apr_array_header_t *logfiles;
1848 int i;
1849
1850 /* Expect no more arguments. */
1851 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1852
1853 SVN_ERR(svn_repos_db_logfiles(&logfiles,
1854 opt_state->repository_path,
1855 only_unused,
1856 pool));
1857
1858 /* Loop, printing log files. We append the log paths to the
1859 repository path, making sure to return everything to the native
1860 style before printing. */
1861 for (i = 0; i < logfiles->nelts; i++)
1862 {
1863 const char *log_path;
1864 log_path = svn_dirent_join(opt_state->repository_path,
1865 APR_ARRAY_IDX(logfiles, i, const char *),
1866 pool);
1867 log_path = svn_dirent_local_style(log_path, pool);
1868 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_path));
1869 }
1870
1871 return SVN_NO_ERROR;
1872 }
1873
1874
1875 /* This implements `svn_opt_subcommand_t'. */
1876 static svn_error_t *
subcommand_list_dblogs(apr_getopt_t * os,void * baton,apr_pool_t * pool)1877 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1878 {
1879 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1880 return SVN_NO_ERROR;
1881 }
1882
1883
1884 /* This implements `svn_opt_subcommand_t'. */
1885 static svn_error_t *
subcommand_list_unused_dblogs(apr_getopt_t * os,void * baton,apr_pool_t * pool)1886 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1887 {
1888 /* Expect no more arguments. */
1889 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1890
1891 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1892 return SVN_NO_ERROR;
1893 }
1894
1895
1896 /* This implements `svn_opt_subcommand_t'. */
1897 static svn_error_t *
subcommand_rmtxns(apr_getopt_t * os,void * baton,apr_pool_t * pool)1898 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1899 {
1900 struct svnadmin_opt_state *opt_state = baton;
1901 svn_repos_t *repos;
1902 svn_fs_t *fs;
1903 svn_fs_txn_t *txn;
1904 apr_array_header_t *args;
1905 int i;
1906 apr_pool_t *subpool = svn_pool_create(pool);
1907
1908 SVN_ERR(parse_args(&args, os, -1, -1, pool));
1909
1910 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1911 fs = svn_repos_fs(repos);
1912
1913 /* All the rest of the arguments are transaction names. */
1914 for (i = 0; i < args->nelts; i++)
1915 {
1916 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1917 svn_error_t *err;
1918
1919 svn_pool_clear(subpool);
1920
1921 /* Try to open the txn. If that succeeds, try to abort it. */
1922 err = svn_fs_open_txn(&txn, fs, txn_name, subpool);
1923 if (! err)
1924 err = svn_fs_abort_txn(txn, subpool);
1925
1926 /* If either the open or the abort of the txn fails because that
1927 transaction is dead, just try to purge the thing. Else,
1928 there was either an error worth reporting, or not error at
1929 all. */
1930 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1931 {
1932 svn_error_clear(err);
1933 err = svn_fs_purge_txn(fs, txn_name, subpool);
1934 }
1935
1936 /* If we had a real from the txn open, abort, or purge, we clear
1937 that error and just report to the user that we had an issue
1938 with this particular txn. */
1939 if (err)
1940 {
1941 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1942 svn_error_clear(err);
1943 }
1944 else if (! opt_state->quiet)
1945 {
1946 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1947 txn_name));
1948 }
1949 }
1950
1951 svn_pool_destroy(subpool);
1952
1953 return SVN_NO_ERROR;
1954 }
1955
1956
1957 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
1958 OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and
1959 OPT_STATE->use_post_revprop_change_hook to be set appropriately.
1960 If FILENAME is NULL, delete property PROP_NAME. */
1961 static svn_error_t *
set_revprop(const char * prop_name,const char * filename,struct svnadmin_opt_state * opt_state,apr_pool_t * pool)1962 set_revprop(const char *prop_name, const char *filename,
1963 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1964 {
1965 svn_repos_t *repos;
1966 svn_string_t *prop_value;
1967
1968 if (filename)
1969 {
1970 svn_stringbuf_t *file_contents;
1971
1972 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1973
1974 prop_value = svn_string_create_empty(pool);
1975 prop_value->data = file_contents->data;
1976 prop_value->len = file_contents->len;
1977
1978 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1979 NULL, FALSE, pool, pool));
1980 }
1981 else
1982 {
1983 prop_value = NULL;
1984 }
1985
1986 /* Open the filesystem */
1987 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1988
1989 if (opt_state->txn_id)
1990 {
1991 svn_fs_t *fs = svn_repos_fs(repos);
1992 svn_fs_txn_t *txn;
1993
1994 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1995 SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool));
1996 }
1997 else
1998 SVN_ERR(svn_repos_fs_change_rev_prop4(
1999 repos, opt_state->start_revision.value.number,
2000 NULL, prop_name, NULL, prop_value,
2001 opt_state->use_pre_revprop_change_hook,
2002 opt_state->use_post_revprop_change_hook,
2003 NULL, NULL, pool));
2004
2005 return SVN_NO_ERROR;
2006 }
2007
2008
2009 /* This implements `svn_opt_subcommand_t'. */
2010 static svn_error_t *
subcommand_setrevprop(apr_getopt_t * os,void * baton,apr_pool_t * pool)2011 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2012 {
2013 struct svnadmin_opt_state *opt_state = baton;
2014 apr_array_header_t *args;
2015 const char *prop_name, *filename;
2016
2017 /* Expect two more arguments: NAME FILE */
2018 SVN_ERR(parse_args(&args, os, 2, 2, pool));
2019 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2020 filename = APR_ARRAY_IDX(args, 1, const char *);
2021 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2022
2023 if (opt_state->txn_id)
2024 {
2025 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2026 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2027 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2028 _("--revision (-r) and --transaction (-t) "
2029 "are mutually exclusive"));
2030
2031 if (opt_state->use_pre_revprop_change_hook
2032 || opt_state->use_post_revprop_change_hook)
2033 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2034 _("Calling hooks is incompatible with "
2035 "--transaction (-t)"));
2036 }
2037 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2038 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2039 _("Missing revision"));
2040 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2041 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2042 _("Only one revision allowed"));
2043
2044 return set_revprop(prop_name, filename, opt_state, pool);
2045 }
2046
2047
2048 /* This implements `svn_opt_subcommand_t'. */
2049 static svn_error_t *
subcommand_setuuid(apr_getopt_t * os,void * baton,apr_pool_t * pool)2050 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2051 {
2052 struct svnadmin_opt_state *opt_state = baton;
2053 apr_array_header_t *args;
2054 svn_repos_t *repos;
2055 svn_fs_t *fs;
2056 const char *uuid = NULL;
2057
2058 /* Expect zero or one more arguments: [UUID] */
2059 SVN_ERR(parse_args(&args, os, 0, 1, pool));
2060 if (args->nelts == 1)
2061 uuid = APR_ARRAY_IDX(args, 0, const char *);
2062
2063 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2064 fs = svn_repos_fs(repos);
2065 return svn_fs_set_uuid(fs, uuid, pool);
2066 }
2067
2068
2069 /* This implements `svn_opt_subcommand_t'. */
2070 static svn_error_t *
subcommand_setlog(apr_getopt_t * os,void * baton,apr_pool_t * pool)2071 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2072 {
2073 struct svnadmin_opt_state *opt_state = baton;
2074 apr_array_header_t *args;
2075 const char *filename;
2076
2077 /* Expect one more argument: FILE */
2078 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2079 filename = APR_ARRAY_IDX(args, 0, const char *);
2080 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2081
2082 if (opt_state->start_revision.kind != svn_opt_revision_number)
2083 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2084 _("Missing revision"));
2085 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2086 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2087 _("Only one revision allowed"));
2088
2089 /* set_revprop() responds only to pre-/post-revprop-change opts. */
2090 if (!opt_state->bypass_hooks)
2091 {
2092 opt_state->use_pre_revprop_change_hook = TRUE;
2093 opt_state->use_post_revprop_change_hook = TRUE;
2094 }
2095
2096 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
2097 }
2098
2099
2100 /* This implements 'svn_opt_subcommand_t'. */
2101 static svn_error_t *
subcommand_pack(apr_getopt_t * os,void * baton,apr_pool_t * pool)2102 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2103 {
2104 struct svnadmin_opt_state *opt_state = baton;
2105 svn_repos_t *repos;
2106 svn_stream_t *feedback_stream = NULL;
2107
2108 /* Expect no more arguments. */
2109 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2110
2111 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2112
2113 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2114 if (! opt_state->quiet)
2115 feedback_stream = recode_stream_create(stdout, pool);
2116
2117 return svn_error_trace(
2118 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
2119 feedback_stream, check_cancel, NULL, pool));
2120 }
2121
2122
2123 /* This implements `svn_opt_subcommand_t'. */
2124 static svn_error_t *
subcommand_verify(apr_getopt_t * os,void * baton,apr_pool_t * pool)2125 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2126 {
2127 struct svnadmin_opt_state *opt_state = baton;
2128 svn_repos_t *repos;
2129 svn_fs_t *fs;
2130 svn_revnum_t youngest, lower, upper;
2131 svn_stream_t *feedback_stream = NULL;
2132 struct repos_verify_callback_baton verify_baton = { 0 };
2133
2134 /* Expect no more arguments. */
2135 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2136
2137 if (opt_state->txn_id
2138 && (opt_state->start_revision.kind != svn_opt_revision_unspecified
2139 || opt_state->end_revision.kind != svn_opt_revision_unspecified))
2140 {
2141 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2142 _("--revision (-r) and --transaction (-t) "
2143 "are mutually exclusive"));
2144 }
2145
2146 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2147 fs = svn_repos_fs(repos);
2148 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2149
2150 /* Usage 2. */
2151 if (opt_state->txn_id)
2152 {
2153 svn_fs_txn_t *txn;
2154 svn_fs_root_t *root;
2155
2156 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
2157 SVN_ERR(svn_fs_txn_root(&root, txn, pool));
2158 SVN_ERR(svn_fs_verify_root(root, pool));
2159 return SVN_NO_ERROR;
2160 }
2161 else
2162 /* Usage 1. */
2163 ;
2164
2165 /* Find the revision numbers at which to start and end. */
2166 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
2167 youngest, repos, pool));
2168 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
2169 youngest, repos, pool));
2170
2171 if (upper == SVN_INVALID_REVNUM)
2172 {
2173 upper = lower;
2174 }
2175
2176 if (!opt_state->quiet)
2177 feedback_stream = recode_stream_create(stdout, pool);
2178
2179 verify_baton.keep_going = opt_state->keep_going;
2180 verify_baton.error_summary =
2181 apr_array_make(pool, 0, sizeof(struct verification_error *));
2182 verify_baton.result_pool = pool;
2183
2184 SVN_ERR(svn_repos_verify_fs3(repos, lower, upper,
2185 opt_state->check_normalization,
2186 opt_state->metadata_only,
2187 !opt_state->quiet
2188 ? repos_notify_handler : NULL,
2189 feedback_stream,
2190 repos_verify_callback, &verify_baton,
2191 check_cancel, NULL, pool));
2192
2193 /* Show the --keep-going error summary. */
2194 if (!opt_state->quiet
2195 && opt_state->keep_going
2196 && verify_baton.error_summary->nelts > 0)
2197 {
2198 int rev_maxlength;
2199 svn_revnum_t end_revnum;
2200 apr_pool_t *iterpool;
2201 int i;
2202
2203 svn_error_clear(
2204 svn_stream_puts(feedback_stream,
2205 _("\n-----Summary of corrupt revisions-----\n")));
2206
2207 /* The standard column width for the revision number is 6 characters.
2208 If the revision number can potentially be larger (i.e. if end_revnum
2209 is larger than 1000000), we increase the column width as needed. */
2210 rev_maxlength = 6;
2211 end_revnum = APR_ARRAY_IDX(verify_baton.error_summary,
2212 verify_baton.error_summary->nelts - 1,
2213 struct verification_error *)->rev;
2214 while (end_revnum >= 1000000)
2215 {
2216 rev_maxlength++;
2217 end_revnum = end_revnum / 10;
2218 }
2219
2220 iterpool = svn_pool_create(pool);
2221 for (i = 0; i < verify_baton.error_summary->nelts; i++)
2222 {
2223 struct verification_error *verr;
2224 svn_error_t *err;
2225 const char *rev_str;
2226
2227 svn_pool_clear(iterpool);
2228
2229 verr = APR_ARRAY_IDX(verify_baton.error_summary, i,
2230 struct verification_error *);
2231
2232 if (verr->rev != SVN_INVALID_REVNUM)
2233 {
2234 rev_str = apr_psprintf(iterpool, "r%ld", verr->rev);
2235 rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str);
2236 for (err = svn_error_purge_tracing(verr->err);
2237 err != SVN_NO_ERROR; err = err->child)
2238 {
2239 char buf[512];
2240 const char *message;
2241
2242 message = svn_err_best_message(err, buf, sizeof(buf));
2243 svn_error_clear(svn_stream_printf(feedback_stream, iterpool,
2244 "%s: E%06d: %s\n",
2245 rev_str, err->apr_err,
2246 message));
2247 }
2248 }
2249 }
2250
2251 svn_pool_destroy(iterpool);
2252 }
2253
2254 if (verify_baton.error_summary->nelts > 0)
2255 {
2256 return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL,
2257 _("Failed to verify repository '%s'"),
2258 svn_dirent_local_style(
2259 opt_state->repository_path, pool));
2260 }
2261
2262 return SVN_NO_ERROR;
2263 }
2264
2265 /* This implements `svn_opt_subcommand_t'. */
2266 svn_error_t *
subcommand_hotcopy(apr_getopt_t * os,void * baton,apr_pool_t * pool)2267 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2268 {
2269 struct svnadmin_opt_state *opt_state = baton;
2270 svn_stream_t *feedback_stream = NULL;
2271 apr_array_header_t *targets;
2272 const char *new_repos_path;
2273
2274 /* Expect one more argument: NEW_REPOS_PATH */
2275 SVN_ERR(parse_args(&targets, os, 1, 1, pool));
2276 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
2277 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
2278
2279 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2280 if (! opt_state->quiet)
2281 feedback_stream = recode_stream_create(stdout, pool);
2282
2283 return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path,
2284 opt_state->clean_logs, opt_state->incremental,
2285 !opt_state->quiet ? repos_notify_handler : NULL,
2286 feedback_stream, check_cancel, NULL, pool);
2287 }
2288
2289 svn_error_t *
subcommand_info(apr_getopt_t * os,void * baton,apr_pool_t * pool)2290 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2291 {
2292 struct svnadmin_opt_state *opt_state = baton;
2293 svn_repos_t *repos;
2294 svn_fs_t *fs;
2295 int fs_format;
2296 const char *uuid;
2297 svn_revnum_t head_rev;
2298
2299 /* Expect no more arguments. */
2300 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2301
2302 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2303 fs = svn_repos_fs(repos);
2304 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
2305 svn_dirent_local_style(svn_repos_path(repos, pool),
2306 pool)));
2307
2308 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2309 SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid));
2310
2311 SVN_ERR(svn_fs_youngest_rev(&head_rev, fs, pool));
2312 SVN_ERR(svn_cmdline_printf(pool, _("Revisions: %ld\n"), head_rev));
2313 {
2314 int repos_format, minor;
2315 svn_version_t *repos_version, *fs_version;
2316 SVN_ERR(svn_repos_info_format(&repos_format, &repos_version,
2317 repos, pool, pool));
2318 SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"),
2319 repos_format));
2320
2321 SVN_ERR(svn_fs_info_format(&fs_format, &fs_version,
2322 fs, pool, pool));
2323 /* fs_format will be printed later. */
2324
2325 SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR);
2326 SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR);
2327 SVN_ERR_ASSERT(repos_version->patch == 0);
2328 SVN_ERR_ASSERT(fs_version->patch == 0);
2329
2330 minor = (repos_version->minor > fs_version->minor)
2331 ? repos_version->minor : fs_version->minor;
2332 SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"),
2333 SVN_VER_MAJOR, minor));
2334 }
2335
2336 {
2337 apr_hash_t *capabilities_set;
2338 apr_array_header_t *capabilities;
2339 int i;
2340
2341 SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool));
2342 capabilities = svn_sort__hash(capabilities_set,
2343 svn_sort_compare_items_lexically,
2344 pool);
2345
2346 for (i = 0; i < capabilities->nelts; i++)
2347 {
2348 svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i,
2349 svn_sort__item_t);
2350 const char *capability = item->key;
2351 SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"),
2352 capability));
2353 }
2354 }
2355
2356 {
2357 const svn_fs_info_placeholder_t *info;
2358
2359 SVN_ERR(svn_fs_info(&info, fs, pool, pool));
2360 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"),
2361 info->fs_type));
2362 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"),
2363 fs_format));
2364 if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS))
2365 {
2366 const svn_fs_fsfs_info_t *fsfs_info = (const void *)info;
2367
2368 if (fsfs_info->shard_size)
2369 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n")));
2370 else
2371 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n")));
2372
2373 if (fsfs_info->shard_size)
2374 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"),
2375 fsfs_info->shard_size));
2376
2377 /* Print packing statistics, if enabled on the FS. */
2378 if (fsfs_info->shard_size)
2379 {
2380 const int shard_size = fsfs_info->shard_size;
2381 const long shards_packed = fsfs_info->min_unpacked_rev / shard_size;
2382 const long shards_full = (head_rev + 1) / shard_size;
2383 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"),
2384 shards_packed, shards_full));
2385 }
2386
2387 if (fsfs_info->log_addressing)
2388 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n")));
2389 else
2390 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n")));
2391 }
2392 else if (!strcmp(info->fs_type, SVN_FS_TYPE_FSX))
2393 {
2394 const svn_fs_fsx_info_t *fsx_info = (const void *)info;
2395
2396 const int shard_size = fsx_info->shard_size;
2397 const long shards_packed = fsx_info->min_unpacked_rev / shard_size;
2398 long shards_full = (head_rev + 1) / shard_size;
2399
2400 SVN_ERR(svn_cmdline_printf(pool, _("FSX Shard Size: %d\n"),
2401 shard_size));
2402 SVN_ERR(svn_cmdline_printf(pool, _("FSX Shards Packed: %ld/%ld\n"),
2403 shards_packed, shards_full));
2404 }
2405 }
2406
2407 {
2408 apr_array_header_t *files;
2409 int i;
2410
2411 SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
2412 for (i = 0; i < files->nelts; i++)
2413 SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"),
2414 svn_dirent_local_style(
2415 APR_ARRAY_IDX(files, i, const char *),
2416 pool)));
2417 }
2418
2419 /* 'svn info' prints an extra newline here, to support multiple targets.
2420 We'll do the same. */
2421 SVN_ERR(svn_cmdline_printf(pool, "\n"));
2422
2423 return SVN_NO_ERROR;
2424 }
2425
2426 /* This implements `svn_opt_subcommand_t'. */
2427 static svn_error_t *
subcommand_lock(apr_getopt_t * os,void * baton,apr_pool_t * pool)2428 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2429 {
2430 struct svnadmin_opt_state *opt_state = baton;
2431 svn_repos_t *repos;
2432 svn_fs_t *fs;
2433 svn_fs_access_t *access;
2434 apr_array_header_t *args;
2435 const char *username;
2436 const char *lock_path;
2437 const char *comment_file_name;
2438 svn_stringbuf_t *file_contents;
2439 svn_lock_t *lock;
2440 const char *lock_token = NULL;
2441
2442 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
2443 SVN_ERR(parse_args(&args, os, 3, 4, pool));
2444 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2445 username = APR_ARRAY_IDX(args, 1, const char *);
2446 comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
2447
2448 /* Expect one more optional argument: TOKEN */
2449 if (args->nelts == 4)
2450 lock_token = APR_ARRAY_IDX(args, 3, const char *);
2451
2452 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
2453
2454 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2455 fs = svn_repos_fs(repos);
2456
2457 /* Create an access context describing the user. */
2458 SVN_ERR(svn_fs_create_access(&access, username, pool));
2459
2460 /* Attach the access context to the filesystem. */
2461 SVN_ERR(svn_fs_set_access(fs, access));
2462
2463 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
2464
2465 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2466
2467 if (opt_state->bypass_hooks)
2468 SVN_ERR(svn_fs_lock(&lock, fs, lock_path,
2469 lock_token,
2470 file_contents->data, /* comment */
2471 0, /* is_dav_comment */
2472 0, /* no expiration time. */
2473 SVN_INVALID_REVNUM,
2474 FALSE, pool));
2475 else
2476 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path,
2477 lock_token,
2478 file_contents->data, /* comment */
2479 0, /* is_dav_comment */
2480 0, /* no expiration time. */
2481 SVN_INVALID_REVNUM,
2482 FALSE, pool));
2483
2484 if (! opt_state->quiet)
2485 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
2486 lock_path, username));
2487
2488 return SVN_NO_ERROR;
2489 }
2490
2491 static svn_error_t *
subcommand_lslocks(apr_getopt_t * os,void * baton,apr_pool_t * pool)2492 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2493 {
2494 struct svnadmin_opt_state *opt_state = baton;
2495 apr_array_header_t *targets;
2496 svn_repos_t *repos;
2497 const char *fs_path;
2498 apr_hash_t *locks;
2499 apr_hash_index_t *hi;
2500 apr_pool_t *iterpool = svn_pool_create(pool);
2501
2502 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
2503 apr_array_make(pool, 0,
2504 sizeof(const char *)),
2505 pool));
2506 if (targets->nelts > 1)
2507 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2508 _("Too many arguments given"));
2509 if (targets->nelts)
2510 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
2511 else
2512 fs_path = "/";
2513 SVN_ERR(target_arg_to_fspath(&fs_path, fs_path, pool, pool));
2514
2515 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2516
2517 /* Fetch all locks on or below the root directory. */
2518 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
2519 NULL, NULL, pool));
2520
2521 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2522 {
2523 const char *cr_date, *exp_date = "";
2524 const char *path = apr_hash_this_key(hi);
2525 svn_lock_t *lock = apr_hash_this_val(hi);
2526 int comment_lines = 0;
2527
2528 svn_pool_clear(iterpool);
2529
2530 SVN_ERR(check_cancel(NULL));
2531
2532 cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool);
2533
2534 if (lock->expiration_date)
2535 exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool);
2536
2537 if (lock->comment)
2538 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2539
2540 SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path));
2541 SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token));
2542 SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner));
2543 SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date));
2544 SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date));
2545 SVN_ERR(svn_cmdline_printf(iterpool,
2546 Q_("Comment (%i line):\n%s\n\n",
2547 "Comment (%i lines):\n%s\n\n",
2548 comment_lines),
2549 comment_lines,
2550 lock->comment ? lock->comment : ""));
2551 }
2552
2553 svn_pool_destroy(iterpool);
2554
2555 return SVN_NO_ERROR;
2556 }
2557
2558
2559
2560 static svn_error_t *
subcommand_rmlocks(apr_getopt_t * os,void * baton,apr_pool_t * pool)2561 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2562 {
2563 struct svnadmin_opt_state *opt_state = baton;
2564 svn_repos_t *repos;
2565 svn_fs_t *fs;
2566 svn_fs_access_t *access;
2567 svn_error_t *err;
2568 apr_array_header_t *args;
2569 int i;
2570 const char *username;
2571 apr_pool_t *subpool = svn_pool_create(pool);
2572
2573 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2574 fs = svn_repos_fs(repos);
2575
2576 /* svn_fs_unlock() demands that some username be associated with the
2577 filesystem, so just use the UID of the person running 'svnadmin'.*/
2578 username = svn_user_get_name(pool);
2579 if (! username)
2580 username = "administrator";
2581
2582 /* Create an access context describing the current user. */
2583 SVN_ERR(svn_fs_create_access(&access, username, pool));
2584
2585 /* Attach the access context to the filesystem. */
2586 SVN_ERR(svn_fs_set_access(fs, access));
2587
2588 /* Parse out any options. */
2589 SVN_ERR(parse_args(&args, os, -1, -1, pool));
2590
2591 /* Our usage requires at least one FS path. */
2592 if (args->nelts == 0)
2593 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2594 _("No paths to unlock provided"));
2595
2596 /* All the rest of the arguments are paths from which to remove locks. */
2597 for (i = 0; i < args->nelts; i++)
2598 {
2599 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
2600 svn_lock_t *lock;
2601
2602 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, subpool, subpool));
2603
2604 /* Fetch the path's svn_lock_t. */
2605 err = svn_fs_get_lock(&lock, fs, lock_path, subpool);
2606 if (err)
2607 goto move_on;
2608 if (! lock)
2609 {
2610 if (! opt_state->quiet)
2611 SVN_ERR(svn_cmdline_printf(subpool,
2612 _("Path '%s' isn't locked.\n"),
2613 lock_path));
2614 continue;
2615 }
2616 lock = NULL; /* Don't access LOCK after this point. */
2617
2618 /* Now forcibly destroy the lock. */
2619 err = svn_fs_unlock(fs, lock_path,
2620 NULL, 1 /* force */, subpool);
2621 if (err)
2622 goto move_on;
2623
2624 if (! opt_state->quiet)
2625 SVN_ERR(svn_cmdline_printf(subpool,
2626 _("Removed lock on '%s'.\n"),
2627 lock_path));
2628
2629 move_on:
2630 if (err)
2631 {
2632 /* Print the error, but move on to the next lock. */
2633 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2634 svn_error_clear(err);
2635 }
2636
2637 svn_pool_clear(subpool);
2638 }
2639
2640 svn_pool_destroy(subpool);
2641 return SVN_NO_ERROR;
2642 }
2643
2644
2645 /* This implements `svn_opt_subcommand_t'. */
2646 static svn_error_t *
subcommand_unlock(apr_getopt_t * os,void * baton,apr_pool_t * pool)2647 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2648 {
2649 struct svnadmin_opt_state *opt_state = baton;
2650 svn_repos_t *repos;
2651 svn_fs_t *fs;
2652 svn_fs_access_t *access;
2653 apr_array_header_t *args;
2654 const char *username;
2655 const char *lock_path;
2656 const char *lock_token = NULL;
2657
2658 /* Expect three more arguments: PATH USERNAME TOKEN */
2659 SVN_ERR(parse_args(&args, os, 3, 3, pool));
2660 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2661 username = APR_ARRAY_IDX(args, 1, const char *);
2662 lock_token = APR_ARRAY_IDX(args, 2, const char *);
2663
2664 /* Open the repos/FS, and associate an access context containing
2665 USERNAME. */
2666 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2667 fs = svn_repos_fs(repos);
2668 SVN_ERR(svn_fs_create_access(&access, username, pool));
2669 SVN_ERR(svn_fs_set_access(fs, access));
2670
2671 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2672 if (opt_state->bypass_hooks)
2673 SVN_ERR(svn_fs_unlock(fs, lock_path, lock_token,
2674 FALSE, pool));
2675 else
2676 SVN_ERR(svn_repos_fs_unlock(repos, lock_path, lock_token,
2677 FALSE, pool));
2678
2679 if (! opt_state->quiet)
2680 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
2681 lock_path, username));
2682
2683 return SVN_NO_ERROR;
2684 }
2685
2686
2687 /* This implements `svn_opt_subcommand_t'. */
2688 static svn_error_t *
subcommand_upgrade(apr_getopt_t * os,void * baton,apr_pool_t * pool)2689 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2690 {
2691 svn_error_t *err;
2692 struct svnadmin_opt_state *opt_state = baton;
2693 svn_stream_t *feedback_stream = NULL;
2694
2695 /* Expect no more arguments. */
2696 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2697
2698 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
2699
2700 /* Restore default signal handlers. */
2701 svn_cmdline__disable_cancellation_handler();
2702
2703 err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
2704 repos_notify_handler, feedback_stream, pool);
2705 if (err)
2706 {
2707 if (APR_STATUS_IS_EAGAIN(err->apr_err))
2708 {
2709 svn_error_clear(err);
2710 err = SVN_NO_ERROR;
2711 if (! opt_state->wait)
2712 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
2713 _("Failed to get exclusive repository "
2714 "access; perhaps another process\n"
2715 "such as httpd, svnserve or svn "
2716 "has it open?"));
2717 SVN_ERR(svn_cmdline_printf(pool,
2718 _("Waiting on repository lock; perhaps"
2719 " another process has it open?\n")));
2720 SVN_ERR(svn_cmdline_fflush(stdout));
2721 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
2722 repos_notify_handler, feedback_stream,
2723 pool));
2724 }
2725 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
2726 {
2727 return svn_error_quick_wrap(err,
2728 _("Upgrade of this repository's underlying versioned "
2729 "filesystem is not supported; consider "
2730 "dumping and loading the data elsewhere"));
2731 }
2732 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
2733 {
2734 return svn_error_quick_wrap(err,
2735 _("Upgrade of this repository is not supported; consider "
2736 "dumping and loading the data elsewhere"));
2737 }
2738 }
2739 SVN_ERR(err);
2740
2741 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
2742 return SVN_NO_ERROR;
2743 }
2744
2745
2746 /* This implements `svn_opt_subcommand_t'. */
2747 static svn_error_t *
subcommand_delrevprop(apr_getopt_t * os,void * baton,apr_pool_t * pool)2748 subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2749 {
2750 struct svnadmin_opt_state *opt_state = baton;
2751 apr_array_header_t *args;
2752 const char *prop_name;
2753
2754 /* Expect one more argument: NAME */
2755 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2756 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2757
2758 if (opt_state->txn_id)
2759 {
2760 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2761 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2762 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2763 _("--revision (-r) and --transaction (-t) "
2764 "are mutually exclusive"));
2765
2766 if (opt_state->use_pre_revprop_change_hook
2767 || opt_state->use_post_revprop_change_hook)
2768 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2769 _("Calling hooks is incompatible with "
2770 "--transaction (-t)"));
2771 }
2772 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2773 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2774 _("Missing revision"));
2775 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2776 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2777 _("Only one revision allowed"));
2778
2779 return set_revprop(prop_name, NULL, opt_state, pool);
2780 }
2781
2782
2783
2784 /** Main. **/
2785
2786 /*
2787 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2788 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2789 * return SVN_NO_ERROR.
2790 */
2791 static svn_error_t *
sub_main(int * exit_code,int argc,const char * argv[],apr_pool_t * pool)2792 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2793 {
2794 svn_error_t *err;
2795 apr_status_t apr_err;
2796
2797 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2798 struct svnadmin_opt_state opt_state = { 0 };
2799 apr_getopt_t *os;
2800 int opt_id;
2801 apr_array_header_t *received_opts;
2802 int i;
2803 svn_boolean_t dash_F_arg = FALSE;
2804
2805 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2806
2807 /* Check library versions */
2808 SVN_ERR(check_lib_versions());
2809
2810 /* Initialize the FS library. */
2811 SVN_ERR(svn_fs_initialize(pool));
2812
2813 if (argc <= 1)
2814 {
2815 SVN_ERR(subcommand_help(NULL, NULL, pool));
2816 *exit_code = EXIT_FAILURE;
2817 return SVN_NO_ERROR;
2818 }
2819
2820 /* Initialize opt_state. */
2821 opt_state.start_revision.kind = svn_opt_revision_unspecified;
2822 opt_state.end_revision.kind = svn_opt_revision_unspecified;
2823 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2824
2825 /* Parse options. */
2826 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2827
2828 os->interleave = 1;
2829
2830 while (1)
2831 {
2832 const char *opt_arg;
2833 const char *utf8_opt_arg;
2834
2835 /* Parse the next option. */
2836 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2837 if (APR_STATUS_IS_EOF(apr_err))
2838 break;
2839 else if (apr_err)
2840 {
2841 SVN_ERR(subcommand_help(NULL, NULL, pool));
2842 *exit_code = EXIT_FAILURE;
2843 return SVN_NO_ERROR;
2844 }
2845
2846 /* Stash the option code in an array before parsing it. */
2847 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2848
2849 switch (opt_id) {
2850 case 'r':
2851 {
2852 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2853 {
2854 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2855 _("Multiple revision arguments encountered; "
2856 "try '-r N:M' instead of '-r N -r M'"));
2857 }
2858 if (svn_opt_parse_revision(&(opt_state.start_revision),
2859 &(opt_state.end_revision),
2860 opt_arg, pool) != 0)
2861 {
2862 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2863
2864 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2865 _("Syntax error in revision argument '%s'"),
2866 utf8_opt_arg);
2867 }
2868 }
2869 break;
2870 case 't':
2871 opt_state.txn_id = opt_arg;
2872 break;
2873
2874 case 'q':
2875 opt_state.quiet = TRUE;
2876 break;
2877 case 'h':
2878 case '?':
2879 opt_state.help = TRUE;
2880 break;
2881 case 'M':
2882 {
2883 apr_uint64_t sz_val;
2884 SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
2885
2886 opt_state.memory_cache_size = 0x100000 * sz_val;
2887 }
2888 break;
2889 case 'F':
2890 SVN_ERR(svn_utf_cstring_to_utf8(&(opt_state.file), opt_arg, pool));
2891 dash_F_arg = TRUE;
2892 break;
2893 case svnadmin__version:
2894 opt_state.version = TRUE;
2895 break;
2896 case svnadmin__incremental:
2897 opt_state.incremental = TRUE;
2898 break;
2899 case svnadmin__deltas:
2900 opt_state.use_deltas = TRUE;
2901 break;
2902 case svnadmin__ignore_uuid:
2903 opt_state.uuid_action = svn_repos_load_uuid_ignore;
2904 break;
2905 case svnadmin__force_uuid:
2906 opt_state.uuid_action = svn_repos_load_uuid_force;
2907 break;
2908 case svnadmin__pre_1_4_compatible:
2909 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2910 opt_state.compatible_version->major = 1;
2911 opt_state.compatible_version->minor = 3;
2912 break;
2913 case svnadmin__pre_1_5_compatible:
2914 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2915 opt_state.compatible_version->major = 1;
2916 opt_state.compatible_version->minor = 4;
2917 break;
2918 case svnadmin__pre_1_6_compatible:
2919 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2920 opt_state.compatible_version->major = 1;
2921 opt_state.compatible_version->minor = 5;
2922 break;
2923 case svnadmin__compatible_version:
2924 {
2925 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2926 SVN_VER_PATCH, NULL };
2927 svn_version_t *compatible_version;
2928
2929 /* Parse the version string which carries our target
2930 compatibility. */
2931 SVN_ERR(svn_version__parse_version_string(&compatible_version,
2932 opt_arg, pool));
2933
2934 /* We can't create repository with a version older than 1.0.0. */
2935 if (! svn_version__at_least(compatible_version, 1, 0, 0))
2936 {
2937 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2938 _("Cannot create pre-1.0-compatible "
2939 "repositories"));
2940 }
2941
2942 /* We can't create repository with a version newer than what
2943 the running version of Subversion supports. */
2944 if (! svn_version__at_least(&latest,
2945 compatible_version->major,
2946 compatible_version->minor,
2947 compatible_version->patch))
2948 {
2949 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2950 _("Cannot guarantee compatibility "
2951 "beyond the current running version "
2952 "(%s)"),
2953 SVN_VER_NUM);
2954 }
2955
2956 opt_state.compatible_version = compatible_version;
2957 }
2958 break;
2959 case svnadmin__keep_going:
2960 opt_state.keep_going = TRUE;
2961 break;
2962 case svnadmin__check_normalization:
2963 opt_state.check_normalization = TRUE;
2964 break;
2965 case svnadmin__metadata_only:
2966 opt_state.metadata_only = TRUE;
2967 break;
2968 case svnadmin__fs_type:
2969 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2970 break;
2971 case svnadmin__parent_dir:
2972 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2973 pool));
2974 opt_state.parent_dir
2975 = svn_dirent_internal_style(opt_state.parent_dir, pool);
2976 break;
2977 case svnadmin__use_pre_commit_hook:
2978 opt_state.use_pre_commit_hook = TRUE;
2979 break;
2980 case svnadmin__use_post_commit_hook:
2981 opt_state.use_post_commit_hook = TRUE;
2982 break;
2983 case svnadmin__use_pre_revprop_change_hook:
2984 opt_state.use_pre_revprop_change_hook = TRUE;
2985 break;
2986 case svnadmin__use_post_revprop_change_hook:
2987 opt_state.use_post_revprop_change_hook = TRUE;
2988 break;
2989 case svnadmin__bdb_txn_nosync:
2990 opt_state.bdb_txn_nosync = TRUE;
2991 break;
2992 case svnadmin__bdb_log_keep:
2993 opt_state.bdb_log_keep = TRUE;
2994 break;
2995 case svnadmin__bypass_hooks:
2996 opt_state.bypass_hooks = TRUE;
2997 break;
2998 case svnadmin__bypass_prop_validation:
2999 opt_state.bypass_prop_validation = TRUE;
3000 break;
3001 case svnadmin__ignore_dates:
3002 opt_state.ignore_dates = TRUE;
3003 break;
3004 case svnadmin__clean_logs:
3005 opt_state.clean_logs = TRUE;
3006 break;
3007 case svnadmin__config_dir:
3008 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3009 opt_state.config_dir =
3010 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
3011 break;
3012 case svnadmin__wait:
3013 opt_state.wait = TRUE;
3014 break;
3015 case svnadmin__no_flush_to_disk:
3016 opt_state.no_flush_to_disk = TRUE;
3017 break;
3018 case svnadmin__normalize_props:
3019 opt_state.normalize_props = TRUE;
3020 break;
3021 case svnadmin__exclude:
3022 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3023
3024 if (! opt_state.exclude)
3025 opt_state.exclude = apr_array_make(pool, 1, sizeof(const char *));
3026 APR_ARRAY_PUSH(opt_state.exclude, const char *) = utf8_opt_arg;
3027 break;
3028 case svnadmin__include:
3029 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3030
3031 if (! opt_state.include)
3032 opt_state.include = apr_array_make(pool, 1, sizeof(const char *));
3033 APR_ARRAY_PUSH(opt_state.include, const char *) = utf8_opt_arg;
3034 break;
3035 case svnadmin__glob:
3036 opt_state.glob = TRUE;
3037 break;
3038 default:
3039 {
3040 SVN_ERR(subcommand_help(NULL, NULL, pool));
3041 *exit_code = EXIT_FAILURE;
3042 return SVN_NO_ERROR;
3043 }
3044 } /* close `switch' */
3045 } /* close `while' */
3046
3047 /* If the user asked for help, then the rest of the arguments are
3048 the names of subcommands to get help on (if any), or else they're
3049 just typos/mistakes. Whatever the case, the subcommand to
3050 actually run is subcommand_help(). */
3051 if (opt_state.help)
3052 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
3053
3054 /* If we're not running the `help' subcommand, then look for a
3055 subcommand in the first argument. */
3056 if (subcommand == NULL)
3057 {
3058 if (os->ind >= os->argc)
3059 {
3060 if (opt_state.version)
3061 {
3062 /* Use the "help" subcommand to handle the "--version" option. */
3063 static const svn_opt_subcommand_desc2_t pseudo_cmd =
3064 { "--version", subcommand_help, {0}, "",
3065 {svnadmin__version, /* must accept its own option */
3066 'q', /* --quiet */
3067 } };
3068
3069 subcommand = &pseudo_cmd;
3070 }
3071 else
3072 {
3073 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
3074 _("subcommand argument required\n")));
3075 SVN_ERR(subcommand_help(NULL, NULL, pool));
3076 *exit_code = EXIT_FAILURE;
3077 return SVN_NO_ERROR;
3078 }
3079 }
3080 else
3081 {
3082 const char *first_arg;
3083
3084 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
3085 pool));
3086 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
3087 if (subcommand == NULL)
3088 {
3089 svn_error_clear(
3090 svn_cmdline_fprintf(stderr, pool,
3091 _("Unknown subcommand: '%s'\n"),
3092 first_arg));
3093 SVN_ERR(subcommand_help(NULL, NULL, pool));
3094 *exit_code = EXIT_FAILURE;
3095 return SVN_NO_ERROR;
3096 }
3097 }
3098 }
3099
3100 /* Every subcommand except `help' and `freeze' with '-F' require a
3101 second argument -- the repository path. Parse it out here and
3102 store it in opt_state. */
3103 if (!(subcommand->cmd_func == subcommand_help
3104 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
3105 {
3106 const char *repos_path = NULL;
3107
3108 if (os->ind >= os->argc)
3109 {
3110 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3111 _("Repository argument required"));
3112 }
3113
3114 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
3115
3116 if (svn_path_is_url(repos_path))
3117 {
3118 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3119 _("'%s' is a URL when it should be a "
3120 "local path"), repos_path);
3121 }
3122
3123 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
3124 }
3125
3126 /* Check that the subcommand wasn't passed any inappropriate options. */
3127 for (i = 0; i < received_opts->nelts; i++)
3128 {
3129 opt_id = APR_ARRAY_IDX(received_opts, i, int);
3130
3131 /* All commands implicitly accept --help, so just skip over this
3132 when we see it. Note that we don't want to include this option
3133 in their "accepted options" list because it would be awfully
3134 redundant to display it in every commands' help text. */
3135 if (opt_id == 'h' || opt_id == '?')
3136 continue;
3137
3138 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
3139 {
3140 const char *optstr;
3141 const apr_getopt_option_t *badopt =
3142 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
3143 pool);
3144 svn_opt_format_option(&optstr, badopt, FALSE, pool);
3145 if (subcommand->name[0] == '-')
3146 SVN_ERR(subcommand_help(NULL, NULL, pool));
3147 else
3148 svn_error_clear(svn_cmdline_fprintf(stderr, pool
3149 , _("Subcommand '%s' doesn't accept option '%s'\n"
3150 "Type 'svnadmin help %s' for usage.\n"),
3151 subcommand->name, optstr, subcommand->name));
3152 *exit_code = EXIT_FAILURE;
3153 return SVN_NO_ERROR;
3154 }
3155 }
3156
3157 check_cancel = svn_cmdline__setup_cancellation_handler();
3158
3159 /* Configure FSFS caches for maximum efficiency with svnadmin.
3160 * Also, apply the respective command line parameters, if given. */
3161 {
3162 svn_cache_config_t settings = *svn_cache_config_get();
3163
3164 settings.cache_size = opt_state.memory_cache_size;
3165 settings.single_threaded = TRUE;
3166
3167 svn_cache_config_set(&settings);
3168 }
3169
3170 /* Run the subcommand. */
3171 err = (*subcommand->cmd_func)(os, &opt_state, pool);
3172 if (err)
3173 {
3174 /* For argument-related problems, suggest using the 'help'
3175 subcommand. */
3176 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
3177 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
3178 {
3179 err = svn_error_quick_wrap(err,
3180 _("Try 'svnadmin help' for more info"));
3181 }
3182 return err;
3183 }
3184
3185 return SVN_NO_ERROR;
3186 }
3187
3188 int
main(int argc,const char * argv[])3189 main(int argc, const char *argv[])
3190 {
3191 apr_pool_t *pool;
3192 int exit_code = EXIT_SUCCESS;
3193 svn_error_t *err;
3194
3195 /* Initialize the app. */
3196 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
3197 return EXIT_FAILURE;
3198
3199 /* Create our top-level pool. Use a separate mutexless allocator,
3200 * given this application is single threaded.
3201 */
3202 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
3203
3204 err = sub_main(&exit_code, argc, argv, pool);
3205
3206 /* Flush stdout and report if it fails. It would be flushed on exit anyway
3207 but this makes sure that output is not silently lost if it fails. */
3208 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
3209
3210 if (err)
3211 {
3212 exit_code = EXIT_FAILURE;
3213 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ");
3214 }
3215
3216 svn_pool_destroy(pool);
3217
3218 svn_cmdline__cancellation_exit();
3219
3220 return exit_code;
3221 }
3222