From 0bac8416081072f3b60cb5b27c97a2179ff462f7 Mon Sep 17 00:00:00 2001 From: Ludovico de Nittis Date: Sat, 25 Dec 2021 17:34:50 +0100 Subject: [PATCH] Get the correct size for block devices (#207) The function `Size()` of `FileInfo` returns the length in bytes for regular files. However if we use a block device, `Size()` will return a length of zero. To chunk a file, we use the size to split the expected work in even parts. For this reason, when we wanted to generate an index from a block device, due to the incorrect reported zero size, the whole task was carried out by a single Goroutine. With this commit we use the ioctl `BLKGETSIZE64` to get the correct size for block devices. In a test environment the `make` operation against a block device took 2 minutes and 20 seconds to complete with the current master branch and only 24 seconds with this patch. Signed-off-by: Ludovico de Nittis --- ioctl_linux.go | 28 ++++++++++++++++++++++++++++ ioctl_nonlinux.go | 14 ++++++++++++++ make.go | 14 +++++++------- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ioctl_linux.go b/ioctl_linux.go index 6abe030..206584e 100644 --- a/ioctl_linux.go +++ b/ioctl_linux.go @@ -14,6 +14,9 @@ import ( "github.com/pkg/errors" ) +// BLKGETSIZE64 ioctl +const blkGetSize64 = 0x80081272 + // FICLONERANGE ioctl const fiCloneRange = 0x4020940d @@ -56,6 +59,31 @@ func CloneRange(dst, src *os.File, srcOffset, srcLength, dstOffset uint64) error 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 { diff --git a/ioctl_nonlinux.go b/ioctl_nonlinux.go index bb7200f..b6392b3 100644 --- a/ioctl_nonlinux.go +++ b/ioctl_nonlinux.go @@ -14,3 +14,17 @@ func CanClone(dstFile string, srcFile string) bool { func CloneRange(dst, src *os.File, srcOffset, srcLength, dstOffset uint64) error { return errors.New("Not available on this platform") } + +// 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) { + // TODO we probably should do something platform specific here to get the correct size + } + return uint64(info.Size()), nil +} diff --git a/make.go b/make.go index 54c2e2d..8b5566b 100644 --- a/make.go +++ b/make.go @@ -60,21 +60,21 @@ func IndexFromFile(ctx context.Context, } f.Close() - // Adjust n if it's a small file that doesn't have n*max bytes - info, err := os.Stat(name) + size, err := GetFileSize(name) if err != nil { return index, stats, err } - nn := int(info.Size()/int64(max)) + 1 - if nn < n { - n = nn + + // Adjust n if it's a small file that doesn't have n*max bytes + nn := size/max + 1 + if nn < uint64(n) { + n = int(nn) } - size := uint64(info.Size()) span := size / uint64(n) // initial spacing between chunkers // Setup and start the progressbar if any if pb != nil { - pb.SetTotal(int(info.Size())) + pb.SetTotal(int(size)) pb.Start() defer pb.Finish() }