Menu

#268 patch silently skips trailing diffs if long, piped to stdin

Patch
closed
GnuWin
Binaries (396)
5
2014-08-23
2005-07-15
No

I am using the GnuWin32 patch 2.5.9 under Windows XP.
There is a bug that causes silent misoperation for some
common input files when piped to input.

** Symptom:

Patch correctly applies hunks from at least one diff in
the file, but exits normally (says "done" with
--verbose) without applying one or more remaining diffs
in the file.

** How to Reproduce:

1) Create a patch file such that NL > NC where:
NL is the number of lines in the file up to the last
line of a diff that will be applied and
NC is the number of characters/bytes in the rest of the
file including diffs that will be missed

2) Attempt to apply the patch by piping to stdin:
cat <test_patch_file> | patch -d <directory> -p1 --verbose

Note that this is a common type of patch file,
resulting whenever the last file alphabetically has a
small change, following diffs of sufficient number and
length. Or it could be a long email message with more
than one file diff at the end. One reason someone would
pipe input to patch would be to apply several
successive patches in one operation by concatenating
the patch files in the right order as one input -- so
the size of the patch input is the combined size of the
successive patches.

** Confirmed Workaround:

Use input redirection or specify input file as argument.
patch -d <directory> -p1 < <test_patch_file>

** Suspected Cause:

See source code file pch.c.

At line 206, if pointer into file exceeds file length,
say "done" instead of looking for more diffs. OK.

At line 161, filesize is taken from stat results
structure. OK.

At lines 141 to 143, for stdin not a regular file, the
size in the stat results structure is overwritten by
counting charsread from the fread function while
creating the temporary file. I confirmed that the
temporary file is created correctly, an exact copy of
the input, but I suspect that fread reports the wrong
charsread.

I found one version of fread doc that is confusing. It
says that CR/LF is replaced by a single newline
character for a file opened in text mode, but it claims
that the count returned and the file position are not
affected by the substitution. Similarly, fwrite
substitutes CR/LF for a newline on the way out. It
seems to me that the count is in fact affected to
represent the actual number of bytes written to the
buffer, but a byte is missing in the buffer for every
end-of-line pair of bytes in the files. Everything
would work fine except that the file position pointer
counts two bytes for end-of-line.

Specifying the --binary flag does not work around the
problem, because the files are in fact text. The patch
fails to apply on every hunk with --binary specified.

** Proposed Fix:

My first proposal was to apply the patch below.
However, in email with patch maintainer Paul Eggert,
bug-patch@gnu.org, I proposed another fix that he
thought was better.

I do not have tools to build and test either fix, and
Paul needs a tested change to implement.

The fix Paul prefers is to remove the character
counting and add a "file_tell(pfp)" before seeking back
to the beginning of the temporary file in line 149 (pch.c).

Here's the other possible fix, to fstat the temporary file:

--- pch.c 2005-03-23 21:28:44.000000000 -0600
+++ pch_proposed.c 2005-06-23 09:40:15.343511200 -0500
@@ -138,9 +138,7 @@
printf ("TMPPATNAME: %s\n", TMPPATNAME);
if (!pfp)
pfatal ("Can't open stream for file %s",
quotearg (TMPPATNAME));
- for (st.st_size = 0;
- (charsread = fread (buf, 1, bufsize, stdin)) != 0;
- st.st_size += charsread)
+ for ( ; (charsread = fread (buf, 1, bufsize,
stdin)) != 0; )
if (fwrite (buf, 1, charsread, pfp) != charsread)
write_fatal ();
if (ferror (stdin) || fclose (stdin) != 0)
@@ -148,6 +146,8 @@
if (fflush (pfp) != 0
|| file_seek (pfp, (file_offset) 0, SEEK_SET) != 0)
write_fatal ();
+ if (fstat (fileno (pfp), &st) != 0)
+ pfatal ("fstat");
}
}
else

Discussion

  • GnuWin

    GnuWin - 2005-07-17

    Logged In: YES
    user_id=217802

    I'm not sure I understand well enough the description of the
    type of patch file. Could you perhaps provide a small test file
    that exhibits this bugs?

     
  • Joel VanderZee

    Joel VanderZee - 2005-07-18

    a diff file against patch-2.5.9-src demonstrating bug

     
  • Joel VanderZee

    Joel VanderZee - 2005-07-18

    Logged In: YES
    user_id=1313457

    I'll assume that you have the source for patch 2.5.9, so I
    can provide a diff file to apply against it to demonstrate
    the failure. All that this patch does is rearrange functions
    in pch.h and modify the version function.

    Here's what happens when I run it:
    C:...>cat bug_diff.txt | patch -d patch-2.5.9-src-copy -p1
    TMPPATNAME: C:...\Temp/ppwjvxkc
    patching file pch.c

    C:...

    But if you look at the end of the file, you will see that it
    includes a diff for version.c as well. Version.c is not
    patched, and there is no error message. If you rearrange the
    file so that the version.c diff comes first, then both files
    will be patched correctly.

    All you need is patch file with more lines in the part that
    works than the characters in the rest of the file (that will
    be missed). It's probably not hard to find examples. Any
    project with a large enough change where the last file is
    something like "version.h" with a one-line change will fail
    to patch correctly (under Windows).

     
  • GnuWin

    GnuWin - 2005-07-21

    Logged In: YES
    user_id=217802

    This bug has been introduced by my trying to solve
    the "Assertion failed, hunk, file patch.c, line 343"-bug.
    Inadvertently, I left some changes in pch.c and inp.c, and
    these cause the bug you have described. So, there is no
    need to correct the original sources, and I'll upload a new
    port. I'm sorry for the confusion this has created and for the
    trouble you have taken to spot the cause. Anyway, thanks for
    reporting this.

     
  • GnuWin

    GnuWin - 2005-07-26

    Logged In: YES
    user_id=217802

    This bug has been fixed. A new release is available through
    http://gnuwin32.sourceforge.net/