unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
error-handlingrefactoringtesting-strategy

fs: fix realpath of namespaced drive paths

Project: nodejs/node | Language: JavaScript | Concepts: error-handling, refactoring, testing-strategy | Difficulty: mid
Link: https://github.com/nodejs/node/pull/65378

The problem

On Windows, the JavaScript implementation of fs.realpath did not correctly handle "namespaced" drive paths (e.g., \\?\C:\). When probing such a root, the path handling logic would drop the trailing separator, causing lstat to receive a path like C: instead of C:\. This led to incorrect errors (e.g., EISDIR) or failures to resolve the real path as expected.

Why it's non-trivial

A naive fix might simply pass the path through as-is, but Windows path resolution rules are subtle: namespaced paths need to be preserved for traversal and returned values, but the probe to check if the root exists must use the non-namespaced spelling to avoid losing the trailing separator. Failing to distinguish these cases can break path resolution or introduce subtle bugs.

The approach

This PR introduces a targeted fix by:

  1. Detecting namespaced drive roots and normalizing them for probing only
    Adds a helper to strip the namespace for lstat probes, but leaves the original path untouched for traversal and return.

    +let getRealpathRootLstatPath;
    if (isWindows) {
      ...
    +  const namespacedDriveRootRe = /^\\\\\?\\([a-zA-Z]:\\)$/;
    ...
    +  getRealpathRootLstatPath = function getRealpathRootLstatPath(path) {
    +    const match = SideEffectFreeRegExpPrototypeExec(namespacedDriveRootRe, path);
    +    return match === null ? path : match[1];
    +  };
    }
    

    This snippet defines a regex to match namespaced drive roots and a function to extract the regular drive root (C:\) from a namespaced path (\\?\C:\). This function is only used for probing the root with lstat.

  2. Using the helper when probing the root in realpath logic
    All places that check the existence of a root on Windows now use the helper to ensure the correct path is passed to lstat.

    -    const out = binding.lstat(base, false, undefined, true /* throwIfNoEntry */);
    +    const out = binding.lstat(
    +      getRealpathRootLstatPath(base), false, undefined, true /* throwIfNoEntry */);
    

    This ensures that the probe for the drive root works even if the input is a namespaced path.

    Similarly, in the async version:

    -    fs.lstat(base, (err) => {
    +    fs.lstat(getRealpathRootLstatPath(base), (err) => {
    
  3. Comprehensive Windows-specific tests
    Adds a new test file to check all relevant behaviors, including resolving namespaced paths, error cases, and traversing junctions.

    +test('fs.realpathSync resolves a namespaced drive path', () => {
    +  assertNamespacedRealpath(fs.realpathSync(namespacedEntry));
    +});
    +
    +test('fs.realpathSync reports ENOENT for a missing namespaced drive path', () => {
    +  assert.throws(() => fs.realpathSync(namespacedMissing), { code: 'ENOENT' });
    +});
    +
    +test('fs.realpathSync resolves a namespaced path through a junction', () => {
    +  assert.strictEqual(fs.realpathSync(namespacedJunctionEntry), targetEntry);
    +});
    

    These tests verify that namespaced drive paths are handled correctly in all edge cases.

From the review thread

No notable review discussion on this PR.

Takeaways

  • Handle platform-specific path quirks explicitly: When working with filesystem paths, especially on Windows, always account for special cases like namespaced paths and drive roots.
  • Isolate platform-specific logic in helpers: Encapsulate tricky normalization or transformation logic in dedicated functions to avoid scattering subtle path handling throughout the codebase.
  • Comprehensive testing for edge cases: When fixing bugs involving platform-specific behavior, add targeted tests that cover all relevant scenarios, including error conditions and path traversals.