Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unix: fix and test the FIDEDUPERANGE Linux ioctl #97

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion unix/linux/types.go
Expand Up @@ -473,7 +473,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

Expand Down
59 changes: 54 additions & 5 deletions unix/syscall_linux.go
Expand Up @@ -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
}

Expand Down
91 changes: 91 additions & 0 deletions unix/syscall_linux_test.go
Expand Up @@ -795,3 +795,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)
}
}
17 changes: 16 additions & 1 deletion unix/ztypes_linux.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.