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