xref: /openbsd-src/usr.bin/cvs/edit.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: edit.c,v 1.48 2008/06/23 20:51:08 ragge Exp $	*/
2 /*
3  * Copyright (c) 2006, 2007 Xavier Santolaria <xsa@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/stat.h>
19 
20 #include <errno.h>
21 #include <string.h>
22 #include <time.h>
23 #include <unistd.h>
24 
25 #include "cvs.h"
26 #include "remote.h"
27 
28 #define E_COMMIT	0x01
29 #define E_EDIT		0x02
30 #define E_UNEDIT	0x04
31 #define E_ALL		(E_EDIT|E_COMMIT|E_UNEDIT)
32 
33 #define BASE_ADD	0x01
34 #define BASE_GET	0x02
35 #define BASE_REMOVE	0x04
36 
37 static void	cvs_edit_local(struct cvs_file *);
38 static void	cvs_editors_local(struct cvs_file *);
39 static void	cvs_unedit_local(struct cvs_file *);
40 
41 static RCSNUM	*cvs_base_handle(struct cvs_file *, int);
42 
43 static int	edit_aflags = 0;
44 
45 struct cvs_cmd cvs_cmd_edit = {
46 	CVS_OP_EDIT, CVS_USE_WDIR, "edit",
47 	{ { 0 }, { 0 } },
48 	"Get ready to edit a watched file",
49 	"[-lR] [-a action] [file ...]",
50 	"a:lR",
51 	NULL,
52 	cvs_edit
53 };
54 
55 struct cvs_cmd cvs_cmd_editors = {
56 	CVS_OP_EDITORS, CVS_USE_WDIR, "editors",
57 	{ { 0 }, { 0 } },
58 	"See who is editing a watched file",
59 	"[-lR] [file ...]",
60 	"lR",
61 	NULL,
62 	cvs_editors
63 };
64 
65 struct cvs_cmd cvs_cmd_unedit = {
66 	CVS_OP_UNEDIT, CVS_USE_WDIR, "unedit",
67 	{ { 0 }, { 0 } },
68 	"Undo an edit command",
69 	"[-lR] [file ...]",
70 	"lR",
71 	NULL,
72 	cvs_unedit
73 };
74 
75 int
76 cvs_edit(int argc, char **argv)
77 {
78 	int ch;
79 	int flags;
80 	struct cvs_recursion cr;
81 
82 	flags = CR_RECURSE_DIRS;
83 
84 	while ((ch = getopt(argc, argv, cvs_cmd_edit.cmd_opts)) != -1) {
85 		switch (ch) {
86 		case 'a':
87 			if (strcmp(optarg, "edit") == 0)
88 				edit_aflags |= E_EDIT;
89 			else if (strcmp(optarg, "unedit") == 0)
90 				edit_aflags |= E_UNEDIT;
91 			else if (strcmp(optarg, "commit") == 0)
92 				edit_aflags |= E_COMMIT;
93 			else if (strcmp(optarg, "all") == 0)
94 				edit_aflags |= E_ALL;
95 			else if (strcmp(optarg, "none") == 0)
96 				edit_aflags &= ~E_ALL;
97 			else
98 				fatal("%s", cvs_cmd_edit.cmd_synopsis);
99 			break;
100 		case 'l':
101 			flags &= ~CR_RECURSE_DIRS;
102 			break;
103 		case 'R':
104 			flags |= CR_RECURSE_DIRS;
105 			break;
106 		default:
107 			fatal("%s", cvs_cmd_edit.cmd_synopsis);
108 		}
109 	}
110 
111 	argc -= optind;
112 	argv += optind;
113 
114 	if (argc == 0)
115 		fatal("%s", cvs_cmd_edit.cmd_synopsis);
116 
117 	if (edit_aflags == 0)
118 		edit_aflags |= E_ALL;
119 
120 	cr.enterdir = NULL;
121 	cr.leavedir = NULL;
122 
123 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
124 		cvs_client_connect_to_server();
125 		cr.fileproc = cvs_client_sendfile;
126 
127 		if (!(flags & CR_RECURSE_DIRS))
128 			cvs_client_send_request("Argument -l");
129 	} else {
130 		cr.fileproc = cvs_edit_local;
131 	}
132 
133 	cr.flags = flags;
134 
135 	cvs_file_run(argc, argv, &cr);
136 
137 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
138 		cvs_client_send_files(argv, argc);
139 		cvs_client_senddir(".");
140 		cvs_client_send_request("edit");
141 		cvs_client_get_responses();
142 	}
143 
144 	return (0);
145 }
146 
147 int
148 cvs_editors(int argc, char **argv)
149 {
150 	int ch;
151 	int flags;
152 	struct cvs_recursion cr;
153 
154 	flags = CR_RECURSE_DIRS;
155 
156 	while ((ch = getopt(argc, argv, cvs_cmd_editors.cmd_opts)) != -1) {
157 		switch (ch) {
158 		case 'l':
159 			flags &= ~CR_RECURSE_DIRS;
160 			break;
161 		case 'R':
162 			flags |= CR_RECURSE_DIRS;
163 			break;
164 		default:
165 			fatal("%s", cvs_cmd_editors.cmd_synopsis);
166 		}
167 	}
168 
169 	argc -= optind;
170 	argv += optind;
171 
172 	if (argc == 0)
173 		fatal("%s", cvs_cmd_editors.cmd_synopsis);
174 
175 	cr.enterdir = NULL;
176 	cr.leavedir = NULL;
177 
178 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
179 		cvs_client_connect_to_server();
180 		cr.fileproc = cvs_client_sendfile;
181 
182 		if (!(flags & CR_RECURSE_DIRS))
183 			cvs_client_send_request("Argument -l");
184 	} else {
185 		cr.fileproc = cvs_editors_local;
186 	}
187 
188 	cr.flags = flags;
189 
190 	cvs_file_run(argc, argv, &cr);
191 
192 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
193 		cvs_client_send_files(argv, argc);
194 		cvs_client_senddir(".");
195 		cvs_client_send_request("editors");
196 		cvs_client_get_responses();
197 	}
198 
199 	return (0);
200 }
201 
202 int
203 cvs_unedit(int argc, char **argv)
204 {
205 	int ch;
206 	int flags;
207 	struct cvs_recursion cr;
208 
209 	flags = CR_RECURSE_DIRS;
210 
211 	while ((ch = getopt(argc, argv, cvs_cmd_unedit.cmd_opts)) != -1) {
212 		switch (ch) {
213 		case 'l':
214 			flags &= ~CR_RECURSE_DIRS;
215 			break;
216 		case 'R':
217 			flags |= CR_RECURSE_DIRS;
218 			break;
219 		default:
220 			fatal("%s", cvs_cmd_unedit.cmd_synopsis);
221 		}
222 	}
223 
224 	argc -= optind;
225 	argv += optind;
226 
227 	if (argc == 0)
228 		fatal("%s", cvs_cmd_unedit.cmd_synopsis);
229 
230 	cr.enterdir = NULL;
231 	cr.leavedir = NULL;
232 
233 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
234 		cvs_client_connect_to_server();
235 		cr.fileproc = cvs_client_sendfile;
236 
237 		if (!(flags & CR_RECURSE_DIRS))
238 			cvs_client_send_request("Argument -l");
239 	} else {
240 		cr.fileproc = cvs_unedit_local;
241 	}
242 
243 	cr.flags = flags;
244 
245 	cvs_file_run(argc, argv, &cr);
246 
247 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
248 		cvs_client_send_files(argv, argc);
249 		cvs_client_senddir(".");
250 		cvs_client_send_request("unedit");
251 		cvs_client_get_responses();
252 	}
253 
254 	return (0);
255 }
256 
257 static void
258 cvs_edit_local(struct cvs_file *cf)
259 {
260 	FILE *fp;
261 	struct tm t;
262 	time_t now;
263 	char timebuf[CVS_TIME_BUFSZ], thishost[MAXHOSTNAMELEN];
264 	char bfpath[MAXPATHLEN], wdir[MAXPATHLEN];
265 
266 	if (cvs_noexec == 1)
267 		return;
268 
269 	cvs_log(LP_TRACE, "cvs_edit_local(%s)", cf->file_path);
270 
271 	cvs_file_classify(cf, cvs_directory_tag);
272 
273 	if ((fp = fopen(CVS_PATH_NOTIFY, "a")) == NULL)
274 		fatal("cvs_edit_local: fopen: `%s': %s",
275 		    CVS_PATH_NOTIFY, strerror(errno));
276 
277 	(void)time(&now);
278 	gmtime_r(&now, &t);
279 	asctime_r(&t, timebuf);
280 	timebuf[strcspn(timebuf, "\n")] = '\0';
281 
282 	if (gethostname(thishost, sizeof(thishost)) == -1)
283 		fatal("gethostname failed");
284 
285 	if (getcwd(wdir, sizeof(wdir)) == NULL)
286 		fatal("getcwd failed");
287 
288 	(void)fprintf(fp, "E%s\t%s GMT\t%s\t%s\t",
289 	    cf->file_name, timebuf, thishost, wdir);
290 
291 	if (edit_aflags & E_EDIT)
292 		(void)fprintf(fp, "E");
293 	if (edit_aflags & E_UNEDIT)
294 		(void)fprintf(fp, "U");
295 	if (edit_aflags & E_COMMIT)
296 		(void)fprintf(fp, "C");
297 
298 	(void)fprintf(fp, "\n");
299 
300 	(void)fclose(fp);
301 
302 	if (fchmod(cf->fd, 0644) == -1)
303 		fatal("cvs_edit_local: fchmod %s", strerror(errno));
304 
305 	(void)xsnprintf(bfpath, MAXPATHLEN, "%s/%s",
306 	    CVS_PATH_BASEDIR, cf->file_name);
307 
308 	if (mkdir(CVS_PATH_BASEDIR, 0755) == -1 && errno != EEXIST)
309 		fatal("cvs_edit_local: `%s': %s", CVS_PATH_BASEDIR,
310 		    strerror(errno));
311 
312 	if (cvs_file_copy(cf->file_path, bfpath) == -1)
313 		fatal("cvs_edit_local: cvs_file_copy failed");
314 
315 	(void)cvs_base_handle(cf, BASE_ADD);
316 }
317 
318 static void
319 cvs_editors_local(struct cvs_file *cf)
320 {
321 }
322 
323 static void
324 cvs_unedit_local(struct cvs_file *cf)
325 {
326 	FILE *fp;
327 	struct stat st;
328 	struct tm t;
329 	time_t now;
330 	char bfpath[MAXPATHLEN], timebuf[64], thishost[MAXHOSTNAMELEN];
331 	char wdir[MAXPATHLEN], sticky[CVS_ENT_MAXLINELEN];
332 	RCSNUM *ba_rev;
333 
334 	if (cvs_noexec == 1)
335 		return;
336 
337 	cvs_log(LP_TRACE, "cvs_unedit_local(%s)", cf->file_path);
338 
339 	cvs_file_classify(cf, cvs_directory_tag);
340 
341 	(void)xsnprintf(bfpath, MAXPATHLEN, "%s/%s",
342 	    CVS_PATH_BASEDIR, cf->file_name);
343 
344 	if (stat(bfpath, &st) == -1)
345 		return;
346 
347 	if (cvs_file_cmp(cf->file_path, bfpath) != 0) {
348 		cvs_printf("%s has been modified; revert changes? ",
349 		    cf->file_name);
350 
351 		if (cvs_yesno() == -1)
352 			return;
353 	}
354 
355 	cvs_rename(bfpath, cf->file_path);
356 
357 	if ((fp = fopen(CVS_PATH_NOTIFY, "a")) == NULL)
358 		fatal("cvs_unedit_local: fopen: `%s': %s",
359 		    CVS_PATH_NOTIFY, strerror(errno));
360 
361 	(void)time(&now);
362 	gmtime_r(&now, &t);
363 	asctime_r(&t, timebuf);
364 	timebuf[strcspn(timebuf, "\n")] = '\0';
365 
366 	if (gethostname(thishost, sizeof(thishost)) == -1)
367 		fatal("gethostname failed");
368 
369 	if (getcwd(wdir, sizeof(wdir)) == NULL)
370 		fatal("getcwd failed");
371 
372 	(void)fprintf(fp, "U%s\t%s GMT\t%s\t%s\t\n",
373 	    cf->file_name, timebuf, thishost, wdir);
374 
375 	(void)fclose(fp);
376 
377 	if ((ba_rev = cvs_base_handle(cf, BASE_GET)) == NULL) {
378 		cvs_log(LP_ERR, "%s not mentioned in %s",
379 		    cf->file_name, CVS_PATH_BASEREV);
380 		return;
381 	}
382 
383 	if (cf->file_ent != NULL) {
384 		CVSENTRIES *entlist;
385 		struct cvs_ent *ent;
386 		char *entry, rbuf[CVS_REV_BUFSZ];
387 
388 		entlist = cvs_ent_open(cf->file_wd);
389 
390 		if ((ent = cvs_ent_get(entlist, cf->file_name)) == NULL)
391 			fatal("cvs_unedit_local: cvs_ent_get failed");
392 
393 		(void)rcsnum_tostr(ba_rev, rbuf, sizeof(rbuf));
394 
395 		memset(timebuf, 0, sizeof(timebuf));
396 		ctime_r(&cf->file_ent->ce_mtime, timebuf);
397 		timebuf[strcspn(timebuf, "\n")] = '\0';
398 
399 		sticky[0] = '\0';
400 		if (cf->file_ent->ce_tag != NULL)
401 			(void)xsnprintf(sticky, sizeof(sticky), "T%s",
402 			    cf->file_ent->ce_tag);
403 
404 		(void)xasprintf(&entry, "/%s/%s/%s/%s/%s",
405 		    cf->file_name, rbuf, timebuf, cf->file_ent->ce_opts ?
406 		    cf->file_ent->ce_opts : "", sticky);
407 
408 		cvs_ent_add(entlist, entry);
409 
410 		cvs_ent_free(ent);
411 
412 		xfree(entry);
413 	}
414 
415 	rcsnum_free(ba_rev);
416 
417 	(void)cvs_base_handle(cf, BASE_REMOVE);
418 
419 	if (fchmod(cf->fd, 0644) == -1)
420 		fatal("cvs_unedit_local: fchmod %s", strerror(errno));
421 }
422 
423 static RCSNUM *
424 cvs_base_handle(struct cvs_file *cf, int flags)
425 {
426 	FILE *fp, *tfp;
427 	RCSNUM *ba_rev;
428 	int i;
429 	char *dp, *sp;
430 	char buf[MAXPATHLEN], *fields[2], rbuf[CVS_REV_BUFSZ];
431 
432 	cvs_log(LP_TRACE, "cvs_base_handle(%s)", cf->file_path);
433 
434 	tfp = NULL;
435 	ba_rev = NULL;
436 
437 	if (((fp = fopen(CVS_PATH_BASEREV, "r")) == NULL) && errno != ENOENT) {
438 		cvs_log(LP_ERRNO, "%s", CVS_PATH_BASEREV);
439 		goto out;
440 	}
441 
442 	if (flags & (BASE_ADD|BASE_REMOVE)) {
443 		if ((tfp = fopen(CVS_PATH_BASEREVTMP, "w")) == NULL) {
444 			cvs_log(LP_ERRNO, "%s", CVS_PATH_BASEREVTMP);
445 			goto out;
446 		}
447 	}
448 
449 	if (fp != NULL) {
450 		while (fgets(buf, sizeof(buf), fp)) {
451 			buf[strcspn(buf, "\n")] = '\0';
452 
453 			if (buf[0] != 'B')
454 				continue;
455 
456 			sp = buf + 1;
457 			i = 0;
458 			do {
459 				if ((dp = strchr(sp, '/')) != NULL)
460 					*(dp++) = '\0';
461 				fields[i++] = sp;
462 				sp = dp;
463 			} while (dp != NULL && i < 2);
464 
465 			if (cvs_file_cmpname(fields[0], cf->file_path) == 0) {
466 				if (flags & BASE_GET) {
467 					ba_rev = rcsnum_parse(fields[1]);
468 					if (ba_rev == NULL)
469 						fatal("cvs_base_handle: "
470 						    "rcsnum_parse");
471 					goto got_rev;
472 				}
473 			} else {
474 				if (flags & (BASE_ADD|BASE_REMOVE))
475 					(void)fprintf(tfp, "%s\n", buf);
476 			}
477 		}
478 	}
479 
480 got_rev:
481 	if (flags & (BASE_ADD)) {
482 		(void)rcsnum_tostr(cf->file_ent->ce_rev, rbuf, sizeof(rbuf));
483 		(void)fprintf(tfp, "B%s/%s/\n", cf->file_path, rbuf);
484 	}
485 
486 out:
487 	if (fp != NULL)
488 		(void)fclose(fp);
489 
490 	if (tfp != NULL) {
491 		(void)fclose(tfp);
492 		(void)cvs_rename(CVS_PATH_BASEREVTMP, CVS_PATH_BASEREV);
493 	}
494 
495 	return (ba_rev);
496 }
497