Skip to content

require() no longer follows symlinks after calling statSync() on a FIFO or socket file #51142

@oliverfrye

Description

@oliverfrye

Version

v20.10.0

Platform

Linux debian 6.1.0-13-arm64 #1 SMP Debian 6.1.55-1 (2023-09-29) aarch64 GNU/Linux

Subsystem

fs

What steps will reproduce the bug?

This issue was discovered when using pnpm and dependencies downloaded from NPM, but it can be reproduced with some simple shell commands instead.

  1. In lieu of PNPM, create a node_modules structure that emulates pnpm's virtual store technique where dependencies are symlinked from the root of the node_modules into a virtual store directory called .pnpm:
mkdir -p node_modules/.pnpm/top-level-dependency/node_modules/top-level-dependency
echo 'require("transitive-dependency");' > node_modules/.pnpm/top-level-dependency/node_modules/top-level-dependency/index.js
mkdir -p node_modules/.pnpm/top-level-dependency/node_modules/transitive-dependency
touch node_modules/.pnpm/top-level-dependency/node_modules/transitive-dependency/index.js
cd node_modules
ln -s .pnpm/top-level-dependency/node_modules/top-level-dependency
cd ..
  1. Create a FIFO or socket file. Here, it is simple to use the mkfifo utility:
mkfifo fifo
  1. Create two scripts, one of which will demonstrate the bug, and the other which will serve as control:
cat <<EOF > fail.js
require("fs").statSync("fifo");
require("top-level-dependency");
console.log("Success!");
EOF

cat <<EOF > success.js
require("fs");
require("top-level-dependency");
console.log("Success!");
EOF

Note that these files differ only by the removal of the statSync call.

How often does it reproduce? Is there a required condition?

Every time. The only requirement I have found is that the argument to statSync() be either a socket or FIFO file (i.e. a directory or regular file would not cause the issue).

What is the expected behavior? Why is that the expected behavior?

$ node success.js
Success!

This behaviour is expected because Node is supposed to follow symlinks when resolving modules and use the real path when resolving the transitive dependencies of those modules.

What do you see instead?

$ node fail.js
node:internal/modules/cjs/loader:1147
  throw err;
  ^

Error: Cannot find module 'transitive-dependency'
Require stack:
- /home/oliver/node-require-symlink-repro/node_modules/top-level-dependency/index.js
- /home/oliver/node-require-symlink-repro/fail.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1144:15)
    at Module._load (node:internal/modules/cjs/loader:985:27)
    at Module.require (node:internal/modules/cjs/loader:1235:19)
    at require (node:internal/modules/helpers:176:18)
    at Object.<anonymous> (/home/oliver/node-require-symlink-repro/node_modules/top-level-dependency/index.js:1:1)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Module.require (node:internal/modules/cjs/loader:1235:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/home/oliver/node-require-symlink-repro/node_modules/top-level-dependency/index.js',
    '/home/oliver/node-require-symlink-repro/fail.js'
  ]
}

Node.js v20.10.0

Additional information

I have debugging output with NODE_DEBUG that shows that the symlink from the root of the node_modules is not followed into the .pnpm subdirectory as it should in the case that the statSync() call is made.

I have also got strace output that I can share, if that would be of assistance.

My colleagues have been able to reproduce this issue, both on the same Debian OS as me, and on MacOS (i.e. Darwin).

Metadata

Metadata

Assignees

No one assigned

    Labels

    fsIssues and PRs related to the fs subsystem / file system.loadersIssues and PRs related to ES module loaders

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions