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