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