Skip to content

Commit 0ca57c5

Browse files
committed
fix(cache): add fallback for unsupported copy_file_range
When copy_file_range fails with EXDEV, EOPNOTSUPP, or ENOSYS (e.g. cross-device copies or filesystems that don't support the syscall), fall back to a regular io.Copy for the remainder of the export. Other errors still hard-fail. Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
1 parent be7b86d commit 0ca57c5

1 file changed

Lines changed: 34 additions & 15 deletions

File tree

  • packages/orchestrator/pkg/sandbox/block

packages/orchestrator/pkg/sandbox/block/cache.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"io"
78
"math"
89
"math/rand"
910
"os"
@@ -153,6 +154,7 @@ func (c *Cache) ExportToDiff(ctx context.Context, out *os.File) (*header.DiffMet
153154
dst := int(out.Fd())
154155
var writeOffset int64
155156
var totalRanges int64
157+
fallback := false
156158

157159
copyStart := time.Now()
158160
for r := range BitsetRanges(diffMetadata.Dirty, diffMetadata.BlockSize) {
@@ -163,24 +165,41 @@ func (c *Cache) ExportToDiff(ctx context.Context, out *os.File) (*header.DiffMet
163165
// The kernel may return short writes (e.g. capped at MAX_RW_COUNT on non-reflink filesystems),
164166
// so we loop until the full range is copied. The offset pointers are advanced by the kernel.
165167
for remaining > 0 {
166-
// On XFS this uses reflink automatically.
167-
n, err := unix.CopyFileRange(
168-
src,
169-
&readOffset,
170-
dst,
171-
&writeOffset,
172-
remaining,
173-
0,
174-
)
175-
if err != nil {
176-
return nil, fmt.Errorf("error copying file range: %w", err)
168+
if !fallback {
169+
// On XFS this uses reflink automatically.
170+
n, err := unix.CopyFileRange(
171+
src,
172+
&readOffset,
173+
dst,
174+
&writeOffset,
175+
remaining,
176+
0,
177+
)
178+
if errors.Is(err, syscall.EXDEV) || errors.Is(err, syscall.EOPNOTSUPP) || errors.Is(err, syscall.ENOSYS) {
179+
fallback = true
180+
logger.L().Warn(ctx, "copy_file_range unsupported, falling back to normal copy", zap.Error(err))
181+
} else if err != nil {
182+
return nil, fmt.Errorf("error copying file range: %w", err)
183+
} else if n == 0 {
184+
return nil, fmt.Errorf("copy_file_range returned 0 with %d bytes remaining", remaining)
185+
} else {
186+
remaining -= n
187+
}
177188
}
178189

179-
if n == 0 {
180-
return nil, fmt.Errorf("copy_file_range returned 0 with %d bytes remaining", remaining)
190+
// CopyFileRange failed. Falling back to normal copy
191+
if fallback && remaining > 0 {
192+
if _, err := out.Seek(writeOffset, io.SeekStart); err != nil {
193+
return nil, fmt.Errorf("error seeking: %w", err)
194+
}
195+
sr := io.NewSectionReader(f, readOffset, int64(remaining))
196+
if _, err := io.Copy(out, sr); err != nil {
197+
return nil, fmt.Errorf("error copying file range. %w", err)
198+
}
199+
200+
writeOffset += int64(remaining)
201+
remaining = 0
181202
}
182-
183-
remaining -= n
184203
}
185204
}
186205

0 commit comments

Comments
 (0)