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_desc2_t * 59svn_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 82const apr_getopt_option_t * 83svn_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 115const apr_getopt_option_t * 116svn_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. */ 132static const apr_getopt_option_t * 133get_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 */ 164static void 165format_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 197void 198svn_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 207svn_boolean_t 208svn_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 226svn_boolean_t 227svn_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 236svn_boolean_t 237svn_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. */ 253static svn_error_t * 254print_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 353void 354svn_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 401void 402svn_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 svn_handle_error2(err, stderr, FALSE, "svn: "); 421 svn_error_clear(err); 422 } 423} 424 425 426 427/*** Parsing revision and date options. ***/ 428 429 430/** Parsing "X:Y"-style arguments. **/ 431 432/* If WORD matches one of the special revision descriptors, 433 * case-insensitively, set *REVISION accordingly: 434 * 435 * - For "head", set REVISION->kind to svn_opt_revision_head. 436 * 437 * - For "prev", set REVISION->kind to svn_opt_revision_previous. 438 * 439 * - For "base", set REVISION->kind to svn_opt_revision_base. 440 * 441 * - For "committed", set REVISION->kind to svn_opt_revision_committed. 442 * 443 * If match, return 0, else return -1 and don't touch REVISION. 444 */ 445static int 446revision_from_word(svn_opt_revision_t *revision, const char *word) 447{ 448 if (svn_cstring_casecmp(word, "head") == 0) 449 { 450 revision->kind = svn_opt_revision_head; 451 } 452 else if (svn_cstring_casecmp(word, "prev") == 0) 453 { 454 revision->kind = svn_opt_revision_previous; 455 } 456 else if (svn_cstring_casecmp(word, "base") == 0) 457 { 458 revision->kind = svn_opt_revision_base; 459 } 460 else if (svn_cstring_casecmp(word, "committed") == 0) 461 { 462 revision->kind = svn_opt_revision_committed; 463 } 464 else 465 return -1; 466 467 return 0; 468} 469 470 471/* Parse one revision specification. Return pointer to character 472 after revision, or NULL if the revision is invalid. Modifies 473 str, so make sure to pass a copy of anything precious. Uses 474 POOL for temporary allocation. */ 475static char *parse_one_rev(svn_opt_revision_t *revision, char *str, 476 apr_pool_t *pool) 477{ 478 char *end, save; 479 480 /* Allow any number of 'r's to prefix a revision number, because 481 that way if a script pastes svn output into another svn command 482 (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work, 483 even when compounded. 484 485 As it happens, none of our special revision words begins with 486 "r". If any ever do, then this code will have to get smarter. 487 488 Incidentally, this allows "r{DATE}". We could avoid that with 489 some trivial code rearrangement, but it's not clear what would 490 be gained by doing so. */ 491 while (*str == 'r') 492 str++; 493 494 if (*str == '{') 495 { 496 svn_boolean_t matched; 497 apr_time_t tm; 498 svn_error_t *err; 499 500 /* Brackets denote a date. */ 501 str++; 502 end = strchr(str, '}'); 503 if (!end) 504 return NULL; 505 *end = '\0'; 506 err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool); 507 if (err) 508 { 509 svn_error_clear(err); 510 return NULL; 511 } 512 if (!matched) 513 return NULL; 514 revision->kind = svn_opt_revision_date; 515 revision->value.date = tm; 516 return end + 1; 517 } 518 else if (svn_ctype_isdigit(*str)) 519 { 520 /* It's a number. */ 521 end = str + 1; 522 while (svn_ctype_isdigit(*end)) 523 end++; 524 save = *end; 525 *end = '\0'; 526 revision->kind = svn_opt_revision_number; 527 revision->value.number = SVN_STR_TO_REV(str); 528 *end = save; 529 return end; 530 } 531 else if (svn_ctype_isalpha(*str)) 532 { 533 end = str + 1; 534 while (svn_ctype_isalpha(*end)) 535 end++; 536 save = *end; 537 *end = '\0'; 538 if (revision_from_word(revision, str) != 0) 539 return NULL; 540 *end = save; 541 return end; 542 } 543 else 544 return NULL; 545} 546 547 548int 549svn_opt_parse_revision(svn_opt_revision_t *start_revision, 550 svn_opt_revision_t *end_revision, 551 const char *arg, 552 apr_pool_t *pool) 553{ 554 char *left_rev, *right_rev, *end; 555 556 /* Operate on a copy of the argument. */ 557 left_rev = apr_pstrdup(pool, arg); 558 559 right_rev = parse_one_rev(start_revision, left_rev, pool); 560 if (right_rev && *right_rev == ':') 561 { 562 right_rev++; 563 end = parse_one_rev(end_revision, right_rev, pool); 564 if (!end || *end != '\0') 565 return -1; 566 } 567 else if (!right_rev || *right_rev != '\0') 568 return -1; 569 570 return 0; 571} 572 573 574int 575svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges, 576 const char *arg, 577 apr_pool_t *pool) 578{ 579 svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range)); 580 581 range->start.kind = svn_opt_revision_unspecified; 582 range->end.kind = svn_opt_revision_unspecified; 583 584 if (svn_opt_parse_revision(&(range->start), &(range->end), 585 arg, pool) == -1) 586 return -1; 587 588 APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range; 589 return 0; 590} 591 592svn_error_t * 593svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev, 594 svn_opt_revision_t *op_rev, 595 svn_boolean_t is_url, 596 svn_boolean_t notice_local_mods, 597 apr_pool_t *pool) 598{ 599 if (peg_rev->kind == svn_opt_revision_unspecified) 600 { 601 if (is_url) 602 { 603 peg_rev->kind = svn_opt_revision_head; 604 } 605 else 606 { 607 if (notice_local_mods) 608 peg_rev->kind = svn_opt_revision_working; 609 else 610 peg_rev->kind = svn_opt_revision_base; 611 } 612 } 613 614 if (op_rev->kind == svn_opt_revision_unspecified) 615 *op_rev = *peg_rev; 616 617 return SVN_NO_ERROR; 618} 619 620const char * 621svn_opt__revision_to_string(const svn_opt_revision_t *revision, 622 apr_pool_t *result_pool) 623{ 624 switch (revision->kind) 625 { 626 case svn_opt_revision_unspecified: 627 return "unspecified"; 628 case svn_opt_revision_number: 629 return apr_psprintf(result_pool, "%ld", revision->value.number); 630 case svn_opt_revision_date: 631 /* ### svn_time_to_human_cstring()? */ 632 return svn_time_to_cstring(revision->value.date, result_pool); 633 case svn_opt_revision_committed: 634 return "committed"; 635 case svn_opt_revision_previous: 636 return "previous"; 637 case svn_opt_revision_base: 638 return "base"; 639 case svn_opt_revision_working: 640 return "working"; 641 case svn_opt_revision_head: 642 return "head"; 643 default: 644 return NULL; 645 } 646} 647 648svn_opt_revision_range_t * 649svn_opt__revision_range_create(const svn_opt_revision_t *start_revision, 650 const svn_opt_revision_t *end_revision, 651 apr_pool_t *result_pool) 652{ 653 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); 654 655 range->start = *start_revision; 656 range->end = *end_revision; 657 return range; 658} 659 660svn_opt_revision_range_t * 661svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum, 662 svn_revnum_t end_revnum, 663 apr_pool_t *result_pool) 664{ 665 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); 666 667 range->start.kind = svn_opt_revision_number; 668 range->start.value.number = start_revnum; 669 range->end.kind = svn_opt_revision_number; 670 range->end.value.number = end_revnum; 671 return range; 672} 673 674 675 676/*** Parsing arguments. ***/ 677#define DEFAULT_ARRAY_SIZE 5 678 679 680/* Copy STR into POOL and push the copy onto ARRAY. */ 681static void 682array_push_str(apr_array_header_t *array, 683 const char *str, 684 apr_pool_t *pool) 685{ 686 /* ### Not sure if this function is still necessary. It used to 687 convert str to svn_stringbuf_t * and push it, but now it just 688 dups str in pool and pushes the copy. So its only effect is 689 transfer str's lifetime to pool. Is that something callers are 690 depending on? */ 691 692 APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str); 693} 694 695 696void 697svn_opt_push_implicit_dot_target(apr_array_header_t *targets, 698 apr_pool_t *pool) 699{ 700 if (targets->nelts == 0) 701 APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */ 702 assert(targets->nelts); 703} 704 705 706svn_error_t * 707svn_opt_parse_num_args(apr_array_header_t **args_p, 708 apr_getopt_t *os, 709 int num_args, 710 apr_pool_t *pool) 711{ 712 int i; 713 apr_array_header_t *args 714 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 715 716 /* loop for num_args and add each arg to the args array */ 717 for (i = 0; i < num_args; i++) 718 { 719 if (os->ind >= os->argc) 720 { 721 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 722 } 723 array_push_str(args, os->argv[os->ind++], pool); 724 } 725 726 *args_p = args; 727 return SVN_NO_ERROR; 728} 729 730svn_error_t * 731svn_opt_parse_all_args(apr_array_header_t **args_p, 732 apr_getopt_t *os, 733 apr_pool_t *pool) 734{ 735 apr_array_header_t *args 736 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 737 738 if (os->ind > os->argc) 739 { 740 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 741 } 742 while (os->ind < os->argc) 743 { 744 array_push_str(args, os->argv[os->ind++], pool); 745 } 746 747 *args_p = args; 748 return SVN_NO_ERROR; 749} 750 751 752svn_error_t * 753svn_opt_parse_path(svn_opt_revision_t *rev, 754 const char **truepath, 755 const char *path /* UTF-8! */, 756 apr_pool_t *pool) 757{ 758 const char *peg_rev; 759 760 SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool)); 761 762 /* Parse the peg revision, if one was found */ 763 if (strlen(peg_rev)) 764 { 765 int ret; 766 svn_opt_revision_t start_revision, end_revision; 767 768 end_revision.kind = svn_opt_revision_unspecified; 769 770 if (peg_rev[1] == '\0') /* looking at empty peg revision */ 771 { 772 ret = 0; 773 start_revision.kind = svn_opt_revision_unspecified; 774 start_revision.value.number = 0; 775 } 776 else /* looking at non-empty peg revision */ 777 { 778 const char *rev_str = &peg_rev[1]; 779 780 /* URLs get treated differently from wc paths. */ 781 if (svn_path_is_url(path)) 782 { 783 /* URLs are URI-encoded, so we look for dates with 784 URI-encoded delimeters. */ 785 size_t rev_len = strlen(rev_str); 786 if (rev_len > 6 787 && rev_str[0] == '%' 788 && rev_str[1] == '7' 789 && (rev_str[2] == 'B' 790 || rev_str[2] == 'b') 791 && rev_str[rev_len-3] == '%' 792 && rev_str[rev_len-2] == '7' 793 && (rev_str[rev_len-1] == 'D' 794 || rev_str[rev_len-1] == 'd')) 795 { 796 rev_str = svn_path_uri_decode(rev_str, pool); 797 } 798 } 799 ret = svn_opt_parse_revision(&start_revision, 800 &end_revision, 801 rev_str, pool); 802 } 803 804 if (ret || end_revision.kind != svn_opt_revision_unspecified) 805 { 806 /* If an svn+ssh URL was used and it contains only one @, 807 * provide an error message that presents a possible solution 808 * to the parsing error (issue #2349). */ 809 if (strncmp(path, "svn+ssh://", 10) == 0) 810 { 811 const char *at; 812 813 at = strchr(path, '@'); 814 if (at && strrchr(path, '@') == at) 815 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 816 _("Syntax error parsing peg revision " 817 "'%s'; did you mean '%s@'?"), 818 &peg_rev[1], path); 819 } 820 821 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 822 _("Syntax error parsing peg revision '%s'"), 823 &peg_rev[1]); 824 } 825 rev->kind = start_revision.kind; 826 rev->value = start_revision.value; 827 } 828 else 829 { 830 /* Didn't find a peg revision. */ 831 rev->kind = svn_opt_revision_unspecified; 832 } 833 834 return SVN_NO_ERROR; 835} 836 837 838/* Note: This is substantially copied into svn_client_args_to_target_array() in 839 * order to move to libsvn_client while maintaining backward compatibility. */ 840svn_error_t * 841svn_opt__args_to_target_array(apr_array_header_t **targets_p, 842 apr_getopt_t *os, 843 const apr_array_header_t *known_targets, 844 apr_pool_t *pool) 845{ 846 int i; 847 svn_error_t *err = SVN_NO_ERROR; 848 apr_array_header_t *input_targets = 849 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 850 apr_array_header_t *output_targets = 851 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 852 853 /* Step 1: create a master array of targets that are in UTF-8 854 encoding, and come from concatenating the targets left by apr_getopt, 855 plus any extra targets (e.g., from the --targets switch.) */ 856 857 for (; os->ind < os->argc; os->ind++) 858 { 859 /* The apr_getopt targets are still in native encoding. */ 860 const char *raw_target = os->argv[os->ind]; 861 SVN_ERR(svn_utf_cstring_to_utf8 862 ((const char **) apr_array_push(input_targets), 863 raw_target, pool)); 864 } 865 866 if (known_targets) 867 { 868 for (i = 0; i < known_targets->nelts; i++) 869 { 870 /* The --targets array have already been converted to UTF-8, 871 because we needed to split up the list with svn_cstring_split. */ 872 const char *utf8_target = APR_ARRAY_IDX(known_targets, 873 i, const char *); 874 APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; 875 } 876 } 877 878 /* Step 2: process each target. */ 879 880 for (i = 0; i < input_targets->nelts; i++) 881 { 882 const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *); 883 const char *true_target; 884 const char *target; /* after all processing is finished */ 885 const char *peg_rev; 886 887 /* 888 * This is needed so that the target can be properly canonicalized, 889 * otherwise the canonicalization does not treat a ".@BASE" as a "." 890 * with a BASE peg revision, and it is not canonicalized to "@BASE". 891 * If any peg revision exists, it is appended to the final 892 * canonicalized path or URL. Do not use svn_opt_parse_path() 893 * because the resulting peg revision is a structure that would have 894 * to be converted back into a string. Converting from a string date 895 * to the apr_time_t field in the svn_opt_revision_value_t and back to 896 * a string would not necessarily preserve the exact bytes of the 897 * input date, so its easier just to keep it in string form. 898 */ 899 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, 900 utf8_target, pool)); 901 902 /* URLs and wc-paths get treated differently. */ 903 if (svn_path_is_url(true_target)) 904 { 905 SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target, 906 pool)); 907 } 908 else /* not a url, so treat as a path */ 909 { 910 const char *base_name; 911 912 SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target, 913 pool)); 914 915 /* If the target has the same name as a Subversion 916 working copy administrative dir, skip it. */ 917 base_name = svn_dirent_basename(true_target, pool); 918 919 /* FIXME: 920 The canonical list of administrative directory names is 921 maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir(). 922 That list can't be used here, because that use would 923 create a circular dependency between libsvn_wc and 924 libsvn_subr. Make sure changes to the lists are always 925 synchronized! */ 926 if (0 == strcmp(base_name, ".svn") 927 || 0 == strcmp(base_name, "_svn")) 928 { 929 err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, 930 err, _("'%s' ends in a reserved name"), 931 utf8_target); 932 continue; 933 } 934 } 935 936 target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL); 937 938 APR_ARRAY_PUSH(output_targets, const char *) = target; 939 } 940 941 942 /* kff todo: need to remove redundancies from targets before 943 passing it to the cmd_func. */ 944 945 *targets_p = output_targets; 946 947 return err; 948} 949 950svn_error_t * 951svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec, 952 apr_pool_t *pool) 953{ 954 const char *sep, *propname; 955 svn_string_t *propval; 956 957 if (! *revprop_spec) 958 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 959 _("Revision property pair is empty")); 960 961 if (! *revprop_table_p) 962 *revprop_table_p = apr_hash_make(pool); 963 964 sep = strchr(revprop_spec, '='); 965 if (sep) 966 { 967 propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec); 968 SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool)); 969 propval = svn_string_create(sep + 1, pool); 970 } 971 else 972 { 973 SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool)); 974 propval = svn_string_create_empty(pool); 975 } 976 977 if (!svn_prop_name_is_valid(propname)) 978 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 979 _("'%s' is not a valid Subversion property name"), 980 propname); 981 982 svn_hash_sets(*revprop_table_p, propname, propval); 983 984 return SVN_NO_ERROR; 985} 986 987svn_error_t * 988svn_opt__split_arg_at_peg_revision(const char **true_target, 989 const char **peg_revision, 990 const char *utf8_target, 991 apr_pool_t *pool) 992{ 993 const char *peg_start = NULL; /* pointer to the peg revision, if any */ 994 const char *ptr; 995 996 for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target; 997 --ptr) 998 { 999 /* If we hit a path separator, stop looking. This is OK 1000 only because our revision specifiers can't contain '/'. */ 1001 if (*ptr == '/') 1002 break; 1003 1004 if (*ptr == '@') 1005 { 1006 peg_start = ptr; 1007 break; 1008 } 1009 } 1010 1011 if (peg_start) 1012 { 1013 /* Error out if target is the empty string. */ 1014 if (ptr == utf8_target) 1015 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, 1016 _("'%s' is just a peg revision. " 1017 "Maybe try '%s@' instead?"), 1018 utf8_target, utf8_target); 1019 1020 *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target); 1021 if (peg_revision) 1022 *peg_revision = apr_pstrdup(pool, peg_start); 1023 } 1024 else 1025 { 1026 *true_target = utf8_target; 1027 if (peg_revision) 1028 *peg_revision = ""; 1029 } 1030 1031 return SVN_NO_ERROR; 1032} 1033 1034svn_error_t * 1035svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in, 1036 apr_pool_t *pool) 1037{ 1038 const char *target; 1039 1040 /* Convert to URI. */ 1041 target = svn_path_uri_from_iri(url_in, pool); 1042 /* Auto-escape some ASCII characters. */ 1043 target = svn_path_uri_autoescape(target, pool); 1044 1045#if '/' != SVN_PATH_LOCAL_SEPARATOR 1046 /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */ 1047 if (strchr(target, SVN_PATH_LOCAL_SEPARATOR)) 1048 { 1049 char *p = apr_pstrdup(pool, target); 1050 target = p; 1051 1052 /* Convert all local-style separators to the canonical ones. */ 1053 for (; *p != '\0'; ++p) 1054 if (*p == SVN_PATH_LOCAL_SEPARATOR) 1055 *p = '/'; 1056 } 1057#endif 1058 1059 /* Verify that no backpaths are present in the URL. */ 1060 if (svn_path_is_backpath_present(target)) 1061 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1062 _("URL '%s' contains a '..' element"), 1063 target); 1064 1065 /* Strip any trailing '/' and collapse other redundant elements. */ 1066 target = svn_uri_canonicalize(target, pool); 1067 1068 *url_out = target; 1069 return SVN_NO_ERROR; 1070} 1071 1072svn_error_t * 1073svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in, 1074 apr_pool_t *pool) 1075{ 1076 const char *apr_target; 1077 char *truenamed_target; /* APR-encoded */ 1078 apr_status_t apr_err; 1079 1080 /* canonicalize case, and change all separators to '/'. */ 1081 SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); 1082 apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, 1083 APR_FILEPATH_TRUENAME, pool); 1084 1085 if (!apr_err) 1086 /* We have a canonicalized APR-encoded target now. */ 1087 apr_target = truenamed_target; 1088 else if (APR_STATUS_IS_ENOENT(apr_err)) 1089 /* It's okay for the file to not exist, that just means we 1090 have to accept the case given to the client. We'll use 1091 the original APR-encoded target. */ 1092 ; 1093 else 1094 return svn_error_createf(apr_err, NULL, 1095 _("Error resolving case of '%s'"), 1096 svn_dirent_local_style(path_in, pool)); 1097 1098 /* convert back to UTF-8. */ 1099 SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); 1100 *path_out = svn_dirent_canonicalize(*path_out, pool); 1101 1102 return SVN_NO_ERROR; 1103} 1104 1105 1106svn_error_t * 1107svn_opt__print_version_info(const char *pgm_name, 1108 const char *footer, 1109 const svn_version_extended_t *info, 1110 svn_boolean_t quiet, 1111 svn_boolean_t verbose, 1112 apr_pool_t *pool) 1113{ 1114 if (quiet) 1115 return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER); 1116 1117 SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n" 1118 " compiled on %s\n\n"), 1119 pgm_name, SVN_VERSION, 1120 svn_version_ext_build_host(info))); 1121 SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info))); 1122 1123 if (footer) 1124 { 1125 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer)); 1126 } 1127 1128 if (verbose) 1129 { 1130 const apr_array_header_t *libs; 1131 1132 SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool)); 1133 SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), 1134 svn_version_ext_runtime_host(info))); 1135 if (svn_version_ext_runtime_osname(info)) 1136 { 1137 SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"), 1138 svn_version_ext_runtime_osname(info))); 1139 } 1140 1141 libs = svn_version_ext_linked_libs(info); 1142 if (libs && libs->nelts) 1143 { 1144 const svn_version_ext_linked_lib_t *lib; 1145 int i; 1146 1147 SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"), 1148 stdout, pool)); 1149 for (i = 0; i < libs->nelts; ++i) 1150 { 1151 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t); 1152 if (lib->runtime_version) 1153 SVN_ERR(svn_cmdline_printf(pool, 1154 " - %s %s (compiled with %s)\n", 1155 lib->name, 1156 lib->runtime_version, 1157 lib->compiled_version)); 1158 else 1159 SVN_ERR(svn_cmdline_printf(pool, 1160 " - %s %s (static)\n", 1161 lib->name, 1162 lib->compiled_version)); 1163 } 1164 } 1165 1166 libs = svn_version_ext_loaded_libs(info); 1167 if (libs && libs->nelts) 1168 { 1169 const svn_version_ext_loaded_lib_t *lib; 1170 int i; 1171 1172 SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"), 1173 stdout, pool)); 1174 for (i = 0; i < libs->nelts; ++i) 1175 { 1176 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t); 1177 if (lib->version) 1178 SVN_ERR(svn_cmdline_printf(pool, 1179 " - %s (%s)\n", 1180 lib->name, lib->version)); 1181 else 1182 SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name)); 1183 } 1184 } 1185 } 1186 1187 return SVN_NO_ERROR; 1188} 1189 1190svn_error_t * 1191svn_opt_print_help4(apr_getopt_t *os, 1192 const char *pgm_name, 1193 svn_boolean_t print_version, 1194 svn_boolean_t quiet, 1195 svn_boolean_t verbose, 1196 const char *version_footer, 1197 const char *header, 1198 const svn_opt_subcommand_desc2_t *cmd_table, 1199 const apr_getopt_option_t *option_table, 1200 const int *global_options, 1201 const char *footer, 1202 apr_pool_t *pool) 1203{ 1204 apr_array_header_t *targets = NULL; 1205 1206 if (os) 1207 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); 1208 1209 if (os && targets->nelts) /* help on subcommand(s) requested */ 1210 { 1211 int i; 1212 1213 for (i = 0; i < targets->nelts; i++) 1214 { 1215 svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *), 1216 cmd_table, option_table, 1217 global_options, pool); 1218 } 1219 } 1220 else if (print_version) /* just --version */ 1221 { 1222 SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, 1223 svn_version_extended(verbose, pool), 1224 quiet, verbose, pool)); 1225 } 1226 else if (os && !targets->nelts) /* `-h', `--help', or `help' */ 1227 svn_opt_print_generic_help2(header, 1228 cmd_table, 1229 option_table, 1230 footer, 1231 pool, 1232 stdout); 1233 else /* unknown option or cmd */ 1234 SVN_ERR(svn_cmdline_fprintf(stderr, pool, 1235 _("Type '%s help' for usage.\n"), pgm_name)); 1236 1237 return SVN_NO_ERROR; 1238} 1239