fix: handle EINVAL from copy_file_range on eCryptfs (#25534)

## Summary
- Add `EINVAL` and `OPNOTSUPP` to the list of errors that trigger
fallback from `copy_file_range` to `sendfile`/read-write loop
- Fixes `Bun.write` and `fs.copyFile` failing on eCryptfs filesystems

## Test plan
- [x] Existing `copyFile` tests pass (`bun bd test
test/js/node/fs/fs.test.ts -t "copyFile"`)
- [x] Existing `copy_file_range` fallback tests pass (`bun bd test
test/js/bun/io/bun-write.test.js -t "should work when copyFileRange is
not available"`)

Fixes #13968

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
robobun
2025-12-15 17:47:08 -08:00
committed by GitHub
parent 6386eef8aa
commit 4061e1cb4f
2 changed files with 31 additions and 10 deletions

View File

@@ -3678,8 +3678,12 @@ pub const NodeFS = struct {
if (ret.errnoSysP(written, .copy_file_range, dest)) |err| {
return switch (err.getErrno()) {
.INTR => continue,
inline .XDEV, .NOSYS => |errno| brk: {
if (comptime errno == .NOSYS) {
// EINVAL: eCryptfs and other filesystems may not support copy_file_range
// XDEV: cross-device copy not supported
// NOSYS: syscall not available
// OPNOTSUPP: filesystem doesn't support this operation
inline .XDEV, .NOSYS, .INVAL, .OPNOTSUPP => |errno| brk: {
if (comptime errno == .NOSYS or errno == .OPNOTSUPP) {
bun.disableCopyFileRangeSyscall();
}
break :brk copyFileUsingSendfileOnLinuxWithReadWriteFallback(src, dest, src_fd, dest_fd, size, &wrote);
@@ -3699,8 +3703,12 @@ pub const NodeFS = struct {
if (ret.errnoSysP(written, .copy_file_range, dest)) |err| {
return switch (err.getErrno()) {
.INTR => continue,
inline .XDEV, .NOSYS => |errno| brk: {
if (comptime errno == .NOSYS) {
// EINVAL: eCryptfs and other filesystems may not support copy_file_range
// XDEV: cross-device copy not supported
// NOSYS: syscall not available
// OPNOTSUPP: filesystem doesn't support this operation
inline .XDEV, .NOSYS, .INVAL, .OPNOTSUPP => |errno| brk: {
if (comptime errno == .NOSYS or errno == .OPNOTSUPP) {
bun.disableCopyFileRangeSyscall();
}
break :brk copyFileUsingSendfileOnLinuxWithReadWriteFallback(src, dest, src_fd, dest_fd, size, &wrote);
@@ -6431,8 +6439,12 @@ pub const NodeFS = struct {
const written = linux.copy_file_range(src_fd.cast(), &off_in_copy, dest_fd.cast(), &off_out_copy, std.heap.pageSize(), 0);
if (ret.errnoSysP(written, .copy_file_range, dest)) |err| {
return switch (err.getErrno()) {
inline .XDEV, .NOSYS => |errno| brk: {
if (comptime errno == .NOSYS) {
// EINVAL: eCryptfs and other filesystems may not support copy_file_range
// XDEV: cross-device copy not supported
// NOSYS: syscall not available
// OPNOTSUPP: filesystem doesn't support this operation
inline .XDEV, .NOSYS, .INVAL, .OPNOTSUPP => |errno| brk: {
if (comptime errno == .NOSYS or errno == .OPNOTSUPP) {
bun.disableCopyFileRangeSyscall();
}
break :brk copyFileUsingSendfileOnLinuxWithReadWriteFallback(src, dest, src_fd, dest_fd, size, &wrote);
@@ -6451,8 +6463,12 @@ pub const NodeFS = struct {
const written = linux.copy_file_range(src_fd.cast(), &off_in_copy, dest_fd.cast(), &off_out_copy, size, 0);
if (ret.errnoSysP(written, .copy_file_range, dest)) |err| {
return switch (err.getErrno()) {
inline .XDEV, .NOSYS => |errno| brk: {
if (comptime errno == .NOSYS) {
// EINVAL: eCryptfs and other filesystems may not support copy_file_range
// XDEV: cross-device copy not supported
// NOSYS: syscall not available
// OPNOTSUPP: filesystem doesn't support this operation
inline .XDEV, .NOSYS, .INVAL, .OPNOTSUPP => |errno| brk: {
if (comptime errno == .NOSYS or errno == .OPNOTSUPP) {
bun.disableCopyFileRangeSyscall();
}
break :brk copyFileUsingSendfileOnLinuxWithReadWriteFallback(src, dest, src_fd, dest_fd, size, &wrote);

View File

@@ -257,7 +257,10 @@ pub const CopyFile = struct {
switch (bun.sys.getErrno(written)) {
.SUCCESS => {},
.NOSYS, .XDEV => {
// XDEV: cross-device copy not supported
// NOSYS: syscall not available
// OPNOTSUPP: filesystem doesn't support this operation
.NOSYS, .XDEV, .OPNOTSUPP => {
// TODO: this should use non-blocking I/O.
switch (jsc.Node.fs.NodeFS.copyFileUsingReadWriteLoop("", "", src_fd, dest_fd, if (unknown_size) 0 else remain, &total_written)) {
.err => |err| {
@@ -271,6 +274,8 @@ pub const CopyFile = struct {
}
},
// EINVAL: eCryptfs and other filesystems may not support copy_file_range.
// Also returned when the file descriptor is incompatible with the syscall.
.INVAL => {
if (comptime clear_append_if_invalid) {
if (!has_unset_append) {
@@ -287,7 +292,7 @@ pub const CopyFile = struct {
}
// If the Linux machine doesn't support
// copy_file_range or the file descrpitor is
// copy_file_range or the file descriptor is
// incompatible with the chosen syscall, fall back
// to a read/write loop
if (total_written == 0) {