xref: /openbsd/usr.bin/cvs/admin.c (revision f6aab3d8)
1 /*	$OpenBSD: admin.c,v 1.69 2020/10/19 19:51:20 naddy Exp $	*/
2 /*
3  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4  * Copyright (c) 2005 Joris Vink <joris@openbsd.org>
5  * Copyright (c) 2006, 2007 Xavier Santolaria <xsa@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/dirent.h>
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <libgen.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "cvs.h"
31 #include "remote.h"
32 
33 #define ADM_EFLAG	0x01
34 
35 void	cvs_admin_local(struct cvs_file *);
36 
37 struct cvs_cmd cvs_cmd_admin = {
38 	CVS_OP_ADMIN, CVS_USE_WDIR | CVS_LOCK_REPO, "admin",
39 	{ "adm", "rcs" },
40 	"Administrative front-end for RCS",
41 	"[-ILqU] [-A oldfile] [-a users] [-b branch]\n"
42 	"[-c string] [-e [users]] [-k mode] [-l [rev]] [-m rev:msg]\n"
43 	"[-N tag[:rev]] [-n tag[:rev]] [-o rev] [-s state[:rev]]"
44 	"[-t file | str]\n"
45 	"[-u [rev]] file ...",
46 	"A:a:b::c:e::Ik:l::Lm:N:n:o:qs:t:Uu::",
47 	NULL,
48 	cvs_admin
49 };
50 
51 static int	 runflags = 0;
52 static int	 lkmode = RCS_LOCK_INVAL;
53 static char	*alist, *comment, *elist, *logmsg, *logstr, *koptstr;
54 static char	*oldfilename, *orange, *state, *staterevstr;
55 
56 int
57 cvs_admin(int argc, char **argv)
58 {
59 	int ch;
60 	int flags;
61 	char *statestr;
62 	struct cvs_recursion cr;
63 
64 	flags = CR_RECURSE_DIRS;
65 
66 	alist = comment = elist = logmsg = logstr = NULL;
67 	oldfilename = orange = state = statestr = NULL;
68 
69 	while ((ch = getopt(argc, argv, cvs_cmd_admin.cmd_opts)) != -1) {
70 		switch (ch) {
71 		case 'A':
72 			oldfilename = optarg;
73 			break;
74 		case 'a':
75 			alist = optarg;
76 			break;
77 		case 'b':
78 			break;
79 		case 'c':
80 			comment = optarg;
81 			break;
82 		case 'e':
83 			elist = optarg;
84 			runflags |= ADM_EFLAG;
85 			break;
86 		case 'I':
87 			break;
88 		case 'k':
89 			koptstr = optarg;
90 			kflag = rcs_kflag_get(koptstr);
91 			if (RCS_KWEXP_INVAL(kflag)) {
92 				cvs_log(LP_ERR,
93 				    "invalid RCS keyword expansion mode");
94 				fatal("%s", cvs_cmd_admin.cmd_synopsis);
95 			}
96 			break;
97 		case 'L':
98 			if (lkmode == RCS_LOCK_LOOSE) {
99 				cvs_log(LP_ERR, "-L and -U are incompatible");
100 				fatal("%s", cvs_cmd_admin.cmd_synopsis);
101 			}
102 			lkmode = RCS_LOCK_STRICT;
103 			break;
104 		case 'l':
105 			break;
106 		case 'm':
107 			logstr = optarg;
108 			break;
109 		case 'N':
110 			break;
111 		case 'n':
112 			break;
113 		case 'o':
114 			orange = optarg;
115 			break;
116 		case 'q':
117 			verbosity = 0;
118 			break;
119 		case 's':
120 			statestr = optarg;
121 			break;
122 		case 't':
123 			break;
124 		case 'U':
125 			if (lkmode == RCS_LOCK_STRICT) {
126 				cvs_log(LP_ERR, "-U and -L are incompatible");
127 				fatal("%s", cvs_cmd_admin.cmd_synopsis);
128 			}
129 			lkmode = RCS_LOCK_LOOSE;
130 			break;
131 		case 'u':
132 			break;
133 		default:
134 			fatal("%s", cvs_cmd_admin.cmd_synopsis);
135 		}
136 	}
137 
138 	argc -= optind;
139 	argv += optind;
140 
141 	if (argc == 0)
142 		fatal("%s", cvs_cmd_admin.cmd_synopsis);
143 
144 	cr.enterdir = NULL;
145 	cr.leavedir = NULL;
146 
147 	if (cvsroot_is_remote()) {
148 		cvs_client_connect_to_server();
149 		cr.fileproc = cvs_client_sendfile;
150 
151 		if (oldfilename != NULL)
152 			cvs_client_send_request("Argument -A%s", oldfilename);
153 
154 		if (alist != NULL)
155 			cvs_client_send_request("Argument -a%s", alist);
156 
157 		if (comment != NULL)
158 			cvs_client_send_request("Argument -c%s", comment);
159 
160 		if (runflags & ADM_EFLAG)
161 			cvs_client_send_request("Argument -e%s",
162 			    (elist != NULL) ? elist : "");
163 
164 		if (koptstr != NULL)
165 			cvs_client_send_request("Argument -k%s", koptstr);
166 
167 		if (lkmode == RCS_LOCK_STRICT)
168 			cvs_client_send_request("Argument -L");
169 		else if (lkmode == RCS_LOCK_LOOSE)
170 			cvs_client_send_request("Argument -U");
171 
172 		if (logstr != NULL)
173 			cvs_client_send_logmsg(logstr);
174 
175 		if (orange != NULL)
176 			cvs_client_send_request("Argument -o%s", orange);
177 
178 		if (statestr != NULL)
179 			cvs_client_send_request("Argument -s%s", statestr);
180 
181 		if (verbosity == 0)
182 			cvs_client_send_request("Argument -q");
183 
184 	} else {
185 		if (statestr != NULL) {
186 			if ((staterevstr = strchr(statestr, ':')) != NULL)
187 				*staterevstr++ = '\0';
188 			state = statestr;
189 			if (rcs_state_check(state) < 0) {
190 				cvs_log(LP_ERR, "invalid state `%s'", state);
191 				state = NULL;
192 			}
193 		}
194 
195 		flags |= CR_REPO;
196 		cr.fileproc = cvs_admin_local;
197 	}
198 
199 	cr.flags = flags;
200 
201 	cvs_file_run(argc, argv, &cr);
202 
203 	if (cvsroot_is_remote()) {
204 		cvs_client_send_files(argv, argc);
205 		cvs_client_senddir(".");
206 		cvs_client_send_request("admin");
207 		cvs_client_get_responses();
208 	}
209 
210 	return (0);
211 }
212 
213 void
214 cvs_admin_local(struct cvs_file *cf)
215 {
216 	int i;
217 	RCSNUM *rev;
218 
219 	cvs_log(LP_TRACE, "cvs_admin_local(%s)", cf->file_path);
220 
221 	cvs_file_classify(cf, cvs_directory_tag);
222 
223 	if (cf->file_type == CVS_DIR) {
224 		if (verbosity > 1)
225 			cvs_log(LP_NOTICE, "Administrating %s", cf->file_name);
226 		return;
227 	}
228 
229 	if (cf->file_ent == NULL)
230 		return;
231 	else if (cf->file_status == FILE_ADDED) {
232 		cvs_log(LP_ERR, "cannot admin newly added file `%s'",
233 		    cf->file_name);
234 		return;
235 	}
236 
237 	if (cf->file_rcs == NULL) {
238 		cvs_log(LP_ERR, "lost RCS file for `%s'", cf->file_path);
239 		return;
240 	}
241 
242 	if (verbosity > 0)
243 		cvs_printf("RCS file: %s\n", cf->file_rcs->rf_path);
244 
245 	if (oldfilename != NULL) {
246 		struct cvs_file *ocf;
247 		struct rcs_access *acp;
248 		int ofd;
249 		char *d, dbuf[PATH_MAX], *f, fbuf[PATH_MAX];
250 		char fpath[PATH_MAX], repo[PATH_MAX];
251 
252 		if (strlcpy(fbuf, oldfilename, sizeof(fbuf)) >= sizeof(fbuf))
253 			fatal("cvs_admin_local: truncation");
254 		if ((f = basename(fbuf)) == NULL)
255 			fatal("cvs_admin_local: basename failed");
256 
257 		if (strlcpy(dbuf, oldfilename, sizeof(dbuf)) >= sizeof(dbuf))
258 			fatal("cvs_admin_local: truncation");
259 		if ((d = dirname(dbuf)) == NULL)
260 			fatal("cvs_admin_local: dirname failed");
261 
262 		cvs_get_repository_path(d, repo, PATH_MAX);
263 
264 		(void)xsnprintf(fpath, PATH_MAX, "%s/%s", repo, f);
265 
266 		if (strlcat(fpath, RCS_FILE_EXT, PATH_MAX) >= PATH_MAX)
267 			fatal("cvs_admin_local: truncation");
268 
269 		if ((ofd = open(fpath, O_RDONLY)) == -1)
270 			fatal("cvs_admin_local: open: `%s': %s", fpath,
271 			    strerror(errno));
272 
273 		/* XXX: S_ISREG() check instead of blindly using CVS_FILE? */
274 		ocf = cvs_file_get_cf(d, f, oldfilename, ofd, CVS_FILE, 0);
275 
276 		ocf->file_rcs = rcs_open(fpath, ofd, RCS_READ, 0444);
277 		if (ocf->file_rcs == NULL)
278 			fatal("cvs_admin_local: rcs_open failed");
279 
280 		TAILQ_FOREACH(acp, &(ocf->file_rcs->rf_access), ra_list)
281 			rcs_access_add(cf->file_rcs, acp->ra_name);
282 
283 		cvs_file_free(ocf);
284 	}
285 
286 	if (alist != NULL) {
287 		struct cvs_argvector *aargv;
288 
289 		aargv = cvs_strsplit(alist, ",");
290 		for (i = 0; aargv->argv[i] != NULL; i++)
291 			rcs_access_add(cf->file_rcs, aargv->argv[i]);
292 
293 		cvs_argv_destroy(aargv);
294 	}
295 
296 	if (comment != NULL)
297 		rcs_comment_set(cf->file_rcs, comment);
298 
299 	if (elist != NULL) {
300 		struct cvs_argvector *eargv;
301 
302 		eargv = cvs_strsplit(elist, ",");
303 		for (i = 0; eargv->argv[i] != NULL; i++)
304 			rcs_access_remove(cf->file_rcs, eargv->argv[i]);
305 
306 		cvs_argv_destroy(eargv);
307 	} else if (runflags & ADM_EFLAG) {
308 		struct rcs_access *rap;
309 
310 		while (!TAILQ_EMPTY(&(cf->file_rcs->rf_access))) {
311 			rap = TAILQ_FIRST(&(cf->file_rcs->rf_access));
312 			TAILQ_REMOVE(&(cf->file_rcs->rf_access), rap, ra_list);
313 			free(rap->ra_name);
314 			free(rap);
315 		}
316 		/* no synced anymore */
317 		cf->file_rcs->rf_flags &= ~RCS_SYNCED;
318 	}
319 
320 	/* Default `-kv' is accepted here. */
321 	if (kflag) {
322 		if (cf->file_rcs->rf_expand == NULL ||
323 		    strcmp(cf->file_rcs->rf_expand, koptstr) != 0)
324 			rcs_kwexp_set(cf->file_rcs, kflag);
325 	}
326 
327 	if (logstr != NULL) {
328 		if ((logmsg = strchr(logstr, ':')) == NULL) {
329 			cvs_log(LP_ERR, "missing log message");
330 			return;
331 		}
332 
333 		*logmsg++ = '\0';
334 		if ((rev = rcsnum_parse(logstr)) == NULL) {
335 			cvs_log(LP_ERR, "`%s' bad revision number", logstr);
336 			return;
337 		}
338 
339 		if (rcs_rev_setlog(cf->file_rcs, rev, logmsg) < 0) {
340 			cvs_log(LP_ERR, "failed to set logmsg for `%s' to `%s'",
341 			    logstr, logmsg);
342 			free(rev);
343 			return;
344 		}
345 
346 		free(rev);
347 	}
348 
349 	if (orange != NULL) {
350 		struct rcs_delta *rdp, *nrdp;
351 		char b[CVS_REV_BUFSZ];
352 
353 		cvs_revision_select(cf->file_rcs, orange);
354 		for (rdp = TAILQ_FIRST(&(cf->file_rcs->rf_delta));
355 		    rdp != NULL; rdp = nrdp) {
356 			nrdp = TAILQ_NEXT(rdp, rd_list);
357 
358 			/*
359 			 * Delete selected revisions.
360 			 */
361 			if (rdp->rd_flags & RCS_RD_SELECT) {
362 				rcsnum_tostr(rdp->rd_num, b, sizeof(b));
363 				if (verbosity > 0)
364 					cvs_printf("deleting revision %s\n", b);
365 
366 				(void)rcs_rev_remove(cf->file_rcs, rdp->rd_num);
367 			}
368 		}
369 	}
370 
371 	if (state != NULL) {
372 		if (staterevstr != NULL) {
373 			if ((rev = rcsnum_parse(staterevstr)) == NULL) {
374 				cvs_log(LP_ERR, "`%s' bad revision number",
375 				    staterevstr);
376 				return;
377 			}
378 		} else if (cf->file_rcs->rf_head != NULL) {
379 			rev = rcsnum_alloc();
380 			rcsnum_cpy(cf->file_rcs->rf_head, rev, 0);
381 		} else {
382 			cvs_log(LP_ERR, "head revision missing");
383 			return;
384 		}
385 
386 		(void)rcs_state_set(cf->file_rcs, rev, state);
387 
388 		free(rev);
389 	}
390 
391 	if (lkmode != RCS_LOCK_INVAL)
392 		(void)rcs_lock_setmode(cf->file_rcs, lkmode);
393 
394 	rcs_write(cf->file_rcs);
395 
396 	if (verbosity > 0)
397 		cvs_printf("done\n");
398 }
399