1 /* $OpenBSD: diff.c,v 1.164 2021/10/24 21:24:16 deraadt 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
cvs_diff(int argc,char ** argv)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 (cvsroot_is_remote()) {
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 || cvsroot_is_local()) {
281 if (argc > 0)
282 cvs_file_run(argc, argv, &cr);
283 else
284 cvs_file_run(1, &arg, &cr);
285 }
286
287 if (cvsroot_is_remote()) {
288 cvs_client_send_files(argv, argc);
289 cvs_client_senddir(".");
290
291 cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ?
292 "rdiff" : "diff");
293
294 cvs_client_get_responses();
295 }
296
297 return (0);
298 }
299
300 void
cvs_diff_local(struct cvs_file * cf)301 cvs_diff_local(struct cvs_file *cf)
302 {
303 BUF *b1;
304 int fd1, fd2;
305 struct stat st;
306 struct timeval tv[2], tv2[2];
307 struct tm datetm;
308 char rbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ], *p1, *p2;
309
310 b1 = NULL;
311 fd1 = fd2 = -1;
312 p1 = p2 = NULL;
313
314 cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path);
315
316 if (cf->file_type == CVS_DIR) {
317 if (verbosity > 1)
318 cvs_log(LP_ERR, "Diffing inside %s", cf->file_path);
319 return;
320 }
321
322 cvs_file_classify(cf, cvs_directory_tag);
323
324 if (cvs_cmdop == CVS_OP_DIFF) {
325 if (cf->file_ent == NULL) {
326 cvs_log(LP_ERR, "I know nothing about %s",
327 cf->file_path);
328 return;
329 }
330
331 switch (cf->file_ent->ce_status) {
332 case CVS_ENT_ADDED:
333 if (Nflag == 0) {
334 cvs_log(LP_ERR, "%s is a new entry, no "
335 "comparison available", cf->file_path);
336 return;
337 }
338 if (!(cf->file_flags & FILE_ON_DISK)) {
339 cvs_log(LP_ERR, "cannot find %s",
340 cf->file_path);
341 return;
342 }
343 break;
344 case CVS_ENT_REMOVED:
345 if (Nflag == 0) {
346 cvs_log(LP_ERR, "%s was removed, no "
347 "comparison available", cf->file_path);
348 return;
349 }
350 if (cf->file_rcs == NULL) {
351 cvs_log(LP_ERR, "cannot find RCS file for %s",
352 cf->file_path);
353 return;
354 }
355 break;
356 default:
357 if (!(cf->file_flags & FILE_ON_DISK)) {
358 cvs_printf("? %s\n", cf->file_path);
359 return;
360 }
361
362 if (cf->file_rcs == NULL) {
363 cvs_log(LP_ERR, "cannot find RCS file for %s",
364 cf->file_path);
365 return;
366 }
367 break;
368 }
369 }
370
371 if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL &&
372 date1 == -1 && date2 == -1)
373 return;
374
375 if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) {
376 cvs_log(LP_ERR, "no head revision in RCS file for %s\n",
377 cf->file_path);
378 return;
379 }
380
381 if (kflag && cf->file_rcs != NULL)
382 rcs_kwexp_set(cf->file_rcs, kflag);
383
384 if (cf->file_rcs == NULL)
385 diff_rev1 = NULL;
386 else if (rev1 != NULL || date1 != -1) {
387 cvs_specified_date = date1;
388 diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs);
389 if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) {
390 if (rev1 != NULL) {
391 cvs_log(LP_ERR, "tag %s not in file %s", rev1,
392 cf->file_path);
393 goto cleanup;
394 } else if (Nflag) {
395 diff_rev1 = NULL;
396 } else {
397 gmtime_r(&cvs_specified_date, &datetm);
398 strftime(tbuf, sizeof(tbuf),
399 "%Y.%m.%d.%H.%M.%S", &datetm);
400 cvs_log(LP_ERR, "no revision for date %s in "
401 "file %s", tbuf, cf->file_path);
402 goto cleanup;
403 }
404 } else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
405 force_head) {
406 /* -f is not allowed for unknown symbols */
407 if ((diff_rev1 = rcsnum_parse(rev1)) == NULL)
408 fatal("no such tag %s", rev1);
409 free(diff_rev1);
410
411 diff_rev1 = cf->file_rcs->rf_head;
412 }
413 cvs_specified_date = -1;
414 } else if (cvs_cmdop == CVS_OP_DIFF) {
415 if (cf->file_ent->ce_status == CVS_ENT_ADDED)
416 diff_rev1 = NULL;
417 else
418 diff_rev1 = cf->file_ent->ce_rev;
419 }
420
421 if (cf->file_rcs == NULL)
422 diff_rev2 = NULL;
423 else if (rev2 != NULL || date2 != -1) {
424 cvs_specified_date = date2;
425 diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs);
426 if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) {
427 if (rev2 != NULL) {
428 cvs_log(LP_ERR, "tag %s not in file %s", rev2,
429 cf->file_path);
430 goto cleanup;
431 } else if (Nflag) {
432 diff_rev2 = NULL;
433 } else {
434 gmtime_r(&cvs_specified_date, &datetm);
435 strftime(tbuf, sizeof(tbuf),
436 "%Y.%m.%d.%H.%M.%S", &datetm);
437 cvs_log(LP_ERR, "no revision for date %s in "
438 "file %s", tbuf, cf->file_path);
439 goto cleanup;
440 }
441 } else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
442 force_head) {
443 /* -f is not allowed for unknown symbols */
444 if ((diff_rev2 = rcsnum_parse(rev2)) == NULL)
445 fatal("no such tag %s", rev2);
446 free(diff_rev2);
447
448 diff_rev2 = cf->file_rcs->rf_head;
449 }
450 cvs_specified_date = -1;
451 } else if (cvs_cmdop == CVS_OP_RDIFF)
452 diff_rev2 = cf->file_rcs->rf_head;
453 else if (cf->file_ent->ce_status == CVS_ENT_REMOVED)
454 diff_rev2 = NULL;
455
456 if (diff_rev1 != NULL && diff_rev2 != NULL &&
457 rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0)
458 goto cleanup;
459
460 switch (cvs_cmdop) {
461 case CVS_OP_DIFF:
462 if (cf->file_status == FILE_UPTODATE) {
463 if (diff_rev2 == NULL &&
464 !rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0))
465 goto cleanup;
466 }
467 break;
468 case CVS_OP_RDIFF:
469 if (diff_rev1 == NULL && diff_rev2 == NULL)
470 goto cleanup;
471 break;
472 }
473
474 cvs_printf("Index: %s\n", cf->file_path);
475 if (cvs_cmdop == CVS_OP_DIFF)
476 cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV,
477 cf->file_rcs != NULL ? cf->file_rpath : cf->file_path);
478
479 if (diff_rev1 != NULL) {
480 if (cvs_cmdop == CVS_OP_DIFF && diff_rev1 != NULL) {
481 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
482 cvs_printf("retrieving revision %s\n", rbuf);
483 }
484
485 tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev1);
486 tv[0].tv_usec = 0;
487 tv[1] = tv[0];
488
489 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
490 fd1 = rcs_rev_write_stmp(cf->file_rcs, diff_rev1, p1, 0);
491 if (futimes(fd1, tv) == -1)
492 fatal("cvs_diff_local: utimes failed");
493 }
494
495 if (diff_rev2 != NULL) {
496 if (cvs_cmdop == CVS_OP_DIFF && rev2 != NULL) {
497 (void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf));
498 cvs_printf("retrieving revision %s\n", rbuf);
499 }
500
501 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2);
502 tv2[0].tv_usec = 0;
503 tv2[1] = tv2[0];
504
505 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
506 fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0);
507 if (futimes(fd2, tv2) == -1)
508 fatal("cvs_diff_local: utimes failed");
509 } else if (cvs_cmdop == CVS_OP_DIFF &&
510 (cf->file_flags & FILE_ON_DISK) &&
511 cf->file_ent->ce_status != CVS_ENT_REMOVED) {
512 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
513 if (cvs_server_active == 1 && cf->fd == -1) {
514 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs,
515 cf->file_ent->ce_rev);
516 tv2[0].tv_usec = 0;
517 tv2[1] = tv2[0];
518
519 fd2 = rcs_rev_write_stmp(cf->file_rcs,
520 cf->file_ent->ce_rev, p2, 0);
521 if (futimes(fd2, tv2) == -1)
522 fatal("cvs_diff_local: futimes failed");
523 } else {
524 if (fstat(cf->fd, &st) == -1)
525 fatal("fstat failed %s", strerror(errno));
526 b1 = buf_load_fd(cf->fd);
527
528 tv2[0].tv_sec = st.st_mtime;
529 tv2[0].tv_usec = 0;
530 tv2[1] = tv2[0];
531
532 fd2 = buf_write_stmp(b1, p2, tv2);
533 buf_free(b1);
534 }
535 }
536
537 switch (cvs_cmdop) {
538 case CVS_OP_DIFF:
539 cvs_printf("%s", diffargs);
540
541 if (rev1 != NULL && diff_rev1 != NULL) {
542 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
543 cvs_printf(" -r%s", rbuf);
544
545 if (rev2 != NULL && diff_rev2 != NULL) {
546 (void)rcsnum_tostr(diff_rev2, rbuf,
547 sizeof(rbuf));
548 cvs_printf(" -r%s", rbuf);
549 }
550 }
551
552 if (diff_rev2 == NULL)
553 cvs_printf(" %s", cf->file_path);
554 cvs_printf("\n");
555 break;
556 case CVS_OP_RDIFF:
557 cvs_printf("diff ");
558 switch (diff_format) {
559 case D_CONTEXT:
560 cvs_printf("-c ");
561 break;
562 case D_RCSDIFF:
563 cvs_printf("-n ");
564 break;
565 case D_UNIFIED:
566 cvs_printf("-u ");
567 break;
568 default:
569 break;
570 }
571 if (diff_rev1 == NULL) {
572 cvs_printf("%s ", CVS_PATH_DEVNULL);
573 } else {
574 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
575 cvs_printf("%s:%s ", cf->file_path, rbuf);
576 }
577
578 if (diff_rev2 == NULL) {
579 cvs_printf("%s:removed\n", cf->file_path);
580 } else {
581 (void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 :
582 cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
583 cvs_printf("%s:%s\n", cf->file_path, rbuf);
584 }
585 break;
586 }
587
588 if (fd1 == -1) {
589 if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY)) == -1)
590 fatal("cannot open %s", CVS_PATH_DEVNULL);
591 }
592 if (fd2 == -1) {
593 if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY)) == -1)
594 fatal("cannot open %s", CVS_PATH_DEVNULL);
595 }
596
597 if (diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL,
598 p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL,
599 dflags) == D_ERROR)
600 fatal("cvs_diff_local: failed to get RCS patch");
601
602 close(fd1);
603 close(fd2);
604
605 worklist_run(&temp_files, worklist_unlink);
606
607 free(p1);
608 free(p2);
609
610 cleanup:
611 if (diff_rev1 != NULL &&
612 (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) &&
613 (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev))
614 free(diff_rev1);
615 diff_rev1 = NULL;
616
617 if (diff_rev2 != NULL &&
618 (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head))
619 free(diff_rev2);
620 diff_rev2 = NULL;
621 }
622