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