xref: /netbsd/external/gpl2/xcvs/dist/src/admin.c (revision 3cd63638)
1a7c91847Schristos /*
2a7c91847Schristos  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3a7c91847Schristos  *
4a7c91847Schristos  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5a7c91847Schristos  *                                  and others.
6a7c91847Schristos  *
7a7c91847Schristos  * Portions Copyright (c) 1992, Brian Berliner and Jeff Polk
8a7c91847Schristos  * Portions Copyright (c) 1989-1992, Brian Berliner
9a7c91847Schristos  *
10a7c91847Schristos  * You may distribute under the terms of the GNU General Public License as
11a7c91847Schristos  * specified in the README file that comes with the CVS source distribution.
12a7c91847Schristos  *
13a7c91847Schristos  * Administration ("cvs admin")
14a7c91847Schristos  *
15a7c91847Schristos  */
16*3cd63638Schristos #include <sys/cdefs.h>
17*3cd63638Schristos __RCSID("$NetBSD: admin.c,v 1.6 2016/05/17 14:00:09 christos Exp $");
18a7c91847Schristos 
19a7c91847Schristos #include "cvs.h"
20a7c91847Schristos #include <grp.h>
21a7c91847Schristos 
22a7c91847Schristos static Dtype admin_dirproc (void *callerdat, const char *dir,
23a7c91847Schristos                             const char *repos, const char *update_dir,
24a7c91847Schristos                             List *entries);
25a7c91847Schristos static int admin_fileproc (void *callerdat, struct file_info *finfo);
26a7c91847Schristos 
27a7c91847Schristos static const char *const admin_usage[] =
28a7c91847Schristos {
29a7c91847Schristos     "Usage: %s %s [options] files...\n",
30a7c91847Schristos     "\t-a users   Append (comma-separated) user names to access list.\n",
31a7c91847Schristos     "\t-A file    Append another file's access list.\n",
32a7c91847Schristos     "\t-b[rev]    Set default branch (highest branch on trunk if omitted).\n",
33a7c91847Schristos     "\t-c string  Set comment leader.\n",
34a7c91847Schristos     "\t-e[users]  Remove (comma-separated) user names from access list\n",
35a7c91847Schristos     "\t           (all names if omitted).\n",
36a7c91847Schristos     "\t-I         Run interactively.\n",
37a7c91847Schristos     "\t-k subst   Set keyword substitution mode:\n",
38a7c91847Schristos     "\t   kv   (Default) Substitute keyword and value.\n",
39a7c91847Schristos     "\t   kvl  Substitute keyword, value, and locker (if any).\n",
40a7c91847Schristos     "\t   k    Substitute keyword only.\n",
41a7c91847Schristos     "\t   o    Preserve original string.\n",
42a7c91847Schristos     "\t   b    Like o, but mark file as binary.\n",
43a7c91847Schristos     "\t   v    Substitute value only.\n",
44a7c91847Schristos     "\t-l[rev]    Lock revision (latest revision on branch,\n",
45a7c91847Schristos     "\t           latest revision on trunk if omitted).\n",
46a7c91847Schristos     "\t-L         Set strict locking.\n",
47a7c91847Schristos     "\t-m rev:msg  Replace revision's log message.\n",
48a7c91847Schristos     "\t-n tag[:[rev]]  Tag branch or revision.  If :rev is omitted,\n",
49a7c91847Schristos     "\t                delete the tag; if rev is omitted, tag the latest\n",
50a7c91847Schristos     "\t                revision on the default branch.\n",
51a7c91847Schristos     "\t-N tag[:[rev]]  Same as -n except override existing tag.\n",
52a7c91847Schristos     "\t-o range   Delete (outdate) specified range of revisions:\n",
53a7c91847Schristos     "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
54a7c91847Schristos     "\t   rev1::rev2  Between rev1 and rev2, excluding rev1 and rev2.\n",
55a7c91847Schristos     "\t   rev:        rev and following revisions on the same branch.\n",
56a7c91847Schristos     "\t   rev::       After rev on the same branch.\n",
57a7c91847Schristos     "\t   :rev        rev and previous revisions on the same branch.\n",
58a7c91847Schristos     "\t   ::rev       Before rev on the same branch.\n",
59a7c91847Schristos     "\t   rev         Just rev.\n",
60a7c91847Schristos     "\t-q         Run quietly.\n",
61a7c91847Schristos     "\t-s state[:rev]  Set revision state (latest revision on branch,\n",
62a7c91847Schristos     "\t                latest revision on trunk if omitted).\n",
63a7c91847Schristos     "\t-t[file]   Get descriptive text from file (stdin if omitted).\n",
64a7c91847Schristos     "\t-t-string  Set descriptive text.\n",
65a7c91847Schristos     "\t-u[rev]    Unlock the revision (latest revision on branch,\n",
66a7c91847Schristos     "\t           latest revision on trunk if omitted).\n",
67a7c91847Schristos     "\t-U         Unset strict locking.\n",
68a7c91847Schristos     "(Specify the --help global option for a list of other help options)\n",
69a7c91847Schristos     NULL
70a7c91847Schristos };
71a7c91847Schristos 
72a7c91847Schristos /* This structure is used to pass information through start_recursion.  */
73a7c91847Schristos struct admin_data
74a7c91847Schristos {
75a7c91847Schristos     /* Set default branch (-b).  It is "-b" followed by the value
76a7c91847Schristos        given, or NULL if not specified, or merely "-b" if -b is
77a7c91847Schristos        specified without a value.  */
78a7c91847Schristos     char *branch;
79a7c91847Schristos 
80a7c91847Schristos     /* Set comment leader (-c).  It is "-c" followed by the value
81a7c91847Schristos        given, or NULL if not specified.  The comment leader is
82a7c91847Schristos        relevant only for old versions of RCS, but we let people set it
83a7c91847Schristos        anyway.  */
84a7c91847Schristos     char *comment;
85a7c91847Schristos 
86a7c91847Schristos     /* Set strict locking (-L).  */
87a7c91847Schristos     int set_strict;
88a7c91847Schristos 
89a7c91847Schristos     /* Set nonstrict locking (-U).  */
90a7c91847Schristos     int set_nonstrict;
91a7c91847Schristos 
92a7c91847Schristos     /* Delete revisions (-o).  It is "-o" followed by the value specified.  */
93a7c91847Schristos     char *delete_revs;
94a7c91847Schristos 
95a7c91847Schristos     /* Keyword substitution mode (-k), e.g. "-kb".  */
96a7c91847Schristos     char *kflag;
97a7c91847Schristos 
98a7c91847Schristos     /* Description (-t).  */
99a7c91847Schristos     char *desc;
100a7c91847Schristos 
101a7c91847Schristos     /* Interactive (-I).  Problematic with client/server.  */
102a7c91847Schristos     int interactive;
103a7c91847Schristos 
104a7c91847Schristos     /* This is the cheesy part.  It is a vector with the options which
105a7c91847Schristos        we don't deal with above (e.g. "-afoo" "-abar,baz").  In the future
106a7c91847Schristos        this presumably will be replaced by other variables which break
107a7c91847Schristos        out the data in a more convenient fashion.  AV as well as each of
108a7c91847Schristos        the strings it points to is malloc'd.  */
109a7c91847Schristos     int ac;
110a7c91847Schristos     char **av;
111a7c91847Schristos     int av_alloc;
112274254cdSchristos 
113274254cdSchristos     /* This contains a printable version of the command line used
114274254cdSchristos      * for logging
115274254cdSchristos      */
116274254cdSchristos     char *cmdline;
117a7c91847Schristos };
118a7c91847Schristos 
119a7c91847Schristos /* Add an argument.  OPT is the option letter, e.g. 'a'.  ARG is the
120a7c91847Schristos    argument to that option, or NULL if omitted (whether NULL can actually
121a7c91847Schristos    happen depends on whether the option was specified as optional to
122a7c91847Schristos    getopt).  */
123a7c91847Schristos static void
arg_add(struct admin_data * dat,int opt,char * arg)124a7c91847Schristos arg_add (struct admin_data *dat, int opt, char *arg)
125a7c91847Schristos {
126a7c91847Schristos     char *newelt = Xasprintf ("-%c%s", opt, arg ? arg : "");
127a7c91847Schristos 
128a7c91847Schristos     if (dat->av_alloc == 0)
129a7c91847Schristos     {
130a7c91847Schristos 	dat->av_alloc = 1;
131a7c91847Schristos 	dat->av = xnmalloc (dat->av_alloc, sizeof (*dat->av));
132a7c91847Schristos     }
133a7c91847Schristos     else if (dat->ac >= dat->av_alloc)
134a7c91847Schristos     {
135a7c91847Schristos 	dat->av_alloc *= 2;
136a7c91847Schristos 	dat->av = xnrealloc (dat->av, dat->av_alloc, sizeof (*dat->av));
137a7c91847Schristos     }
138a7c91847Schristos     dat->av[dat->ac++] = newelt;
139a7c91847Schristos }
140a7c91847Schristos 
141a7c91847Schristos 
142a7c91847Schristos 
143a7c91847Schristos /*
144a7c91847Schristos  * callback proc to run a script when admin finishes.
145a7c91847Schristos  */
146a7c91847Schristos static int
postadmin_proc(const char * repository,const char * filter,void * closure)147a7c91847Schristos postadmin_proc (const char *repository, const char *filter, void *closure)
148a7c91847Schristos {
149a7c91847Schristos     char *cmdline;
150a7c91847Schristos     const char *srepos = Short_Repository (repository);
151a7c91847Schristos 
152a7c91847Schristos     TRACE (TRACE_FUNCTION, "postadmin_proc (%s, %s)", repository, filter);
153a7c91847Schristos 
154a7c91847Schristos     /* %c = cvs_cmd_name
155a7c91847Schristos      * %R = referrer
156a7c91847Schristos      * %p = shortrepos
157a7c91847Schristos      * %r = repository
158a7c91847Schristos      */
159a7c91847Schristos     /*
160a7c91847Schristos      * Cast any NULL arguments as appropriate pointers as this is an
161a7c91847Schristos      * stdarg function and we need to be certain the caller gets what
162a7c91847Schristos      * is expected.
163a7c91847Schristos      */
164a7c91847Schristos     cmdline = format_cmdline (
165a7c91847Schristos #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
166a7c91847Schristos 	                      false, srepos,
167a7c91847Schristos #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
168a7c91847Schristos 	                      filter,
169a7c91847Schristos 	                      "c", "s", cvs_cmd_name,
170a7c91847Schristos #ifdef SERVER_SUPPORT
171a7c91847Schristos 	                      "R", "s", referrer ? referrer->original : "NONE",
172a7c91847Schristos #endif /* SERVER_SUPPORT */
173a7c91847Schristos 	                      "p", "s", srepos,
174a7c91847Schristos 	                      "r", "s", current_parsed_root->directory,
175a7c91847Schristos 	                      (char *) NULL);
176a7c91847Schristos 
177a7c91847Schristos     if (!cmdline || !strlen (cmdline))
178a7c91847Schristos     {
179a7c91847Schristos 	if (cmdline) free (cmdline);
180a7c91847Schristos 	error (0, 0, "postadmin proc resolved to the empty string!");
181a7c91847Schristos 	return 1;
182a7c91847Schristos     }
183a7c91847Schristos 
184a7c91847Schristos     run_setup (cmdline);
185a7c91847Schristos 
186a7c91847Schristos     free (cmdline);
187a7c91847Schristos 
188a7c91847Schristos     /* FIXME - read the comment in verifymsg_proc() about why we use abs()
189a7c91847Schristos      * below() and shouldn't.
190a7c91847Schristos      */
191a7c91847Schristos     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
192a7c91847Schristos 			  RUN_NORMAL | RUN_SIGIGNORE));
193a7c91847Schristos }
194a7c91847Schristos 
195a7c91847Schristos 
196a7c91847Schristos 
197a7c91847Schristos /*
198a7c91847Schristos  * Call any postadmin procs.
199a7c91847Schristos  */
200a7c91847Schristos static int
admin_filesdoneproc(void * callerdat,int err,const char * repository,const char * update_dir,List * entries)201a7c91847Schristos admin_filesdoneproc (void *callerdat, int err, const char *repository,
202a7c91847Schristos                      const char *update_dir, List *entries)
203a7c91847Schristos {
204a7c91847Schristos     TRACE (TRACE_FUNCTION, "admin_filesdoneproc (%d, %s, %s)", err, repository,
205a7c91847Schristos            update_dir);
206a7c91847Schristos     Parse_Info (CVSROOTADM_POSTADMIN, repository, postadmin_proc, PIOPT_ALL,
207a7c91847Schristos                 NULL);
208a7c91847Schristos 
209a7c91847Schristos     return err;
210a7c91847Schristos }
211a7c91847Schristos 
212a7c91847Schristos 
213274254cdSchristos static size_t
wescape(char * dst,const char * src)214274254cdSchristos wescape (char *dst, const char *src)
215274254cdSchristos {
216274254cdSchristos     const unsigned char *s = src;
217274254cdSchristos     char *d = dst;
218274254cdSchristos     for (; *s; s++) {
219274254cdSchristos 	if (!isprint(*s) || isspace(*s) || *s == '|') {
220274254cdSchristos 	    *d++ = '\\';
221274254cdSchristos 	    *d++ = ((*s >> 6) & 3) + '0';
222274254cdSchristos 	    *d++ = ((*s >> 3) & 7) + '0';
223274254cdSchristos 	    *d++ = ((*s >> 0) & 7) + '0';
224274254cdSchristos 	} else  {
225274254cdSchristos 	    *d++ = *s;
226274254cdSchristos 	}
227274254cdSchristos     }
228274254cdSchristos     *d = '\0';
229274254cdSchristos     return d - dst;
230274254cdSchristos }
231274254cdSchristos 
232274254cdSchristos static char *
makecmdline(int argc,char ** argv)233274254cdSchristos makecmdline (int argc, char **argv)
234274254cdSchristos {
235274254cdSchristos     size_t clen = 1024, wlen = 1024, len, cpos = 0, i;
236274254cdSchristos     char *cmd = xmalloc(clen);
237274254cdSchristos     char *word = xmalloc(wlen);
238274254cdSchristos 
239274254cdSchristos     for (i = 0; i < argc; i++) {
240274254cdSchristos 	char *arg = (strncmp(argv[i], "cvs ", 4) == 0) ? argv[i] + 4 : argv[i];
241274254cdSchristos 	len = strlen(arg);
242274254cdSchristos 	if (len * 4 < wlen) {
243274254cdSchristos 	    wlen += len * 4;
244274254cdSchristos 	    word = xrealloc(word, wlen);
245274254cdSchristos 	}
246274254cdSchristos 	len = wescape(word, arg);
247274254cdSchristos 	if (clen - cpos < len + 2) {
248274254cdSchristos 	    clen += len + 2;
249274254cdSchristos 	    cmd = xrealloc(cmd, clen);
250274254cdSchristos 	}
251274254cdSchristos 	memcpy(&cmd[cpos], word, len);
252274254cdSchristos 	cpos += len;
253274254cdSchristos 	cmd[cpos++] = ' ';
254274254cdSchristos     }
255274254cdSchristos     if (cpos != 0)
256274254cdSchristos 	cmd[cpos - 1] = '\0';
257274254cdSchristos     else
258274254cdSchristos 	cmd[cpos] = '\0';
259274254cdSchristos     free(word);
260274254cdSchristos     return cmd;
261274254cdSchristos }
262274254cdSchristos 
263274254cdSchristos int
admin_group_member(void)264274254cdSchristos admin_group_member (void)
265274254cdSchristos {
266274254cdSchristos     struct group *grp;
267274254cdSchristos     int i;
268274254cdSchristos 
269889c434eSchristos     if (config == NULL || config->UserAdminGroup == NULL)
270274254cdSchristos 	return 1;
271274254cdSchristos 
272889c434eSchristos     if ((grp = getgrnam(config->UserAdminGroup)) == NULL)
273274254cdSchristos 	return 0;
274274254cdSchristos 
275274254cdSchristos     {
276274254cdSchristos #ifdef HAVE_GETGROUPS
277274254cdSchristos 	gid_t *grps;
278274254cdSchristos 	int n;
279274254cdSchristos 
280274254cdSchristos 	/* get number of auxiliary groups */
281274254cdSchristos 	n = getgroups (0, NULL);
282274254cdSchristos 	if (n < 0)
283274254cdSchristos 	    error (1, errno, "unable to get number of auxiliary groups");
284274254cdSchristos 	grps = (gid_t *) xmalloc((n + 1) * sizeof *grps);
285274254cdSchristos 	n = getgroups (n, grps);
286274254cdSchristos 	if (n < 0)
287274254cdSchristos 	    error (1, errno, "unable to get list of auxiliary groups");
288274254cdSchristos 	grps[n] = getgid();
289274254cdSchristos 	for (i = 0; i <= n; i++)
290274254cdSchristos 	    if (grps[i] == grp->gr_gid) break;
291274254cdSchristos 	free (grps);
292274254cdSchristos 	if (i > n)
293274254cdSchristos 	    return 0;
294274254cdSchristos #else
295274254cdSchristos 	char *me = getcaller();
296274254cdSchristos 	char **grnam;
297274254cdSchristos 
298274254cdSchristos 	for (grnam = grp->gr_mem; *grnam; grnam++)
299274254cdSchristos 	    if (strcmp (*grnam, me) == 0) break;
300274254cdSchristos 	if (!*grnam && getgid() != grp->gr_gid)
301274254cdSchristos 	    return 0;
302274254cdSchristos #endif
303274254cdSchristos     }
304889c434eSchristos     return 1;
305274254cdSchristos }
306a7c91847Schristos 
307a7c91847Schristos int
admin(int argc,char ** argv)308a7c91847Schristos admin (int argc, char **argv)
309a7c91847Schristos {
310a7c91847Schristos     int err;
311a7c91847Schristos     struct admin_data admin_data;
312a7c91847Schristos     int c;
313a7c91847Schristos     int i;
314a7c91847Schristos     bool only_allowed_options;
315a7c91847Schristos 
316a7c91847Schristos     if (argc <= 1)
317a7c91847Schristos 	usage (admin_usage);
318a7c91847Schristos 
319a7c91847Schristos     wrap_setup ();
320a7c91847Schristos 
321a7c91847Schristos     memset (&admin_data, 0, sizeof admin_data);
322274254cdSchristos     admin_data.cmdline = makecmdline (argc, argv);
323a7c91847Schristos 
324a7c91847Schristos     /* TODO: get rid of `-' switch notation in admin_data.  For
325a7c91847Schristos        example, admin_data->branch should be not `-bfoo' but simply `foo'. */
326a7c91847Schristos 
327889c434eSchristos     getoptreset ();
328a7c91847Schristos     only_allowed_options = true;
329a7c91847Schristos     while ((c = getopt (argc, argv,
330a7c91847Schristos 			"+ib::c:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1)
331a7c91847Schristos     {
332ecf07e79Sapb 	if (config != NULL) {
333274254cdSchristos 	    if (c != 'q' && !strchr (config->UserAdminOptions, c))
334a7c91847Schristos 		only_allowed_options = false;
335ecf07e79Sapb 	} else {
336ecf07e79Sapb #ifdef CLIENT_SUPPORT
337ecf07e79Sapb 	    assert(current_parsed_root->isremote);
338ecf07e79Sapb 	    only_allowed_options = false;
339ecf07e79Sapb #else
340ecf07e79Sapb 	    assert(0); /* config should not be NULL, except in a client */
341ecf07e79Sapb #endif
342ecf07e79Sapb 	}
343a7c91847Schristos 
344a7c91847Schristos 	switch (c)
345a7c91847Schristos 	{
346a7c91847Schristos 	    case 'i':
347a7c91847Schristos 		/* This has always been documented as useless in cvs.texinfo
348a7c91847Schristos 		   and it really is--admin_fileproc silently does nothing
349a7c91847Schristos 		   if vers->vn_user is NULL. */
350a7c91847Schristos 		error (0, 0, "the -i option to admin is not supported");
351a7c91847Schristos 		error (0, 0, "run add or import to create an RCS file");
352a7c91847Schristos 		goto usage_error;
353a7c91847Schristos 
354a7c91847Schristos 	    case 'b':
355a7c91847Schristos 		if (admin_data.branch != NULL)
356a7c91847Schristos 		{
357a7c91847Schristos 		    error (0, 0, "duplicate 'b' option");
358a7c91847Schristos 		    goto usage_error;
359a7c91847Schristos 		}
360a7c91847Schristos 		if (optarg == NULL)
361a7c91847Schristos 		    admin_data.branch = xstrdup ("-b");
362a7c91847Schristos 		else
363a7c91847Schristos 		    admin_data.branch = Xasprintf ("-b%s", optarg);
364a7c91847Schristos 		break;
365a7c91847Schristos 
366a7c91847Schristos 	    case 'c':
367a7c91847Schristos 		if (admin_data.comment != NULL)
368a7c91847Schristos 		{
369a7c91847Schristos 		    error (0, 0, "duplicate 'c' option");
370a7c91847Schristos 		    goto usage_error;
371a7c91847Schristos 		}
372a7c91847Schristos 		admin_data.comment = Xasprintf ("-c%s", optarg);
373a7c91847Schristos 		break;
374a7c91847Schristos 
375a7c91847Schristos 	    case 'a':
376a7c91847Schristos 		arg_add (&admin_data, 'a', optarg);
377a7c91847Schristos 		break;
378a7c91847Schristos 
379a7c91847Schristos 	    case 'A':
380a7c91847Schristos 		/* In the client/server case, this is cheesy because
381a7c91847Schristos 		   we just pass along the name of the RCS file, which
382a7c91847Schristos 		   then will want to exist on the server.  This is
383a7c91847Schristos 		   accidental; having the client specify a pathname on
384a7c91847Schristos 		   the server is not a design feature of the protocol.  */
385a7c91847Schristos 		arg_add (&admin_data, 'A', optarg);
386a7c91847Schristos 		break;
387a7c91847Schristos 
388a7c91847Schristos 	    case 'e':
389a7c91847Schristos 		arg_add (&admin_data, 'e', optarg);
390a7c91847Schristos 		break;
391a7c91847Schristos 
392a7c91847Schristos 	    case 'l':
393a7c91847Schristos 		/* Note that multiple -l options are valid.  */
394a7c91847Schristos 		arg_add (&admin_data, 'l', optarg);
395a7c91847Schristos 		break;
396a7c91847Schristos 
397a7c91847Schristos 	    case 'u':
398a7c91847Schristos 		/* Note that multiple -u options are valid.  */
399a7c91847Schristos 		arg_add (&admin_data, 'u', optarg);
400a7c91847Schristos 		break;
401a7c91847Schristos 
402a7c91847Schristos 	    case 'L':
403a7c91847Schristos 		/* Probably could also complain if -L is specified multiple
404a7c91847Schristos 		   times, although RCS doesn't and I suppose it is reasonable
405a7c91847Schristos 		   just to have it mean the same as a single -L.  */
406a7c91847Schristos 		if (admin_data.set_nonstrict)
407a7c91847Schristos 		{
408a7c91847Schristos 		    error (0, 0, "-U and -L are incompatible");
409a7c91847Schristos 		    goto usage_error;
410a7c91847Schristos 		}
411a7c91847Schristos 		admin_data.set_strict = 1;
412a7c91847Schristos 		break;
413a7c91847Schristos 
414a7c91847Schristos 	    case 'U':
415a7c91847Schristos 		/* Probably could also complain if -U is specified multiple
416a7c91847Schristos 		   times, although RCS doesn't and I suppose it is reasonable
417a7c91847Schristos 		   just to have it mean the same as a single -U.  */
418a7c91847Schristos 		if (admin_data.set_strict)
419a7c91847Schristos 		{
420a7c91847Schristos 		    error (0, 0, "-U and -L are incompatible");
421a7c91847Schristos 		    goto usage_error;
422a7c91847Schristos 		}
423a7c91847Schristos 		admin_data.set_nonstrict = 1;
424a7c91847Schristos 		break;
425a7c91847Schristos 
426a7c91847Schristos 	    case 'n':
427a7c91847Schristos 		/* Mostly similar to cvs tag.  Could also be parsing
428a7c91847Schristos 		   the syntax of optarg, although for now we just pass
429a7c91847Schristos 		   it to rcs as-is.  Note that multiple -n options are
430a7c91847Schristos 		   valid.  */
431a7c91847Schristos 		arg_add (&admin_data, 'n', optarg);
432a7c91847Schristos 		break;
433a7c91847Schristos 
434a7c91847Schristos 	    case 'N':
435a7c91847Schristos 		/* Mostly similar to cvs tag.  Could also be parsing
436a7c91847Schristos 		   the syntax of optarg, although for now we just pass
437a7c91847Schristos 		   it to rcs as-is.  Note that multiple -N options are
438a7c91847Schristos 		   valid.  */
439a7c91847Schristos 		arg_add (&admin_data, 'N', optarg);
440a7c91847Schristos 		break;
441a7c91847Schristos 
442a7c91847Schristos 	    case 'm':
443a7c91847Schristos 		/* Change log message.  Could also be parsing the syntax
444a7c91847Schristos 		   of optarg, although for now we just pass it to rcs
445a7c91847Schristos 		   as-is.  Note that multiple -m options are valid.  */
446a7c91847Schristos 		arg_add (&admin_data, 'm', optarg);
447a7c91847Schristos 		break;
448a7c91847Schristos 
449a7c91847Schristos 	    case 'o':
450a7c91847Schristos 		/* Delete revisions.  Probably should also be parsing the
451a7c91847Schristos 		   syntax of optarg, so that the client can give errors
452a7c91847Schristos 		   rather than making the server take care of that.
453a7c91847Schristos 		   Other than that I'm not sure whether it matters much
454a7c91847Schristos 		   whether we parse it here or in admin_fileproc.
455a7c91847Schristos 
456a7c91847Schristos 		   Note that multiple -o options are invalid, in RCS
457a7c91847Schristos 		   as well as here.  */
458a7c91847Schristos 
459a7c91847Schristos 		if (admin_data.delete_revs != NULL)
460a7c91847Schristos 		{
461a7c91847Schristos 		    error (0, 0, "duplicate '-o' option");
462a7c91847Schristos 		    goto usage_error;
463a7c91847Schristos 		}
464a7c91847Schristos 		admin_data.delete_revs = Xasprintf ("-o%s", optarg);
465a7c91847Schristos 		break;
466a7c91847Schristos 
467a7c91847Schristos 	    case 's':
468a7c91847Schristos 		/* Note that multiple -s options are valid.  */
469a7c91847Schristos 		arg_add (&admin_data, 's', optarg);
470a7c91847Schristos 		break;
471a7c91847Schristos 
472a7c91847Schristos 	    case 't':
473a7c91847Schristos 		if (admin_data.desc != NULL)
474a7c91847Schristos 		{
475a7c91847Schristos 		    error (0, 0, "duplicate 't' option");
476a7c91847Schristos 		    goto usage_error;
477a7c91847Schristos 		}
478a7c91847Schristos 		if (optarg != NULL && optarg[0] == '-')
479a7c91847Schristos 		    admin_data.desc = xstrdup (optarg + 1);
480a7c91847Schristos 		else
481a7c91847Schristos 		{
482a7c91847Schristos 		    size_t bufsize = 0;
483a7c91847Schristos 		    size_t len;
484a7c91847Schristos 
485a7c91847Schristos 		    get_file (optarg, optarg, "r", &admin_data.desc,
486a7c91847Schristos 			      &bufsize, &len);
487a7c91847Schristos 		}
488a7c91847Schristos 		break;
489a7c91847Schristos 
490a7c91847Schristos 	    case 'I':
491a7c91847Schristos 		/* At least in RCS this can be specified several times,
492a7c91847Schristos 		   with the same meaning as being specified once.  */
493a7c91847Schristos 		admin_data.interactive = 1;
494a7c91847Schristos 		break;
495a7c91847Schristos 
496a7c91847Schristos 	    case 'q':
497a7c91847Schristos 		/* Silently set the global really_quiet flag.  This keeps admin in
498a7c91847Schristos 		 * sync with the RCS man page and allows us to silently support
499a7c91847Schristos 		 * older servers when necessary.
500a7c91847Schristos 		 *
501a7c91847Schristos 		 * Some logic says we might want to output a deprecation warning
502a7c91847Schristos 		 * here, but I'm opting not to in order to stay quietly in sync
503a7c91847Schristos 		 * with the RCS man page.
504a7c91847Schristos 		 */
505a7c91847Schristos 		really_quiet = 1;
506a7c91847Schristos 		break;
507a7c91847Schristos 
508a7c91847Schristos 	    case 'x':
509a7c91847Schristos 		error (0, 0, "the -x option has never done anything useful");
510a7c91847Schristos 		error (0, 0, "RCS files in CVS always end in ,v");
511a7c91847Schristos 		goto usage_error;
512a7c91847Schristos 
513a7c91847Schristos 	    case 'V':
514a7c91847Schristos 		/* No longer supported. */
515a7c91847Schristos 		error (0, 0, "the `-V' option is obsolete");
516a7c91847Schristos 		break;
517a7c91847Schristos 
518a7c91847Schristos 	    case 'k':
519a7c91847Schristos 		if (admin_data.kflag != NULL)
520a7c91847Schristos 		{
521a7c91847Schristos 		    error (0, 0, "duplicate '-k' option");
522a7c91847Schristos 		    goto usage_error;
523a7c91847Schristos 		}
524a7c91847Schristos 		admin_data.kflag = RCS_check_kflag (optarg);
525a7c91847Schristos 		break;
526a7c91847Schristos 	    default:
527a7c91847Schristos 	    case '?':
528a7c91847Schristos 		/* getopt will have printed an error message.  */
529a7c91847Schristos 
530a7c91847Schristos 	    usage_error:
531a7c91847Schristos 		/* Don't use cvs_cmd_name; it might be "server".  */
532a7c91847Schristos 	        error (1, 0, "specify %s -H admin for usage information",
533a7c91847Schristos 		       program_name);
534a7c91847Schristos 	}
535a7c91847Schristos     }
536a7c91847Schristos     argc -= optind;
537a7c91847Schristos     argv += optind;
538a7c91847Schristos 
539a7c91847Schristos     /* The use of `cvs admin -k' is unrestricted.  However, any other
540a7c91847Schristos        option is restricted if the group CVS_ADMIN_GROUP exists on the
541a7c91847Schristos        server.  */
542a7c91847Schristos     /* This is only "secure" on the server, since the user could edit the
543a7c91847Schristos      * RCS file on a local host, but some people like this kind of
544a7c91847Schristos      * check anyhow.  The alternative would be to check only when
545a7c91847Schristos      * (server_active) rather than when not on the client.
546a7c91847Schristos      */
547274254cdSchristos     if (!only_allowed_options && !admin_group_member())
548a7c91847Schristos 	error (1, 0, "usage is restricted to members of the group %s",
549a7c91847Schristos 	       CVS_ADMIN_GROUP);
550a7c91847Schristos 
551a7c91847Schristos     for (i = 0; i < admin_data.ac; ++i)
552a7c91847Schristos     {
553a7c91847Schristos 	assert (admin_data.av[i][0] == '-');
554a7c91847Schristos 	switch (admin_data.av[i][1])
555a7c91847Schristos 	{
556a7c91847Schristos 	    case 'm':
557a7c91847Schristos 	    case 'l':
558a7c91847Schristos 	    case 'u':
559a7c91847Schristos 		check_numeric (&admin_data.av[i][2], argc, argv);
560a7c91847Schristos 		break;
561a7c91847Schristos 	    default:
562a7c91847Schristos 		break;
563a7c91847Schristos 	}
564a7c91847Schristos     }
565a7c91847Schristos     if (admin_data.branch != NULL)
566a7c91847Schristos 	check_numeric (admin_data.branch + 2, argc, argv);
567a7c91847Schristos     if (admin_data.delete_revs != NULL)
568a7c91847Schristos     {
569a7c91847Schristos 	char *p;
570a7c91847Schristos 
571a7c91847Schristos 	check_numeric (admin_data.delete_revs + 2, argc, argv);
572a7c91847Schristos 	p = strchr (admin_data.delete_revs + 2, ':');
573a7c91847Schristos 	if (p != NULL && isdigit ((unsigned char) p[1]))
574a7c91847Schristos 	    check_numeric (p + 1, argc, argv);
575a7c91847Schristos 	else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2]))
576a7c91847Schristos 	    check_numeric (p + 2, argc, argv);
577a7c91847Schristos     }
578a7c91847Schristos 
579a7c91847Schristos #ifdef CLIENT_SUPPORT
580a7c91847Schristos     if (current_parsed_root->isremote)
581a7c91847Schristos     {
582a7c91847Schristos 	/* We're the client side.  Fire up the remote server.  */
583a7c91847Schristos 	start_server ();
584a7c91847Schristos 
585a7c91847Schristos 	ign_setup ();
586a7c91847Schristos 
587a7c91847Schristos 	/* Note that option_with_arg does not work for us, because some
588a7c91847Schristos 	   of the options must be sent without a space between the option
589a7c91847Schristos 	   and its argument.  */
590a7c91847Schristos 	if (admin_data.interactive)
591a7c91847Schristos 	    error (1, 0, "-I option not useful with client/server");
592a7c91847Schristos 	if (admin_data.branch != NULL)
593a7c91847Schristos 	    send_arg (admin_data.branch);
594a7c91847Schristos 	if (admin_data.comment != NULL)
595a7c91847Schristos 	    send_arg (admin_data.comment);
596a7c91847Schristos 	if (admin_data.set_strict)
597a7c91847Schristos 	    send_arg ("-L");
598a7c91847Schristos 	if (admin_data.set_nonstrict)
599a7c91847Schristos 	    send_arg ("-U");
600a7c91847Schristos 	if (admin_data.delete_revs != NULL)
601a7c91847Schristos 	    send_arg (admin_data.delete_revs);
602a7c91847Schristos 	if (admin_data.desc != NULL)
603a7c91847Schristos 	{
604a7c91847Schristos 	    char *p = admin_data.desc;
605a7c91847Schristos 	    send_to_server ("Argument -t-", 0);
606a7c91847Schristos 	    while (*p)
607a7c91847Schristos 	    {
608a7c91847Schristos 		if (*p == '\n')
609a7c91847Schristos 		{
610a7c91847Schristos 		    send_to_server ("\012Argumentx ", 0);
611a7c91847Schristos 		    ++p;
612a7c91847Schristos 		}
613a7c91847Schristos 		else
614a7c91847Schristos 		{
615a7c91847Schristos 		    char *q = strchr (p, '\n');
616a7c91847Schristos 		    if (q == NULL) q = p + strlen (p);
617a7c91847Schristos 		    send_to_server (p, q - p);
618a7c91847Schristos 		    p = q;
619a7c91847Schristos 		}
620a7c91847Schristos 	    }
621a7c91847Schristos 	    send_to_server ("\012", 1);
622a7c91847Schristos 	}
623a7c91847Schristos 	/* Send this for all really_quiets since we know that it will be silently
624a7c91847Schristos 	 * ignored when unneeded.  This supports old servers.
625a7c91847Schristos 	 */
626a7c91847Schristos 	if (really_quiet)
627a7c91847Schristos 	    send_arg ("-q");
628a7c91847Schristos 	if (admin_data.kflag != NULL)
629a7c91847Schristos 	    send_arg (admin_data.kflag);
630a7c91847Schristos 
631a7c91847Schristos 	for (i = 0; i < admin_data.ac; ++i)
632a7c91847Schristos 	    send_arg (admin_data.av[i]);
633a7c91847Schristos 
634a7c91847Schristos 	send_arg ("--");
635a7c91847Schristos 	send_files (argc, argv, 0, 0, SEND_NO_CONTENTS);
636a7c91847Schristos 	send_file_names (argc, argv, SEND_EXPAND_WILD);
637a7c91847Schristos 	send_to_server ("admin\012", 0);
638a7c91847Schristos         err = get_responses_and_close ();
639a7c91847Schristos 	goto return_it;
640a7c91847Schristos     }
641a7c91847Schristos #endif /* CLIENT_SUPPORT */
642a7c91847Schristos 
643a7c91847Schristos     lock_tree_promotably (argc, argv, 0, W_LOCAL, 0);
644a7c91847Schristos 
645a7c91847Schristos     err = start_recursion
646a7c91847Schristos 	    (admin_fileproc, admin_filesdoneproc, admin_dirproc,
647a7c91847Schristos 	     NULL, &admin_data,
648a7c91847Schristos 	     argc, argv, 0,
649a7c91847Schristos 	     W_LOCAL, 0, CVS_LOCK_WRITE, NULL, 1, NULL);
650a7c91847Schristos 
651a7c91847Schristos     Lock_Cleanup ();
652a7c91847Schristos 
653a7c91847Schristos /* This just suppresses a warning from -Wall.  */
654a7c91847Schristos #ifdef CLIENT_SUPPORT
655a7c91847Schristos  return_it:
656a7c91847Schristos #endif /* CLIENT_SUPPORT */
657274254cdSchristos     if (admin_data.cmdline != NULL)
658274254cdSchristos 	free (admin_data.cmdline);
659a7c91847Schristos     if (admin_data.branch != NULL)
660a7c91847Schristos 	free (admin_data.branch);
661a7c91847Schristos     if (admin_data.comment != NULL)
662a7c91847Schristos 	free (admin_data.comment);
663a7c91847Schristos     if (admin_data.delete_revs != NULL)
664a7c91847Schristos 	free (admin_data.delete_revs);
665a7c91847Schristos     if (admin_data.kflag != NULL)
666a7c91847Schristos 	free (admin_data.kflag);
667a7c91847Schristos     if (admin_data.desc != NULL)
668a7c91847Schristos 	free (admin_data.desc);
669a7c91847Schristos     for (i = 0; i < admin_data.ac; ++i)
670a7c91847Schristos 	free (admin_data.av[i]);
671a7c91847Schristos     if (admin_data.av != NULL)
672a7c91847Schristos 	free (admin_data.av);
673a7c91847Schristos 
674a7c91847Schristos     return err;
675a7c91847Schristos }
676a7c91847Schristos 
677a7c91847Schristos 
678a7c91847Schristos 
679a7c91847Schristos /*
680a7c91847Schristos  * Called to run "rcs" on a particular file.
681a7c91847Schristos  */
682a7c91847Schristos /* ARGSUSED */
683a7c91847Schristos static int
admin_fileproc(void * callerdat,struct file_info * finfo)684a7c91847Schristos admin_fileproc (void *callerdat, struct file_info *finfo)
685a7c91847Schristos {
686a7c91847Schristos     struct admin_data *admin_data = (struct admin_data *) callerdat;
687a7c91847Schristos     Vers_TS *vers;
688a7c91847Schristos     char *version;
689a7c91847Schristos     int i;
690a7c91847Schristos     int status = 0;
691a7c91847Schristos     RCSNode *rcs, *rcs2;
692a7c91847Schristos 
693a7c91847Schristos     vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
694a7c91847Schristos 
695a2b2f673Schristos /* cvsacl patch */
696a2b2f673Schristos #ifdef SERVER_SUPPORT
697a2b2f673Schristos     if (use_cvs_acl /* && server_active */)
698a2b2f673Schristos     {
699a2b2f673Schristos 	if (!access_allowed (finfo->file, finfo->repository, NULL, 2,
700a2b2f673Schristos 			     NULL, NULL, 1))
701a2b2f673Schristos 	{
702a2b2f673Schristos 	    if (stop_at_first_permission_denied)
703a2b2f673Schristos 		error (1, 0, "permission denied for %s",
704a2b2f673Schristos 		       Short_Repository (finfo->repository));
705a2b2f673Schristos 	    else
706a2b2f673Schristos 		error (0, 0, "permission denied for %s/%s",
707a2b2f673Schristos 		       Short_Repository (finfo->repository), finfo->file);
708a2b2f673Schristos 
709a2b2f673Schristos 	    return (0);
710a2b2f673Schristos 	}
711a2b2f673Schristos     }
712a2b2f673Schristos #endif
713a2b2f673Schristos 
714a7c91847Schristos     version = vers->vn_user;
715a7c91847Schristos     if (version != NULL && strcmp (version, "0") == 0)
716a7c91847Schristos     {
717a7c91847Schristos 	error (0, 0, "cannot admin newly added file `%s'", finfo->file);
718a7c91847Schristos 	status = 1;
719a7c91847Schristos 	goto exitfunc;
720a7c91847Schristos     }
721a7c91847Schristos 
722274254cdSchristos     history_write ('X', finfo->update_dir, admin_data->cmdline, finfo->file,
723274254cdSchristos 	finfo->repository);
724a7c91847Schristos     rcs = vers->srcfile;
725a7c91847Schristos     if (rcs == NULL)
726a7c91847Schristos     {
727a7c91847Schristos 	if (!really_quiet)
728a7c91847Schristos 	    error (0, 0, "nothing known about %s", finfo->file);
729a7c91847Schristos 	status = 1;
730a7c91847Schristos 	goto exitfunc;
731a7c91847Schristos     }
732a7c91847Schristos 
733a7c91847Schristos     if (rcs->flags & PARTIAL)
734a7c91847Schristos 	RCS_reparsercsfile (rcs, NULL, NULL);
735a7c91847Schristos 
736a7c91847Schristos     if (!really_quiet)
737a7c91847Schristos     {
738a7c91847Schristos 	cvs_output ("RCS file: ", 0);
739a7c91847Schristos 	cvs_output (rcs->path, 0);
740a7c91847Schristos 	cvs_output ("\n", 1);
741a7c91847Schristos     }
742a7c91847Schristos 
743a7c91847Schristos     if (admin_data->branch != NULL)
744a7c91847Schristos     {
745a7c91847Schristos 	char *branch = &admin_data->branch[2];
746a7c91847Schristos 	if (*branch != '\0' && ! isdigit ((unsigned char) *branch))
747a7c91847Schristos 	{
748a7c91847Schristos 	    branch = RCS_whatbranch (rcs, admin_data->branch + 2);
749a7c91847Schristos 	    if (branch == NULL)
750a7c91847Schristos 	    {
751a7c91847Schristos 		error (0, 0, "%s: Symbolic name %s is undefined.",
752a7c91847Schristos 				rcs->path, admin_data->branch + 2);
753a7c91847Schristos 		status = 1;
754a7c91847Schristos 	    }
755a7c91847Schristos 	}
756a7c91847Schristos 	if (status == 0)
757a7c91847Schristos 	    RCS_setbranch (rcs, branch);
758a7c91847Schristos 	if (branch != NULL && branch != &admin_data->branch[2])
759a7c91847Schristos 	    free (branch);
760a7c91847Schristos     }
761a7c91847Schristos     if (admin_data->comment != NULL)
762a7c91847Schristos     {
763a7c91847Schristos 	if (rcs->comment != NULL)
764a7c91847Schristos 	    free (rcs->comment);
765a7c91847Schristos 	rcs->comment = xstrdup (admin_data->comment + 2);
766a7c91847Schristos     }
767a7c91847Schristos     if (admin_data->set_strict)
768a7c91847Schristos 	rcs->strict_locks = 1;
769a7c91847Schristos     if (admin_data->set_nonstrict)
770a7c91847Schristos 	rcs->strict_locks = 0;
771a7c91847Schristos     if (admin_data->delete_revs != NULL)
772a7c91847Schristos     {
773a7c91847Schristos 	char *s, *t, *rev1, *rev2;
774a7c91847Schristos 	/* Set for :, clear for ::.  */
775a7c91847Schristos 	int inclusive;
776a7c91847Schristos 	char *t2;
777a7c91847Schristos 
778a7c91847Schristos 	s = admin_data->delete_revs + 2;
779a7c91847Schristos 	inclusive = 1;
780a7c91847Schristos 	t = strchr (s, ':');
781a7c91847Schristos 	if (t != NULL)
782a7c91847Schristos 	{
783a7c91847Schristos 	    if (t[1] == ':')
784a7c91847Schristos 	    {
785a7c91847Schristos 		inclusive = 0;
786a7c91847Schristos 		t2 = t + 2;
787a7c91847Schristos 	    }
788a7c91847Schristos 	    else
789a7c91847Schristos 		t2 = t + 1;
790a7c91847Schristos 	}
791a7c91847Schristos 
792a7c91847Schristos 	/* Note that we don't support '-' for ranges.  RCS considers it
793a7c91847Schristos 	   obsolete and it is problematic with tags containing '-'.  "cvs log"
794a7c91847Schristos 	   has made the same decision.  */
795a7c91847Schristos 
796a7c91847Schristos 	if (t == NULL)
797a7c91847Schristos 	{
798a7c91847Schristos 	    /* -orev */
799a7c91847Schristos 	    rev1 = xstrdup (s);
800a7c91847Schristos 	    rev2 = xstrdup (s);
801a7c91847Schristos 	}
802a7c91847Schristos 	else if (t == s)
803a7c91847Schristos 	{
804a7c91847Schristos 	    /* -o:rev2 */
805a7c91847Schristos 	    rev1 = NULL;
806a7c91847Schristos 	    rev2 = xstrdup (t2);
807a7c91847Schristos 	}
808a7c91847Schristos 	else
809a7c91847Schristos 	{
810a7c91847Schristos 	    *t = '\0';
811a7c91847Schristos 	    rev1 = xstrdup (s);
812a7c91847Schristos 	    *t = ':';	/* probably unnecessary */
813a7c91847Schristos 	    if (*t2 == '\0')
814a7c91847Schristos 		/* -orev1: */
815a7c91847Schristos 		rev2 = NULL;
816a7c91847Schristos 	    else
817a7c91847Schristos 		/* -orev1:rev2 */
818a7c91847Schristos 		rev2 = xstrdup (t2);
819a7c91847Schristos 	}
820a7c91847Schristos 
821a7c91847Schristos 	if (rev1 == NULL && rev2 == NULL)
822a7c91847Schristos 	{
823a7c91847Schristos 	    /* RCS segfaults if `-o:' is given */
824a7c91847Schristos 	    error (0, 0, "no valid revisions specified in `%s' option",
825a7c91847Schristos 		   admin_data->delete_revs);
826a7c91847Schristos 	    status = 1;
827a7c91847Schristos 	}
828a7c91847Schristos 	else
829a7c91847Schristos 	{
830a7c91847Schristos 	    status |= RCS_delete_revs (rcs, rev1, rev2, inclusive);
831a7c91847Schristos 	    if (rev1)
832a7c91847Schristos 		free (rev1);
833a7c91847Schristos 	    if (rev2)
834a7c91847Schristos 		free (rev2);
835a7c91847Schristos 	}
836a7c91847Schristos     }
837a7c91847Schristos     if (admin_data->desc != NULL)
838a7c91847Schristos     {
839a7c91847Schristos 	free (rcs->desc);
840a7c91847Schristos 	rcs->desc = xstrdup (admin_data->desc);
841a7c91847Schristos     }
842a7c91847Schristos     if (admin_data->kflag != NULL)
843a7c91847Schristos     {
844a7c91847Schristos 	char *kflag = admin_data->kflag + 2;
845a7c91847Schristos 	char *oldexpand = RCS_getexpand (rcs);
846a7c91847Schristos 	if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0)
847a7c91847Schristos 	    RCS_setexpand (rcs, kflag);
848a7c91847Schristos     }
849a7c91847Schristos 
850a7c91847Schristos     /* Handle miscellaneous options.  TODO: decide whether any or all
851a7c91847Schristos        of these should have their own fields in the admin_data
852a7c91847Schristos        structure. */
853a7c91847Schristos     for (i = 0; i < admin_data->ac; ++i)
854a7c91847Schristos     {
855a7c91847Schristos 	char *arg;
856a7c91847Schristos 	char *p, *rev, *revnum, *tag, *msg;
857a7c91847Schristos 	char **users;
858a7c91847Schristos 	int argc, u;
859a7c91847Schristos 	Node *n;
860a7c91847Schristos 	RCSVers *delta;
861a7c91847Schristos 
862a7c91847Schristos 	arg = admin_data->av[i];
863a7c91847Schristos 	switch (arg[1])
864a7c91847Schristos 	{
865a7c91847Schristos 	    case 'a': /* fall through */
866a7c91847Schristos 	    case 'e':
867a7c91847Schristos 	        line2argv (&argc, &users, arg + 2, " ,\t\n");
868a7c91847Schristos 		if (arg[1] == 'a')
869a7c91847Schristos 		    for (u = 0; u < argc; ++u)
870a7c91847Schristos 			RCS_addaccess (rcs, users[u]);
871a7c91847Schristos 		else if (argc == 0)
872a7c91847Schristos 		    RCS_delaccess (rcs, NULL);
873a7c91847Schristos 		else
874a7c91847Schristos 		    for (u = 0; u < argc; ++u)
875a7c91847Schristos 			RCS_delaccess (rcs, users[u]);
876a7c91847Schristos 		free_names (&argc, users);
877a7c91847Schristos 		break;
878a7c91847Schristos 	    case 'A':
879a7c91847Schristos 
880a7c91847Schristos 		/* See admin-19a-admin and friends in sanity.sh for
881a7c91847Schristos 		   relative pathnames.  It makes sense to think in
882a7c91847Schristos 		   terms of a syntax which give pathnames relative to
883a7c91847Schristos 		   the repository or repository corresponding to the
884a7c91847Schristos 		   current directory or some such (and perhaps don't
885a7c91847Schristos 		   include ,v), but trying to worry about such things
886a7c91847Schristos 		   is a little pointless unless you first worry about
887a7c91847Schristos 		   whether "cvs admin -A" as a whole makes any sense
888a7c91847Schristos 		   (currently probably not, as access lists don't
889a7c91847Schristos 		   affect the behavior of CVS).  */
890a7c91847Schristos 
891a7c91847Schristos 		rcs2 = RCS_parsercsfile (arg + 2);
892a7c91847Schristos 		if (rcs2 == NULL)
893a7c91847Schristos 		    error (1, 0, "cannot continue");
894a7c91847Schristos 
895a7c91847Schristos 		p = xstrdup (RCS_getaccess (rcs2));
896a7c91847Schristos 	        line2argv (&argc, &users, p, " \t\n");
897a7c91847Schristos 		free (p);
898a7c91847Schristos 		freercsnode (&rcs2);
899a7c91847Schristos 
900a7c91847Schristos 		for (u = 0; u < argc; ++u)
901a7c91847Schristos 		    RCS_addaccess (rcs, users[u]);
902a7c91847Schristos 		free_names (&argc, users);
903a7c91847Schristos 		break;
904a7c91847Schristos 	    case 'n': /* fall through */
905a7c91847Schristos 	    case 'N':
906a7c91847Schristos 		if (arg[2] == '\0')
907a7c91847Schristos 		{
908a7c91847Schristos 		    cvs_outerr ("missing symbolic name after ", 0);
909a7c91847Schristos 		    cvs_outerr (arg, 0);
910a7c91847Schristos 		    cvs_outerr ("\n", 1);
911a7c91847Schristos 		    break;
912a7c91847Schristos 		}
913a7c91847Schristos 		p = strchr (arg, ':');
914a7c91847Schristos 		if (p == NULL)
915a7c91847Schristos 		{
916a7c91847Schristos 		    if (RCS_deltag (rcs, arg + 2) != 0)
917a7c91847Schristos 		    {
918a7c91847Schristos 			error (0, 0, "%s: Symbolic name %s is undefined.",
919a7c91847Schristos 			       rcs->path,
920a7c91847Schristos 			       arg + 2);
921a7c91847Schristos 			status = 1;
922a7c91847Schristos 			continue;
923a7c91847Schristos 		    }
924a7c91847Schristos 		    break;
925a7c91847Schristos 		}
926a7c91847Schristos 		*p = '\0';
927a7c91847Schristos 		tag = xstrdup (arg + 2);
928a7c91847Schristos 		*p++ = ':';
929a7c91847Schristos 
930a7c91847Schristos 		/* Option `n' signals an error if this tag is already bound. */
931a7c91847Schristos 		if (arg[1] == 'n')
932a7c91847Schristos 		{
933a7c91847Schristos 		    n = findnode (RCS_symbols (rcs), tag);
934a7c91847Schristos 		    if (n != NULL)
935a7c91847Schristos 		    {
936a7c91847Schristos 			error (0, 0,
937a7c91847Schristos 			       "%s: symbolic name %s already bound to %s",
938a7c91847Schristos 			       rcs->path,
939a7c91847Schristos 			       tag, (char *)n->data);
940a7c91847Schristos 			status = 1;
941a7c91847Schristos 			free (tag);
942a7c91847Schristos 			continue;
943a7c91847Schristos 		    }
944a7c91847Schristos 		}
945a7c91847Schristos 
946a7c91847Schristos                 /* Attempt to perform the requested tagging.  */
947a7c91847Schristos 
948a7c91847Schristos 		if ((*p == 0 && (rev = RCS_head (rcs)))
949a7c91847Schristos                     || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */
950a7c91847Schristos 		{
951a7c91847Schristos 		    RCS_check_tag (tag); /* exit if not a valid tag */
952a7c91847Schristos 		    RCS_settag (rcs, tag, rev);
953a7c91847Schristos 		    free (rev);
954a7c91847Schristos 		}
955a7c91847Schristos                 else
956a7c91847Schristos 		{
957a7c91847Schristos 		    if (!really_quiet)
958a7c91847Schristos 			error (0, 0,
959a7c91847Schristos 			       "%s: Symbolic name or revision %s is undefined.",
960a7c91847Schristos 			       rcs->path, p);
961a7c91847Schristos 		    status = 1;
962a7c91847Schristos 		}
963a7c91847Schristos 		free (tag);
964a7c91847Schristos 		break;
965a7c91847Schristos 	    case 's':
966a7c91847Schristos 	        p = strchr (arg, ':');
967a7c91847Schristos 		if (p == NULL)
968a7c91847Schristos 		{
969a7c91847Schristos 		    tag = xstrdup (arg + 2);
970a7c91847Schristos 		    rev = RCS_head (rcs);
971a7c91847Schristos 		    if (!rev)
972a7c91847Schristos 		    {
973a7c91847Schristos 			error (0, 0, "No head revision in archive file `%s'.",
974a7c91847Schristos 			       rcs->path);
975a7c91847Schristos 			status = 1;
976a7c91847Schristos 			continue;
977a7c91847Schristos 		    }
978a7c91847Schristos 		}
979a7c91847Schristos 		else
980a7c91847Schristos 		{
981a7c91847Schristos 		    *p = '\0';
982a7c91847Schristos 		    tag = xstrdup (arg + 2);
983a7c91847Schristos 		    *p++ = ':';
984a7c91847Schristos 		    rev = xstrdup (p);
985a7c91847Schristos 		}
986a7c91847Schristos 		revnum = RCS_gettag (rcs, rev, 0, NULL);
987a7c91847Schristos 		if (revnum != NULL)
988a7c91847Schristos 		{
989a7c91847Schristos 		    n = findnode (rcs->versions, revnum);
990a7c91847Schristos 		    free (revnum);
991a7c91847Schristos 		}
992a7c91847Schristos 		else
993a7c91847Schristos 		    n = NULL;
994a7c91847Schristos 		if (n == NULL)
995a7c91847Schristos 		{
996a7c91847Schristos 		    error (0, 0,
997a7c91847Schristos 			   "%s: can't set state of nonexisting revision %s",
998a7c91847Schristos 			   rcs->path,
999a7c91847Schristos 			   rev);
1000a7c91847Schristos 		    free (rev);
1001a7c91847Schristos 		    status = 1;
1002a7c91847Schristos 		    continue;
1003a7c91847Schristos 		}
1004a7c91847Schristos 		free (rev);
1005a7c91847Schristos 		delta = n->data;
1006a7c91847Schristos 		free (delta->state);
1007a7c91847Schristos 		delta->state = tag;
1008a7c91847Schristos 		break;
1009a7c91847Schristos 
1010a7c91847Schristos 	    case 'm':
1011a7c91847Schristos 	        p = strchr (arg, ':');
1012a7c91847Schristos 		if (p == NULL)
1013a7c91847Schristos 		{
1014a7c91847Schristos 		    error (0, 0, "%s: -m option lacks revision number",
1015a7c91847Schristos 			   rcs->path);
1016a7c91847Schristos 		    status = 1;
1017a7c91847Schristos 		    continue;
1018a7c91847Schristos 		}
1019a7c91847Schristos 		*p = '\0';	/* temporarily make arg+2 its own string */
1020a7c91847Schristos 		rev = RCS_gettag (rcs, arg + 2, 1, NULL); /* Force tag match */
1021a7c91847Schristos 		if (rev == NULL)
1022a7c91847Schristos 		{
1023a7c91847Schristos 		    error (0, 0, "%s: no such revision %s", rcs->path, arg+2);
1024a7c91847Schristos 		    status = 1;
1025a7c91847Schristos 		    *p = ':';	/* restore the full text of the -m argument */
1026a7c91847Schristos 		    continue;
1027a7c91847Schristos 		}
1028a7c91847Schristos 		msg = p+1;
1029a7c91847Schristos 
1030a7c91847Schristos 		n = findnode (rcs->versions, rev);
1031a7c91847Schristos 		/* tags may exist against non-existing versions */
1032a7c91847Schristos 		if (n == NULL)
1033a7c91847Schristos 		{
1034a7c91847Schristos 		     error (0, 0, "%s: no such revision %s: %s",
1035a7c91847Schristos 			    rcs->path, arg+2, rev);
1036a7c91847Schristos 		    status = 1;
1037a7c91847Schristos 		    *p = ':';	/* restore the full text of the -m argument */
1038a7c91847Schristos 		    free (rev);
1039a7c91847Schristos 		    continue;
1040a7c91847Schristos 		}
1041a7c91847Schristos 		*p = ':';	/* restore the full text of the -m argument */
1042a7c91847Schristos 		free (rev);
1043a7c91847Schristos 
1044a7c91847Schristos 		delta = n->data;
1045a7c91847Schristos 		if (delta->text == NULL)
1046a7c91847Schristos 		{
1047a7c91847Schristos 		    delta->text = xmalloc (sizeof (Deltatext));
1048a7c91847Schristos 		    memset (delta->text, 0, sizeof (Deltatext));
1049a7c91847Schristos 		}
1050a7c91847Schristos 		delta->text->version = xstrdup (delta->version);
1051a7c91847Schristos 		delta->text->log = make_message_rcsvalid (msg);
1052a7c91847Schristos 		break;
1053a7c91847Schristos 
1054a7c91847Schristos 	    case 'l':
1055a7c91847Schristos 	        status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0);
1056a7c91847Schristos 		break;
1057a7c91847Schristos 	    case 'u':
1058a7c91847Schristos 		status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0);
1059a7c91847Schristos 		break;
1060a7c91847Schristos 	    default: assert(0);	/* can't happen */
1061a7c91847Schristos 	}
1062a7c91847Schristos     }
1063a7c91847Schristos 
1064a7c91847Schristos     if (status == 0)
1065a7c91847Schristos     {
1066a7c91847Schristos 	RCS_rewrite (rcs, NULL, NULL);
1067a7c91847Schristos 	if (!really_quiet)
1068a7c91847Schristos 	    cvs_output ("done\n", 5);
1069a7c91847Schristos     }
1070a7c91847Schristos     else
1071a7c91847Schristos     {
1072a7c91847Schristos 	/* Note that this message should only occur after another
1073a7c91847Schristos 	   message has given a more specific error.  The point of this
1074a7c91847Schristos 	   additional message is to make it clear that the previous problems
1075a7c91847Schristos 	   caused CVS to forget about the idea of modifying the RCS file.  */
1076a7c91847Schristos 	if (!really_quiet)
1077a7c91847Schristos 	    error (0, 0, "RCS file for `%s' not modified.", finfo->file);
1078a7c91847Schristos 	RCS_abandon (rcs);
1079a7c91847Schristos     }
1080a7c91847Schristos 
1081a7c91847Schristos   exitfunc:
1082a7c91847Schristos     freevers_ts (&vers);
1083a7c91847Schristos     return status;
1084a7c91847Schristos }
1085a7c91847Schristos 
1086a7c91847Schristos 
1087a7c91847Schristos 
1088a7c91847Schristos /*
1089a7c91847Schristos  * Print a warm fuzzy message
1090a7c91847Schristos  */
1091a7c91847Schristos /* ARGSUSED */
1092a7c91847Schristos static Dtype
admin_dirproc(void * callerdat,const char * dir,const char * repos,const char * update_dir,List * entries)1093a7c91847Schristos admin_dirproc (void *callerdat, const char *dir, const char *repos,
1094a7c91847Schristos                const char *update_dir, List *entries)
1095a7c91847Schristos {
1096a7c91847Schristos     if (!quiet)
1097a7c91847Schristos 	error (0, 0, "Administrating %s", update_dir);
1098a7c91847Schristos     return R_PROCESS;
1099a7c91847Schristos }
1100