/* sdiff - side-by-side merge of file differences Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 2001, 2002 Free Software Foundation, Inc. This file is part of GNU DIFF. GNU DIFF is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU DIFF is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" #include #include #include #include #include #include #include #include #include static char const authorship_msgid[] = N_("Written by Thomas Lord."); static char const copyright_string[] = "Copyright (C) 2002 Free Software Foundation, Inc."; extern char const version_string[]; /* Size of chunks read from files which must be parsed into lines. */ #define SDIFF_BUFSIZE ((size_t) 65536) char *program_name; static char const *editor_program = DEFAULT_EDITOR_PROGRAM; static char const **diffargv; static char * volatile tmpname; static FILE *tmp; #if HAVE_WORKING_FORK || HAVE_WORKING_VFORK static pid_t volatile diffpid; #endif struct line_filter; static RETSIGTYPE catchsig (int); static bool edit (struct line_filter *, char const *, lin, lin, struct line_filter *, char const *, lin, lin, FILE *); static bool interact (struct line_filter *, struct line_filter *, char const *, struct line_filter *, char const *, FILE *); static void checksigs (void); static void diffarg (char const *); static void fatal (char const *) __attribute__((noreturn)); static void perror_fatal (char const *) __attribute__((noreturn)); static void trapsigs (void); static void untrapsig (int); #define NUM_SIGS (sizeof sigs / sizeof *sigs) static int const sigs[] = { #ifdef SIGHUP SIGHUP, #endif #ifdef SIGQUIT SIGQUIT, #endif #ifdef SIGTERM SIGTERM, #endif #ifdef SIGXCPU SIGXCPU, #endif #ifdef SIGXFSZ SIGXFSZ, #endif SIGINT, SIGPIPE }; #define handler_index_of_SIGINT (NUM_SIGS - 2) #define handler_index_of_SIGPIPE (NUM_SIGS - 1) #if HAVE_SIGACTION /* Prefer `sigaction' if available, since `signal' can lose signals. */ static struct sigaction initial_action[NUM_SIGS]; # define initial_handler(i) (initial_action[i].sa_handler) static void signal_handler (int, RETSIGTYPE (*) (int)); #else static RETSIGTYPE (*initial_action[NUM_SIGS]) (); # define initial_handler(i) (initial_action[i]) # define signal_handler(sig, handler) signal (sig, handler) #endif #if ! HAVE_SIGPROCMASK # define sigset_t int # define sigemptyset(s) (*(s) = 0) # ifndef sigmask # define sigmask(sig) (1 << ((sig) - 1)) # endif # define sigaddset(s, sig) (*(s) |= sigmask (sig)) # ifndef SIG_BLOCK # define SIG_BLOCK 0 # endif # ifndef SIG_SETMASK # define SIG_SETMASK (! SIG_BLOCK) # endif # define sigprocmask(how, n, o) \ ((how) == SIG_BLOCK ? *(o) = sigblock (*(n)) : sigsetmask (*(n))) #endif static bool diraccess (char const *); static int temporary_file (void); /* Options: */ /* Name of output file if -o specified. */ static char const *output; /* Do not print common lines. */ static bool suppress_common_lines; /* Value for the long option that does not have single-letter equivalents. */ enum { DIFF_PROGRAM_OPTION = CHAR_MAX + 1, HELP_OPTION, STRIP_TRAILING_CR_OPTION }; static struct option const longopts[] = { {"diff-program", 1, 0, DIFF_PROGRAM_OPTION}, {"expand-tabs", 0, 0, 't'}, {"help", 0, 0, HELP_OPTION}, {"ignore-all-space", 0, 0, 'W'}, /* swap W and w for historical reasons */ {"ignore-blank-lines", 0, 0, 'B'}, {"ignore-case", 0, 0, 'i'}, {"ignore-matching-lines", 1, 0, 'I'}, {"ignore-space-change", 0, 0, 'b'}, {"ignore-tab-expansion", 0, 0, 'E'}, {"left-column", 0, 0, 'l'}, {"minimal", 0, 0, 'd'}, {"output", 1, 0, 'o'}, {"speed-large-files", 0, 0, 'H'}, {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION}, {"suppress-common-lines", 0, 0, 's'}, {"text", 0, 0, 'a'}, {"version", 0, 0, 'v'}, {"width", 1, 0, 'w'}, {0, 0, 0, 0} }; static void try_help (char const *, char const *) __attribute__((noreturn)); static void try_help (char const *reason_msgid, char const *operand) { if (reason_msgid) error (0, 0, _(reason_msgid), operand); error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."), program_name); abort (); } static void check_stdout (void) { if (ferror (stdout)) fatal ("write failed"); else if (fclose (stdout) != 0) perror_fatal (_("standard output")); } static char const * const option_help_msgid[] = { N_("-o FILE --output=FILE Operate interactively, sending output to FILE."), "", N_("-i --ignore-case Consider upper- and lower-case to be the same."), N_("-E --ignore-tab-expansion Ignore changes due to tab expansion."), N_("-b --ignore-space-change Ignore changes in the amount of white space."), N_("-W --ignore-all-space Ignore all white space."), N_("-B --ignore-blank-lines Ignore changes whose lines are all blank."), N_("-I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE."), N_("--strip-trailing-cr Strip trailing carriage return on input."), N_("-a --text Treat all files as text."), "", N_("-w NUM --width=NUM Output at most NUM (default 130) columns per line."), N_("-l --left-column Output only the left column of common lines."), N_("-s --suppress-common-lines Do not output common lines."), "", N_("-t --expand-tabs Expand tabs to spaces in output."), "", N_("-d --minimal Try hard to find a smaller set of changes."), N_("-H --speed-large-files Assume large files and many scattered small changes."), N_("--diff-program=PROGRAM Use PROGRAM to compare files."), "", N_("-v --version Output version info."), N_("--help Output this help."), 0 }; static void usage (void) { char const * const *p; printf (_("Usage: %s [OPTION]... FILE1 FILE2\n"), program_name); printf ("%s\n\n", _("Side-by-side merge of file differences.")); for (p = option_help_msgid; *p; p++) if (**p) printf (" %s\n", _(*p)); else putchar ('\n'); printf ("\n%s\n\n%s\n", _("If a FILE is `-', read standard input."), _("Report bugs to .")); } static void cleanup (void) { #if HAVE_WORKING_FORK || HAVE_WORKING_VFORK if (0 < diffpid) kill (diffpid, SIGPIPE); #endif if (tmpname) unlink (tmpname); } static void exiterr (void) __attribute__((noreturn)); static void exiterr (void) { cleanup (); untrapsig (0); checksigs (); exit (EXIT_TROUBLE); } static void fatal (char const *msgid) { error (0, 0, "%s", _(msgid)); exiterr (); } static void perror_fatal (char const *msg) { int e = errno; checksigs (); error (0, e, "%s", msg); exiterr (); } static void ck_editor_status (int errnum, int status) { if (errnum | status) { char const *failure_msgid = N_("subsidiary program `%s' failed"); if (! errnum && WIFEXITED (status)) switch (WEXITSTATUS (status)) { case 126: failure_msgid = N_("subsidiary program `%s' not executable"); break; case 127: failure_msgid = N_("subsidiary program `%s' not found"); break; } error (0, errnum, _(failure_msgid), editor_program); exiterr (); } } static FILE * ck_fopen (char const *fname, char const *type) { FILE *r = fopen (fname, type); if (! r) perror_fatal (fname); return r; } static void ck_fclose (FILE *f) { if (fclose (f)) perror_fatal ("fclose"); } static size_t ck_fread (char *buf, size_t size, FILE *f) { size_t r = fread (buf, sizeof (char), size, f); if (r == 0 && ferror (f)) perror_fatal (_("read failed")); return r; } static void ck_fwrite (char const *buf, size_t size, FILE *f) { if (fwrite (buf, sizeof (char), size, f) != size) perror_fatal (_("write failed")); } static void ck_fflush (FILE *f) { if (fflush (f) != 0) perror_fatal (_("write failed")); } static char const * expand_name (char *name, bool is_dir, char const *other_name) { if (strcmp (name, "-") == 0) fatal ("cannot interactively merge standard input"); if (! is_dir) return name; else { /* Yield NAME/BASE, where BASE is OTHER_NAME's basename. */ char const *base = base_name (other_name); size_t namelen = strlen (name), baselen = strlen (base); bool insert_slash = *base_name (name) && name[namelen - 1] != '/'; char *r = xmalloc (namelen + insert_slash + baselen + 1); memcpy (r, name, namelen); r[namelen] = '/'; memcpy (r + namelen + insert_slash, base, baselen + 1); return r; } } struct line_filter { FILE *infile; char *bufpos; char *buffer; char *buflim; }; static void lf_init (struct line_filter *lf, FILE *infile) { lf->infile = infile; lf->bufpos = lf->buffer = lf->buflim = xmalloc (SDIFF_BUFSIZE + 1); lf->buflim[0] = '\n'; } /* Fill an exhausted line_filter buffer from its INFILE */ static size_t lf_refill (struct line_filter *lf) { size_t s = ck_fread (lf->buffer, SDIFF_BUFSIZE, lf->infile); lf->bufpos = lf->buffer; lf->buflim = lf->buffer + s; lf->buflim[0] = '\n'; checksigs (); return s; } /* Advance LINES on LF's infile, copying lines to OUTFILE */ static void lf_copy (struct line_filter *lf, lin lines, FILE *outfile) { char *start = lf->bufpos; while (lines) { lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos); if (! lf->bufpos) { ck_fwrite (start, lf->buflim - start, outfile); if (! lf_refill (lf)) return; start = lf->bufpos; } else { --lines; ++lf->bufpos; } } ck_fwrite (start, lf->bufpos - start, outfile); } /* Advance LINES on LF's infile without doing output */ static void lf_skip (struct line_filter *lf, lin lines) { while (lines) { lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos); if (! lf->bufpos) { if (! lf_refill (lf)) break; } else { --lines; ++lf->bufpos; } } } /* Snarf a line into a buffer. Return EOF if EOF, 0 if error, 1 if OK. */ static int lf_snarf (struct line_filter *lf, char *buffer, size_t bufsize) { for (;;) { char *start = lf->bufpos; char *next = (char *) memchr (start, '\n', lf->buflim + 1 - start); size_t s = next - start; if (bufsize <= s) return 0; memcpy (buffer, start, s); if (next < lf->buflim) { buffer[s] = 0; lf->bufpos = next + 1; return 1; } if (! lf_refill (lf)) return s ? 0 : EOF; buffer += s; bufsize -= s; } } int main (int argc, char *argv[]) { int opt; char const *prog; exit_failure = EXIT_TROUBLE; initialize_main (&argc, &argv); program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); c_stack_action (c_stack_die); prog = getenv ("EDITOR"); if (prog) editor_program = prog; diffarg (DEFAULT_DIFF_PROGRAM); /* parse command line args */ while ((opt = getopt_long (argc, argv, "abBdHiI:lo:stvw:W", longopts, 0)) != -1) { switch (opt) { case 'a': diffarg ("-a"); break; case 'b': diffarg ("-b"); break; case 'B': diffarg ("-B"); break; case 'd': diffarg ("-d"); break; case 'E': diffarg ("-E"); break; case 'H': diffarg ("-H"); break; case 'i': diffarg ("-i"); break; case 'I': diffarg ("-I"); diffarg (optarg); break; case 'l': diffarg ("--left-column"); break; case 'o': output = optarg; break; case 's': suppress_common_lines = 1; break; case 't': diffarg ("-t"); break; case 'v': printf ("sdiff %s\n%s\n\n%s\n\n%s\n", version_string, copyright_string, _(free_software_msgid), _(authorship_msgid)); check_stdout (); return EXIT_SUCCESS; case 'w': diffarg ("-W"); diffarg (optarg); break; case 'W': diffarg ("-w"); break; case DIFF_PROGRAM_OPTION: diffargv[0] = optarg; break; case HELP_OPTION: usage (); check_stdout (); return EXIT_SUCCESS; case STRIP_TRAILING_CR_OPTION: diffarg ("--strip-trailing-cr"); break; default: try_help (0, 0); } } if (argc - optind != 2) { if (argc - optind < 2) try_help ("missing operand after `%s'", argv[argc - 1]); else try_help ("extra operand `%s'", argv[optind + 2]); } if (! output) { /* easy case: diff does everything for us */ if (suppress_common_lines) diffarg ("--suppress-common-lines"); diffarg ("-y"); diffarg ("--"); diffarg (argv[optind]); diffarg (argv[optind + 1]); diffarg (0); execvp (diffargv[0], (char **) diffargv); perror_fatal (diffargv[0]); } else { char const *lname, *rname; FILE *left, *right, *out, *diffout; bool interact_ok; struct line_filter lfilt; struct line_filter rfilt; struct line_filter diff_filt; bool leftdir = diraccess (argv[optind]); bool rightdir = diraccess (argv[optind + 1]); if (leftdir & rightdir) fatal ("both files to be compared are directories"); lname = expand_name (argv[optind], leftdir, argv[optind + 1]); left = ck_fopen (lname, "r"); rname = expand_name (argv[optind + 1], rightdir, argv[optind]); right = ck_fopen (rname, "r"); out = ck_fopen (output, "w"); diffarg ("--sdiff-merge-assist"); diffarg ("--"); diffarg (argv[optind]); diffarg (argv[optind + 1]); diffarg (0); trapsigs (); #if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK) { size_t cmdsize = 1; char *p, *command; int i; for (i = 0; diffargv[i]; i++) cmdsize += quote_system_arg (0, diffargv[i]) + 1; command = p = xmalloc (cmdsize); for (i = 0; diffargv[i]; i++) { p += quote_system_arg (p, diffargv[i]); *p++ = ' '; } p[-1] = 0; errno = 0; diffout = popen (command, "r"); if (! diffout) perror_fatal (command); free (command); } #else { int diff_fds[2]; # if HAVE_WORKING_VFORK sigset_t procmask; sigset_t blocked; # endif if (pipe (diff_fds) != 0) perror_fatal ("pipe"); # if HAVE_WORKING_VFORK /* Block SIGINT and SIGPIPE. */ sigemptyset (&blocked); sigaddset (&blocked, SIGINT); sigaddset (&blocked, SIGPIPE); sigprocmask (SIG_BLOCK, &blocked, &procmask); # endif diffpid = vfork (); if (diffpid < 0) perror_fatal ("fork"); if (! diffpid) { /* Alter the child's SIGINT and SIGPIPE handlers; this may munge the parent. The child ignores SIGINT in case the user interrupts the editor. The child does not ignore SIGPIPE, even if the parent does. */ if (initial_handler (handler_index_of_SIGINT) != SIG_IGN) signal_handler (SIGINT, SIG_IGN); signal_handler (SIGPIPE, SIG_DFL); # if HAVE_WORKING_VFORK /* Stop blocking SIGINT and SIGPIPE in the child. */ sigprocmask (SIG_SETMASK, &procmask, 0); # endif close (diff_fds[0]); if (diff_fds[1] != STDOUT_FILENO) { dup2 (diff_fds[1], STDOUT_FILENO); close (diff_fds[1]); } execvp (diffargv[0], (char **) diffargv); _exit (errno == ENOEXEC ? 126 : 127); } # if HAVE_WORKING_VFORK /* Restore the parent's SIGINT and SIGPIPE behavior. */ if (initial_handler (handler_index_of_SIGINT) != SIG_IGN) signal_handler (SIGINT, catchsig); if (initial_handler (handler_index_of_SIGPIPE) != SIG_IGN) signal_handler (SIGPIPE, catchsig); else signal_handler (SIGPIPE, SIG_IGN); /* Stop blocking SIGINT and SIGPIPE in the parent. */ sigprocmask (SIG_SETMASK, &procmask, 0); # endif close (diff_fds[1]); diffout = fdopen (diff_fds[0], "r"); if (! diffout) perror_fatal ("fdopen"); } #endif lf_init (&diff_filt, diffout); lf_init (&lfilt, left); lf_init (&rfilt, right); interact_ok = interact (&diff_filt, &lfilt, lname, &rfilt, rname, out); ck_fclose (left); ck_fclose (right); ck_fclose (out); { int wstatus; int werrno = 0; #if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK) wstatus = pclose (diffout); if (wstatus == -1) werrno = errno; #else ck_fclose (diffout); while (waitpid (diffpid, &wstatus, 0) < 0) if (errno == EINTR) checksigs (); else perror_fatal ("waitpid"); diffpid = 0; #endif if (tmpname) { unlink (tmpname); tmpname = 0; } if (! interact_ok) exiterr (); ck_editor_status (werrno, wstatus); untrapsig (0); checksigs (); exit (WEXITSTATUS (wstatus)); } } return EXIT_SUCCESS; /* Fool `-Wall'. */ } static void diffarg (char const *a) { static size_t diffargs, diffarglim; if (diffargs == diffarglim) { if (! diffarglim) diffarglim = 16; else if (PTRDIFF_MAX / (2 * sizeof *diffargv) <= diffarglim) xalloc_die (); else diffarglim *= 2; diffargv = xrealloc (diffargv, diffarglim * sizeof *diffargv); } diffargv[diffargs++] = a; } /* Signal handling */ static bool volatile ignore_SIGINT; static int volatile signal_received; static bool sigs_trapped; static RETSIGTYPE catchsig (int s) { #if ! HAVE_SIGACTION signal (s, SIG_IGN); #endif if (! (s == SIGINT && ignore_SIGINT)) signal_received = s; } #if HAVE_SIGACTION static struct sigaction catchaction; static void signal_handler (int sig, RETSIGTYPE (*handler) (int)) { catchaction.sa_handler = handler; sigaction (sig, &catchaction, 0); } #endif static void trapsigs (void) { int i; #if HAVE_SIGACTION catchaction.sa_flags = SA_RESTART; sigemptyset (&catchaction.sa_mask); for (i = 0; i < NUM_SIGS; i++) sigaddset (&catchaction.sa_mask, sigs[i]); #endif for (i = 0; i < NUM_SIGS; i++) { #if HAVE_SIGACTION sigaction (sigs[i], 0, &initial_action[i]); #else initial_action[i] = signal (sigs[i], SIG_IGN); #endif if (initial_handler (i) != SIG_IGN) signal_handler (sigs[i], catchsig); } #ifdef SIGCHLD /* System V fork+wait does not work if SIGCHLD is ignored. */ signal (SIGCHLD, SIG_DFL); #endif sigs_trapped = 1; } /* Untrap signal S, or all trapped signals if S is zero. */ static void untrapsig (int s) { int i; if (sigs_trapped) for (i = 0; i < NUM_SIGS; i++) if ((! s || sigs[i] == s) && initial_handler (i) != SIG_IGN) #if HAVE_SIGACTION sigaction (sigs[i], &initial_action[i], 0); #else signal (sigs[i], initial_action[i]); #endif } /* Exit if a signal has been received. */ static void checksigs (void) { int s = signal_received; if (s) { cleanup (); /* Yield an exit status indicating that a signal was received. */ untrapsig (s); kill (getpid (), s); /* That didn't work, so exit with error status. */ exit (EXIT_TROUBLE); } } static void give_help (void) { fprintf (stderr, "%s", _("\ ed:\tEdit then use both versions, each decorated with a header.\n\ eb:\tEdit then use both versions.\n\ el:\tEdit then use the left version.\n\ er:\tEdit then use the right version.\n\ e:\tEdit a new version.\n\ l:\tUse the left version.\n\ r:\tUse the right version.\n\ s:\tSilently include common lines.\n\ v:\tVerbosely include common lines.\n\ q:\tQuit.\n\ ")); } static int skip_white (void) { int c; for (;;) { c = getchar (); if (! ISSPACE (c) || c == '\n') break; checksigs (); } if (ferror (stdin)) perror_fatal (_("read failed")); return c; } static void flush_line (void) { int c; while ((c = getchar ()) != '\n' && c != EOF) continue; if (ferror (stdin)) perror_fatal (_("read failed")); } /* interpret an edit command */ static bool edit (struct line_filter *left, char const *lname, lin lline, lin llen, struct line_filter *right, char const *rname, lin rline, lin rlen, FILE *outfile) { for (;;) { int cmd0, cmd1; bool gotcmd = 0; cmd1 = 0; /* Pacify `gcc -W'. */ while (! gotcmd) { if (putchar ('%') != '%') perror_fatal (_("write failed")); ck_fflush (stdout); cmd0 = skip_white (); switch (cmd0) { case 'l': case 'r': case 's': case 'v': case 'q': if (skip_white () != '\n') { give_help (); flush_line (); continue; } gotcmd = 1; break; case 'e': cmd1 = skip_white (); switch (cmd1) { case 'b': case 'd': case 'l': case 'r': if (skip_white () != '\n') { give_help (); flush_line (); continue; } gotcmd = 1; break; case '\n': gotcmd = 1; break; default: give_help (); flush_line (); continue; } break; case EOF: if (feof (stdin)) { gotcmd = 1; cmd0 = 'q'; break; } /* Fall through. */ default: flush_line (); /* Fall through. */ case '\n': give_help (); continue; } } switch (cmd0) { case 'l': lf_copy (left, llen, outfile); lf_skip (right, rlen); return 1; case 'r': lf_copy (right, rlen, outfile); lf_skip (left, llen); return 1; case 's': suppress_common_lines = 1; break; case 'v': suppress_common_lines = 0; break; case 'q': return 0; case 'e': { int fd; if (tmpname) tmp = fopen (tmpname, "w"); else { if ((fd = temporary_file ()) < 0) perror_fatal ("mkstemp"); tmp = fdopen (fd, "w"); } if (! tmp) perror_fatal (tmpname); switch (cmd1) { case 'd': if (llen) { if (llen == 1) fprintf (tmp, "--- %s %ld\n", lname, (long) lline); else fprintf (tmp, "--- %s %ld,%ld\n", lname, (long) lline, (long) (lline + llen - 1)); } /* Fall through. */ case 'b': case 'l': lf_copy (left, llen, tmp); break; default: lf_skip (left, llen); break; } switch (cmd1) { case 'd': if (rlen) { if (rlen == 1) fprintf (tmp, "+++ %s %ld\n", rname, (long) rline); else fprintf (tmp, "+++ %s %ld,%ld\n", rname, (long) rline, (long) (rline + rlen - 1)); } /* Fall through. */ case 'b': case 'r': lf_copy (right, rlen, tmp); break; default: lf_skip (right, rlen); break; } ck_fclose (tmp); { int wstatus; int werrno = 0; ignore_SIGINT = 1; checksigs (); { #if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK) char *command = xmalloc (quote_system_arg (0, editor_program) + 1 + strlen (tmpname) + 1); sprintf (command + quote_system_arg (command, editor_program), " %s", tmpname); wstatus = system (command); if (wstatus == -1) werrno = errno; free (command); #else pid_t pid; pid = vfork (); if (pid == 0) { char const *argv[3]; int i = 0; argv[i++] = editor_program; argv[i++] = tmpname; argv[i] = 0; execvp (editor_program, (char **) argv); _exit (errno == ENOEXEC ? 126 : 127); } if (pid < 0) perror_fatal ("fork"); while (waitpid (pid, &wstatus, 0) < 0) if (errno == EINTR) checksigs (); else perror_fatal ("waitpid"); #endif } ignore_SIGINT = 0; ck_editor_status (werrno, wstatus); } { char buf[SDIFF_BUFSIZE]; size_t size; tmp = ck_fopen (tmpname, "r"); while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0) { checksigs (); ck_fwrite (buf, size, outfile); } ck_fclose (tmp); } return 1; } default: give_help (); break; } } } /* Alternately reveal bursts of diff output and handle user commands. */ static bool interact (struct line_filter *diff, struct line_filter *left, char const *lname, struct line_filter *right, char const *rname, FILE *outfile) { lin lline = 1, rline = 1; for (;;) { char diff_help[256]; int snarfed = lf_snarf (diff, diff_help, sizeof diff_help); if (snarfed <= 0) return snarfed != 0; checksigs (); if (diff_help[0] == ' ') puts (diff_help + 1); else { char *numend; uintmax_t val; lin llen, rlen, lenmax; errno = 0; llen = val = strtoumax (diff_help + 1, &numend, 10); if (llen < 0 || llen != val || errno || *numend != ',') fatal (diff_help); rlen = val = strtoumax (numend + 1, &numend, 10); if (rlen < 0 || rlen != val || errno || *numend) fatal (diff_help); lenmax = MAX (llen, rlen); switch (diff_help[0]) { case 'i': if (suppress_common_lines) lf_skip (diff, lenmax); else lf_copy (diff, lenmax, stdout); lf_copy (left, llen, outfile); lf_skip (right, rlen); break; case 'c': lf_copy (diff, lenmax, stdout); if (! edit (left, lname, lline, llen, right, rname, rline, rlen, outfile)) return 0; break; default: fatal (diff_help); } lline += llen; rline += rlen; } } } /* Return nonzero if DIR is an existing directory. */ static bool diraccess (char const *dir) { struct stat buf; return stat (dir, &buf) == 0 && S_ISDIR (buf.st_mode); } #ifndef P_tmpdir # define P_tmpdir "/tmp" #endif #ifndef TMPDIR_ENV # define TMPDIR_ENV "TMPDIR" #endif /* Open a temporary file and return its file descriptor. Put into tmpname the address of a newly allocated buffer that holds the file's name. Use the prefix "sdiff". */ static int temporary_file (void) { char const *tmpdir = getenv (TMPDIR_ENV); char const *dir = tmpdir ? tmpdir : P_tmpdir; char *buf = xmalloc (strlen (dir) + 1 + 5 + 6 + 1); int fd; int e; sigset_t procmask; sigset_t blocked; sprintf (buf, "%s/sdiffXXXXXX", dir); sigemptyset (&blocked); sigaddset (&blocked, SIGINT); sigprocmask (SIG_BLOCK, &blocked, &procmask); fd = mkstemp (buf); e = errno; if (0 <= fd) tmpname = buf; sigprocmask (SIG_SETMASK, &procmask, 0); errno = e; return fd; }