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