streamich / memfs

JavaScript file system utilities
http://streamich.github.io/memfs/
Apache License 2.0
1.77k stars 130 forks source link

memfs Symlinks do not provide correct behavior when traversing the link as a directory #1035

Open tistocks opened 5 months ago

tistocks commented 5 months ago

Given the following:

// symbolicLinkTest.ts
// src/symbolicLinkTest.ts
import * as osPath from 'path';
import { Volume, createFsFromVolume, IFs } from 'memfs';
import * as realFs from 'fs';

// Function to execute tasks
function executeTasks(fs: IFs) {
    // Define directories
    const testDir = osPath.join(__dirname, 'test-run');
    const targetDir = osPath.join(testDir, 'target');
    const linkDir = osPath.join(testDir, 'link');
    const targetSubDir = osPath.join(targetDir, 'subDir');
    const targetSubDirFile = osPath.join(targetSubDir, 'test.txt');

    // Create directory structure
    fs.mkdirSync(targetSubDir, { recursive: true });
    fs.writeFileSync(targetSubDirFile, "Hello World");
    fs.symlinkSync(targetDir, linkDir);

    // Function to check existence of a file through a link
    function checkFileExistence(linkPath: string, filePath: string) {
        const resolvedFilePath = osPath.join(linkPath, filePath);
        const resolvedDirPath = osPath.dirname(resolvedFilePath);
        console.log(`Does '${resolvedFilePath}' exist? ${fs.existsSync(resolvedFilePath)}`);
        console.log(`Of course it's a child of it's directory: ${fs.lstatSync(resolvedDirPath).isDirectory()}`);
    }

    // Test symbolic link
    console.log("Testing symbolic link:");
    checkFileExistence(linkDir, 'subDir/test.txt');
}

// Execute tasks with real file system (realFs)
console.log("Running with real file system (realFs):");
executeTasks(realFs as unknown as IFs);

// Create in-memory file system (memfs)
const vol = new Volume();
const memFs = createFsFromVolume(vol);

// Execute tasks with in-memory file system (memfs)
console.log("\nRunning with in-memory file system (memfs):");
executeTasks(memFs);

The following results are received:

% node symbolicLinkTest.js
Running with real file system (realFs): Testing symbolic link: Does '/Users/timothystockstill/symbolic-link-test/test-run/link/subDir/test.txt' exist? true Of course it's a child of it's directory: true

Running with in-memory file system (memfs): Testing symbolic link: Does '/Users/timothystockstill/symbolic-link-test/test-run/link/subDir/test.txt' exist? true /Users/timothystockstill/symbolic-link-test/node_modules/memfs/lib/volume.js:927 throw (0, util_1.createError)(ENOENT, 'lstat', filename); ^

Error: ENOENT: no such file or directory, lstat '/Users/timothystockstill/symbolic-link-test/test-run/link/subDir' at createError (/Users/timothystockstill/symbolic-link-test/node_modules/memfs/lib/node/util.js:143:19) at Volume.lstatBase (/Users/timothystockstill/symbolic-link-test/node_modules/memfs/lib/volume.js:927:42) at Volume.lstatSync (/Users/timothystockstill/symbolic-link-test/node_modules/memfs/lib/volume.js:932:21) at checkFileExistence (/Users/timothystockstill/symbolic-link-test/symbolicLinkTest.js:24:76) at executeTasks (/Users/timothystockstill/symbolic-link-test/symbolicLinkTest.js:28:5) at Object. (/Users/timothystockstill/symbolic-link-test/symbolicLinkTest.js:38:1) at Module._compile (node:internal/modules/cjs/loader:1233:14) at Module._extensions..js (node:internal/modules/cjs/loader:1287:10) at Module.load (node:internal/modules/cjs/loader:1091:32) at Module._load (node:internal/modules/cjs/loader:938:12) { code: 'ENOENT', path: '/Users/timothystockstill/symbolic-link-test/test-run/link/subDir' }

Node.js v20.5.0

Expected results:

No exception because the parent directory of the file that memFs confirmed to exist does in fact also exist (it has to).

I believe the problem is related to how getChild handles symlinks, when determining file path steps, but haven't fully investigated the code.

Is this expected behavior for memFs, is this a bug, is there a work around or will I need to look deeper at getChild?