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