Merge from vendor branch BINUTILS:
[dragonfly.git] / contrib / cvs-1.12 / src / admin.c
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
17 #include "cvs.h"
18 #ifdef CVS_ADMIN_GROUP
19 #include <grp.h>
20 #endif
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
114 /* Add an argument.  OPT is the option letter, e.g. 'a'.  ARG is the
115    argument to that option, or NULL if omitted (whether NULL can actually
116    happen depends on whether the option was specified as optional to
117    getopt).  */
118 static void
119 arg_add (struct admin_data *dat, int opt, char *arg)
120 {
121     char *newelt = Xasprintf ("-%c%s", opt, arg ? arg : "");
122
123     if (dat->av_alloc == 0)
124     {
125         dat->av_alloc = 1;
126         dat->av = xnmalloc (dat->av_alloc, sizeof (*dat->av));
127     }
128     else if (dat->ac >= dat->av_alloc)
129     {
130         dat->av_alloc *= 2;
131         dat->av = xnrealloc (dat->av, dat->av_alloc, sizeof (*dat->av));
132     }
133     dat->av[dat->ac++] = newelt;
134 }
135
136
137
138 /*
139  * callback proc to run a script when admin finishes.
140  */
141 static int
142 postadmin_proc (const char *repository, const char *filter, void *closure)
143 {
144     char *cmdline;
145     const char *srepos = Short_Repository (repository);
146
147     TRACE (TRACE_FUNCTION, "postadmin_proc (%s, %s)", repository, filter);
148
149     /* %c = cvs_cmd_name
150      * %R = referrer
151      * %p = shortrepos
152      * %r = repository
153      */
154     /*
155      * Cast any NULL arguments as appropriate pointers as this is an
156      * stdarg function and we need to be certain the caller gets what
157      * is expected.
158      */
159     cmdline = format_cmdline (
160 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
161                               false, srepos,
162 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
163                               filter,
164                               "c", "s", cvs_cmd_name,
165 #ifdef SERVER_SUPPORT
166                               "R", "s", referrer ? referrer->original : "NONE",
167 #endif /* SERVER_SUPPORT */
168                               "p", "s", srepos,
169                               "r", "s", current_parsed_root->directory,
170                               (char *) NULL);
171
172     if (!cmdline || !strlen (cmdline))
173     {
174         if (cmdline) free (cmdline);
175         error (0, 0, "postadmin proc resolved to the empty string!");
176         return 1;
177     }
178
179     run_setup (cmdline);
180
181     free (cmdline);
182
183     /* FIXME - read the comment in verifymsg_proc() about why we use abs()
184      * below() and shouldn't.
185      */
186     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
187                           RUN_NORMAL | RUN_SIGIGNORE));
188 }
189
190
191
192 /*
193  * Call any postadmin procs.
194  */
195 static int
196 admin_filesdoneproc (void *callerdat, int err, const char *repository,
197                      const char *update_dir, List *entries)
198 {
199     TRACE (TRACE_FUNCTION, "admin_filesdoneproc (%d, %s, %s)", err, repository,
200            update_dir);
201     Parse_Info (CVSROOTADM_POSTADMIN, repository, postadmin_proc, PIOPT_ALL,
202                 NULL);
203
204     return err;
205 }
206
207
208
209 int
210 admin (int argc, char **argv)
211 {
212     int err;
213 #ifdef CVS_ADMIN_GROUP
214     struct group *grp;
215     struct group *getgrnam (const char *);
216 #endif
217     struct admin_data admin_data;
218     int c;
219     int i;
220     bool only_allowed_options;
221
222     if (argc <= 1)
223         usage (admin_usage);
224
225     wrap_setup ();
226
227     memset (&admin_data, 0, sizeof admin_data);
228
229     /* TODO: get rid of `-' switch notation in admin_data.  For
230        example, admin_data->branch should be not `-bfoo' but simply `foo'. */
231
232     optind = 0;
233     only_allowed_options = true;
234     while ((c = getopt (argc, argv,
235                         "+ib::c:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1)
236     {
237         if (
238 # ifdef CLIENT_SUPPORT
239             !current_parsed_root->isremote &&
240 # endif /* CLIENT_SUPPORT */
241             c != 'q' && !strchr (config->UserAdminOptions, c)
242            )
243             only_allowed_options = false;
244
245         switch (c)
246         {
247             case 'i':
248                 /* This has always been documented as useless in cvs.texinfo
249                    and it really is--admin_fileproc silently does nothing
250                    if vers->vn_user is NULL. */
251                 error (0, 0, "the -i option to admin is not supported");
252                 error (0, 0, "run add or import to create an RCS file");
253                 goto usage_error;
254
255             case 'b':
256                 if (admin_data.branch != NULL)
257                 {
258                     error (0, 0, "duplicate 'b' option");
259                     goto usage_error;
260                 }
261                 if (optarg == NULL)
262                     admin_data.branch = xstrdup ("-b");
263                 else
264                     admin_data.branch = Xasprintf ("-b%s", optarg);
265                 break;
266
267             case 'c':
268                 if (admin_data.comment != NULL)
269                 {
270                     error (0, 0, "duplicate 'c' option");
271                     goto usage_error;
272                 }
273                 admin_data.comment = Xasprintf ("-c%s", optarg);
274                 break;
275
276             case 'a':
277                 arg_add (&admin_data, 'a', optarg);
278                 break;
279
280             case 'A':
281                 /* In the client/server case, this is cheesy because
282                    we just pass along the name of the RCS file, which
283                    then will want to exist on the server.  This is
284                    accidental; having the client specify a pathname on
285                    the server is not a design feature of the protocol.  */
286                 arg_add (&admin_data, 'A', optarg);
287                 break;
288
289             case 'e':
290                 arg_add (&admin_data, 'e', optarg);
291                 break;
292
293             case 'l':
294                 /* Note that multiple -l options are valid.  */
295                 arg_add (&admin_data, 'l', optarg);
296                 break;
297
298             case 'u':
299                 /* Note that multiple -u options are valid.  */
300                 arg_add (&admin_data, 'u', optarg);
301                 break;
302
303             case 'L':
304                 /* Probably could also complain if -L is specified multiple
305                    times, although RCS doesn't and I suppose it is reasonable
306                    just to have it mean the same as a single -L.  */
307                 if (admin_data.set_nonstrict)
308                 {
309                     error (0, 0, "-U and -L are incompatible");
310                     goto usage_error;
311                 }
312                 admin_data.set_strict = 1;
313                 break;
314
315             case 'U':
316                 /* Probably could also complain if -U is specified multiple
317                    times, although RCS doesn't and I suppose it is reasonable
318                    just to have it mean the same as a single -U.  */
319                 if (admin_data.set_strict)
320                 {
321                     error (0, 0, "-U and -L are incompatible");
322                     goto usage_error;
323                 }
324                 admin_data.set_nonstrict = 1;
325                 break;
326
327             case 'n':
328                 /* Mostly similar to cvs tag.  Could also be parsing
329                    the syntax of optarg, although for now we just pass
330                    it to rcs as-is.  Note that multiple -n options are
331                    valid.  */
332                 arg_add (&admin_data, 'n', optarg);
333                 break;
334
335             case 'N':
336                 /* Mostly similar to cvs tag.  Could also be parsing
337                    the syntax of optarg, although for now we just pass
338                    it to rcs as-is.  Note that multiple -N options are
339                    valid.  */
340                 arg_add (&admin_data, 'N', optarg);
341                 break;
342
343             case 'm':
344                 /* Change log message.  Could also be parsing the syntax
345                    of optarg, although for now we just pass it to rcs
346                    as-is.  Note that multiple -m options are valid.  */
347                 arg_add (&admin_data, 'm', optarg);
348                 break;
349
350             case 'o':
351                 /* Delete revisions.  Probably should also be parsing the
352                    syntax of optarg, so that the client can give errors
353                    rather than making the server take care of that.
354                    Other than that I'm not sure whether it matters much
355                    whether we parse it here or in admin_fileproc.
356
357                    Note that multiple -o options are invalid, in RCS
358                    as well as here.  */
359
360                 if (admin_data.delete_revs != NULL)
361                 {
362                     error (0, 0, "duplicate '-o' option");
363                     goto usage_error;
364                 }
365                 admin_data.delete_revs = Xasprintf ("-o%s", optarg);
366                 break;
367
368             case 's':
369                 /* Note that multiple -s options are valid.  */
370                 arg_add (&admin_data, 's', optarg);
371                 break;
372
373             case 't':
374                 if (admin_data.desc != NULL)
375                 {
376                     error (0, 0, "duplicate 't' option");
377                     goto usage_error;
378                 }
379                 if (optarg != NULL && optarg[0] == '-')
380                     admin_data.desc = xstrdup (optarg + 1);
381                 else
382                 {
383                     size_t bufsize = 0;
384                     size_t len;
385
386                     get_file (optarg, optarg, "r", &admin_data.desc,
387                               &bufsize, &len);
388                 }
389                 break;
390
391             case 'I':
392                 /* At least in RCS this can be specified several times,
393                    with the same meaning as being specified once.  */
394                 admin_data.interactive = 1;
395                 break;
396
397             case 'q':
398                 /* Silently set the global really_quiet flag.  This keeps admin in
399                  * sync with the RCS man page and allows us to silently support
400                  * older servers when necessary.
401                  *
402                  * Some logic says we might want to output a deprecation warning
403                  * here, but I'm opting not to in order to stay quietly in sync
404                  * with the RCS man page.
405                  */
406                 really_quiet = 1;
407                 break;
408
409             case 'x':
410                 error (0, 0, "the -x option has never done anything useful");
411                 error (0, 0, "RCS files in CVS always end in ,v");
412                 goto usage_error;
413
414             case 'V':
415                 /* No longer supported. */
416                 error (0, 0, "the `-V' option is obsolete");
417                 break;
418
419             case 'k':
420                 if (admin_data.kflag != NULL)
421                 {
422                     error (0, 0, "duplicate '-k' option");
423                     goto usage_error;
424                 }
425                 admin_data.kflag = RCS_check_kflag (optarg);
426                 break;
427             default:
428             case '?':
429                 /* getopt will have printed an error message.  */
430
431             usage_error:
432                 /* Don't use cvs_cmd_name; it might be "server".  */
433                 error (1, 0, "specify %s -H admin for usage information",
434                        program_name);
435         }
436     }
437     argc -= optind;
438     argv += optind;
439
440 #ifdef CVS_ADMIN_GROUP
441     /* The use of `cvs admin -k' is unrestricted.  However, any other
442        option is restricted if the group CVS_ADMIN_GROUP exists on the
443        server.  */
444     /* This is only "secure" on the server, since the user could edit the
445      * RCS file on a local host, but some people like this kind of
446      * check anyhow.  The alternative would be to check only when
447      * (server_active) rather than when not on the client.
448      */
449     if (!current_parsed_root->isremote && !only_allowed_options &&
450         (grp = getgrnam(CVS_ADMIN_GROUP)) != NULL)
451     {
452 #ifdef HAVE_GETGROUPS
453         gid_t *grps;
454         int n;
455
456         /* get number of auxiliary groups */
457         n = getgroups (0, NULL);
458         if (n < 0)
459             error (1, errno, "unable to get number of auxiliary groups");
460         grps = xnmalloc (n + 1, sizeof *grps);
461         n = getgroups (n, grps);
462         if (n < 0)
463             error (1, errno, "unable to get list of auxiliary groups");
464         grps[n] = getgid ();
465         for (i = 0; i <= n; i++)
466             if (grps[i] == grp->gr_gid) break;
467         free (grps);
468         if (i > n)
469             error (1, 0, "usage is restricted to members of the group %s",
470                    CVS_ADMIN_GROUP);
471 #else
472         char *me = getcaller ();
473         char **grnam;
474         
475         for (grnam = grp->gr_mem; *grnam; grnam++)
476             if (strcmp (*grnam, me) == 0) break;
477         if (!*grnam && getgid () != grp->gr_gid)
478             error (1, 0, "usage is restricted to members of the group %s",
479                    CVS_ADMIN_GROUP);
480 #endif
481     }
482 #endif /* defined CVS_ADMIN_GROUP */
483
484     for (i = 0; i < admin_data.ac; ++i)
485     {
486         assert (admin_data.av[i][0] == '-');
487         switch (admin_data.av[i][1])
488         {
489             case 'm':
490             case 'l':
491             case 'u':
492                 check_numeric (&admin_data.av[i][2], argc, argv);
493                 break;
494             default:
495                 break;
496         }
497     }
498     if (admin_data.branch != NULL)
499         check_numeric (admin_data.branch + 2, argc, argv);
500     if (admin_data.delete_revs != NULL)
501     {
502         char *p;
503
504         check_numeric (admin_data.delete_revs + 2, argc, argv);
505         p = strchr (admin_data.delete_revs + 2, ':');
506         if (p != NULL && isdigit ((unsigned char) p[1]))
507             check_numeric (p + 1, argc, argv);
508         else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2]))
509             check_numeric (p + 2, argc, argv);
510     }
511
512 #ifdef CLIENT_SUPPORT
513     if (current_parsed_root->isremote)
514     {
515         /* We're the client side.  Fire up the remote server.  */
516         start_server ();
517         
518         ign_setup ();
519
520         /* Note that option_with_arg does not work for us, because some
521            of the options must be sent without a space between the option
522            and its argument.  */
523         if (admin_data.interactive)
524             error (1, 0, "-I option not useful with client/server");
525         if (admin_data.branch != NULL)
526             send_arg (admin_data.branch);
527         if (admin_data.comment != NULL)
528             send_arg (admin_data.comment);
529         if (admin_data.set_strict)
530             send_arg ("-L");
531         if (admin_data.set_nonstrict)
532             send_arg ("-U");
533         if (admin_data.delete_revs != NULL)
534             send_arg (admin_data.delete_revs);
535         if (admin_data.desc != NULL)
536         {
537             char *p = admin_data.desc;
538             send_to_server ("Argument -t-", 0);
539             while (*p)
540             {
541                 if (*p == '\n')
542                 {
543                     send_to_server ("\012Argumentx ", 0);
544                     ++p;
545                 }
546                 else
547                 {
548                     char *q = strchr (p, '\n');
549                     if (q == NULL) q = p + strlen (p);
550                     send_to_server (p, q - p);
551                     p = q;
552                 }
553             }
554             send_to_server ("\012", 1);
555         }
556         /* Send this for all really_quiets since we know that it will be silently
557          * ignored when unneeded.  This supports old servers.
558          */
559         if (really_quiet)
560             send_arg ("-q");
561         if (admin_data.kflag != NULL)
562             send_arg (admin_data.kflag);
563
564         for (i = 0; i < admin_data.ac; ++i)
565             send_arg (admin_data.av[i]);
566
567         send_arg ("--");
568         send_files (argc, argv, 0, 0, SEND_NO_CONTENTS);
569         send_file_names (argc, argv, SEND_EXPAND_WILD);
570         send_to_server ("admin\012", 0);
571         err = get_responses_and_close ();
572         goto return_it;
573     }
574 #endif /* CLIENT_SUPPORT */
575
576     lock_tree_promotably (argc, argv, 0, W_LOCAL, 0);
577
578     err = start_recursion
579             (admin_fileproc, admin_filesdoneproc, admin_dirproc,
580              NULL, &admin_data,
581              argc, argv, 0,
582              W_LOCAL, 0, CVS_LOCK_WRITE, NULL, 1, NULL);
583
584     Lock_Cleanup ();
585
586 /* This just suppresses a warning from -Wall.  */
587 #ifdef CLIENT_SUPPORT
588  return_it:
589 #endif /* CLIENT_SUPPORT */
590     if (admin_data.branch != NULL)
591         free (admin_data.branch);
592     if (admin_data.comment != NULL)
593         free (admin_data.comment);
594     if (admin_data.delete_revs != NULL)
595         free (admin_data.delete_revs);
596     if (admin_data.kflag != NULL)
597         free (admin_data.kflag);
598     if (admin_data.desc != NULL)
599         free (admin_data.desc);
600     for (i = 0; i < admin_data.ac; ++i)
601         free (admin_data.av[i]);
602     if (admin_data.av != NULL)
603         free (admin_data.av);
604
605     return err;
606 }
607
608
609
610 /*
611  * Called to run "rcs" on a particular file.
612  */
613 /* ARGSUSED */
614 static int
615 admin_fileproc (void *callerdat, struct file_info *finfo)
616 {
617     struct admin_data *admin_data = (struct admin_data *) callerdat;
618     Vers_TS *vers;
619     char *version;
620     int i;
621     int status = 0;
622     RCSNode *rcs, *rcs2;
623
624     vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
625
626     version = vers->vn_user;
627     if (version != NULL && strcmp (version, "0") == 0)
628     {
629         error (0, 0, "cannot admin newly added file `%s'", finfo->file);
630         status = 1;
631         goto exitfunc;
632     }
633
634     rcs = vers->srcfile;
635     if (rcs == NULL)
636     {
637         if (!really_quiet)
638             error (0, 0, "nothing known about %s", finfo->file);
639         status = 1;
640         goto exitfunc;
641     }
642
643     if (rcs->flags & PARTIAL)
644         RCS_reparsercsfile (rcs, NULL, NULL);
645
646     if (!really_quiet)
647     {
648         cvs_output ("RCS file: ", 0);
649         cvs_output (rcs->path, 0);
650         cvs_output ("\n", 1);
651     }
652
653     if (admin_data->branch != NULL)
654     {
655         char *branch = &admin_data->branch[2];
656         if (*branch != '\0' && ! isdigit ((unsigned char) *branch))
657         {
658             branch = RCS_whatbranch (rcs, admin_data->branch + 2);
659             if (branch == NULL)
660             {
661                 error (0, 0, "%s: Symbolic name %s is undefined.",
662                                 rcs->path, admin_data->branch + 2);
663                 status = 1;
664             }
665         }
666         if (status == 0)
667             RCS_setbranch (rcs, branch);
668         if (branch != NULL && branch != &admin_data->branch[2])
669             free (branch);
670     }
671     if (admin_data->comment != NULL)
672     {
673         if (rcs->comment != NULL)
674             free (rcs->comment);
675         rcs->comment = xstrdup (admin_data->comment + 2);
676     }
677     if (admin_data->set_strict)
678         rcs->strict_locks = 1;
679     if (admin_data->set_nonstrict)
680         rcs->strict_locks = 0;
681     if (admin_data->delete_revs != NULL)
682     {
683         char *s, *t, *rev1, *rev2;
684         /* Set for :, clear for ::.  */
685         int inclusive;
686         char *t2;
687
688         s = admin_data->delete_revs + 2;
689         inclusive = 1;
690         t = strchr (s, ':');
691         if (t != NULL)
692         {
693             if (t[1] == ':')
694             {
695                 inclusive = 0;
696                 t2 = t + 2;
697             }
698             else
699                 t2 = t + 1;
700         }
701
702         /* Note that we don't support '-' for ranges.  RCS considers it
703            obsolete and it is problematic with tags containing '-'.  "cvs log"
704            has made the same decision.  */
705
706         if (t == NULL)
707         {
708             /* -orev */
709             rev1 = xstrdup (s);
710             rev2 = xstrdup (s);
711         }
712         else if (t == s)
713         {
714             /* -o:rev2 */
715             rev1 = NULL;
716             rev2 = xstrdup (t2);
717         }
718         else
719         {
720             *t = '\0';
721             rev1 = xstrdup (s);
722             *t = ':';   /* probably unnecessary */
723             if (*t2 == '\0')
724                 /* -orev1: */
725                 rev2 = NULL;
726             else
727                 /* -orev1:rev2 */
728                 rev2 = xstrdup (t2);
729         }
730
731         if (rev1 == NULL && rev2 == NULL)
732         {
733             /* RCS segfaults if `-o:' is given */
734             error (0, 0, "no valid revisions specified in `%s' option",
735                    admin_data->delete_revs);
736             status = 1;
737         }
738         else
739         {
740             status |= RCS_delete_revs (rcs, rev1, rev2, inclusive);
741             if (rev1)
742                 free (rev1);
743             if (rev2)
744                 free (rev2);
745         }
746     }
747     if (admin_data->desc != NULL)
748     {
749         free (rcs->desc);
750         rcs->desc = xstrdup (admin_data->desc);
751     }
752     if (admin_data->kflag != NULL)
753     {
754         char *kflag = admin_data->kflag + 2;
755         char *oldexpand = RCS_getexpand (rcs);
756         if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0)
757             RCS_setexpand (rcs, kflag);
758     }
759
760     /* Handle miscellaneous options.  TODO: decide whether any or all
761        of these should have their own fields in the admin_data
762        structure. */
763     for (i = 0; i < admin_data->ac; ++i)
764     {
765         char *arg;
766         char *p, *rev, *revnum, *tag, *msg;
767         char **users;
768         int argc, u;
769         Node *n;
770         RCSVers *delta;
771         
772         arg = admin_data->av[i];
773         switch (arg[1])
774         {
775             case 'a': /* fall through */
776             case 'e':
777                 line2argv (&argc, &users, arg + 2, " ,\t\n");
778                 if (arg[1] == 'a')
779                     for (u = 0; u < argc; ++u)
780                         RCS_addaccess (rcs, users[u]);
781                 else if (argc == 0)
782                     RCS_delaccess (rcs, NULL);
783                 else
784                     for (u = 0; u < argc; ++u)
785                         RCS_delaccess (rcs, users[u]);
786                 free_names (&argc, users);
787                 break;
788             case 'A':
789
790                 /* See admin-19a-admin and friends in sanity.sh for
791                    relative pathnames.  It makes sense to think in
792                    terms of a syntax which give pathnames relative to
793                    the repository or repository corresponding to the
794                    current directory or some such (and perhaps don't
795                    include ,v), but trying to worry about such things
796                    is a little pointless unless you first worry about
797                    whether "cvs admin -A" as a whole makes any sense
798                    (currently probably not, as access lists don't
799                    affect the behavior of CVS).  */
800
801                 rcs2 = RCS_parsercsfile (arg + 2);
802                 if (rcs2 == NULL)
803                     error (1, 0, "cannot continue");
804
805                 p = xstrdup (RCS_getaccess (rcs2));
806                 line2argv (&argc, &users, p, " \t\n");
807                 free (p);
808                 freercsnode (&rcs2);
809
810                 for (u = 0; u < argc; ++u)
811                     RCS_addaccess (rcs, users[u]);
812                 free_names (&argc, users);
813                 break;
814             case 'n': /* fall through */
815             case 'N':
816                 if (arg[2] == '\0')
817                 {
818                     cvs_outerr ("missing symbolic name after ", 0);
819                     cvs_outerr (arg, 0);
820                     cvs_outerr ("\n", 1);
821                     break;
822                 }
823                 p = strchr (arg, ':');
824                 if (p == NULL)
825                 {
826                     if (RCS_deltag (rcs, arg + 2) != 0)
827                     {
828                         error (0, 0, "%s: Symbolic name %s is undefined.",
829                                rcs->path, 
830                                arg + 2);
831                         status = 1;
832                         continue;
833                     }
834                     break;
835                 }
836                 *p = '\0';
837                 tag = xstrdup (arg + 2);
838                 *p++ = ':';
839
840                 /* Option `n' signals an error if this tag is already bound. */
841                 if (arg[1] == 'n')
842                 {
843                     n = findnode (RCS_symbols (rcs), tag);
844                     if (n != NULL)
845                     {
846                         error (0, 0,
847                                "%s: symbolic name %s already bound to %s",
848                                rcs->path,
849                                tag, (char *)n->data);
850                         status = 1;
851                         free (tag);
852                         continue;
853                     }
854                 }
855
856                 /* Attempt to perform the requested tagging.  */
857
858                 if ((*p == 0 && (rev = RCS_head (rcs)))
859                     || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */
860                 {
861                     RCS_check_tag (tag); /* exit if not a valid tag */
862                     RCS_settag (rcs, tag, rev);
863                     free (rev);
864                 }
865                 else
866                 {
867                     if (!really_quiet)
868                         error (0, 0,
869                                "%s: Symbolic name or revision %s is undefined.",
870                                rcs->path, p);
871                     status = 1;
872                 }
873                 free (tag);
874                 break;
875             case 's':
876                 p = strchr (arg, ':');
877                 if (p == NULL)
878                 {
879                     tag = xstrdup (arg + 2);
880                     rev = RCS_head (rcs);
881                     if (!rev)
882                     {
883                         error (0, 0, "No head revision in archive file `%s'.",
884                                rcs->path);
885                         status = 1;
886                         continue;
887                     }
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 = xmalloc (sizeof (Deltatext));
958                     memset (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 }