1 /* 2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 3 * 4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5 * and others. 6 * 7 * Portions Copyright (c) 1992, Brian Berliner and Jeff Polk 8 * Portions Copyright (c) 1989-1992, Brian Berliner 9 * 10 * You may distribute under the terms of the GNU General Public License as 11 * specified in the README file that comes with the CVS source distribution. 12 * 13 * Administration ("cvs admin") 14 * 15 */ 16 17 #include "cvs.h" 18 #include <grp.h> 19 20 static Dtype admin_dirproc (void *callerdat, const char *dir, 21 const char *repos, const char *update_dir, 22 List *entries); 23 static int admin_fileproc (void *callerdat, struct file_info *finfo); 24 25 static const char *const admin_usage[] = 26 { 27 "Usage: %s %s [options] files...\n", 28 "\t-a users Append (comma-separated) user names to access list.\n", 29 "\t-A file Append another file's access list.\n", 30 "\t-b[rev] Set default branch (highest branch on trunk if omitted).\n", 31 "\t-c string Set comment leader.\n", 32 "\t-e[users] Remove (comma-separated) user names from access list\n", 33 "\t (all names if omitted).\n", 34 "\t-I Run interactively.\n", 35 "\t-k subst Set keyword substitution mode:\n", 36 "\t kv (Default) Substitute keyword and value.\n", 37 "\t kvl Substitute keyword, value, and locker (if any).\n", 38 "\t k Substitute keyword only.\n", 39 "\t o Preserve original string.\n", 40 "\t b Like o, but mark file as binary.\n", 41 "\t v Substitute value only.\n", 42 "\t-l[rev] Lock revision (latest revision on branch,\n", 43 "\t latest revision on trunk if omitted).\n", 44 "\t-L Set strict locking.\n", 45 "\t-m rev:msg Replace revision's log message.\n", 46 "\t-n tag[:[rev]] Tag branch or revision. If :rev is omitted,\n", 47 "\t delete the tag; if rev is omitted, tag the latest\n", 48 "\t revision on the default branch.\n", 49 "\t-N tag[:[rev]] Same as -n except override existing tag.\n", 50 "\t-o range Delete (outdate) specified range of revisions:\n", 51 "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n", 52 "\t rev1::rev2 Between rev1 and rev2, excluding rev1 and rev2.\n", 53 "\t rev: rev and following revisions on the same branch.\n", 54 "\t rev:: After rev on the same branch.\n", 55 "\t :rev rev and previous revisions on the same branch.\n", 56 "\t ::rev Before rev on the same branch.\n", 57 "\t rev Just rev.\n", 58 "\t-q Run quietly.\n", 59 "\t-s state[:rev] Set revision state (latest revision on branch,\n", 60 "\t latest revision on trunk if omitted).\n", 61 "\t-t[file] Get descriptive text from file (stdin if omitted).\n", 62 "\t-t-string Set descriptive text.\n", 63 "\t-u[rev] Unlock the revision (latest revision on branch,\n", 64 "\t latest revision on trunk if omitted).\n", 65 "\t-U Unset strict locking.\n", 66 "(Specify the --help global option for a list of other help options)\n", 67 NULL 68 }; 69 70 /* This structure is used to pass information through start_recursion. */ 71 struct admin_data 72 { 73 /* Set default branch (-b). It is "-b" followed by the value 74 given, or NULL if not specified, or merely "-b" if -b is 75 specified without a value. */ 76 char *branch; 77 78 /* Set comment leader (-c). It is "-c" followed by the value 79 given, or NULL if not specified. The comment leader is 80 relevant only for old versions of RCS, but we let people set it 81 anyway. */ 82 char *comment; 83 84 /* Set strict locking (-L). */ 85 int set_strict; 86 87 /* Set nonstrict locking (-U). */ 88 int set_nonstrict; 89 90 /* Delete revisions (-o). It is "-o" followed by the value specified. */ 91 char *delete_revs; 92 93 /* Keyword substitution mode (-k), e.g. "-kb". */ 94 char *kflag; 95 96 /* Description (-t). */ 97 char *desc; 98 99 /* Interactive (-I). Problematic with client/server. */ 100 int interactive; 101 102 /* This is the cheesy part. It is a vector with the options which 103 we don't deal with above (e.g. "-afoo" "-abar,baz"). In the future 104 this presumably will be replaced by other variables which break 105 out the data in a more convenient fashion. AV as well as each of 106 the strings it points to is malloc'd. */ 107 int ac; 108 char **av; 109 int av_alloc; 110 111 /* This contains a printable version of the command line used 112 * for logging 113 */ 114 char *cmdline; 115 }; 116 117 /* Add an argument. OPT is the option letter, e.g. 'a'. ARG is the 118 argument to that option, or NULL if omitted (whether NULL can actually 119 happen depends on whether the option was specified as optional to 120 getopt). */ 121 static void 122 arg_add (struct admin_data *dat, int opt, char *arg) 123 { 124 char *newelt = Xasprintf ("-%c%s", opt, arg ? arg : ""); 125 126 if (dat->av_alloc == 0) 127 { 128 dat->av_alloc = 1; 129 dat->av = xnmalloc (dat->av_alloc, sizeof (*dat->av)); 130 } 131 else if (dat->ac >= dat->av_alloc) 132 { 133 dat->av_alloc *= 2; 134 dat->av = xnrealloc (dat->av, dat->av_alloc, sizeof (*dat->av)); 135 } 136 dat->av[dat->ac++] = newelt; 137 } 138 139 140 141 /* 142 * callback proc to run a script when admin finishes. 143 */ 144 static int 145 postadmin_proc (const char *repository, const char *filter, void *closure) 146 { 147 char *cmdline; 148 const char *srepos = Short_Repository (repository); 149 150 TRACE (TRACE_FUNCTION, "postadmin_proc (%s, %s)", repository, filter); 151 152 /* %c = cvs_cmd_name 153 * %R = referrer 154 * %p = shortrepos 155 * %r = repository 156 */ 157 /* 158 * Cast any NULL arguments as appropriate pointers as this is an 159 * stdarg function and we need to be certain the caller gets what 160 * is expected. 161 */ 162 cmdline = format_cmdline ( 163 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS 164 false, srepos, 165 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */ 166 filter, 167 "c", "s", cvs_cmd_name, 168 #ifdef SERVER_SUPPORT 169 "R", "s", referrer ? referrer->original : "NONE", 170 #endif /* SERVER_SUPPORT */ 171 "p", "s", srepos, 172 "r", "s", current_parsed_root->directory, 173 (char *) NULL); 174 175 if (!cmdline || !strlen (cmdline)) 176 { 177 if (cmdline) free (cmdline); 178 error (0, 0, "postadmin proc resolved to the empty string!"); 179 return 1; 180 } 181 182 run_setup (cmdline); 183 184 free (cmdline); 185 186 /* FIXME - read the comment in verifymsg_proc() about why we use abs() 187 * below() and shouldn't. 188 */ 189 return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 190 RUN_NORMAL | RUN_SIGIGNORE)); 191 } 192 193 194 195 /* 196 * Call any postadmin procs. 197 */ 198 static int 199 admin_filesdoneproc (void *callerdat, int err, const char *repository, 200 const char *update_dir, List *entries) 201 { 202 TRACE (TRACE_FUNCTION, "admin_filesdoneproc (%d, %s, %s)", err, repository, 203 update_dir); 204 Parse_Info (CVSROOTADM_POSTADMIN, repository, postadmin_proc, PIOPT_ALL, 205 NULL); 206 207 return err; 208 } 209 210 211 static size_t 212 wescape (char *dst, const char *src) 213 { 214 const unsigned char *s = src; 215 char *d = dst; 216 for (; *s; s++) { 217 if (!isprint(*s) || isspace(*s) || *s == '|') { 218 *d++ = '\\'; 219 *d++ = ((*s >> 6) & 3) + '0'; 220 *d++ = ((*s >> 3) & 7) + '0'; 221 *d++ = ((*s >> 0) & 7) + '0'; 222 } else { 223 *d++ = *s; 224 } 225 } 226 *d = '\0'; 227 return d - dst; 228 } 229 230 static char * 231 makecmdline (int argc, char **argv) 232 { 233 size_t clen = 1024, wlen = 1024, len, cpos = 0, i; 234 char *cmd = xmalloc(clen); 235 char *word = xmalloc(wlen); 236 237 for (i = 0; i < argc; i++) { 238 char *arg = (strncmp(argv[i], "cvs ", 4) == 0) ? argv[i] + 4 : argv[i]; 239 len = strlen(arg); 240 if (len * 4 < wlen) { 241 wlen += len * 4; 242 word = xrealloc(word, wlen); 243 } 244 len = wescape(word, arg); 245 if (clen - cpos < len + 2) { 246 clen += len + 2; 247 cmd = xrealloc(cmd, clen); 248 } 249 memcpy(&cmd[cpos], word, len); 250 cpos += len; 251 cmd[cpos++] = ' '; 252 } 253 if (cpos != 0) 254 cmd[cpos - 1] = '\0'; 255 else 256 cmd[cpos] = '\0'; 257 free(word); 258 return cmd; 259 } 260 261 int 262 admin_group_member (void) 263 { 264 struct group *grp; 265 int i; 266 267 if (config->UserAdminGroup == NULL) 268 return 1; 269 270 if ((grp = getgrnam(CVS_ADMIN_GROUP)) == NULL) 271 return 0; 272 273 { 274 #ifdef HAVE_GETGROUPS 275 gid_t *grps; 276 int n; 277 278 /* get number of auxiliary groups */ 279 n = getgroups (0, NULL); 280 if (n < 0) 281 error (1, errno, "unable to get number of auxiliary groups"); 282 grps = (gid_t *) xmalloc((n + 1) * sizeof *grps); 283 n = getgroups (n, grps); 284 if (n < 0) 285 error (1, errno, "unable to get list of auxiliary groups"); 286 grps[n] = getgid(); 287 for (i = 0; i <= n; i++) 288 if (grps[i] == grp->gr_gid) break; 289 free (grps); 290 if (i > n) 291 return 0; 292 #else 293 char *me = getcaller(); 294 char **grnam; 295 296 for (grnam = grp->gr_mem; *grnam; grnam++) 297 if (strcmp (*grnam, me) == 0) break; 298 if (!*grnam && getgid() != grp->gr_gid) 299 return 0; 300 #endif 301 } 302 } 303 304 int 305 admin (int argc, char **argv) 306 { 307 int err; 308 struct admin_data admin_data; 309 int c; 310 int i; 311 bool only_allowed_options; 312 313 if (argc <= 1) 314 usage (admin_usage); 315 316 wrap_setup (); 317 318 memset (&admin_data, 0, sizeof admin_data); 319 admin_data.cmdline = makecmdline (argc, argv); 320 321 /* TODO: get rid of `-' switch notation in admin_data. For 322 example, admin_data->branch should be not `-bfoo' but simply `foo'. */ 323 324 optind = 0; 325 only_allowed_options = true; 326 while ((c = getopt (argc, argv, 327 "+ib::c:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1) 328 { 329 if (c != 'q' && !strchr (config->UserAdminOptions, c)) 330 only_allowed_options = false; 331 332 switch (c) 333 { 334 case 'i': 335 /* This has always been documented as useless in cvs.texinfo 336 and it really is--admin_fileproc silently does nothing 337 if vers->vn_user is NULL. */ 338 error (0, 0, "the -i option to admin is not supported"); 339 error (0, 0, "run add or import to create an RCS file"); 340 goto usage_error; 341 342 case 'b': 343 if (admin_data.branch != NULL) 344 { 345 error (0, 0, "duplicate 'b' option"); 346 goto usage_error; 347 } 348 if (optarg == NULL) 349 admin_data.branch = xstrdup ("-b"); 350 else 351 admin_data.branch = Xasprintf ("-b%s", optarg); 352 break; 353 354 case 'c': 355 if (admin_data.comment != NULL) 356 { 357 error (0, 0, "duplicate 'c' option"); 358 goto usage_error; 359 } 360 admin_data.comment = Xasprintf ("-c%s", optarg); 361 break; 362 363 case 'a': 364 arg_add (&admin_data, 'a', optarg); 365 break; 366 367 case 'A': 368 /* In the client/server case, this is cheesy because 369 we just pass along the name of the RCS file, which 370 then will want to exist on the server. This is 371 accidental; having the client specify a pathname on 372 the server is not a design feature of the protocol. */ 373 arg_add (&admin_data, 'A', optarg); 374 break; 375 376 case 'e': 377 arg_add (&admin_data, 'e', optarg); 378 break; 379 380 case 'l': 381 /* Note that multiple -l options are valid. */ 382 arg_add (&admin_data, 'l', optarg); 383 break; 384 385 case 'u': 386 /* Note that multiple -u options are valid. */ 387 arg_add (&admin_data, 'u', optarg); 388 break; 389 390 case 'L': 391 /* Probably could also complain if -L is specified multiple 392 times, although RCS doesn't and I suppose it is reasonable 393 just to have it mean the same as a single -L. */ 394 if (admin_data.set_nonstrict) 395 { 396 error (0, 0, "-U and -L are incompatible"); 397 goto usage_error; 398 } 399 admin_data.set_strict = 1; 400 break; 401 402 case 'U': 403 /* Probably could also complain if -U is specified multiple 404 times, although RCS doesn't and I suppose it is reasonable 405 just to have it mean the same as a single -U. */ 406 if (admin_data.set_strict) 407 { 408 error (0, 0, "-U and -L are incompatible"); 409 goto usage_error; 410 } 411 admin_data.set_nonstrict = 1; 412 break; 413 414 case 'n': 415 /* Mostly similar to cvs tag. Could also be parsing 416 the syntax of optarg, although for now we just pass 417 it to rcs as-is. Note that multiple -n options are 418 valid. */ 419 arg_add (&admin_data, 'n', optarg); 420 break; 421 422 case 'N': 423 /* Mostly similar to cvs tag. Could also be parsing 424 the syntax of optarg, although for now we just pass 425 it to rcs as-is. Note that multiple -N options are 426 valid. */ 427 arg_add (&admin_data, 'N', optarg); 428 break; 429 430 case 'm': 431 /* Change log message. Could also be parsing the syntax 432 of optarg, although for now we just pass it to rcs 433 as-is. Note that multiple -m options are valid. */ 434 arg_add (&admin_data, 'm', optarg); 435 break; 436 437 case 'o': 438 /* Delete revisions. Probably should also be parsing the 439 syntax of optarg, so that the client can give errors 440 rather than making the server take care of that. 441 Other than that I'm not sure whether it matters much 442 whether we parse it here or in admin_fileproc. 443 444 Note that multiple -o options are invalid, in RCS 445 as well as here. */ 446 447 if (admin_data.delete_revs != NULL) 448 { 449 error (0, 0, "duplicate '-o' option"); 450 goto usage_error; 451 } 452 admin_data.delete_revs = Xasprintf ("-o%s", optarg); 453 break; 454 455 case 's': 456 /* Note that multiple -s options are valid. */ 457 arg_add (&admin_data, 's', optarg); 458 break; 459 460 case 't': 461 if (admin_data.desc != NULL) 462 { 463 error (0, 0, "duplicate 't' option"); 464 goto usage_error; 465 } 466 if (optarg != NULL && optarg[0] == '-') 467 admin_data.desc = xstrdup (optarg + 1); 468 else 469 { 470 size_t bufsize = 0; 471 size_t len; 472 473 get_file (optarg, optarg, "r", &admin_data.desc, 474 &bufsize, &len); 475 } 476 break; 477 478 case 'I': 479 /* At least in RCS this can be specified several times, 480 with the same meaning as being specified once. */ 481 admin_data.interactive = 1; 482 break; 483 484 case 'q': 485 /* Silently set the global really_quiet flag. This keeps admin in 486 * sync with the RCS man page and allows us to silently support 487 * older servers when necessary. 488 * 489 * Some logic says we might want to output a deprecation warning 490 * here, but I'm opting not to in order to stay quietly in sync 491 * with the RCS man page. 492 */ 493 really_quiet = 1; 494 break; 495 496 case 'x': 497 error (0, 0, "the -x option has never done anything useful"); 498 error (0, 0, "RCS files in CVS always end in ,v"); 499 goto usage_error; 500 501 case 'V': 502 /* No longer supported. */ 503 error (0, 0, "the `-V' option is obsolete"); 504 break; 505 506 case 'k': 507 if (admin_data.kflag != NULL) 508 { 509 error (0, 0, "duplicate '-k' option"); 510 goto usage_error; 511 } 512 admin_data.kflag = RCS_check_kflag (optarg); 513 break; 514 default: 515 case '?': 516 /* getopt will have printed an error message. */ 517 518 usage_error: 519 /* Don't use cvs_cmd_name; it might be "server". */ 520 error (1, 0, "specify %s -H admin for usage information", 521 program_name); 522 } 523 } 524 argc -= optind; 525 argv += optind; 526 527 /* The use of `cvs admin -k' is unrestricted. However, any other 528 option is restricted if the group CVS_ADMIN_GROUP exists on the 529 server. */ 530 /* This is only "secure" on the server, since the user could edit the 531 * RCS file on a local host, but some people like this kind of 532 * check anyhow. The alternative would be to check only when 533 * (server_active) rather than when not on the client. 534 */ 535 if (!only_allowed_options && !admin_group_member()) 536 error (1, 0, "usage is restricted to members of the group %s", 537 CVS_ADMIN_GROUP); 538 539 for (i = 0; i < admin_data.ac; ++i) 540 { 541 assert (admin_data.av[i][0] == '-'); 542 switch (admin_data.av[i][1]) 543 { 544 case 'm': 545 case 'l': 546 case 'u': 547 check_numeric (&admin_data.av[i][2], argc, argv); 548 break; 549 default: 550 break; 551 } 552 } 553 if (admin_data.branch != NULL) 554 check_numeric (admin_data.branch + 2, argc, argv); 555 if (admin_data.delete_revs != NULL) 556 { 557 char *p; 558 559 check_numeric (admin_data.delete_revs + 2, argc, argv); 560 p = strchr (admin_data.delete_revs + 2, ':'); 561 if (p != NULL && isdigit ((unsigned char) p[1])) 562 check_numeric (p + 1, argc, argv); 563 else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2])) 564 check_numeric (p + 2, argc, argv); 565 } 566 567 #ifdef CLIENT_SUPPORT 568 if (current_parsed_root->isremote) 569 { 570 /* We're the client side. Fire up the remote server. */ 571 start_server (); 572 573 ign_setup (); 574 575 /* Note that option_with_arg does not work for us, because some 576 of the options must be sent without a space between the option 577 and its argument. */ 578 if (admin_data.interactive) 579 error (1, 0, "-I option not useful with client/server"); 580 if (admin_data.branch != NULL) 581 send_arg (admin_data.branch); 582 if (admin_data.comment != NULL) 583 send_arg (admin_data.comment); 584 if (admin_data.set_strict) 585 send_arg ("-L"); 586 if (admin_data.set_nonstrict) 587 send_arg ("-U"); 588 if (admin_data.delete_revs != NULL) 589 send_arg (admin_data.delete_revs); 590 if (admin_data.desc != NULL) 591 { 592 char *p = admin_data.desc; 593 send_to_server ("Argument -t-", 0); 594 while (*p) 595 { 596 if (*p == '\n') 597 { 598 send_to_server ("\012Argumentx ", 0); 599 ++p; 600 } 601 else 602 { 603 char *q = strchr (p, '\n'); 604 if (q == NULL) q = p + strlen (p); 605 send_to_server (p, q - p); 606 p = q; 607 } 608 } 609 send_to_server ("\012", 1); 610 } 611 /* Send this for all really_quiets since we know that it will be silently 612 * ignored when unneeded. This supports old servers. 613 */ 614 if (really_quiet) 615 send_arg ("-q"); 616 if (admin_data.kflag != NULL) 617 send_arg (admin_data.kflag); 618 619 for (i = 0; i < admin_data.ac; ++i) 620 send_arg (admin_data.av[i]); 621 622 send_arg ("--"); 623 send_files (argc, argv, 0, 0, SEND_NO_CONTENTS); 624 send_file_names (argc, argv, SEND_EXPAND_WILD); 625 send_to_server ("admin\012", 0); 626 err = get_responses_and_close (); 627 goto return_it; 628 } 629 #endif /* CLIENT_SUPPORT */ 630 631 lock_tree_promotably (argc, argv, 0, W_LOCAL, 0); 632 633 err = start_recursion 634 (admin_fileproc, admin_filesdoneproc, admin_dirproc, 635 NULL, &admin_data, 636 argc, argv, 0, 637 W_LOCAL, 0, CVS_LOCK_WRITE, NULL, 1, NULL); 638 639 Lock_Cleanup (); 640 641 /* This just suppresses a warning from -Wall. */ 642 #ifdef CLIENT_SUPPORT 643 return_it: 644 #endif /* CLIENT_SUPPORT */ 645 if (admin_data.cmdline != NULL) 646 free (admin_data.cmdline); 647 if (admin_data.branch != NULL) 648 free (admin_data.branch); 649 if (admin_data.comment != NULL) 650 free (admin_data.comment); 651 if (admin_data.delete_revs != NULL) 652 free (admin_data.delete_revs); 653 if (admin_data.kflag != NULL) 654 free (admin_data.kflag); 655 if (admin_data.desc != NULL) 656 free (admin_data.desc); 657 for (i = 0; i < admin_data.ac; ++i) 658 free (admin_data.av[i]); 659 if (admin_data.av != NULL) 660 free (admin_data.av); 661 662 return err; 663 } 664 665 666 667 /* 668 * Called to run "rcs" on a particular file. 669 */ 670 /* ARGSUSED */ 671 static int 672 admin_fileproc (void *callerdat, struct file_info *finfo) 673 { 674 struct admin_data *admin_data = (struct admin_data *) callerdat; 675 Vers_TS *vers; 676 char *version; 677 int i; 678 int status = 0; 679 RCSNode *rcs, *rcs2; 680 681 vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0); 682 683 version = vers->vn_user; 684 if (version != NULL && strcmp (version, "0") == 0) 685 { 686 error (0, 0, "cannot admin newly added file `%s'", finfo->file); 687 status = 1; 688 goto exitfunc; 689 } 690 691 history_write ('X', finfo->update_dir, admin_data->cmdline, finfo->file, 692 finfo->repository); 693 rcs = vers->srcfile; 694 if (rcs == NULL) 695 { 696 if (!really_quiet) 697 error (0, 0, "nothing known about %s", finfo->file); 698 status = 1; 699 goto exitfunc; 700 } 701 702 if (rcs->flags & PARTIAL) 703 RCS_reparsercsfile (rcs, NULL, NULL); 704 705 if (!really_quiet) 706 { 707 cvs_output ("RCS file: ", 0); 708 cvs_output (rcs->path, 0); 709 cvs_output ("\n", 1); 710 } 711 712 if (admin_data->branch != NULL) 713 { 714 char *branch = &admin_data->branch[2]; 715 if (*branch != '\0' && ! isdigit ((unsigned char) *branch)) 716 { 717 branch = RCS_whatbranch (rcs, admin_data->branch + 2); 718 if (branch == NULL) 719 { 720 error (0, 0, "%s: Symbolic name %s is undefined.", 721 rcs->path, admin_data->branch + 2); 722 status = 1; 723 } 724 } 725 if (status == 0) 726 RCS_setbranch (rcs, branch); 727 if (branch != NULL && branch != &admin_data->branch[2]) 728 free (branch); 729 } 730 if (admin_data->comment != NULL) 731 { 732 if (rcs->comment != NULL) 733 free (rcs->comment); 734 rcs->comment = xstrdup (admin_data->comment + 2); 735 } 736 if (admin_data->set_strict) 737 rcs->strict_locks = 1; 738 if (admin_data->set_nonstrict) 739 rcs->strict_locks = 0; 740 if (admin_data->delete_revs != NULL) 741 { 742 char *s, *t, *rev1, *rev2; 743 /* Set for :, clear for ::. */ 744 int inclusive; 745 char *t2; 746 747 s = admin_data->delete_revs + 2; 748 inclusive = 1; 749 t = strchr (s, ':'); 750 if (t != NULL) 751 { 752 if (t[1] == ':') 753 { 754 inclusive = 0; 755 t2 = t + 2; 756 } 757 else 758 t2 = t + 1; 759 } 760 761 /* Note that we don't support '-' for ranges. RCS considers it 762 obsolete and it is problematic with tags containing '-'. "cvs log" 763 has made the same decision. */ 764 765 if (t == NULL) 766 { 767 /* -orev */ 768 rev1 = xstrdup (s); 769 rev2 = xstrdup (s); 770 } 771 else if (t == s) 772 { 773 /* -o:rev2 */ 774 rev1 = NULL; 775 rev2 = xstrdup (t2); 776 } 777 else 778 { 779 *t = '\0'; 780 rev1 = xstrdup (s); 781 *t = ':'; /* probably unnecessary */ 782 if (*t2 == '\0') 783 /* -orev1: */ 784 rev2 = NULL; 785 else 786 /* -orev1:rev2 */ 787 rev2 = xstrdup (t2); 788 } 789 790 if (rev1 == NULL && rev2 == NULL) 791 { 792 /* RCS segfaults if `-o:' is given */ 793 error (0, 0, "no valid revisions specified in `%s' option", 794 admin_data->delete_revs); 795 status = 1; 796 } 797 else 798 { 799 status |= RCS_delete_revs (rcs, rev1, rev2, inclusive); 800 if (rev1) 801 free (rev1); 802 if (rev2) 803 free (rev2); 804 } 805 } 806 if (admin_data->desc != NULL) 807 { 808 free (rcs->desc); 809 rcs->desc = xstrdup (admin_data->desc); 810 } 811 if (admin_data->kflag != NULL) 812 { 813 char *kflag = admin_data->kflag + 2; 814 char *oldexpand = RCS_getexpand (rcs); 815 if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0) 816 RCS_setexpand (rcs, kflag); 817 } 818 819 /* Handle miscellaneous options. TODO: decide whether any or all 820 of these should have their own fields in the admin_data 821 structure. */ 822 for (i = 0; i < admin_data->ac; ++i) 823 { 824 char *arg; 825 char *p, *rev, *revnum, *tag, *msg; 826 char **users; 827 int argc, u; 828 Node *n; 829 RCSVers *delta; 830 831 arg = admin_data->av[i]; 832 switch (arg[1]) 833 { 834 case 'a': /* fall through */ 835 case 'e': 836 line2argv (&argc, &users, arg + 2, " ,\t\n"); 837 if (arg[1] == 'a') 838 for (u = 0; u < argc; ++u) 839 RCS_addaccess (rcs, users[u]); 840 else if (argc == 0) 841 RCS_delaccess (rcs, NULL); 842 else 843 for (u = 0; u < argc; ++u) 844 RCS_delaccess (rcs, users[u]); 845 free_names (&argc, users); 846 break; 847 case 'A': 848 849 /* See admin-19a-admin and friends in sanity.sh for 850 relative pathnames. It makes sense to think in 851 terms of a syntax which give pathnames relative to 852 the repository or repository corresponding to the 853 current directory or some such (and perhaps don't 854 include ,v), but trying to worry about such things 855 is a little pointless unless you first worry about 856 whether "cvs admin -A" as a whole makes any sense 857 (currently probably not, as access lists don't 858 affect the behavior of CVS). */ 859 860 rcs2 = RCS_parsercsfile (arg + 2); 861 if (rcs2 == NULL) 862 error (1, 0, "cannot continue"); 863 864 p = xstrdup (RCS_getaccess (rcs2)); 865 line2argv (&argc, &users, p, " \t\n"); 866 free (p); 867 freercsnode (&rcs2); 868 869 for (u = 0; u < argc; ++u) 870 RCS_addaccess (rcs, users[u]); 871 free_names (&argc, users); 872 break; 873 case 'n': /* fall through */ 874 case 'N': 875 if (arg[2] == '\0') 876 { 877 cvs_outerr ("missing symbolic name after ", 0); 878 cvs_outerr (arg, 0); 879 cvs_outerr ("\n", 1); 880 break; 881 } 882 p = strchr (arg, ':'); 883 if (p == NULL) 884 { 885 if (RCS_deltag (rcs, arg + 2) != 0) 886 { 887 error (0, 0, "%s: Symbolic name %s is undefined.", 888 rcs->path, 889 arg + 2); 890 status = 1; 891 continue; 892 } 893 break; 894 } 895 *p = '\0'; 896 tag = xstrdup (arg + 2); 897 *p++ = ':'; 898 899 /* Option `n' signals an error if this tag is already bound. */ 900 if (arg[1] == 'n') 901 { 902 n = findnode (RCS_symbols (rcs), tag); 903 if (n != NULL) 904 { 905 error (0, 0, 906 "%s: symbolic name %s already bound to %s", 907 rcs->path, 908 tag, (char *)n->data); 909 status = 1; 910 free (tag); 911 continue; 912 } 913 } 914 915 /* Attempt to perform the requested tagging. */ 916 917 if ((*p == 0 && (rev = RCS_head (rcs))) 918 || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */ 919 { 920 RCS_check_tag (tag); /* exit if not a valid tag */ 921 RCS_settag (rcs, tag, rev); 922 free (rev); 923 } 924 else 925 { 926 if (!really_quiet) 927 error (0, 0, 928 "%s: Symbolic name or revision %s is undefined.", 929 rcs->path, p); 930 status = 1; 931 } 932 free (tag); 933 break; 934 case 's': 935 p = strchr (arg, ':'); 936 if (p == NULL) 937 { 938 tag = xstrdup (arg + 2); 939 rev = RCS_head (rcs); 940 if (!rev) 941 { 942 error (0, 0, "No head revision in archive file `%s'.", 943 rcs->path); 944 status = 1; 945 continue; 946 } 947 } 948 else 949 { 950 *p = '\0'; 951 tag = xstrdup (arg + 2); 952 *p++ = ':'; 953 rev = xstrdup (p); 954 } 955 revnum = RCS_gettag (rcs, rev, 0, NULL); 956 if (revnum != NULL) 957 { 958 n = findnode (rcs->versions, revnum); 959 free (revnum); 960 } 961 else 962 n = NULL; 963 if (n == NULL) 964 { 965 error (0, 0, 966 "%s: can't set state of nonexisting revision %s", 967 rcs->path, 968 rev); 969 free (rev); 970 status = 1; 971 continue; 972 } 973 free (rev); 974 delta = n->data; 975 free (delta->state); 976 delta->state = tag; 977 break; 978 979 case 'm': 980 p = strchr (arg, ':'); 981 if (p == NULL) 982 { 983 error (0, 0, "%s: -m option lacks revision number", 984 rcs->path); 985 status = 1; 986 continue; 987 } 988 *p = '\0'; /* temporarily make arg+2 its own string */ 989 rev = RCS_gettag (rcs, arg + 2, 1, NULL); /* Force tag match */ 990 if (rev == NULL) 991 { 992 error (0, 0, "%s: no such revision %s", rcs->path, arg+2); 993 status = 1; 994 *p = ':'; /* restore the full text of the -m argument */ 995 continue; 996 } 997 msg = p+1; 998 999 n = findnode (rcs->versions, rev); 1000 /* tags may exist against non-existing versions */ 1001 if (n == NULL) 1002 { 1003 error (0, 0, "%s: no such revision %s: %s", 1004 rcs->path, arg+2, rev); 1005 status = 1; 1006 *p = ':'; /* restore the full text of the -m argument */ 1007 free (rev); 1008 continue; 1009 } 1010 *p = ':'; /* restore the full text of the -m argument */ 1011 free (rev); 1012 1013 delta = n->data; 1014 if (delta->text == NULL) 1015 { 1016 delta->text = xmalloc (sizeof (Deltatext)); 1017 memset (delta->text, 0, sizeof (Deltatext)); 1018 } 1019 delta->text->version = xstrdup (delta->version); 1020 delta->text->log = make_message_rcsvalid (msg); 1021 break; 1022 1023 case 'l': 1024 status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0); 1025 break; 1026 case 'u': 1027 status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0); 1028 break; 1029 default: assert(0); /* can't happen */ 1030 } 1031 } 1032 1033 if (status == 0) 1034 { 1035 RCS_rewrite (rcs, NULL, NULL); 1036 if (!really_quiet) 1037 cvs_output ("done\n", 5); 1038 } 1039 else 1040 { 1041 /* Note that this message should only occur after another 1042 message has given a more specific error. The point of this 1043 additional message is to make it clear that the previous problems 1044 caused CVS to forget about the idea of modifying the RCS file. */ 1045 if (!really_quiet) 1046 error (0, 0, "RCS file for `%s' not modified.", finfo->file); 1047 RCS_abandon (rcs); 1048 } 1049 1050 exitfunc: 1051 freevers_ts (&vers); 1052 return status; 1053 } 1054 1055 1056 1057 /* 1058 * Print a warm fuzzy message 1059 */ 1060 /* ARGSUSED */ 1061 static Dtype 1062 admin_dirproc (void *callerdat, const char *dir, const char *repos, 1063 const char *update_dir, List *entries) 1064 { 1065 if (!quiet) 1066 error (0, 0, "Administrating %s", update_dir); 1067 return R_PROCESS; 1068 } 1069