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