diff --git a/unix/linux/types.go b/unix/linux/types.go index 330ce115..6c14074a 100644 --- a/unix/linux/types.go +++ b/unix/linux/types.go @@ -480,6 +480,8 @@ type RawFileDedupeRangeInfo C.struct_file_dedupe_range_info const ( SizeofRawFileDedupeRange = C.sizeof_struct_file_dedupe_range SizeofRawFileDedupeRangeInfo = C.sizeof_struct_file_dedupe_range_info + FILE_DEDUPE_RANGE_SAME = C.FILE_DEDUPE_RANGE_SAME + FILE_DEDUPE_RANGE_DIFFERS = C.FILE_DEDUPE_RANGE_DIFFERS ) // Filesystem Encryption diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index 674a116a..775f74d5 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -140,7 +140,6 @@ func IoctlFileClone(destFd, srcFd int) error { type FileDedupeRange struct { Src_offset uint64 Src_length uint64 - Dest_count uint16 Reserved1 uint16 Reserved2 uint32 Info []FileDedupeRangeInfo @@ -155,12 +154,13 @@ type FileDedupeRangeInfo struct { } // IoctlFileDedupeRange performs an FIDEDUPERANGE ioctl operation to share the -// range of data conveyed in value with the file associated with the file -// descriptor destFd. See the ioctl_fideduperange(2) man page for details. +// range of data conveyed in value from the file associated with the file +// descriptor srcFd to the value.Info destinations. See the +// ioctl_fideduperange(2) man page for details. func IoctlFileDedupeRange(srcFd int, value *FileDedupeRange) error { buf := make([]byte, SizeofRawFileDedupeRange+ len(value.Info)*SizeofRawFileDedupeRangeInfo) - rawrange := (*FileDedupeRange)(unsafe.Pointer(&buf[0])) + rawrange := (*RawFileDedupeRange)(unsafe.Pointer(&buf[0])) rawrange.Src_offset = value.Src_offset rawrange.Src_length = value.Src_length rawrange.Dest_count = uint16(len(value.Info)) @@ -170,7 +170,7 @@ func IoctlFileDedupeRange(srcFd int, value *FileDedupeRange) error { for i := range value.Info { rawinfo := (*RawFileDedupeRangeInfo)(unsafe.Pointer( uintptr(unsafe.Pointer(&buf[0])) + uintptr(SizeofRawFileDedupeRange) + - uintptr(SizeofRawFileDedupeRangeInfo))) + uintptr(i*SizeofRawFileDedupeRangeInfo))) rawinfo.Dest_fd = value.Info[i].Dest_fd rawinfo.Dest_offset = value.Info[i].Dest_offset rawinfo.Bytes_deduped = value.Info[i].Bytes_deduped @@ -179,7 +179,16 @@ func IoctlFileDedupeRange(srcFd int, value *FileDedupeRange) error { } err := ioctl(srcFd, FIDEDUPERANGE, uintptr(unsafe.Pointer(&buf[0]))) - runtime.KeepAlive(buf) + + // Output + for i := range value.Info { + rawinfo := (*RawFileDedupeRangeInfo)(unsafe.Pointer( + uintptr(unsafe.Pointer(&buf[0])) + uintptr(SizeofRawFileDedupeRange) + + uintptr(i*SizeofRawFileDedupeRangeInfo))) + value.Info[i].Bytes_deduped = rawinfo.Bytes_deduped + value.Info[i].Status = rawinfo.Status + } + return err } diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 3625f462..39b2a1f1 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -795,3 +795,90 @@ func TestOpenat2(t *testing.T) { t.Errorf("Openat2 should fail with EXDEV, got %v", err) } } + +func TestFideduperange(t *testing.T) { + f1, err := ioutil.TempFile("", t.Name()) + if err != nil { + t.Fatal(err) + } + defer f1.Close() + + for i := 0; i < 2; i += 1 { + _, err = f1.Write(make([]byte, 4096)) + if err != nil { + t.Fatal(err) + } + } + + f2, err := ioutil.TempFile("", t.Name()) + if err != nil { + t.Fatal(err) + } + defer f2.Close() + + for i := 0; i < 2; i += 1 { + data := make([]byte, 4096) + + // Make the 2nd block different + if i == 1 { + data[1] = 1 + } + + _, err = f2.Write(data) + if err != nil { + t.Fatal(err) + } + } + + dedupe := unix.FileDedupeRange{ + Src_offset: uint64(0), + Src_length: uint64(4096), + Info: []unix.FileDedupeRangeInfo{ + unix.FileDedupeRangeInfo{ + Dest_fd: int64(f2.Fd()), + Dest_offset: uint64(0), + Bytes_deduped: uint64(4096), + }, + unix.FileDedupeRangeInfo{ + Dest_fd: int64(f2.Fd()), + Dest_offset: uint64(4096), + Bytes_deduped: uint64(4096), + }, + }} + + err = unix.IoctlFileDedupeRange(int(f1.Fd()), &dedupe) + + if err != nil { + if err == unix.EOPNOTSUPP { + // We can't test if the fs doesn't support deduplication + t.SkipNow() + } + t.Fatal(err) + } + + // The first Info should be equal + if dedupe.Info[0].Status < 0 { + // We expect status to be a negated errno + t.Errorf("Unexpected error in FileDedupeRange: %s", + unix.ErrnoName(unix.Errno(-dedupe.Info[0].Status))) + } else if dedupe.Info[0].Status == unix.FILE_DEDUPE_RANGE_DIFFERS { + t.Errorf("Unexpected different bytes in FileDedupeRange") + } + if dedupe.Info[0].Bytes_deduped != 4096 { + t.Errorf("Unexpected amount of bytes deduped %v != %v", + dedupe.Info[0].Bytes_deduped, 4096) + } + + // The second Info should be different + if dedupe.Info[1].Status < 0 { + // We expect status to be a negated errno + t.Errorf("Unexpected error in FileDedupeRange: %s", + unix.ErrnoName(unix.Errno(-dedupe.Info[1].Status))) + } else if dedupe.Info[1].Status == unix.FILE_DEDUPE_RANGE_SAME { + t.Errorf("Unexpected equal bytes in FileDedupeRange") + } + if dedupe.Info[1].Bytes_deduped != 0 { + t.Errorf("Unexpected amount of bytes deduped %v != %v", + dedupe.Info[1].Bytes_deduped, 0) + } +} diff --git a/unix/ztypes_linux.go b/unix/ztypes_linux.go index d9a25cda..56bb6f96 100644 --- a/unix/ztypes_linux.go +++ b/unix/ztypes_linux.go @@ -102,6 +102,8 @@ type RawFileDedupeRangeInfo struct { const ( SizeofRawFileDedupeRange = 0x18 SizeofRawFileDedupeRangeInfo = 0x20 + FILE_DEDUPE_RANGE_SAME = 0x0 + FILE_DEDUPE_RANGE_DIFFERS = 0x1 ) type FscryptPolicy struct {