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