Skip to content

A long-path self-test aborted or passed depending on the length of $TMPDIR - #1411

Open
xroche wants to merge 5 commits into
masterfrom
fix-1409-tmpdir-assert
Open

A long-path self-test aborted or passed depending on the length of $TMPDIR#1411
xroche wants to merge 5 commits into
masterfrom
fix-1409-tmpdir-assert

Conversation

@xroche

@xroche xroche commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Four long-path self-tests built their directory by appending fixed-size segments while a counter stayed under 300, then asserted the result had passed MAX_PATH. The starting length is the base directory's, so the final value lands somewhere different for every $TMPDIR, and for a band of lengths it lands on exactly 260, where the > 260 assertion aborts. Two of the four asserted on the directory alone, so they could fire; the other two had a leaf name's worth of slack and never did. It reads as an intermittent CI abort because where a machine's build tree happens to live is what decides whether it trips.

The four copies are now one helper that loops on the MAX_PATH comparison itself, so no base length can leave it short. Both tests sweep 41 consecutive base-dir lengths, one segment's worth, so every residue is covered whatever $TMPDIR is. The pre-fix engine aborts at two of the 41 and the fixed one at none, standalone and through make check.

The sweep on its own would not have caught a regression. The assertion it replaced was the only thing pinning that the path ever exceeded MAX_PATH, so reverting the loop with that assertion gone reintroduces the bug and the suite stays green. The postcondition is now asserted inside the helper and kept at both call sites, and both mutants go red.

One thing found while in here: the bound was in bytes, but Windows measures MAX_PATH in UTF-16 units, so the 5-byte, 2-unit non-ASCII segment let an exit at 261 bytes be 259 units, under the limit these tests exist to cross. The helper now charges that segment by code point. That part has no local red test, since only the Windows path can observe it.

Closes #1409

xroche and others added 2 commits August 24, 2026 21:19
st_direnum and st_cookieimport built their directory by appending fixed-size
segments while a counter stayed under 300, then asserted the result had
exceeded MAX_PATH. The starting length is the base directory's, so the final
value lands somewhere different for every $TMPDIR, and for a band of lengths
it lands on exactly 260, where the assertion aborts. It read as an
intermittent CI abort.

Fold the four copies of that loop into st_mkdeep(), which stops on the
MAX_PATH comparison itself. Both tests now sweep a full segment's worth of
base-dir lengths.

Closes #1409

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Review caught the first version deleting assertf(dirlen > 260) from st_direnum
and st_cookieimport on the grounds that st_mkdeep now guarantees it. That
assertion was the only thing pinning that the path ever exceeded MAX_PATH:
with it gone, reverting the loop to the old arithmetic bound reintroduces
#1409 and the whole suite stays green.

Assert the postcondition inside st_mkdeep and keep it at the two call sites,
where it also catches a helper returning the base length rather than the deep
one. Both mutants now go red.

Charge the non-ASCII segment by code point while here: Windows measures
MAX_PATH in UTF-16 units, so a byte-only bound can exit at 261 bytes and 259
units, under the limit these tests exist to cross.

Reuse repeat_chars for the padding, and fail the sweep if it queues nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche enabled auto-merge (squash) August 24, 2026 19:40
xroche and others added 3 commits August 24, 2026 22:36
st_mkdeep charged its slack only against the non-ASCII segment it appends, so a
base directory that is itself non-ASCII made the byte count overstate the length
Windows measures MAX_PATH in, and the loop stopped short of the limit. With a
30-é base, direnum built a 264-byte directory that is 231 UTF-16 units: #1409's
bug moved from the length axis to the charset axis.

The loop and the guarantee it asserts now go through st_utf16_units(), which
counts the whole buffer and charges a non-BMP code point the two units its
surrogate pair costs. A byte it cannot read is charged short, which only ever
builds a longer path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
The sweep ran on mktemp -d, which is ASCII, so no mutant of st_mkdeep's slack
arithmetic could red it: the base now carries 15 é ahead of the ASCII pad, which
is what makes the charset axis observable at all. longpath-io and mirror-io join
the sweep too — they had lost their own stop bound and gained no replacement, so
restoring the old arithmetic one left both green while the other two failed.

The sweep helper takes a mode, and direnum asserts a head that says something
(two leaves enumerated) rather than a tail that only pins ": OK". What actually
detects #1409 is st_mkdeep's assertf, and the helper now says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Three probes passed -O "$(mktemp -d)" inline, so nothing held the name and
nothing removed it. One trapped directory serves all three; none of them crawls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche

xroche commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

Follow-up commits address the review P0: st_mkdeep charged its UTF-16 slack only against the non-ASCII segment it appends and never against the base directory, so a non-ASCII $TMPDIR made the byte count overstate the real length and the loop stopped under MAX_PATH — the same bug as #1409, moved from the length axis to the charset axis. With a 30-é base, direnum built a 264-byte directory that is 231 UTF-16 units; longpath, mirrorio and cookieimport all landed short for part of the length band too.

The loop and the guarantee it asserts now go through st_utf16_units(), which counts the whole path and charges a code point outside the BMP the two units its surrogate pair costs. A byte it cannot decode is charged short, and short only ever builds a longer path, which is the safe direction here. Same 30-é base now yields 272 units, and a 840-case sweep (é, 中, 😀, mixed and pure-ASCII fills, every base length, four self-tests) has nothing under 260.

The sweep base is non-ASCII now as well: on mktemp -d alone no mutant of that arithmetic can red, which is why the bug survived the sweep. longpath-io and mirror-io join the sweep — they had lost their own stop bound and gained no replacement, and restoring the old n + sizeof(seg) - 1 < 300 bound left both green while direnum and cookieimport failed; with the follow-up all four red. Restoring the nseg-only slack reds all four too. Also here: direnum asserts a head that says something (two leaves enumerated) instead of a tail that only pins ": OK", the helper states plainly that st_mkdeep's assertf is what detects #1409, *baselen is written on every path, the 64-byte leaf headroom is named, and 270_local-host-alias.test stops leaking a mirror directory per run from three inline -O "$(mktemp -d)".

Full suite green here: 383 total, 370 pass, 13 skip, 0 fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

st_cookieimport and st_direnum abort or pass depending on the length of $TMPDIR

1 participant