Repository: nodejs/node
Pull request: fs,win: fix readdir for named pipe

This PR fixes the issue by giving the user the responsibility to add/remove trailing slashes for `fs.readdir`. Examples:

```
> require('fs').readdirSync('\\\\.\\pipe').length
Uncaught Error: ENOTDIR: not a directory, scandir '\\.\pipe'
    at Object.readdirSync (node:fs:1503:26) {
  errno: -4052,
  code: 'ENOTDIR',
  syscall: 'scandir',
  path: '\\\\.\\pipe'
}
> require('fs').readdirSync('\\\\.\\pipe\\').length
243
```

For additional context, most of the paths are resolved in [resolve()](https://github.com/nodejs/node/blob/61e4ad535281fca6418e1417175253560253deca/lib/path.js#L189) in Node.js. This relies on [normalizeString()](https://github.com/nodejs/node/blob/61e4ad535281fca6418e1417175253560253deca/lib/path.js#L77) which removes the trailing slashes. Since `resolve()` is widely used, any change in this function can cause many breaks as seen [here](https://github.com/nodejs/node/pull/55414).

Additionally, `fs.readdir()` internally relies on [NtQueryDirectoryFile](https://github.com/nodejs/node/blob/61e4ad535281fca6418e1417175253560253deca/deps/uv/src/win/fs.c#L1395). This API doesn't allow us to use pipe paths without trailing slashes. On the other hand, `fs.openSync()` uses a different underlying API and works without trailing slashes in the physical drive paths. This difference highlights some inconsistencies across API functions, which seem to stem from the combined behaviors of libuv and the Windows API. 

I'm open to suggestions.

cc: @Flarna


Fixes: https://github.com/nodejs/node/issues/56002
Refs: https://github.com/nodejs/node/pull/55623
Refs: https://github.com/nodejs/node/pull/56088

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: src/node_file.cc
patch: @@ -1986,8 +1986,29 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
 
   BufferValue path(isolate, args[0]);
   CHECK_NOT_NULL(*path);
+#ifdef _WIN32
+  // On Windows, some API functions accept paths with trailing slashes,
+  // while others do not. This code checks if the input path ends with
+  // a slash (either '/' or '\\') and, if so, ensures that the processed
+  // path also ends with a trailing backslash ('\\').
+  bool slashCheck = false;
+  if (path.ToStringView().ends_with("/") ||
+      path.ToStringView().ends_with("\\")) {
+    slashCheck = true;
+  }
+#endif
+
   ToNamespacedPath(env, &path);
 
+#ifdef _WIN32
+  if (slashCheck) {
+    size_t new_length = path.length() + 1;
+    path.AllocateSufficientStorage(new_length + 1);
+    path.SetLengthAndZeroTerminate(new_length);
+    path.out()[new_length - 1] = '\\';
+  }
+#endif
+
   const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8);
 
   bool with_types = args[2]->IsTrue();

filename: test/parallel/test-fs-readdir-pipe.js
patch: @@ -0,0 +1,21 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const { readdir, readdirSync } = require('fs');
+
+if (!common.isWindows) {
+  common.skip('This test is specific to Windows to test enumerate pipes');
+}
+
+// Ref: https://github.com/nodejs/node/issues/56002
+// This test is specific to Windows.
+
+const pipe = '\\\\.\\pipe\\';
+
+const { length } = readdirSync(pipe);
+assert.ok(length >= 0, `${length} is not greater or equal to 0`);
+
+readdir(pipe, common.mustSucceed((files) => {
+  assert.ok(files.length >= 0, `${files.length} is not greater or equal to 0`);
+}));