1 /* sdiff - side-by-side merge of file differences 2 3 Copyright (C) 1992-1996, 1998, 2001-2002, 2004, 2006-2007, 2009-2011 Free 4 Software Foundation, Inc. 5 6 This file is part of GNU DIFF. 7 8 This program is free software: you can redistribute it and/or modify 9 it under the terms of the GNU General Public License as published by 10 the Free Software Foundation, either version 3 of the License, or 11 (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 20 21 #include "system.h" 22 #include "paths.h" 23 24 #include <stdio.h> 25 #include <unlocked-io.h> 26 27 #include <c-stack.h> 28 #include <dirname.h> 29 #include <error.h> 30 #include <exitfail.h> 31 #include <file-type.h> 32 #include <getopt.h> 33 #include <progname.h> 34 #include <sh-quote.h> 35 #include <version-etc.h> 36 #include <xalloc.h> 37 38 /* The official name of this program (e.g., no `g' prefix). */ 39 #define PROGRAM_NAME "sdiff" 40 41 #define AUTHORS \ 42 proper_name ("Thomas Lord") 43 44 /* Size of chunks read from files which must be parsed into lines. */ 45 #define SDIFF_BUFSIZE ((size_t) 65536) 46 47 static char const *editor_program = DEFAULT_EDITOR_PROGRAM; 48 static char const **diffargv; 49 50 static char * volatile tmpname; 51 static FILE *tmp; 52 53 #if HAVE_WORKING_FORK 54 static pid_t volatile diffpid; 55 #endif 56 57 struct line_filter; 58 59 static void catchsig (int); 60 static bool edit (struct line_filter *, char const *, lin, lin, struct line_filter *, char const *, lin, lin, FILE *); 61 static bool interact (struct line_filter *, struct line_filter *, char const *, struct line_filter *, char const *, FILE *); 62 static void checksigs (void); 63 static void diffarg (char const *); 64 static void fatal (char const *) __attribute__((noreturn)); 65 static void perror_fatal (char const *) __attribute__((noreturn)); 66 static void trapsigs (void); 67 static void untrapsig (int); 68 69 #define NUM_SIGS (sizeof sigs / sizeof *sigs) 70 static int const sigs[] = { 71 #ifdef SIGHUP 72 SIGHUP, 73 #endif 74 #ifdef SIGQUIT 75 SIGQUIT, 76 #endif 77 #ifdef SIGTERM 78 SIGTERM, 79 #endif 80 #ifdef SIGXCPU 81 SIGXCPU, 82 #endif 83 #ifdef SIGXFSZ 84 SIGXFSZ, 85 #endif 86 #ifdef SIGPIPE 87 SIGPIPE, 88 #endif 89 SIGINT 90 #define handler_index_of_SIGINT (NUM_SIGS - 1) 91 }; 92 93 #if HAVE_SIGACTION 94 /* Prefer `sigaction' if available, since `signal' can lose signals. */ 95 static struct sigaction initial_action[NUM_SIGS]; 96 # define initial_handler(i) (initial_action[i].sa_handler) 97 static void signal_handler (int, void (*) (int)); 98 #else 99 static void (*initial_action[NUM_SIGS]) (); 100 # define initial_handler(i) (initial_action[i]) 101 # define signal_handler(sig, handler) signal (sig, handler) 102 #endif 103 104 static bool diraccess (char const *); 105 static int temporary_file (void); 106 107 /* Options: */ 108 109 /* Name of output file if -o specified. */ 110 static char const *output; 111 112 /* Do not print common lines. */ 113 static bool suppress_common_lines; 114 115 /* Value for the long option that does not have single-letter equivalents. */ 116 enum 117 { 118 DIFF_PROGRAM_OPTION = CHAR_MAX + 1, 119 HELP_OPTION, 120 STRIP_TRAILING_CR_OPTION, 121 TABSIZE_OPTION 122 }; 123 124 static struct option const longopts[] = 125 { 126 {"diff-program", 1, 0, DIFF_PROGRAM_OPTION}, 127 {"expand-tabs", 0, 0, 't'}, 128 {"help", 0, 0, HELP_OPTION}, 129 {"ignore-all-space", 0, 0, 'W'}, /* swap W and w for historical reasons */ 130 {"ignore-blank-lines", 0, 0, 'B'}, 131 {"ignore-case", 0, 0, 'i'}, 132 {"ignore-matching-lines", 1, 0, 'I'}, 133 {"ignore-space-change", 0, 0, 'b'}, 134 {"ignore-tab-expansion", 0, 0, 'E'}, 135 {"ignore-trailing-space", 0, 0, 'Z'}, 136 {"left-column", 0, 0, 'l'}, 137 {"minimal", 0, 0, 'd'}, 138 {"output", 1, 0, 'o'}, 139 {"speed-large-files", 0, 0, 'H'}, 140 {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION}, 141 {"suppress-common-lines", 0, 0, 's'}, 142 {"tabsize", 1, 0, TABSIZE_OPTION}, 143 {"text", 0, 0, 'a'}, 144 {"version", 0, 0, 'v'}, 145 {"width", 1, 0, 'w'}, 146 {0, 0, 0, 0} 147 }; 148 149 static void try_help (char const *, char const *) __attribute__((noreturn)); 150 static void 151 try_help (char const *reason_msgid, char const *operand) 152 { 153 if (reason_msgid) 154 error (0, 0, _(reason_msgid), operand); 155 error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."), 156 program_name); 157 abort (); 158 } 159 160 static void 161 check_stdout (void) 162 { 163 if (ferror (stdout)) 164 fatal ("write failed"); 165 else if (fclose (stdout) != 0) 166 perror_fatal (_("standard output")); 167 } 168 169 static char const * const option_help_msgid[] = { 170 N_("-o, --output=FILE operate interactively, sending output to FILE"), 171 "", 172 N_("-i, --ignore-case consider upper- and lower-case to be the same"), 173 N_("-E, --ignore-tab-expansion ignore changes due to tab expansion"), 174 N_("-Z, --ignore-trailing-space ignore white space at line end"), 175 N_("-b, --ignore-space-change ignore changes in the amount of white space"), 176 N_("-W, --ignore-all-space ignore all white space"), 177 N_("-B, --ignore-blank-lines ignore changes whose lines are all blank"), 178 N_("-I, --ignore-matching-lines=RE ignore changes whose lines all match RE"), 179 N_(" --strip-trailing-cr strip trailing carriage return on input"), 180 N_("-a, --text treat all files as text"), 181 "", 182 N_("-w, --width=NUM output at most NUM (default 130) print columns"), 183 N_("-l, --left-column output only the left column of common lines"), 184 N_("-s, --suppress-common-lines do not output common lines"), 185 "", 186 N_("-t, --expand-tabs expand tabs to spaces in output"), 187 N_(" --tabsize=NUM tab stops at every NUM (default 8) print columns"), 188 "", 189 N_("-d, --minimal try hard to find a smaller set of changes"), 190 N_("-H, --speed-large-files assume large files, many scattered small changes"), 191 N_(" --diff-program=PROGRAM use PROGRAM to compare files"), 192 "", 193 N_(" --help display this help and exit"), 194 N_("-v, --version output version information and exit"), 195 0 196 }; 197 198 static void 199 usage (void) 200 { 201 char const * const *p; 202 203 printf (_("Usage: %s [OPTION]... FILE1 FILE2\n"), program_name); 204 printf ("%s\n\n", 205 _("Side-by-side merge of differences between FILE1 and FILE2.")); 206 207 fputs (_("\ 208 Mandatory arguments to long options are mandatory for short options too.\n\ 209 "), stdout); 210 for (p = option_help_msgid; *p; p++) 211 if (**p) 212 printf (" %s\n", _(*p)); 213 else 214 putchar ('\n'); 215 printf ("\n%s\n%s\n", 216 _("If a FILE is `-', read standard input."), 217 _("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble.")); 218 emit_bug_reporting_address (); 219 } 220 221 /* Clean up after a signal or other failure. This function is 222 async-signal-safe. */ 223 static void 224 cleanup (int signo __attribute__((unused))) 225 { 226 #if HAVE_WORKING_FORK 227 if (0 < diffpid) 228 kill (diffpid, SIGPIPE); 229 #endif 230 if (tmpname) 231 unlink (tmpname); 232 } 233 234 static void exiterr (void) __attribute__((noreturn)); 235 static void 236 exiterr (void) 237 { 238 cleanup (0); 239 untrapsig (0); 240 checksigs (); 241 exit (EXIT_TROUBLE); 242 } 243 244 static void 245 fatal (char const *msgid) 246 { 247 error (0, 0, "%s", _(msgid)); 248 exiterr (); 249 } 250 251 static void 252 perror_fatal (char const *msg) 253 { 254 int e = errno; 255 checksigs (); 256 error (0, e, "%s", msg); 257 exiterr (); 258 } 259 260 static void 261 check_child_status (int werrno, int wstatus, int max_ok_status, 262 char const *subsidiary_program) 263 { 264 int status = (! werrno && WIFEXITED (wstatus) 265 ? WEXITSTATUS (wstatus) 266 : INT_MAX); 267 268 if (max_ok_status < status) 269 { 270 error (0, werrno, 271 _(status == 126 272 ? "subsidiary program `%s' could not be invoked" 273 : status == 127 274 ? "subsidiary program `%s' not found" 275 : status == INT_MAX 276 ? "subsidiary program `%s' failed" 277 : "subsidiary program `%s' failed (exit status %d)"), 278 subsidiary_program, status); 279 exiterr (); 280 } 281 } 282 283 static FILE * 284 ck_fopen (char const *fname, char const *type) 285 { 286 FILE *r = fopen (fname, type); 287 if (! r) 288 perror_fatal (fname); 289 return r; 290 } 291 292 static void 293 ck_fclose (FILE *f) 294 { 295 if (fclose (f)) 296 perror_fatal ("fclose"); 297 } 298 299 static size_t 300 ck_fread (char *buf, size_t size, FILE *f) 301 { 302 size_t r = fread (buf, sizeof (char), size, f); 303 if (r == 0 && ferror (f)) 304 perror_fatal (_("read failed")); 305 return r; 306 } 307 308 static void 309 ck_fwrite (char const *buf, size_t size, FILE *f) 310 { 311 if (fwrite (buf, sizeof (char), size, f) != size) 312 perror_fatal (_("write failed")); 313 } 314 315 static void 316 ck_fflush (FILE *f) 317 { 318 if (fflush (f) != 0) 319 perror_fatal (_("write failed")); 320 } 321 322 static char const * 323 expand_name (char *name, bool is_dir, char const *other_name) 324 { 325 if (STREQ (name, "-")) 326 fatal ("cannot interactively merge standard input"); 327 if (! is_dir) 328 return name; 329 else 330 { 331 /* Yield NAME/BASE, where BASE is OTHER_NAME's basename. */ 332 char const *base = last_component (other_name); 333 size_t namelen = strlen (name), baselen = base_len (base); 334 bool insert_slash = *last_component (name) && name[namelen - 1] != '/'; 335 char *r = xmalloc (namelen + insert_slash + baselen + 1); 336 memcpy (r, name, namelen); 337 r[namelen] = '/'; 338 memcpy (r + namelen + insert_slash, base, baselen); 339 r[namelen + insert_slash + baselen] = '\0'; 340 return r; 341 } 342 } 343 344 struct line_filter { 345 FILE *infile; 346 char *bufpos; 347 char *buffer; 348 char *buflim; 349 }; 350 351 static void 352 lf_init (struct line_filter *lf, FILE *infile) 353 { 354 lf->infile = infile; 355 lf->bufpos = lf->buffer = lf->buflim = xmalloc (SDIFF_BUFSIZE + 1); 356 lf->buflim[0] = '\n'; 357 } 358 359 /* Fill an exhausted line_filter buffer from its INFILE */ 360 static size_t 361 lf_refill (struct line_filter *lf) 362 { 363 size_t s = ck_fread (lf->buffer, SDIFF_BUFSIZE, lf->infile); 364 lf->bufpos = lf->buffer; 365 lf->buflim = lf->buffer + s; 366 lf->buflim[0] = '\n'; 367 checksigs (); 368 return s; 369 } 370 371 /* Advance LINES on LF's infile, copying lines to OUTFILE */ 372 static void 373 lf_copy (struct line_filter *lf, lin lines, FILE *outfile) 374 { 375 char *start = lf->bufpos; 376 377 while (lines) 378 { 379 lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos); 380 if (! lf->bufpos) 381 { 382 ck_fwrite (start, lf->buflim - start, outfile); 383 if (! lf_refill (lf)) 384 return; 385 start = lf->bufpos; 386 } 387 else 388 { 389 --lines; 390 ++lf->bufpos; 391 } 392 } 393 394 ck_fwrite (start, lf->bufpos - start, outfile); 395 } 396 397 /* Advance LINES on LF's infile without doing output */ 398 static void 399 lf_skip (struct line_filter *lf, lin lines) 400 { 401 while (lines) 402 { 403 lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos); 404 if (! lf->bufpos) 405 { 406 if (! lf_refill (lf)) 407 break; 408 } 409 else 410 { 411 --lines; 412 ++lf->bufpos; 413 } 414 } 415 } 416 417 /* Snarf a line into a buffer. Return EOF if EOF, 0 if error, 1 if OK. */ 418 static int 419 lf_snarf (struct line_filter *lf, char *buffer, size_t bufsize) 420 { 421 for (;;) 422 { 423 char *start = lf->bufpos; 424 char *next = (char *) memchr (start, '\n', lf->buflim + 1 - start); 425 size_t s = next - start; 426 if (bufsize <= s) 427 return 0; 428 memcpy (buffer, start, s); 429 if (next < lf->buflim) 430 { 431 buffer[s] = 0; 432 lf->bufpos = next + 1; 433 return 1; 434 } 435 if (! lf_refill (lf)) 436 return s ? 0 : EOF; 437 buffer += s; 438 bufsize -= s; 439 } 440 } 441 442 int 443 main (int argc, char *argv[]) 444 { 445 int opt; 446 char const *prog; 447 448 exit_failure = EXIT_TROUBLE; 449 initialize_main (&argc, &argv); 450 set_program_name (argv[0]); 451 setlocale (LC_ALL, ""); 452 textdomain (PACKAGE); 453 c_stack_action (cleanup); 454 455 prog = getenv ("EDITOR"); 456 if (prog) 457 editor_program = prog; 458 459 diffarg (DEFAULT_DIFF_PROGRAM); 460 461 /* parse command line args */ 462 while ((opt = getopt_long (argc, argv, "abBdEHiI:lo:stvw:WZ", longopts, 0)) 463 != -1) 464 { 465 switch (opt) 466 { 467 case 'a': 468 diffarg ("-a"); 469 break; 470 471 case 'b': 472 diffarg ("-b"); 473 break; 474 475 case 'B': 476 diffarg ("-B"); 477 break; 478 479 case 'd': 480 diffarg ("-d"); 481 break; 482 483 case 'E': 484 diffarg ("-E"); 485 break; 486 487 case 'H': 488 diffarg ("-H"); 489 break; 490 491 case 'i': 492 diffarg ("-i"); 493 break; 494 495 case 'I': 496 diffarg ("-I"); 497 diffarg (optarg); 498 break; 499 500 case 'l': 501 diffarg ("--left-column"); 502 break; 503 504 case 'o': 505 output = optarg; 506 break; 507 508 case 's': 509 suppress_common_lines = true; 510 break; 511 512 case 't': 513 diffarg ("-t"); 514 break; 515 516 case 'v': 517 version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, 518 AUTHORS, (char *) NULL); 519 check_stdout (); 520 return EXIT_SUCCESS; 521 522 case 'w': 523 diffarg ("-W"); 524 diffarg (optarg); 525 break; 526 527 case 'W': 528 diffarg ("-w"); 529 break; 530 531 case 'Z': 532 diffarg ("-Z"); 533 break; 534 535 case DIFF_PROGRAM_OPTION: 536 diffargv[0] = optarg; 537 break; 538 539 case HELP_OPTION: 540 usage (); 541 check_stdout (); 542 return EXIT_SUCCESS; 543 544 case STRIP_TRAILING_CR_OPTION: 545 diffarg ("--strip-trailing-cr"); 546 break; 547 548 case TABSIZE_OPTION: 549 diffarg ("--tabsize"); 550 diffarg (optarg); 551 break; 552 553 default: 554 try_help (0, 0); 555 } 556 } 557 558 if (argc - optind != 2) 559 { 560 if (argc - optind < 2) 561 try_help ("missing operand after `%s'", argv[argc - 1]); 562 else 563 try_help ("extra operand `%s'", argv[optind + 2]); 564 } 565 566 if (! output) 567 { 568 /* easy case: diff does everything for us */ 569 if (suppress_common_lines) 570 diffarg ("--suppress-common-lines"); 571 diffarg ("-y"); 572 diffarg ("--"); 573 diffarg (argv[optind]); 574 diffarg (argv[optind + 1]); 575 diffarg (0); 576 execvp (diffargv[0], (char **) diffargv); 577 perror_fatal (diffargv[0]); 578 } 579 else 580 { 581 char const *lname, *rname; 582 FILE *left, *right, *out, *diffout; 583 bool interact_ok; 584 struct line_filter lfilt; 585 struct line_filter rfilt; 586 struct line_filter diff_filt; 587 bool leftdir = diraccess (argv[optind]); 588 bool rightdir = diraccess (argv[optind + 1]); 589 590 if (leftdir & rightdir) 591 fatal ("both files to be compared are directories"); 592 593 lname = expand_name (argv[optind], leftdir, argv[optind + 1]); 594 left = ck_fopen (lname, "r"); 595 rname = expand_name (argv[optind + 1], rightdir, argv[optind]); 596 right = ck_fopen (rname, "r"); 597 out = ck_fopen (output, "w"); 598 599 diffarg ("--sdiff-merge-assist"); 600 diffarg ("--"); 601 diffarg (argv[optind]); 602 diffarg (argv[optind + 1]); 603 diffarg (0); 604 605 trapsigs (); 606 607 #if ! HAVE_WORKING_FORK 608 { 609 size_t cmdsize = 1; 610 char *p, *command; 611 int i; 612 613 for (i = 0; diffargv[i]; i++) 614 cmdsize += shell_quote_length (diffargv[i]) + 1; 615 command = p = xmalloc (cmdsize); 616 for (i = 0; diffargv[i]; i++) 617 { 618 p = shell_quote_copy (p, diffargv[i]); 619 *p++ = ' '; 620 } 621 p[-1] = 0; 622 errno = 0; 623 diffout = popen (command, "r"); 624 if (! diffout) 625 perror_fatal (command); 626 free (command); 627 } 628 #else 629 { 630 int diff_fds[2]; 631 632 if (pipe (diff_fds) != 0) 633 perror_fatal ("pipe"); 634 635 diffpid = fork (); 636 if (diffpid < 0) 637 perror_fatal ("fork"); 638 if (! diffpid) 639 { 640 /* Alter the child's SIGINT and SIGPIPE handlers; 641 this may munge the parent. 642 The child ignores SIGINT in case the user interrupts the editor. 643 The child does not ignore SIGPIPE, even if the parent does. */ 644 if (initial_handler (handler_index_of_SIGINT) != SIG_IGN) 645 signal_handler (SIGINT, SIG_IGN); 646 signal_handler (SIGPIPE, SIG_DFL); 647 close (diff_fds[0]); 648 if (diff_fds[1] != STDOUT_FILENO) 649 { 650 dup2 (diff_fds[1], STDOUT_FILENO); 651 close (diff_fds[1]); 652 } 653 654 execvp (diffargv[0], (char **) diffargv); 655 _exit (errno == ENOENT ? 127 : 126); 656 } 657 658 close (diff_fds[1]); 659 diffout = fdopen (diff_fds[0], "r"); 660 if (! diffout) 661 perror_fatal ("fdopen"); 662 } 663 #endif 664 665 lf_init (&diff_filt, diffout); 666 lf_init (&lfilt, left); 667 lf_init (&rfilt, right); 668 669 interact_ok = interact (&diff_filt, &lfilt, lname, &rfilt, rname, out); 670 671 ck_fclose (left); 672 ck_fclose (right); 673 ck_fclose (out); 674 675 { 676 int wstatus; 677 int werrno = 0; 678 679 #if ! HAVE_WORKING_FORK 680 wstatus = pclose (diffout); 681 if (wstatus == -1) 682 werrno = errno; 683 #else 684 ck_fclose (diffout); 685 while (waitpid (diffpid, &wstatus, 0) < 0) 686 if (errno == EINTR) 687 checksigs (); 688 else 689 perror_fatal ("waitpid"); 690 diffpid = 0; 691 #endif 692 693 if (tmpname) 694 { 695 unlink (tmpname); 696 tmpname = 0; 697 } 698 699 if (! interact_ok) 700 exiterr (); 701 702 check_child_status (werrno, wstatus, EXIT_FAILURE, diffargv[0]); 703 untrapsig (0); 704 checksigs (); 705 exit (WEXITSTATUS (wstatus)); 706 } 707 } 708 return EXIT_SUCCESS; /* Fool `-Wall'. */ 709 } 710 711 static void 712 diffarg (char const *a) 713 { 714 static size_t diffargs, diffarglim; 715 716 if (diffargs == diffarglim) 717 { 718 if (! diffarglim) 719 diffarglim = 16; 720 else if (PTRDIFF_MAX / (2 * sizeof *diffargv) <= diffarglim) 721 xalloc_die (); 722 else 723 diffarglim *= 2; 724 diffargv = xrealloc (diffargv, diffarglim * sizeof *diffargv); 725 } 726 diffargv[diffargs++] = a; 727 } 728 729 /* Signal handling */ 730 731 static bool volatile ignore_SIGINT; 732 static int volatile signal_received; 733 static bool sigs_trapped; 734 735 static void 736 catchsig (int s) 737 { 738 #if ! HAVE_SIGACTION 739 signal (s, SIG_IGN); 740 #endif 741 if (! (s == SIGINT && ignore_SIGINT)) 742 signal_received = s; 743 } 744 745 #if HAVE_SIGACTION 746 static struct sigaction catchaction; 747 748 static void 749 signal_handler (int sig, void (*handler) (int)) 750 { 751 catchaction.sa_handler = handler; 752 sigaction (sig, &catchaction, 0); 753 } 754 #endif 755 756 static void 757 trapsigs (void) 758 { 759 int i; 760 761 #if HAVE_SIGACTION 762 catchaction.sa_flags = SA_RESTART; 763 sigemptyset (&catchaction.sa_mask); 764 for (i = 0; i < NUM_SIGS; i++) 765 sigaddset (&catchaction.sa_mask, sigs[i]); 766 #endif 767 768 for (i = 0; i < NUM_SIGS; i++) 769 { 770 #if HAVE_SIGACTION 771 sigaction (sigs[i], 0, &initial_action[i]); 772 #else 773 initial_action[i] = signal (sigs[i], SIG_IGN); 774 #endif 775 if (initial_handler (i) != SIG_IGN) 776 signal_handler (sigs[i], catchsig); 777 } 778 779 #ifdef SIGCHLD 780 /* System V fork+wait does not work if SIGCHLD is ignored. */ 781 signal (SIGCHLD, SIG_DFL); 782 #endif 783 784 sigs_trapped = true; 785 } 786 787 /* Untrap signal S, or all trapped signals if S is zero. */ 788 static void 789 untrapsig (int s) 790 { 791 int i; 792 793 if (sigs_trapped) 794 for (i = 0; i < NUM_SIGS; i++) 795 if ((! s || sigs[i] == s) && initial_handler (i) != SIG_IGN) 796 { 797 #if HAVE_SIGACTION 798 sigaction (sigs[i], &initial_action[i], 0); 799 #else 800 signal (sigs[i], initial_action[i]); 801 #endif 802 } 803 } 804 805 /* Exit if a signal has been received. */ 806 static void 807 checksigs (void) 808 { 809 int s = signal_received; 810 if (s) 811 { 812 cleanup (0); 813 814 /* Yield an exit status indicating that a signal was received. */ 815 untrapsig (s); 816 kill (getpid (), s); 817 818 /* That didn't work, so exit with error status. */ 819 exit (EXIT_TROUBLE); 820 } 821 } 822 823 static void 824 give_help (void) 825 { 826 fprintf (stderr, "%s", _("\ 827 ed:\tEdit then use both versions, each decorated with a header.\n\ 828 eb:\tEdit then use both versions.\n\ 829 el or e1:\tEdit then use the left version.\n\ 830 er or e2:\tEdit then use the right version.\n\ 831 e:\tDiscard both versions then edit a new one.\n\ 832 l or 1:\tUse the left version.\n\ 833 r or 2:\tUse the right version.\n\ 834 s:\tSilently include common lines.\n\ 835 v:\tVerbosely include common lines.\n\ 836 q:\tQuit.\n\ 837 ")); 838 } 839 840 static int 841 skip_white (void) 842 { 843 int c; 844 for (;;) 845 { 846 c = getchar (); 847 if (! isspace (c) || c == '\n') 848 break; 849 checksigs (); 850 } 851 if (ferror (stdin)) 852 perror_fatal (_("read failed")); 853 return c; 854 } 855 856 static void 857 flush_line (void) 858 { 859 int c; 860 while ((c = getchar ()) != '\n' && c != EOF) 861 continue; 862 if (ferror (stdin)) 863 perror_fatal (_("read failed")); 864 } 865 866 867 /* interpret an edit command */ 868 static bool 869 edit (struct line_filter *left, char const *lname, lin lline, lin llen, 870 struct line_filter *right, char const *rname, lin rline, lin rlen, 871 FILE *outfile) 872 { 873 for (;;) 874 { 875 int cmd0 IF_LINT (= 0); 876 int cmd1 IF_LINT (= 0); 877 bool gotcmd = false; 878 879 while (! gotcmd) 880 { 881 if (putchar ('%') != '%') 882 perror_fatal (_("write failed")); 883 ck_fflush (stdout); 884 885 cmd0 = skip_white (); 886 switch (cmd0) 887 { 888 case '1': case '2': case 'l': case 'r': 889 case 's': case 'v': case 'q': 890 if (skip_white () != '\n') 891 { 892 give_help (); 893 flush_line (); 894 continue; 895 } 896 gotcmd = true; 897 break; 898 899 case 'e': 900 cmd1 = skip_white (); 901 switch (cmd1) 902 { 903 case '1': case '2': case 'b': case 'd': case 'l': case 'r': 904 if (skip_white () != '\n') 905 { 906 give_help (); 907 flush_line (); 908 continue; 909 } 910 gotcmd = true; 911 break; 912 case '\n': 913 gotcmd = true; 914 break; 915 default: 916 give_help (); 917 flush_line (); 918 continue; 919 } 920 break; 921 922 case EOF: 923 if (feof (stdin)) 924 { 925 gotcmd = true; 926 cmd0 = 'q'; 927 break; 928 } 929 /* Fall through. */ 930 default: 931 flush_line (); 932 /* Fall through. */ 933 case '\n': 934 give_help (); 935 continue; 936 } 937 } 938 939 switch (cmd0) 940 { 941 case '1': case 'l': 942 lf_copy (left, llen, outfile); 943 lf_skip (right, rlen); 944 return true; 945 case '2': case 'r': 946 lf_copy (right, rlen, outfile); 947 lf_skip (left, llen); 948 return true; 949 case 's': 950 suppress_common_lines = true; 951 break; 952 case 'v': 953 suppress_common_lines = false; 954 break; 955 case 'q': 956 return false; 957 case 'e': 958 { 959 int fd; 960 961 if (tmpname) 962 tmp = fopen (tmpname, "w"); 963 else 964 { 965 if ((fd = temporary_file ()) < 0) 966 perror_fatal ("mkstemp"); 967 tmp = fdopen (fd, "w"); 968 } 969 970 if (! tmp) 971 perror_fatal (tmpname); 972 973 switch (cmd1) 974 { 975 case 'd': 976 if (llen) 977 { 978 if (llen == 1) 979 fprintf (tmp, "--- %s %ld\n", lname, (long int) lline); 980 else 981 fprintf (tmp, "--- %s %ld,%ld\n", lname, 982 (long int) lline, 983 (long int) (lline + llen - 1)); 984 } 985 /* Fall through. */ 986 case '1': case 'b': case 'l': 987 lf_copy (left, llen, tmp); 988 break; 989 990 default: 991 lf_skip (left, llen); 992 break; 993 } 994 995 switch (cmd1) 996 { 997 case 'd': 998 if (rlen) 999 { 1000 if (rlen == 1) 1001 fprintf (tmp, "+++ %s %ld\n", rname, (long int) rline); 1002 else 1003 fprintf (tmp, "+++ %s %ld,%ld\n", rname, 1004 (long int) rline, 1005 (long int) (rline + rlen - 1)); 1006 } 1007 /* Fall through. */ 1008 case '2': case 'b': case 'r': 1009 lf_copy (right, rlen, tmp); 1010 break; 1011 1012 default: 1013 lf_skip (right, rlen); 1014 break; 1015 } 1016 1017 ck_fclose (tmp); 1018 1019 { 1020 int wstatus; 1021 int werrno = 0; 1022 ignore_SIGINT = true; 1023 checksigs (); 1024 1025 { 1026 #if ! HAVE_WORKING_FORK 1027 char *command = 1028 xmalloc (shell_quote_length (editor_program) 1029 + 1 + strlen (tmpname) + 1); 1030 sprintf (shell_quote_copy (command, editor_program), 1031 " %s", tmpname); 1032 wstatus = system (command); 1033 if (wstatus == -1) 1034 werrno = errno; 1035 free (command); 1036 #else 1037 pid_t pid; 1038 1039 pid = fork (); 1040 if (pid == 0) 1041 { 1042 char const *argv[3]; 1043 int i = 0; 1044 1045 argv[i++] = editor_program; 1046 argv[i++] = tmpname; 1047 argv[i] = 0; 1048 1049 execvp (editor_program, (char **) argv); 1050 _exit (errno == ENOENT ? 127 : 126); 1051 } 1052 1053 if (pid < 0) 1054 perror_fatal ("fork"); 1055 1056 while (waitpid (pid, &wstatus, 0) < 0) 1057 if (errno == EINTR) 1058 checksigs (); 1059 else 1060 perror_fatal ("waitpid"); 1061 #endif 1062 } 1063 1064 ignore_SIGINT = false; 1065 check_child_status (werrno, wstatus, EXIT_SUCCESS, 1066 editor_program); 1067 } 1068 1069 { 1070 char buf[SDIFF_BUFSIZE]; 1071 size_t size; 1072 tmp = ck_fopen (tmpname, "r"); 1073 while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0) 1074 { 1075 checksigs (); 1076 ck_fwrite (buf, size, outfile); 1077 } 1078 ck_fclose (tmp); 1079 } 1080 return true; 1081 } 1082 default: 1083 give_help (); 1084 break; 1085 } 1086 } 1087 } 1088 1089 /* Alternately reveal bursts of diff output and handle user commands. */ 1090 static bool 1091 interact (struct line_filter *diff, 1092 struct line_filter *left, char const *lname, 1093 struct line_filter *right, char const *rname, 1094 FILE *outfile) 1095 { 1096 lin lline = 1, rline = 1; 1097 1098 for (;;) 1099 { 1100 char diff_help[256]; 1101 int snarfed = lf_snarf (diff, diff_help, sizeof diff_help); 1102 1103 if (snarfed <= 0) 1104 return snarfed != 0; 1105 1106 checksigs (); 1107 1108 if (diff_help[0] == ' ') 1109 puts (diff_help + 1); 1110 else 1111 { 1112 char *numend; 1113 uintmax_t val; 1114 lin llen, rlen, lenmax; 1115 errno = 0; 1116 llen = val = strtoumax (diff_help + 1, &numend, 10); 1117 if (llen < 0 || llen != val || errno || *numend != ',') 1118 fatal (diff_help); 1119 rlen = val = strtoumax (numend + 1, &numend, 10); 1120 if (rlen < 0 || rlen != val || errno || *numend) 1121 fatal (diff_help); 1122 1123 lenmax = MAX (llen, rlen); 1124 1125 switch (diff_help[0]) 1126 { 1127 case 'i': 1128 if (suppress_common_lines) 1129 lf_skip (diff, lenmax); 1130 else 1131 lf_copy (diff, lenmax, stdout); 1132 1133 lf_copy (left, llen, outfile); 1134 lf_skip (right, rlen); 1135 break; 1136 1137 case 'c': 1138 lf_copy (diff, lenmax, stdout); 1139 if (! edit (left, lname, lline, llen, 1140 right, rname, rline, rlen, 1141 outfile)) 1142 return false; 1143 break; 1144 1145 default: 1146 fatal (diff_help); 1147 } 1148 1149 lline += llen; 1150 rline += rlen; 1151 } 1152 } 1153 } 1154 1155 /* Return true if DIR is an existing directory. */ 1156 static bool 1157 diraccess (char const *dir) 1158 { 1159 struct stat buf; 1160 return stat (dir, &buf) == 0 && S_ISDIR (buf.st_mode); 1161 } 1162 1163 #ifndef P_tmpdir 1164 # define P_tmpdir "/tmp" 1165 #endif 1166 #ifndef TMPDIR_ENV 1167 # define TMPDIR_ENV "TMPDIR" 1168 #endif 1169 1170 /* Open a temporary file and return its file descriptor. Put into 1171 tmpname the address of a newly allocated buffer that holds the 1172 file's name. Use the prefix "sdiff". */ 1173 static int 1174 temporary_file (void) 1175 { 1176 char const *tmpdir = getenv (TMPDIR_ENV); 1177 char const *dir = tmpdir ? tmpdir : P_tmpdir; 1178 char *buf = xmalloc (strlen (dir) + 1 + 5 + 6 + 1); 1179 int fd; 1180 int e; 1181 sigset_t procmask; 1182 sigset_t blocked; 1183 sprintf (buf, "%s/sdiffXXXXXX", dir); 1184 sigemptyset (&blocked); 1185 sigaddset (&blocked, SIGINT); 1186 sigprocmask (SIG_BLOCK, &blocked, &procmask); 1187 fd = mkstemp (buf); 1188 e = errno; 1189 if (0 <= fd) 1190 tmpname = buf; 1191 sigprocmask (SIG_SETMASK, &procmask, 0); 1192 errno = e; 1193 return fd; 1194 } 1195