opt.c revision 269847
1245803Stheraven/* 2245803Stheraven * opt.c : option and argument parsing for Subversion command lines 3245803Stheraven * 4245803Stheraven * ==================================================================== 5245803Stheraven * Licensed to the Apache Software Foundation (ASF) under one 6245803Stheraven * or more contributor license agreements. See the NOTICE file 7245803Stheraven * distributed with this work for additional information 8245803Stheraven * regarding copyright ownership. The ASF licenses this file 9245803Stheraven * to you under the Apache License, Version 2.0 (the 10245803Stheraven * "License"); you may not use this file except in compliance 11245803Stheraven * with the License. You may obtain a copy of the License at 12245803Stheraven * 13245803Stheraven * http://www.apache.org/licenses/LICENSE-2.0 14245803Stheraven * 15245803Stheraven * Unless required by applicable law or agreed to in writing, 16245803Stheraven * software distributed under the License is distributed on an 17245803Stheraven * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18245803Stheraven * KIND, either express or implied. See the License for the 19245803Stheraven * specific language governing permissions and limitations 20245803Stheraven * under the License. 21245803Stheraven * ==================================================================== 22245803Stheraven */ 23245803Stheraven 24245803Stheraven 25245803Stheraven 26245803Stheraven#define APR_WANT_STRFUNC 27245803Stheraven#include <apr_want.h> 28245803Stheraven 29245803Stheraven#include <stdio.h> 30245803Stheraven#include <string.h> 31245803Stheraven#include <assert.h> 32245803Stheraven#include <apr_pools.h> 33245803Stheraven#include <apr_general.h> 34245839Stheraven#include <apr_lib.h> 35245803Stheraven#include <apr_file_info.h> 36245839Stheraven 37245839Stheraven#include "svn_hash.h" 38245803Stheraven#include "svn_cmdline.h" 39245839Stheraven#include "svn_version.h" 40245803Stheraven#include "svn_types.h" 41245803Stheraven#include "svn_opt.h" 42245803Stheraven#include "svn_error.h" 43245803Stheraven#include "svn_dirent_uri.h" 44245803Stheraven#include "svn_path.h" 45245803Stheraven#include "svn_utf.h" 46245803Stheraven#include "svn_time.h" 47245803Stheraven#include "svn_props.h" 48245803Stheraven#include "svn_ctype.h" 49245803Stheraven 50245803Stheraven#include "private/svn_opt_private.h" 51245803Stheraven 52245803Stheraven#include "opt.h" 53245803Stheraven#include "svn_private_config.h" 54245803Stheraven 55245803Stheraven 56245803Stheraven/*** Code. ***/ 57245803Stheraven 58245803Stheravenconst svn_opt_subcommand_desc2_t * 59245803Stheravensvn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table, 60245803Stheraven const char *cmd_name) 61245803Stheraven{ 62245803Stheraven int i = 0; 63245803Stheraven 64245803Stheraven if (cmd_name == NULL) 65245803Stheraven return NULL; 66245803Stheraven 67245803Stheraven while (table[i].name) { 68245803Stheraven int j; 69245803Stheraven if (strcmp(cmd_name, table[i].name) == 0) 70245803Stheraven return table + i; 71245803Stheraven for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++) 72245803Stheraven if (strcmp(cmd_name, table[i].aliases[j]) == 0) 73245803Stheraven return table + i; 74245803Stheraven 75245803Stheraven i++; 76245803Stheraven } 77245803Stheraven 78245803Stheraven /* If we get here, there was no matching subcommand name or alias. */ 79245803Stheraven return NULL; 80245803Stheraven} 81245803Stheraven 82245803Stheravenconst apr_getopt_option_t * 83245803Stheravensvn_opt_get_option_from_code2(int code, 84245803Stheraven const apr_getopt_option_t *option_table, 85245803Stheraven const svn_opt_subcommand_desc2_t *command, 86245803Stheraven apr_pool_t *pool) 87245803Stheraven{ 88245803Stheraven apr_size_t i; 89245803Stheraven 90245803Stheraven for (i = 0; option_table[i].optch; i++) 91245803Stheraven if (option_table[i].optch == code) 92245803Stheraven { 93245803Stheraven if (command) 94245803Stheraven { 95245803Stheraven int j; 96245803Stheraven 97245803Stheraven for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) && 98245803Stheraven command->desc_overrides[j].optch); j++) 99245803Stheraven if (command->desc_overrides[j].optch == code) 100245803Stheraven { 101245803Stheraven apr_getopt_option_t *tmpopt = 102245803Stheraven apr_palloc(pool, sizeof(*tmpopt)); 103245803Stheraven *tmpopt = option_table[i]; 104245803Stheraven tmpopt->description = command->desc_overrides[j].desc; 105245803Stheraven return tmpopt; 106245803Stheraven } 107245803Stheraven } 108245803Stheraven return &(option_table[i]); 109245803Stheraven } 110245803Stheraven 111245803Stheraven return NULL; 112245803Stheraven} 113245803Stheraven 114245803Stheraven 115245803Stheravenconst apr_getopt_option_t * 116245803Stheravensvn_opt_get_option_from_code(int code, 117245803Stheraven const apr_getopt_option_t *option_table) 118245803Stheraven{ 119245803Stheraven apr_size_t i; 120245803Stheraven 121245803Stheraven for (i = 0; option_table[i].optch; i++) 122245803Stheraven if (option_table[i].optch == code) 123245803Stheraven return &(option_table[i]); 124245803Stheraven 125245803Stheraven return NULL; 126245803Stheraven} 127245803Stheraven 128245803Stheraven 129245803Stheraven/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second 130245803Stheraven * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that 131245803Stheraven * second name, else set it to NULL. */ 132245803Stheravenstatic const apr_getopt_option_t * 133245803Stheravenget_option_from_code(const char **long_alias, 134245803Stheraven int code, 135245803Stheraven const apr_getopt_option_t *option_table, 136245803Stheraven const svn_opt_subcommand_desc2_t *command, 137245803Stheraven apr_pool_t *pool) 138245803Stheraven{ 139245803Stheraven const apr_getopt_option_t *i; 140245803Stheraven const apr_getopt_option_t *opt 141245803Stheraven = svn_opt_get_option_from_code2(code, option_table, command, pool); 142245803Stheraven 143245803Stheraven /* Find a long alias in the table, if there is one. */ 144245803Stheraven *long_alias = NULL; 145245803Stheraven for (i = option_table; i->optch; i++) 146245803Stheraven { 147245803Stheraven if (i->optch == code && i->name != opt->name) 148245803Stheraven { 149245803Stheraven *long_alias = i->name; 150245803Stheraven break; 151245803Stheraven } 152245803Stheraven } 153245803Stheraven 154245803Stheraven return opt; 155245803Stheraven} 156245803Stheraven 157245803Stheraven 158245803Stheraven/* Print an option OPT nicely into a STRING allocated in POOL. 159245803Stheraven * If OPT has a single-character short form, then print OPT->name (if not 160245803Stheraven * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias. 161245803Stheraven * If DOC is set, include the generic documentation string of OPT, 162245803Stheraven * localized to the current locale if a translation is available. 163245803Stheraven */ 164245803Stheravenstatic void 165245803Stheravenformat_option(const char **string, 166245803Stheraven const apr_getopt_option_t *opt, 167245803Stheraven const char *long_alias, 168245803Stheraven svn_boolean_t doc, 169245803Stheraven apr_pool_t *pool) 170245803Stheraven{ 171245803Stheraven char *opts; 172245803Stheraven 173245803Stheraven if (opt == NULL) 174245803Stheraven { 175245803Stheraven *string = "?"; 176245803Stheraven return; 177245803Stheraven } 178245803Stheraven 179245803Stheraven /* We have a valid option which may or may not have a "short 180245803Stheraven name" (a single-character alias for the long option). */ 181245803Stheraven if (opt->optch <= 255) 182245803Stheraven opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name); 183245803Stheraven else if (long_alias) 184245803Stheraven opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias); 185245803Stheraven else 186245803Stheraven opts = apr_psprintf(pool, "--%s", opt->name); 187245803Stheraven 188245803Stheraven if (opt->has_arg) 189245803Stheraven opts = apr_pstrcat(pool, opts, _(" ARG"), (char *)NULL); 190245803Stheraven 191245803Stheraven if (doc) 192245803Stheraven opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description)); 193245803Stheraven 194245803Stheraven *string = opts; 195245803Stheraven} 196245803Stheraven 197245803Stheravenvoid 198245803Stheravensvn_opt_format_option(const char **string, 199245803Stheraven const apr_getopt_option_t *opt, 200245803Stheraven svn_boolean_t doc, 201245803Stheraven apr_pool_t *pool) 202245803Stheraven{ 203245803Stheraven format_option(string, opt, NULL, doc, pool); 204245803Stheraven} 205245803Stheraven 206245803Stheraven 207245803Stheravensvn_boolean_t 208245803Stheravensvn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command, 209245803Stheraven int option_code, 210245803Stheraven const int *global_options) 211245803Stheraven{ 212245803Stheraven apr_size_t i; 213245803Stheraven 214245803Stheraven for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) 215245803Stheraven if (command->valid_options[i] == option_code) 216245803Stheraven return TRUE; 217245803Stheraven 218245803Stheraven if (global_options) 219245803Stheraven for (i = 0; global_options[i]; i++) 220245803Stheraven if (global_options[i] == option_code) 221245803Stheraven return TRUE; 222245803Stheraven 223245803Stheraven return FALSE; 224245803Stheraven} 225245803Stheraven 226245803Stheravensvn_boolean_t 227245803Stheravensvn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command, 228245803Stheraven int option_code) 229245803Stheraven{ 230245803Stheraven return svn_opt_subcommand_takes_option3(command, 231245803Stheraven option_code, 232245803Stheraven NULL); 233245803Stheraven} 234245803Stheraven 235245803Stheraven 236245803Stheravensvn_boolean_t 237245803Stheravensvn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command, 238245803Stheraven int option_code) 239245803Stheraven{ 240245803Stheraven apr_size_t i; 241245803Stheraven 242245803Stheraven for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) 243245803Stheraven if (command->valid_options[i] == option_code) 244245803Stheraven return TRUE; 245245803Stheraven 246245803Stheraven return FALSE; 247245803Stheraven} 248245803Stheraven 249245803Stheraven 250245803Stheraven/* Print the canonical command name for CMD, and all its aliases, to 251245803Stheraven STREAM. If HELP is set, print CMD's help string too, in which case 252245803Stheraven obtain option usage from OPTIONS_TABLE. */ 253245803Stheravenstatic svn_error_t * 254245803Stheravenprint_command_info2(const svn_opt_subcommand_desc2_t *cmd, 255245803Stheraven const apr_getopt_option_t *options_table, 256245803Stheraven const int *global_options, 257245803Stheraven svn_boolean_t help, 258245803Stheraven apr_pool_t *pool, 259245803Stheraven FILE *stream) 260245803Stheraven{ 261245803Stheraven svn_boolean_t first_time; 262245803Stheraven apr_size_t i; 263245803Stheraven 264245803Stheraven /* Print the canonical command name. */ 265245803Stheraven SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool)); 266245803Stheraven 267245803Stheraven /* Print the list of aliases. */ 268245803Stheraven first_time = TRUE; 269245803Stheraven for (i = 0; i < SVN_OPT_MAX_ALIASES; i++) 270245803Stheraven { 271245803Stheraven if (cmd->aliases[i] == NULL) 272245803Stheraven break; 273245803Stheraven 274245803Stheraven if (first_time) { 275245803Stheraven SVN_ERR(svn_cmdline_fputs(" (", stream, pool)); 276245803Stheraven first_time = FALSE; 277245803Stheraven } 278245803Stheraven else 279245803Stheraven SVN_ERR(svn_cmdline_fputs(", ", stream, pool)); 280245803Stheraven 281245803Stheraven SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool)); 282245803Stheraven } 283245803Stheraven 284245803Stheraven if (! first_time) 285245803Stheraven SVN_ERR(svn_cmdline_fputs(")", stream, pool)); 286245803Stheraven 287245803Stheraven if (help) 288245803Stheraven { 289245803Stheraven const apr_getopt_option_t *option; 290245803Stheraven const char *long_alias; 291245803Stheraven svn_boolean_t have_options = FALSE; 292245803Stheraven 293245803Stheraven SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help))); 294245803Stheraven 295245803Stheraven /* Loop over all valid option codes attached to the subcommand */ 296245803Stheraven for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) 297245803Stheraven { 298245803Stheraven if (cmd->valid_options[i]) 299245803Stheraven { 300245803Stheraven if (!have_options) 301245803Stheraven { 302245803Stheraven SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"), 303245803Stheraven stream, pool)); 304245803Stheraven have_options = TRUE; 305245803Stheraven } 306245803Stheraven 307245803Stheraven /* convert each option code into an option */ 308245803Stheraven option = get_option_from_code(&long_alias, cmd->valid_options[i], 309245803Stheraven options_table, cmd, pool); 310245803Stheraven 311245803Stheraven /* print the option's docstring */ 312245803Stheraven 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 delimeters. */ 787 size_t rev_len = strlen(rev_str); 788 if (rev_len > 6 789 && rev_str[0] == '%' 790 && rev_str[1] == '7' 791 && (rev_str[2] == 'B' 792 || rev_str[2] == 'b') 793 && rev_str[rev_len-3] == '%' 794 && rev_str[rev_len-2] == '7' 795 && (rev_str[rev_len-1] == 'D' 796 || rev_str[rev_len-1] == 'd')) 797 { 798 rev_str = svn_path_uri_decode(rev_str, pool); 799 } 800 } 801 ret = svn_opt_parse_revision(&start_revision, 802 &end_revision, 803 rev_str, pool); 804 } 805 806 if (ret || end_revision.kind != svn_opt_revision_unspecified) 807 { 808 /* If an svn+ssh URL was used and it contains only one @, 809 * provide an error message that presents a possible solution 810 * to the parsing error (issue #2349). */ 811 if (strncmp(path, "svn+ssh://", 10) == 0) 812 { 813 const char *at; 814 815 at = strchr(path, '@'); 816 if (at && strrchr(path, '@') == at) 817 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 818 _("Syntax error parsing peg revision " 819 "'%s'; did you mean '%s@'?"), 820 &peg_rev[1], path); 821 } 822 823 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 824 _("Syntax error parsing peg revision '%s'"), 825 &peg_rev[1]); 826 } 827 rev->kind = start_revision.kind; 828 rev->value = start_revision.value; 829 } 830 else 831 { 832 /* Didn't find a peg revision. */ 833 rev->kind = svn_opt_revision_unspecified; 834 } 835 836 return SVN_NO_ERROR; 837} 838 839 840/* Note: This is substantially copied into svn_client_args_to_target_array() in 841 * order to move to libsvn_client while maintaining backward compatibility. */ 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, (char *)NULL); 939 940 APR_ARRAY_PUSH(output_targets, const char *) = target; 941 } 942 943 944 /* kff todo: need to remove redundancies from targets before 945 passing it to the cmd_func. */ 946 947 *targets_p = output_targets; 948 949 return err; 950} 951 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 /* Error out if target is the empty string. */ 1016 if (ptr == utf8_target) 1017 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, 1018 _("'%s' is just a peg revision. " 1019 "Maybe try '%s@' instead?"), 1020 utf8_target, utf8_target); 1021 1022 *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target); 1023 if (peg_revision) 1024 *peg_revision = apr_pstrdup(pool, peg_start); 1025 } 1026 else 1027 { 1028 *true_target = utf8_target; 1029 if (peg_revision) 1030 *peg_revision = ""; 1031 } 1032 1033 return SVN_NO_ERROR; 1034} 1035 1036svn_error_t * 1037svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in, 1038 apr_pool_t *pool) 1039{ 1040 const char *target; 1041 1042 /* Convert to URI. */ 1043 target = svn_path_uri_from_iri(url_in, pool); 1044 /* Auto-escape some ASCII characters. */ 1045 target = svn_path_uri_autoescape(target, pool); 1046 1047#if '/' != SVN_PATH_LOCAL_SEPARATOR 1048 /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */ 1049 if (strchr(target, SVN_PATH_LOCAL_SEPARATOR)) 1050 { 1051 char *p = apr_pstrdup(pool, target); 1052 target = p; 1053 1054 /* Convert all local-style separators to the canonical ones. */ 1055 for (; *p != '\0'; ++p) 1056 if (*p == SVN_PATH_LOCAL_SEPARATOR) 1057 *p = '/'; 1058 } 1059#endif 1060 1061 /* Verify that no backpaths are present in the URL. */ 1062 if (svn_path_is_backpath_present(target)) 1063 return svn_error_createf(SVN_ERR_BAD_URL, 0, 1064 _("URL '%s' contains a '..' element"), 1065 target); 1066 1067 /* Strip any trailing '/' and collapse other redundant elements. */ 1068 target = svn_uri_canonicalize(target, pool); 1069 1070 *url_out = target; 1071 return SVN_NO_ERROR; 1072} 1073 1074svn_error_t * 1075svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in, 1076 apr_pool_t *pool) 1077{ 1078 const char *apr_target; 1079 char *truenamed_target; /* APR-encoded */ 1080 apr_status_t apr_err; 1081 1082 /* canonicalize case, and change all separators to '/'. */ 1083 SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); 1084 apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, 1085 APR_FILEPATH_TRUENAME, pool); 1086 1087 if (!apr_err) 1088 /* We have a canonicalized APR-encoded target now. */ 1089 apr_target = truenamed_target; 1090 else if (APR_STATUS_IS_ENOENT(apr_err)) 1091 /* It's okay for the file to not exist, that just means we 1092 have to accept the case given to the client. We'll use 1093 the original APR-encoded target. */ 1094 ; 1095 else 1096 return svn_error_createf(apr_err, NULL, 1097 _("Error resolving case of '%s'"), 1098 svn_dirent_local_style(path_in, pool)); 1099 1100 /* convert back to UTF-8. */ 1101 SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); 1102 *path_out = svn_dirent_canonicalize(*path_out, pool); 1103 1104 return SVN_NO_ERROR; 1105} 1106 1107 1108svn_error_t * 1109svn_opt__print_version_info(const char *pgm_name, 1110 const char *footer, 1111 const svn_version_extended_t *info, 1112 svn_boolean_t quiet, 1113 svn_boolean_t verbose, 1114 apr_pool_t *pool) 1115{ 1116 if (quiet) 1117 return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER); 1118 1119 SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n" 1120 " compiled on %s\n\n"), 1121 pgm_name, SVN_VERSION, 1122 svn_version_ext_build_host(info))); 1123 SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info))); 1124 1125 if (footer) 1126 { 1127 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer)); 1128 } 1129 1130 if (verbose) 1131 { 1132 const apr_array_header_t *libs; 1133 1134 SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool)); 1135 SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), 1136 svn_version_ext_runtime_host(info))); 1137 if (svn_version_ext_runtime_osname(info)) 1138 { 1139 SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"), 1140 svn_version_ext_runtime_osname(info))); 1141 } 1142 1143 libs = svn_version_ext_linked_libs(info); 1144 if (libs && libs->nelts) 1145 { 1146 const svn_version_ext_linked_lib_t *lib; 1147 int i; 1148 1149 SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"), 1150 stdout, pool)); 1151 for (i = 0; i < libs->nelts; ++i) 1152 { 1153 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t); 1154 if (lib->runtime_version) 1155 SVN_ERR(svn_cmdline_printf(pool, 1156 " - %s %s (compiled with %s)\n", 1157 lib->name, 1158 lib->runtime_version, 1159 lib->compiled_version)); 1160 else 1161 SVN_ERR(svn_cmdline_printf(pool, 1162 " - %s %s (static)\n", 1163 lib->name, 1164 lib->compiled_version)); 1165 } 1166 } 1167 1168 libs = svn_version_ext_loaded_libs(info); 1169 if (libs && libs->nelts) 1170 { 1171 const svn_version_ext_loaded_lib_t *lib; 1172 int i; 1173 1174 SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"), 1175 stdout, pool)); 1176 for (i = 0; i < libs->nelts; ++i) 1177 { 1178 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t); 1179 if (lib->version) 1180 SVN_ERR(svn_cmdline_printf(pool, 1181 " - %s (%s)\n", 1182 lib->name, lib->version)); 1183 else 1184 SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name)); 1185 } 1186 } 1187 } 1188 1189 return SVN_NO_ERROR; 1190} 1191 1192svn_error_t * 1193svn_opt_print_help4(apr_getopt_t *os, 1194 const char *pgm_name, 1195 svn_boolean_t print_version, 1196 svn_boolean_t quiet, 1197 svn_boolean_t verbose, 1198 const char *version_footer, 1199 const char *header, 1200 const svn_opt_subcommand_desc2_t *cmd_table, 1201 const apr_getopt_option_t *option_table, 1202 const int *global_options, 1203 const char *footer, 1204 apr_pool_t *pool) 1205{ 1206 apr_array_header_t *targets = NULL; 1207 1208 if (os) 1209 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); 1210 1211 if (os && targets->nelts) /* help on subcommand(s) requested */ 1212 { 1213 int i; 1214 1215 for (i = 0; i < targets->nelts; i++) 1216 { 1217 svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *), 1218 cmd_table, option_table, 1219 global_options, pool); 1220 } 1221 } 1222 else if (print_version) /* just --version */ 1223 { 1224 SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, 1225 svn_version_extended(verbose, pool), 1226 quiet, verbose, pool)); 1227 } 1228 else if (os && !targets->nelts) /* `-h', `--help', or `help' */ 1229 svn_opt_print_generic_help2(header, 1230 cmd_table, 1231 option_table, 1232 footer, 1233 pool, 1234 stdout); 1235 else /* unknown option or cmd */ 1236 SVN_ERR(svn_cmdline_fprintf(stderr, pool, 1237 _("Type '%s help' for usage.\n"), pgm_name)); 1238 1239 return SVN_NO_ERROR; 1240} 1241