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