1 /* GNU sdiff - side-by-side merge of file differences
3 Copyright (C) 1992-1996, 1998, 2001-2002, 2004, 2006-2007, 2009-2013,
4 2015-2025 Free Software Foundation, Inc.
6 This file is part of GNU DIFF.
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.
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.
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/>. */
25 #include <unlocked-io.h>
33 #include <file-type.h>
37 #include <system-quote.h>
38 #include <version-etc.h>
42 /* The official name of this program (e.g., no 'g' prefix). */
43 static char const PROGRAM_NAME
[] = "sdiff";
48 /* Size of chunks read from files which must be parsed into lines. */
49 enum { SDIFF_BUFSIZE
= 65536 };
51 static char const *editor_program
= DEFAULT_EDITOR_PROGRAM
;
52 static char const **diffargv
;
54 static char *volatile tmpname
;
58 static pid_t
volatile diffpid
;
63 static void catchsig (int);
64 static bool edit (struct line_filter
*, char const *, lin
, lin
, struct line_filter
*, char const *, lin
, lin
, FILE *);
65 static bool interact (struct line_filter
*, struct line_filter
*, char const *, struct line_filter
*, char const *, FILE *);
66 static void checksigs (void);
67 static void diffarg (char const *);
68 static _Noreturn
void fatal (char const *);
69 static _Noreturn
void perror_fatal (char const *);
70 static void trapsigs (void);
71 static void untrapsig (int);
73 static int const sigs
[] = {
96 NUM_SIGS
= sizeof sigs
/ sizeof *sigs
,
97 handler_index_of_SIGINT
= NUM_SIGS
- 1
100 typedef void (*sighandler
) (int);
101 static void signal_handler (int, sighandler
);
104 /* Prefer 'sigaction' if available, since 'signal' can lose signals. */
105 static struct sigaction initial_action
[NUM_SIGS
];
107 initial_handler (int i
)
109 return initial_action
[i
].sa_handler
;
112 static sighandler initial_action
[NUM_SIGS
];
114 initial_handler (int i
)
116 return initial_action
[i
];
120 static bool diraccess (char const *);
121 static int temporary_file (void);
125 /* Name of output file if -o specified. */
126 static char const *output
;
128 /* Do not print common lines. */
129 static bool suppress_common_lines
;
131 /* Value for the long option that does not have single-letter equivalents. */
134 DIFF_PROGRAM_OPTION
= CHAR_MAX
+ 1,
136 STRIP_TRAILING_CR_OPTION
,
140 static char const shortopts
[] = "abBdEHiI:lo:stvw:WZ";
141 static struct option
const longopts
[] =
143 {"diff-program", 1, 0, DIFF_PROGRAM_OPTION
},
144 {"expand-tabs", 0, 0, 't'},
145 {"help", 0, 0, HELP_OPTION
},
146 {"ignore-all-space", 0, 0, 'W'}, /* swap W and w for historical reasons */
147 {"ignore-blank-lines", 0, 0, 'B'},
148 {"ignore-case", 0, 0, 'i'},
149 {"ignore-matching-lines", 1, 0, 'I'},
150 {"ignore-space-change", 0, 0, 'b'},
151 {"ignore-tab-expansion", 0, 0, 'E'},
152 {"ignore-trailing-space", 0, 0, 'Z'},
153 {"left-column", 0, 0, 'l'},
154 {"minimal", 0, 0, 'd'},
155 {"output", 1, 0, 'o'},
156 {"speed-large-files", 0, 0, 'H'},
157 {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION
},
158 {"suppress-common-lines", 0, 0, 's'},
159 {"tabsize", 1, 0, TABSIZE_OPTION
},
161 {"version", 0, 0, 'v'},
162 {"width", 1, 0, 'w'},
170 fatal ("write failed");
171 else if (fclose (stdout
) != 0)
172 perror_fatal (_("standard output"));
175 static char const *const option_help_msgid
[] = {
176 N_("-o, --output=FILE operate interactively, sending output to FILE"),
178 N_("-i, --ignore-case consider upper- and lower-case to be the same"),
179 N_("-E, --ignore-tab-expansion ignore changes due to tab expansion"),
180 N_("-Z, --ignore-trailing-space ignore white space at line end"),
181 N_("-b, --ignore-space-change ignore changes in the amount of white space"),
182 N_("-W, --ignore-all-space ignore all white space"),
183 N_("-B, --ignore-blank-lines ignore changes whose lines are all blank"),
184 N_("-I, --ignore-matching-lines=RE ignore changes all whose lines match RE"),
185 N_(" --strip-trailing-cr strip trailing carriage return on input"),
186 N_("-a, --text treat all files as text"),
188 N_("-w, --width=NUM output at most NUM (default 130) print columns"),
189 N_("-l, --left-column output only the left column of common lines"),
190 N_("-s, --suppress-common-lines do not output common lines"),
192 N_("-t, --expand-tabs expand tabs to spaces in output"),
193 N_(" --tabsize=NUM tab stops at every NUM (default 8) print columns"),
195 N_("-d, --minimal try hard to find a smaller set of changes"),
196 N_("-H, --speed-large-files assume large files, many scattered small changes"),
197 N_(" --diff-program=PROGRAM use PROGRAM to compare files"),
199 N_(" --help display this help and exit"),
200 N_("-v, --version output version information and exit"),
207 printf (_("Usage: %s [OPTION]... FILE1 FILE2\n"), squote (0, program_name
));
209 _("Side-by-side merge of differences between FILE1 and FILE2."));
212 Mandatory arguments to long options are mandatory for short options too.\n\
214 for (char const *const *p
= option_help_msgid
; *p
; p
++)
216 printf (" %s\n", _(*p
));
219 printf ("\n%s\n%s\n",
220 _("If a FILE is '-', read standard input."),
221 _("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."));
222 emit_bug_reporting_address ();
225 /* Clean up after a signal or other failure. This function is
226 async-signal-safe. */
228 cleanup (_GL_UNUSED
int signo
)
230 #if HAVE_WORKING_FORK
232 kill (diffpid
, SIGPIPE
);
238 static _Noreturn
void
248 fatal (char const *msgid
)
250 error (0, 0, "%s", _(msgid
));
255 perror_fatal (char const *msg
)
259 error (0, e
, "%s", msg
);
264 check_child_status (int werrno
, int wstatus
, int max_ok_status
,
265 char const *subsidiary_program
)
267 int status
= (! werrno
&& WIFEXITED (wstatus
)
268 ? WEXITSTATUS (wstatus
)
271 if (max_ok_status
< status
)
275 ? "subsidiary program %s could not be invoked"
277 ? "subsidiary program %s not found"
279 ? "subsidiary program %s failed"
280 : "subsidiary program %s failed (exit status %d)"),
281 quote (subsidiary_program
), status
);
287 ck_fopen (char const *fname
, char const *type
)
289 FILE *r
= fopen (fname
, type
);
291 perror_fatal (squote (0, fname
));
299 perror_fatal ("fclose");
303 ck_fread (char *buf
, idx_t size
, FILE *f
)
305 idx_t r
= fread (buf
, sizeof (char), size
, f
);
306 if (r
== 0 && ferror (f
))
307 perror_fatal (_("read failed"));
312 ck_fwrite (char const *buf
, idx_t size
, FILE *f
)
314 if (fwrite (buf
, sizeof (char), size
, f
) != size
)
315 perror_fatal (_("write failed"));
322 perror_fatal (_("write failed"));
326 expand_name (char *name
, bool is_dir
, char const *other_name
)
328 if (STREQ (name
, "-"))
329 fatal ("cannot interactively merge standard input");
334 /* Yield NAME/BASE, where BASE is OTHER_NAME's basename. */
335 char const *base
= last_component (other_name
);
336 idx_t namelen
= strlen (name
), baselen
= base_len (base
);
337 bool insert_slash
= *last_component (name
) && name
[namelen
- 1] != '/';
338 char *r
= ximalloc (namelen
+ insert_slash
+ baselen
+ 1);
339 char *p
= stpcpy (r
, name
);
341 p
= mempcpy (p
+ insert_slash
, base
, baselen
);
355 lf_init (struct line_filter
*lf
, FILE *infile
)
358 lf
->bufpos
= lf
->buffer
= lf
->buflim
= ximalloc (SDIFF_BUFSIZE
+ 1);
359 lf
->buflim
[0] = '\n';
362 /* Fill an exhausted line_filter buffer from its INFILE */
364 lf_refill (struct line_filter
*lf
)
366 idx_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';
374 /* Advance LINES on LF's infile, copying lines to OUTFILE */
376 lf_copy (struct line_filter
*lf
, lin lines
, FILE *outfile
)
378 char *start
= lf
->bufpos
;
382 lf
->bufpos
= rawmemchr (lf
->bufpos
, '\n');
383 if (lf
->bufpos
== lf
->buflim
)
385 ck_fwrite (start
, lf
->buflim
- start
, outfile
);
386 if (! lf_refill (lf
))
397 ck_fwrite (start
, lf
->bufpos
- start
, outfile
);
400 /* Advance LINES on LF's infile without doing output */
402 lf_skip (struct line_filter
*lf
, lin lines
)
406 lf
->bufpos
= rawmemchr (lf
->bufpos
, '\n');
407 if (lf
->bufpos
== lf
->buflim
)
409 if (! lf_refill (lf
))
420 /* Snarf a line into a buffer. Return EOF if EOF, 0 if error, 1 if OK. */
422 lf_snarf (struct line_filter
*lf
, char *buffer
, idx_t bufsize
)
426 char *start
= lf
->bufpos
;
427 char *next
= rawmemchr (start
, '\n');
428 idx_t s
= next
- start
;
431 buffer
= mempcpy (buffer
, start
, s
);
433 if (next
< lf
->buflim
)
436 lf
->bufpos
= next
+ 1;
439 if (! lf_refill (lf
))
445 main (int argc
, char *argv
[])
447 exit_failure
= EXIT_TROUBLE
;
448 initialize_main (&argc
, &argv
);
449 set_program_name (argv
[0]);
450 setlocale (LC_ALL
, "");
451 bindtextdomain (PACKAGE
, LOCALEDIR
);
452 textdomain (PACKAGE
);
453 c_stack_action (cleanup
);
456 char const *prog
= getenv ("EDITOR");
458 editor_program
= prog
;
460 diffarg (DEFAULT_DIFF_PROGRAM
);
462 /* Parse command line options. */
465 0 <= (c
= getopt_long (argc
, argv
, shortopts
, longopts
, 0)); )
502 diffarg ("--left-column");
510 suppress_common_lines
= true;
518 version_etc (stdout
, PROGRAM_NAME
, PACKAGE_NAME
, Version
,
536 case DIFF_PROGRAM_OPTION
:
537 diffargv
[0] = optarg
;
545 case STRIP_TRAILING_CR_OPTION
:
546 diffarg ("--strip-trailing-cr");
550 diffarg ("--tabsize");
555 try_help (nullptr, nullptr);
558 if (argc
- optind
!= 2)
560 if (argc
- optind
< 2)
561 try_help ("missing operand after %s", quote (argv
[argc
- 1]));
563 try_help ("extra operand %s", quote (argv
[optind
+ 2]));
568 /* easy case: diff does everything for us */
569 if (suppress_common_lines
)
570 diffarg ("--suppress-common-lines");
573 diffarg (argv
[optind
]);
574 diffarg (argv
[optind
+ 1]);
576 execvp (diffargv
[0], (char **) diffargv
);
577 perror_fatal (squote (0, diffargv
[0]));
581 bool leftdir
= diraccess (argv
[optind
]);
582 bool rightdir
= diraccess (argv
[optind
+ 1]);
584 if (leftdir
& rightdir
)
585 fatal ("both files to be compared are directories");
588 = expand_name (argv
[optind
], leftdir
, argv
[optind
+ 1]);
590 = expand_name (argv
[optind
+ 1], rightdir
, argv
[optind
]);
591 FILE *left
= ck_fopen (lname
, "re");
592 FILE *right
= ck_fopen (rname
, "re");
593 FILE *out
= ck_fopen (output
, "we");
595 diffarg ("--sdiff-merge-assist");
597 diffarg (argv
[optind
]);
598 diffarg (argv
[optind
+ 1]);
604 #if ! HAVE_WORKING_FORK
606 char *command
= system_quote_argv (SCI_SYSTEM
, (char **) diffargv
);
608 diffout
= popen (command
, "r");
610 perror_fatal (command
);
617 if (pipe (diff_fds
) != 0)
618 perror_fatal ("pipe");
622 perror_fatal ("fork");
625 /* Alter the child's SIGINT and SIGPIPE handlers;
626 this may munge the parent.
627 The child ignores SIGINT in case the user interrupts the editor.
628 The child does not ignore SIGPIPE, even if the parent does. */
629 if (initial_handler (handler_index_of_SIGINT
) != SIG_IGN
)
630 signal_handler (SIGINT
, SIG_IGN
);
631 signal_handler (SIGPIPE
, SIG_DFL
);
633 if (diff_fds
[1] != STDOUT_FILENO
)
635 dup2 (diff_fds
[1], STDOUT_FILENO
);
639 execvp (diffargv
[0], (char **) diffargv
);
640 _exit (errno
== ENOENT
? 127 : 126);
644 diffout
= fdopen (diff_fds
[0], "r");
646 perror_fatal ("fdopen");
650 struct line_filter lfilt
, rfilt
, diff_filt
;
651 lf_init (&diff_filt
, diffout
);
652 lf_init (&lfilt
, left
);
653 lf_init (&rfilt
, right
);
656 = interact (&diff_filt
, &lfilt
, lname
, &rfilt
, rname
, out
);
666 #if ! HAVE_WORKING_FORK
667 wstatus
= pclose (diffout
);
672 while (waitpid (diffpid
, &wstatus
, 0) < 0)
676 perror_fatal ("waitpid");
689 check_child_status (werrno
, wstatus
, EXIT_FAILURE
, diffargv
[0]);
692 exit (WEXITSTATUS (wstatus
));
695 return EXIT_SUCCESS
; /* Fool '-Wall'. */
699 diffarg (char const *a
)
701 static idx_t diffargs
, diffarglim
;
703 if (diffargs
== diffarglim
)
704 diffargv
= xpalloc (diffargv
, &diffarglim
, 1, -1, sizeof *diffargv
);
705 diffargv
[diffargs
++] = a
;
708 /* Signal handling */
710 static bool volatile ignore_SIGINT
;
711 static int volatile signal_received
;
712 static bool sigs_trapped
;
720 if (! (s
== SIGINT
&& ignore_SIGINT
))
725 static struct sigaction catchaction
;
729 signal_handler (int sig
, sighandler handler
)
732 catchaction
.sa_handler
= handler
;
733 sigaction (sig
, &catchaction
, 0);
735 signal (sig
, handler
);
743 catchaction
.sa_flags
= SA_RESTART
;
744 sigemptyset (&catchaction
.sa_mask
);
745 for (int i
= 0; i
< NUM_SIGS
; i
++)
746 sigaddset (&catchaction
.sa_mask
, sigs
[i
]);
749 for (int i
= 0; i
< NUM_SIGS
; i
++)
752 sigaction (sigs
[i
], nullptr, &initial_action
[i
]);
754 initial_action
[i
] = signal (sigs
[i
], SIG_IGN
);
756 if (initial_handler (i
) != SIG_IGN
)
757 signal_handler (sigs
[i
], catchsig
);
761 /* System V fork+wait does not work if SIGCHLD is ignored. */
762 signal (SIGCHLD
, SIG_DFL
);
768 /* Untrap signal S, or all trapped signals if S is zero. */
773 for (int i
= 0; i
< NUM_SIGS
; i
++)
774 if ((! s
|| sigs
[i
] == s
) && initial_handler (i
) != SIG_IGN
)
777 sigaction (sigs
[i
], &initial_action
[i
], nullptr);
779 signal (sigs
[i
], initial_action
[i
]);
784 /* Exit if a signal has been received. */
788 int s
= signal_received
;
793 /* Yield an exit status indicating that a signal was received. */
797 /* That didn't work, so exit with error status. */
805 fprintf (stderr
, "%s", _("\
806 ed:\tEdit then use both versions, each decorated with a header.\n\
807 eb:\tEdit then use both versions.\n\
808 el or e1:\tEdit then use the left version.\n\
809 er or e2:\tEdit then use the right version.\n\
810 e:\tDiscard both versions then edit a new one.\n\
811 l or 1:\tUse the left version.\n\
812 r or 2:\tUse the right version.\n\
813 s:\tSilently include common lines.\n\
814 v:\tVerbosely include common lines.\n\
826 if (! c_isspace (c
) || c
== '\n')
831 perror_fatal (_("read failed"));
838 for (int c
; (c
= getchar ()) != '\n' && c
!= EOF
; )
841 perror_fatal (_("read failed"));
844 /* Suppress gcc's "...may be used before initialized" warnings,
845 generated by GCC versions up to at least GCC 14.0.0 20231227. */
846 #if __GNUC__ + (__GNUC_MINOR__ >= 7) > 4
847 # pragma GCC diagnostic push
848 # pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
851 /* interpret an edit command */
853 edit (struct line_filter
*left
, char const *lname
, lin lline
, lin llen
,
854 struct line_filter
*right
, char const *rname
, lin rline
, lin rlen
,
865 if (putchar ('%') != '%')
866 perror_fatal (_("write failed"));
869 cmd0
= skip_white ();
872 case '1': case '2': case 'l': case 'r':
873 case 's': case 'v': case 'q':
874 if (skip_white () != '\n')
884 cmd1
= skip_white ();
887 case '1': case '2': case 'b': case 'd': case 'l': case 'r':
888 if (skip_white () != '\n')
926 lf_copy (left
, llen
, outfile
);
927 lf_skip (right
, rlen
);
930 lf_copy (right
, rlen
, outfile
);
931 lf_skip (left
, llen
);
934 suppress_common_lines
= true;
937 suppress_common_lines
= false;
944 tmp
= fopen (tmpname
, "we");
947 int fd
= temporary_file ();
949 perror_fatal ("mkstemp");
950 tmp
= fdopen (fd
, "w");
954 perror_fatal (squote (0, tmpname
));
962 fprintf (tmp
, "--- %s %"pI
"d\n", lname
, lline
);
964 fprintf (tmp
, "--- %s %"pI
"d,%"pI
"d\n", lname
, lline
,
968 case '1': case 'b': case 'l':
969 lf_copy (left
, llen
, tmp
);
973 lf_skip (left
, llen
);
983 fprintf (tmp
, "+++ %s %"pI
"d\n", rname
, rline
);
985 fprintf (tmp
, "+++ %s %"pI
"d,%"pI
"d\n", rname
, rline
,
989 case '2': case 'b': case 'r':
990 lf_copy (right
, rlen
, tmp
);
994 lf_skip (right
, rlen
);
1000 ignore_SIGINT
= true;
1002 char *argv
[] = { (char *) editor_program
, tmpname
, nullptr };
1005 #if ! HAVE_WORKING_FORK
1006 char *command
= system_quote_argv (SCI_SYSTEM
, argv
);
1007 wstatus
= system (command
);
1012 pid_t pid
= fork ();
1015 execvp (editor_program
, argv
);
1016 _exit (errno
== ENOENT
? 127 : 126);
1020 perror_fatal ("fork");
1022 while (waitpid (pid
, &wstatus
, 0) < 0)
1026 perror_fatal ("waitpid");
1028 ignore_SIGINT
= false;
1029 check_child_status (werrno
, wstatus
, EXIT_SUCCESS
,
1032 char buf
[SDIFF_BUFSIZE
];
1033 tmp
= ck_fopen (tmpname
, "re");
1035 (size
= ck_fread (buf
, SDIFF_BUFSIZE
, tmp
)) != 0; )
1038 ck_fwrite (buf
, size
, outfile
);
1050 #if __GNUC__ + (__GNUC_MINOR__ >= 7) > 4
1051 # pragma GCC diagnostic pop
1054 /* Alternately reveal bursts of diff output and handle user commands. */
1056 interact (struct line_filter
*diff
,
1057 struct line_filter
*left
, char const *lname
,
1058 struct line_filter
*right
, char const *rname
,
1061 lin lline
= 1, rline
= 1;
1065 char diff_help
[256];
1066 int snarfed
= lf_snarf (diff
, diff_help
, sizeof diff_help
);
1069 return snarfed
!= 0;
1073 if (diff_help
[0] == ' ')
1074 puts (diff_help
+ 1);
1079 intmax_t val
= strtoimax (diff_help
+ 1, &numend
, 10);
1080 if (! (0 <= val
&& val
<= LIN_MAX
) || errno
|| *numend
!= ',')
1083 val
= strtoimax (numend
+ 1, &numend
, 10);
1084 if (! (0 <= val
&& val
<= LIN_MAX
) || errno
|| *numend
)
1088 lin lenmax
= MAX (llen
, rlen
);
1090 switch (diff_help
[0])
1093 if (suppress_common_lines
)
1094 lf_skip (diff
, lenmax
);
1096 lf_copy (diff
, lenmax
, stdout
);
1098 lf_copy (left
, llen
, outfile
);
1099 lf_skip (right
, rlen
);
1103 lf_copy (diff
, lenmax
, stdout
);
1104 if (! edit (left
, lname
, lline
, llen
,
1105 right
, rname
, rline
, rlen
,
1120 /* Return true if DIR is an existing directory. */
1122 diraccess (char const *dir
)
1125 return stat (dir
, &buf
) == 0 && S_ISDIR (buf
.st_mode
);
1129 # define P_tmpdir "/tmp"
1132 # define TMPDIR_ENV "TMPDIR"
1135 /* Open a temporary file and return its file descriptor. Put into
1136 tmpname the address of a newly allocated buffer that holds the
1137 file's name. Use the prefix "sdiff". */
1139 temporary_file (void)
1141 char const *tmpdir
= getenv (TMPDIR_ENV
);
1142 char const *dir
= tmpdir
? tmpdir
: P_tmpdir
;
1143 char *buf
= xmalloc (strlen (dir
) + 1 + 5 + 6 + 1);
1144 strcpy (stpcpy (buf
, dir
), "/sdiffXXXXXX");
1145 int fd
= mkstemp (buf
);