1 /*
2 * opt.c : option and argument parsing for Subversion command lines
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
26 #define APR_WANT_STRFUNC
27 #include <apr_want.h>
28
29 #include <stdio.h>
30 #include <string.h>
31 #include <assert.h>
32 #include <apr_pools.h>
33 #include <apr_general.h>
34 #include <apr_lib.h>
35 #include <apr_file_info.h>
36
37 #include "svn_hash.h"
38 #include "svn_cmdline.h"
39 #include "svn_version.h"
40 #include "svn_types.h"
41 #include "svn_opt.h"
42 #include "svn_error.h"
43 #include "svn_dirent_uri.h"
44 #include "svn_path.h"
45 #include "svn_utf.h"
46 #include "svn_time.h"
47 #include "svn_props.h"
48 #include "svn_ctype.h"
49
50 #include "private/svn_opt_private.h"
51
52 #include "opt.h"
53 #include "svn_private_config.h"
54
55
56 /*** Code. ***/
57
58 const svn_opt_subcommand_desc3_t *
svn_opt_get_canonical_subcommand3(const svn_opt_subcommand_desc3_t * table,const char * cmd_name)59 svn_opt_get_canonical_subcommand3(const svn_opt_subcommand_desc3_t *table,
60 const char *cmd_name)
61 {
62 int i = 0;
63
64 if (cmd_name == NULL)
65 return NULL;
66
67 while (table[i].name) {
68 int j;
69 if (strcmp(cmd_name, table[i].name) == 0)
70 return table + i;
71 for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
72 if (strcmp(cmd_name, table[i].aliases[j]) == 0)
73 return table + i;
74
75 i++;
76 }
77
78 /* If we get here, there was no matching subcommand name or alias. */
79 return NULL;
80 }
81
82 const apr_getopt_option_t *
svn_opt_get_option_from_code3(int code,const apr_getopt_option_t * option_table,const svn_opt_subcommand_desc3_t * command,apr_pool_t * pool)83 svn_opt_get_option_from_code3(int code,
84 const apr_getopt_option_t *option_table,
85 const svn_opt_subcommand_desc3_t *command,
86 apr_pool_t *pool)
87 {
88 apr_size_t i;
89
90 for (i = 0; option_table[i].optch; i++)
91 if (option_table[i].optch == code)
92 {
93 if (command)
94 {
95 int j;
96
97 for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
98 command->desc_overrides[j].optch); j++)
99 if (command->desc_overrides[j].optch == code)
100 {
101 apr_getopt_option_t *tmpopt =
102 apr_palloc(pool, sizeof(*tmpopt));
103 *tmpopt = option_table[i];
104 tmpopt->description = command->desc_overrides[j].desc;
105 return tmpopt;
106 }
107 }
108 return &(option_table[i]);
109 }
110
111 return NULL;
112 }
113
114 /* Like svn_opt_get_option_from_code3(), but also, if CODE appears a second
115 * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
116 * second name, else set it to NULL. */
117 static const apr_getopt_option_t *
get_option_from_code3(const char ** long_alias,int code,const apr_getopt_option_t * option_table,const svn_opt_subcommand_desc3_t * command,apr_pool_t * pool)118 get_option_from_code3(const char **long_alias,
119 int code,
120 const apr_getopt_option_t *option_table,
121 const svn_opt_subcommand_desc3_t *command,
122 apr_pool_t *pool)
123 {
124 const apr_getopt_option_t *i;
125 const apr_getopt_option_t *opt
126 = svn_opt_get_option_from_code3(code, option_table, command, pool);
127
128 /* Find a long alias in the table, if there is one. */
129 *long_alias = NULL;
130 for (i = option_table; i->optch; i++)
131 {
132 if (i->optch == code && i->name != opt->name)
133 {
134 *long_alias = i->name;
135 break;
136 }
137 }
138
139 return opt;
140 }
141
142
143 /* Print an option OPT nicely into a STRING allocated in POOL.
144 * If OPT has a single-character short form, then print OPT->name (if not
145 * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
146 * If DOC is set, include the generic documentation string of OPT,
147 * localized to the current locale if a translation is available.
148 */
149 static void
format_option(const char ** string,const apr_getopt_option_t * opt,const char * long_alias,svn_boolean_t doc,apr_pool_t * pool)150 format_option(const char **string,
151 const apr_getopt_option_t *opt,
152 const char *long_alias,
153 svn_boolean_t doc,
154 apr_pool_t *pool)
155 {
156 char *opts;
157
158 if (opt == NULL)
159 {
160 *string = "?";
161 return;
162 }
163
164 /* We have a valid option which may or may not have a "short
165 name" (a single-character alias for the long option). */
166 if (opt->optch <= 255)
167 opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
168 else if (long_alias)
169 opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
170 else
171 opts = apr_psprintf(pool, "--%s", opt->name);
172
173 if (opt->has_arg)
174 opts = apr_pstrcat(pool, opts, _(" ARG"), SVN_VA_NULL);
175
176 if (doc)
177 opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
178
179 *string = opts;
180 }
181
182 void
svn_opt_format_option(const char ** string,const apr_getopt_option_t * opt,svn_boolean_t doc,apr_pool_t * pool)183 svn_opt_format_option(const char **string,
184 const apr_getopt_option_t *opt,
185 svn_boolean_t doc,
186 apr_pool_t *pool)
187 {
188 format_option(string, opt, NULL, doc, pool);
189 }
190
191
192 svn_boolean_t
svn_opt_subcommand_takes_option4(const svn_opt_subcommand_desc3_t * command,int option_code,const int * global_options)193 svn_opt_subcommand_takes_option4(const svn_opt_subcommand_desc3_t *command,
194 int option_code,
195 const int *global_options)
196 {
197 apr_size_t i;
198
199 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
200 if (command->valid_options[i] == option_code)
201 return TRUE;
202
203 if (global_options)
204 for (i = 0; global_options[i]; i++)
205 if (global_options[i] == option_code)
206 return TRUE;
207
208 return FALSE;
209 }
210
211
212 /* Print the canonical command name for CMD, and all its aliases, to
213 STREAM. If HELP is set, print CMD's help string too, in which case
214 obtain option usage from OPTIONS_TABLE.
215
216 Include global and experimental options iff VERBOSE is true.
217 */
218 static svn_error_t *
print_command_info3(const svn_opt_subcommand_desc3_t * cmd,const apr_getopt_option_t * options_table,const int * global_options,svn_boolean_t help,svn_boolean_t verbose,apr_pool_t * pool,FILE * stream)219 print_command_info3(const svn_opt_subcommand_desc3_t *cmd,
220 const apr_getopt_option_t *options_table,
221 const int *global_options,
222 svn_boolean_t help,
223 svn_boolean_t verbose,
224 apr_pool_t *pool,
225 FILE *stream)
226 {
227 svn_boolean_t first_time;
228 apr_size_t i;
229
230 /* Print the canonical command name. */
231 SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
232
233 /* Print the list of aliases. */
234 first_time = TRUE;
235 for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
236 {
237 if (cmd->aliases[i] == NULL)
238 break;
239
240 if (first_time) {
241 SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
242 first_time = FALSE;
243 }
244 else
245 SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
246
247 SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
248 }
249
250 if (! first_time)
251 SVN_ERR(svn_cmdline_fputs(")", stream, pool));
252
253 if (help)
254 {
255 const apr_getopt_option_t *option;
256 const char *long_alias;
257 svn_boolean_t have_options = FALSE;
258 svn_boolean_t have_experimental = FALSE;
259
260 SVN_ERR(svn_cmdline_fprintf(stream, pool, ": "));
261
262 for (i = 0; i < SVN_OPT_MAX_PARAGRAPHS && cmd->help[i]; i++)
263 {
264 SVN_ERR(svn_cmdline_fprintf(stream, pool, "%s", _(cmd->help[i])));
265 }
266
267 /* Loop over all valid option codes attached to the subcommand */
268 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
269 {
270 if (cmd->valid_options[i])
271 {
272 if (!have_options)
273 {
274 SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
275 stream, pool));
276 have_options = TRUE;
277 }
278
279 /* convert each option code into an option */
280 option = get_option_from_code3(&long_alias, cmd->valid_options[i],
281 options_table, cmd, pool);
282
283 /* print the option's docstring */
284 if (option && option->description)
285 {
286 const char *optstr;
287
288 if (option->name && strncmp(option->name, "x-", 2) == 0)
289 {
290 if (verbose && !have_experimental)
291 SVN_ERR(svn_cmdline_fputs(_("\nExperimental options:\n"),
292 stream, pool));
293 have_experimental = TRUE;
294 if (!verbose)
295 continue;
296 }
297
298 format_option(&optstr, option, long_alias, TRUE, pool);
299 SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
300 optstr));
301 }
302 }
303 }
304 /* And global options too */
305 if (verbose && global_options && *global_options)
306 {
307 SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
308 stream, pool));
309 have_options = TRUE;
310
311 for (i = 0; global_options[i]; i++)
312 {
313
314 /* convert each option code into an option */
315 option = get_option_from_code3(&long_alias, global_options[i],
316 options_table, cmd, pool);
317
318 /* print the option's docstring */
319 if (option && option->description)
320 {
321 const char *optstr;
322 format_option(&optstr, option, long_alias, TRUE, pool);
323 SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
324 optstr));
325 }
326 }
327 }
328
329 if (!verbose && global_options && *global_options)
330 SVN_ERR(svn_cmdline_fputs(_("\n(Use '-v' to show global and experimental options.)\n"),
331 stream, pool));
332 if (have_options)
333 SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
334 }
335
336 return SVN_NO_ERROR;
337 }
338
339 /* The body for svn_opt_print_generic_help3() function with standard error
340 * handling semantic. Handling of errors implemented at caller side. */
341 static svn_error_t *
print_generic_help_body3(const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * opt_table,const char * footer,svn_boolean_t with_experimental,apr_pool_t * pool,FILE * stream)342 print_generic_help_body3(const char *header,
343 const svn_opt_subcommand_desc3_t *cmd_table,
344 const apr_getopt_option_t *opt_table,
345 const char *footer,
346 svn_boolean_t with_experimental,
347 apr_pool_t *pool, FILE *stream)
348 {
349 svn_boolean_t have_experimental = FALSE;
350 int i;
351
352 if (header)
353 SVN_ERR(svn_cmdline_fputs(header, stream, pool));
354
355 for (i = 0; cmd_table[i].name; i++)
356 {
357 if (strncmp(cmd_table[i].name, "x-", 2) == 0)
358 {
359 if (with_experimental && !have_experimental)
360 SVN_ERR(svn_cmdline_fputs(_("\nExperimental subcommands:\n"),
361 stream, pool));
362 have_experimental = TRUE;
363 if (!with_experimental)
364 continue;
365 }
366 SVN_ERR(svn_cmdline_fputs(" ", stream, pool));
367 SVN_ERR(print_command_info3(cmd_table + i, opt_table,
368 NULL, FALSE, FALSE,
369 pool, stream));
370 SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
371 }
372
373 if (have_experimental && !with_experimental)
374 SVN_ERR(svn_cmdline_fputs(_("\n(Use '-v' to show experimental subcommands.)\n"),
375 stream, pool));
376
377 SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
378
379 if (footer)
380 SVN_ERR(svn_cmdline_fputs(footer, stream, pool));
381
382 return SVN_NO_ERROR;
383 }
384
385 static void
print_generic_help(const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * opt_table,const char * footer,svn_boolean_t with_experimental,apr_pool_t * pool,FILE * stream)386 print_generic_help(const char *header,
387 const svn_opt_subcommand_desc3_t *cmd_table,
388 const apr_getopt_option_t *opt_table,
389 const char *footer,
390 svn_boolean_t with_experimental,
391 apr_pool_t *pool, FILE *stream)
392 {
393 svn_error_t *err;
394
395 err = print_generic_help_body3(header, cmd_table, opt_table, footer,
396 with_experimental,
397 pool, stream);
398
399 /* Issue #3014:
400 * Don't print anything on broken pipes. The pipe was likely
401 * closed by the process at the other end. We expect that
402 * process to perform error reporting as necessary.
403 *
404 * ### This assumes that there is only one error in a chain for
405 * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
406 if (err && err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
407 svn_handle_error2(err, stderr, FALSE, "svn: ");
408 svn_error_clear(err);
409 }
410
411 void
svn_opt_print_generic_help3(const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * opt_table,const char * footer,apr_pool_t * pool,FILE * stream)412 svn_opt_print_generic_help3(const char *header,
413 const svn_opt_subcommand_desc3_t *cmd_table,
414 const apr_getopt_option_t *opt_table,
415 const char *footer,
416 apr_pool_t *pool, FILE *stream)
417 {
418 print_generic_help(header, cmd_table, opt_table, footer,
419 TRUE, pool, stream);
420 }
421
422
423 /* The body of svn_opt_subcommand_help4(), which see.
424 *
425 * VERBOSE means show also the subcommand's global and experimental options.
426 */
427 static void
subcommand_help(const char * subcommand,const svn_opt_subcommand_desc3_t * table,const apr_getopt_option_t * options_table,const int * global_options,svn_boolean_t verbose,apr_pool_t * pool)428 subcommand_help(const char *subcommand,
429 const svn_opt_subcommand_desc3_t *table,
430 const apr_getopt_option_t *options_table,
431 const int *global_options,
432 svn_boolean_t verbose,
433 apr_pool_t *pool)
434 {
435 const svn_opt_subcommand_desc3_t *cmd =
436 svn_opt_get_canonical_subcommand3(table, subcommand);
437 svn_error_t *err;
438
439 if (cmd)
440 err = print_command_info3(cmd, options_table, global_options,
441 TRUE, verbose, pool, stdout);
442 else
443 err = svn_cmdline_fprintf(stderr, pool,
444 _("\"%s\": unknown command.\n\n"), subcommand);
445
446 if (err) {
447 /* Issue #3014: Don't print anything on broken pipes. */
448 if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
449 svn_handle_error2(err, stderr, FALSE, "svn: ");
450 svn_error_clear(err);
451 }
452 }
453
454 void
svn_opt_subcommand_help4(const char * subcommand,const svn_opt_subcommand_desc3_t * table,const apr_getopt_option_t * options_table,const int * global_options,apr_pool_t * pool)455 svn_opt_subcommand_help4(const char *subcommand,
456 const svn_opt_subcommand_desc3_t *table,
457 const apr_getopt_option_t *options_table,
458 const int *global_options,
459 apr_pool_t *pool)
460 {
461 subcommand_help(subcommand, table, options_table, global_options,
462 TRUE, pool);
463 }
464
465
466
467 /*** Parsing revision and date options. ***/
468
469
470 /** Parsing "X:Y"-style arguments. **/
471
472 /* If WORD matches one of the special revision descriptors,
473 * case-insensitively, set *REVISION accordingly:
474 *
475 * - For "head", set REVISION->kind to svn_opt_revision_head.
476 *
477 * - For "prev", set REVISION->kind to svn_opt_revision_previous.
478 *
479 * - For "base", set REVISION->kind to svn_opt_revision_base.
480 *
481 * - For "committed", set REVISION->kind to svn_opt_revision_committed.
482 *
483 * If match, return 0, else return -1 and don't touch REVISION.
484 */
485 static int
revision_from_word(svn_opt_revision_t * revision,const char * word)486 revision_from_word(svn_opt_revision_t *revision, const char *word)
487 {
488 if (svn_cstring_casecmp(word, "head") == 0)
489 {
490 revision->kind = svn_opt_revision_head;
491 }
492 else if (svn_cstring_casecmp(word, "prev") == 0)
493 {
494 revision->kind = svn_opt_revision_previous;
495 }
496 else if (svn_cstring_casecmp(word, "base") == 0)
497 {
498 revision->kind = svn_opt_revision_base;
499 }
500 else if (svn_cstring_casecmp(word, "committed") == 0)
501 {
502 revision->kind = svn_opt_revision_committed;
503 }
504 else
505 return -1;
506
507 return 0;
508 }
509
510
511 /* Parse one revision specification. Return pointer to character
512 after revision, or NULL if the revision is invalid. Modifies
513 str, so make sure to pass a copy of anything precious. Uses
514 POOL for temporary allocation. */
parse_one_rev(svn_opt_revision_t * revision,char * str,apr_pool_t * pool)515 static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
516 apr_pool_t *pool)
517 {
518 char *end, save;
519
520 /* Allow any number of 'r's to prefix a revision number, because
521 that way if a script pastes svn output into another svn command
522 (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
523 even when compounded.
524
525 As it happens, none of our special revision words begins with
526 "r". If any ever do, then this code will have to get smarter.
527
528 Incidentally, this allows "r{DATE}". We could avoid that with
529 some trivial code rearrangement, but it's not clear what would
530 be gained by doing so. */
531 while (*str == 'r')
532 str++;
533
534 if (*str == '{')
535 {
536 svn_boolean_t matched;
537 apr_time_t tm;
538 svn_error_t *err;
539
540 /* Brackets denote a date. */
541 str++;
542 end = strchr(str, '}');
543 if (!end)
544 return NULL;
545 *end = '\0';
546 err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
547 if (err)
548 {
549 svn_error_clear(err);
550 return NULL;
551 }
552 if (!matched)
553 return NULL;
554 revision->kind = svn_opt_revision_date;
555 revision->value.date = tm;
556 return end + 1;
557 }
558 else if (svn_ctype_isdigit(*str))
559 {
560 /* It's a number. */
561 end = str + 1;
562 while (svn_ctype_isdigit(*end))
563 end++;
564 save = *end;
565 *end = '\0';
566 revision->kind = svn_opt_revision_number;
567 revision->value.number = SVN_STR_TO_REV(str);
568 *end = save;
569 return end;
570 }
571 else if (svn_ctype_isalpha(*str))
572 {
573 end = str + 1;
574 while (svn_ctype_isalpha(*end))
575 end++;
576 save = *end;
577 *end = '\0';
578 if (revision_from_word(revision, str) != 0)
579 return NULL;
580 *end = save;
581 return end;
582 }
583 else
584 return NULL;
585 }
586
587
588 int
svn_opt_parse_revision(svn_opt_revision_t * start_revision,svn_opt_revision_t * end_revision,const char * arg,apr_pool_t * pool)589 svn_opt_parse_revision(svn_opt_revision_t *start_revision,
590 svn_opt_revision_t *end_revision,
591 const char *arg,
592 apr_pool_t *pool)
593 {
594 char *left_rev, *right_rev, *end;
595
596 /* Operate on a copy of the argument. */
597 left_rev = apr_pstrdup(pool, arg);
598
599 right_rev = parse_one_rev(start_revision, left_rev, pool);
600 if (right_rev && *right_rev == ':')
601 {
602 right_rev++;
603 end = parse_one_rev(end_revision, right_rev, pool);
604 if (!end || *end != '\0')
605 return -1;
606 }
607 else if (!right_rev || *right_rev != '\0')
608 return -1;
609
610 return 0;
611 }
612
613
614 int
svn_opt_parse_revision_to_range(apr_array_header_t * opt_ranges,const char * arg,apr_pool_t * pool)615 svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
616 const char *arg,
617 apr_pool_t *pool)
618 {
619 svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
620
621 range->start.kind = svn_opt_revision_unspecified;
622 range->end.kind = svn_opt_revision_unspecified;
623
624 if (svn_opt_parse_revision(&(range->start), &(range->end),
625 arg, pool) == -1)
626 return -1;
627
628 APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
629 return 0;
630 }
631
632 svn_error_t *
svn_opt_resolve_revisions(svn_opt_revision_t * peg_rev,svn_opt_revision_t * op_rev,svn_boolean_t is_url,svn_boolean_t notice_local_mods,apr_pool_t * pool)633 svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
634 svn_opt_revision_t *op_rev,
635 svn_boolean_t is_url,
636 svn_boolean_t notice_local_mods,
637 apr_pool_t *pool)
638 {
639 if (peg_rev->kind == svn_opt_revision_unspecified)
640 {
641 if (is_url)
642 {
643 peg_rev->kind = svn_opt_revision_head;
644 }
645 else
646 {
647 if (notice_local_mods)
648 peg_rev->kind = svn_opt_revision_working;
649 else
650 peg_rev->kind = svn_opt_revision_base;
651 }
652 }
653
654 if (op_rev->kind == svn_opt_revision_unspecified)
655 *op_rev = *peg_rev;
656
657 return SVN_NO_ERROR;
658 }
659
660 const char *
svn_opt__revision_to_string(const svn_opt_revision_t * revision,apr_pool_t * result_pool)661 svn_opt__revision_to_string(const svn_opt_revision_t *revision,
662 apr_pool_t *result_pool)
663 {
664 switch (revision->kind)
665 {
666 case svn_opt_revision_unspecified:
667 return "unspecified";
668 case svn_opt_revision_number:
669 return apr_psprintf(result_pool, "%ld", revision->value.number);
670 case svn_opt_revision_date:
671 /* ### svn_time_to_human_cstring()? */
672 return svn_time_to_cstring(revision->value.date, result_pool);
673 case svn_opt_revision_committed:
674 return "committed";
675 case svn_opt_revision_previous:
676 return "previous";
677 case svn_opt_revision_base:
678 return "base";
679 case svn_opt_revision_working:
680 return "working";
681 case svn_opt_revision_head:
682 return "head";
683 default:
684 return NULL;
685 }
686 }
687
688 svn_opt_revision_range_t *
svn_opt__revision_range_create(const svn_opt_revision_t * start_revision,const svn_opt_revision_t * end_revision,apr_pool_t * result_pool)689 svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
690 const svn_opt_revision_t *end_revision,
691 apr_pool_t *result_pool)
692 {
693 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
694
695 range->start = *start_revision;
696 range->end = *end_revision;
697 return range;
698 }
699
700 svn_opt_revision_range_t *
svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,svn_revnum_t end_revnum,apr_pool_t * result_pool)701 svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
702 svn_revnum_t end_revnum,
703 apr_pool_t *result_pool)
704 {
705 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
706
707 range->start.kind = svn_opt_revision_number;
708 range->start.value.number = start_revnum;
709 range->end.kind = svn_opt_revision_number;
710 range->end.value.number = end_revnum;
711 return range;
712 }
713
714
715
716 /*** Parsing arguments. ***/
717 #define DEFAULT_ARRAY_SIZE 5
718
719
720 /* Copy STR into POOL and push the copy onto ARRAY. */
721 static void
array_push_str(apr_array_header_t * array,const char * str,apr_pool_t * pool)722 array_push_str(apr_array_header_t *array,
723 const char *str,
724 apr_pool_t *pool)
725 {
726 /* ### Not sure if this function is still necessary. It used to
727 convert str to svn_stringbuf_t * and push it, but now it just
728 dups str in pool and pushes the copy. So its only effect is
729 transfer str's lifetime to pool. Is that something callers are
730 depending on? */
731
732 APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
733 }
734
735
736 void
svn_opt_push_implicit_dot_target(apr_array_header_t * targets,apr_pool_t * pool)737 svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
738 apr_pool_t *pool)
739 {
740 if (targets->nelts == 0)
741 APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
742 assert(targets->nelts);
743 }
744
745
746 svn_error_t *
svn_opt_parse_num_args(apr_array_header_t ** args_p,apr_getopt_t * os,int num_args,apr_pool_t * pool)747 svn_opt_parse_num_args(apr_array_header_t **args_p,
748 apr_getopt_t *os,
749 int num_args,
750 apr_pool_t *pool)
751 {
752 int i;
753 apr_array_header_t *args
754 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
755
756 /* loop for num_args and add each arg to the args array */
757 for (i = 0; i < num_args; i++)
758 {
759 if (os->ind >= os->argc)
760 {
761 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
762 }
763 array_push_str(args, os->argv[os->ind++], pool);
764 }
765
766 *args_p = args;
767 return SVN_NO_ERROR;
768 }
769
770 svn_error_t *
svn_opt_parse_all_args(apr_array_header_t ** args_p,apr_getopt_t * os,apr_pool_t * pool)771 svn_opt_parse_all_args(apr_array_header_t **args_p,
772 apr_getopt_t *os,
773 apr_pool_t *pool)
774 {
775 apr_array_header_t *args
776 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
777
778 if (os->ind > os->argc)
779 {
780 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
781 }
782 while (os->ind < os->argc)
783 {
784 array_push_str(args, os->argv[os->ind++], pool);
785 }
786
787 *args_p = args;
788 return SVN_NO_ERROR;
789 }
790
791
792 svn_error_t *
svn_opt_parse_path(svn_opt_revision_t * rev,const char ** truepath,const char * path,apr_pool_t * pool)793 svn_opt_parse_path(svn_opt_revision_t *rev,
794 const char **truepath,
795 const char *path /* UTF-8! */,
796 apr_pool_t *pool)
797 {
798 const char *peg_rev;
799
800 SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
801
802 /* Parse the peg revision, if one was found */
803 if (strlen(peg_rev))
804 {
805 int ret;
806 svn_opt_revision_t start_revision, end_revision;
807
808 end_revision.kind = svn_opt_revision_unspecified;
809
810 if (peg_rev[1] == '\0') /* looking at empty peg revision */
811 {
812 ret = 0;
813 start_revision.kind = svn_opt_revision_unspecified;
814 start_revision.value.number = 0;
815 }
816 else /* looking at non-empty peg revision */
817 {
818 const char *rev_str = &peg_rev[1];
819
820 /* URLs get treated differently from wc paths. */
821 if (svn_path_is_url(path))
822 {
823 /* URLs are URI-encoded, so we look for dates with
824 URI-encoded delimiters. */
825 size_t rev_len = strlen(rev_str);
826 if (rev_len > 6
827 && rev_str[0] == '%'
828 && rev_str[1] == '7'
829 && (rev_str[2] == 'B'
830 || rev_str[2] == 'b')
831 && rev_str[rev_len-3] == '%'
832 && rev_str[rev_len-2] == '7'
833 && (rev_str[rev_len-1] == 'D'
834 || rev_str[rev_len-1] == 'd'))
835 {
836 rev_str = svn_path_uri_decode(rev_str, pool);
837 }
838 }
839 ret = svn_opt_parse_revision(&start_revision,
840 &end_revision,
841 rev_str, pool);
842 }
843
844 if (ret || end_revision.kind != svn_opt_revision_unspecified)
845 {
846 /* If an svn+ssh URL was used and it contains only one @,
847 * provide an error message that presents a possible solution
848 * to the parsing error (issue #2349). */
849 if (strncmp(path, "svn+ssh://", 10) == 0)
850 {
851 const char *at;
852
853 at = strchr(path, '@');
854 if (at && strrchr(path, '@') == at)
855 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
856 _("Syntax error parsing peg revision "
857 "'%s'; did you mean '%s@'?"),
858 &peg_rev[1], path);
859 }
860
861 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
862 _("Syntax error parsing peg revision '%s'"),
863 &peg_rev[1]);
864 }
865 rev->kind = start_revision.kind;
866 rev->value = start_revision.value;
867 }
868 else
869 {
870 /* Didn't find a peg revision. */
871 rev->kind = svn_opt_revision_unspecified;
872 }
873
874 return SVN_NO_ERROR;
875 }
876
877
878 /* Note: This is substantially copied into svn_client_args_to_target_array() in
879 * order to move to libsvn_client while maintaining backward compatibility. */
880 svn_error_t *
svn_opt__args_to_target_array(apr_array_header_t ** targets_p,apr_getopt_t * os,const apr_array_header_t * known_targets,apr_pool_t * pool)881 svn_opt__args_to_target_array(apr_array_header_t **targets_p,
882 apr_getopt_t *os,
883 const apr_array_header_t *known_targets,
884 apr_pool_t *pool)
885 {
886 int i;
887 svn_error_t *err = SVN_NO_ERROR;
888 apr_array_header_t *input_targets =
889 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
890 apr_array_header_t *output_targets =
891 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
892
893 /* Step 1: create a master array of targets that are in UTF-8
894 encoding, and come from concatenating the targets left by apr_getopt,
895 plus any extra targets (e.g., from the --targets switch.) */
896
897 for (; os->ind < os->argc; os->ind++)
898 {
899 /* The apr_getopt targets are still in native encoding. */
900 const char *raw_target = os->argv[os->ind];
901 SVN_ERR(svn_utf_cstring_to_utf8
902 ((const char **) apr_array_push(input_targets),
903 raw_target, pool));
904 }
905
906 if (known_targets)
907 {
908 for (i = 0; i < known_targets->nelts; i++)
909 {
910 /* The --targets array have already been converted to UTF-8,
911 because we needed to split up the list with svn_cstring_split. */
912 const char *utf8_target = APR_ARRAY_IDX(known_targets,
913 i, const char *);
914 APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
915 }
916 }
917
918 /* Step 2: process each target. */
919
920 for (i = 0; i < input_targets->nelts; i++)
921 {
922 const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
923 const char *true_target;
924 const char *target; /* after all processing is finished */
925 const char *peg_rev;
926
927 /*
928 * This is needed so that the target can be properly canonicalized,
929 * otherwise the canonicalization does not treat a ".@BASE" as a "."
930 * with a BASE peg revision, and it is not canonicalized to "@BASE".
931 * If any peg revision exists, it is appended to the final
932 * canonicalized path or URL. Do not use svn_opt_parse_path()
933 * because the resulting peg revision is a structure that would have
934 * to be converted back into a string. Converting from a string date
935 * to the apr_time_t field in the svn_opt_revision_value_t and back to
936 * a string would not necessarily preserve the exact bytes of the
937 * input date, so its easier just to keep it in string form.
938 */
939 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
940 utf8_target, pool));
941
942 /* URLs and wc-paths get treated differently. */
943 if (svn_path_is_url(true_target))
944 {
945 SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
946 pool));
947 }
948 else /* not a url, so treat as a path */
949 {
950 const char *base_name;
951
952 SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
953 pool));
954
955 /* If the target has the same name as a Subversion
956 working copy administrative dir, skip it. */
957 base_name = svn_dirent_basename(true_target, pool);
958
959 /* FIXME:
960 The canonical list of administrative directory names is
961 maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
962 That list can't be used here, because that use would
963 create a circular dependency between libsvn_wc and
964 libsvn_subr. Make sure changes to the lists are always
965 synchronized! */
966 if (0 == strcmp(base_name, ".svn")
967 || 0 == strcmp(base_name, "_svn"))
968 {
969 err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
970 err, _("'%s' ends in a reserved name"),
971 utf8_target);
972 continue;
973 }
974 }
975
976 target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL);
977
978 APR_ARRAY_PUSH(output_targets, const char *) = target;
979 }
980
981
982 /* kff todo: need to remove redundancies from targets before
983 passing it to the cmd_func. */
984
985 *targets_p = output_targets;
986
987 return err;
988 }
989
990 svn_error_t *
svn_opt_parse_revprop(apr_hash_t ** revprop_table_p,const char * revprop_spec,apr_pool_t * pool)991 svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
992 apr_pool_t *pool)
993 {
994 const char *sep, *propname;
995 svn_string_t *propval;
996
997 if (! *revprop_spec)
998 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
999 _("Revision property pair is empty"));
1000
1001 if (! *revprop_table_p)
1002 *revprop_table_p = apr_hash_make(pool);
1003
1004 sep = strchr(revprop_spec, '=');
1005 if (sep)
1006 {
1007 propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
1008 SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
1009 propval = svn_string_create(sep + 1, pool);
1010 }
1011 else
1012 {
1013 SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
1014 propval = svn_string_create_empty(pool);
1015 }
1016
1017 if (!svn_prop_name_is_valid(propname))
1018 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
1019 _("'%s' is not a valid Subversion property name"),
1020 propname);
1021
1022 svn_hash_sets(*revprop_table_p, propname, propval);
1023
1024 return SVN_NO_ERROR;
1025 }
1026
1027 svn_error_t *
svn_opt__split_arg_at_peg_revision(const char ** true_target,const char ** peg_revision,const char * utf8_target,apr_pool_t * pool)1028 svn_opt__split_arg_at_peg_revision(const char **true_target,
1029 const char **peg_revision,
1030 const char *utf8_target,
1031 apr_pool_t *pool)
1032 {
1033 const char *peg_start = NULL; /* pointer to the peg revision, if any */
1034 const char *ptr;
1035
1036 for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
1037 --ptr)
1038 {
1039 /* If we hit a path separator, stop looking. This is OK
1040 only because our revision specifiers can't contain '/'. */
1041 if (*ptr == '/')
1042 break;
1043
1044 if (*ptr == '@')
1045 {
1046 peg_start = ptr;
1047 break;
1048 }
1049 }
1050
1051 if (peg_start)
1052 {
1053 *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1054 if (peg_revision)
1055 *peg_revision = apr_pstrdup(pool, peg_start);
1056 }
1057 else
1058 {
1059 *true_target = utf8_target;
1060 if (peg_revision)
1061 *peg_revision = "";
1062 }
1063
1064 return SVN_NO_ERROR;
1065 }
1066
1067 svn_error_t *
svn_opt__arg_canonicalize_url(const char ** url_out,const char * url_in,apr_pool_t * pool)1068 svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1069 apr_pool_t *pool)
1070 {
1071 const char *target;
1072
1073 /* Convert to URI. */
1074 target = svn_path_uri_from_iri(url_in, pool);
1075 /* Auto-escape some ASCII characters. */
1076 target = svn_path_uri_autoescape(target, pool);
1077
1078 #if '/' != SVN_PATH_LOCAL_SEPARATOR
1079 /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1080 if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1081 {
1082 char *p = apr_pstrdup(pool, target);
1083 target = p;
1084
1085 /* Convert all local-style separators to the canonical ones. */
1086 for (; *p != '\0'; ++p)
1087 if (*p == SVN_PATH_LOCAL_SEPARATOR)
1088 *p = '/';
1089 }
1090 #endif
1091
1092 /* Verify that no backpaths are present in the URL. */
1093 if (svn_path_is_backpath_present(target))
1094 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1095 _("URL '%s' contains a '..' element"),
1096 target);
1097
1098 /* Strip any trailing '/' and collapse other redundant elements. */
1099 target = svn_uri_canonicalize(target, pool);
1100
1101 *url_out = target;
1102 return SVN_NO_ERROR;
1103 }
1104
1105 svn_error_t *
svn_opt__arg_canonicalize_path(const char ** path_out,const char * path_in,apr_pool_t * pool)1106 svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1107 apr_pool_t *pool)
1108 {
1109 const char *apr_target;
1110 char *truenamed_target; /* APR-encoded */
1111 apr_status_t apr_err;
1112
1113 /* canonicalize case, and change all separators to '/'. */
1114 SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1115 apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1116 APR_FILEPATH_TRUENAME, pool);
1117
1118 if (!apr_err)
1119 /* We have a canonicalized APR-encoded target now. */
1120 apr_target = truenamed_target;
1121 else if (APR_STATUS_IS_ENOENT(apr_err))
1122 /* It's okay for the file to not exist, that just means we
1123 have to accept the case given to the client. We'll use
1124 the original APR-encoded target. */
1125 ;
1126 else
1127 return svn_error_createf(apr_err, NULL,
1128 _("Error resolving case of '%s'"),
1129 svn_dirent_local_style(path_in, pool));
1130
1131 /* convert back to UTF-8. */
1132 SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1133 *path_out = svn_dirent_canonicalize(*path_out, pool);
1134
1135 return SVN_NO_ERROR;
1136 }
1137
1138
1139 svn_error_t *
svn_opt__print_version_info(const char * pgm_name,const char * footer,const svn_version_extended_t * info,svn_boolean_t quiet,svn_boolean_t verbose,apr_pool_t * pool)1140 svn_opt__print_version_info(const char *pgm_name,
1141 const char *footer,
1142 const svn_version_extended_t *info,
1143 svn_boolean_t quiet,
1144 svn_boolean_t verbose,
1145 apr_pool_t *pool)
1146 {
1147 if (quiet)
1148 return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1149
1150 SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1151 " compiled %s, %s on %s\n\n"),
1152 pgm_name, SVN_VERSION,
1153 svn_version_ext_build_date(info),
1154 svn_version_ext_build_time(info),
1155 svn_version_ext_build_host(info)));
1156 SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1157
1158 if (footer)
1159 {
1160 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1161 }
1162
1163 if (verbose)
1164 {
1165 const apr_array_header_t *libs;
1166
1167 SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1168 SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1169 svn_version_ext_runtime_host(info)));
1170 if (svn_version_ext_runtime_osname(info))
1171 {
1172 SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"),
1173 svn_version_ext_runtime_osname(info)));
1174 }
1175
1176 libs = svn_version_ext_linked_libs(info);
1177 if (libs && libs->nelts)
1178 {
1179 const svn_version_ext_linked_lib_t *lib;
1180 int i;
1181
1182 SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1183 stdout, pool));
1184 for (i = 0; i < libs->nelts; ++i)
1185 {
1186 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1187 if (lib->runtime_version)
1188 SVN_ERR(svn_cmdline_printf(pool,
1189 " - %s %s (compiled with %s)\n",
1190 lib->name,
1191 lib->runtime_version,
1192 lib->compiled_version));
1193 else
1194 SVN_ERR(svn_cmdline_printf(pool,
1195 " - %s %s (static)\n",
1196 lib->name,
1197 lib->compiled_version));
1198 }
1199 }
1200
1201 libs = svn_version_ext_loaded_libs(info);
1202 if (libs && libs->nelts)
1203 {
1204 const svn_version_ext_loaded_lib_t *lib;
1205 int i;
1206
1207 SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1208 stdout, pool));
1209 for (i = 0; i < libs->nelts; ++i)
1210 {
1211 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1212 if (lib->version)
1213 SVN_ERR(svn_cmdline_printf(pool,
1214 " - %s (%s)\n",
1215 lib->name, lib->version));
1216 else
1217 SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name));
1218 }
1219 }
1220 }
1221
1222 return SVN_NO_ERROR;
1223 }
1224
1225 svn_error_t *
svn_opt_print_help5(apr_getopt_t * os,const char * pgm_name,svn_boolean_t print_version,svn_boolean_t quiet,svn_boolean_t verbose,const char * version_footer,const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * option_table,const int * global_options,const char * footer,apr_pool_t * pool)1226 svn_opt_print_help5(apr_getopt_t *os,
1227 const char *pgm_name,
1228 svn_boolean_t print_version,
1229 svn_boolean_t quiet,
1230 svn_boolean_t verbose,
1231 const char *version_footer,
1232 const char *header,
1233 const svn_opt_subcommand_desc3_t *cmd_table,
1234 const apr_getopt_option_t *option_table,
1235 const int *global_options,
1236 const char *footer,
1237 apr_pool_t *pool)
1238 {
1239 apr_array_header_t *targets = NULL;
1240
1241 if (os)
1242 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1243
1244 if (os && targets->nelts) /* help on subcommand(s) requested */
1245 {
1246 int i;
1247
1248 for (i = 0; i < targets->nelts; i++)
1249 {
1250 subcommand_help(APR_ARRAY_IDX(targets, i, const char *),
1251 cmd_table, option_table, global_options,
1252 verbose, pool);
1253 }
1254 }
1255 else if (print_version) /* just --version */
1256 {
1257 SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1258 svn_version_extended(verbose, pool),
1259 quiet, verbose, pool));
1260 }
1261 else if (os && !targets->nelts) /* `-h', `--help', or `help' */
1262 print_generic_help(header, cmd_table, option_table, footer,
1263 verbose,
1264 pool, stdout);
1265 else /* unknown option or cmd */
1266 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1267 _("Type '%s help' for usage.\n"), pgm_name));
1268
1269 return SVN_NO_ERROR;
1270 }
1271