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