Skip to content

Commit d354f78

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 c0d4dc1 commit d354f78

File tree

1 file changed

+35
-15
lines changed
  • packages/orchestrator/pkg/sandbox/block

1 file changed

+35
-15
lines changed

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

Lines changed: 35 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,42 @@ 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+
switch {
179+
case errors.Is(err, syscall.EXDEV) || errors.Is(err, syscall.EOPNOTSUPP) || errors.Is(err, syscall.ENOSYS):
180+
fallback = true
181+
logger.L().Warn(ctx, "copy_file_range unsupported, falling back to normal copy", zap.Error(err))
182+
case err != nil:
183+
return nil, fmt.Errorf("error copying file range: %w", err)
184+
case n == 0:
185+
return nil, fmt.Errorf("copy_file_range returned 0 with %d bytes remaining", remaining)
186+
default:
187+
remaining -= n
188+
}
177189
}
178190

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

0 commit comments

Comments
 (0)