Skip to content

fs.promises.cp() is broken for file names with certain characters #61121

@petcap

Description

@petcap

Version

v24.12.0

Platform

Linux 6.14.0-37. Tested with several different distros.

Subsystem

fs

What steps will reproduce the bug?

If fs.promises.cp() is called with the "recursive" flag set to true and it encounters a file name which contains characters that is not valid UTF8, the copy will fail.

To reproduce, first create a source folder with a file that contains an UTF8 invalid file name:

$ mkdir source
$ touch source/test_$'\240'.txt

Then, implement a simple JS script that copies the source folder:

const fs = require("fs").promises;
fs.cp("source", "destination", { recursive: true }).then(() => console.log("Done!"))

Execute the script:

$ node cp.js 
node:internal/fs/promises:1031
  const result = await PromisePrototypeThen(
                 ^

Error: ENOENT: no such file or directory, lstat 'source/test_�.txt'
    at async lstat (node:internal/fs/promises:1031:18)
    at async Promise.all (index 0)
    at async checkPaths (node:internal/fs/cp/cp:77:39)
    at async copyDir (node:internal/fs/cp/cp:320:35)
    at async mkDirAndCopy (node:internal/fs/cp/cp:310:3) {
  errno: -2,
  code: 'ENOENT',
  syscall: 'lstat',
  path: 'source/test_�.txt'
}

Since the file name contains \240 (or 0xA0 in hex) which is not a valid UTF8 character, the UTF8 decoder replaces it with , causing the stat() call to a file name which does not exist.

How often does it reproduce? Is there a required condition?

Reproduces every time. Tested on Debian, Ubuntu, Red Hat and Fedora. All NodeJS versions I have tested have this bug, including the latest LTS.

What is the expected behavior? Why is that the expected behavior?

I expect fs.promises.cp() to just work regardless of the file names on whatever platform NodeJS is running on.

What do you see instead?

N/A

Additional information

I haven't checked the NodeJS code but I suspect the file names are stored in UTF8 internally. In Linux, file names are essentially byte arrays and can contain any data with a few exceptions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions