admin.c revision 107484
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
18static Dtype admin_dirproc PROTO ((void *callerdat, char *dir,
19				   char *repos, char *update_dir,
20				   List *entries));
21static int admin_fileproc PROTO ((void *callerdat, struct file_info *finfo));
22
23static 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) Substitute keyword and value.\n",
35    "\t   kvl  Substitute keyword, value, and locker (if any).\n",
36    "\t   k    Substitute keyword only.\n",
37    "\t   o    Preserve original string.\n",
38    "\t   b    Like o, but mark file as binary.\n",
39    "\t   v    Substitute 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.  */
69struct 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).  */
114static void
115arg_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
142int
143admin (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' && c != 'q')
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_arg ("--");
500	send_files (argc, argv, 0, 0, SEND_NO_CONTENTS);
501	send_file_names (argc, argv, SEND_EXPAND_WILD);
502	send_to_server ("admin\012", 0);
503        err = get_responses_and_close ();
504	goto return_it;
505    }
506#endif /* CLIENT_SUPPORT */
507
508    lock_tree_for_write (argc, argv, 0, W_LOCAL, 0);
509
510    err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc,
511			   (DIRLEAVEPROC) NULL, (void *)&admin_data,
512			   argc, argv, 0,
513			   W_LOCAL, 0, LOCK_NONE, (char *) NULL, 1);
514    Lock_Cleanup ();
515
516 return_it:
517    if (admin_data.branch != NULL)
518	free (admin_data.branch);
519    if (admin_data.comment != NULL)
520	free (admin_data.comment);
521    if (admin_data.delete_revs != NULL)
522	free (admin_data.delete_revs);
523    if (admin_data.kflag != NULL)
524	free (admin_data.kflag);
525    if (admin_data.desc != NULL)
526	free (admin_data.desc);
527    for (i = 0; i < admin_data.ac; ++i)
528	free (admin_data.av[i]);
529    if (admin_data.av != NULL)
530	free (admin_data.av);
531
532    return (err);
533}
534
535/*
536 * Called to run "rcs" on a particular file.
537 */
538/* ARGSUSED */
539static int
540admin_fileproc (callerdat, finfo)
541    void *callerdat;
542    struct file_info *finfo;
543{
544    struct admin_data *admin_data = (struct admin_data *) callerdat;
545    Vers_TS *vers;
546    char *version;
547    int i;
548    int status = 0;
549    RCSNode *rcs, *rcs2;
550
551    vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
552
553    version = vers->vn_user;
554    if (version != NULL && strcmp (version, "0") == 0)
555    {
556	error (0, 0, "cannot admin newly added file `%s'", finfo->file);
557	goto exitfunc;
558    }
559
560    rcs = vers->srcfile;
561    if (rcs->flags & PARTIAL)
562	RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
563
564    status = 0;
565
566    if (!really_quiet)
567    {
568	cvs_output ("RCS file: ", 0);
569	cvs_output (rcs->path, 0);
570	cvs_output ("\n", 1);
571    }
572
573    if (admin_data->branch != NULL)
574    {
575	char *branch = &admin_data->branch[2];
576	if (*branch != '\0' && ! isdigit ((unsigned char) *branch))
577	{
578	    branch = RCS_whatbranch (rcs, admin_data->branch + 2);
579	    if (branch == NULL)
580	    {
581		error (0, 0, "%s: Symbolic name %s is undefined.",
582				rcs->path, admin_data->branch + 2);
583		status = 1;
584	    }
585	}
586	if (status == 0)
587	    RCS_setbranch (rcs, branch);
588	if (branch != NULL && branch != &admin_data->branch[2])
589	    free (branch);
590    }
591    if (admin_data->comment != NULL)
592    {
593	if (rcs->comment != NULL)
594	    free (rcs->comment);
595	rcs->comment = xstrdup (admin_data->comment + 2);
596    }
597    if (admin_data->set_strict)
598	rcs->strict_locks = 1;
599    if (admin_data->set_nonstrict)
600	rcs->strict_locks = 0;
601    if (admin_data->delete_revs != NULL)
602    {
603	char *s, *t, *rev1, *rev2;
604	/* Set for :, clear for ::.  */
605	int inclusive;
606	char *t2;
607
608	s = admin_data->delete_revs + 2;
609	inclusive = 1;
610	t = strchr (s, ':');
611	if (t != NULL)
612	{
613	    if (t[1] == ':')
614	    {
615		inclusive = 0;
616		t2 = t + 2;
617	    }
618	    else
619		t2 = t + 1;
620	}
621
622	/* Note that we don't support '-' for ranges.  RCS considers it
623	   obsolete and it is problematic with tags containing '-'.  "cvs log"
624	   has made the same decision.  */
625
626	if (t == NULL)
627	{
628	    /* -orev */
629	    rev1 = xstrdup (s);
630	    rev2 = xstrdup (s);
631	}
632	else if (t == s)
633	{
634	    /* -o:rev2 */
635	    rev1 = NULL;
636	    rev2 = xstrdup (t2);
637	}
638	else
639	{
640	    *t = '\0';
641	    rev1 = xstrdup (s);
642	    *t = ':';	/* probably unnecessary */
643	    if (*t2 == '\0')
644		/* -orev1: */
645		rev2 = NULL;
646	    else
647		/* -orev1:rev2 */
648		rev2 = xstrdup (t2);
649	}
650
651	if (rev1 == NULL && rev2 == NULL)
652	{
653	    /* RCS segfaults if `-o:' is given */
654	    error (0, 0, "no valid revisions specified in `%s' option",
655		   admin_data->delete_revs);
656	    status = 1;
657	}
658	else
659	{
660	    status |= RCS_delete_revs (rcs, rev1, rev2, inclusive);
661	    if (rev1)
662		free (rev1);
663	    if (rev2)
664		free (rev2);
665	}
666    }
667    if (admin_data->desc != NULL)
668    {
669	free (rcs->desc);
670	rcs->desc = xstrdup (admin_data->desc);
671    }
672    if (admin_data->kflag != NULL)
673    {
674	char *kflag = admin_data->kflag + 2;
675	char *oldexpand = RCS_getexpand (rcs);
676	if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0)
677	    RCS_setexpand (rcs, kflag);
678    }
679
680    /* Handle miscellaneous options.  TODO: decide whether any or all
681       of these should have their own fields in the admin_data
682       structure. */
683    for (i = 0; i < admin_data->ac; ++i)
684    {
685	char *arg;
686	char *p, *rev, *revnum, *tag, *msg;
687	char **users;
688	int argc, u;
689	Node *n;
690	RCSVers *delta;
691
692	arg = admin_data->av[i];
693	switch (arg[1])
694	{
695	    case 'a': /* fall through */
696	    case 'e':
697	        line2argv (&argc, &users, arg + 2, " ,\t\n");
698		if (arg[1] == 'a')
699		    for (u = 0; u < argc; ++u)
700			RCS_addaccess (rcs, users[u]);
701		else if (argc == 0)
702		    RCS_delaccess (rcs, NULL);
703		else
704		    for (u = 0; u < argc; ++u)
705			RCS_delaccess (rcs, users[u]);
706		free_names (&argc, users);
707		break;
708	    case 'A':
709
710		/* See admin-19a-admin and friends in sanity.sh for
711		   relative pathnames.  It makes sense to think in
712		   terms of a syntax which give pathnames relative to
713		   the repository or repository corresponding to the
714		   current directory or some such (and perhaps don't
715		   include ,v), but trying to worry about such things
716		   is a little pointless unless you first worry about
717		   whether "cvs admin -A" as a whole makes any sense
718		   (currently probably not, as access lists don't
719		   affect the behavior of CVS).  */
720
721		rcs2 = RCS_parsercsfile (arg + 2);
722		if (rcs2 == NULL)
723		    error (1, 0, "cannot continue");
724
725		p = xstrdup (RCS_getaccess (rcs2));
726	        line2argv (&argc, &users, p, " \t\n");
727		free (p);
728		freercsnode (&rcs2);
729
730		for (u = 0; u < argc; ++u)
731		    RCS_addaccess (rcs, users[u]);
732		free_names (&argc, users);
733		break;
734	    case 'n': /* fall through */
735	    case 'N':
736		if (arg[2] == '\0')
737		{
738		    cvs_outerr ("missing symbolic name after ", 0);
739		    cvs_outerr (arg, 0);
740		    cvs_outerr ("\n", 1);
741		    break;
742		}
743		p = strchr (arg, ':');
744		if (p == NULL)
745		{
746		    if (RCS_deltag (rcs, arg + 2) != 0)
747		    {
748			error (0, 0, "%s: Symbolic name %s is undefined.",
749			       rcs->path,
750			       arg + 2);
751			status = 1;
752			continue;
753		    }
754		    break;
755		}
756		*p = '\0';
757		tag = xstrdup (arg + 2);
758		*p++ = ':';
759
760		/* Option `n' signals an error if this tag is already bound. */
761		if (arg[1] == 'n')
762		{
763		    n = findnode (RCS_symbols (rcs), tag);
764		    if (n != NULL)
765		    {
766			error (0, 0,
767			       "%s: symbolic name %s already bound to %s",
768			       rcs->path,
769			       tag, n->data);
770			status = 1;
771			free (tag);
772			continue;
773		    }
774		}
775
776                /* Attempt to perform the requested tagging.  */
777
778		if ((*p == 0 && (rev = RCS_head (rcs)))
779                    || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */
780		{
781		    RCS_check_tag (tag); /* exit if not a valid tag */
782		    RCS_settag (rcs, tag, rev);
783		    free (rev);
784		}
785                else
786		{
787		    if (!really_quiet)
788			error (0, 0,
789			       "%s: Symbolic name or revision %s is undefined.",
790			       rcs->path, p);
791		    status = 1;
792		}
793		free (tag);
794		break;
795	    case 's':
796	        p = strchr (arg, ':');
797		if (p == NULL)
798		{
799		    tag = xstrdup (arg + 2);
800		    rev = RCS_head (rcs);
801		}
802		else
803		{
804		    *p = '\0';
805		    tag = xstrdup (arg + 2);
806		    *p++ = ':';
807		    rev = xstrdup (p);
808		}
809		revnum = RCS_gettag (rcs, rev, 0, NULL);
810		if (revnum != NULL)
811		{
812		    n = findnode (rcs->versions, revnum);
813		    free (revnum);
814		}
815		else
816		    n = NULL;
817		if (n == NULL)
818		{
819		    error (0, 0,
820			   "%s: can't set state of nonexisting revision %s",
821			   rcs->path,
822			   rev);
823		    free (rev);
824		    status = 1;
825		    continue;
826		}
827		free (rev);
828		delta = (RCSVers *) n->data;
829		free (delta->state);
830		delta->state = tag;
831		break;
832
833	    case 'm':
834	        p = strchr (arg, ':');
835		if (p == NULL)
836		{
837		    error (0, 0, "%s: -m option lacks revision number",
838			   rcs->path);
839		    status = 1;
840		    continue;
841		}
842		*p = '\0';
843		rev = RCS_gettag (rcs, arg + 2, 0, NULL);
844		if (rev == NULL)
845		{
846		    error (0, 0, "%s: no such revision %s", rcs->path, rev);
847		    status = 1;
848		    continue;
849		}
850		*p++ = ':';
851		msg = p;
852
853		n = findnode (rcs->versions, rev);
854		free (rev);
855		delta = (RCSVers *) n->data;
856		if (delta->text == NULL)
857		{
858		    delta->text = (Deltatext *) xmalloc (sizeof (Deltatext));
859		    memset ((void *) delta->text, 0, sizeof (Deltatext));
860		}
861		delta->text->version = xstrdup (delta->version);
862		delta->text->log = make_message_rcslegal (msg);
863		break;
864
865	    case 'l':
866	        status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0);
867		break;
868	    case 'u':
869		status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0);
870		break;
871	    default: assert(0);	/* can't happen */
872	}
873    }
874
875    if (status == 0)
876    {
877	RCS_rewrite (rcs, NULL, NULL);
878	if (!really_quiet)
879	    cvs_output ("done\n", 5);
880    }
881    else
882    {
883	/* Note that this message should only occur after another
884	   message has given a more specific error.  The point of this
885	   additional message is to make it clear that the previous problems
886	   caused CVS to forget about the idea of modifying the RCS file.  */
887	if (!really_quiet)
888	    error (0, 0, "RCS file for `%s' not modified.", finfo->file);
889	RCS_abandon (rcs);
890    }
891
892  exitfunc:
893    freevers_ts (&vers);
894    return status;
895}
896
897/*
898 * Print a warm fuzzy message
899 */
900/* ARGSUSED */
901static Dtype
902admin_dirproc (callerdat, dir, repos, update_dir, entries)
903    void *callerdat;
904    char *dir;
905    char *repos;
906    char *update_dir;
907    List *entries;
908{
909    if (!quiet)
910	error (0, 0, "Administrating %s", update_dir);
911    return (R_PROCESS);
912}
913