osbase/blockdev: add tests, fix minor issues
Add a lot of bounds checks which should make BlockDev safer to use. Fix
a bug in the ReadWriteSeeker.Seek function with io.SeekEnd; the offset
should be added to, not subtracted from the size. Add the Sync()
function to the BlockDev interface.
Change-Id: I247095b3dbc6410064844b4ac7c6208d88a7abcd
Reviewed-on: https://review.monogon.dev/c/monogon/+/3338
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/osbase/blockdev/blockdev_linux.go b/osbase/blockdev/blockdev_linux.go
index c5fa784..f6d5b4c 100644
--- a/osbase/blockdev/blockdev_linux.go
+++ b/osbase/blockdev/blockdev_linux.go
@@ -5,6 +5,7 @@
import (
"errors"
"fmt"
+ "io"
"math/bits"
"os"
"syscall"
@@ -21,10 +22,32 @@
}
func (d *Device) ReadAt(p []byte, off int64) (n int, err error) {
+ size := d.blockSize * d.blockCount
+ if off > size {
+ return 0, io.EOF
+ }
+ if int64(len(p)) > size-off {
+ n, err = d.backend.ReadAt(p[:size-off], off)
+ if err == nil {
+ err = io.EOF
+ }
+ return
+ }
return d.backend.ReadAt(p, off)
}
func (d *Device) WriteAt(p []byte, off int64) (n int, err error) {
+ size := d.blockSize * d.blockCount
+ if off > size {
+ return 0, ErrOutOfBounds
+ }
+ if int64(len(p)) > size-off {
+ n, err = d.backend.WriteAt(p[:size-off], off)
+ if err == nil {
+ err = ErrOutOfBounds
+ }
+ return
+ }
return d.backend.WriteAt(p, off)
}
@@ -40,7 +63,17 @@
return d.blockSize
}
+func (d *Device) OptimalBlockSize() int64 {
+ return d.blockSize
+}
+
func (d *Device) Discard(startByte int64, endByte int64) error {
+ if err := validAlignedRange(d, startByte, endByte); err != nil {
+ return err
+ }
+ if startByte == endByte {
+ return nil
+ }
var args [2]uint64
var err unix.Errno
args[0] = uint64(startByte)
@@ -59,11 +92,13 @@
return nil
}
-func (d *Device) OptimalBlockSize() int64 {
- return d.blockSize
-}
-
func (d *Device) Zero(startByte int64, endByte int64) error {
+ if err := validAlignedRange(d, startByte, endByte); err != nil {
+ return err
+ }
+ if startByte == endByte {
+ return nil
+ }
var args [2]uint64
var err error
args[0] = uint64(startByte)
@@ -92,6 +127,10 @@
return nil
}
+func (d *Device) Sync() error {
+ return d.backend.Sync()
+}
+
// RefreshPartitionTable refreshes the kernel's view of the partition table
// after changes made from userspace.
func (d *Device) RefreshPartitionTable() error {
@@ -165,7 +204,7 @@
func CreateFile(name string, blockSize int64, blockCount int64) (*File, error) {
if blockSize < 512 {
- return nil, fmt.Errorf("blockSize must be bigger than 512 bytes")
+ return nil, fmt.Errorf("blockSize must be at least 512 bytes")
}
if bits.OnesCount64(uint64(blockSize)) != 1 {
return nil, fmt.Errorf("blockSize must be a power of two")
@@ -187,10 +226,32 @@
}
func (d *File) ReadAt(p []byte, off int64) (n int, err error) {
+ size := d.blockSize * d.blockCount
+ if off > size {
+ return 0, io.EOF
+ }
+ if int64(len(p)) > size-off {
+ n, err = d.backend.ReadAt(p[:size-off], off)
+ if err == nil {
+ err = io.EOF
+ }
+ return
+ }
return d.backend.ReadAt(p, off)
}
func (d *File) WriteAt(p []byte, off int64) (n int, err error) {
+ size := d.blockSize * d.blockCount
+ if off > size {
+ return 0, ErrOutOfBounds
+ }
+ if int64(len(p)) > size-off {
+ n, err = d.backend.WriteAt(p[:size-off], off)
+ if err == nil {
+ err = ErrOutOfBounds
+ }
+ return
+ }
return d.backend.WriteAt(p, off)
}
@@ -206,7 +267,17 @@
return d.blockSize
}
+func (d *File) OptimalBlockSize() int64 {
+ return d.blockSize
+}
+
func (d *File) Discard(startByte int64, endByte int64) error {
+ if err := validAlignedRange(d, startByte, endByte); err != nil {
+ return err
+ }
+ if startByte == endByte {
+ return nil
+ }
var err error
if ctrlErr := d.rawConn.Control(func(fd uintptr) {
// There is FALLOC_FL_NO_HIDE_STALE, but it's not implemented by
@@ -224,11 +295,13 @@
return nil
}
-func (d *File) OptimalBlockSize() int64 {
- return d.blockSize
-}
-
func (d *File) Zero(startByte int64, endByte int64) error {
+ if err := validAlignedRange(d, startByte, endByte); err != nil {
+ return err
+ }
+ if startByte == endByte {
+ return nil
+ }
var err error
if ctrlErr := d.rawConn.Control(func(fd uintptr) {
// Tell the filesystem to punch out the given blocks.
@@ -246,3 +319,7 @@
}
return nil
}
+
+func (d *File) Sync() error {
+ return d.backend.Sync()
+}