#include "cvs.h"
#ifdef CVS_ADMIN_GROUP
#include <grp.h>
#endif
#include <assert.h>
static Dtype admin_dirproc PROTO ((void *callerdat, const char *dir,
const char *repos, const char *update_dir,
List *entries));
static int admin_fileproc PROTO ((void *callerdat, struct file_info *finfo));
static const char *const admin_usage[] =
{
"Usage: %s %s [options] files...\n",
"\t-a users Append (comma-separated) user names to access list.\n",
"\t-A file Append another file's access list.\n",
"\t-b[rev] Set default branch (highest branch on trunk if omitted).\n",
"\t-c string Set comment leader.\n",
"\t-e[users] Remove (comma-separated) user names from access list\n",
"\t (all names if omitted).\n",
"\t-I Run interactively.\n",
"\t-k subst Set keyword substitution mode:\n",
"\t kv (Default) Substitute keyword and value.\n",
"\t kvl Substitute keyword, value, and locker (if any).\n",
"\t k Substitute keyword only.\n",
"\t o Preserve original string.\n",
"\t b Like o, but mark file as binary.\n",
"\t v Substitute value only.\n",
"\t-l[rev] Lock revision (latest revision on branch,\n",
"\t latest revision on trunk if omitted).\n",
"\t-L Set strict locking.\n",
"\t-m rev:msg Replace revision's log message.\n",
"\t-n tag[:[rev]] Tag branch or revision. If :rev is omitted,\n",
"\t delete the tag; if rev is omitted, tag the latest\n",
"\t revision on the default branch.\n",
"\t-N tag[:[rev]] Same as -n except override existing tag.\n",
"\t-o range Delete (outdate) specified range of revisions:\n",
"\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
"\t rev1::rev2 Between rev1 and rev2, excluding rev1 and rev2.\n",
"\t rev: rev and following revisions on the same branch.\n",
"\t rev:: After rev on the same branch.\n",
"\t :rev rev and previous revisions on the same branch.\n",
"\t ::rev Before rev on the same branch.\n",
"\t rev Just rev.\n",
"\t-q Run quietly.\n",
"\t-s state[:rev] Set revision state (latest revision on branch,\n",
"\t latest revision on trunk if omitted).\n",
"\t-t[file] Get descriptive text from file (stdin if omitted).\n",
"\t-t-string Set descriptive text.\n",
"\t-u[rev] Unlock the revision (latest revision on branch,\n",
"\t latest revision on trunk if omitted).\n",
"\t-U Unset strict locking.\n",
"(Specify the --help global option for a list of other help options)\n",
NULL
};
struct admin_data
{
char *branch;
char *comment;
int set_strict;
int set_nonstrict;
char *delete_revs;
char *kflag;
char *desc;
int interactive;
int ac;
char **av;
int av_alloc;
};
static void
arg_add (dat, opt, arg)
struct admin_data *dat;
int opt;
char *arg;
{
char *newelt = xmalloc ((arg == NULL ? 0 : strlen (arg)) + 3);
strcpy (newelt, "-");
newelt[1] = opt;
if (arg == NULL)
newelt[2] = '\0';
else
strcpy (newelt + 2, arg);
if (dat->av_alloc == 0)
{
dat->av_alloc = 1;
dat->av = (char **) xmalloc (dat->av_alloc * sizeof (*dat->av));
}
else if (dat->ac >= dat->av_alloc)
{
dat->av_alloc *= 2;
dat->av = (char **) xrealloc (dat->av,
dat->av_alloc * sizeof (*dat->av));
}
dat->av[dat->ac++] = newelt;
}
int
admin (argc, argv)
int argc;
char **argv;
{
int err;
#ifdef CVS_ADMIN_GROUP
struct group *grp;
struct group *getgrnam();
#endif
struct admin_data admin_data;
int c;
int i;
int only_k_option;
if (argc <= 1)
usage (admin_usage);
wrap_setup ();
memset (&admin_data, 0, sizeof admin_data);
optind = 0;
only_k_option = 1;
while ((c = getopt (argc, argv,
"+ib::c:a:A:e::l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1)
{
if (c != 'k' && c != 'q')
only_k_option = 0;
switch (c)
{
case 'i':
error (0, 0, "the -i option to admin is not supported");
error (0, 0, "run add or import to create an RCS file");
goto usage_error;
case 'b':
if (admin_data.branch != NULL)
{
error (0, 0, "duplicate 'b' option");
goto usage_error;
}
if (optarg == NULL)
admin_data.branch = xstrdup ("-b");
else
{
admin_data.branch = xmalloc (strlen (optarg) + 5);
strcpy (admin_data.branch, "-b");
strcat (admin_data.branch, optarg);
}
break;
case 'c':
if (admin_data.comment != NULL)
{
error (0, 0, "duplicate 'c' option");
goto usage_error;
}
admin_data.comment = xmalloc (strlen (optarg) + 5);
strcpy (admin_data.comment, "-c");
strcat (admin_data.comment, optarg);
break;
case 'a':
arg_add (&admin_data, 'a', optarg);
break;
case 'A':
arg_add (&admin_data, 'A', optarg);
break;
case 'e':
arg_add (&admin_data, 'e', optarg);
break;
case 'l':
arg_add (&admin_data, 'l', optarg);
break;
case 'u':
arg_add (&admin_data, 'u', optarg);
break;
case 'L':
if (admin_data.set_nonstrict)
{
error (0, 0, "-U and -L are incompatible");
goto usage_error;
}
admin_data.set_strict = 1;
break;
case 'U':
if (admin_data.set_strict)
{
error (0, 0, "-U and -L are incompatible");
goto usage_error;
}
admin_data.set_nonstrict = 1;
break;
case 'n':
arg_add (&admin_data, 'n', optarg);
break;
case 'N':
arg_add (&admin_data, 'N', optarg);
break;
case 'm':
arg_add (&admin_data, 'm', optarg);
break;
case 'o':
if (admin_data.delete_revs != NULL)
{
error (0, 0, "duplicate '-o' option");
goto usage_error;
}
admin_data.delete_revs = xmalloc (strlen (optarg) + 5);
strcpy (admin_data.delete_revs, "-o");
strcat (admin_data.delete_revs, optarg);
break;
case 's':
arg_add (&admin_data, 's', optarg);
break;
case 't':
if (admin_data.desc != NULL)
{
error (0, 0, "duplicate 't' option");
goto usage_error;
}
if (optarg != NULL && optarg[0] == '-')
admin_data.desc = xstrdup (optarg + 1);
else
{
size_t bufsize = 0;
size_t len;
get_file (optarg, optarg, "r", &admin_data.desc,
&bufsize, &len);
}
break;
case 'I':
admin_data.interactive = 1;
break;
case 'q':
really_quiet = 1;
break;
case 'x':
error (0, 0, "the -x option has never done anything useful");
error (0, 0, "RCS files in CVS always end in ,v");
goto usage_error;
case 'V':
error (0, 0, "the `-V' option is obsolete");
break;
case 'k':
if (admin_data.kflag != NULL)
{
error (0, 0, "duplicate '-k' option");
goto usage_error;
}
admin_data.kflag = RCS_check_kflag (optarg);
break;
default:
case '?':
usage_error:
error (1, 0, "specify %s -H admin for usage information",
program_name);
}
}
argc -= optind;
argv += optind;
#ifdef CVS_ADMIN_GROUP
if (
# ifdef CLIENT_SUPPORT
!current_parsed_root->isremote &&
# endif
!only_k_option
&& (grp = getgrnam(CVS_ADMIN_GROUP)) != NULL)
{
#ifdef HAVE_GETGROUPS
gid_t *grps;
int n;
n = getgroups (0, NULL);
if (n < 0)
error (1, errno, "unable to get number of auxiliary groups");
grps = (gid_t *) xmalloc((n + 1) * sizeof *grps);
n = getgroups (n, grps);
if (n < 0)
error (1, errno, "unable to get list of auxiliary groups");
grps[n] = getgid();
for (i = 0; i <= n; i++)
if (grps[i] == grp->gr_gid) break;
free (grps);
if (i > n)
error (1, 0, "usage is restricted to members of the group %s",
CVS_ADMIN_GROUP);
#else
char *me = getcaller();
char **grnam;
for (grnam = grp->gr_mem; *grnam; grnam++)
if (strcmp (*grnam, me) == 0) break;
if (!*grnam && getgid() != grp->gr_gid)
error (1, 0, "usage is restricted to members of the group %s",
CVS_ADMIN_GROUP);
#endif
}
#endif
for (i = 0; i < admin_data.ac; ++i)
{
assert (admin_data.av[i][0] == '-');
switch (admin_data.av[i][1])
{
case 'm':
case 'l':
case 'u':
check_numeric (&admin_data.av[i][2], argc, argv);
break;
default:
break;
}
}
if (admin_data.branch != NULL)
check_numeric (admin_data.branch + 2, argc, argv);
if (admin_data.delete_revs != NULL)
{
char *p;
check_numeric (admin_data.delete_revs + 2, argc, argv);
p = strchr (admin_data.delete_revs + 2, ':');
if (p != NULL && isdigit ((unsigned char) p[1]))
check_numeric (p + 1, argc, argv);
else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2]))
check_numeric (p + 2, argc, argv);
}
#ifdef CLIENT_SUPPORT
if (current_parsed_root->isremote)
{
start_server ();
ign_setup ();
if (admin_data.interactive)
error (1, 0, "-I option not useful with client/server");
if (admin_data.branch != NULL)
send_arg (admin_data.branch);
if (admin_data.comment != NULL)
send_arg (admin_data.comment);
if (admin_data.set_strict)
send_arg ("-L");
if (admin_data.set_nonstrict)
send_arg ("-U");
if (admin_data.delete_revs != NULL)
send_arg (admin_data.delete_revs);
if (admin_data.desc != NULL)
{
char *p = admin_data.desc;
send_to_server ("Argument -t-", 0);
while (*p)
{
if (*p == '\n')
{
send_to_server ("\012Argumentx ", 0);
++p;
}
else
{
char *q = strchr (p, '\n');
if (q == NULL) q = p + strlen (p);
send_to_server (p, q - p);
p = q;
}
}
send_to_server ("\012", 1);
}
if (really_quiet)
send_arg ("-q");
if (admin_data.kflag != NULL)
send_arg (admin_data.kflag);
for (i = 0; i < admin_data.ac; ++i)
send_arg (admin_data.av[i]);
send_arg ("--");
send_files (argc, argv, 0, 0, SEND_NO_CONTENTS);
send_file_names (argc, argv, SEND_EXPAND_WILD);
send_to_server ("admin\012", 0);
err = get_responses_and_close ();
goto return_it;
}
#endif
lock_tree_for_write (argc, argv, 0, W_LOCAL, 0);
err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc,
(DIRLEAVEPROC) NULL, (void *)&admin_data,
argc, argv, 0,
W_LOCAL, 0, CVS_LOCK_NONE, (char *) NULL, 1,
(char *) NULL);
Lock_Cleanup ();
return_it:
if (admin_data.branch != NULL)
free (admin_data.branch);
if (admin_data.comment != NULL)
free (admin_data.comment);
if (admin_data.delete_revs != NULL)
free (admin_data.delete_revs);
if (admin_data.kflag != NULL)
free (admin_data.kflag);
if (admin_data.desc != NULL)
free (admin_data.desc);
for (i = 0; i < admin_data.ac; ++i)
free (admin_data.av[i]);
if (admin_data.av != NULL)
free (admin_data.av);
return (err);
}
static int
admin_fileproc (callerdat, finfo)
void *callerdat;
struct file_info *finfo;
{
struct admin_data *admin_data = (struct admin_data *) callerdat;
Vers_TS *vers;
char *version;
int i;
int status = 0;
RCSNode *rcs, *rcs2;
vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
version = vers->vn_user;
if (version != NULL && strcmp (version, "0") == 0)
{
error (0, 0, "cannot admin newly added file `%s'", finfo->file);
status = 1;
goto exitfunc;
}
rcs = vers->srcfile;
if (rcs == NULL)
{
if (!really_quiet)
error (0, 0, "nothing known about %s", finfo->file);
status = 1;
goto exitfunc;
}
if (rcs->flags & PARTIAL)
RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
if (!really_quiet)
{
cvs_output ("RCS file: ", 0);
cvs_output (rcs->path, 0);
cvs_output ("\n", 1);
}
if (admin_data->branch != NULL)
{
char *branch = &admin_data->branch[2];
if (*branch != '\0' && ! isdigit ((unsigned char) *branch))
{
branch = RCS_whatbranch (rcs, admin_data->branch + 2);
if (branch == NULL)
{
error (0, 0, "%s: Symbolic name %s is undefined.",
rcs->path, admin_data->branch + 2);
status = 1;
}
}
if (status == 0)
RCS_setbranch (rcs, branch);
if (branch != NULL && branch != &admin_data->branch[2])
free (branch);
}
if (admin_data->comment != NULL)
{
if (rcs->comment != NULL)
free (rcs->comment);
rcs->comment = xstrdup (admin_data->comment + 2);
}
if (admin_data->set_strict)
rcs->strict_locks = 1;
if (admin_data->set_nonstrict)
rcs->strict_locks = 0;
if (admin_data->delete_revs != NULL)
{
char *s, *t, *rev1, *rev2;
int inclusive;
char *t2;
s = admin_data->delete_revs + 2;
inclusive = 1;
t = strchr (s, ':');
if (t != NULL)
{
if (t[1] == ':')
{
inclusive = 0;
t2 = t + 2;
}
else
t2 = t + 1;
}
if (t == NULL)
{
rev1 = xstrdup (s);
rev2 = xstrdup (s);
}
else if (t == s)
{
rev1 = NULL;
rev2 = xstrdup (t2);
}
else
{
*t = '\0';
rev1 = xstrdup (s);
*t = ':';
if (*t2 == '\0')
rev2 = NULL;
else
rev2 = xstrdup (t2);
}
if (rev1 == NULL && rev2 == NULL)
{
error (0, 0, "no valid revisions specified in `%s' option",
admin_data->delete_revs);
status = 1;
}
else
{
status |= RCS_delete_revs (rcs, rev1, rev2, inclusive);
if (rev1)
free (rev1);
if (rev2)
free (rev2);
}
}
if (admin_data->desc != NULL)
{
free (rcs->desc);
rcs->desc = xstrdup (admin_data->desc);
}
if (admin_data->kflag != NULL)
{
char *kflag = admin_data->kflag + 2;
char *oldexpand = RCS_getexpand (rcs);
if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0)
RCS_setexpand (rcs, kflag);
}
for (i = 0; i < admin_data->ac; ++i)
{
char *arg;
char *p, *rev, *revnum, *tag, *msg;
char **users;
int argc, u;
Node *n;
RCSVers *delta;
arg = admin_data->av[i];
switch (arg[1])
{
case 'a':
case 'e':
line2argv (&argc, &users, arg + 2, " ,\t\n");
if (arg[1] == 'a')
for (u = 0; u < argc; ++u)
RCS_addaccess (rcs, users[u]);
else if (argc == 0)
RCS_delaccess (rcs, NULL);
else
for (u = 0; u < argc; ++u)
RCS_delaccess (rcs, users[u]);
free_names (&argc, users);
break;
case 'A':
rcs2 = RCS_parsercsfile (arg + 2);
if (rcs2 == NULL)
error (1, 0, "cannot continue");
p = xstrdup (RCS_getaccess (rcs2));
line2argv (&argc, &users, p, " \t\n");
free (p);
freercsnode (&rcs2);
for (u = 0; u < argc; ++u)
RCS_addaccess (rcs, users[u]);
free_names (&argc, users);
break;
case 'n':
case 'N':
if (arg[2] == '\0')
{
cvs_outerr ("missing symbolic name after ", 0);
cvs_outerr (arg, 0);
cvs_outerr ("\n", 1);
break;
}
p = strchr (arg, ':');
if (p == NULL)
{
if (RCS_deltag (rcs, arg + 2) != 0)
{
error (0, 0, "%s: Symbolic name %s is undefined.",
rcs->path,
arg + 2);
status = 1;
continue;
}
break;
}
*p = '\0';
tag = xstrdup (arg + 2);
*p++ = ':';
if (arg[1] == 'n')
{
n = findnode (RCS_symbols (rcs), tag);
if (n != NULL)
{
error (0, 0,
"%s: symbolic name %s already bound to %s",
rcs->path,
tag, (char *)n->data);
status = 1;
free (tag);
continue;
}
}
if ((*p == 0 && (rev = RCS_head (rcs)))
|| (rev = RCS_tag2rev (rcs, p)))
{
RCS_check_tag (tag);
RCS_settag (rcs, tag, rev);
free (rev);
}
else
{
if (!really_quiet)
error (0, 0,
"%s: Symbolic name or revision %s is undefined.",
rcs->path, p);
status = 1;
}
free (tag);
break;
case 's':
p = strchr (arg, ':');
if (p == NULL)
{
tag = xstrdup (arg + 2);
rev = RCS_head (rcs);
}
else
{
*p = '\0';
tag = xstrdup (arg + 2);
*p++ = ':';
rev = xstrdup (p);
}
revnum = RCS_gettag (rcs, rev, 0, NULL);
if (revnum != NULL)
{
n = findnode (rcs->versions, revnum);
free (revnum);
}
else
n = NULL;
if (n == NULL)
{
error (0, 0,
"%s: can't set state of nonexisting revision %s",
rcs->path,
rev);
free (rev);
status = 1;
continue;
}
free (rev);
delta = n->data;
free (delta->state);
delta->state = tag;
break;
case 'm':
p = strchr (arg, ':');
if (p == NULL)
{
error (0, 0, "%s: -m option lacks revision number",
rcs->path);
status = 1;
continue;
}
*p = '\0';
rev = RCS_gettag (rcs, arg + 2, 1, NULL);
if (rev == NULL)
{
error (0, 0, "%s: no such revision %s", rcs->path, arg+2);
status = 1;
*p = ':';
continue;
}
msg = p+1;
n = findnode (rcs->versions, rev);
if (n == NULL)
{
error (0, 0, "%s: no such revision %s: %s",
rcs->path, arg+2, rev);
status = 1;
*p = ':';
free (rev);
continue;
}
*p = ':';
free (rev);
delta = n->data;
if (delta->text == NULL)
{
delta->text = (Deltatext *) xmalloc (sizeof (Deltatext));
memset ((void *) delta->text, 0, sizeof (Deltatext));
}
delta->text->version = xstrdup (delta->version);
delta->text->log = make_message_rcslegal (msg);
break;
case 'l':
status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0);
break;
case 'u':
status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0);
break;
default: assert(0);
}
}
if (status == 0)
{
RCS_rewrite (rcs, NULL, NULL);
if (!really_quiet)
cvs_output ("done\n", 5);
}
else
{
if (!really_quiet)
error (0, 0, "RCS file for `%s' not modified.", finfo->file);
RCS_abandon (rcs);
}
exitfunc:
freevers_ts (&vers);
return status;
}
static Dtype
admin_dirproc (callerdat, dir, repos, update_dir, entries)
void *callerdat;
const char *dir;
const char *repos;
const char *update_dir;
List *entries;
{
if (!quiet)
error (0, 0, "Administrating %s", update_dir);
return (R_PROCESS);
}