diff --git a/lib/quotearg.c b/lib/quotearg.c
index f657033..a4fcc2e 100644
--- a/lib/quotearg.c
+++ b/lib/quotearg.c
@@ -886,8 +886,9 @@ quotearg_n_options (int n, char const *arg, size_t argsize,
if (nslots <= n)
{
bool preallocated = (sv == &slotvec0);
-
- if (MIN (INT_MAX, MIN (PTRDIFF_MAX, SIZE_MAX) / sizeof *sv) <= n)
+ int nmax = MIN (INT_MAX, MIN (PTRDIFF_MAX, SIZE_MAX) / sizeof *sv) - 1;
+
+ if (nmax < n)
xalloc_die ();
slotvec = sv = xrealloc (preallocated ? NULL : sv, (n + 1) * sizeof *sv);
diff --git a/lib/quotearg.c.covscan b/lib/quotearg.c.covscan
new file mode 100644
index 0000000..f657033
--- /dev/null
+++ b/lib/quotearg.c.covscan
@@ -0,0 +1,1080 @@
+/* quotearg.c - quote arguments for output
+
+ Copyright (C) 1998-2002, 2004-2017 Free Software Foundation, Inc.
+
+ This program 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 3 of the License, or
+ (at your option) any later version.
+
+ This program 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. If not, see . */
+
+/* Written by Paul Eggert */
+
+/* Without this pragma, gcc 4.7.0 20111124 mistakenly suggests that
+ the quoting_options_from_style function might be candidate for
+ attribute 'pure' */
+#if (__GNUC__ == 4 && 6 <= __GNUC_MINOR__) || 4 < __GNUC__
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
+#endif
+
+#include
+
+#include "quotearg.h"
+#include "quote.h"
+
+#include "minmax.h"
+#include "xalloc.h"
+#include "c-strcaseeq.h"
+#include "localcharset.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+
+#define INT_BITS (sizeof (int) * CHAR_BIT)
+
+#ifndef FALLTHROUGH
+# if __GNUC__ < 7
+# define FALLTHROUGH ((void) 0)
+# else
+# define FALLTHROUGH __attribute__ ((__fallthrough__))
+# endif
+#endif
+
+struct quoting_options
+{
+ /* Basic quoting style. */
+ enum quoting_style style;
+
+ /* Additional flags. Bitwise combination of enum quoting_flags. */
+ int flags;
+
+ /* Quote the characters indicated by this bit vector even if the
+ quoting style would not normally require them to be quoted. */
+ unsigned int quote_these_too[(UCHAR_MAX / INT_BITS) + 1];
+
+ /* The left quote for custom_quoting_style. */
+ char const *left_quote;
+
+ /* The right quote for custom_quoting_style. */
+ char const *right_quote;
+};
+
+/* Names of quoting styles. */
+char const *const quoting_style_args[] =
+{
+ "literal",
+ "shell",
+ "shell-always",
+ "shell-escape",
+ "shell-escape-always",
+ "c",
+ "c-maybe",
+ "escape",
+ "locale",
+ "clocale",
+ 0
+};
+
+/* Correspondences to quoting style names. */
+enum quoting_style const quoting_style_vals[] =
+{
+ literal_quoting_style,
+ shell_quoting_style,
+ shell_always_quoting_style,
+ shell_escape_quoting_style,
+ shell_escape_always_quoting_style,
+ c_quoting_style,
+ c_maybe_quoting_style,
+ escape_quoting_style,
+ locale_quoting_style,
+ clocale_quoting_style
+};
+
+/* The default quoting options. */
+static struct quoting_options default_quoting_options;
+
+/* Allocate a new set of quoting options, with contents initially identical
+ to O if O is not null, or to the default if O is null.
+ It is the caller's responsibility to free the result. */
+struct quoting_options *
+clone_quoting_options (struct quoting_options *o)
+{
+ int e = errno;
+ struct quoting_options *p = xmemdup (o ? o : &default_quoting_options,
+ sizeof *o);
+ errno = e;
+ return p;
+}
+
+/* Get the value of O's quoting style. If O is null, use the default. */
+enum quoting_style
+get_quoting_style (struct quoting_options const *o)
+{
+ return (o ? o : &default_quoting_options)->style;
+}
+
+/* In O (or in the default if O is null),
+ set the value of the quoting style to S. */
+void
+set_quoting_style (struct quoting_options *o, enum quoting_style s)
+{
+ (o ? o : &default_quoting_options)->style = s;
+}
+
+/* In O (or in the default if O is null),
+ set the value of the quoting options for character C to I.
+ Return the old value. Currently, the only values defined for I are
+ 0 (the default) and 1 (which means to quote the character even if
+ it would not otherwise be quoted). */
+int
+set_char_quoting (struct quoting_options *o, char c, int i)
+{
+ unsigned char uc = c;
+ unsigned int *p =
+ (o ? o : &default_quoting_options)->quote_these_too + uc / INT_BITS;
+ int shift = uc % INT_BITS;
+ int r = (*p >> shift) & 1;
+ *p ^= ((i & 1) ^ r) << shift;
+ return r;
+}
+
+/* In O (or in the default if O is null),
+ set the value of the quoting options flag to I, which can be a
+ bitwise combination of enum quoting_flags, or 0 for default
+ behavior. Return the old value. */
+int
+set_quoting_flags (struct quoting_options *o, int i)
+{
+ int r;
+ if (!o)
+ o = &default_quoting_options;
+ r = o->flags;
+ o->flags = i;
+ return r;
+}
+
+void
+set_custom_quoting (struct quoting_options *o,
+ char const *left_quote, char const *right_quote)
+{
+ if (!o)
+ o = &default_quoting_options;
+ o->style = custom_quoting_style;
+ if (!left_quote || !right_quote)
+ abort ();
+ o->left_quote = left_quote;
+ o->right_quote = right_quote;
+}
+
+/* Return quoting options for STYLE, with no extra quoting. */
+static struct quoting_options /* NOT PURE!! */
+quoting_options_from_style (enum quoting_style style)
+{
+ struct quoting_options o = { literal_quoting_style, 0, { 0 }, NULL, NULL };
+ if (style == custom_quoting_style)
+ abort ();
+ o.style = style;
+ return o;
+}
+
+/* MSGID approximates a quotation mark. Return its translation if it
+ has one; otherwise, return either it or "\"", depending on S.
+
+ S is either clocale_quoting_style or locale_quoting_style. */
+static char const *
+gettext_quote (char const *msgid, enum quoting_style s)
+{
+ char const *translation = _(msgid);
+ char const *locale_code;
+
+ if (translation != msgid)
+ return translation;
+
+ /* For UTF-8 and GB-18030, use single quotes U+2018 and U+2019.
+ Here is a list of other locales that include U+2018 and U+2019:
+
+ ISO-8859-7 0xA1 KOI8-T 0x91
+ CP869 0x8B CP874 0x91
+ CP932 0x81 0x65 CP936 0xA1 0xAE
+ CP949 0xA1 0xAE CP950 0xA1 0xA5
+ CP1250 0x91 CP1251 0x91
+ CP1252 0x91 CP1253 0x91
+ CP1254 0x91 CP1255 0x91
+ CP1256 0x91 CP1257 0x91
+ EUC-JP 0xA1 0xC6 EUC-KR 0xA1 0xAE
+ EUC-TW 0xA1 0xE4 BIG5 0xA1 0xA5
+ BIG5-HKSCS 0xA1 0xA5 EUC-CN 0xA1 0xAE
+ GBK 0xA1 0xAE Georgian-PS 0x91
+ PT154 0x91
+
+ None of these is still in wide use; using iconv is overkill. */
+ locale_code = locale_charset ();
+ if (STRCASEEQ (locale_code, "UTF-8", 'U','T','F','-','8',0,0,0,0))
+ return msgid[0] == '`' ? "\xe2\x80\x98": "\xe2\x80\x99";
+ if (STRCASEEQ (locale_code, "GB18030", 'G','B','1','8','0','3','0',0,0))
+ return msgid[0] == '`' ? "\xa1\ae": "\xa1\xaf";
+
+ return (s == clocale_quoting_style ? "\"" : "'");
+}
+
+/* Place into buffer BUFFER (of size BUFFERSIZE) a quoted version of
+ argument ARG (of size ARGSIZE), using QUOTING_STYLE, FLAGS, and
+ QUOTE_THESE_TOO to control quoting.
+ Terminate the output with a null character, and return the written
+ size of the output, not counting the terminating null.
+ If BUFFERSIZE is too small to store the output string, return the
+ value that would have been returned had BUFFERSIZE been large enough.
+ If ARGSIZE is SIZE_MAX, use the string length of the argument for ARGSIZE.
+
+ This function acts like quotearg_buffer (BUFFER, BUFFERSIZE, ARG,
+ ARGSIZE, O), except it breaks O into its component pieces and is
+ not careful about errno. */
+
+static size_t
+quotearg_buffer_restyled (char *buffer, size_t buffersize,
+ char const *arg, size_t argsize,
+ enum quoting_style quoting_style, int flags,
+ unsigned int const *quote_these_too,
+ char const *left_quote,
+ char const *right_quote)
+{
+ size_t i;
+ size_t len = 0;
+ size_t orig_buffersize = 0;
+ char const *quote_string = 0;
+ size_t quote_string_len = 0;
+ bool backslash_escapes = false;
+ bool unibyte_locale = MB_CUR_MAX == 1;
+ bool elide_outer_quotes = (flags & QA_ELIDE_OUTER_QUOTES) != 0;
+ bool pending_shell_escape_end = false;
+ bool encountered_single_quote = false;
+ bool all_c_and_shell_quote_compat = true;
+
+#define STORE(c) \
+ do \
+ { \
+ if (len < buffersize) \
+ buffer[len] = (c); \
+ len++; \
+ } \
+ while (0)
+
+#define START_ESC() \
+ do \
+ { \
+ if (elide_outer_quotes) \
+ goto force_outer_quoting_style; \
+ escaping = true; \
+ if (quoting_style == shell_always_quoting_style \
+ && ! pending_shell_escape_end) \
+ { \
+ STORE ('\''); \
+ STORE ('$'); \
+ STORE ('\''); \
+ pending_shell_escape_end = true; \
+ } \
+ STORE ('\\'); \
+ } \
+ while (0)
+
+#define END_ESC() \
+ do \
+ { \
+ if (pending_shell_escape_end && ! escaping) \
+ { \
+ STORE ('\''); \
+ STORE ('\''); \
+ pending_shell_escape_end = false; \
+ } \
+ } \
+ while (0)
+
+ process_input:
+
+ switch (quoting_style)
+ {
+ case c_maybe_quoting_style:
+ quoting_style = c_quoting_style;
+ elide_outer_quotes = true;
+ FALLTHROUGH;
+ case c_quoting_style:
+ if (!elide_outer_quotes)
+ STORE ('"');
+ backslash_escapes = true;
+ quote_string = "\"";
+ quote_string_len = 1;
+ break;
+
+ case escape_quoting_style:
+ backslash_escapes = true;
+ elide_outer_quotes = false;
+ break;
+
+ case locale_quoting_style:
+ case clocale_quoting_style:
+ case custom_quoting_style:
+ {
+ if (quoting_style != custom_quoting_style)
+ {
+ /* TRANSLATORS:
+ Get translations for open and closing quotation marks.
+ The message catalog should translate "`" to a left
+ quotation mark suitable for the locale, and similarly for
+ "'". For example, a French Unicode local should translate
+ these to U+00AB (LEFT-POINTING DOUBLE ANGLE
+ QUOTATION MARK), and U+00BB (RIGHT-POINTING DOUBLE ANGLE
+ QUOTATION MARK), respectively.
+
+ If the catalog has no translation, we will try to
+ use Unicode U+2018 (LEFT SINGLE QUOTATION MARK) and
+ Unicode U+2019 (RIGHT SINGLE QUOTATION MARK). If the
+ current locale is not Unicode, locale_quoting_style
+ will quote 'like this', and clocale_quoting_style will
+ quote "like this". You should always include translations
+ for "`" and "'" even if U+2018 and U+2019 are appropriate
+ for your locale.
+
+ If you don't know what to put here, please see
+
+ and use glyphs suitable for your language. */
+ left_quote = gettext_quote (N_("`"), quoting_style);
+ right_quote = gettext_quote (N_("'"), quoting_style);
+ }
+ if (!elide_outer_quotes)
+ for (quote_string = left_quote; *quote_string; quote_string++)
+ STORE (*quote_string);
+ backslash_escapes = true;
+ quote_string = right_quote;
+ quote_string_len = strlen (quote_string);
+ }
+ break;
+
+ case shell_escape_quoting_style:
+ backslash_escapes = true;
+ FALLTHROUGH;
+ case shell_quoting_style:
+ elide_outer_quotes = true;
+ FALLTHROUGH;
+ case shell_escape_always_quoting_style:
+ if (!elide_outer_quotes)
+ backslash_escapes = true;
+ FALLTHROUGH;
+ case shell_always_quoting_style:
+ quoting_style = shell_always_quoting_style;
+ if (!elide_outer_quotes)
+ STORE ('\'');
+ quote_string = "'";
+ quote_string_len = 1;
+ break;
+
+ case literal_quoting_style:
+ elide_outer_quotes = false;
+ break;
+
+ default:
+ abort ();
+ }
+
+ for (i = 0; ! (argsize == SIZE_MAX ? arg[i] == '\0' : i == argsize); i++)
+ {
+ unsigned char c;
+ unsigned char esc;
+ bool is_right_quote = false;
+ bool escaping = false;
+ bool c_and_shell_quote_compat = false;
+
+ if (backslash_escapes
+ && quoting_style != shell_always_quoting_style
+ && quote_string_len
+ && (i + quote_string_len
+ <= (argsize == SIZE_MAX && 1 < quote_string_len
+ /* Use strlen only if we must: when argsize is SIZE_MAX,
+ and when the quote string is more than 1 byte long.
+ If we do call strlen, save the result. */
+ ? (argsize = strlen (arg)) : argsize))
+ && memcmp (arg + i, quote_string, quote_string_len) == 0)
+ {
+ if (elide_outer_quotes)
+ goto force_outer_quoting_style;
+ is_right_quote = true;
+ }
+
+ c = arg[i];
+ switch (c)
+ {
+ case '\0':
+ if (backslash_escapes)
+ {
+ START_ESC ();
+ /* If quote_string were to begin with digits, we'd need to
+ test for the end of the arg as well. However, it's
+ hard to imagine any locale that would use digits in
+ quotes, and set_custom_quoting is documented not to
+ accept them. Use only a single \0 with shell-escape
+ as currently digits are not printed within $'...' */
+ if (quoting_style != shell_always_quoting_style
+ && i + 1 < argsize && '0' <= arg[i + 1] && arg[i + 1] <= '9')
+ {
+ STORE ('0');
+ STORE ('0');
+ }
+ c = '0';
+ /* We don't have to worry that this last '0' will be
+ backslash-escaped because, again, quote_string should
+ not start with it and because quote_these_too is
+ documented as not accepting it. */
+ }
+ else if (flags & QA_ELIDE_NULL_BYTES)
+ continue;
+ break;
+
+ case '?':
+ switch (quoting_style)
+ {
+ case shell_always_quoting_style:
+ if (elide_outer_quotes)
+ goto force_outer_quoting_style;
+ break;
+
+ case c_quoting_style:
+ if ((flags & QA_SPLIT_TRIGRAPHS)
+ && i + 2 < argsize && arg[i + 1] == '?')
+ switch (arg[i + 2])
+ {
+ case '!': case '\'':
+ case '(': case ')': case '-': case '/':
+ case '<': case '=': case '>':
+ /* Escape the second '?' in what would otherwise be
+ a trigraph. */
+ if (elide_outer_quotes)
+ goto force_outer_quoting_style;
+ c = arg[i + 2];
+ i += 2;
+ STORE ('?');
+ STORE ('"');
+ STORE ('"');
+ STORE ('?');
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case '\a': esc = 'a'; goto c_escape;
+ case '\b': esc = 'b'; goto c_escape;
+ case '\f': esc = 'f'; goto c_escape;
+ case '\n': esc = 'n'; goto c_and_shell_escape;
+ case '\r': esc = 'r'; goto c_and_shell_escape;
+ case '\t': esc = 't'; goto c_and_shell_escape;
+ case '\v': esc = 'v'; goto c_escape;
+ case '\\': esc = c;
+ /* Never need to escape '\' in shell case. */
+ if (quoting_style == shell_always_quoting_style)
+ {
+ if (elide_outer_quotes)
+ goto force_outer_quoting_style;
+ goto store_c;
+ }
+
+ /* No need to escape the escape if we are trying to elide
+ outer quotes and nothing else is problematic. */
+ if (backslash_escapes && elide_outer_quotes && quote_string_len)
+ goto store_c;
+
+ c_and_shell_escape:
+ if (quoting_style == shell_always_quoting_style
+ && elide_outer_quotes)
+ goto force_outer_quoting_style;
+ c_escape:
+ if (backslash_escapes)
+ {
+ c = esc;
+ goto store_escape;
+ }
+ break;
+
+ case '{': case '}': /* sometimes special if isolated */
+ if (! (argsize == SIZE_MAX ? arg[1] == '\0' : argsize == 1))
+ break;
+ FALLTHROUGH;
+ case '#': case '~':
+ if (i != 0)
+ break;
+ FALLTHROUGH;
+ case ' ':
+ c_and_shell_quote_compat = true;
+ FALLTHROUGH;
+ case '!': /* special in bash */
+ case '"': case '$': case '&':
+ case '(': case ')': case '*': case ';':
+ case '<':
+ case '=': /* sometimes special in 0th or (with "set -k") later args */
+ case '>': case '[':
+ case '^': /* special in old /bin/sh, e.g. SunOS 4.1.4 */
+ case '`': case '|':
+ /* A shell special character. In theory, '$' and '`' could
+ be the first bytes of multibyte characters, which means
+ we should check them with mbrtowc, but in practice this
+ doesn't happen so it's not worth worrying about. */
+ if (quoting_style == shell_always_quoting_style
+ && elide_outer_quotes)
+ goto force_outer_quoting_style;
+ break;
+
+ case '\'':
+ encountered_single_quote = true;
+ c_and_shell_quote_compat = true;
+ if (quoting_style == shell_always_quoting_style)
+ {
+ if (elide_outer_quotes)
+ goto force_outer_quoting_style;
+
+ if (buffersize && ! orig_buffersize)
+ {
+ /* Just scan string to see if supports a more concise
+ representation, rather than writing a longer string
+ but returning the length of the more concise form. */
+ orig_buffersize = buffersize;
+ buffersize = 0;
+ }
+
+ STORE ('\'');
+ STORE ('\\');
+ STORE ('\'');
+ pending_shell_escape_end = false;
+ }
+ break;
+
+ case '%': case '+': case ',': case '-': case '.': case '/':
+ case '0': case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9': case ':':
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z': case ']': case '_': case 'a': case 'b':
+ case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
+ case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
+ case 'o': case 'p': case 'q': case 'r': case 's': case 't':
+ case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
+ /* These characters don't cause problems, no matter what the
+ quoting style is. They cannot start multibyte sequences.
+ A digit or a special letter would cause trouble if it
+ appeared at the beginning of quote_string because we'd then
+ escape by prepending a backslash. However, it's hard to
+ imagine any locale that would use digits or letters as
+ quotes, and set_custom_quoting is documented not to accept
+ them. Also, a digit or a special letter would cause
+ trouble if it appeared in quote_these_too, but that's also
+ documented as not accepting them. */
+ c_and_shell_quote_compat = true;
+ break;
+
+ default:
+ /* If we have a multibyte sequence, copy it until we reach
+ its end, find an error, or come back to the initial shift
+ state. For C-like styles, if the sequence has
+ unprintable characters, escape the whole sequence, since
+ we can't easily escape single characters within it. */
+ {
+ /* Length of multibyte sequence found so far. */
+ size_t m;
+
+ bool printable;
+
+ if (unibyte_locale)
+ {
+ m = 1;
+ printable = isprint (c) != 0;
+ }
+ else
+ {
+ mbstate_t mbstate;
+ memset (&mbstate, 0, sizeof mbstate);
+
+ m = 0;
+ printable = true;
+ if (argsize == SIZE_MAX)
+ argsize = strlen (arg);
+
+ do
+ {
+ wchar_t w;
+ size_t bytes = mbrtowc (&w, &arg[i + m],
+ argsize - (i + m), &mbstate);
+ if (bytes == 0)
+ break;
+ else if (bytes == (size_t) -1)
+ {
+ printable = false;
+ break;
+ }
+ else if (bytes == (size_t) -2)
+ {
+ printable = false;
+ while (i + m < argsize && arg[i + m])
+ m++;
+ break;
+ }
+ else
+ {
+ /* Work around a bug with older shells that "see" a '\'
+ that is really the 2nd byte of a multibyte character.
+ In practice the problem is limited to ASCII
+ chars >= '@' that are shell special chars. */
+ if ('[' == 0x5b && elide_outer_quotes
+ && quoting_style == shell_always_quoting_style)
+ {
+ size_t j;
+ for (j = 1; j < bytes; j++)
+ switch (arg[i + m + j])
+ {
+ case '[': case '\\': case '^':
+ case '`': case '|':
+ goto force_outer_quoting_style;
+
+ default:
+ break;
+ }
+ }
+
+ if (! iswprint (w))
+ printable = false;
+ m += bytes;
+ }
+ }
+ while (! mbsinit (&mbstate));
+ }
+
+ c_and_shell_quote_compat = printable;
+
+ if (1 < m || (backslash_escapes && ! printable))
+ {
+ /* Output a multibyte sequence, or an escaped
+ unprintable unibyte character. */
+ size_t ilim = i + m;
+
+ for (;;)
+ {
+ if (backslash_escapes && ! printable)
+ {
+ START_ESC ();
+ STORE ('0' + (c >> 6));
+ STORE ('0' + ((c >> 3) & 7));
+ c = '0' + (c & 7);
+ }
+ else if (is_right_quote)
+ {
+ STORE ('\\');
+ is_right_quote = false;
+ }
+ if (ilim <= i + 1)
+ break;
+ END_ESC ();
+ STORE (c);
+ c = arg[++i];
+ }
+
+ goto store_c;
+ }
+ }
+ }
+
+ if (! (((backslash_escapes && quoting_style != shell_always_quoting_style)
+ || elide_outer_quotes)
+ && quote_these_too
+ && quote_these_too[c / INT_BITS] >> (c % INT_BITS) & 1)
+ && !is_right_quote)
+ goto store_c;
+
+ store_escape:
+ START_ESC ();
+
+ store_c:
+ END_ESC ();
+ STORE (c);
+
+ if (! c_and_shell_quote_compat)
+ all_c_and_shell_quote_compat = false;
+ }
+
+ if (len == 0 && quoting_style == shell_always_quoting_style
+ && elide_outer_quotes)
+ goto force_outer_quoting_style;
+
+ /* Single shell quotes (') are commonly enough used as an apostrophe,
+ that we attempt to minimize the quoting in this case. Note itʼs
+ better to use the apostrophe modifier "\u02BC" if possible, as that
+ renders better and works with the word match regex \W+ etc. */
+ if (quoting_style == shell_always_quoting_style && ! elide_outer_quotes
+ && encountered_single_quote)
+ {
+ if (all_c_and_shell_quote_compat)
+ return quotearg_buffer_restyled (buffer, orig_buffersize, arg, argsize,
+ c_quoting_style,
+ flags, quote_these_too,
+ left_quote, right_quote);
+ else if (! buffersize && orig_buffersize)
+ {
+ /* Disable read-only scan, and reprocess to write quoted string. */
+ buffersize = orig_buffersize;
+ len = 0;
+ goto process_input;
+ }
+ }
+
+ if (quote_string && !elide_outer_quotes)
+ for (; *quote_string; quote_string++)
+ STORE (*quote_string);
+
+ if (len < buffersize)
+ buffer[len] = '\0';
+ return len;
+
+ force_outer_quoting_style:
+ /* Don't reuse quote_these_too, since the addition of outer quotes
+ sufficiently quotes the specified characters. */
+ if (quoting_style == shell_always_quoting_style && backslash_escapes)
+ quoting_style = shell_escape_always_quoting_style;
+ return quotearg_buffer_restyled (buffer, buffersize, arg, argsize,
+ quoting_style,
+ flags & ~QA_ELIDE_OUTER_QUOTES, NULL,
+ left_quote, right_quote);
+}
+
+/* Place into buffer BUFFER (of size BUFFERSIZE) a quoted version of
+ argument ARG (of size ARGSIZE), using O to control quoting.
+ If O is null, use the default.
+ Terminate the output with a null character, and return the written
+ size of the output, not counting the terminating null.
+ If BUFFERSIZE is too small to store the output string, return the
+ value that would have been returned had BUFFERSIZE been large enough.
+ If ARGSIZE is SIZE_MAX, use the string length of the argument for
+ ARGSIZE. */
+size_t
+quotearg_buffer (char *buffer, size_t buffersize,
+ char const *arg, size_t argsize,
+ struct quoting_options const *o)
+{
+ struct quoting_options const *p = o ? o : &default_quoting_options;
+ int e = errno;
+ size_t r = quotearg_buffer_restyled (buffer, buffersize, arg, argsize,
+ p->style, p->flags, p->quote_these_too,
+ p->left_quote, p->right_quote);
+ errno = e;
+ return r;
+}
+
+/* Equivalent to quotearg_alloc (ARG, ARGSIZE, NULL, O). */
+char *
+quotearg_alloc (char const *arg, size_t argsize,
+ struct quoting_options const *o)
+{
+ return quotearg_alloc_mem (arg, argsize, NULL, o);
+}
+
+/* Like quotearg_buffer (..., ARG, ARGSIZE, O), except return newly
+ allocated storage containing the quoted string, and store the
+ resulting size into *SIZE, if non-NULL. The result can contain
+ embedded null bytes only if ARGSIZE is not SIZE_MAX, SIZE is not
+ NULL, and set_quoting_flags has not set the null byte elision
+ flag. */
+char *
+quotearg_alloc_mem (char const *arg, size_t argsize, size_t *size,
+ struct quoting_options const *o)
+{
+ struct quoting_options const *p = o ? o : &default_quoting_options;
+ int e = errno;
+ /* Elide embedded null bytes if we can't return a size. */
+ int flags = p->flags | (size ? 0 : QA_ELIDE_NULL_BYTES);
+ size_t bufsize = quotearg_buffer_restyled (0, 0, arg, argsize, p->style,
+ flags, p->quote_these_too,
+ p->left_quote,
+ p->right_quote) + 1;
+ char *buf = xcharalloc (bufsize);
+ quotearg_buffer_restyled (buf, bufsize, arg, argsize, p->style, flags,
+ p->quote_these_too,
+ p->left_quote, p->right_quote);
+ errno = e;
+ if (size)
+ *size = bufsize - 1;
+ return buf;
+}
+
+/* A storage slot with size and pointer to a value. */
+struct slotvec
+{
+ size_t size;
+ char *val;
+};
+
+/* Preallocate a slot 0 buffer, so that the caller can always quote
+ one small component of a "memory exhausted" message in slot 0. */
+static char slot0[256];
+static int nslots = 1;
+static struct slotvec slotvec0 = {sizeof slot0, slot0};
+static struct slotvec *slotvec = &slotvec0;
+
+void
+quotearg_free (void)
+{
+ struct slotvec *sv = slotvec;
+ int i;
+ for (i = 1; i < nslots; i++)
+ free (sv[i].val);
+ if (sv[0].val != slot0)
+ {
+ free (sv[0].val);
+ slotvec0.size = sizeof slot0;
+ slotvec0.val = slot0;
+ }
+ if (sv != &slotvec0)
+ {
+ free (sv);
+ slotvec = &slotvec0;
+ }
+ nslots = 1;
+}
+
+/* Use storage slot N to return a quoted version of argument ARG.
+ ARG is of size ARGSIZE, but if that is SIZE_MAX, ARG is a
+ null-terminated string.
+ OPTIONS specifies the quoting options.
+ The returned value points to static storage that can be
+ reused by the next call to this function with the same value of N.
+ N must be nonnegative. N is deliberately declared with type "int"
+ to allow for future extensions (using negative values). */
+static char *
+quotearg_n_options (int n, char const *arg, size_t argsize,
+ struct quoting_options const *options)
+{
+ int e = errno;
+
+ struct slotvec *sv = slotvec;
+
+ if (n < 0)
+ abort ();
+
+ if (nslots <= n)
+ {
+ bool preallocated = (sv == &slotvec0);
+
+ if (MIN (INT_MAX, MIN (PTRDIFF_MAX, SIZE_MAX) / sizeof *sv) <= n)
+ xalloc_die ();
+
+ slotvec = sv = xrealloc (preallocated ? NULL : sv, (n + 1) * sizeof *sv);
+ if (preallocated)
+ *sv = slotvec0;
+ memset (sv + nslots, 0, (n + 1 - nslots) * sizeof *sv);
+ nslots = n + 1;
+ }
+
+ {
+ size_t size = sv[n].size;
+ char *val = sv[n].val;
+ /* Elide embedded null bytes since we don't return a size. */
+ int flags = options->flags | QA_ELIDE_NULL_BYTES;
+ size_t qsize = quotearg_buffer_restyled (val, size, arg, argsize,
+ options->style, flags,
+ options->quote_these_too,
+ options->left_quote,
+ options->right_quote);
+
+ if (size <= qsize)
+ {
+ sv[n].size = size = qsize + 1;
+ if (val != slot0)
+ free (val);
+ sv[n].val = val = xcharalloc (size);
+ quotearg_buffer_restyled (val, size, arg, argsize, options->style,
+ flags, options->quote_these_too,
+ options->left_quote,
+ options->right_quote);
+ }
+
+ errno = e;
+ return val;
+ }
+}
+
+char *
+quotearg_n (int n, char const *arg)
+{
+ return quotearg_n_options (n, arg, SIZE_MAX, &default_quoting_options);
+}
+
+char *
+quotearg_n_mem (int n, char const *arg, size_t argsize)
+{
+ return quotearg_n_options (n, arg, argsize, &default_quoting_options);
+}
+
+char *
+quotearg (char const *arg)
+{
+ return quotearg_n (0, arg);
+}
+
+char *
+quotearg_mem (char const *arg, size_t argsize)
+{
+ return quotearg_n_mem (0, arg, argsize);
+}
+
+char *
+quotearg_n_style (int n, enum quoting_style s, char const *arg)
+{
+ struct quoting_options const o = quoting_options_from_style (s);
+ return quotearg_n_options (n, arg, SIZE_MAX, &o);
+}
+
+char *
+quotearg_n_style_mem (int n, enum quoting_style s,
+ char const *arg, size_t argsize)
+{
+ struct quoting_options const o = quoting_options_from_style (s);
+ return quotearg_n_options (n, arg, argsize, &o);
+}
+
+char *
+quotearg_style (enum quoting_style s, char const *arg)
+{
+ return quotearg_n_style (0, s, arg);
+}
+
+char *
+quotearg_style_mem (enum quoting_style s, char const *arg, size_t argsize)
+{
+ return quotearg_n_style_mem (0, s, arg, argsize);
+}
+
+char *
+quotearg_char_mem (char const *arg, size_t argsize, char ch)
+{
+ struct quoting_options options;
+ options = default_quoting_options;
+ set_char_quoting (&options, ch, 1);
+ return quotearg_n_options (0, arg, argsize, &options);
+}
+
+char *
+quotearg_char (char const *arg, char ch)
+{
+ return quotearg_char_mem (arg, SIZE_MAX, ch);
+}
+
+char *
+quotearg_colon (char const *arg)
+{
+ return quotearg_char (arg, ':');
+}
+
+char *
+quotearg_colon_mem (char const *arg, size_t argsize)
+{
+ return quotearg_char_mem (arg, argsize, ':');
+}
+
+char *
+quotearg_n_style_colon (int n, enum quoting_style s, char const *arg)
+{
+ struct quoting_options options;
+ options = quoting_options_from_style (s);
+ set_char_quoting (&options, ':', 1);
+ return quotearg_n_options (n, arg, SIZE_MAX, &options);
+}
+
+char *
+quotearg_n_custom (int n, char const *left_quote,
+ char const *right_quote, char const *arg)
+{
+ return quotearg_n_custom_mem (n, left_quote, right_quote, arg,
+ SIZE_MAX);
+}
+
+char *
+quotearg_n_custom_mem (int n, char const *left_quote,
+ char const *right_quote,
+ char const *arg, size_t argsize)
+{
+ struct quoting_options o = default_quoting_options;
+ set_custom_quoting (&o, left_quote, right_quote);
+ return quotearg_n_options (n, arg, argsize, &o);
+}
+
+char *
+quotearg_custom (char const *left_quote, char const *right_quote,
+ char const *arg)
+{
+ return quotearg_n_custom (0, left_quote, right_quote, arg);
+}
+
+char *
+quotearg_custom_mem (char const *left_quote, char const *right_quote,
+ char const *arg, size_t argsize)
+{
+ return quotearg_n_custom_mem (0, left_quote, right_quote, arg,
+ argsize);
+}
+
+
+/* The quoting option used by the functions of quote.h. */
+struct quoting_options quote_quoting_options =
+ {
+ locale_quoting_style,
+ 0,
+ { 0 },
+ NULL, NULL
+ };
+
+char const *
+quote_n_mem (int n, char const *arg, size_t argsize)
+{
+ return quotearg_n_options (n, arg, argsize, "e_quoting_options);
+}
+
+char const *
+quote_mem (char const *arg, size_t argsize)
+{
+ return quote_n_mem (0, arg, argsize);
+}
+
+char const *
+quote_n (int n, char const *arg)
+{
+ return quote_n_mem (n, arg, SIZE_MAX);
+}
+
+char const *
+quote (char const *arg)
+{
+ return quote_n (0, arg);
+}
diff --git a/src/diff.h b/src/diff.h
index a438a8e..e5f1218 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -392,7 +392,7 @@ extern void print_sdiff_script (struct change *);
extern char const change_letter[4];
extern char const pr_program[];
extern char *concat (char const *, char const *, char const *);
-extern bool (*lines_differ) (char const *, size_t, char const *, size_t) _GL_ATTRIBUTE_PURE;
+extern bool (*lines_differ) (char const *, size_t, char const *, size_t);
extern bool lines_differ_singlebyte (char const *, size_t, char const *, size_t) _GL_ATTRIBUTE_PURE;
#ifdef HANDLE_MULTIBYTE
extern bool lines_differ_multibyte (char const *, size_t, char const *, size_t) _GL_ATTRIBUTE_PURE;
diff --git a/src/diff.h.covscan b/src/diff.h.covscan
new file mode 100644
index 0000000..a438a8e
--- /dev/null
+++ b/src/diff.h.covscan
@@ -0,0 +1,438 @@
+/* Shared definitions for GNU DIFF
+
+ Copyright (C) 1988-1989, 1991-1995, 1998, 2001-2002, 2004, 2009-2013,
+ 2015-2017 Free Software Foundation, Inc.
+
+ This file is part of GNU DIFF.
+
+ This program 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 3 of the License, or
+ (at your option) any later version.
+
+ This program 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. If not, see . */
+
+#include "system.h"
+#include
+#include
+#include
+
+/* For platforms which support the ISO C ammendment 1 functionality we
+ support user-defined character classes. */
+#if defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H
+/* Solaris 2.5 has a bug: must be included before . */
+# include
+# include
+# if defined (HAVE_MBRTOWC)
+# define HANDLE_MULTIBYTE 1
+# endif
+#endif
+
+/* What kind of changes a hunk contains. */
+enum changes
+{
+ /* No changes: lines common to both files. */
+ UNCHANGED,
+
+ /* Deletes only: lines taken from just the first file. */
+ OLD,
+
+ /* Inserts only: lines taken from just the second file. */
+ NEW,
+
+ /* Both deletes and inserts: a hunk containing both old and new lines. */
+ CHANGED
+};
+
+/* When colors should be used in the output. */
+enum colors_style
+{
+ /* Never output colors. */
+ NEVER,
+
+ /* Output colors if the output is a terminal. */
+ AUTO,
+
+ /* Always output colors. */
+ ALWAYS,
+};
+
+/* Variables for command line options */
+
+#ifndef GDIFF_MAIN
+# define XTERN extern
+#else
+# define XTERN
+#endif
+
+enum output_style
+{
+ /* No output style specified. */
+ OUTPUT_UNSPECIFIED,
+
+ /* Default output style. */
+ OUTPUT_NORMAL,
+
+ /* Output the differences with lines of context before and after (-c). */
+ OUTPUT_CONTEXT,
+
+ /* Output the differences in a unified context diff format (-u). */
+ OUTPUT_UNIFIED,
+
+ /* Output the differences as commands suitable for 'ed' (-e). */
+ OUTPUT_ED,
+
+ /* Output the diff as a forward ed script (-f). */
+ OUTPUT_FORWARD_ED,
+
+ /* Like -f, but output a count of changed lines in each "command" (-n). */
+ OUTPUT_RCS,
+
+ /* Output merged #ifdef'd file (-D). */
+ OUTPUT_IFDEF,
+
+ /* Output sdiff style (-y). */
+ OUTPUT_SDIFF
+};
+
+/* True for output styles that are robust,
+ i.e. can handle a file that ends in a non-newline. */
+#define ROBUST_OUTPUT_STYLE(S) ((S) != OUTPUT_ED && (S) != OUTPUT_FORWARD_ED)
+
+XTERN enum output_style output_style;
+
+/* Define the current color context used to print a line. */
+XTERN enum colors_style colors_style;
+
+/* Nonzero if output cannot be generated for identical files. */
+XTERN bool no_diff_means_no_output;
+
+/* Number of lines of context to show in each set of diffs.
+ This is zero when context is not to be shown. */
+XTERN lin context;
+
+/* Consider all files as text files (-a).
+ Don't interpret codes over 0177 as implying a "binary file". */
+XTERN bool text;
+
+/* Number of lines to keep in identical prefix and suffix. */
+XTERN lin horizon_lines;
+
+/* The significance of white space during comparisons. */
+enum DIFF_white_space
+{
+ /* All white space is significant (the default). */
+ IGNORE_NO_WHITE_SPACE,
+
+ /* Ignore changes due to tab expansion (-E). */
+ IGNORE_TAB_EXPANSION,
+
+ /* Ignore changes in trailing horizontal white space (-Z). */
+ IGNORE_TRAILING_SPACE,
+
+ /* IGNORE_TAB_EXPANSION and IGNORE_TRAILING_SPACE are a special case
+ because they are independent and can be ORed together, yielding
+ IGNORE_TAB_EXPANSION_AND_TRAILING_SPACE. */
+ IGNORE_TAB_EXPANSION_AND_TRAILING_SPACE,
+
+ /* Ignore changes in horizontal white space (-b). */
+ IGNORE_SPACE_CHANGE,
+
+ /* Ignore all horizontal white space (-w). */
+ IGNORE_ALL_SPACE
+};
+XTERN enum DIFF_white_space ignore_white_space;
+
+/* Ignore changes that affect only blank lines (-B). */
+XTERN bool ignore_blank_lines;
+
+/* Files can be compared byte-by-byte, as if they were binary.
+ This depends on various options. */
+XTERN bool files_can_be_treated_as_binary;
+
+/* Ignore differences in case of letters (-i). */
+XTERN bool ignore_case;
+
+/* Ignore differences in case of letters in file names. */
+XTERN bool ignore_file_name_case;
+
+/* Act on symbolic links themselves rather than on their target
+ (--no-dereference). */
+XTERN bool no_dereference_symlinks;
+
+/* File labels for '-c' output headers (--label). */
+XTERN char *file_label[2];
+
+/* Regexp to identify function-header lines (-F). */
+XTERN struct re_pattern_buffer function_regexp;
+
+/* Ignore changes that affect only lines matching this regexp (-I). */
+XTERN struct re_pattern_buffer ignore_regexp;
+
+/* Say only whether files differ, not how (-q). */
+XTERN bool brief;
+
+/* Expand tabs in the output so the text lines up properly
+ despite the characters added to the front of each line (-t). */
+XTERN bool expand_tabs;
+
+/* Number of columns between tab stops. */
+XTERN size_t tabsize;
+
+/* Use a tab in the output, rather than a space, before the text of an
+ input line, so as to keep the proper alignment in the input line
+ without changing the characters in it (-T). */
+XTERN bool initial_tab;
+
+/* Do not output an initial space or tab before the text of an empty line. */
+XTERN bool suppress_blank_empty;
+
+/* Remove trailing carriage returns from input. */
+XTERN bool strip_trailing_cr;
+
+/* In directory comparison, specify file to start with (-S).
+ This is used for resuming an aborted comparison.
+ All file names less than this name are ignored. */
+XTERN char const *starting_file;
+
+/* Pipe each file's output through pr (-l). */
+XTERN bool paginate;
+
+/* Line group formats for unchanged, old, new, and changed groups. */
+XTERN char const *group_format[CHANGED + 1];
+
+/* Line formats for unchanged, old, and new lines. */
+XTERN char const *line_format[NEW + 1];
+
+/* If using OUTPUT_SDIFF print extra information to help the sdiff filter. */
+XTERN bool sdiff_merge_assist;
+
+/* Tell OUTPUT_SDIFF to show only the left version of common lines. */
+XTERN bool left_column;
+
+/* Tell OUTPUT_SDIFF to not show common lines. */
+XTERN bool suppress_common_lines;
+
+/* The half line width and column 2 offset for OUTPUT_SDIFF. */
+XTERN size_t sdiff_half_width;
+XTERN size_t sdiff_column2_offset;
+
+/* String containing all the command options diff received,
+ with spaces between and at the beginning but none at the end.
+ If there were no options given, this string is empty. */
+XTERN char *switch_string;
+
+/* Use heuristics for better speed with large files with a small
+ density of changes. */
+XTERN bool speed_large_files;
+
+/* Patterns that match file names to be excluded. */
+XTERN struct exclude *excluded;
+
+/* Don't discard lines. This makes things slower (sometimes much
+ slower) but will find a guaranteed minimal set of changes. */
+XTERN bool minimal;
+
+/* The strftime format to use for time strings. */
+XTERN char const *time_format;
+
+/* The result of comparison is an "edit script": a chain of 'struct change'.
+ Each 'struct change' represents one place where some lines are deleted
+ and some are inserted.
+
+ LINE0 and LINE1 are the first affected lines in the two files (origin 0).
+ DELETED is the number of lines deleted here from file 0.
+ INSERTED is the number of lines inserted here in file 1.
+
+ If DELETED is 0 then LINE0 is the number of the line before
+ which the insertion was done; vice versa for INSERTED and LINE1. */
+
+struct change
+{
+ struct change *link; /* Previous or next edit command */
+ lin inserted; /* # lines of file 1 changed here. */
+ lin deleted; /* # lines of file 0 changed here. */
+ lin line0; /* Line number of 1st deleted line. */
+ lin line1; /* Line number of 1st inserted line. */
+ bool ignore; /* Flag used in context.c. */
+};
+
+/* Structures that describe the input files. */
+
+/* Data on one input file being compared. */
+
+struct file_data {
+ int desc; /* File descriptor */
+ char const *name; /* File name */
+ struct stat stat; /* File status */
+
+ /* Buffer in which text of file is read. */
+ word *buffer;
+
+ /* Allocated size of buffer, in bytes. Always a multiple of
+ sizeof *buffer. */
+ size_t bufsize;
+
+ /* Number of valid bytes now in the buffer. */
+ size_t buffered;
+
+ /* Array of pointers to lines in the file. */
+ char const **linbuf;
+
+ /* linbuf_base <= buffered_lines <= valid_lines <= alloc_lines.
+ linebuf[linbuf_base ... buffered_lines - 1] are possibly differing.
+ linebuf[linbuf_base ... valid_lines - 1] contain valid data.
+ linebuf[linbuf_base ... alloc_lines - 1] are allocated. */
+ lin linbuf_base, buffered_lines, valid_lines, alloc_lines;
+
+ /* Pointer to end of prefix of this file to ignore when hashing. */
+ char const *prefix_end;
+
+ /* Count of lines in the prefix.
+ There are this many lines in the file before linbuf[0]. */
+ lin prefix_lines;
+
+ /* Pointer to start of suffix of this file to ignore when hashing. */
+ char const *suffix_begin;
+
+ /* Vector, indexed by line number, containing an equivalence code for
+ each line. It is this vector that is actually compared with that
+ of another file to generate differences. */
+ lin *equivs;
+
+ /* Vector, like the previous one except that
+ the elements for discarded lines have been squeezed out. */
+ lin *undiscarded;
+
+ /* Vector mapping virtual line numbers (not counting discarded lines)
+ to real ones (counting those lines). Both are origin-0. */
+ lin *realindexes;
+
+ /* Total number of nondiscarded lines. */
+ lin nondiscarded_lines;
+
+ /* Vector, indexed by real origin-0 line number,
+ containing 1 for a line that is an insertion or a deletion.
+ The results of comparison are stored here. */
+ char *changed;
+
+ /* 1 if file ends in a line with no final newline. */
+ bool missing_newline;
+
+ /* 1 if at end of file. */
+ bool eof;
+
+ /* 1 more than the maximum equivalence value used for this or its
+ sibling file. */
+ lin equiv_max;
+};
+
+/* The file buffer, considered as an array of bytes rather than
+ as an array of words. */
+#define FILE_BUFFER(f) ((char *) (f)->buffer)
+
+/* Data on two input files being compared. */
+
+struct comparison
+ {
+ struct file_data file[2];
+ struct comparison const *parent; /* parent, if a recursive comparison */
+ };
+
+/* Describe the two files currently being compared. */
+
+XTERN struct file_data files[2];
+
+/* Stdio stream to output diffs to. */
+
+XTERN FILE *outfile;
+
+/* Declare various functions. */
+
+/* analyze.c */
+extern int diff_2_files (struct comparison *);
+
+/* context.c */
+extern void print_context_header (struct file_data[], char const * const *, bool);
+extern void print_context_script (struct change *, bool);
+
+/* dir.c */
+extern int diff_dirs (struct comparison const *,
+ int (*) (struct comparison const *,
+ char const *, char const *));
+extern char *find_dir_file_pathname (char const *, char const *);
+
+/* ed.c */
+extern void print_ed_script (struct change *);
+extern void pr_forward_ed_script (struct change *);
+
+/* ifdef.c */
+extern void print_ifdef_script (struct change *);
+
+/* io.c */
+extern void file_block_read (struct file_data *, size_t);
+extern bool read_files (struct file_data[], bool);
+
+/* normal.c */
+extern void print_normal_script (struct change *);
+
+/* rcs.c */
+extern void print_rcs_script (struct change *);
+
+/* side.c */
+extern void print_sdiff_script (struct change *);
+
+/* util.c */
+extern char const change_letter[4];
+extern char const pr_program[];
+extern char *concat (char const *, char const *, char const *);
+extern bool (*lines_differ) (char const *, size_t, char const *, size_t) _GL_ATTRIBUTE_PURE;
+extern bool lines_differ_singlebyte (char const *, size_t, char const *, size_t) _GL_ATTRIBUTE_PURE;
+#ifdef HANDLE_MULTIBYTE
+extern bool lines_differ_multibyte (char const *, size_t, char const *, size_t) _GL_ATTRIBUTE_PURE;
+#endif
+extern lin translate_line_number (struct file_data const *, lin);
+extern struct change *find_change (struct change *);
+extern struct change *find_reverse_change (struct change *);
+extern void *zalloc (size_t);
+extern enum changes analyze_hunk (struct change *, lin *, lin *, lin *, lin *);
+extern void begin_output (void);
+extern void debug_script (struct change *);
+extern void fatal (char const *) __attribute__((noreturn));
+extern void finish_output (void);
+extern void message (char const *, char const *, char const *);
+extern void message5 (char const *, char const *, char const *,
+ char const *, char const *);
+extern void output_1_line (char const *, char const *, char const *,
+ char const *);
+extern void perror_with_name (char const *);
+extern void pfatal_with_name (char const *) __attribute__((noreturn));
+extern void print_1_line (char const *, char const * const *);
+extern void print_1_line_nl (char const *, char const * const *, bool);
+extern void print_message_queue (void);
+extern void print_number_range (char, struct file_data *, lin, lin);
+extern void print_script (struct change *, struct change * (*) (struct change *),
+ void (*) (struct change *));
+extern void setup_output (char const *, char const *, bool);
+extern void translate_range (struct file_data const *, lin, lin,
+ printint *, printint *);
+
+enum color_context
+{
+ HEADER_CONTEXT,
+ ADD_CONTEXT,
+ DELETE_CONTEXT,
+ RESET_CONTEXT,
+ LINE_NUMBER_CONTEXT,
+};
+
+XTERN bool presume_output_tty;
+
+extern void set_color_context (enum color_context color_context);
+extern void set_color_palette (char const *palette);
diff --git a/src/ifdef.c b/src/ifdef.c
index c7dae8d..648b378 100644
--- a/src/ifdef.c
+++ b/src/ifdef.c
@@ -362,20 +362,14 @@ do_printf_spec (FILE *out, char const *spec,
printint print_value = value;
size_t spec_prefix_len = f - spec - 2;
size_t pI_len = sizeof pI - 1;
-#if 0
- char format[spec_prefix_len + pI_len + 2];
-#else
char *format = xmalloc (spec_prefix_len + pI_len + 2);
-#endif
char *p = format + spec_prefix_len + pI_len;
memcpy (format, spec, spec_prefix_len);
memcpy (format + spec_prefix_len, pI, pI_len);
*p++ = c;
*p = '\0';
fprintf (out, format, print_value);
-#if ! HAVE_C_VARARRAYS
free (format);
-#endif
}
}
break;
diff --git a/src/ifdef.c.covscan b/src/ifdef.c.covscan
new file mode 100644
index 0000000..c7dae8d
--- /dev/null
+++ b/src/ifdef.c.covscan
@@ -0,0 +1,431 @@
+/* #ifdef-format output routines for GNU DIFF.
+
+ Copyright (C) 1989, 1991-1994, 2001-2002, 2004, 2006, 2009-2013, 2015-2017
+ Free Software Foundation, Inc.
+
+ This file is part of GNU DIFF.
+
+ GNU DIFF is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY. No author or distributor
+ accepts responsibility to anyone for the consequences of using it
+ or for whether it serves any particular purpose or works at all,
+ unless he says so in writing. Refer to the GNU General Public
+ License for full details.
+
+ Everyone is granted permission to copy, modify and redistribute
+ GNU DIFF, but only under the conditions described in the
+ GNU General Public License. A copy of this license is
+ supposed to have been given to you along with GNU DIFF so you
+ can know your rights and responsibilities. It should be in a
+ file named COPYING. Among other things, the copyright notice
+ and this notice must be preserved on all copies. */
+
+#include "diff.h"
+
+#include
+
+struct group
+{
+ struct file_data const *file;
+ lin from, upto; /* start and limit lines for this group of lines */
+};
+
+static char const *format_group (FILE *, char const *, char,
+ struct group const *);
+static char const *do_printf_spec (FILE *, char const *,
+ struct file_data const *, lin,
+ struct group const *);
+static char const *scan_char_literal (char const *, char *);
+static lin groups_letter_value (struct group const *, char);
+static void format_ifdef (char const *, lin, lin, lin, lin);
+static void print_ifdef_hunk (struct change *);
+static void print_ifdef_lines (FILE *, char const *, struct group const *);
+
+static lin next_line0;
+static lin next_line1;
+
+/* Print the edit-script SCRIPT as a merged #ifdef file. */
+
+void
+print_ifdef_script (struct change *script)
+{
+ next_line0 = next_line1 = - files[0].prefix_lines;
+ print_script (script, find_change, print_ifdef_hunk);
+ if (next_line0 < files[0].valid_lines
+ || next_line1 < files[1].valid_lines)
+ {
+ begin_output ();
+ format_ifdef (group_format[UNCHANGED],
+ next_line0, files[0].valid_lines,
+ next_line1, files[1].valid_lines);
+ }
+}
+
+/* Print a hunk of an ifdef diff.
+ This is a contiguous portion of a complete edit script,
+ describing changes in consecutive lines. */
+
+static void
+print_ifdef_hunk (struct change *hunk)
+{
+ lin first0, last0, first1, last1;
+
+ /* Determine range of line numbers involved in each file. */
+ enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
+ if (!changes)
+ return;
+
+ begin_output ();
+
+ /* Print lines up to this change. */
+ if (next_line0 < first0 || next_line1 < first1)
+ format_ifdef (group_format[UNCHANGED],
+ next_line0, first0,
+ next_line1, first1);
+
+ /* Print this change. */
+ next_line0 = last0 + 1;
+ next_line1 = last1 + 1;
+ format_ifdef (group_format[changes],
+ first0, next_line0,
+ first1, next_line1);
+}
+
+/* Print a set of lines according to FORMAT.
+ Lines BEG0 up to END0 are from the first file;
+ lines BEG1 up to END1 are from the second file. */
+
+static void
+format_ifdef (char const *format, lin beg0, lin end0, lin beg1, lin end1)
+{
+ struct group groups[2];
+
+ groups[0].file = &files[0];
+ groups[0].from = beg0;
+ groups[0].upto = end0;
+ groups[1].file = &files[1];
+ groups[1].from = beg1;
+ groups[1].upto = end1;
+ format_group (outfile, format, 0, groups);
+}
+
+/* Print to file OUT a set of lines according to FORMAT.
+ The format ends at the first free instance of ENDCHAR.
+ Yield the address of the terminating character.
+ GROUPS specifies which lines to print.
+ If OUT is zero, do not actually print anything; just scan the format. */
+
+static char const *
+format_group (register FILE *out, char const *format, char endchar,
+ struct group const *groups)
+{
+ register char c;
+ register char const *f = format;
+
+ while ((c = *f) != endchar && c != 0)
+ {
+ char const *f1 = ++f;
+ if (c == '%')
+ switch ((c = *f++))
+ {
+ case '%':
+ break;
+
+ case '(':
+ /* Print if-then-else format e.g. '%(n=1?thenpart:elsepart)'. */
+ {
+ int i;
+ uintmax_t value[2];
+ FILE *thenout, *elseout;
+
+ for (i = 0; i < 2; i++)
+ {
+ if (ISDIGIT (*f))
+ {
+ char *fend;
+ errno = 0;
+ value[i] = strtoumax (f, &fend, 10);
+ if (errno)
+ goto bad_format;
+ f = fend;
+ }
+ else
+ {
+ value[i] = groups_letter_value (groups, *f);
+ if (value[i] == -1)
+ goto bad_format;
+ f++;
+ }
+ if (*f++ != "=?"[i])
+ goto bad_format;
+ }
+ if (value[0] == value[1])
+ thenout = out, elseout = 0;
+ else
+ thenout = 0, elseout = out;
+ f = format_group (thenout, f, ':', groups);
+ if (*f)
+ {
+ f = format_group (elseout, f + 1, ')', groups);
+ if (*f)
+ f++;
+ }
+ }
+ continue;
+
+ case '<':
+ /* Print lines deleted from first file. */
+ print_ifdef_lines (out, line_format[OLD], &groups[0]);
+ continue;
+
+ case '=':
+ /* Print common lines. */
+ print_ifdef_lines (out, line_format[UNCHANGED], &groups[0]);
+ continue;
+
+ case '>':
+ /* Print lines inserted from second file. */
+ print_ifdef_lines (out, line_format[NEW], &groups[1]);
+ continue;
+
+ default:
+ f = do_printf_spec (out, f - 2, 0, 0, groups);
+ if (f)
+ continue;
+ /* Fall through. */
+ bad_format:
+ c = '%';
+ f = f1;
+ break;
+ }
+
+ if (out)
+ putc (c, out);
+ }
+
+ return f;
+}
+
+/* For the line group pair G, return the number corresponding to LETTER.
+ Return -1 if LETTER is not a group format letter. */
+static lin
+groups_letter_value (struct group const *g, char letter)
+{
+ switch (letter)
+ {
+ case 'E': letter = 'e'; g++; break;
+ case 'F': letter = 'f'; g++; break;
+ case 'L': letter = 'l'; g++; break;
+ case 'M': letter = 'm'; g++; break;
+ case 'N': letter = 'n'; g++; break;
+ }
+
+ switch (letter)
+ {
+ case 'e': return translate_line_number (g->file, g->from) - 1;
+ case 'f': return translate_line_number (g->file, g->from);
+ case 'l': return translate_line_number (g->file, g->upto) - 1;
+ case 'm': return translate_line_number (g->file, g->upto);
+ case 'n': return g->upto - g->from;
+ default: return -1;
+ }
+}
+
+/* Print to file OUT, using FORMAT to print the line group GROUP.
+ But do nothing if OUT is zero. */
+static void
+print_ifdef_lines (register FILE *out, char const *format,
+ struct group const *group)
+{
+ struct file_data const *file = group->file;
+ char const * const *linbuf = file->linbuf;
+ lin from = group->from, upto = group->upto;
+
+ if (!out)
+ return;
+
+ /* If possible, use a single fwrite; it's faster. */
+ if (!expand_tabs && format[0] == '%')
+ {
+ if (format[1] == 'l' && format[2] == '\n' && !format[3] && from < upto)
+ {
+ fwrite (linbuf[from], sizeof (char),
+ linbuf[upto] + (linbuf[upto][-1] != '\n') - linbuf[from],
+ out);
+ return;
+ }
+ if (format[1] == 'L' && !format[2])
+ {
+ fwrite (linbuf[from], sizeof (char),
+ linbuf[upto] - linbuf[from], out);
+ return;
+ }
+ }
+
+ for (; from < upto; from++)
+ {
+ register char c;
+ register char const *f = format;
+
+ while ((c = *f++) != 0)
+ {
+ char const *f1 = f;
+ if (c == '%')
+ switch ((c = *f++))
+ {
+ case '%':
+ break;
+
+ case 'l':
+ output_1_line (linbuf[from],
+ (linbuf[from + 1]
+ - (linbuf[from + 1][-1] == '\n')),
+ 0, 0);
+ continue;
+
+ case 'L':
+ output_1_line (linbuf[from], linbuf[from + 1], 0, 0);
+ continue;
+
+ default:
+ f = do_printf_spec (out, f - 2, file, from, 0);
+ if (f)
+ continue;
+ c = '%';
+ f = f1;
+ break;
+ }
+
+ putc (c, out);
+ }
+ }
+}
+
+static char const *
+do_printf_spec (FILE *out, char const *spec,
+ struct file_data const *file, lin n,
+ struct group const *groups)
+{
+ char const *f = spec;
+ char c;
+ char c1;
+
+ /* Scan printf-style SPEC of the form %[-'0]*[0-9]*(.[0-9]*)?[cdoxX]. */
+ /* assert (*f == '%'); */
+ f++;
+ while ((c = *f++) == '-' || c == '\'' || c == '0')
+ continue;
+ while (ISDIGIT (c))
+ c = *f++;
+ if (c == '.')
+ while (ISDIGIT (c = *f++))
+ continue;
+ c1 = *f++;
+
+ switch (c)
+ {
+ case 'c':
+ if (c1 != '\'')
+ return 0;
+ else
+ {
+ char value IF_LINT (= 0);
+ f = scan_char_literal (f, &value);
+ if (!f)
+ return 0;
+ if (out)
+ putc (value, out);
+ }
+ break;
+
+ case 'd': case 'o': case 'x': case 'X':
+ {
+ lin value;
+
+ if (file)
+ {
+ if (c1 != 'n')
+ return 0;
+ value = translate_line_number (file, n);
+ }
+ else
+ {
+ value = groups_letter_value (groups, c1);
+ if (value < 0)
+ return 0;
+ }
+
+ if (out)
+ {
+ /* For example, if the spec is "%3xn" and pI is "l", use the printf
+ format spec "%3lx". Here the spec prefix is "%3". */
+ printint print_value = value;
+ size_t spec_prefix_len = f - spec - 2;
+ size_t pI_len = sizeof pI - 1;
+#if 0
+ char format[spec_prefix_len + pI_len + 2];
+#else
+ char *format = xmalloc (spec_prefix_len + pI_len + 2);
+#endif
+ char *p = format + spec_prefix_len + pI_len;
+ memcpy (format, spec, spec_prefix_len);
+ memcpy (format + spec_prefix_len, pI, pI_len);
+ *p++ = c;
+ *p = '\0';
+ fprintf (out, format, print_value);
+#if ! HAVE_C_VARARRAYS
+ free (format);
+#endif
+ }
+ }
+ break;
+
+ default:
+ return 0;
+ }
+
+ return f;
+}
+
+/* Scan the character literal represented in the string LIT; LIT points just
+ after the initial apostrophe. Put the literal's value into *VALPTR.
+ Yield the address of the first character after the closing apostrophe,
+ or a null pointer if the literal is ill-formed. */
+static char const *
+scan_char_literal (char const *lit, char *valptr)
+{
+ register char const *p = lit;
+ char value;
+ ptrdiff_t digits;
+ char c = *p++;
+
+ switch (c)
+ {
+ case 0:
+ case '\'':
+ return NULL;
+
+ case '\\':
+ value = 0;
+ while ((c = *p++) != '\'')
+ {
+ unsigned int digit = c - '0';
+ if (8 <= digit)
+ return NULL;
+ value = 8 * value + digit;
+ }
+ digits = p - lit - 2;
+ if (! (1 <= digits && digits <= 3))
+ return NULL;
+ break;
+
+ default:
+ value = c;
+ if (*p++ != '\'')
+ return NULL;
+ break;
+ }
+
+ *valptr = value;
+ return p;
+}
diff --git a/src/sdiff.c b/src/sdiff.c
index 1ae3dcb..78a44c4 100644
--- a/src/sdiff.c
+++ b/src/sdiff.c
@@ -230,8 +230,10 @@ cleanup (int signo __attribute__((unused)))
if (0 < diffpid)
kill (diffpid, SIGPIPE);
#endif
- if (tmpname)
+ if (tmpname) {
unlink (tmpname);
+ free (tmpname);
+ }
}
static void exiterr (void) __attribute__((noreturn));
@@ -685,6 +687,7 @@ main (int argc, char *argv[])
if (tmpname)
{
unlink (tmpname);
+ free (tmpname);
tmpname = 0;
}
diff --git a/src/sdiff.c.covscan b/src/sdiff.c.covscan
new file mode 100644
index 0000000..1ae3dcb
--- /dev/null
+++ b/src/sdiff.c.covscan
@@ -0,0 +1,1173 @@
+/* sdiff - side-by-side merge of file differences
+
+ Copyright (C) 1992-1996, 1998, 2001-2002, 2004, 2006-2007, 2009-2013,
+ 2015-2017 Free Software Foundation, Inc.
+
+ This file is part of GNU DIFF.
+
+ This program 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 3 of the License, or
+ (at your option) any later version.
+
+ This program 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. If not, see . */
+
+#include "system.h"
+#include "paths.h"
+
+#include
+#include
+
+#include
+#include
+#include "die.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* The official name of this program (e.g., no 'g' prefix). */
+#define PROGRAM_NAME "sdiff"
+
+#define AUTHORS \
+ proper_name ("Thomas Lord")
+
+/* Size of chunks read from files which must be parsed into lines. */
+#define SDIFF_BUFSIZE ((size_t) 65536)
+
+static char const *editor_program = DEFAULT_EDITOR_PROGRAM;
+static char const **diffargv;
+
+static char * volatile tmpname;
+static FILE *tmp;
+
+#if HAVE_WORKING_FORK
+static pid_t volatile diffpid;
+#endif
+
+struct line_filter;
+
+static void 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);
+
+static int const sigs[] = {
+#ifdef SIGHUP
+ SIGHUP,
+#endif
+#ifdef SIGQUIT
+ SIGQUIT,
+#endif
+#ifdef SIGTERM
+ SIGTERM,
+#endif
+#ifdef SIGXCPU
+ SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+ SIGXFSZ,
+#endif
+#ifdef SIGPIPE
+ SIGPIPE,
+#endif
+ SIGINT
+};
+enum
+ {
+ NUM_SIGS = sizeof sigs / sizeof *sigs,
+ handler_index_of_SIGINT = 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, void (*) (int));
+#else
+ static void (*initial_action[NUM_SIGS]) ();
+# define initial_handler(i) (initial_action[i])
+# define signal_handler(sig, handler) signal (sig, handler)
+#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,
+ TABSIZE_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'},
+ {"ignore-trailing-space", 0, 0, 'Z'},
+ {"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'},
+ {"tabsize", 1, 0, TABSIZE_OPTION},
+ {"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);
+ die (EXIT_TROUBLE, 0, _("Try '%s --help' for more information."),
+ program_name);
+}
+
+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, --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_("-Z, --ignore-trailing-space ignore white space at line end"),
+ 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, --ignore-matching-lines=RE ignore changes all whose lines match RE"),
+ N_(" --strip-trailing-cr strip trailing carriage return on input"),
+ N_("-a, --text treat all files as text"),
+ "",
+ N_("-w, --width=NUM output at most NUM (default 130) print columns"),
+ 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_(" --tabsize=NUM tab stops at every NUM (default 8) print columns"),
+ "",
+ N_("-d, --minimal try hard to find a smaller set of changes"),
+ N_("-H, --speed-large-files assume large files, many scattered small changes"),
+ N_(" --diff-program=PROGRAM use PROGRAM to compare files"),
+ "",
+ N_(" --help display this help and exit"),
+ N_("-v, --version output version information and exit"),
+ 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 differences between FILE1 and FILE2."));
+
+ fputs (_("\
+Mandatory arguments to long options are mandatory for short options too.\n\
+"), stdout);
+ for (p = option_help_msgid; *p; p++)
+ if (**p)
+ printf (" %s\n", _(*p));
+ else
+ putchar ('\n');
+ printf ("\n%s\n%s\n",
+ _("If a FILE is '-', read standard input."),
+ _("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."));
+ emit_bug_reporting_address ();
+}
+
+/* Clean up after a signal or other failure. This function is
+ async-signal-safe. */
+static void
+cleanup (int signo __attribute__((unused)))
+{
+#if HAVE_WORKING_FORK
+ if (0 < diffpid)
+ kill (diffpid, SIGPIPE);
+#endif
+ if (tmpname)
+ unlink (tmpname);
+}
+
+static void exiterr (void) __attribute__((noreturn));
+static void
+exiterr (void)
+{
+ cleanup (0);
+ 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
+check_child_status (int werrno, int wstatus, int max_ok_status,
+ char const *subsidiary_program)
+{
+ int status = (! werrno && WIFEXITED (wstatus)
+ ? WEXITSTATUS (wstatus)
+ : INT_MAX);
+
+ if (max_ok_status < status)
+ {
+ error (0, werrno,
+ _(status == 126
+ ? "subsidiary program '%s' could not be invoked"
+ : status == 127
+ ? "subsidiary program '%s' not found"
+ : status == INT_MAX
+ ? "subsidiary program '%s' failed"
+ : "subsidiary program '%s' failed (exit status %d)"),
+ subsidiary_program, status);
+ 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 (STREQ (name, "-"))
+ 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 = last_component (other_name);
+ size_t namelen = strlen (name), baselen = base_len (base);
+ bool insert_slash = *last_component (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);
+ r[namelen + insert_slash + baselen] = '\0';
+ 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 = rawmemchr (lf->bufpos, '\n');
+ if (lf->bufpos == lf->buflim)
+ {
+ 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 = rawmemchr (lf->bufpos, '\n');
+ if (lf->bufpos == lf->buflim)
+ {
+ 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 = rawmemchr (start, '\n');
+ 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);
+ set_program_name (argv[0]);
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+ c_stack_action (cleanup);
+
+ prog = getenv ("EDITOR");
+ if (prog)
+ editor_program = prog;
+
+ diffarg (DEFAULT_DIFF_PROGRAM);
+
+ /* parse command line args */
+ while ((opt = getopt_long (argc, argv, "abBdEHiI:lo:stvw:WZ", 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 = true;
+ break;
+
+ case 't':
+ diffarg ("-t");
+ break;
+
+ case 'v':
+ version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version,
+ AUTHORS, (char *) NULL);
+ check_stdout ();
+ return EXIT_SUCCESS;
+
+ case 'w':
+ diffarg ("-W");
+ diffarg (optarg);
+ break;
+
+ case 'W':
+ diffarg ("-w");
+ break;
+
+ case 'Z':
+ diffarg ("-Z");
+ 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;
+
+ case TABSIZE_OPTION:
+ diffarg ("--tabsize");
+ diffarg (optarg);
+ 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
+ {
+ char *command = system_quote_argv (SCI_SYSTEM, (char **) diffargv);
+ errno = 0;
+ diffout = popen (command, "r");
+ if (! diffout)
+ perror_fatal (command);
+ free (command);
+ }
+#else
+ {
+ int diff_fds[2];
+
+ if (pipe (diff_fds) != 0)
+ perror_fatal ("pipe");
+
+ diffpid = fork ();
+ 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);
+ 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 == ENOENT ? 127 : 126);
+ }
+
+ 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
+ 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 ();
+
+ check_child_status (werrno, wstatus, EXIT_FAILURE, diffargv[0]);
+ 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 void
+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, void (*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 = true;
+}
+
+/* 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 (0);
+
+ /* 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 or e1:\tEdit then use the left version.\n\
+er or e2:\tEdit then use the right version.\n\
+e:\tDiscard both versions then edit a new one.\n\
+l or 1:\tUse the left version.\n\
+r or 2:\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 IF_LINT (= 0);
+ int cmd1 IF_LINT (= 0);
+ bool gotcmd = false;
+
+ while (! gotcmd)
+ {
+ if (putchar ('%') != '%')
+ perror_fatal (_("write failed"));
+ ck_fflush (stdout);
+
+ cmd0 = skip_white ();
+ switch (cmd0)
+ {
+ case '1': case '2': case 'l': case 'r':
+ case 's': case 'v': case 'q':
+ if (skip_white () != '\n')
+ {
+ give_help ();
+ flush_line ();
+ continue;
+ }
+ gotcmd = true;
+ break;
+
+ case 'e':
+ cmd1 = skip_white ();
+ switch (cmd1)
+ {
+ case '1': case '2': case 'b': case 'd': case 'l': case 'r':
+ if (skip_white () != '\n')
+ {
+ give_help ();
+ flush_line ();
+ continue;
+ }
+ gotcmd = true;
+ break;
+ case '\n':
+ gotcmd = true;
+ break;
+ default:
+ give_help ();
+ flush_line ();
+ continue;
+ }
+ break;
+
+ case EOF:
+ if (feof (stdin))
+ {
+ gotcmd = true;
+ cmd0 = 'q';
+ break;
+ }
+ FALLTHROUGH;
+ default:
+ flush_line ();
+ FALLTHROUGH;
+ case '\n':
+ give_help ();
+ continue;
+ }
+ }
+
+ switch (cmd0)
+ {
+ case '1': case 'l':
+ lf_copy (left, llen, outfile);
+ lf_skip (right, rlen);
+ return true;
+ case '2': case 'r':
+ lf_copy (right, rlen, outfile);
+ lf_skip (left, llen);
+ return true;
+ case 's':
+ suppress_common_lines = true;
+ break;
+ case 'v':
+ suppress_common_lines = false;
+ break;
+ case 'q':
+ return false;
+ 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)
+ {
+ printint l1 = lline;
+ printint l2 = lline + llen - 1;
+ if (llen == 1)
+ fprintf (tmp, "--- %s %"pI"d\n", lname, l1);
+ else
+ fprintf (tmp, "--- %s %"pI"d,%"pI"d\n", lname, l1, l2);
+ }
+ FALLTHROUGH;
+ case '1': case 'b': case 'l':
+ lf_copy (left, llen, tmp);
+ break;
+
+ default:
+ lf_skip (left, llen);
+ break;
+ }
+
+ switch (cmd1)
+ {
+ case 'd':
+ if (rlen)
+ {
+ printint l1 = rline;
+ printint l2 = rline + rlen - 1;
+ if (rlen == 1)
+ fprintf (tmp, "+++ %s %"pI"d\n", rname, l1);
+ else
+ fprintf (tmp, "+++ %s %"pI"d,%"pI"d\n", rname, l1, l2);
+ }
+ FALLTHROUGH;
+ case '2': case 'b': case 'r':
+ lf_copy (right, rlen, tmp);
+ break;
+
+ default:
+ lf_skip (right, rlen);
+ break;
+ }
+
+ ck_fclose (tmp);
+
+ {
+ int wstatus;
+ int werrno = 0;
+ char const *argv[3];
+
+ ignore_SIGINT = true;
+ checksigs ();
+ argv[0] = editor_program;
+ argv[1] = tmpname;
+ argv[2] = 0;
+
+ {
+#if ! HAVE_WORKING_FORK
+ char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
+ wstatus = system (command);
+ if (wstatus == -1)
+ werrno = errno;
+ free (command);
+#else
+ pid_t pid;
+
+ pid = fork ();
+ if (pid == 0)
+ {
+ execvp (editor_program, (char **) argv);
+ _exit (errno == ENOENT ? 127 : 126);
+ }
+
+ if (pid < 0)
+ perror_fatal ("fork");
+
+ while (waitpid (pid, &wstatus, 0) < 0)
+ if (errno == EINTR)
+ checksigs ();
+ else
+ perror_fatal ("waitpid");
+#endif
+ }
+
+ ignore_SIGINT = false;
+ check_child_status (werrno, wstatus, EXIT_SUCCESS,
+ editor_program);
+ }
+
+ {
+ 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 true;
+ }
+ 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;
+ val = strtoumax (diff_help + 1, &numend, 10);
+ if (LIN_MAX < val || errno || *numend != ',')
+ fatal (diff_help);
+ llen = val;
+ val = strtoumax (numend + 1, &numend, 10);
+ if (LIN_MAX < val || errno || *numend)
+ fatal (diff_help);
+ rlen = val;
+
+ 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 false;
+ break;
+
+ default:
+ fatal (diff_help);
+ }
+
+ lline += llen;
+ rline += rlen;
+ }
+ }
+}
+
+/* Return true 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;
+ sprintf (buf, "%s/sdiffXXXXXX", dir);
+ fd = mkstemp (buf);
+ if (0 <= fd)
+ tmpname = buf;
+ return fd;
+}