xref: /openbsd/usr.bin/cvs/diff.c (revision 8932bfb7)
1 /*	$OpenBSD: diff.c,v 1.160 2011/04/20 18:33:13 nicm Exp $	*/
2 /*
3  * Copyright (c) 2008 Tobias Stoeckmann <tobias@openbsd.org>
4  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/stat.h>
20 #include <sys/time.h>
21 
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <unistd.h>
28 
29 #include "cvs.h"
30 #include "diff.h"
31 #include "remote.h"
32 
33 void	cvs_diff_local(struct cvs_file *);
34 
35 static int	 dflags = 0;
36 static int	 Nflag = 0;
37 static int	 force_head = 0;
38 static char	*koptstr;
39 static char	*rev1 = NULL;
40 static char	*rev2 = NULL;
41 static time_t	 date1 = -1;
42 static time_t	 date2 = -1;
43 static char	*dateflag1 = NULL;
44 static char	*dateflag2 = NULL;
45 
46 struct cvs_cmd cvs_cmd_diff = {
47 	CVS_OP_DIFF, CVS_USE_WDIR, "diff",
48 	{ "di", "dif" },
49 	"Show differences between revisions",
50 	"[-abcdilNnpRuw] [[-D date] [-r rev] [-D date2 | -r rev2]] "
51 	"[-k mode] [file ...]",
52 	"abcfC:dD:ik:lNnpr:RuU:w",
53 	NULL,
54 	cvs_diff
55 };
56 
57 struct cvs_cmd cvs_cmd_rdiff = {
58 	CVS_OP_RDIFF, 0, "rdiff",
59 	{ "patch", "pa" },
60 	"Show differences between revisions",
61 	"[-flR] [-c | -u] [-s | -t] [-V ver] -D date | -r rev\n"
62 	"[-D date2 | -r rev2] [-k mode] module ...",
63 	"cfD:k:lr:RuV:",
64 	NULL,
65 	cvs_diff
66 };
67 
68 int
69 cvs_diff(int argc, char **argv)
70 {
71 	int ch, flags;
72 	char *arg = ".";
73 	const char *errstr;
74 	struct cvs_recursion cr;
75 
76 	flags = CR_RECURSE_DIRS;
77 	strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff",
78 	    sizeof(diffargs));
79 
80 	while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ?
81 	    cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) {
82 		switch (ch) {
83 		case 'a':
84 			strlcat(diffargs, " -a", sizeof(diffargs));
85 			dflags |= D_FORCEASCII;
86 			break;
87 		case 'b':
88 			strlcat(diffargs, " -b", sizeof(diffargs));
89 			dflags |= D_FOLDBLANKS;
90 			break;
91 		case 'c':
92 			strlcat(diffargs, " -c", sizeof(diffargs));
93 			diff_format = D_CONTEXT;
94 			break;
95 		case 'C':
96 			diff_context = strtonum(optarg, 0, INT_MAX, &errstr);
97 			if (errstr != NULL)
98 				fatal("context lines %s: %s", errstr, optarg);
99 			strlcat(diffargs, " -C ", sizeof(diffargs));
100 			strlcat(diffargs, optarg, sizeof(diffargs));
101 			diff_format = D_CONTEXT;
102 			break;
103 		case 'd':
104 			strlcat(diffargs, " -d", sizeof(diffargs));
105 			dflags |= D_MINIMAL;
106 			break;
107 		case 'D':
108 			if (date1 == -1 && rev1 == NULL) {
109 				if ((date1 = date_parse(optarg)) == -1)
110 					fatal("invalid date: %s", optarg);
111 				dateflag1 = optarg;
112 			} else if (date2 == -1 && rev2 == NULL) {
113 				if ((date2 = date_parse(optarg)) == -1)
114 					fatal("invalid date: %s", optarg);
115 				dateflag2 = optarg;
116 			} else {
117 				fatal("no more than 2 revisions/dates can"
118 				    " be specified");
119 			}
120 			break;
121 		case 'f':
122 			force_head = 1;
123 			break;
124 		case 'i':
125 			strlcat(diffargs, " -i", sizeof(diffargs));
126 			dflags |= D_IGNORECASE;
127 			break;
128 		case 'k':
129 			koptstr = optarg;
130 			kflag = rcs_kflag_get(koptstr);
131 			if (RCS_KWEXP_INVAL(kflag)) {
132 				cvs_log(LP_ERR,
133 				    "invalid RCS keyword expansion mode");
134 				fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
135 				    cvs_cmd_diff.cmd_synopsis :
136 				    cvs_cmd_rdiff.cmd_synopsis);
137 			}
138 			break;
139 		case 'l':
140 			flags &= ~CR_RECURSE_DIRS;
141 			break;
142 		case 'n':
143 			strlcat(diffargs, " -n", sizeof(diffargs));
144 			diff_format = D_RCSDIFF;
145 			break;
146 		case 'N':
147 			strlcat(diffargs, " -N", sizeof(diffargs));
148 			Nflag = 1;
149 			break;
150 		case 'p':
151 			strlcat(diffargs, " -p", sizeof(diffargs));
152 			dflags |= D_PROTOTYPE;
153 			break;
154 		case 'R':
155 			flags |= CR_RECURSE_DIRS;
156 			break;
157 		case 'r':
158 			if (date1 == -1 && rev1 == NULL) {
159 				rev1 = optarg;
160 			} else if (date2 == -1 && rev2 == NULL) {
161 				rev2 = optarg;
162 			} else {
163 				fatal("no more than 2 revisions/dates can"
164 				    " be specified");
165 			}
166 			break;
167 		case 't':
168 			strlcat(diffargs, " -t", sizeof(diffargs));
169 			dflags |= D_EXPANDTABS;
170 			break;
171 		case 'u':
172 			strlcat(diffargs, " -u", sizeof(diffargs));
173 			diff_format = D_UNIFIED;
174 			break;
175 		case 'U':
176 			diff_context = strtonum(optarg, 0, INT_MAX, &errstr);
177 			if (errstr != NULL)
178 				fatal("context lines %s: %s", errstr, optarg);
179 			strlcat(diffargs, " -U ", sizeof(diffargs));
180 			strlcat(diffargs, optarg, sizeof(diffargs));
181 			diff_format = D_UNIFIED;
182 			break;
183 		case 'V':
184 			fatal("the -V option is obsolete "
185 			    "and should not be used");
186 		case 'w':
187 			strlcat(diffargs, " -w", sizeof(diffargs));
188 			dflags |= D_IGNOREBLANKS;
189 			break;
190 		default:
191 			fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
192 			    cvs_cmd_diff.cmd_synopsis :
193 			    cvs_cmd_rdiff.cmd_synopsis);
194 		}
195 	}
196 
197 	argc -= optind;
198 	argv += optind;
199 
200 	cr.enterdir = NULL;
201 	cr.leavedir = NULL;
202 
203 	if (cvs_cmdop == CVS_OP_RDIFF) {
204 		if (rev1 == NULL && rev2 == NULL && dateflag1 == NULL &&
205 		    dateflag2 == NULL)
206 			fatal("must specify at least one revision/date!");
207 
208 		if (!argc)
209 			fatal("%s", cvs_cmd_rdiff.cmd_synopsis);
210 
211 		if (!diff_format) {
212 			strlcat(diffargs, " -c", sizeof(diffargs));
213 			diff_format = D_CONTEXT;
214 		}
215 
216 		flags |= CR_REPO;
217 	}
218 
219 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
220 		cvs_client_connect_to_server();
221 		cr.fileproc = cvs_client_sendfile;
222 
223 		if (!(flags & CR_RECURSE_DIRS))
224 			cvs_client_send_request("Argument -l");
225 
226 		if (kflag)
227 			cvs_client_send_request("Argument -k%s", koptstr);
228 
229 		switch (diff_format) {
230 		case D_CONTEXT:
231 			if (cvs_cmdop == CVS_OP_RDIFF)
232 				cvs_client_send_request("Argument -c");
233 			else {
234 				cvs_client_send_request("Argument -C %d",
235 				    diff_context);
236 			}
237 			break;
238 		case D_RCSDIFF:
239 			cvs_client_send_request("Argument -n");
240 			break;
241 		case D_UNIFIED:
242 			if (cvs_cmdop == CVS_OP_RDIFF || diff_context == 3)
243 				cvs_client_send_request("Argument -u");
244 			else {
245 				cvs_client_send_request("Argument -U %d",
246 				    diff_context);
247 			}
248 			break;
249 		default:
250 			break;
251 		}
252 
253 		if (Nflag == 1)
254 			cvs_client_send_request("Argument -N");
255 
256 		if (dflags & D_PROTOTYPE)
257 			cvs_client_send_request("Argument -p");
258 
259 		if (rev1 != NULL)
260 			cvs_client_send_request("Argument -r%s", rev1);
261 		if (rev2 != NULL)
262 			cvs_client_send_request("Argument -r%s", rev2);
263 
264 		if (dateflag1 != NULL)
265 			cvs_client_send_request("Argument -D%s", dateflag1);
266 		if (dateflag2 != NULL)
267 			cvs_client_send_request("Argument -D%s", dateflag2);
268 	} else {
269 		if (cvs_cmdop == CVS_OP_RDIFF &&
270 		    chdir(current_cvsroot->cr_dir) == -1)
271 			fatal("cvs_diff: %s", strerror(errno));
272 
273 		cr.fileproc = cvs_diff_local;
274 	}
275 
276 	cr.flags = flags;
277 
278 	diff_rev1 = diff_rev2 = NULL;
279 
280 	if (cvs_cmdop == CVS_OP_DIFF ||
281 	    current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
282 		if (argc > 0)
283 			cvs_file_run(argc, argv, &cr);
284 		else
285 			cvs_file_run(1, &arg, &cr);
286 	}
287 
288 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
289 		cvs_client_send_files(argv, argc);
290 		cvs_client_senddir(".");
291 
292 		cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ?
293 		    "rdiff" : "diff");
294 
295 		cvs_client_get_responses();
296 	}
297 
298 	return (0);
299 }
300 
301 void
302 cvs_diff_local(struct cvs_file *cf)
303 {
304 	BUF *b1;
305 	int fd1, fd2;
306 	struct stat st;
307 	struct timeval tv[2], tv2[2];
308 	struct tm datetm;
309 	char rbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ], *p1, *p2;
310 
311 	b1 = NULL;
312 	fd1 = fd2 = -1;
313 	p1 = p2 = NULL;
314 
315 	cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path);
316 
317 	if (cf->file_type == CVS_DIR) {
318 		if (verbosity > 1)
319 			cvs_log(LP_ERR, "Diffing inside %s", cf->file_path);
320 		return;
321 	}
322 
323 	cvs_file_classify(cf, cvs_directory_tag);
324 
325 	if (cvs_cmdop == CVS_OP_DIFF) {
326 		if (cf->file_ent == NULL) {
327 			cvs_log(LP_ERR, "I know nothing about %s",
328 			    cf->file_path);
329 			return;
330 		}
331 
332 		switch (cf->file_ent->ce_status) {
333 		case CVS_ENT_ADDED:
334 			if (Nflag == 0) {
335 				cvs_log(LP_ERR, "%s is a new entry, no "
336 				    "comparison available", cf->file_path);
337 				return;
338 			}
339 			if (!(cf->file_flags & FILE_ON_DISK)) {
340 				cvs_log(LP_ERR, "cannot find %s",
341 				    cf->file_path);
342 				return;
343 			}
344 			break;
345 		case CVS_ENT_REMOVED:
346 			if (Nflag == 0) {
347 				cvs_log(LP_ERR, "%s was removed, no "
348 				    "comparison available", cf->file_path);
349 				return;
350 			}
351 			if (cf->file_rcs == NULL) {
352 				cvs_log(LP_ERR, "cannot find RCS file for %s",
353 				    cf->file_path);
354 				return;
355 			}
356 			break;
357 		default:
358 			if (!(cf->file_flags & FILE_ON_DISK)) {
359 				cvs_printf("? %s\n", cf->file_path);
360 				return;
361 			}
362 
363 			if (cf->file_rcs == NULL) {
364 				cvs_log(LP_ERR, "cannot find RCS file for %s",
365 				    cf->file_path);
366 				return;
367 			}
368 			break;
369 		}
370 	}
371 
372 	if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL &&
373 	    date1 == -1 && date2 == -1)
374 		return;
375 
376 	if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) {
377 		cvs_log(LP_ERR, "no head revision in RCS file for %s\n",
378 		    cf->file_path);
379 		return;
380 	}
381 
382 	if (kflag && cf->file_rcs != NULL)
383 		rcs_kwexp_set(cf->file_rcs, kflag);
384 
385 	if (cf->file_rcs == NULL)
386 		diff_rev1 = NULL;
387 	else if (rev1 != NULL || date1 != -1) {
388 		cvs_specified_date = date1;
389 		diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs);
390 		if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) {
391 			if (rev1 != NULL) {
392 				cvs_log(LP_ERR, "tag %s not in file %s", rev1,
393 				    cf->file_path);
394 				goto cleanup;
395 			} else if (Nflag) {
396 				diff_rev1 = NULL;
397 			} else {
398 				gmtime_r(&cvs_specified_date, &datetm);
399 				strftime(tbuf, sizeof(tbuf),
400 				    "%Y.%m.%d.%H.%M.%S", &datetm);
401 				cvs_log(LP_ERR, "no revision for date %s in "
402 				    "file %s", tbuf, cf->file_path);
403 				goto cleanup;
404 			}
405 		} else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
406 		    force_head) {
407 			/* -f is not allowed for unknown symbols */
408 			if ((diff_rev1 = rcsnum_parse(rev1)) == NULL)
409 				fatal("no such tag %s", rev1);
410 			rcsnum_free(diff_rev1);
411 
412 			diff_rev1 = cf->file_rcs->rf_head;
413 		}
414 		cvs_specified_date = -1;
415 	} else if (cvs_cmdop == CVS_OP_DIFF) {
416 		if (cf->file_ent->ce_status == CVS_ENT_ADDED)
417 			diff_rev1 = NULL;
418 		else
419 			diff_rev1 = cf->file_ent->ce_rev;
420 	}
421 
422 	if (cf->file_rcs == NULL)
423 		diff_rev2 = NULL;
424 	else if (rev2 != NULL || date2 != -1) {
425 		cvs_specified_date = date2;
426 		diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs);
427 		if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) {
428 			if (rev2 != NULL) {
429 				cvs_log(LP_ERR, "tag %s not in file %s", rev2,
430 				    cf->file_path);
431 				goto cleanup;
432 			} else if (Nflag) {
433 				diff_rev2 = NULL;
434 			} else {
435 				gmtime_r(&cvs_specified_date, &datetm);
436 				strftime(tbuf, sizeof(tbuf),
437 				    "%Y.%m.%d.%H.%M.%S", &datetm);
438 				cvs_log(LP_ERR, "no revision for date %s in "
439 				    "file %s", tbuf, cf->file_path);
440 				goto cleanup;
441 			}
442 		} else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
443 		    force_head) {
444 			/* -f is not allowed for unknown symbols */
445 			if ((diff_rev2 = rcsnum_parse(rev2)) == NULL)
446 				fatal("no such tag %s", rev2);
447 			rcsnum_free(diff_rev2);
448 
449 			diff_rev2 = cf->file_rcs->rf_head;
450 		}
451 		cvs_specified_date = -1;
452 	} else if (cvs_cmdop == CVS_OP_RDIFF)
453 		diff_rev2 = cf->file_rcs->rf_head;
454 	else if (cf->file_ent->ce_status == CVS_ENT_REMOVED)
455 		diff_rev2 = NULL;
456 
457 	if (diff_rev1 != NULL && diff_rev2 != NULL &&
458 	    rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0)
459 		goto cleanup;
460 
461 	switch (cvs_cmdop) {
462 	case CVS_OP_DIFF:
463 		if (cf->file_status == FILE_UPTODATE) {
464 			if (diff_rev2 == NULL &&
465 			    !rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0))
466 				goto cleanup;
467 		}
468 		break;
469 	case CVS_OP_RDIFF:
470 		if (diff_rev1 == NULL && diff_rev2 == NULL)
471 			goto cleanup;
472 		break;
473 	}
474 
475 	cvs_printf("Index: %s\n", cf->file_path);
476 	if (cvs_cmdop == CVS_OP_DIFF)
477 		cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV,
478 		    cf->file_rcs != NULL ? cf->file_rpath : cf->file_path);
479 
480 	if (diff_rev1 != NULL) {
481 		if (cvs_cmdop == CVS_OP_DIFF && diff_rev1 != NULL) {
482 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
483 			cvs_printf("retrieving revision %s\n", rbuf);
484 		}
485 
486 		tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev1);
487 		tv[0].tv_usec = 0;
488 		tv[1] = tv[0];
489 
490 		(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
491 		fd1 = rcs_rev_write_stmp(cf->file_rcs, diff_rev1, p1, 0);
492 		if (futimes(fd1, tv) == -1)
493 			fatal("cvs_diff_local: utimes failed");
494 	}
495 
496 	if (diff_rev2 != NULL) {
497 		if (cvs_cmdop == CVS_OP_DIFF && rev2 != NULL) {
498 			(void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf));
499 			cvs_printf("retrieving revision %s\n", rbuf);
500 		}
501 
502 		tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2);
503 		tv2[0].tv_usec = 0;
504 		tv2[1] = tv2[0];
505 
506 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
507 		fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0);
508 		if (futimes(fd2, tv2) == -1)
509 			fatal("cvs_diff_local: utimes failed");
510 	} else if (cvs_cmdop == CVS_OP_DIFF &&
511 	    (cf->file_flags & FILE_ON_DISK) &&
512 	    cf->file_ent->ce_status != CVS_ENT_REMOVED) {
513 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
514 		if (cvs_server_active == 1 && cf->fd == -1) {
515 			tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs,
516 			    cf->file_ent->ce_rev);
517 			tv2[0].tv_usec = 0;
518 			tv2[1] = tv2[0];
519 
520 			fd2 = rcs_rev_write_stmp(cf->file_rcs,
521 			    cf->file_ent->ce_rev, p2, 0);
522 			if (futimes(fd2, tv2) == -1)
523 				fatal("cvs_diff_local: futimes failed");
524 		} else {
525 			if (fstat(cf->fd, &st) == -1)
526 				fatal("fstat failed %s", strerror(errno));
527 			b1 = buf_load_fd(cf->fd);
528 
529 			tv2[0].tv_sec = st.st_mtime;
530 			tv2[0].tv_usec = 0;
531 			tv2[1] = tv2[0];
532 
533 			fd2 = buf_write_stmp(b1, p2, tv2);
534 			buf_free(b1);
535 		}
536 	}
537 
538 	switch (cvs_cmdop) {
539 	case CVS_OP_DIFF:
540 		cvs_printf("%s", diffargs);
541 
542 		if (rev1 != NULL && diff_rev1 != NULL) {
543 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
544 			cvs_printf(" -r%s", rbuf);
545 
546 			if (rev2 != NULL && diff_rev2 != NULL) {
547 				(void)rcsnum_tostr(diff_rev2, rbuf,
548 				    sizeof(rbuf));
549 				cvs_printf(" -r%s", rbuf);
550 			}
551 		}
552 
553 		if (diff_rev2 == NULL)
554 			cvs_printf(" %s", cf->file_path);
555 		cvs_printf("\n");
556 		break;
557 	case CVS_OP_RDIFF:
558 		cvs_printf("diff ");
559 		switch (diff_format) {
560 		case D_CONTEXT:
561 			cvs_printf("-c ");
562 			break;
563 		case D_RCSDIFF:
564 			cvs_printf("-n ");
565 			break;
566 		case D_UNIFIED:
567 			cvs_printf("-u ");
568 			break;
569 		default:
570 			break;
571 		}
572 		if (diff_rev1 == NULL) {
573 			cvs_printf("%s ", CVS_PATH_DEVNULL);
574 		} else {
575 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
576 			cvs_printf("%s:%s ", cf->file_path, rbuf);
577 		}
578 
579 		if (diff_rev2 == NULL) {
580 			cvs_printf("%s:removed\n", cf->file_path);
581 		} else {
582 			(void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 :
583 			    cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
584 			cvs_printf("%s:%s\n", cf->file_path, rbuf);
585 		}
586 		break;
587 	}
588 
589 	if (fd1 == -1) {
590 		if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
591 			fatal("cannot open %s", CVS_PATH_DEVNULL);
592 	}
593 	if (fd2 == -1) {
594 		if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
595 			fatal("cannot open %s", CVS_PATH_DEVNULL);
596 	}
597 
598 	if (diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL,
599 	    p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL,
600 	    dflags) == D_ERROR)
601 		fatal("cvs_diff_local: failed to get RCS patch");
602 
603 	close(fd1);
604 	close(fd2);
605 
606 	worklist_run(&temp_files, worklist_unlink);
607 
608 	if (p1 != NULL)
609 		xfree(p1);
610 	if (p2 != NULL)
611 		xfree(p2);
612 
613 cleanup:
614 	if (diff_rev1 != NULL &&
615 	    (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) &&
616 	    (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev))
617 		xfree(diff_rev1);
618 	diff_rev1 = NULL;
619 
620 	if (diff_rev2 != NULL &&
621 	    (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head))
622 		xfree(diff_rev2);
623 	diff_rev2 = NULL;
624 }
625