11e72d8d2Sderaadt /*
21e72d8d2Sderaadt * Copyright (c) 1992, Brian Berliner and Jeff Polk
31e72d8d2Sderaadt * Copyright (c) 1989-1992, Brian Berliner
41e72d8d2Sderaadt *
51e72d8d2Sderaadt * You may distribute under the terms of the GNU General Public License as
62286d8edStholo * specified in the README file that comes with the CVS source distribution.
71e72d8d2Sderaadt *
82286d8edStholo * Administration ("cvs admin")
91e72d8d2Sderaadt *
101e72d8d2Sderaadt */
111e72d8d2Sderaadt
121e72d8d2Sderaadt #include "cvs.h"
1313571821Stholo #ifdef CVS_ADMIN_GROUP
1413571821Stholo #include <grp.h>
1513571821Stholo #endif
162286d8edStholo #include <assert.h>
171e72d8d2Sderaadt
1850bf276cStholo static Dtype admin_dirproc PROTO ((void *callerdat, char *dir,
1950bf276cStholo char *repos, char *update_dir,
2050bf276cStholo List *entries));
2150bf276cStholo static int admin_fileproc PROTO ((void *callerdat, struct file_info *finfo));
221e72d8d2Sderaadt
231e72d8d2Sderaadt static const char *const admin_usage[] =
241e72d8d2Sderaadt {
25e77048c1Stholo "Usage: %s %s [options] files...\n",
26e77048c1Stholo "\t-a users Append (comma-separated) user names to access list.\n",
27e77048c1Stholo "\t-A file Append another file's access list.\n",
28e77048c1Stholo "\t-b[rev] Set default branch (highest branch on trunk if omitted).\n",
29e77048c1Stholo "\t-c string Set comment leader.\n",
30f523375bSjcs "\t-C rev:id Set revision's commit id.\n",
31e77048c1Stholo "\t-e[users] Remove (comma-separated) user names from access list\n",
32e77048c1Stholo "\t (all names if omitted).\n",
33e77048c1Stholo "\t-I Run interactively.\n",
34e77048c1Stholo "\t-k subst Set keyword substitution mode:\n",
35e77048c1Stholo "\t kv (Default) Substitue keyword and value.\n",
36e77048c1Stholo "\t kvl Substitue keyword, value, and locker (if any).\n",
37e77048c1Stholo "\t k Substitue keyword only.\n",
38e77048c1Stholo "\t o Preserve original string.\n",
39e77048c1Stholo "\t b Like o, but mark file as binary.\n",
40e77048c1Stholo "\t v Substitue value only.\n",
41e77048c1Stholo "\t-l[rev] Lock revision (latest revision on branch,\n",
42e77048c1Stholo "\t latest revision on trunk if omitted).\n",
43e77048c1Stholo "\t-L Set strict locking.\n",
44e77048c1Stholo "\t-m rev:msg Replace revision's log message.\n",
45e77048c1Stholo "\t-n tag[:[rev]] Tag branch or revision. If :rev is omitted,\n",
46e77048c1Stholo "\t delete the tag; if rev is omitted, tag the latest\n",
47e77048c1Stholo "\t revision on the default branch.\n",
48e77048c1Stholo "\t-N tag[:[rev]] Same as -n except override existing tag.\n",
49e77048c1Stholo "\t-o range Delete (outdate) specified range of revisions:\n",
5043c1707eStholo "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
51e77048c1Stholo "\t rev1::rev2 Between rev1 and rev2, excluding rev1 and rev2.\n",
5243c1707eStholo "\t rev: rev and following revisions on the same branch.\n",
53e77048c1Stholo "\t rev:: After rev on the same branch.\n",
5443c1707eStholo "\t :rev rev and previous revisions on the same branch.\n",
55e77048c1Stholo "\t ::rev Before rev on the same branch.\n",
56e77048c1Stholo "\t rev Just rev.\n",
57e77048c1Stholo "\t-q Run quietly.\n",
58e77048c1Stholo "\t-s state[:rev] Set revision state (latest revision on branch,\n",
59e77048c1Stholo "\t latest revision on trunk if omitted).\n",
60e77048c1Stholo "\t-t[file] Get descriptive text from file (stdin if omitted).\n",
61e77048c1Stholo "\t-t-string Set descriptive text.\n",
62e77048c1Stholo "\t-u[rev] Unlock the revision (latest revision on branch,\n",
63e77048c1Stholo "\t latest revision on trunk if omitted).\n",
64e77048c1Stholo "\t-U Unset strict locking.\n",
652286d8edStholo "(Specify the --help global option for a list of other help options)\n",
661e72d8d2Sderaadt NULL
671e72d8d2Sderaadt };
681e72d8d2Sderaadt
692286d8edStholo /* This structure is used to pass information through start_recursion. */
702286d8edStholo struct admin_data
712286d8edStholo {
722286d8edStholo /* Set default branch (-b). It is "-b" followed by the value
732286d8edStholo given, or NULL if not specified, or merely "-b" if -b is
742286d8edStholo specified without a value. */
752286d8edStholo char *branch;
762286d8edStholo
772286d8edStholo /* Set comment leader (-c). It is "-c" followed by the value
782286d8edStholo given, or NULL if not specified. The comment leader is
792286d8edStholo relevant only for old versions of RCS, but we let people set it
802286d8edStholo anyway. */
812286d8edStholo char *comment;
822286d8edStholo
832286d8edStholo /* Set strict locking (-L). */
842286d8edStholo int set_strict;
852286d8edStholo
862286d8edStholo /* Set nonstrict locking (-U). */
872286d8edStholo int set_nonstrict;
882286d8edStholo
892286d8edStholo /* Delete revisions (-o). It is "-o" followed by the value specified. */
902286d8edStholo char *delete_revs;
912286d8edStholo
922286d8edStholo /* Keyword substitution mode (-k), e.g. "-kb". */
932286d8edStholo char *kflag;
942286d8edStholo
95e77048c1Stholo /* Description (-t). */
962286d8edStholo char *desc;
972286d8edStholo
982286d8edStholo /* Interactive (-I). Problematic with client/server. */
992286d8edStholo int interactive;
1002286d8edStholo
1012286d8edStholo /* This is the cheesy part. It is a vector with the options which
1022286d8edStholo we don't deal with above (e.g. "-afoo" "-abar,baz"). In the future
1032286d8edStholo this presumably will be replaced by other variables which break
1042286d8edStholo out the data in a more convenient fashion. AV as well as each of
1052286d8edStholo the strings it points to is malloc'd. */
1062286d8edStholo int ac;
1072286d8edStholo char **av;
1082286d8edStholo int av_alloc;
1092286d8edStholo };
1102286d8edStholo
1112286d8edStholo /* Add an argument. OPT is the option letter, e.g. 'a'. ARG is the
1122286d8edStholo argument to that option, or NULL if omitted (whether NULL can actually
1132286d8edStholo happen depends on whether the option was specified as optional to
1142286d8edStholo getopt). */
1152286d8edStholo static void
arg_add(dat,opt,arg)1162286d8edStholo arg_add (dat, opt, arg)
1172286d8edStholo struct admin_data *dat;
1182286d8edStholo int opt;
1192286d8edStholo char *arg;
1202286d8edStholo {
1212286d8edStholo char *newelt = xmalloc ((arg == NULL ? 0 : strlen (arg)) + 3);
1222286d8edStholo strcpy (newelt, "-");
1232286d8edStholo newelt[1] = opt;
1242286d8edStholo if (arg == NULL)
1252286d8edStholo newelt[2] = '\0';
1262286d8edStholo else
1272286d8edStholo strcpy (newelt + 2, arg);
1282286d8edStholo
1292286d8edStholo if (dat->av_alloc == 0)
1302286d8edStholo {
1312286d8edStholo dat->av_alloc = 1;
1322286d8edStholo dat->av = (char **) xmalloc (dat->av_alloc * sizeof (*dat->av));
1332286d8edStholo }
1342286d8edStholo else if (dat->ac >= dat->av_alloc)
1352286d8edStholo {
1362286d8edStholo dat->av_alloc *= 2;
1372286d8edStholo dat->av = (char **) xrealloc (dat->av,
1382286d8edStholo dat->av_alloc * sizeof (*dat->av));
1392286d8edStholo }
1402286d8edStholo dat->av[dat->ac++] = newelt;
1412286d8edStholo }
1421e72d8d2Sderaadt
1431e72d8d2Sderaadt int
admin(argc,argv)1441e72d8d2Sderaadt admin (argc, argv)
1451e72d8d2Sderaadt int argc;
1461e72d8d2Sderaadt char **argv;
1471e72d8d2Sderaadt {
1481e72d8d2Sderaadt int err;
14913571821Stholo #ifdef CVS_ADMIN_GROUP
15013571821Stholo struct group *grp;
151780d15dfStholo struct group *getgrnam();
15213571821Stholo #endif
1532286d8edStholo struct admin_data admin_data;
1542286d8edStholo int c;
1552286d8edStholo int i;
156892c0aadStholo int only_k_option;
1572286d8edStholo
1581e72d8d2Sderaadt if (argc <= 1)
1591e72d8d2Sderaadt usage (admin_usage);
1601e72d8d2Sderaadt
1611e72d8d2Sderaadt wrap_setup ();
1621e72d8d2Sderaadt
1632286d8edStholo memset (&admin_data, 0, sizeof admin_data);
1642286d8edStholo
1652286d8edStholo /* TODO: get rid of `-' switch notation in admin_data. For
1662286d8edStholo example, admin_data->branch should be not `-bfoo' but simply `foo'. */
1672286d8edStholo
1682286d8edStholo optind = 0;
169892c0aadStholo only_k_option = 1;
1702286d8edStholo while ((c = getopt (argc, argv,
171f523375bSjcs "+ib::c:C:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1)
1722286d8edStholo {
173892c0aadStholo if (c != 'k')
174892c0aadStholo only_k_option = 0;
175892c0aadStholo
1762286d8edStholo switch (c)
1772286d8edStholo {
1782286d8edStholo case 'i':
1792286d8edStholo /* This has always been documented as useless in cvs.texinfo
1802286d8edStholo and it really is--admin_fileproc silently does nothing
1812286d8edStholo if vers->vn_user is NULL. */
1822286d8edStholo error (0, 0, "the -i option to admin is not supported");
1832286d8edStholo error (0, 0, "run add or import to create an RCS file");
1842286d8edStholo goto usage_error;
1852286d8edStholo
1862286d8edStholo case 'b':
1872286d8edStholo if (admin_data.branch != NULL)
1882286d8edStholo {
1892286d8edStholo error (0, 0, "duplicate 'b' option");
1902286d8edStholo goto usage_error;
1912286d8edStholo }
1922286d8edStholo if (optarg == NULL)
1932286d8edStholo admin_data.branch = xstrdup ("-b");
1942286d8edStholo else
1952286d8edStholo {
1962286d8edStholo admin_data.branch = xmalloc (strlen (optarg) + 5);
1972286d8edStholo strcpy (admin_data.branch, "-b");
1982286d8edStholo strcat (admin_data.branch, optarg);
1992286d8edStholo }
2001e72d8d2Sderaadt break;
2012286d8edStholo
2022286d8edStholo case 'c':
2032286d8edStholo if (admin_data.comment != NULL)
2042286d8edStholo {
2052286d8edStholo error (0, 0, "duplicate 'c' option");
2062286d8edStholo goto usage_error;
2072286d8edStholo }
2082286d8edStholo admin_data.comment = xmalloc (strlen (optarg) + 5);
2092286d8edStholo strcpy (admin_data.comment, "-c");
2102286d8edStholo strcat (admin_data.comment, optarg);
2112286d8edStholo break;
2122286d8edStholo
213f523375bSjcs case 'C':
214f523375bSjcs /* Add commitid. */
215f523375bSjcs arg_add (&admin_data, 'C', optarg);
216f523375bSjcs break;
217f523375bSjcs
2182286d8edStholo case 'a':
2192286d8edStholo arg_add (&admin_data, 'a', optarg);
2202286d8edStholo break;
2212286d8edStholo
2222286d8edStholo case 'A':
2232286d8edStholo /* In the client/server case, this is cheesy because
2242286d8edStholo we just pass along the name of the RCS file, which
2252286d8edStholo then will want to exist on the server. This is
2262286d8edStholo accidental; having the client specify a pathname on
2272286d8edStholo the server is not a design feature of the protocol. */
2282286d8edStholo arg_add (&admin_data, 'A', optarg);
2292286d8edStholo break;
2302286d8edStholo
2312286d8edStholo case 'e':
2322286d8edStholo arg_add (&admin_data, 'e', optarg);
2332286d8edStholo break;
2342286d8edStholo
2352286d8edStholo case 'l':
2362286d8edStholo /* Note that multiple -l options are legal. */
2372286d8edStholo arg_add (&admin_data, 'l', optarg);
2382286d8edStholo break;
2392286d8edStholo
2402286d8edStholo case 'u':
2412286d8edStholo /* Note that multiple -u options are legal. */
2422286d8edStholo arg_add (&admin_data, 'u', optarg);
2432286d8edStholo break;
2442286d8edStholo
2452286d8edStholo case 'L':
2462286d8edStholo /* Probably could also complain if -L is specified multiple
2472286d8edStholo times, although RCS doesn't and I suppose it is reasonable
2482286d8edStholo just to have it mean the same as a single -L. */
2492286d8edStholo if (admin_data.set_nonstrict)
2502286d8edStholo {
2512286d8edStholo error (0, 0, "-U and -L are incompatible");
2522286d8edStholo goto usage_error;
2532286d8edStholo }
2542286d8edStholo admin_data.set_strict = 1;
2552286d8edStholo break;
2562286d8edStholo
2572286d8edStholo case 'U':
2582286d8edStholo /* Probably could also complain if -U is specified multiple
2592286d8edStholo times, although RCS doesn't and I suppose it is reasonable
2602286d8edStholo just to have it mean the same as a single -U. */
2612286d8edStholo if (admin_data.set_strict)
2622286d8edStholo {
2632286d8edStholo error (0, 0, "-U and -L are incompatible");
2642286d8edStholo goto usage_error;
2652286d8edStholo }
2662286d8edStholo admin_data.set_nonstrict = 1;
2672286d8edStholo break;
2682286d8edStholo
2692286d8edStholo case 'n':
2702286d8edStholo /* Mostly similar to cvs tag. Could also be parsing
2712286d8edStholo the syntax of optarg, although for now we just pass
2722286d8edStholo it to rcs as-is. Note that multiple -n options are
2732286d8edStholo legal. */
2742286d8edStholo arg_add (&admin_data, 'n', optarg);
2752286d8edStholo break;
2762286d8edStholo
2772286d8edStholo case 'N':
2782286d8edStholo /* Mostly similar to cvs tag. Could also be parsing
2792286d8edStholo the syntax of optarg, although for now we just pass
2802286d8edStholo it to rcs as-is. Note that multiple -N options are
2812286d8edStholo legal. */
2822286d8edStholo arg_add (&admin_data, 'N', optarg);
2832286d8edStholo break;
2842286d8edStholo
2852286d8edStholo case 'm':
2862286d8edStholo /* Change log message. Could also be parsing the syntax
2872286d8edStholo of optarg, although for now we just pass it to rcs
2882286d8edStholo as-is. Note that multiple -m options are legal. */
2892286d8edStholo arg_add (&admin_data, 'm', optarg);
2902286d8edStholo break;
2912286d8edStholo
2922286d8edStholo case 'o':
2932286d8edStholo /* Delete revisions. Probably should also be parsing the
2942286d8edStholo syntax of optarg, so that the client can give errors
2952286d8edStholo rather than making the server take care of that.
2962286d8edStholo Other than that I'm not sure whether it matters much
2972286d8edStholo whether we parse it here or in admin_fileproc.
2982286d8edStholo
2992286d8edStholo Note that multiple -o options are illegal, in RCS
3002286d8edStholo as well as here. */
3012286d8edStholo
3022286d8edStholo if (admin_data.delete_revs != NULL)
3032286d8edStholo {
3042286d8edStholo error (0, 0, "duplicate '-o' option");
3052286d8edStholo goto usage_error;
3062286d8edStholo }
3072286d8edStholo admin_data.delete_revs = xmalloc (strlen (optarg) + 5);
3082286d8edStholo strcpy (admin_data.delete_revs, "-o");
3092286d8edStholo strcat (admin_data.delete_revs, optarg);
3102286d8edStholo break;
3112286d8edStholo
3122286d8edStholo case 's':
3132286d8edStholo /* Note that multiple -s options are legal. */
3142286d8edStholo arg_add (&admin_data, 's', optarg);
3152286d8edStholo break;
3162286d8edStholo
3172286d8edStholo case 't':
3182286d8edStholo if (admin_data.desc != NULL)
3192286d8edStholo {
3202286d8edStholo error (0, 0, "duplicate 't' option");
3212286d8edStholo goto usage_error;
3222286d8edStholo }
323e77048c1Stholo if (optarg != NULL && optarg[0] == '-')
324e77048c1Stholo admin_data.desc = xstrdup (optarg + 1);
3252286d8edStholo else
3262286d8edStholo {
327e77048c1Stholo size_t bufsize = 0;
328e77048c1Stholo size_t len;
329e77048c1Stholo
330e77048c1Stholo get_file (optarg, optarg, "r", &admin_data.desc,
331e77048c1Stholo &bufsize, &len);
3322286d8edStholo }
3332286d8edStholo break;
3342286d8edStholo
3352286d8edStholo case 'I':
3362286d8edStholo /* At least in RCS this can be specified several times,
3372286d8edStholo with the same meaning as being specified once. */
3382286d8edStholo admin_data.interactive = 1;
3392286d8edStholo break;
3402286d8edStholo
3412286d8edStholo case 'q':
34243c1707eStholo /* Silently set the global really_quiet flag. This keeps admin in
34343c1707eStholo * sync with the RCS man page and allows us to silently support
34443c1707eStholo * older servers when necessary.
34543c1707eStholo *
34643c1707eStholo * Some logic says we might want to output a deprecation warning
34743c1707eStholo * here, but I'm opting not to in order to stay quietly in sync
34843c1707eStholo * with the RCS man page.
34943c1707eStholo */
35043c1707eStholo really_quiet = 1;
3512286d8edStholo break;
3522286d8edStholo
3532286d8edStholo case 'x':
3542286d8edStholo error (0, 0, "the -x option has never done anything useful");
3552286d8edStholo error (0, 0, "RCS files in CVS always end in ,v");
3562286d8edStholo goto usage_error;
3572286d8edStholo
3582286d8edStholo case 'V':
3592286d8edStholo /* No longer supported. */
3602286d8edStholo error (0, 0, "the `-V' option is obsolete");
3612286d8edStholo break;
3622286d8edStholo
3632286d8edStholo case 'k':
3642286d8edStholo if (admin_data.kflag != NULL)
3652286d8edStholo {
3662286d8edStholo error (0, 0, "duplicate '-k' option");
3672286d8edStholo goto usage_error;
3682286d8edStholo }
3692286d8edStholo admin_data.kflag = RCS_check_kflag (optarg);
3702286d8edStholo break;
3712286d8edStholo default:
3722286d8edStholo case '?':
3732286d8edStholo /* getopt will have printed an error message. */
3742286d8edStholo
3752286d8edStholo usage_error:
3762286d8edStholo /* Don't use command_name; it might be "server". */
3772286d8edStholo error (1, 0, "specify %s -H admin for usage information",
3782286d8edStholo program_name);
3792286d8edStholo }
3802286d8edStholo }
3812286d8edStholo argc -= optind;
3822286d8edStholo argv += optind;
3832286d8edStholo
384892c0aadStholo #ifdef CVS_ADMIN_GROUP
38543c1707eStholo /* The use of `cvs admin -k' is unrestricted. However, any other
38643c1707eStholo option is restricted if the group CVS_ADMIN_GROUP exists. */
38743c1707eStholo if (!only_k_option &&
38843c1707eStholo (grp = getgrnam(CVS_ADMIN_GROUP)) != NULL)
389892c0aadStholo {
39043c1707eStholo #ifdef HAVE_GETGROUPS
39143c1707eStholo gid_t *grps;
39243c1707eStholo int n;
393892c0aadStholo
39443c1707eStholo /* get number of auxiliary groups */
39543c1707eStholo n = getgroups (0, NULL);
39643c1707eStholo if (n < 0)
39743c1707eStholo error (1, errno, "unable to get number of auxiliary groups");
39843c1707eStholo grps = (gid_t *) xmalloc((n + 1) * sizeof *grps);
39943c1707eStholo n = getgroups (n, grps);
40043c1707eStholo if (n < 0)
40143c1707eStholo error (1, errno, "unable to get list of auxiliary groups");
40243c1707eStholo grps[n] = getgid();
40343c1707eStholo for (i = 0; i <= n; i++)
40443c1707eStholo if (grps[i] == grp->gr_gid) break;
40543c1707eStholo free (grps);
40643c1707eStholo if (i > n)
407892c0aadStholo error (1, 0, "usage is restricted to members of the group %s",
408892c0aadStholo CVS_ADMIN_GROUP);
40943c1707eStholo #else
41043c1707eStholo char *me = getcaller();
41143c1707eStholo char **grnam;
41243c1707eStholo
41343c1707eStholo for (grnam = grp->gr_mem; *grnam; grnam++)
41443c1707eStholo if (strcmp (*grnam, me) == 0) break;
41543c1707eStholo if (!*grnam && getgid() != grp->gr_gid)
41643c1707eStholo error (1, 0, "usage is restricted to members of the group %s",
41743c1707eStholo CVS_ADMIN_GROUP);
41843c1707eStholo #endif
419892c0aadStholo }
420892c0aadStholo #endif
421892c0aadStholo
4222286d8edStholo for (i = 0; i < admin_data.ac; ++i)
4232286d8edStholo {
4242286d8edStholo assert (admin_data.av[i][0] == '-');
4252286d8edStholo switch (admin_data.av[i][1])
4262286d8edStholo {
4272286d8edStholo case 'm':
4282286d8edStholo case 'l':
4292286d8edStholo case 'u':
4302286d8edStholo check_numeric (&admin_data.av[i][2], argc, argv);
4312286d8edStholo break;
4322286d8edStholo default:
4332286d8edStholo break;
4342286d8edStholo }
4352286d8edStholo }
4362286d8edStholo if (admin_data.branch != NULL)
4372286d8edStholo check_numeric (admin_data.branch + 2, argc, argv);
4382286d8edStholo if (admin_data.delete_revs != NULL)
4392286d8edStholo {
4402286d8edStholo char *p;
4412286d8edStholo
4422286d8edStholo check_numeric (admin_data.delete_revs + 2, argc, argv);
4432286d8edStholo p = strchr (admin_data.delete_revs + 2, ':');
444c71bc7e2Stholo if (p != NULL && isdigit ((unsigned char) p[1]))
4452286d8edStholo check_numeric (p + 1, argc, argv);
446c71bc7e2Stholo else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2]))
4472286d8edStholo check_numeric (p + 2, argc, argv);
4482286d8edStholo }
4491e72d8d2Sderaadt
4501e72d8d2Sderaadt #ifdef CLIENT_SUPPORT
45143c1707eStholo if (current_parsed_root->isremote)
4521e72d8d2Sderaadt {
4531e72d8d2Sderaadt /* We're the client side. Fire up the remote server. */
4541e72d8d2Sderaadt start_server ();
4551e72d8d2Sderaadt
4561e72d8d2Sderaadt ign_setup ();
4571e72d8d2Sderaadt
4582286d8edStholo /* Note that option_with_arg does not work for us, because some
4592286d8edStholo of the options must be sent without a space between the option
4602286d8edStholo and its argument. */
4612286d8edStholo if (admin_data.interactive)
4622286d8edStholo error (1, 0, "-I option not useful with client/server");
4632286d8edStholo if (admin_data.branch != NULL)
4642286d8edStholo send_arg (admin_data.branch);
4652286d8edStholo if (admin_data.comment != NULL)
4662286d8edStholo send_arg (admin_data.comment);
4672286d8edStholo if (admin_data.set_strict)
4682286d8edStholo send_arg ("-L");
4692286d8edStholo if (admin_data.set_nonstrict)
4702286d8edStholo send_arg ("-U");
4712286d8edStholo if (admin_data.delete_revs != NULL)
4722286d8edStholo send_arg (admin_data.delete_revs);
4732286d8edStholo if (admin_data.desc != NULL)
474e77048c1Stholo {
475e77048c1Stholo char *p = admin_data.desc;
476e77048c1Stholo send_to_server ("Argument -t-", 0);
477e77048c1Stholo while (*p)
478e77048c1Stholo {
479e77048c1Stholo if (*p == '\n')
480e77048c1Stholo {
481e77048c1Stholo send_to_server ("\012Argumentx ", 0);
482e77048c1Stholo ++p;
483e77048c1Stholo }
484e77048c1Stholo else
485e77048c1Stholo {
486e77048c1Stholo char *q = strchr (p, '\n');
487e77048c1Stholo if (q == NULL) q = p + strlen (p);
488e77048c1Stholo send_to_server (p, q - p);
489e77048c1Stholo p = q;
490e77048c1Stholo }
491e77048c1Stholo }
492e77048c1Stholo send_to_server ("\012", 1);
493e77048c1Stholo }
49443c1707eStholo /* Send this for all really_quiets since we know that it will be silently
49543c1707eStholo * ignored when unneeded. This supports old servers.
49643c1707eStholo */
49743c1707eStholo if (really_quiet)
4982286d8edStholo send_arg ("-q");
4992286d8edStholo if (admin_data.kflag != NULL)
5002286d8edStholo send_arg (admin_data.kflag);
5012286d8edStholo
5022286d8edStholo for (i = 0; i < admin_data.ac; ++i)
5032286d8edStholo send_arg (admin_data.av[i]);
5041e72d8d2Sderaadt
505b6c02222Stholo send_files (argc, argv, 0, 0, SEND_NO_CONTENTS);
506c71bc7e2Stholo send_file_names (argc, argv, SEND_EXPAND_WILD);
50713571821Stholo send_to_server ("admin\012", 0);
5082286d8edStholo err = get_responses_and_close ();
5092286d8edStholo goto return_it;
5101e72d8d2Sderaadt }
5111e72d8d2Sderaadt #endif /* CLIENT_SUPPORT */
5121e72d8d2Sderaadt
51343c1707eStholo lock_tree_for_write (argc, argv, 0, W_LOCAL, 0);
5142286d8edStholo
51513571821Stholo err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc,
5162286d8edStholo (DIRLEAVEPROC) NULL, (void *)&admin_data,
5172286d8edStholo argc, argv, 0,
5182286d8edStholo W_LOCAL, 0, 0, (char *) NULL, 1);
5192286d8edStholo Lock_Cleanup ();
5202286d8edStholo
5212286d8edStholo return_it:
5222286d8edStholo if (admin_data.branch != NULL)
5232286d8edStholo free (admin_data.branch);
5242286d8edStholo if (admin_data.comment != NULL)
5252286d8edStholo free (admin_data.comment);
5262286d8edStholo if (admin_data.delete_revs != NULL)
5272286d8edStholo free (admin_data.delete_revs);
5282286d8edStholo if (admin_data.kflag != NULL)
5292286d8edStholo free (admin_data.kflag);
5302286d8edStholo if (admin_data.desc != NULL)
5312286d8edStholo free (admin_data.desc);
5322286d8edStholo for (i = 0; i < admin_data.ac; ++i)
5332286d8edStholo free (admin_data.av[i]);
5342286d8edStholo if (admin_data.av != NULL)
5352286d8edStholo free (admin_data.av);
5362286d8edStholo
5371e72d8d2Sderaadt return (err);
5381e72d8d2Sderaadt }
5391e72d8d2Sderaadt
5401e72d8d2Sderaadt /*
5411e72d8d2Sderaadt * Called to run "rcs" on a particular file.
5421e72d8d2Sderaadt */
5431e72d8d2Sderaadt /* ARGSUSED */
5441e72d8d2Sderaadt static int
admin_fileproc(callerdat,finfo)54550bf276cStholo admin_fileproc (callerdat, finfo)
54650bf276cStholo void *callerdat;
547c26070a5Stholo struct file_info *finfo;
5481e72d8d2Sderaadt {
5492286d8edStholo struct admin_data *admin_data = (struct admin_data *) callerdat;
5501e72d8d2Sderaadt Vers_TS *vers;
5511e72d8d2Sderaadt char *version;
5522286d8edStholo int i;
55313571821Stholo int status = 0;
5542286d8edStholo RCSNode *rcs, *rcs2;
5551e72d8d2Sderaadt
55650bf276cStholo vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
5571e72d8d2Sderaadt
5581e72d8d2Sderaadt version = vers->vn_user;
5591e72d8d2Sderaadt if (version == NULL)
56013571821Stholo goto exitfunc;
5611e72d8d2Sderaadt else if (strcmp (version, "0") == 0)
5621e72d8d2Sderaadt {
563c26070a5Stholo error (0, 0, "cannot admin newly added file `%s'", finfo->file);
56413571821Stholo goto exitfunc;
5651e72d8d2Sderaadt }
5661e72d8d2Sderaadt
5672286d8edStholo rcs = vers->srcfile;
568518886fbStobias if (rcs == NULL)
569518886fbStobias {
570518886fbStobias error (0, 0, "lost revision control file for `%s'", finfo->file);
571518886fbStobias goto exitfunc;
572518886fbStobias }
5732286d8edStholo if (rcs->flags & PARTIAL)
5745e617892Stholo RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
5752286d8edStholo
5762286d8edStholo status = 0;
5772286d8edStholo
57843c1707eStholo if (!really_quiet)
5791e72d8d2Sderaadt {
5802286d8edStholo cvs_output ("RCS file: ", 0);
5812286d8edStholo cvs_output (rcs->path, 0);
5822286d8edStholo cvs_output ("\n", 1);
5831e72d8d2Sderaadt }
5842286d8edStholo
5852286d8edStholo if (admin_data->branch != NULL)
5865e617892Stholo {
5875e617892Stholo char *branch = &admin_data->branch[2];
588c71bc7e2Stholo if (*branch != '\0' && ! isdigit ((unsigned char) *branch))
5895e617892Stholo {
5905e617892Stholo branch = RCS_whatbranch (rcs, admin_data->branch + 2);
5915e617892Stholo if (branch == NULL)
5925e617892Stholo {
5935e617892Stholo error (0, 0, "%s: Symbolic name %s is undefined.",
5945e617892Stholo rcs->path, admin_data->branch + 2);
5955e617892Stholo status = 1;
5965e617892Stholo }
5975e617892Stholo }
5985e617892Stholo if (status == 0)
5995e617892Stholo RCS_setbranch (rcs, branch);
6005e617892Stholo if (branch != NULL && branch != &admin_data->branch[2])
6015e617892Stholo free (branch);
6025e617892Stholo }
6032286d8edStholo if (admin_data->comment != NULL)
6042286d8edStholo {
6052286d8edStholo if (rcs->comment != NULL)
6062286d8edStholo free (rcs->comment);
6072286d8edStholo rcs->comment = xstrdup (admin_data->comment + 2);
6082286d8edStholo }
6092286d8edStholo if (admin_data->set_strict)
6102286d8edStholo rcs->strict_locks = 1;
6112286d8edStholo if (admin_data->set_nonstrict)
6122286d8edStholo rcs->strict_locks = 0;
6132286d8edStholo if (admin_data->delete_revs != NULL)
6142286d8edStholo {
6152286d8edStholo char *s, *t, *rev1, *rev2;
6162286d8edStholo /* Set for :, clear for ::. */
6172286d8edStholo int inclusive;
6182286d8edStholo char *t2;
6192286d8edStholo
6202286d8edStholo s = admin_data->delete_revs + 2;
6212286d8edStholo inclusive = 1;
6222286d8edStholo t = strchr (s, ':');
6232286d8edStholo if (t != NULL)
6242286d8edStholo {
6252286d8edStholo if (t[1] == ':')
6262286d8edStholo {
6272286d8edStholo inclusive = 0;
6282286d8edStholo t2 = t + 2;
6292286d8edStholo }
6302286d8edStholo else
6312286d8edStholo t2 = t + 1;
6322286d8edStholo }
6332286d8edStholo
6342286d8edStholo /* Note that we don't support '-' for ranges. RCS considers it
6352286d8edStholo obsolete and it is problematic with tags containing '-'. "cvs log"
6362286d8edStholo has made the same decision. */
6372286d8edStholo
6382286d8edStholo if (t == NULL)
6392286d8edStholo {
6402286d8edStholo /* -orev */
6412286d8edStholo rev1 = xstrdup (s);
6422286d8edStholo rev2 = xstrdup (s);
6432286d8edStholo }
6442286d8edStholo else if (t == s)
6452286d8edStholo {
6462286d8edStholo /* -o:rev2 */
6472286d8edStholo rev1 = NULL;
6482286d8edStholo rev2 = xstrdup (t2);
6492286d8edStholo }
6502286d8edStholo else
6512286d8edStholo {
6522286d8edStholo *t = '\0';
6532286d8edStholo rev1 = xstrdup (s);
6542286d8edStholo *t = ':'; /* probably unnecessary */
6552286d8edStholo if (*t2 == '\0')
6562286d8edStholo /* -orev1: */
6572286d8edStholo rev2 = NULL;
6582286d8edStholo else
6592286d8edStholo /* -orev1:rev2 */
6602286d8edStholo rev2 = xstrdup (t2);
6612286d8edStholo }
6622286d8edStholo
6632286d8edStholo if (rev1 == NULL && rev2 == NULL)
6642286d8edStholo {
6652286d8edStholo /* RCS segfaults if `-o:' is given */
6662286d8edStholo error (0, 0, "no valid revisions specified in `%s' option",
6672286d8edStholo admin_data->delete_revs);
6682286d8edStholo status = 1;
6692286d8edStholo }
6702286d8edStholo else
6712286d8edStholo {
6722286d8edStholo status |= RCS_delete_revs (rcs, rev1, rev2, inclusive);
6732286d8edStholo if (rev1)
6742286d8edStholo free (rev1);
6752286d8edStholo if (rev2)
6762286d8edStholo free (rev2);
6772286d8edStholo }
6782286d8edStholo }
6792286d8edStholo if (admin_data->desc != NULL)
6802286d8edStholo {
6812286d8edStholo free (rcs->desc);
682e77048c1Stholo rcs->desc = xstrdup (admin_data->desc);
6832286d8edStholo }
6842286d8edStholo if (admin_data->kflag != NULL)
6852286d8edStholo {
6862286d8edStholo char *kflag = admin_data->kflag + 2;
687c71bc7e2Stholo char *oldexpand = RCS_getexpand (rcs);
688c71bc7e2Stholo if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0)
689c71bc7e2Stholo RCS_setexpand (rcs, kflag);
6902286d8edStholo }
6912286d8edStholo
6922286d8edStholo /* Handle miscellaneous options. TODO: decide whether any or all
6932286d8edStholo of these should have their own fields in the admin_data
6942286d8edStholo structure. */
6952286d8edStholo for (i = 0; i < admin_data->ac; ++i)
6962286d8edStholo {
6972286d8edStholo char *arg;
698f523375bSjcs char *p, *rev, *revnum, *tag, *msg, *commitid;
6992286d8edStholo char **users;
7002286d8edStholo int argc, u;
7012286d8edStholo Node *n;
7022286d8edStholo RCSVers *delta;
7032286d8edStholo
7042286d8edStholo arg = admin_data->av[i];
7052286d8edStholo switch (arg[1])
7062286d8edStholo {
7072286d8edStholo case 'a': /* fall through */
7082286d8edStholo case 'e':
7092286d8edStholo line2argv (&argc, &users, arg + 2, " ,\t\n");
7102286d8edStholo if (arg[1] == 'a')
7112286d8edStholo for (u = 0; u < argc; ++u)
7122286d8edStholo RCS_addaccess (rcs, users[u]);
713e77048c1Stholo else if (argc == 0)
714e77048c1Stholo RCS_delaccess (rcs, NULL);
7152286d8edStholo else
7162286d8edStholo for (u = 0; u < argc; ++u)
7172286d8edStholo RCS_delaccess (rcs, users[u]);
7182286d8edStholo free_names (&argc, users);
7192286d8edStholo break;
7202286d8edStholo case 'A':
7212286d8edStholo
7222286d8edStholo /* See admin-19a-admin and friends in sanity.sh for
7232286d8edStholo relative pathnames. It makes sense to think in
7242286d8edStholo terms of a syntax which give pathnames relative to
7252286d8edStholo the repository or repository corresponding to the
7262286d8edStholo current directory or some such (and perhaps don't
7272286d8edStholo include ,v), but trying to worry about such things
7282286d8edStholo is a little pointless unless you first worry about
7292286d8edStholo whether "cvs admin -A" as a whole makes any sense
7302286d8edStholo (currently probably not, as access lists don't
7312286d8edStholo affect the behavior of CVS). */
7322286d8edStholo
7332286d8edStholo rcs2 = RCS_parsercsfile (arg + 2);
7342286d8edStholo if (rcs2 == NULL)
7352286d8edStholo error (1, 0, "cannot continue");
7362286d8edStholo
7372286d8edStholo p = xstrdup (RCS_getaccess (rcs2));
7382286d8edStholo line2argv (&argc, &users, p, " \t\n");
7392286d8edStholo free (p);
7402286d8edStholo freercsnode (&rcs2);
7412286d8edStholo
7422286d8edStholo for (u = 0; u < argc; ++u)
7432286d8edStholo RCS_addaccess (rcs, users[u]);
7442286d8edStholo free_names (&argc, users);
7452286d8edStholo break;
746f523375bSjcs case 'C':
747f523375bSjcs p = strchr (arg, ':');
748f523375bSjcs if (p == NULL)
749f523375bSjcs {
750f523375bSjcs error (0, 0, "%s: -C option lacks commitid", rcs->path);
751f523375bSjcs status = 1;
752f523375bSjcs continue;
753f523375bSjcs }
754f523375bSjcs *p = '\0';
755f523375bSjcs rev = RCS_gettag (rcs, arg + 2, 1, NULL);
756f523375bSjcs if (rev == NULL)
757f523375bSjcs {
758f523375bSjcs error (0, 0, "%s: no such revision %s", rcs->path, arg + 2);
759f523375bSjcs status = 1;
760f523375bSjcs continue;
761f523375bSjcs }
762f523375bSjcs *p++ = ':';
763f523375bSjcs commitid = p;
764f523375bSjcs
765f523375bSjcs if (*commitid == '\0')
766f523375bSjcs {
767f523375bSjcs error (0, 0, "%s: -C option lacks commitid", rcs->path);
768f523375bSjcs free (rev);
769f523375bSjcs status = 1;
770f523375bSjcs continue;
771f523375bSjcs }
772f523375bSjcs
773f523375bSjcs n = findnode (rcs->versions, rev);
774f523375bSjcs delta = (RCSVers *) n->data;
775f523375bSjcs
776f523375bSjcs if (delta->other_delta == NULL)
777f523375bSjcs delta->other_delta = getlist();
778f523375bSjcs
779*0971b67fStb if ((n = findnode (delta->other_delta, "commitid")))
780f523375bSjcs {
781f523375bSjcs error (0, 0, "%s: revision %s already has commitid %s",
782f523375bSjcs rcs->path, rev, n->data);
783f523375bSjcs free (rev);
784f523375bSjcs status = 1;
785f523375bSjcs continue;
786f523375bSjcs }
787f523375bSjcs
788f523375bSjcs n = getnode();
789f523375bSjcs n->type = RCSFIELD;
790f523375bSjcs n->key = xstrdup ("commitid");
791f523375bSjcs n->data = xstrdup(commitid);
792f523375bSjcs addnode (delta->other_delta, n);
793f523375bSjcs
794f523375bSjcs free (rev);
795f523375bSjcs
796f523375bSjcs break;
7972286d8edStholo case 'n': /* fall through */
7982286d8edStholo case 'N':
7992286d8edStholo if (arg[2] == '\0')
8002286d8edStholo {
8012286d8edStholo cvs_outerr ("missing symbolic name after ", 0);
8022286d8edStholo cvs_outerr (arg, 0);
8032286d8edStholo cvs_outerr ("\n", 1);
8042286d8edStholo break;
8052286d8edStholo }
8062286d8edStholo p = strchr (arg, ':');
8072286d8edStholo if (p == NULL)
8082286d8edStholo {
8092286d8edStholo if (RCS_deltag (rcs, arg + 2) != 0)
8102286d8edStholo {
8112286d8edStholo error (0, 0, "%s: Symbolic name %s is undefined.",
8122286d8edStholo rcs->path,
8132286d8edStholo arg + 2);
8142286d8edStholo status = 1;
8152286d8edStholo continue;
8162286d8edStholo }
8172286d8edStholo break;
8182286d8edStholo }
8192286d8edStholo *p = '\0';
8202286d8edStholo tag = xstrdup (arg + 2);
8212286d8edStholo *p++ = ':';
8222286d8edStholo
8232286d8edStholo /* Option `n' signals an error if this tag is already bound. */
8242286d8edStholo if (arg[1] == 'n')
8252286d8edStholo {
8262286d8edStholo n = findnode (RCS_symbols (rcs), tag);
8272286d8edStholo if (n != NULL)
8282286d8edStholo {
8292286d8edStholo error (0, 0,
8302286d8edStholo "%s: symbolic name %s already bound to %s",
8312286d8edStholo rcs->path,
8322286d8edStholo tag, n->data);
8332286d8edStholo status = 1;
834b6f6614eStholo free (tag);
8352286d8edStholo continue;
8362286d8edStholo }
8372286d8edStholo }
8382286d8edStholo
839b6f6614eStholo /* Attempt to perform the requested tagging. */
840b6f6614eStholo
841b6f6614eStholo if ((*p == 0 && (rev = RCS_head (rcs)))
842b6f6614eStholo || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */
843b6f6614eStholo {
844b6f6614eStholo RCS_check_tag (tag); /* exit if not a valid tag */
8452286d8edStholo RCS_settag (rcs, tag, rev);
8462286d8edStholo free (rev);
847b6f6614eStholo }
848b6f6614eStholo else
849b6f6614eStholo {
85043c1707eStholo if (!really_quiet)
851b6f6614eStholo error (0, 0,
85243c1707eStholo "%s: Symbolic name or revision %s is undefined.",
853b6f6614eStholo rcs->path, p);
854b6f6614eStholo status = 1;
855b6f6614eStholo }
8562286d8edStholo free (tag);
8572286d8edStholo break;
8582286d8edStholo case 's':
8592286d8edStholo p = strchr (arg, ':');
8602286d8edStholo if (p == NULL)
8612286d8edStholo {
8622286d8edStholo tag = xstrdup (arg + 2);
8632286d8edStholo rev = RCS_head (rcs);
8642286d8edStholo }
8652286d8edStholo else
8662286d8edStholo {
8672286d8edStholo *p = '\0';
8682286d8edStholo tag = xstrdup (arg + 2);
8692286d8edStholo *p++ = ':';
8702286d8edStholo rev = xstrdup (p);
8712286d8edStholo }
8722286d8edStholo revnum = RCS_gettag (rcs, rev, 0, NULL);
8732286d8edStholo if (revnum != NULL)
874e77048c1Stholo {
8752286d8edStholo n = findnode (rcs->versions, revnum);
876e77048c1Stholo free (revnum);
877e77048c1Stholo }
878e77048c1Stholo else
879e77048c1Stholo n = NULL;
880e77048c1Stholo if (n == NULL)
8812286d8edStholo {
8822286d8edStholo error (0, 0,
8832286d8edStholo "%s: can't set state of nonexisting revision %s",
8842286d8edStholo rcs->path,
8852286d8edStholo rev);
886e77048c1Stholo free (rev);
8872286d8edStholo status = 1;
8882286d8edStholo continue;
8892286d8edStholo }
890e77048c1Stholo free (rev);
8912286d8edStholo delta = (RCSVers *) n->data;
8922286d8edStholo free (delta->state);
8932286d8edStholo delta->state = tag;
8942286d8edStholo break;
8952286d8edStholo
8962286d8edStholo case 'm':
8972286d8edStholo p = strchr (arg, ':');
8982286d8edStholo if (p == NULL)
8992286d8edStholo {
9002286d8edStholo error (0, 0, "%s: -m option lacks revision number",
9012286d8edStholo rcs->path);
9022286d8edStholo status = 1;
9032286d8edStholo continue;
9042286d8edStholo }
9052286d8edStholo *p = '\0';
9062286d8edStholo rev = RCS_gettag (rcs, arg + 2, 0, NULL);
9072286d8edStholo if (rev == NULL)
9082286d8edStholo {
9092286d8edStholo error (0, 0, "%s: no such revision %s", rcs->path, rev);
9102286d8edStholo status = 1;
9112286d8edStholo continue;
9122286d8edStholo }
9132286d8edStholo *p++ = ':';
9142286d8edStholo msg = p;
9152286d8edStholo
9162286d8edStholo n = findnode (rcs->versions, rev);
917e77048c1Stholo free (rev);
9182286d8edStholo delta = (RCSVers *) n->data;
9192286d8edStholo if (delta->text == NULL)
9202286d8edStholo {
9212286d8edStholo delta->text = (Deltatext *) xmalloc (sizeof (Deltatext));
9222286d8edStholo memset ((void *) delta->text, 0, sizeof (Deltatext));
9232286d8edStholo }
9242286d8edStholo delta->text->version = xstrdup (delta->version);
9252286d8edStholo delta->text->log = make_message_rcslegal (msg);
9262286d8edStholo break;
9272286d8edStholo
9282286d8edStholo case 'l':
9292286d8edStholo status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0);
9302286d8edStholo break;
9312286d8edStholo case 'u':
9322286d8edStholo status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0);
9332286d8edStholo break;
9342286d8edStholo default: assert(0); /* can't happen */
9352286d8edStholo }
9362286d8edStholo }
9372286d8edStholo
9382286d8edStholo if (status == 0)
9392286d8edStholo {
9402286d8edStholo RCS_rewrite (rcs, NULL, NULL);
94143c1707eStholo if (!really_quiet)
9422286d8edStholo cvs_output ("done\n", 5);
9432286d8edStholo }
9442286d8edStholo else
9452286d8edStholo {
9462286d8edStholo /* Note that this message should only occur after another
9472286d8edStholo message has given a more specific error. The point of this
9482286d8edStholo additional message is to make it clear that the previous problems
9492286d8edStholo caused CVS to forget about the idea of modifying the RCS file. */
95043c1707eStholo if (!really_quiet)
95143c1707eStholo error (0, 0, "RCS file for `%s' not modified.", finfo->file);
952e77048c1Stholo RCS_abandon (rcs);
9532286d8edStholo }
9542286d8edStholo
95513571821Stholo exitfunc:
95613571821Stholo freevers_ts (&vers);
95713571821Stholo return status;
9581e72d8d2Sderaadt }
9591e72d8d2Sderaadt
9601e72d8d2Sderaadt /*
9611e72d8d2Sderaadt * Print a warm fuzzy message
9621e72d8d2Sderaadt */
9631e72d8d2Sderaadt /* ARGSUSED */
9641e72d8d2Sderaadt static Dtype
admin_dirproc(callerdat,dir,repos,update_dir,entries)96550bf276cStholo admin_dirproc (callerdat, dir, repos, update_dir, entries)
96650bf276cStholo void *callerdat;
9671e72d8d2Sderaadt char *dir;
9681e72d8d2Sderaadt char *repos;
9691e72d8d2Sderaadt char *update_dir;
97050bf276cStholo List *entries;
9711e72d8d2Sderaadt {
9721e72d8d2Sderaadt if (!quiet)
9731e72d8d2Sderaadt error (0, 0, "Administrating %s", update_dir);
9741e72d8d2Sderaadt return (R_PROCESS);
9751e72d8d2Sderaadt }
976