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 /* cvsacl patch */ 694 #ifdef SERVER_SUPPORT 695 if (use_cvs_acl /* && server_active */) 696 { 697 if (!access_allowed (finfo->file, finfo->repository, NULL, 2, 698 NULL, NULL, 1)) 699 { 700 if (stop_at_first_permission_denied) 701 error (1, 0, "permission denied for %s", 702 Short_Repository (finfo->repository)); 703 else 704 error (0, 0, "permission denied for %s/%s", 705 Short_Repository (finfo->repository), finfo->file); 706 707 return (0); 708 } 709 } 710 #endif 711 712 version = vers->vn_user; 713 if (version != NULL && strcmp (version, "0") == 0) 714 { 715 error (0, 0, "cannot admin newly added file `%s'", finfo->file); 716 status = 1; 717 goto exitfunc; 718 } 719 720 history_write ('X', finfo->update_dir, admin_data->cmdline, finfo->file, 721 finfo->repository); 722 rcs = vers->srcfile; 723 if (rcs == NULL) 724 { 725 if (!really_quiet) 726 error (0, 0, "nothing known about %s", finfo->file); 727 status = 1; 728 goto exitfunc; 729 } 730 731 if (rcs->flags & PARTIAL) 732 RCS_reparsercsfile (rcs, NULL, NULL); 733 734 if (!really_quiet) 735 { 736 cvs_output ("RCS file: ", 0); 737 cvs_output (rcs->path, 0); 738 cvs_output ("\n", 1); 739 } 740 741 if (admin_data->branch != NULL) 742 { 743 char *branch = &admin_data->branch[2]; 744 if (*branch != '\0' && ! isdigit ((unsigned char) *branch)) 745 { 746 branch = RCS_whatbranch (rcs, admin_data->branch + 2); 747 if (branch == NULL) 748 { 749 error (0, 0, "%s: Symbolic name %s is undefined.", 750 rcs->path, admin_data->branch + 2); 751 status = 1; 752 } 753 } 754 if (status == 0) 755 RCS_setbranch (rcs, branch); 756 if (branch != NULL && branch != &admin_data->branch[2]) 757 free (branch); 758 } 759 if (admin_data->comment != NULL) 760 { 761 if (rcs->comment != NULL) 762 free (rcs->comment); 763 rcs->comment = xstrdup (admin_data->comment + 2); 764 } 765 if (admin_data->set_strict) 766 rcs->strict_locks = 1; 767 if (admin_data->set_nonstrict) 768 rcs->strict_locks = 0; 769 if (admin_data->delete_revs != NULL) 770 { 771 char *s, *t, *rev1, *rev2; 772 /* Set for :, clear for ::. */ 773 int inclusive; 774 char *t2; 775 776 s = admin_data->delete_revs + 2; 777 inclusive = 1; 778 t = strchr (s, ':'); 779 if (t != NULL) 780 { 781 if (t[1] == ':') 782 { 783 inclusive = 0; 784 t2 = t + 2; 785 } 786 else 787 t2 = t + 1; 788 } 789 790 /* Note that we don't support '-' for ranges. RCS considers it 791 obsolete and it is problematic with tags containing '-'. "cvs log" 792 has made the same decision. */ 793 794 if (t == NULL) 795 { 796 /* -orev */ 797 rev1 = xstrdup (s); 798 rev2 = xstrdup (s); 799 } 800 else if (t == s) 801 { 802 /* -o:rev2 */ 803 rev1 = NULL; 804 rev2 = xstrdup (t2); 805 } 806 else 807 { 808 *t = '\0'; 809 rev1 = xstrdup (s); 810 *t = ':'; /* probably unnecessary */ 811 if (*t2 == '\0') 812 /* -orev1: */ 813 rev2 = NULL; 814 else 815 /* -orev1:rev2 */ 816 rev2 = xstrdup (t2); 817 } 818 819 if (rev1 == NULL && rev2 == NULL) 820 { 821 /* RCS segfaults if `-o:' is given */ 822 error (0, 0, "no valid revisions specified in `%s' option", 823 admin_data->delete_revs); 824 status = 1; 825 } 826 else 827 { 828 status |= RCS_delete_revs (rcs, rev1, rev2, inclusive); 829 if (rev1) 830 free (rev1); 831 if (rev2) 832 free (rev2); 833 } 834 } 835 if (admin_data->desc != NULL) 836 { 837 free (rcs->desc); 838 rcs->desc = xstrdup (admin_data->desc); 839 } 840 if (admin_data->kflag != NULL) 841 { 842 char *kflag = admin_data->kflag + 2; 843 char *oldexpand = RCS_getexpand (rcs); 844 if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0) 845 RCS_setexpand (rcs, kflag); 846 } 847 848 /* Handle miscellaneous options. TODO: decide whether any or all 849 of these should have their own fields in the admin_data 850 structure. */ 851 for (i = 0; i < admin_data->ac; ++i) 852 { 853 char *arg; 854 char *p, *rev, *revnum, *tag, *msg; 855 char **users; 856 int argc, u; 857 Node *n; 858 RCSVers *delta; 859 860 arg = admin_data->av[i]; 861 switch (arg[1]) 862 { 863 case 'a': /* fall through */ 864 case 'e': 865 line2argv (&argc, &users, arg + 2, " ,\t\n"); 866 if (arg[1] == 'a') 867 for (u = 0; u < argc; ++u) 868 RCS_addaccess (rcs, users[u]); 869 else if (argc == 0) 870 RCS_delaccess (rcs, NULL); 871 else 872 for (u = 0; u < argc; ++u) 873 RCS_delaccess (rcs, users[u]); 874 free_names (&argc, users); 875 break; 876 case 'A': 877 878 /* See admin-19a-admin and friends in sanity.sh for 879 relative pathnames. It makes sense to think in 880 terms of a syntax which give pathnames relative to 881 the repository or repository corresponding to the 882 current directory or some such (and perhaps don't 883 include ,v), but trying to worry about such things 884 is a little pointless unless you first worry about 885 whether "cvs admin -A" as a whole makes any sense 886 (currently probably not, as access lists don't 887 affect the behavior of CVS). */ 888 889 rcs2 = RCS_parsercsfile (arg + 2); 890 if (rcs2 == NULL) 891 error (1, 0, "cannot continue"); 892 893 p = xstrdup (RCS_getaccess (rcs2)); 894 line2argv (&argc, &users, p, " \t\n"); 895 free (p); 896 freercsnode (&rcs2); 897 898 for (u = 0; u < argc; ++u) 899 RCS_addaccess (rcs, users[u]); 900 free_names (&argc, users); 901 break; 902 case 'n': /* fall through */ 903 case 'N': 904 if (arg[2] == '\0') 905 { 906 cvs_outerr ("missing symbolic name after ", 0); 907 cvs_outerr (arg, 0); 908 cvs_outerr ("\n", 1); 909 break; 910 } 911 p = strchr (arg, ':'); 912 if (p == NULL) 913 { 914 if (RCS_deltag (rcs, arg + 2) != 0) 915 { 916 error (0, 0, "%s: Symbolic name %s is undefined.", 917 rcs->path, 918 arg + 2); 919 status = 1; 920 continue; 921 } 922 break; 923 } 924 *p = '\0'; 925 tag = xstrdup (arg + 2); 926 *p++ = ':'; 927 928 /* Option `n' signals an error if this tag is already bound. */ 929 if (arg[1] == 'n') 930 { 931 n = findnode (RCS_symbols (rcs), tag); 932 if (n != NULL) 933 { 934 error (0, 0, 935 "%s: symbolic name %s already bound to %s", 936 rcs->path, 937 tag, (char *)n->data); 938 status = 1; 939 free (tag); 940 continue; 941 } 942 } 943 944 /* Attempt to perform the requested tagging. */ 945 946 if ((*p == 0 && (rev = RCS_head (rcs))) 947 || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */ 948 { 949 RCS_check_tag (tag); /* exit if not a valid tag */ 950 RCS_settag (rcs, tag, rev); 951 free (rev); 952 } 953 else 954 { 955 if (!really_quiet) 956 error (0, 0, 957 "%s: Symbolic name or revision %s is undefined.", 958 rcs->path, p); 959 status = 1; 960 } 961 free (tag); 962 break; 963 case 's': 964 p = strchr (arg, ':'); 965 if (p == NULL) 966 { 967 tag = xstrdup (arg + 2); 968 rev = RCS_head (rcs); 969 if (!rev) 970 { 971 error (0, 0, "No head revision in archive file `%s'.", 972 rcs->path); 973 status = 1; 974 continue; 975 } 976 } 977 else 978 { 979 *p = '\0'; 980 tag = xstrdup (arg + 2); 981 *p++ = ':'; 982 rev = xstrdup (p); 983 } 984 revnum = RCS_gettag (rcs, rev, 0, NULL); 985 if (revnum != NULL) 986 { 987 n = findnode (rcs->versions, revnum); 988 free (revnum); 989 } 990 else 991 n = NULL; 992 if (n == NULL) 993 { 994 error (0, 0, 995 "%s: can't set state of nonexisting revision %s", 996 rcs->path, 997 rev); 998 free (rev); 999 status = 1; 1000 continue; 1001 } 1002 free (rev); 1003 delta = n->data; 1004 free (delta->state); 1005 delta->state = tag; 1006 break; 1007 1008 case 'm': 1009 p = strchr (arg, ':'); 1010 if (p == NULL) 1011 { 1012 error (0, 0, "%s: -m option lacks revision number", 1013 rcs->path); 1014 status = 1; 1015 continue; 1016 } 1017 *p = '\0'; /* temporarily make arg+2 its own string */ 1018 rev = RCS_gettag (rcs, arg + 2, 1, NULL); /* Force tag match */ 1019 if (rev == NULL) 1020 { 1021 error (0, 0, "%s: no such revision %s", rcs->path, arg+2); 1022 status = 1; 1023 *p = ':'; /* restore the full text of the -m argument */ 1024 continue; 1025 } 1026 msg = p+1; 1027 1028 n = findnode (rcs->versions, rev); 1029 /* tags may exist against non-existing versions */ 1030 if (n == NULL) 1031 { 1032 error (0, 0, "%s: no such revision %s: %s", 1033 rcs->path, arg+2, rev); 1034 status = 1; 1035 *p = ':'; /* restore the full text of the -m argument */ 1036 free (rev); 1037 continue; 1038 } 1039 *p = ':'; /* restore the full text of the -m argument */ 1040 free (rev); 1041 1042 delta = n->data; 1043 if (delta->text == NULL) 1044 { 1045 delta->text = xmalloc (sizeof (Deltatext)); 1046 memset (delta->text, 0, sizeof (Deltatext)); 1047 } 1048 delta->text->version = xstrdup (delta->version); 1049 delta->text->log = make_message_rcsvalid (msg); 1050 break; 1051 1052 case 'l': 1053 status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0); 1054 break; 1055 case 'u': 1056 status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0); 1057 break; 1058 default: assert(0); /* can't happen */ 1059 } 1060 } 1061 1062 if (status == 0) 1063 { 1064 RCS_rewrite (rcs, NULL, NULL); 1065 if (!really_quiet) 1066 cvs_output ("done\n", 5); 1067 } 1068 else 1069 { 1070 /* Note that this message should only occur after another 1071 message has given a more specific error. The point of this 1072 additional message is to make it clear that the previous problems 1073 caused CVS to forget about the idea of modifying the RCS file. */ 1074 if (!really_quiet) 1075 error (0, 0, "RCS file for `%s' not modified.", finfo->file); 1076 RCS_abandon (rcs); 1077 } 1078 1079 exitfunc: 1080 freevers_ts (&vers); 1081 return status; 1082 } 1083 1084 1085 1086 /* 1087 * Print a warm fuzzy message 1088 */ 1089 /* ARGSUSED */ 1090 static Dtype 1091 admin_dirproc (void *callerdat, const char *dir, const char *repos, 1092 const char *update_dir, List *entries) 1093 { 1094 if (!quiet) 1095 error (0, 0, "Administrating %s", update_dir); 1096 return R_PROCESS; 1097 } 1098