Skip to content

fix: keep tmpfile names from exceeding the destination name - #240

Open
cpruijsen wants to merge 2 commits into
npm:mainfrom
cpruijsen:fix/issue-63
Open

cpruijsen wants to merge 2 commits into
npm:mainfrom
cpruijsen:fix/issue-63

Conversation

@cpruijsen

Copy link
Copy Markdown

Summary

getTmpname no longer appends a suffix that can push a legal destination basename past the filesystem NAME_MAX. If the destination basename is longer than 11 characters (the longest '.' + uint32 suffix), that unique suffix replaces the end of the basename so the tmpfile is not longer than the target. Names of 11 characters or fewer still get filename.<hash>, as before. Covers both the async and sync writers; they already share getTmpname.

Fixes #63. The request was that the temp name be "short or at least not longer than the target file name." The linked pnpm report (pnpm/pnpm#2605) overflowed on eCryptfs, whose NAME_MAX is 143, not 255.

What I chose and the alternative

Never lengthen a tmpfile basename that is already longer than the longest suffix. Same unique suffix as today (hash of this module's path, process.pid, worker threadId, invocation counter). Decide append-vs-replace using 11, not this invocation's decimal width, so a name like index.json (10) is not sometimes appended and sometimes truncated.

Alternatives: truncate only when the tmp basename would exceed 255; or use a short same-directory .$hash name (floated on PR #3); or retry open on ENAMETOOLONG.

A hardcoded 255 would not have fixed the eCryptfs case in the original report. A non-unique .tmp suffix was already rejected on PR #3. Retrying on ENAMETOOLONG would preserve package.json.<n> for typical files but is more code on both the sync and async paths. Node has no portable per-filesystem NAME_MAX.

I can switch to "truncate only past 255", to a short .$hash tmp name, or to .update(filename) in the hash, if you prefer.

Test plan

  • npx tap test/basic.js: getTmpname still unique; short names still match filename.{digits}; a 255-character basename produces a tmp path of equal length in the same directory
  • npx tap test/integration.js: sync and async writes of a 255-character basename succeed; tmpfileCreated reports a basename of length 255
  • Revert getTmpname to always append: the new tests fail with ENAMETOOLONG / a longer tmp path
  • Existing mocked error-propagation tests still pass (including rename failure when unlink also fails)

Appending a unique suffix to a basename that already saturates the
filesystem NAME_MAX made the tmpfile unopenable (ENAMETOOLONG). Replace
the end of the basename with the suffix so a legal destination name is
also a legal tmp name, including on filesystems whose NAME_MAX is not
255.

Fixes npm#63
Comparing against the actual uint32 decimal width made names of length
3-11 append or truncate depending on the hash. Use the maximum width
(11) so the choice is stable, while still never lengthening names that
can hit NAME_MAX.
@cpruijsen
cpruijsen requested a review from a team as a code owner September 11, 2026 15:01
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.

[BUG] The temporary file name is longer than the target file name

1 participant