-
Notifications
You must be signed in to change notification settings - Fork 43
/
ioctl_linux.go
93 lines (84 loc) · 2.48 KB
/
ioctl_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// +build linux
package desync
import (
"bytes"
"encoding/binary"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"unsafe"
"github.com/pkg/errors"
)
// BLKGETSIZE64 ioctl
const blkGetSize64 = 0x80081272
// FICLONERANGE ioctl
const fiCloneRange = 0x4020940d
// CanClone tries to determine if the filesystem allows cloning of blocks between
// two files. It'll create two tempfiles in the same dirs and attempt to perfom
// a 0-byte long block clone. If that's successful it'll return true.
func CanClone(dstFile, srcFile string) bool {
dst, err := ioutil.TempFile(filepath.Dir(dstFile), ".tmp")
if err != nil {
return false
}
defer os.Remove(dst.Name())
defer dst.Close()
src, err := ioutil.TempFile(filepath.Dir(srcFile), ".tmp")
if err != nil {
return false
}
defer os.Remove(src.Name())
defer src.Close()
err = CloneRange(dst, src, 0, 0, 0)
return err == nil
}
// CloneRange uses the FICLONERANGE ioctl to de-dupe blocks between two files
// when using XFS or btrfs. Only works at block-boundaries.
func CloneRange(dst, src *os.File, srcOffset, srcLength, dstOffset uint64) error {
// Build a structure to hold the argument for this IOCTL
// struct file_clone_range {
// __s64 src_fd;
// __u64 src_offset;
// __u64 src_length;
// __u64 dest_offset;
// };
arg := new(bytes.Buffer)
binary.Write(arg, binary.LittleEndian, uint64(src.Fd()))
binary.Write(arg, binary.LittleEndian, srcOffset)
binary.Write(arg, binary.LittleEndian, srcLength)
binary.Write(arg, binary.LittleEndian, dstOffset)
err := ioctl(dst.Fd(), fiCloneRange, uintptr(unsafe.Pointer(&arg.Bytes()[0])))
return errors.Wrapf(err, "failure cloning blocks from %s to %s", src.Name(), dst.Name())
}
// GetFileSize determines the size, in Bytes, of the file located at the given
// fileName.
func GetFileSize(fileName string) (size uint64, err error) {
info, err := os.Stat(fileName)
if err != nil {
return 0, err
}
fm := info.Mode()
if isDevice(fm) {
// When we are working with block devices, we can't simply use `Size()`, because it
// will return zero instead of the expected device size.
f, err := os.Open(fileName)
if err != nil {
return 0, err
}
err = ioctl(f.Fd(), blkGetSize64, uintptr(unsafe.Pointer(&size)))
if err != nil {
return 0, err
}
return size, nil
} else {
return uint64(info.Size()), nil
}
}
func ioctl(fd, operation, argp uintptr) error {
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, operation, argp)
if e != 0 {
return syscall.Errno(e)
}
return nil
}