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