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