Hi Scott,

Scott Orme <[email protected]> writes:

> wc -c reports too few bytes when its standard input is an inherited,
> seekable regular file whose size is an exact multiple of the system page
> size and whose descriptor is positioned at a non-zero offset. (Named file
> operands are unaffected: wc opens them itself at offset 0. Only a
> pre-positioned inherited stdin triggers it.) This is a documented, supported
> case -- the comment at src/wc.c (~lines 400-408) states wc must report fewer
> than st_size bytes when stdin is not at the beginning, and bug#61300 was
> accepted on that same premise.
> 
> Reproducer (portable; P = page size):
> 
>     P=$(getconf PAGESIZE)
>     head -c $((2 * P)) /dev/zero > f            # size = 2 pages
>     (dd bs=1 skip=100 count=0 2>/dev/null; wc -c) < f
> 
> On a 4096-byte-page machine this prints 7992; the correct answer is 8092
> (8192 - 100). Cross-check: `tail -c +101 f | wc -c` prints 8092.
> 
> More strikingly, at a larger offset the count collapses toward 1:
> 
>     (dd bs=1 skip=4096 count=0 2>/dev/null; wc -c) < f   # prints 1,
> should be 4096
> 
> Affected: reproduced on 9.11 and 9.12, and the code is byte-identical in git
> master (as of 2026-10-07); also reproduced on the 8.32 system binary. The
> defective seek was introduced by commit 2662702b (2014-10-07, the fix for
> bug#18621) and first released in 8.24; it affected every sized file at a
> non-zero offset until commit e17e5f40 (released 8.27, 2016-12) narrowed it to
> the page-multiple case. So every release from 8.24 through 9.12 is affected.
> 
> Root cause -- src/wc.c, the byte-only fast path, the branch taken when the
> size is an exact multiple of the page size:
> 
>     else
>       {
>         off_t hi_pos = (end_pos
>                         - end_pos % (STP_BLKSIZE (&fstatus->st) + 1));
>         if (0 <= current_pos && current_pos < hi_pos
>             && 0 <= lseek (fd, hi_pos, SEEK_CUR))      /* <-- bug */
>           bytes = hi_pos - current_pos;
>       }

Nice catch. Given the preceeding checks in that if statement, I suspect
Paul meant to write something like this:

diff --git a/src/wc.c b/src/wc.c
index 272a905f5..adb38314e 100644
--- a/src/wc.c
+++ b/src/wc.c
@@ -447,7 +447,7 @@ wc (int fd, char const *file_x, struct fstatus *fstatus)
               off_t hi_pos = (end_pos
                               - end_pos % (STP_BLKSIZE (&fstatus->st) + 1));
               if (0 <= current_pos && current_pos < hi_pos
-                  && 0 <= lseek (fd, hi_pos, SEEK_CUR))
+                  && 0 <= lseek (fd, hi_pos - current_pos, SEEK_CUR))
                 bytes = hi_pos - current_pos;
             }
         }

I think either would work fine, but I'll give him a chance to look over
it before pushing anything.

Thank you for the detailed report and suggested fix.

Collin



Reply via email to