xref: /openbsd-src/usr.bin/cvs/admin.c (revision 431378d1a7a36c77c1b34ffa43b6933d2ecc970a)
1*431378d1Snaddy /*	$OpenBSD: admin.c,v 1.69 2020/10/19 19:51:20 naddy Exp $	*/
2a30554baSxsa /*
3a30554baSxsa  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4a30554baSxsa  * Copyright (c) 2005 Joris Vink <joris@openbsd.org>
5dcb4505bSxsa  * Copyright (c) 2006, 2007 Xavier Santolaria <xsa@openbsd.org>
6a30554baSxsa  *
7a30554baSxsa  * Permission to use, copy, modify, and distribute this software for any
8a30554baSxsa  * purpose with or without fee is hereby granted, provided that the above
9a30554baSxsa  * copyright notice and this permission notice appear in all copies.
10a30554baSxsa  *
11a30554baSxsa  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12a30554baSxsa  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13a30554baSxsa  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14a30554baSxsa  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15a30554baSxsa  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16a30554baSxsa  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17a30554baSxsa  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18a30554baSxsa  */
19a30554baSxsa 
20b9fc9a72Sderaadt #include <sys/types.h>
211f8531bdSotto #include <sys/dirent.h>
221f8531bdSotto 
231f8531bdSotto #include <errno.h>
241f8531bdSotto #include <fcntl.h>
251f8531bdSotto #include <libgen.h>
26397ddb8aSnicm #include <stdlib.h>
271f8531bdSotto #include <string.h>
281f8531bdSotto #include <unistd.h>
29a30554baSxsa 
30a30554baSxsa #include "cvs.h"
31a30554baSxsa #include "remote.h"
32a30554baSxsa 
33a30554baSxsa #define ADM_EFLAG	0x01
34a30554baSxsa 
35a30554baSxsa void	cvs_admin_local(struct cvs_file *);
36a30554baSxsa 
37a30554baSxsa struct cvs_cmd cvs_cmd_admin = {
38fb3beb6cSjoris 	CVS_OP_ADMIN, CVS_USE_WDIR | CVS_LOCK_REPO, "admin",
39a30554baSxsa 	{ "adm", "rcs" },
40a30554baSxsa 	"Administrative front-end for RCS",
41607fa2aeSxsa 	"[-ILqU] [-A oldfile] [-a users] [-b branch]\n"
42a30554baSxsa 	"[-c string] [-e [users]] [-k mode] [-l [rev]] [-m rev:msg]\n"
43a30554baSxsa 	"[-N tag[:rev]] [-n tag[:rev]] [-o rev] [-s state[:rev]]"
44a30554baSxsa 	"[-t file | str]\n"
45a30554baSxsa 	"[-u [rev]] file ...",
46a30554baSxsa 	"A:a:b::c:e::Ik:l::Lm:N:n:o:qs:t:Uu::",
47a30554baSxsa 	NULL,
48a30554baSxsa 	cvs_admin
49a30554baSxsa };
50a30554baSxsa 
51a30554baSxsa static int	 runflags = 0;
52a30554baSxsa static int	 lkmode = RCS_LOCK_INVAL;
53dcb4505bSxsa static char	*alist, *comment, *elist, *logmsg, *logstr, *koptstr;
54911a7d5cStobias static char	*oldfilename, *orange, *state, *staterevstr;
55a30554baSxsa 
56a30554baSxsa int
cvs_admin(int argc,char ** argv)57a30554baSxsa cvs_admin(int argc, char **argv)
58a30554baSxsa {
59a30554baSxsa 	int ch;
60a30554baSxsa 	int flags;
61911a7d5cStobias 	char *statestr;
62a30554baSxsa 	struct cvs_recursion cr;
63a30554baSxsa 
64a30554baSxsa 	flags = CR_RECURSE_DIRS;
65a30554baSxsa 
66607fa2aeSxsa 	alist = comment = elist = logmsg = logstr = NULL;
67607fa2aeSxsa 	oldfilename = orange = state = statestr = NULL;
6827109178Sxsa 
69a30554baSxsa 	while ((ch = getopt(argc, argv, cvs_cmd_admin.cmd_opts)) != -1) {
70a30554baSxsa 		switch (ch) {
71a30554baSxsa 		case 'A':
72607fa2aeSxsa 			oldfilename = optarg;
73a30554baSxsa 			break;
74a30554baSxsa 		case 'a':
750a78a75fSxsa 			alist = optarg;
76a30554baSxsa 			break;
77a30554baSxsa 		case 'b':
78a30554baSxsa 			break;
79a30554baSxsa 		case 'c':
80a30554baSxsa 			comment = optarg;
81a30554baSxsa 			break;
82a30554baSxsa 		case 'e':
83a30554baSxsa 			elist = optarg;
84a30554baSxsa 			runflags |= ADM_EFLAG;
85a30554baSxsa 			break;
86a30554baSxsa 		case 'I':
87a30554baSxsa 			break;
88a30554baSxsa 		case 'k':
89dcb4505bSxsa 			koptstr = optarg;
90dcb4505bSxsa 			kflag = rcs_kflag_get(koptstr);
91dcb4505bSxsa 			if (RCS_KWEXP_INVAL(kflag)) {
92dcb4505bSxsa 				cvs_log(LP_ERR,
930bc1d395Stobias 				    "invalid RCS keyword expansion mode");
94dcb4505bSxsa 				fatal("%s", cvs_cmd_admin.cmd_synopsis);
95dcb4505bSxsa 			}
96a30554baSxsa 			break;
97a30554baSxsa 		case 'L':
98a30554baSxsa 			if (lkmode == RCS_LOCK_LOOSE) {
99a30554baSxsa 				cvs_log(LP_ERR, "-L and -U are incompatible");
100a30554baSxsa 				fatal("%s", cvs_cmd_admin.cmd_synopsis);
101a30554baSxsa 			}
102a30554baSxsa 			lkmode = RCS_LOCK_STRICT;
103a30554baSxsa 			break;
104a30554baSxsa 		case 'l':
105a30554baSxsa 			break;
106a30554baSxsa 		case 'm':
10727109178Sxsa 			logstr = optarg;
108a30554baSxsa 			break;
109a30554baSxsa 		case 'N':
110a30554baSxsa 			break;
111a30554baSxsa 		case 'n':
112a30554baSxsa 			break;
113a30554baSxsa 		case 'o':
1140a78a75fSxsa 			orange = optarg;
115a30554baSxsa 			break;
116a30554baSxsa 		case 'q':
117a30554baSxsa 			verbosity = 0;
118a30554baSxsa 			break;
119a30554baSxsa 		case 's':
1208b6645caSxsa 			statestr = optarg;
121a30554baSxsa 			break;
122a30554baSxsa 		case 't':
123a30554baSxsa 			break;
124a30554baSxsa 		case 'U':
125a30554baSxsa 			if (lkmode == RCS_LOCK_STRICT) {
126a30554baSxsa 				cvs_log(LP_ERR, "-U and -L are incompatible");
127a30554baSxsa 				fatal("%s", cvs_cmd_admin.cmd_synopsis);
128a30554baSxsa 			}
129a30554baSxsa 			lkmode = RCS_LOCK_LOOSE;
130a30554baSxsa 			break;
131a30554baSxsa 		case 'u':
132a30554baSxsa 			break;
133a30554baSxsa 		default:
134a30554baSxsa 			fatal("%s", cvs_cmd_admin.cmd_synopsis);
135a30554baSxsa 		}
136a30554baSxsa 	}
137a30554baSxsa 
138a30554baSxsa 	argc -= optind;
139a30554baSxsa 	argv += optind;
140a30554baSxsa 
141fc5a22f5Sxsa 	if (argc == 0)
142a30554baSxsa 		fatal("%s", cvs_cmd_admin.cmd_synopsis);
143a30554baSxsa 
144a30554baSxsa 	cr.enterdir = NULL;
145a30554baSxsa 	cr.leavedir = NULL;
146a30554baSxsa 
1474dcde513Sjoris 	if (cvsroot_is_remote()) {
14880f6ca9bSjoris 		cvs_client_connect_to_server();
149a30554baSxsa 		cr.fileproc = cvs_client_sendfile;
150a30554baSxsa 
151607fa2aeSxsa 		if (oldfilename != NULL)
152607fa2aeSxsa 			cvs_client_send_request("Argument -A%s", oldfilename);
153607fa2aeSxsa 
1540a78a75fSxsa 		if (alist != NULL)
1550a78a75fSxsa 			cvs_client_send_request("Argument -a%s", alist);
1560a78a75fSxsa 
157a30554baSxsa 		if (comment != NULL)
158a30554baSxsa 			cvs_client_send_request("Argument -c%s", comment);
159a30554baSxsa 
1600a78a75fSxsa 		if (runflags & ADM_EFLAG)
1610a78a75fSxsa 			cvs_client_send_request("Argument -e%s",
1620a78a75fSxsa 			    (elist != NULL) ? elist : "");
1630a78a75fSxsa 
164dcb4505bSxsa 		if (koptstr != NULL)
165dcb4505bSxsa 			cvs_client_send_request("Argument -k%s", koptstr);
166dcb4505bSxsa 
167a30554baSxsa 		if (lkmode == RCS_LOCK_STRICT)
168a30554baSxsa 			cvs_client_send_request("Argument -L");
169a30554baSxsa 		else if (lkmode == RCS_LOCK_LOOSE)
170a30554baSxsa 			cvs_client_send_request("Argument -U");
171a30554baSxsa 
17242cb4ab1Sxsa 		if (logstr != NULL)
173dd06abc6Stobias 			cvs_client_send_logmsg(logstr);
17442cb4ab1Sxsa 
1750a78a75fSxsa 		if (orange != NULL)
1760a78a75fSxsa 			cvs_client_send_request("Argument -o%s", orange);
1770a78a75fSxsa 
1788b6645caSxsa 		if (statestr != NULL)
1798b6645caSxsa 			cvs_client_send_request("Argument -s%s", statestr);
1808b6645caSxsa 
181a30554baSxsa 		if (verbosity == 0)
182a30554baSxsa 			cvs_client_send_request("Argument -q");
183a30554baSxsa 
184a30554baSxsa 	} else {
185911a7d5cStobias 		if (statestr != NULL) {
186911a7d5cStobias 			if ((staterevstr = strchr(statestr, ':')) != NULL)
187911a7d5cStobias 				*staterevstr++ = '\0';
188911a7d5cStobias 			state = statestr;
189911a7d5cStobias 			if (rcs_state_check(state) < 0) {
190911a7d5cStobias 				cvs_log(LP_ERR, "invalid state `%s'", state);
191911a7d5cStobias 				state = NULL;
192911a7d5cStobias 			}
193911a7d5cStobias 		}
194911a7d5cStobias 
195214edc83Sxsa 		flags |= CR_REPO;
196a30554baSxsa 		cr.fileproc = cvs_admin_local;
197a30554baSxsa 	}
198a30554baSxsa 
199a30554baSxsa 	cr.flags = flags;
200a30554baSxsa 
201a30554baSxsa 	cvs_file_run(argc, argv, &cr);
202a30554baSxsa 
2034dcde513Sjoris 	if (cvsroot_is_remote()) {
204a30554baSxsa 		cvs_client_send_files(argv, argc);
205a30554baSxsa 		cvs_client_senddir(".");
206a30554baSxsa 		cvs_client_send_request("admin");
207a30554baSxsa 		cvs_client_get_responses();
208a30554baSxsa 	}
209a30554baSxsa 
210a30554baSxsa 	return (0);
211a30554baSxsa }
212a30554baSxsa 
213a30554baSxsa void
cvs_admin_local(struct cvs_file * cf)214a30554baSxsa cvs_admin_local(struct cvs_file *cf)
215a30554baSxsa {
216a30554baSxsa 	int i;
217911a7d5cStobias 	RCSNUM *rev;
218a30554baSxsa 
219a30554baSxsa 	cvs_log(LP_TRACE, "cvs_admin_local(%s)", cf->file_path);
220a30554baSxsa 
22151ef6581Sjoris 	cvs_file_classify(cf, cvs_directory_tag);
222214edc83Sxsa 
223a30554baSxsa 	if (cf->file_type == CVS_DIR) {
224a30554baSxsa 		if (verbosity > 1)
225a30554baSxsa 			cvs_log(LP_NOTICE, "Administrating %s", cf->file_name);
226a30554baSxsa 		return;
227a30554baSxsa 	}
228a30554baSxsa 
2296d2ee454Stobias 	if (cf->file_ent == NULL)
230a30554baSxsa 		return;
231a30554baSxsa 	else if (cf->file_status == FILE_ADDED) {
232a30554baSxsa 		cvs_log(LP_ERR, "cannot admin newly added file `%s'",
233a30554baSxsa 		    cf->file_name);
234a30554baSxsa 		return;
235a30554baSxsa 	}
236a30554baSxsa 
237518886fbStobias 	if (cf->file_rcs == NULL) {
238518886fbStobias 		cvs_log(LP_ERR, "lost RCS file for `%s'", cf->file_path);
239518886fbStobias 		return;
240518886fbStobias 	}
241518886fbStobias 
242a30554baSxsa 	if (verbosity > 0)
243214edc83Sxsa 		cvs_printf("RCS file: %s\n", cf->file_rcs->rf_path);
244a30554baSxsa 
245607fa2aeSxsa 	if (oldfilename != NULL) {
246607fa2aeSxsa 		struct cvs_file *ocf;
247607fa2aeSxsa 		struct rcs_access *acp;
248607fa2aeSxsa 		int ofd;
249*431378d1Snaddy 		char *d, dbuf[PATH_MAX], *f, fbuf[PATH_MAX];
250*431378d1Snaddy 		char fpath[PATH_MAX], repo[PATH_MAX];
251607fa2aeSxsa 
252*431378d1Snaddy 		if (strlcpy(fbuf, oldfilename, sizeof(fbuf)) >= sizeof(fbuf))
253*431378d1Snaddy 			fatal("cvs_admin_local: truncation");
254*431378d1Snaddy 		if ((f = basename(fbuf)) == NULL)
255607fa2aeSxsa 			fatal("cvs_admin_local: basename failed");
256*431378d1Snaddy 
257*431378d1Snaddy 		if (strlcpy(dbuf, oldfilename, sizeof(dbuf)) >= sizeof(dbuf))
258*431378d1Snaddy 			fatal("cvs_admin_local: truncation");
259*431378d1Snaddy 		if ((d = dirname(dbuf)) == NULL)
260607fa2aeSxsa 			fatal("cvs_admin_local: dirname failed");
261607fa2aeSxsa 
262b9fc9a72Sderaadt 		cvs_get_repository_path(d, repo, PATH_MAX);
263607fa2aeSxsa 
264b9fc9a72Sderaadt 		(void)xsnprintf(fpath, PATH_MAX, "%s/%s", repo, f);
265607fa2aeSxsa 
266b9fc9a72Sderaadt 		if (strlcat(fpath, RCS_FILE_EXT, PATH_MAX) >= PATH_MAX)
267607fa2aeSxsa 			fatal("cvs_admin_local: truncation");
268607fa2aeSxsa 
269607fa2aeSxsa 		if ((ofd = open(fpath, O_RDONLY)) == -1)
270607fa2aeSxsa 			fatal("cvs_admin_local: open: `%s': %s", fpath,
271607fa2aeSxsa 			    strerror(errno));
272607fa2aeSxsa 
273607fa2aeSxsa 		/* XXX: S_ISREG() check instead of blindly using CVS_FILE? */
274a4a7c2faSjoris 		ocf = cvs_file_get_cf(d, f, oldfilename, ofd, CVS_FILE, 0);
275607fa2aeSxsa 
276607fa2aeSxsa 		ocf->file_rcs = rcs_open(fpath, ofd, RCS_READ, 0444);
277607fa2aeSxsa 		if (ocf->file_rcs == NULL)
278607fa2aeSxsa 			fatal("cvs_admin_local: rcs_open failed");
279607fa2aeSxsa 
280607fa2aeSxsa 		TAILQ_FOREACH(acp, &(ocf->file_rcs->rf_access), ra_list)
281607fa2aeSxsa 			rcs_access_add(cf->file_rcs, acp->ra_name);
282607fa2aeSxsa 
283607fa2aeSxsa 		cvs_file_free(ocf);
284607fa2aeSxsa 	}
285607fa2aeSxsa 
2860a78a75fSxsa 	if (alist != NULL) {
2870a78a75fSxsa 		struct cvs_argvector *aargv;
2880a78a75fSxsa 
2890a78a75fSxsa 		aargv = cvs_strsplit(alist, ",");
2900a78a75fSxsa 		for (i = 0; aargv->argv[i] != NULL; i++)
2910a78a75fSxsa 			rcs_access_add(cf->file_rcs, aargv->argv[i]);
2920a78a75fSxsa 
2930a78a75fSxsa 		cvs_argv_destroy(aargv);
2940a78a75fSxsa 	}
2950a78a75fSxsa 
296a30554baSxsa 	if (comment != NULL)
297a30554baSxsa 		rcs_comment_set(cf->file_rcs, comment);
298a30554baSxsa 
299a30554baSxsa 	if (elist != NULL) {
300a30554baSxsa 		struct cvs_argvector *eargv;
301a30554baSxsa 
302a30554baSxsa 		eargv = cvs_strsplit(elist, ",");
303a30554baSxsa 		for (i = 0; eargv->argv[i] != NULL; i++)
304a30554baSxsa 			rcs_access_remove(cf->file_rcs, eargv->argv[i]);
305a30554baSxsa 
306a30554baSxsa 		cvs_argv_destroy(eargv);
307a30554baSxsa 	} else if (runflags & ADM_EFLAG) {
308a30554baSxsa 		struct rcs_access *rap;
309a30554baSxsa 
310a30554baSxsa 		while (!TAILQ_EMPTY(&(cf->file_rcs->rf_access))) {
311a30554baSxsa 			rap = TAILQ_FIRST(&(cf->file_rcs->rf_access));
312a30554baSxsa 			TAILQ_REMOVE(&(cf->file_rcs->rf_access), rap, ra_list);
313397ddb8aSnicm 			free(rap->ra_name);
314397ddb8aSnicm 			free(rap);
315a30554baSxsa 		}
316a30554baSxsa 		/* no synced anymore */
317a30554baSxsa 		cf->file_rcs->rf_flags &= ~RCS_SYNCED;
318a30554baSxsa 	}
319a30554baSxsa 
320dcb4505bSxsa 	/* Default `-kv' is accepted here. */
32137fdff3fStobias 	if (kflag) {
322dcb4505bSxsa 		if (cf->file_rcs->rf_expand == NULL ||
323dcb4505bSxsa 		    strcmp(cf->file_rcs->rf_expand, koptstr) != 0)
324dcb4505bSxsa 			rcs_kwexp_set(cf->file_rcs, kflag);
325dcb4505bSxsa 	}
326dcb4505bSxsa 
32727109178Sxsa 	if (logstr != NULL) {
32827109178Sxsa 		if ((logmsg = strchr(logstr, ':')) == NULL) {
32927109178Sxsa 			cvs_log(LP_ERR, "missing log message");
33027109178Sxsa 			return;
33127109178Sxsa 		}
33227109178Sxsa 
33327109178Sxsa 		*logmsg++ = '\0';
334911a7d5cStobias 		if ((rev = rcsnum_parse(logstr)) == NULL) {
33527109178Sxsa 			cvs_log(LP_ERR, "`%s' bad revision number", logstr);
33627109178Sxsa 			return;
33727109178Sxsa 		}
33827109178Sxsa 
339911a7d5cStobias 		if (rcs_rev_setlog(cf->file_rcs, rev, logmsg) < 0) {
34027109178Sxsa 			cvs_log(LP_ERR, "failed to set logmsg for `%s' to `%s'",
34127109178Sxsa 			    logstr, logmsg);
34253ce2177Sfcambus 			free(rev);
34327109178Sxsa 			return;
34427109178Sxsa 		}
34527109178Sxsa 
34653ce2177Sfcambus 		free(rev);
34727109178Sxsa 	}
34827109178Sxsa 
3490a78a75fSxsa 	if (orange != NULL) {
3500a78a75fSxsa 		struct rcs_delta *rdp, *nrdp;
3510a7da307Sxsa 		char b[CVS_REV_BUFSZ];
3520a78a75fSxsa 
3530a78a75fSxsa 		cvs_revision_select(cf->file_rcs, orange);
3540a78a75fSxsa 		for (rdp = TAILQ_FIRST(&(cf->file_rcs->rf_delta));
3550a78a75fSxsa 		    rdp != NULL; rdp = nrdp) {
3560a78a75fSxsa 			nrdp = TAILQ_NEXT(rdp, rd_list);
3570a78a75fSxsa 
3580a78a75fSxsa 			/*
3590a78a75fSxsa 			 * Delete selected revisions.
3600a78a75fSxsa 			 */
3610a78a75fSxsa 			if (rdp->rd_flags & RCS_RD_SELECT) {
3620a78a75fSxsa 				rcsnum_tostr(rdp->rd_num, b, sizeof(b));
3630a78a75fSxsa 				if (verbosity > 0)
3640a78a75fSxsa 					cvs_printf("deleting revision %s\n", b);
3650a78a75fSxsa 
3660a78a75fSxsa 				(void)rcs_rev_remove(cf->file_rcs, rdp->rd_num);
3670a78a75fSxsa 			}
3680a78a75fSxsa 		}
3690a78a75fSxsa 	}
3700a78a75fSxsa 
371911a7d5cStobias 	if (state != NULL) {
372911a7d5cStobias 		if (staterevstr != NULL) {
373911a7d5cStobias 			if ((rev = rcsnum_parse(staterevstr)) == NULL) {
374911a7d5cStobias 				cvs_log(LP_ERR, "`%s' bad revision number",
375911a7d5cStobias 				    staterevstr);
3768b6645caSxsa 				return;
3778b6645caSxsa 			}
378e28eda4eStobias 		} else if (cf->file_rcs->rf_head != NULL) {
379911a7d5cStobias 			rev = rcsnum_alloc();
380911a7d5cStobias 			rcsnum_cpy(cf->file_rcs->rf_head, rev, 0);
381e28eda4eStobias 		} else {
382e28eda4eStobias 			cvs_log(LP_ERR, "head revision missing");
383e28eda4eStobias 			return;
3848b6645caSxsa 		}
3858b6645caSxsa 
386911a7d5cStobias 		(void)rcs_state_set(cf->file_rcs, rev, state);
3878b6645caSxsa 
38853ce2177Sfcambus 		free(rev);
3898b6645caSxsa 	}
3908b6645caSxsa 
391a30554baSxsa 	if (lkmode != RCS_LOCK_INVAL)
392a30554baSxsa 		(void)rcs_lock_setmode(cf->file_rcs, lkmode);
393a30554baSxsa 
3943e180cccSxsa 	rcs_write(cf->file_rcs);
3953e180cccSxsa 
396a30554baSxsa 	if (verbosity > 0)
397a30554baSxsa 		cvs_printf("done\n");
398a30554baSxsa }
399