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