mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
While walking a path, `realpathSync` skips the components it already knows are real, and in that branch it reads the shared stat buffer to decide whether the walk has reached a pipe or a socket. That buffer holds the result of the last stat made anywhere in the process rather than the last one made by the walk, so an unrelated stat of a FIFO ends the walk early and the path comes back with its symlinks unresolved. The unresolved path is then written to the cache, so every later resolution repeats it. The walk only takes that branch once the ancestors are established as real, which is the state the module loader's cache is in. The test goes through `require()` to reach it, where the stale read costs a second copy of a module reached through a symlink. Signed-off-by: Hendrik Liebau <mail@hendrik-liebau.de> PR-URL: https://github.com/nodejs/node/pull/65113 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
63 lines
1.9 KiB
JavaScript
63 lines
1.9 KiB
JavaScript
'use strict';
|
|
|
|
// The async realpath() reads the shared stat buffer the same way realpathSync()
|
|
// did, to decide whether the walk has reached a pipe or a socket. The walk's
|
|
// own fs.stat() does leave the right value there, but it is not read until
|
|
// after fs.readlink() and a process.nextTick(), and any stat completing in that
|
|
// window replaces it.
|
|
//
|
|
// Truncating the walk only costs something when a second symlink follows the
|
|
// one being resolved, so the path used here has two.
|
|
|
|
const common = require('../common');
|
|
|
|
if (common.isWindows)
|
|
common.skip('no mkfifo on Windows');
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execFileSync } = require('child_process');
|
|
const tmpdir = require('../common/tmpdir');
|
|
|
|
tmpdir.refresh();
|
|
|
|
const real = tmpdir.resolve('real');
|
|
const pkg = tmpdir.resolve('pkg');
|
|
const fifo = tmpdir.resolve('fifo');
|
|
|
|
fs.mkdirSync(real);
|
|
fs.mkdirSync(pkg);
|
|
fs.writeFileSync(path.join(real, 'index.js'), '');
|
|
fs.symlinkSync(path.join('..', 'real'), path.join(pkg, 'sub'));
|
|
fs.symlinkSync('pkg', tmpdir.resolve('link'));
|
|
execFileSync('mkfifo', [fifo]);
|
|
|
|
const throughLinks = tmpdir.resolve('link', 'sub', 'index.js');
|
|
const expected = path.join(real, 'index.js');
|
|
|
|
// Keep stats of the FIFO completing for as long as the walk runs, so that one
|
|
// of them lands in the buffer during the window.
|
|
let settled = false;
|
|
(function statFifo() {
|
|
if (settled) return;
|
|
fs.stat(fifo, statFifo);
|
|
})();
|
|
|
|
let error;
|
|
let resolvedPath;
|
|
|
|
fs.realpath(throughLinks, common.mustCall((err, resolved) => {
|
|
settled = true;
|
|
error = err;
|
|
resolvedPath = resolved;
|
|
}));
|
|
|
|
// Asserted on exit rather than in the callback. An assertion that fails inside
|
|
// this callback is lost: it does not reach an `uncaughtException` handler and
|
|
// the process still exits 0, so the test would pass over the bug it covers.
|
|
process.on('exit', () => {
|
|
assert.ifError(error);
|
|
assert.strictEqual(resolvedPath, expected);
|
|
});
|