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