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