From 8e9945a5478f4f1c3aa024380d3e313cca0430dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Batlle=20i=20Rossell?= Date: Wed, 24 Feb 2021 16:54:12 +0000 Subject: [PATCH] unix: fix and test the FIDEDUPERANGE Linux ioctl The previous implementation didn't match the ioctl spec. Fixes golang/go#43678 Change-Id: Ia1c292c2dbd4913fb1d7e8331d9f18e23169d64a GitHub-Last-Rev: 5331c424ef5dd0384afda9effbb76afd7705ecfd GitHub-Pull-Request: golang/sys#97 Reviewed-on: https://go-review.googlesource.com/c/sys/+/284352 Run-TryBot: Tobias Klauser TryBot-Result: Go Bot Reviewed-by: Tobias Klauser Trust: Tobias Klauser Trust: Ian Lance Taylor --- unix/linux/types.go | 11 ++++- unix/syscall_linux.go | 59 +++++++++++++++++++++--- unix/syscall_linux_test.go | 91 ++++++++++++++++++++++++++++++++++++++ unix/ztypes_linux.go | 17 ++++++- 4 files changed, 171 insertions(+), 7 deletions(-) diff --git a/unix/linux/types.go b/unix/linux/types.go index ab2be609..ef138cf5 100644 --- a/unix/linux/types.go +++ b/unix/linux/types.go @@ -474,7 +474,16 @@ type Flock_t C.struct_flock type FileCloneRange C.struct_file_clone_range -type FileDedupeRange C.struct_file_dedupe_range +type RawFileDedupeRange C.struct_file_dedupe_range + +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 ad49b270..0a48548e 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -137,12 +137,61 @@ func IoctlFileClone(destFd, srcFd int) error { return ioctl(destFd, FICLONE, uintptr(srcFd)) } +type FileDedupeRange struct { + Src_offset uint64 + Src_length uint64 + Reserved1 uint16 + Reserved2 uint32 + Info []FileDedupeRangeInfo +} + +type FileDedupeRangeInfo struct { + Dest_fd int64 + Dest_offset uint64 + Bytes_deduped uint64 + Status int32 + Reserved uint32 +} + // 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. -func IoctlFileDedupeRange(destFd int, value *FileDedupeRange) error { - err := ioctl(destFd, FIDEDUPERANGE, uintptr(unsafe.Pointer(value))) - runtime.KeepAlive(value) +// 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 := (*RawFileDedupeRange)(unsafe.Pointer(&buf[0])) + rawrange.Src_offset = value.Src_offset + rawrange.Src_length = value.Src_length + rawrange.Dest_count = uint16(len(value.Info)) + rawrange.Reserved1 = value.Reserved1 + rawrange.Reserved2 = value.Reserved2 + + for i := range value.Info { + rawinfo := (*RawFileDedupeRangeInfo)(unsafe.Pointer( + uintptr(unsafe.Pointer(&buf[0])) + uintptr(SizeofRawFileDedupeRange) + + 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 + rawinfo.Status = value.Info[i].Status + rawinfo.Reserved = value.Info[i].Reserved + } + + err := ioctl(srcFd, FIDEDUPERANGE, uintptr(unsafe.Pointer(&buf[0]))) + + // Output + for i := range value.Info { + rawinfo := (*RawFileDedupeRangeInfo)(unsafe.Pointer( + uintptr(unsafe.Pointer(&buf[0])) + uintptr(SizeofRawFileDedupeRange) + + uintptr(i*SizeofRawFileDedupeRangeInfo))) + value.Info[i].Dest_fd = rawinfo.Dest_fd + value.Info[i].Dest_offset = rawinfo.Dest_offset + value.Info[i].Bytes_deduped = rawinfo.Bytes_deduped + value.Info[i].Status = rawinfo.Status + value.Info[i].Reserved = rawinfo.Reserved + } + return err } diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index db1a960a..699e56f2 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -796,3 +796,94 @@ func TestOpenat2(t *testing.T) { t.Errorf("Openat2 should fail with EXDEV, got %v", err) } } + +func TestFideduperange(t *testing.T) { + if runtime.GOOS == "android" { + // The ioctl in the build robot android-amd64 returned ENOTTY, + // an error not documented for that syscall. + t.Skip("FIDEDUPERANGE ioctl doesn't work on android, skipping test") + } + + f1, err := ioutil.TempFile("", t.Name()) + if err != nil { + t.Fatal(err) + } + defer f1.Close() + defer os.Remove(f1.Name()) + + // Test deduplication with two blocks of zeros + data := make([]byte, 4096) + + for i := 0; i < 2; i += 1 { + _, err = f1.Write(data) + if err != nil { + t.Fatal(err) + } + } + + f2, err := ioutil.TempFile("", t.Name()) + if err != nil { + t.Fatal(err) + } + defer f2.Close() + defer os.Remove(f2.Name()) + + for i := 0; i < 2; i += 1 { + // 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), + }, + unix.FileDedupeRangeInfo{ + Dest_fd: int64(f2.Fd()), + Dest_offset: uint64(4096), + }, + }} + + err = unix.IoctlFileDedupeRange(int(f1.Fd()), &dedupe) + if err == unix.EOPNOTSUPP || err == unix.EINVAL { + t.Skip("deduplication not supported on this filesystem") + } else if err != nil { + 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 3f922dbd..d3a18713 100644 --- a/unix/ztypes_linux.go +++ b/unix/ztypes_linux.go @@ -84,7 +84,7 @@ type FileCloneRange struct { Dest_offset uint64 } -type FileDedupeRange struct { +type RawFileDedupeRange struct { Src_offset uint64 Src_length uint64 Dest_count uint16 @@ -92,6 +92,21 @@ type FileDedupeRange struct { Reserved2 uint32 } +type RawFileDedupeRangeInfo struct { + Dest_fd int64 + Dest_offset uint64 + Bytes_deduped uint64 + Status int32 + Reserved uint32 +} + +const ( + SizeofRawFileDedupeRange = 0x18 + SizeofRawFileDedupeRangeInfo = 0x20 + FILE_DEDUPE_RANGE_SAME = 0x0 + FILE_DEDUPE_RANGE_DIFFERS = 0x1 +) + type FscryptPolicy struct { Version uint8 Contents_encryption_mode uint8