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 58const svn_opt_subcommand_desc3_t * 59svn_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 82const apr_getopt_option_t * 83svn_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. */ 117static const apr_getopt_option_t * 118get_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 */ 149static void 150format_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 182void 183svn_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 192svn_boolean_t 193svn_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 */ 218static svn_error_t * 219print_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. */ 341static svn_error_t * 342print_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 385static void 386print_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 411void 412svn_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 */ 427static void 428subcommand_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 454void 455svn_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 */ 485static int 486revision_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. */ 515static 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 588int 589svn_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 614int 615svn_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 632svn_error_t * 633svn_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 660const char * 661svn_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 688svn_opt_revision_range_t * 689svn_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 700svn_opt_revision_range_t * 701svn_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. */ 721static void 722array_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 736void 737svn_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 746svn_error_t * 747svn_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 770svn_error_t * 771svn_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 792svn_error_t * 793svn_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. */ 880svn_error_t * 881svn_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 990svn_error_t * 991svn_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 1027svn_error_t * 1028svn_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 1067svn_error_t * 1068svn_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 1105svn_error_t * 1106svn_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 1139svn_error_t * 1140svn_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 on %s\n\n"), 1152 pgm_name, SVN_VERSION, 1153 svn_version_ext_build_host(info))); 1154 SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info))); 1155 1156 if (footer) 1157 { 1158 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer)); 1159 } 1160 1161 if (verbose) 1162 { 1163 const apr_array_header_t *libs; 1164 1165 SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool)); 1166 SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), 1167 svn_version_ext_runtime_host(info))); 1168 if (svn_version_ext_runtime_osname(info)) 1169 { 1170 SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"), 1171 svn_version_ext_runtime_osname(info))); 1172 } 1173 1174 libs = svn_version_ext_linked_libs(info); 1175 if (libs && libs->nelts) 1176 { 1177 const svn_version_ext_linked_lib_t *lib; 1178 int i; 1179 1180 SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"), 1181 stdout, pool)); 1182 for (i = 0; i < libs->nelts; ++i) 1183 { 1184 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t); 1185 if (lib->runtime_version) 1186 SVN_ERR(svn_cmdline_printf(pool, 1187 " - %s %s (compiled with %s)\n", 1188 lib->name, 1189 lib->runtime_version, 1190 lib->compiled_version)); 1191 else 1192 SVN_ERR(svn_cmdline_printf(pool, 1193 " - %s %s (static)\n", 1194 lib->name, 1195 lib->compiled_version)); 1196 } 1197 } 1198 1199 libs = svn_version_ext_loaded_libs(info); 1200 if (libs && libs->nelts) 1201 { 1202 const svn_version_ext_loaded_lib_t *lib; 1203 int i; 1204 1205 SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"), 1206 stdout, pool)); 1207 for (i = 0; i < libs->nelts; ++i) 1208 { 1209 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t); 1210 if (lib->version) 1211 SVN_ERR(svn_cmdline_printf(pool, 1212 " - %s (%s)\n", 1213 lib->name, lib->version)); 1214 else 1215 SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name)); 1216 } 1217 } 1218 } 1219 1220 return SVN_NO_ERROR; 1221} 1222 1223svn_error_t * 1224svn_opt_print_help5(apr_getopt_t *os, 1225 const char *pgm_name, 1226 svn_boolean_t print_version, 1227 svn_boolean_t quiet, 1228 svn_boolean_t verbose, 1229 const char *version_footer, 1230 const char *header, 1231 const svn_opt_subcommand_desc3_t *cmd_table, 1232 const apr_getopt_option_t *option_table, 1233 const int *global_options, 1234 const char *footer, 1235 apr_pool_t *pool) 1236{ 1237 apr_array_header_t *targets = NULL; 1238 1239 if (os) 1240 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); 1241 1242 if (os && targets->nelts) /* help on subcommand(s) requested */ 1243 { 1244 int i; 1245 1246 for (i = 0; i < targets->nelts; i++) 1247 { 1248 subcommand_help(APR_ARRAY_IDX(targets, i, const char *), 1249 cmd_table, option_table, global_options, 1250 verbose, pool); 1251 } 1252 } 1253 else if (print_version) /* just --version */ 1254 { 1255 SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, 1256 svn_version_extended(verbose, pool), 1257 quiet, verbose, pool)); 1258 } 1259 else if (os && !targets->nelts) /* `-h', `--help', or `help' */ 1260 print_generic_help(header, cmd_table, option_table, footer, 1261 verbose, 1262 pool, stdout); 1263 else /* unknown option or cmd */ 1264 SVN_ERR(svn_cmdline_fprintf(stderr, pool, 1265 _("Type '%s help' for usage.\n"), pgm_name)); 1266 1267 return SVN_NO_ERROR; 1268} 1269