Skip to content

readdir(p, { withFileTypes: true }) seemingly returns wrong type #30646

@apparebit

Description

@apparebit
  • Version: v13.1.0
  • Platform: Darwin White-Star.local 18.7.0 Darwin Kernel Version 18.7.0: Sat Oct 12 00:02:19 PDT 2019; root:xnu-4903.278.12~1/RELEASE_X86_64 x86_64
  • Subsystem: fs

When listing a directory with readdir and withFileTypes, at least the promisified version of fs.readdir returns the wrong type for entities that are symbolic links to directories:

const { basename, join } = require('path');
const { readdir, lstat, stat, symlink, unlink } = require('fs').promises;
const LINK_NAME = basename(__filename) + '-link';
const LINK_PATH = join(__dirname, LINK_NAME);

const inspectEntity = (label, entity) => {
  console.log(label, (entity.isDirectory() ? 'dir' : '') +
    (entity.isFile() ? 'file' : '') +
    (entity.isSymbolicLink() ? 'symlink' : ''));
};

(async function main() {
  try {
    await symlink(__dirname, LINK_PATH, 'dir');

    for (const entity of await readdir(__dirname, { withFileTypes: true })) {
      if (entity.name === LINK_NAME) inspectEntity('readdir', entity);
    }
    inspectEntity('lstat', await lstat(LINK_PATH));
    inspectEntity('stat', await stat(LINK_PATH));

    await unlink(LINK_PATH);
  } catch (x) {
    console.error(x.stack);
  }
})();

Running the above script yields the following on my machine:

readdir file
lstat symlink
stat dir

Since the file system entity in question is a symbolic link to a directory, I would expect the code to either report a symlink or a directory, depending on whether the implementation of readdir uses stat or lstat under the covers. But in neither case would I expect a file to be reported. Yet that's exactly what's happening on my machine. Am I missing something or is this a genuine bug?

Additional testing with a second machine suggests that this may be related to remote file system usage. When running the test locally on a local disk, readdir reports a symlink (yay!). That stands in contrast to my original testing was performed on a remotely mounted file system.

As an aside, after reading a number of old issues before filing this issue, I am guessing that readdir uses stat not lstat under the covers. It would seem more consistent with the rest of the API. Yet the documentation has no disclaimer on dirent.isSymbolicLink() as it has on stats.isSymbolicLink(). Either way, being more upfront about readdir's behavior {withFileTypes: true} would go a long way towards a better developer experience. I'm happy to submit a PR for that—once I understand what's going on above.

Even with better documentation though, some developers will be disappointed when whatever readdir uses does not meet their use case. So I am also wondering whether configurability of stat/lstat for readdir withFileTypes: true would be desirable in the long term. If that suggestion falls under the heading "long-term costs of exposing things from core," I apologize. I'm gonna practice writing code without using withFileTypes for certain tonight. 😜

Metadata

Metadata

Assignees

No one assigned

    Labels

    fsIssues and PRs related to the fs subsystem / file system.macosIssues and PRs related to the macOS platform / OSX.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions