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