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