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