Repository navigation
fs.copyFile fails with permission denied when source is read-only #37284
Description
Activity
- addedfsIssues and PRs related to file-system APIs and the fs module.Issues and PRs related to file-system APIs and the fs module.
on Feb 9, 2021 It seems to be working for
v15.2.0:❯ cat test.js const fs = require('fs'); fs.copyFileSync('a/file', 'b/file'); ❯ mkdir a b ❯ touch a/file ❯ ls -l a/file -rw-r--r-- 1 raisinten raisinten 0 Feb 9 19:35 a/file ❯ chmod -w a/file ❯ node test.js ❯ ls -l a/file b/file -r--r--r-- 1 raisinten raisinten 0 Feb 9 19:35 a/file -r--r--r-- 1 raisinten raisinten 0 Feb 9 19:35 b/file
It seems to be working for v15.2.0:
I just tried it on 15.2.0 and I still get the same error.
In the output you posted, I can see you're trying to copy from folder a to folder b, but you seem to have missed in the original report the requirement on the fact that it's only reproducible when the destination folder is on a remote mount (in my case cephfs).I understand that it unfortunately makes this much harder to reproduce on a test environment.
I've just tried it now with an NFS mount (which should be easier to setup than cephfs) and I get the same error though it's a slightly different behavior. I get the permission error on both 12.18.4 and 15.2.0, but also in both cases, a file actually gets created, but remains empty (I changed thetouch /tmp/foointo anecho foo > /tmp/footo verify this).EDIT: My bad, it does work on 12.18.4 but fails with 15.2.0 with an NFS mount. A file does get created despite the thrown exception, but it has no data in it (on a cephfs mount, no file is created).
I believe this should be reported to libuv too because internally, this calls
CopyFilewhich in turn, callsuv_fs_copyfile:
Lines 1802 to 1803 in ad3ebed
SyncCall(env, args[4], &req_wrap_sync, "copyfile", uv_fs_copyfile, *src, *dest, flags); EDIT: My bad, it does work on 12.18.4 but fails with 15.2.0 with an NFS mount. A file does get created despite the thrown exception, but it has no data in it (on a cephfs mount, no file is created).
According to the warning in the docs for the function, the destination path
should be removed instead of being just an empty file when there is an error,
so that looks unexpected too.cc @nodejs/fs
I believe this should be reported to libuv too because internally, this calls CopyFile which in turn, calls uv_fs_copyfile
That's what I originally thought as I dug a bit into the internals, but I'm not sure then why the behavior changed between 12.18.4 and 12.19.0, unless libuv is statically linked to nodejs.
If it is, then you'd be more likely to know what changed and trace back the cause in order to send a proper report to libuv. (I just checked and the 12.19.0 changelog mentions upgrading libuv to 1.38.1 (#34187) and 1.39.0 (#34915)).
Thanks.I think, I understand what's going on. libuv/libuv#2352 introduced
the usage ofcopy_file_rangeforuv_fs_copyfilewhen possible.
However, as the man page forcopy_file_rangementions, it can
throw this error for pre Linux 5.3:EXDEV
The files referred to by fd_in and fd_out are not on the same mounted filesystem (pre Linux 5.3).This particular check was ignored.
- added a commit that references this issue
on Feb 13, 2021 Potential fix: libuv/libuv#3108
- added a commit that references this issue
on Feb 13, 2021 @kakaroto does this issue still exist in
v15.9.0?Sorry for my lack of availability. Managing a startup is exhausting!
I'm not sure that this would be the cause, since EXDEV seems to be about "a different mount" but not be about whether or not the source file is read-only or not. The error doesn't happen if the source file is read-write, only if it's read-only, so I think EXDEV is unrelated. The error shown is also with
errno: -13, code: EACCESS, so it's more of a permission problem.Regardless, thank you for looking into that and providing that EXDEV fix so quickly. Unfortunately, I just tested with v15.9.0 and the issue is still happening.
@kakaroto I have reported your issue at libuv: libuv/libuv#3117
Thank you. I appreciate that.
- added a commit that references this issue
on May 16, 2022 - added a commit that references this issue
on Mar 10, 2023 - added a commit that references this issue
on Mar 12, 2023 - added 2 commits that reference this issue
on Jul 23, 2025 - added 2 commits that reference this issue
on Dec 16, 2025
What steps will reproduce the bug?
if I have an external mount (in my case a cephfs cluster mounted using
ceph-fuse), then copying a file into it will fail with permission denied if the source file is read-only.Simple code to reproduce the issue :
Doing a
touch /tmp/foo && chmod -w /tmp/foobefore running the script will allow you to reproduce the error. The above code will fail as long as the source file is read-only, even if the file can be copied in the shell.(note, it might work with an NFS mount or some other type of mount, but have only tested with my cephfs mount. A local ext4fs mount does not seem to be affected by the problem).
Also note that this works with Node 12.18.4. I actually noticed this issue a little while ago when I reinstalled one of my systems, and ended up with node 12.19.0 and my app started failing to copy files from a read-only file system and I was forced to downgrade to 12.18.3 where it was known to be working as expected. Today, I confirmed the bug is still there in node 14.15.4.
Using this command
docker run --rm -it -v /ceph-mount/:/ceph-mount node:12.19.0-alpine /bin/shto run node then pasting these commands in it will show the problem :How often does it reproduce? Is there a required condition?
100% reproducible on node 12.19.0 up to 14.15.4, 0% reproducible on node 12.18.4
What is the expected behavior?
the
fs.copyFileshould be able to succeed in copying the file as long as the destination folder is writable.What do you see instead?
The output is :
Here are screenshots showing the above running on node 12.19.0 as well as 12.18.4 to prove that the bug was introduced in the 12.19.x branch :

And a screenshot showing it with node 14.15.4 proving that a

cpin the shell works as well (this is on another system, the/forge-vtt-dev/folder is the cephfs mount):This also shows a second bug where it's unable to overwrite the file if it already exists, and I'm about to file another issue for it.