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