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