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"), SVN_VA_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 /* 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 */ 447static int 448revision_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. */ 477static 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 550int 551svn_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 576int 577svn_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 594svn_error_t * 595svn_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 622const char * 623svn_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 650svn_opt_revision_range_t * 651svn_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 662svn_opt_revision_range_t * 663svn_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. */ 683static void 684array_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 698void 699svn_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 708svn_error_t * 709svn_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 732svn_error_t * 733svn_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 754svn_error_t * 755svn_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 delimiters. */ 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. */ 842svn_error_t * 843svn_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, SVN_VA_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 952svn_error_t * 953svn_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 989svn_error_t * 990svn_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 *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target); 1016 if (peg_revision) 1017 *peg_revision = apr_pstrdup(pool, peg_start); 1018 } 1019 else 1020 { 1021 *true_target = utf8_target; 1022 if (peg_revision) 1023 *peg_revision = ""; 1024 } 1025 1026 return SVN_NO_ERROR; 1027} 1028 1029svn_error_t * 1030svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in, 1031 apr_pool_t *pool) 1032{ 1033 const char *target; 1034 1035 /* Convert to URI. */ 1036 target = svn_path_uri_from_iri(url_in, pool); 1037 /* Auto-escape some ASCII characters. */ 1038 target = svn_path_uri_autoescape(target, pool); 1039 1040#if '/' != SVN_PATH_LOCAL_SEPARATOR 1041 /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */ 1042 if (strchr(target, SVN_PATH_LOCAL_SEPARATOR)) 1043 { 1044 char *p = apr_pstrdup(pool, target); 1045 target = p; 1046 1047 /* Convert all local-style separators to the canonical ones. */ 1048 for (; *p != '\0'; ++p) 1049 if (*p == SVN_PATH_LOCAL_SEPARATOR) 1050 *p = '/'; 1051 } 1052#endif 1053 1054 /* Verify that no backpaths are present in the URL. */ 1055 if (svn_path_is_backpath_present(target)) 1056 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1057 _("URL '%s' contains a '..' element"), 1058 target); 1059 1060 /* Strip any trailing '/' and collapse other redundant elements. */ 1061 target = svn_uri_canonicalize(target, pool); 1062 1063 *url_out = target; 1064 return SVN_NO_ERROR; 1065} 1066 1067svn_error_t * 1068svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in, 1069 apr_pool_t *pool) 1070{ 1071 const char *apr_target; 1072 char *truenamed_target; /* APR-encoded */ 1073 apr_status_t apr_err; 1074 1075 /* canonicalize case, and change all separators to '/'. */ 1076 SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); 1077 apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, 1078 APR_FILEPATH_TRUENAME, pool); 1079 1080 if (!apr_err) 1081 /* We have a canonicalized APR-encoded target now. */ 1082 apr_target = truenamed_target; 1083 else if (APR_STATUS_IS_ENOENT(apr_err)) 1084 /* It's okay for the file to not exist, that just means we 1085 have to accept the case given to the client. We'll use 1086 the original APR-encoded target. */ 1087 ; 1088 else 1089 return svn_error_createf(apr_err, NULL, 1090 _("Error resolving case of '%s'"), 1091 svn_dirent_local_style(path_in, pool)); 1092 1093 /* convert back to UTF-8. */ 1094 SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); 1095 *path_out = svn_dirent_canonicalize(*path_out, pool); 1096 1097 return SVN_NO_ERROR; 1098} 1099 1100 1101svn_error_t * 1102svn_opt__print_version_info(const char *pgm_name, 1103 const char *footer, 1104 const svn_version_extended_t *info, 1105 svn_boolean_t quiet, 1106 svn_boolean_t verbose, 1107 apr_pool_t *pool) 1108{ 1109 if (quiet) 1110 return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER); 1111 1112 SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n" 1113 " compiled on %s\n\n"), 1114 pgm_name, SVN_VERSION, 1115 svn_version_ext_build_host(info))); 1116 SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info))); 1117 1118 if (footer) 1119 { 1120 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer)); 1121 } 1122 1123 if (verbose) 1124 { 1125 const apr_array_header_t *libs; 1126 1127 SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool)); 1128 SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), 1129 svn_version_ext_runtime_host(info))); 1130 if (svn_version_ext_runtime_osname(info)) 1131 { 1132 SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"), 1133 svn_version_ext_runtime_osname(info))); 1134 } 1135 1136 libs = svn_version_ext_linked_libs(info); 1137 if (libs && libs->nelts) 1138 { 1139 const svn_version_ext_linked_lib_t *lib; 1140 int i; 1141 1142 SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"), 1143 stdout, pool)); 1144 for (i = 0; i < libs->nelts; ++i) 1145 { 1146 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t); 1147 if (lib->runtime_version) 1148 SVN_ERR(svn_cmdline_printf(pool, 1149 " - %s %s (compiled with %s)\n", 1150 lib->name, 1151 lib->runtime_version, 1152 lib->compiled_version)); 1153 else 1154 SVN_ERR(svn_cmdline_printf(pool, 1155 " - %s %s (static)\n", 1156 lib->name, 1157 lib->compiled_version)); 1158 } 1159 } 1160 1161 libs = svn_version_ext_loaded_libs(info); 1162 if (libs && libs->nelts) 1163 { 1164 const svn_version_ext_loaded_lib_t *lib; 1165 int i; 1166 1167 SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"), 1168 stdout, pool)); 1169 for (i = 0; i < libs->nelts; ++i) 1170 { 1171 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t); 1172 if (lib->version) 1173 SVN_ERR(svn_cmdline_printf(pool, 1174 " - %s (%s)\n", 1175 lib->name, lib->version)); 1176 else 1177 SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name)); 1178 } 1179 } 1180 } 1181 1182 return SVN_NO_ERROR; 1183} 1184 1185svn_error_t * 1186svn_opt_print_help4(apr_getopt_t *os, 1187 const char *pgm_name, 1188 svn_boolean_t print_version, 1189 svn_boolean_t quiet, 1190 svn_boolean_t verbose, 1191 const char *version_footer, 1192 const char *header, 1193 const svn_opt_subcommand_desc2_t *cmd_table, 1194 const apr_getopt_option_t *option_table, 1195 const int *global_options, 1196 const char *footer, 1197 apr_pool_t *pool) 1198{ 1199 apr_array_header_t *targets = NULL; 1200 1201 if (os) 1202 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); 1203 1204 if (os && targets->nelts) /* help on subcommand(s) requested */ 1205 { 1206 int i; 1207 1208 for (i = 0; i < targets->nelts; i++) 1209 { 1210 svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *), 1211 cmd_table, option_table, 1212 global_options, pool); 1213 } 1214 } 1215 else if (print_version) /* just --version */ 1216 { 1217 SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, 1218 svn_version_extended(verbose, pool), 1219 quiet, verbose, pool)); 1220 } 1221 else if (os && !targets->nelts) /* `-h', `--help', or `help' */ 1222 svn_opt_print_generic_help2(header, 1223 cmd_table, 1224 option_table, 1225 footer, 1226 pool, 1227 stdout); 1228 else /* unknown option or cmd */ 1229 SVN_ERR(svn_cmdline_fprintf(stderr, pool, 1230 _("Type '%s help' for usage.\n"), pgm_name)); 1231 1232 return SVN_NO_ERROR; 1233} 1234