Skip to content

Commit

Permalink
Get the correct size for block devices (#207)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
RyuzakiKK authored Dec 25, 2021
1 parent b7a13be commit 0bac841
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 7 deletions.
28 changes: 28 additions & 0 deletions ioctl_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
"github.com/pkg/errors"
)

// BLKGETSIZE64 ioctl
const blkGetSize64 = 0x80081272

// FICLONERANGE ioctl
const fiCloneRange = 0x4020940d

Expand Down Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions ioctl_nonlinux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
14 changes: 7 additions & 7 deletions make.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down

0 comments on commit 0bac841

Please sign in to comment.