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