Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[win][fs] Some functions do not support tailing dot "." and space " " in a filename #8836

Open
AlttiRi opened this issue Feb 10, 2024 · 5 comments
Labels
bug Something isn't working node.js Compatibility with Node.js APIs windows An issue that is known to occur on Windows

Comments

@AlttiRi
Copy link

AlttiRi commented Feb 10, 2024

What version of Bun is running?

1.0.26

What platform is your computer?

Window 10

What steps can reproduce the bug?

import fs from "node:fs/promises";

for (const filename of [" spaces.txt ", ".dots.txt."]) {
    await fs.writeFile(filename, `data:"${filename}"`);

    try {
        console.log((await fs.readFile(filename)).toString());
    } catch (e) { console.error(e); }

    try {
        console.log((await fs.stat(filename)).size);
    } catch (e) { console.error(e); }

    try {
        await fs.unlink(filename);
        continue;
    } catch (e) { console.error(e); }

    try {
        const newName = filename.replaceAll(/^[ .]+|[ .]+$/g, "");
        await fs.rename(filename, newName);
    } catch (e) { console.error(e); }
}

What is the expected behavior?

data:" spaces.txt "
19
data:".dots.txt."
17

The " spaces.txt ", ".dots.txt." files have been created, read, then deleted.

What do you see instead?

ENOENT: No such file or directory
   errno: -2
 syscall: "open"
   path: " spaces.txt "

ENOENT: No such file or directory
   errno: -2
 syscall: "stat"

ENOENT: No such file or directory
   errno: -2
 syscall: "unlink"

ENOENT: No such file or directory
   errno: -2
 syscall: "rename"

ENOENT: No such file or directory
   errno: -2
 syscall: "open"
   path: ".dots.txt."

ENOENT: No such file or directory
   errno: -2
 syscall: "stat"

ENOENT: No such file or directory
   errno: -2
 syscall: "unlink"

ENOENT: No such file or directory
   errno: -2
 syscall: "rename"

The " spaces.txt ", ".dots.txt." files have been created, but not read, or deleted.

Additional information

Tailing / heading dots "." and spaces " " are allowed by NTFS, while Windows Explorer does not like them (it works buggy with them).

Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not.

https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file


Bun can create (writeFile) them, but not read (readFile, stat) or delete (unlink, rename) them.


Rel:

@AlttiRi AlttiRi added the bug Something isn't working label Feb 10, 2024
@AlttiRi
Copy link
Author

AlttiRi commented Feb 10, 2024

By the way, I expected that it will not support ADS (Alternate Data Streams) too, but it works fine.

import fs from "node:fs/promises";

await fs.writeFile("ads.txt", `data:ads.txt`);
await fs.writeFile("ads.txt:metadata", `metadata:ads.txt`); // Alternate Data Stream

console.log((await fs.readFile("ads.txt")).toString());
console.log((await fs.readFile("ads.txt:metadata")).toString());
console.log((await fs.stat("ads.txt:metadata")).size);

await fs.unlink("ads.txt");
data:ads.txt
metadata:ads.txt
16

Anyway, it makes sense to add a test for it too in fs.test.ts, just in case.

@Electroid Electroid added windows An issue that is known to occur on Windows docker An issue that occurs when running in Docker node.js Compatibility with Node.js APIs and removed docker An issue that occurs when running in Docker labels Feb 10, 2024
@robobun robobun added docker An issue that occurs when running in Docker node.js Compatibility with Node.js APIs and removed node.js Compatibility with Node.js APIs docker An issue that occurs when running in Docker labels Feb 10, 2024
@AlttiRi
Copy link
Author

AlttiRi commented Feb 10, 2024

It's strange, but adding \\?\ does not help. While it should help.

https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#skip-normalization

Unless the path starts exactly with \\?\ (note the use of the canonical backslash), it is normalized.

Why would you want to skip normalization? There are three major reasons:

  1. To get access to paths that are normally unavailable but are legal. A file or directory called hidden., for example, is impossible to access in any other way.
  2. To improve performance by skipping normalization if you've already normalized.
  3. To skip the MAX_PATH check for path length to allow for paths that are greater than 259 characters.
import fs from "node:fs/promises";
import path from "node:path";

for (const filename of [" spaces.txt ", ".dots.txt."]) {
    // const prefixed_filename = path.toNamespacedPath(path.resolve(filename)); // still does not work #8247
    const prefixed_filename = "\\\\?\\" + path.resolve(filename);
    console.log(prefixed_filename);

    try {
        await fs.writeFile(prefixed_filename, `data:"${filename}"`);
    } catch (e) { console.error(e); }

    try {
        console.log((await fs.readFile(prefixed_filename)).toString());
    } catch (e) { console.error(e); }

    try {
        console.log((await fs.stat(prefixed_filename)).size);
    } catch (e) { console.error(e); }

    try {
        await fs.unlink(filename);
        continue;
    } catch (e) { console.error(e); }

    try {
        const newName = filename.replaceAll(/^[ .]+|[ .]+$/g, "");
        await fs.rename(prefixed_filename, newName);
    } catch (e) { console.error(e); }
}

More over, in this case fs.writeFile fails too.


In this case the prefix works:

@AlttiRi
Copy link
Author

AlttiRi commented Feb 10, 2024

Well, this works in Bun:

import fs from "node:fs/promises";
import path from "node:path";

for (const filename of [" spaces.txt ", ".dots.txt."]) {
    try {
        await fs.writeFile(filename, `data:"${filename}"`);
    } catch (e) { console.error(e); }

    const prefixed_filename = "\\\\?\\" + path.resolve(filename);
    console.log(prefixed_filename);

    try {
        console.log((await fs.readFile(prefixed_filename)).toString());
    } catch (e) { console.error(e); }

    try {
        console.log((await fs.stat(prefixed_filename)).size);
    } catch (e) { console.error(e); }

    try {
        await fs.unlink(filename);
        continue;
    } catch (e) { console.error(e); }

    try {
        const newName = filename.replaceAll(/^[ .]+|[ .]+$/g, "");
        await fs.rename(prefixed_filename, newName);
    } catch (e) { console.error(e); }
}

@AlttiRi
Copy link
Author

AlttiRi commented Feb 10, 2024

fs.writeFile works without "\\\\?\\", but with the prefix it stops to work.
Other functions begin to work after adding of this prefix.
All examples above works in Node.js (regardless of the presence of the prefix).

@paperdave
Copy link
Member

const prefixed_filename = "\\\\?\\" + path.resolve(filename);
is a simplified version of toNamespacedPath

i think once the path pr merges it might be worthwhile to use that api internally for all the fs stuff on windows. it probably should replace PosixToWinNormalizer, which is only really hitting SOME of the cases you present, and is why this only half works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working node.js Compatibility with Node.js APIs windows An issue that is known to occur on Windows
Projects
None yet
Development

No branches or pull requests

4 participants