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 33void cvs_diff_local(struct cvs_file *); 34 35static int dflags = 0; 36static int Nflag = 0; 37static int force_head = 0; 38static char *koptstr; 39static char *rev1 = NULL; 40static char *rev2 = NULL; 41static time_t date1 = -1; 42static time_t date2 = -1; 43static char *dateflag1 = NULL; 44static char *dateflag2 = NULL; 45 46struct 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 57struct 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 68int 69cvs_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 300void 301cvs_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 610cleanup: 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