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