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 == NULL || config->UserAdminGroup == NULL) 268 return 1; 269 270 if ((grp = getgrnam(config->UserAdminGroup)) == 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 return 1; 303 } 304 305 int 306 admin (int argc, char **argv) 307 { 308 int err; 309 struct admin_data admin_data; 310 int c; 311 int i; 312 bool only_allowed_options; 313 314 if (argc <= 1) 315 usage (admin_usage); 316 317 wrap_setup (); 318 319 memset (&admin_data, 0, sizeof admin_data); 320 admin_data.cmdline = makecmdline (argc, argv); 321 322 /* TODO: get rid of `-' switch notation in admin_data. For 323 example, admin_data->branch should be not `-bfoo' but simply `foo'. */ 324 325 getoptreset (); 326 only_allowed_options = true; 327 while ((c = getopt (argc, argv, 328 "+ib::c:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1) 329 { 330 if (config != NULL) { 331 if (c != 'q' && !strchr (config->UserAdminOptions, c)) 332 only_allowed_options = false; 333 } else { 334 #ifdef CLIENT_SUPPORT 335 assert(current_parsed_root->isremote); 336 only_allowed_options = false; 337 #else 338 assert(0); /* config should not be NULL, except in a client */ 339 #endif 340 } 341 342 switch (c) 343 { 344 case 'i': 345 /* This has always been documented as useless in cvs.texinfo 346 and it really is--admin_fileproc silently does nothing 347 if vers->vn_user is NULL. */ 348 error (0, 0, "the -i option to admin is not supported"); 349 error (0, 0, "run add or import to create an RCS file"); 350 goto usage_error; 351 352 case 'b': 353 if (admin_data.branch != NULL) 354 { 355 error (0, 0, "duplicate 'b' option"); 356 goto usage_error; 357 } 358 if (optarg == NULL) 359 admin_data.branch = xstrdup ("-b"); 360 else 361 admin_data.branch = Xasprintf ("-b%s", optarg); 362 break; 363 364 case 'c': 365 if (admin_data.comment != NULL) 366 { 367 error (0, 0, "duplicate 'c' option"); 368 goto usage_error; 369 } 370 admin_data.comment = Xasprintf ("-c%s", optarg); 371 break; 372 373 case 'a': 374 arg_add (&admin_data, 'a', optarg); 375 break; 376 377 case 'A': 378 /* In the client/server case, this is cheesy because 379 we just pass along the name of the RCS file, which 380 then will want to exist on the server. This is 381 accidental; having the client specify a pathname on 382 the server is not a design feature of the protocol. */ 383 arg_add (&admin_data, 'A', optarg); 384 break; 385 386 case 'e': 387 arg_add (&admin_data, 'e', optarg); 388 break; 389 390 case 'l': 391 /* Note that multiple -l options are valid. */ 392 arg_add (&admin_data, 'l', optarg); 393 break; 394 395 case 'u': 396 /* Note that multiple -u options are valid. */ 397 arg_add (&admin_data, 'u', optarg); 398 break; 399 400 case 'L': 401 /* Probably could also complain if -L is specified multiple 402 times, although RCS doesn't and I suppose it is reasonable 403 just to have it mean the same as a single -L. */ 404 if (admin_data.set_nonstrict) 405 { 406 error (0, 0, "-U and -L are incompatible"); 407 goto usage_error; 408 } 409 admin_data.set_strict = 1; 410 break; 411 412 case 'U': 413 /* Probably could also complain if -U is specified multiple 414 times, although RCS doesn't and I suppose it is reasonable 415 just to have it mean the same as a single -U. */ 416 if (admin_data.set_strict) 417 { 418 error (0, 0, "-U and -L are incompatible"); 419 goto usage_error; 420 } 421 admin_data.set_nonstrict = 1; 422 break; 423 424 case 'n': 425 /* Mostly similar to cvs tag. Could also be parsing 426 the syntax of optarg, although for now we just pass 427 it to rcs as-is. Note that multiple -n options are 428 valid. */ 429 arg_add (&admin_data, 'n', optarg); 430 break; 431 432 case 'N': 433 /* Mostly similar to cvs tag. Could also be parsing 434 the syntax of optarg, although for now we just pass 435 it to rcs as-is. Note that multiple -N options are 436 valid. */ 437 arg_add (&admin_data, 'N', optarg); 438 break; 439 440 case 'm': 441 /* Change log message. Could also be parsing the syntax 442 of optarg, although for now we just pass it to rcs 443 as-is. Note that multiple -m options are valid. */ 444 arg_add (&admin_data, 'm', optarg); 445 break; 446 447 case 'o': 448 /* Delete revisions. Probably should also be parsing the 449 syntax of optarg, so that the client can give errors 450 rather than making the server take care of that. 451 Other than that I'm not sure whether it matters much 452 whether we parse it here or in admin_fileproc. 453 454 Note that multiple -o options are invalid, in RCS 455 as well as here. */ 456 457 if (admin_data.delete_revs != NULL) 458 { 459 error (0, 0, "duplicate '-o' option"); 460 goto usage_error; 461 } 462 admin_data.delete_revs = Xasprintf ("-o%s", optarg); 463 break; 464 465 case 's': 466 /* Note that multiple -s options are valid. */ 467 arg_add (&admin_data, 's', optarg); 468 break; 469 470 case 't': 471 if (admin_data.desc != NULL) 472 { 473 error (0, 0, "duplicate 't' option"); 474 goto usage_error; 475 } 476 if (optarg != NULL && optarg[0] == '-') 477 admin_data.desc = xstrdup (optarg + 1); 478 else 479 { 480 size_t bufsize = 0; 481 size_t len; 482 483 get_file (optarg, optarg, "r", &admin_data.desc, 484 &bufsize, &len); 485 } 486 break; 487 488 case 'I': 489 /* At least in RCS this can be specified several times, 490 with the same meaning as being specified once. */ 491 admin_data.interactive = 1; 492 break; 493 494 case 'q': 495 /* Silently set the global really_quiet flag. This keeps admin in 496 * sync with the RCS man page and allows us to silently support 497 * older servers when necessary. 498 * 499 * Some logic says we might want to output a deprecation warning 500 * here, but I'm opting not to in order to stay quietly in sync 501 * with the RCS man page. 502 */ 503 really_quiet = 1; 504 break; 505 506 case 'x': 507 error (0, 0, "the -x option has never done anything useful"); 508 error (0, 0, "RCS files in CVS always end in ,v"); 509 goto usage_error; 510 511 case 'V': 512 /* No longer supported. */ 513 error (0, 0, "the `-V' option is obsolete"); 514 break; 515 516 case 'k': 517 if (admin_data.kflag != NULL) 518 { 519 error (0, 0, "duplicate '-k' option"); 520 goto usage_error; 521 } 522 admin_data.kflag = RCS_check_kflag (optarg); 523 break; 524 default: 525 case '?': 526 /* getopt will have printed an error message. */ 527 528 usage_error: 529 /* Don't use cvs_cmd_name; it might be "server". */ 530 error (1, 0, "specify %s -H admin for usage information", 531 program_name); 532 } 533 } 534 argc -= optind; 535 argv += optind; 536 537 /* The use of `cvs admin -k' is unrestricted. However, any other 538 option is restricted if the group CVS_ADMIN_GROUP exists on the 539 server. */ 540 /* This is only "secure" on the server, since the user could edit the 541 * RCS file on a local host, but some people like this kind of 542 * check anyhow. The alternative would be to check only when 543 * (server_active) rather than when not on the client. 544 */ 545 if (!only_allowed_options && !admin_group_member()) 546 error (1, 0, "usage is restricted to members of the group %s", 547 CVS_ADMIN_GROUP); 548 549 for (i = 0; i < admin_data.ac; ++i) 550 { 551 assert (admin_data.av[i][0] == '-'); 552 switch (admin_data.av[i][1]) 553 { 554 case 'm': 555 case 'l': 556 case 'u': 557 check_numeric (&admin_data.av[i][2], argc, argv); 558 break; 559 default: 560 break; 561 } 562 } 563 if (admin_data.branch != NULL) 564 check_numeric (admin_data.branch + 2, argc, argv); 565 if (admin_data.delete_revs != NULL) 566 { 567 char *p; 568 569 check_numeric (admin_data.delete_revs + 2, argc, argv); 570 p = strchr (admin_data.delete_revs + 2, ':'); 571 if (p != NULL && isdigit ((unsigned char) p[1])) 572 check_numeric (p + 1, argc, argv); 573 else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2])) 574 check_numeric (p + 2, argc, argv); 575 } 576 577 #ifdef CLIENT_SUPPORT 578 if (current_parsed_root->isremote) 579 { 580 /* We're the client side. Fire up the remote server. */ 581 start_server (); 582 583 ign_setup (); 584 585 /* Note that option_with_arg does not work for us, because some 586 of the options must be sent without a space between the option 587 and its argument. */ 588 if (admin_data.interactive) 589 error (1, 0, "-I option not useful with client/server"); 590 if (admin_data.branch != NULL) 591 send_arg (admin_data.branch); 592 if (admin_data.comment != NULL) 593 send_arg (admin_data.comment); 594 if (admin_data.set_strict) 595 send_arg ("-L"); 596 if (admin_data.set_nonstrict) 597 send_arg ("-U"); 598 if (admin_data.delete_revs != NULL) 599 send_arg (admin_data.delete_revs); 600 if (admin_data.desc != NULL) 601 { 602 char *p = admin_data.desc; 603 send_to_server ("Argument -t-", 0); 604 while (*p) 605 { 606 if (*p == '\n') 607 { 608 send_to_server ("\012Argumentx ", 0); 609 ++p; 610 } 611 else 612 { 613 char *q = strchr (p, '\n'); 614 if (q == NULL) q = p + strlen (p); 615 send_to_server (p, q - p); 616 p = q; 617 } 618 } 619 send_to_server ("\012", 1); 620 } 621 /* Send this for all really_quiets since we know that it will be silently 622 * ignored when unneeded. This supports old servers. 623 */ 624 if (really_quiet) 625 send_arg ("-q"); 626 if (admin_data.kflag != NULL) 627 send_arg (admin_data.kflag); 628 629 for (i = 0; i < admin_data.ac; ++i) 630 send_arg (admin_data.av[i]); 631 632 send_arg ("--"); 633 send_files (argc, argv, 0, 0, SEND_NO_CONTENTS); 634 send_file_names (argc, argv, SEND_EXPAND_WILD); 635 send_to_server ("admin\012", 0); 636 err = get_responses_and_close (); 637 goto return_it; 638 } 639 #endif /* CLIENT_SUPPORT */ 640 641 lock_tree_promotably (argc, argv, 0, W_LOCAL, 0); 642 643 err = start_recursion 644 (admin_fileproc, admin_filesdoneproc, admin_dirproc, 645 NULL, &admin_data, 646 argc, argv, 0, 647 W_LOCAL, 0, CVS_LOCK_WRITE, NULL, 1, NULL); 648 649 Lock_Cleanup (); 650 651 /* This just suppresses a warning from -Wall. */ 652 #ifdef CLIENT_SUPPORT 653 return_it: 654 #endif /* CLIENT_SUPPORT */ 655 if (admin_data.cmdline != NULL) 656 free (admin_data.cmdline); 657 if (admin_data.branch != NULL) 658 free (admin_data.branch); 659 if (admin_data.comment != NULL) 660 free (admin_data.comment); 661 if (admin_data.delete_revs != NULL) 662 free (admin_data.delete_revs); 663 if (admin_data.kflag != NULL) 664 free (admin_data.kflag); 665 if (admin_data.desc != NULL) 666 free (admin_data.desc); 667 for (i = 0; i < admin_data.ac; ++i) 668 free (admin_data.av[i]); 669 if (admin_data.av != NULL) 670 free (admin_data.av); 671 672 return err; 673 } 674 675 676 677 /* 678 * Called to run "rcs" on a particular file. 679 */ 680 /* ARGSUSED */ 681 static int 682 admin_fileproc (void *callerdat, struct file_info *finfo) 683 { 684 struct admin_data *admin_data = (struct admin_data *) callerdat; 685 Vers_TS *vers; 686 char *version; 687 int i; 688 int status = 0; 689 RCSNode *rcs, *rcs2; 690 691 vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0); 692 693 version = vers->vn_user; 694 if (version != NULL && strcmp (version, "0") == 0) 695 { 696 error (0, 0, "cannot admin newly added file `%s'", finfo->file); 697 status = 1; 698 goto exitfunc; 699 } 700 701 history_write ('X', finfo->update_dir, admin_data->cmdline, finfo->file, 702 finfo->repository); 703 rcs = vers->srcfile; 704 if (rcs == NULL) 705 { 706 if (!really_quiet) 707 error (0, 0, "nothing known about %s", finfo->file); 708 status = 1; 709 goto exitfunc; 710 } 711 712 if (rcs->flags & PARTIAL) 713 RCS_reparsercsfile (rcs, NULL, NULL); 714 715 if (!really_quiet) 716 { 717 cvs_output ("RCS file: ", 0); 718 cvs_output (rcs->path, 0); 719 cvs_output ("\n", 1); 720 } 721 722 if (admin_data->branch != NULL) 723 { 724 char *branch = &admin_data->branch[2]; 725 if (*branch != '\0' && ! isdigit ((unsigned char) *branch)) 726 { 727 branch = RCS_whatbranch (rcs, admin_data->branch + 2); 728 if (branch == NULL) 729 { 730 error (0, 0, "%s: Symbolic name %s is undefined.", 731 rcs->path, admin_data->branch + 2); 732 status = 1; 733 } 734 } 735 if (status == 0) 736 RCS_setbranch (rcs, branch); 737 if (branch != NULL && branch != &admin_data->branch[2]) 738 free (branch); 739 } 740 if (admin_data->comment != NULL) 741 { 742 if (rcs->comment != NULL) 743 free (rcs->comment); 744 rcs->comment = xstrdup (admin_data->comment + 2); 745 } 746 if (admin_data->set_strict) 747 rcs->strict_locks = 1; 748 if (admin_data->set_nonstrict) 749 rcs->strict_locks = 0; 750 if (admin_data->delete_revs != NULL) 751 { 752 char *s, *t, *rev1, *rev2; 753 /* Set for :, clear for ::. */ 754 int inclusive; 755 char *t2; 756 757 s = admin_data->delete_revs + 2; 758 inclusive = 1; 759 t = strchr (s, ':'); 760 if (t != NULL) 761 { 762 if (t[1] == ':') 763 { 764 inclusive = 0; 765 t2 = t + 2; 766 } 767 else 768 t2 = t + 1; 769 } 770 771 /* Note that we don't support '-' for ranges. RCS considers it 772 obsolete and it is problematic with tags containing '-'. "cvs log" 773 has made the same decision. */ 774 775 if (t == NULL) 776 { 777 /* -orev */ 778 rev1 = xstrdup (s); 779 rev2 = xstrdup (s); 780 } 781 else if (t == s) 782 { 783 /* -o:rev2 */ 784 rev1 = NULL; 785 rev2 = xstrdup (t2); 786 } 787 else 788 { 789 *t = '\0'; 790 rev1 = xstrdup (s); 791 *t = ':'; /* probably unnecessary */ 792 if (*t2 == '\0') 793 /* -orev1: */ 794 rev2 = NULL; 795 else 796 /* -orev1:rev2 */ 797 rev2 = xstrdup (t2); 798 } 799 800 if (rev1 == NULL && rev2 == NULL) 801 { 802 /* RCS segfaults if `-o:' is given */ 803 error (0, 0, "no valid revisions specified in `%s' option", 804 admin_data->delete_revs); 805 status = 1; 806 } 807 else 808 { 809 status |= RCS_delete_revs (rcs, rev1, rev2, inclusive); 810 if (rev1) 811 free (rev1); 812 if (rev2) 813 free (rev2); 814 } 815 } 816 if (admin_data->desc != NULL) 817 { 818 free (rcs->desc); 819 rcs->desc = xstrdup (admin_data->desc); 820 } 821 if (admin_data->kflag != NULL) 822 { 823 char *kflag = admin_data->kflag + 2; 824 char *oldexpand = RCS_getexpand (rcs); 825 if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0) 826 RCS_setexpand (rcs, kflag); 827 } 828 829 /* Handle miscellaneous options. TODO: decide whether any or all 830 of these should have their own fields in the admin_data 831 structure. */ 832 for (i = 0; i < admin_data->ac; ++i) 833 { 834 char *arg; 835 char *p, *rev, *revnum, *tag, *msg; 836 char **users; 837 int argc, u; 838 Node *n; 839 RCSVers *delta; 840 841 arg = admin_data->av[i]; 842 switch (arg[1]) 843 { 844 case 'a': /* fall through */ 845 case 'e': 846 line2argv (&argc, &users, arg + 2, " ,\t\n"); 847 if (arg[1] == 'a') 848 for (u = 0; u < argc; ++u) 849 RCS_addaccess (rcs, users[u]); 850 else if (argc == 0) 851 RCS_delaccess (rcs, NULL); 852 else 853 for (u = 0; u < argc; ++u) 854 RCS_delaccess (rcs, users[u]); 855 free_names (&argc, users); 856 break; 857 case 'A': 858 859 /* See admin-19a-admin and friends in sanity.sh for 860 relative pathnames. It makes sense to think in 861 terms of a syntax which give pathnames relative to 862 the repository or repository corresponding to the 863 current directory or some such (and perhaps don't 864 include ,v), but trying to worry about such things 865 is a little pointless unless you first worry about 866 whether "cvs admin -A" as a whole makes any sense 867 (currently probably not, as access lists don't 868 affect the behavior of CVS). */ 869 870 rcs2 = RCS_parsercsfile (arg + 2); 871 if (rcs2 == NULL) 872 error (1, 0, "cannot continue"); 873 874 p = xstrdup (RCS_getaccess (rcs2)); 875 line2argv (&argc, &users, p, " \t\n"); 876 free (p); 877 freercsnode (&rcs2); 878 879 for (u = 0; u < argc; ++u) 880 RCS_addaccess (rcs, users[u]); 881 free_names (&argc, users); 882 break; 883 case 'n': /* fall through */ 884 case 'N': 885 if (arg[2] == '\0') 886 { 887 cvs_outerr ("missing symbolic name after ", 0); 888 cvs_outerr (arg, 0); 889 cvs_outerr ("\n", 1); 890 break; 891 } 892 p = strchr (arg, ':'); 893 if (p == NULL) 894 { 895 if (RCS_deltag (rcs, arg + 2) != 0) 896 { 897 error (0, 0, "%s: Symbolic name %s is undefined.", 898 rcs->path, 899 arg + 2); 900 status = 1; 901 continue; 902 } 903 break; 904 } 905 *p = '\0'; 906 tag = xstrdup (arg + 2); 907 *p++ = ':'; 908 909 /* Option `n' signals an error if this tag is already bound. */ 910 if (arg[1] == 'n') 911 { 912 n = findnode (RCS_symbols (rcs), tag); 913 if (n != NULL) 914 { 915 error (0, 0, 916 "%s: symbolic name %s already bound to %s", 917 rcs->path, 918 tag, (char *)n->data); 919 status = 1; 920 free (tag); 921 continue; 922 } 923 } 924 925 /* Attempt to perform the requested tagging. */ 926 927 if ((*p == 0 && (rev = RCS_head (rcs))) 928 || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */ 929 { 930 RCS_check_tag (tag); /* exit if not a valid tag */ 931 RCS_settag (rcs, tag, rev); 932 free (rev); 933 } 934 else 935 { 936 if (!really_quiet) 937 error (0, 0, 938 "%s: Symbolic name or revision %s is undefined.", 939 rcs->path, p); 940 status = 1; 941 } 942 free (tag); 943 break; 944 case 's': 945 p = strchr (arg, ':'); 946 if (p == NULL) 947 { 948 tag = xstrdup (arg + 2); 949 rev = RCS_head (rcs); 950 if (!rev) 951 { 952 error (0, 0, "No head revision in archive file `%s'.", 953 rcs->path); 954 status = 1; 955 continue; 956 } 957 } 958 else 959 { 960 *p = '\0'; 961 tag = xstrdup (arg + 2); 962 *p++ = ':'; 963 rev = xstrdup (p); 964 } 965 revnum = RCS_gettag (rcs, rev, 0, NULL); 966 if (revnum != NULL) 967 { 968 n = findnode (rcs->versions, revnum); 969 free (revnum); 970 } 971 else 972 n = NULL; 973 if (n == NULL) 974 { 975 error (0, 0, 976 "%s: can't set state of nonexisting revision %s", 977 rcs->path, 978 rev); 979 free (rev); 980 status = 1; 981 continue; 982 } 983 free (rev); 984 delta = n->data; 985 free (delta->state); 986 delta->state = tag; 987 break; 988 989 case 'm': 990 p = strchr (arg, ':'); 991 if (p == NULL) 992 { 993 error (0, 0, "%s: -m option lacks revision number", 994 rcs->path); 995 status = 1; 996 continue; 997 } 998 *p = '\0'; /* temporarily make arg+2 its own string */ 999 rev = RCS_gettag (rcs, arg + 2, 1, NULL); /* Force tag match */ 1000 if (rev == NULL) 1001 { 1002 error (0, 0, "%s: no such revision %s", rcs->path, arg+2); 1003 status = 1; 1004 *p = ':'; /* restore the full text of the -m argument */ 1005 continue; 1006 } 1007 msg = p+1; 1008 1009 n = findnode (rcs->versions, rev); 1010 /* tags may exist against non-existing versions */ 1011 if (n == NULL) 1012 { 1013 error (0, 0, "%s: no such revision %s: %s", 1014 rcs->path, arg+2, rev); 1015 status = 1; 1016 *p = ':'; /* restore the full text of the -m argument */ 1017 free (rev); 1018 continue; 1019 } 1020 *p = ':'; /* restore the full text of the -m argument */ 1021 free (rev); 1022 1023 delta = n->data; 1024 if (delta->text == NULL) 1025 { 1026 delta->text = xmalloc (sizeof (Deltatext)); 1027 memset (delta->text, 0, sizeof (Deltatext)); 1028 } 1029 delta->text->version = xstrdup (delta->version); 1030 delta->text->log = make_message_rcsvalid (msg); 1031 break; 1032 1033 case 'l': 1034 status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0); 1035 break; 1036 case 'u': 1037 status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0); 1038 break; 1039 default: assert(0); /* can't happen */ 1040 } 1041 } 1042 1043 if (status == 0) 1044 { 1045 RCS_rewrite (rcs, NULL, NULL); 1046 if (!really_quiet) 1047 cvs_output ("done\n", 5); 1048 } 1049 else 1050 { 1051 /* Note that this message should only occur after another 1052 message has given a more specific error. The point of this 1053 additional message is to make it clear that the previous problems 1054 caused CVS to forget about the idea of modifying the RCS file. */ 1055 if (!really_quiet) 1056 error (0, 0, "RCS file for `%s' not modified.", finfo->file); 1057 RCS_abandon (rcs); 1058 } 1059 1060 exitfunc: 1061 freevers_ts (&vers); 1062 return status; 1063 } 1064 1065 1066 1067 /* 1068 * Print a warm fuzzy message 1069 */ 1070 /* ARGSUSED */ 1071 static Dtype 1072 admin_dirproc (void *callerdat, const char *dir, const char *repos, 1073 const char *update_dir, List *entries) 1074 { 1075 if (!quiet) 1076 error (0, 0, "Administrating %s", update_dir); 1077 return R_PROCESS; 1078 } 1079