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