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