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; +}