diff --git a/README.md b/README.md index e230077b..52d77495 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,18 @@ Note: detailed go documentation is available at [godoc.org](https://godoc.org/gi ### Concepts `go-diskfs` has a few basic concepts: +* Backend * Disk * Partition * Filesystem +#### Backend +Backend is a (relatively) thin layer which abstracts low-level read/write operations. Through a backend you can seamlessly operate different disk formats (see a bit stale qcow2 branch). + +Currently there is only one implementation - raw. + +Use `raw` backend to access block devices and raw image files. + #### Disk A disk represents either a file or block device that you access and manipulate. With access to the disk, you can: diff --git a/backend/interface.go b/backend/interface.go new file mode 100644 index 00000000..6b8c562b --- /dev/null +++ b/backend/interface.go @@ -0,0 +1,33 @@ +package backend + +import ( + "errors" + "io" + "io/fs" + "os" +) + +var ( + ErrIncorrectOpenMode = errors.New("disk file or device not open for write") + ErrNotSuitable = errors.New("backing file is not suitable") +) + +type File interface { + fs.File + io.ReaderAt + io.Seeker + io.Closer +} + +type WritableFile interface { + File + io.WriterAt +} + +type Storage interface { + File + // OS-stecific file for ioctl calls via fd + Sys() (*os.File, error) + // file for read-write operations + Writable() (WritableFile, error) +} diff --git a/backend/raw/raw.go b/backend/raw/raw.go new file mode 100644 index 00000000..0e980426 --- /dev/null +++ b/backend/raw/raw.go @@ -0,0 +1,127 @@ +package raw + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + + "github.com/diskfs/go-diskfs/backend" +) + +type rawBackend struct { + storage fs.File + readonly bool +} + +// Create a backend.Storage from provided fs.File +func New(f fs.File, isReadonly bool) backend.Storage { + return rawBackend{ + storage: f, + readonly: isReadonly, + } +} + +// Create a backend.Storage from a path to a device +// Should pass a path to a block device e.g. /dev/sda or a path to a file /tmp/foo.img +// The provided device/file must exist at the time you call OpenFromPath() +func OpenFromPath(pathName string, isReadonly bool) (backend.Storage, error) { + if pathName == "" { + return nil, errors.New("must pass device of file name") + } + + if _, err := os.Stat(pathName); os.IsNotExist(err) { + return nil, fmt.Errorf("provided device/file %s does not exist", pathName) + } + + openMode := os.O_RDONLY + + if !isReadonly { + openMode |= os.O_RDWR | os.O_EXCL + } + + f, err := os.OpenFile(pathName, openMode, 0o600) + if err != nil { + return nil, fmt.Errorf("could not open device %s with mode %v: %w", pathName, openMode, err) + } + + return rawBackend{ + storage: f, + readonly: isReadonly, + }, nil +} + +// Create a backend.Storage from a path to an image file. +// Should pass a path to a file /tmp/foo.img +// The provided file must not exist at the time you call CreateFromPath() +func CreateFromPath(pathName string, size int64) (backend.Storage, error) { + if pathName == "" { + return nil, errors.New("must pass device name") + } + if size <= 0 { + return nil, errors.New("must pass valid device size to create") + } + f, err := os.OpenFile(pathName, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o666) + if err != nil { + return nil, fmt.Errorf("could not create device %s: %w", pathName, err) + } + err = os.Truncate(pathName, size) + if err != nil { + return nil, fmt.Errorf("could not expand device %s to size %d: %w", pathName, size, err) + } + + return rawBackend{ + storage: f, + readonly: false, + }, nil +} + +// backend.Storage interface guard +var _ backend.Storage = (*rawBackend)(nil) + +// OS-stecific file for ioctl calls via fd +func (f rawBackend) Sys() (*os.File, error) { + if osFile, ok := f.storage.(*os.File); ok { + return osFile, nil + } + return nil, backend.ErrNotSuitable +} + +// file for read-write operations +func (f rawBackend) Writable() (backend.WritableFile, error) { + if rwFile, ok := f.storage.(backend.WritableFile); ok { + if !f.readonly { + return rwFile, nil + } + + return nil, backend.ErrIncorrectOpenMode + } + return nil, backend.ErrNotSuitable +} + +func (f rawBackend) Stat() (fs.FileInfo, error) { + return f.storage.Stat() +} + +func (f rawBackend) Read(b []byte) (int, error) { + return f.storage.Read(b) +} + +func (f rawBackend) Close() error { + return f.storage.Close() +} + +func (f rawBackend) ReadAt(p []byte, off int64) (n int, err error) { + if readerAt, ok := f.storage.(io.ReaderAt); ok { + return readerAt.ReadAt(p, off) + } + return -1, backend.ErrNotSuitable +} + +func (f rawBackend) Seek(offset int64, whence int) (int64, error) { + if seeker, ok := f.storage.(io.Seeker); ok { + return seeker.Seek(offset, whence) + } + return -1, backend.ErrNotSuitable +} diff --git a/disk/disk.go b/disk/disk.go index 25d41729..93f0718c 100644 --- a/disk/disk.go +++ b/disk/disk.go @@ -8,28 +8,24 @@ import ( "errors" "fmt" "io" - "os" - - log "github.com/sirupsen/logrus" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/filesystem/ext4" "github.com/diskfs/go-diskfs/filesystem/fat32" "github.com/diskfs/go-diskfs/filesystem/iso9660" "github.com/diskfs/go-diskfs/filesystem/squashfs" "github.com/diskfs/go-diskfs/partition" + log "github.com/sirupsen/logrus" ) // Disk is a reference to a single disk block device or image that has been Create() or Open() type Disk struct { - File *os.File - Info os.FileInfo - Type Type + Backend backend.Storage Size int64 LogicalBlocksize int64 PhysicalBlocksize int64 Table partition.Table - Writable bool DefaultBlocks bool } @@ -43,17 +39,13 @@ const ( Device ) -var ( - errIncorrectOpenMode = errors.New("disk file or device not open for write") -) - // GetPartitionTable retrieves a PartitionTable for a Disk // // If the table is able to be retrieved from the disk, it is saved in the instance. // // returns an error if the Disk is invalid or does not exist, or the partition table is unknown func (d *Disk) GetPartitionTable() (partition.Table, error) { - t, err := partition.Read(d.File, int(d.LogicalBlocksize), int(d.PhysicalBlocksize)) + t, err := partition.Read(d.Backend, int(d.LogicalBlocksize), int(d.PhysicalBlocksize)) if err != nil { return nil, err } @@ -68,24 +60,19 @@ func (d *Disk) GetPartitionTable() (partition.Table, error) { // // Actual writing of the table is delegated to the individual implementation func (d *Disk) Partition(table partition.Table) error { - if !d.Writable { - return errIncorrectOpenMode + rwBackingFile, err := d.Backend.Writable() + if err != nil { + return err } + // fill in the uuid - err := table.Write(d.File, d.Size) + err = table.Write(rwBackingFile, d.Size) if err != nil { return fmt.Errorf("failed to write partition table: %v", err) } d.Table = table - // the partition table needs to be re-read only if - // the disk file is an actual block device - if d.Type == Device { - err = d.ReReadPartitionTable() - if err != nil { - return fmt.Errorf("unable to re-read the partition table. Kernel still uses old partition table: %v", err) - } - } - return nil + + return d.ReReadPartitionTable() } // WritePartitionContents writes the contents of an io.Reader to a given partition @@ -95,8 +82,10 @@ func (d *Disk) Partition(table partition.Table) error { // returns an error if there was an error writing to the disk, reading from the reader, the table // is invalid, or the partition is invalid func (d *Disk) WritePartitionContents(part int, reader io.Reader) (int64, error) { - if !d.Writable { - return -1, errIncorrectOpenMode + backingRwFile, err := d.Backend.Writable() + + if err != nil { + return -1, err } if d.Table == nil { return -1, fmt.Errorf("cannot write contents of a partition on a disk without a partition table") @@ -109,7 +98,7 @@ func (d *Disk) WritePartitionContents(part int, reader io.Reader) (int64, error) if part > len(partitions) { return -1, fmt.Errorf("cannot write contents of partition %d which is greater than max partition %d", part, len(partitions)) } - written, err := partitions[part-1].WriteContents(d.File, reader) + written, err := partitions[part-1].WriteContents(backingRwFile, reader) return int64(written), err } @@ -131,7 +120,7 @@ func (d *Disk) ReadPartitionContents(part int, writer io.Writer) (int64, error) if part > len(partitions) { return -1, fmt.Errorf("cannot read contents of partition %d which is greater than max partition %d", part, len(partitions)) } - return partitions[part-1].ReadContents(d.File, writer) + return partitions[part-1].ReadContents(d.Backend, writer) } // FilesystemSpec represents the specification of a filesystem to be created @@ -162,9 +151,13 @@ func (d *Disk) CreateFilesystem(spec FilesystemSpec) (filesystem.FileSystem, err var ( size, start int64 ) + + rwBackingFile, err := d.Backend.Writable() + if err != nil { + return nil, err + } + switch { - case !d.Writable: - return nil, errIncorrectOpenMode case spec.Partition == 0: size = d.Size start = 0 @@ -183,11 +176,11 @@ func (d *Disk) CreateFilesystem(spec FilesystemSpec) (filesystem.FileSystem, err switch spec.FSType { case filesystem.TypeFat32: - return fat32.Create(d.File, size, start, d.LogicalBlocksize, spec.VolumeLabel) + return fat32.Create(rwBackingFile, size, start, d.LogicalBlocksize, spec.VolumeLabel) case filesystem.TypeISO9660: - return iso9660.Create(d.File, size, start, d.LogicalBlocksize, spec.WorkDir) + return iso9660.Create(rwBackingFile, size, start, d.LogicalBlocksize, spec.WorkDir) case filesystem.TypeExt4: - return ext4.Create(d.File, size, start, d.LogicalBlocksize, nil) + return ext4.Create(rwBackingFile, size, start, d.LogicalBlocksize, nil) case filesystem.TypeSquashfs: return nil, filesystem.ErrReadonlyFilesystem default: @@ -228,7 +221,7 @@ func (d *Disk) GetFilesystem(part int) (filesystem.FileSystem, error) { // just try each type log.Debug("trying fat32") - fat32FS, err := fat32.Read(d.File, size, start, d.LogicalBlocksize) + fat32FS, err := fat32.Read(d.Backend, size, start, d.LogicalBlocksize) if err == nil { return fat32FS, nil } @@ -238,17 +231,17 @@ func (d *Disk) GetFilesystem(part int) (filesystem.FileSystem, error) { pbs = 0 } log.Debugf("trying iso9660 with physical block size %d", pbs) - iso9660FS, err := iso9660.Read(d.File, size, start, pbs) + iso9660FS, err := iso9660.Read(d.Backend, size, start, pbs) if err == nil { return iso9660FS, nil } log.Debugf("iso9660 failed: %v", err) - squashFS, err := squashfs.Read(d.File, size, start, d.LogicalBlocksize) + squashFS, err := squashfs.Read(d.Backend, size, start, d.LogicalBlocksize) if err == nil { return squashFS, nil } log.Debug("trying ext4") - ext4FS, err := ext4.Read(d.File, size, start, d.LogicalBlocksize) + ext4FS, err := ext4.Read(d.Backend, size, start, d.LogicalBlocksize) if err == nil { return ext4FS, nil } @@ -258,7 +251,7 @@ func (d *Disk) GetFilesystem(part int) (filesystem.FileSystem, error) { // Close the disk. Once successfully closed, it can no longer be used. func (d *Disk) Close() error { - if err := d.File.Close(); err != nil { + if err := d.Backend.Close(); err != nil { return err } *d = Disk{} diff --git a/disk/disk_test.go b/disk/disk_test.go index 7bb2ce4c..7a123493 100644 --- a/disk/disk_test.go +++ b/disk/disk_test.go @@ -15,11 +15,13 @@ import ( "strings" "testing" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/disk" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/partition" "github.com/diskfs/go-diskfs/partition/gpt" "github.com/diskfs/go-diskfs/partition/mbr" + "github.com/diskfs/go-diskfs/testhelper" ) var ( @@ -70,10 +72,9 @@ func TestGetPartitionTable(t *testing.T) { defer f.Close() d := &disk.Disk{ - File: f, + Backend: raw.New(f, true), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Writable: false, } table, err := d.GetPartitionTable() @@ -109,11 +110,9 @@ func TestPartition(t *testing.T) { } d := &disk.Disk{ - File: f, + Backend: raw.New(f, false), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, - Writable: true, Size: fileInfo.Size(), } // this is partition start and end in sectors, not bytes @@ -146,17 +145,10 @@ func TestPartition(t *testing.T) { fmt.Println(f.Name()) } - fileInfo, err := f.Stat() - if err != nil { - t.Fatalf("error reading info on temporary disk: %v", err) - } - d := &disk.Disk{ - File: f, + Backend: raw.New(f, false), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, - Writable: true, } // this is partition start and end in sectors, not bytes sectorSize := 512 @@ -176,7 +168,7 @@ func TestPartition(t *testing.T) { }) t.Run("readonly", func(t *testing.T) { d := &disk.Disk{ - Writable: false, + Backend: raw.New(&testhelper.FileImpl{}, true), } expectedErr := fmt.Errorf("disk file or device not open for write") err := d.Partition(&mbr.Table{}) @@ -226,18 +218,11 @@ func TestWritePartitionContents(t *testing.T) { fmt.Println(f.Name()) } - fileInfo, err := f.Stat() - if err != nil { - t.Fatalf("error reading info on temporary disk: %v", err) - } - d := &disk.Disk{ - File: f, + Backend: raw.New(f, false), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, Table: tt.table, - Writable: true, } b := make([]byte, partitionSize) _, _ = rand.Read(b) @@ -256,7 +241,7 @@ func TestWritePartitionContents(t *testing.T) { }) t.Run("readonly", func(t *testing.T) { d := &disk.Disk{ - Writable: false, + Backend: raw.New(&testhelper.FileImpl{}, true), } expectedErr := fmt.Errorf("disk file or device not open for write") _, err := d.WritePartitionContents(0, nil) @@ -266,7 +251,6 @@ func TestWritePartitionContents(t *testing.T) { }) } -//nolint:gocyclo // we do not care much about cyclomatic complexity in the test function. Maybe someday we can improve it. func TestReadPartitionContents(t *testing.T) { t.Run("gpt", func(t *testing.T) { partitionStart := uint64(2048) @@ -308,22 +292,15 @@ func TestReadPartitionContents(t *testing.T) { fmt.Println(f.Name()) } - fileInfo, err := f.Stat() - if err != nil { - t.Fatalf("error reading info on temporary disk: %v", err) - } - // get the actual content b2 := make([]byte, partitionSize*512) _, _ = f.ReadAt(b2, int64(partitionStart*512)) d := &disk.Disk{ - File: f, + Backend: raw.New(f, true), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, Table: tt.table, - Writable: false, } var writer bytes.Buffer read, err := d.ReadPartitionContents(tt.partition, &writer) @@ -381,22 +358,15 @@ func TestReadPartitionContents(t *testing.T) { fmt.Println(f.Name()) } - fileInfo, err := f.Stat() - if err != nil { - t.Fatalf("error reading info on temporary disk: %v", err) - } - // get the actual content b2 := make([]byte, partitionSize*512) _, _ = f.ReadAt(b2, int64(partitionStart*512)) d := &disk.Disk{ - File: f, + Backend: raw.New(f, true), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, Table: tt.table, - Writable: false, } var writer bytes.Buffer read, err := d.ReadPartitionContents(tt.partition, &writer) @@ -433,17 +403,10 @@ func TestCreateFilesystem(t *testing.T) { fmt.Println(f.Name()) } - fileInfo, err := f.Stat() - if err != nil { - t.Fatalf("error reading info on temporary disk: %v", err) - } - d := &disk.Disk{ - File: f, + Backend: raw.New(f, false), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, - Writable: true, } expected := fmt.Errorf("cannot create filesystem on a partition without a partition table") fs, err := d.CreateFilesystem(disk.FilesystemSpec{Partition: 1, FSType: filesystem.TypeFat32}) @@ -473,12 +436,10 @@ func TestCreateFilesystem(t *testing.T) { } d := &disk.Disk{ - File: f, + Backend: raw.New(f, false), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, Size: fileInfo.Size(), - Writable: true, } fs, err := d.CreateFilesystem(disk.FilesystemSpec{Partition: 0, FSType: filesystem.TypeFat32}) if err != nil { @@ -515,13 +476,11 @@ func TestCreateFilesystem(t *testing.T) { LogicalSectorSize: 512, } d := &disk.Disk{ - File: f, + Backend: raw.New(f, false), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, Size: fileInfo.Size(), Table: table, - Writable: true, } fs, err := d.CreateFilesystem(disk.FilesystemSpec{Partition: 1, FSType: filesystem.TypeFat32}) if err != nil { @@ -533,7 +492,7 @@ func TestCreateFilesystem(t *testing.T) { }) t.Run("readonly", func(t *testing.T) { d := &disk.Disk{ - Writable: false, + Backend: raw.New(&testhelper.FileImpl{}, true), } expectedErr := fmt.Errorf("disk file or device not open for write") _, err := d.CreateFilesystem(disk.FilesystemSpec{}) @@ -557,17 +516,10 @@ func TestGetFilesystem(t *testing.T) { fmt.Println(f.Name()) } - fileInfo, err := f.Stat() - if err != nil { - t.Fatalf("error reading info on temporary disk: %v", err) - } - d := &disk.Disk{ - File: f, + Backend: raw.New(f, true), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, - Writable: false, } expected := fmt.Errorf("cannot read filesystem on a partition without a partition table") fs, err := d.GetFilesystem(1) @@ -609,12 +561,10 @@ func TestGetFilesystem(t *testing.T) { } d := &disk.Disk{ - File: f, + Backend: raw.New(f, true), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, Size: fileInfo.Size(), - Writable: false, } fs, err := d.GetFilesystem(0) if err != nil { @@ -651,13 +601,11 @@ func TestGetFilesystem(t *testing.T) { LogicalSectorSize: 512, } d := &disk.Disk{ - File: f, + Backend: raw.New(f, true), LogicalBlocksize: 512, PhysicalBlocksize: 512, - Info: fileInfo, Size: fileInfo.Size(), Table: table, - Writable: false, } fs, err := d.GetFilesystem(1) if err != nil { diff --git a/disk/disk_unix.go b/disk/disk_unix.go index aba635c4..1900f880 100644 --- a/disk/disk_unix.go +++ b/disk/disk_unix.go @@ -5,6 +5,7 @@ package disk import ( "fmt" + "os" "golang.org/x/sys/unix" ) @@ -18,10 +19,24 @@ const ( // // It is done via an ioctl call with request as BLKRRPART. func (d *Disk) ReReadPartitionTable() error { - fd := d.File.Fd() - _, err := unix.IoctlGetInt(int(fd), blkrrpart) + // the partition table needs to be re-read only if + // the disk file is an actual block device + devInfo, err := d.Backend.Stat() if err != nil { - return fmt.Errorf("unable to re-read partition table: %v", err) + return err } + + if devInfo.Mode()&os.ModeDevice != 0 { + osFile, err := d.Backend.Sys() + if err != nil { + return err + } + fd := osFile.Fd() + _, err = unix.IoctlGetInt(int(fd), blkrrpart) + if err != nil { + return fmt.Errorf("unable to re-read the partition table. Kernel still uses old partition table: %v", err) + } + } + return nil } diff --git a/diskfs.go b/diskfs.go index 75fa5367..09789cec 100644 --- a/diskfs.go +++ b/diskfs.go @@ -13,11 +13,15 @@ package diskfs import ( "errors" "fmt" + "io/fs" "os" log "github.com/sirupsen/logrus" + "github.com/diskfs/go-diskfs/backend" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/disk" + "github.com/diskfs/go-diskfs/util" ) // when we use a disk image with a GPT, we cannot get the logical sector size from the disk via the kernel @@ -30,14 +34,6 @@ const ( // blkpbszGet = 0x127b ) -// Format represents the format of the disk -type Format int - -const ( - // Raw disk format for basic raw disk - Raw Format = iota -) - // OpenModeOption represents file open modes type OpenModeOption int @@ -93,15 +89,13 @@ func writableMode(mode OpenModeOption) bool { return false } -func initDisk(f *os.File, openMode OpenModeOption, sectorSize SectorSize) (*disk.Disk, error) { +func initDisk(f fs.File, openMode OpenModeOption, sectorSize SectorSize) (*disk.Disk, error) { + log.Debug("initDisk(): start") + var ( - diskType disk.Type - size int64 - lblksize = int64(defaultBlocksize) - pblksize = int64(defaultBlocksize) - defaultBlocks = true + lblksize = int64(defaultBlocksize) + pblksize = int64(defaultBlocksize) ) - log.Debug("initDisk(): start") if sectorSize != SectorSizeDefault { lblksize = int64(sectorSize) @@ -111,59 +105,62 @@ func initDisk(f *os.File, openMode OpenModeOption, sectorSize SectorSize) (*disk // get device information devInfo, err := f.Stat() if err != nil { - return nil, fmt.Errorf("could not get info for device %s: %v", f.Name(), err) + return nil, fmt.Errorf("could not get info for device %s: %v", devInfo.Name(), err) + } + + newDisk := &disk.Disk{ + Size: devInfo.Size(), + LogicalBlocksize: lblksize, + PhysicalBlocksize: pblksize, + DefaultBlocks: true, } + + newDisk.Backend = util.BackendFromFile(f, !writableMode(openMode)) + mode := devInfo.Mode() switch { case mode.IsRegular(): log.Debug("initDisk(): regular file") - diskType = disk.File - size = devInfo.Size() - if size <= 0 { - return nil, fmt.Errorf("could not get file size for device %s", f.Name()) + if newDisk.Size <= 0 { + return nil, fmt.Errorf("could not get file size for device %s", devInfo.Name()) } case mode&os.ModeDevice != 0: log.Debug("initDisk(): block device") - diskType = disk.Device - size, err = getBlockDeviceSize(f) + osFile, err := newDisk.Backend.Sys() if err != nil { - return nil, fmt.Errorf("error getting block device %s size: %s", f.Name(), err) + return nil, backend.ErrNotSuitable } - lblksize, pblksize, err = getSectorSizes(f) - log.Debugf("initDisk(): logical block size %d, physical block size %d", lblksize, pblksize) - defaultBlocks = false - if err != nil { - return nil, fmt.Errorf("unable to get block sizes for device %s: %v", f.Name(), err) + + if size, err := getBlockDeviceSize(osFile); err != nil { + return nil, fmt.Errorf("error getting block device %s size: %s", devInfo.Name(), err) + } else { + newDisk.Size = size } + + if lblksize, pblksize, err = getSectorSizes(osFile); err != nil { + return nil, fmt.Errorf("unable to get block sizes for device %s: %v", devInfo.Name(), err) + } else { + log.Debugf("initDisk(): logical block size %d, physical block size %d", lblksize, pblksize) + + newDisk.LogicalBlocksize = lblksize + newDisk.PhysicalBlocksize = pblksize + newDisk.DefaultBlocks = false + } + default: - return nil, fmt.Errorf("device %s is neither a block device nor a regular file", f.Name()) + return nil, fmt.Errorf("device %s is neither a block device nor a regular file", devInfo.Name()) } // how many good blocks do we have? // var goodBlocks, orphanedBlocks int // goodBlocks = size / lblksize - writable := writableMode(openMode) - - ret := &disk.Disk{ - File: f, - Info: devInfo, - Type: diskType, - Size: size, - LogicalBlocksize: lblksize, - PhysicalBlocksize: pblksize, - Writable: writable, - DefaultBlocks: defaultBlocks, - } - // try to initialize the partition table. - // we ignore errors, because it is perfectly fine to open a disk - // and use it before it has a partition table. This is solely - // a convenience. - if table, err := ret.GetPartitionTable(); err == nil && table != nil { - ret.Table = table - } - return ret, nil + //nolint:errcheck // we ignore errors, because it is perfectly fine to open a disk and use it before it has a + // partition table. This is solely a convenience. + newDisk.GetPartitionTable() + + return newDisk, nil } func checkDevice(device string) error { @@ -210,6 +207,7 @@ func WithSectorSize(sectorSize SectorSize) OpenOpt { } } +// Might be deprecated in future: use .New + diskfs.OpenStorage // Open a Disk from a path to a device in read-write exclusive mode // Should pass a path to a block device e.g. /dev/sda or a path to a file /tmp/foo.img // The provided device must exist at the time you call Open(). @@ -240,24 +238,32 @@ func Open(device string, opts ...OpenOpt) (*disk.Disk, error) { return initDisk(f, ReadWriteExclusive, opt.sectorSize) } +// Open a Disk using provided fs.File to a device in read-only mode +// Use OpenOpt to control options, such as sector size or open mode. +func OpenStorage(f fs.File, opts ...OpenOpt) (*disk.Disk, error) { + opt := &openOpts{ + mode: ReadOnly, + sectorSize: SectorSizeDefault, + } + + for _, o := range opts { + if err := o(opt); err != nil { + return nil, err + } + } + + return initDisk(f, opt.mode, opt.sectorSize) +} + +// Might be deprecated in future: use .CreateFromPath + diskfs.OpenStorage // Create a Disk from a path to a device // Should pass a path to a block device e.g. /dev/sda or a path to a file /tmp/foo.img // The provided device must not exist at the time you call Create() -func Create(device string, size int64, _ Format, sectorSize SectorSize) (*disk.Disk, error) { - if device == "" { - return nil, errors.New("must pass device name") - } - if size <= 0 { - return nil, errors.New("must pass valid device size to create") - } - f, err := os.OpenFile(device, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o666) +func Create(device string, size int64, sectorSize SectorSize) (*disk.Disk, error) { + rawBackend, err := raw.CreateFromPath(device, size) if err != nil { - return nil, fmt.Errorf("could not create device %s: %w", device, err) - } - err = os.Truncate(device, size) - if err != nil { - return nil, fmt.Errorf("could not expand device %s to size %d: %w", device, size, err) + return nil, err } // return our disk - return initDisk(f, ReadWriteExclusive, sectorSize) + return initDisk(rawBackend, ReadWriteExclusive, sectorSize) } diff --git a/diskfs_test.go b/diskfs_test.go index 314be098..245390e9 100644 --- a/diskfs_test.go +++ b/diskfs_test.go @@ -60,7 +60,7 @@ func checkDiskfsErrs(t *testing.T, msg string, err, tterr error, d, ttd *disk.Di t.Errorf("%s: mismatched errors, actual %v expected %v", msg, err, tterr) case (d == nil && ttd != nil) || (d != nil && ttd == nil): t.Errorf("%s: mismatched disk, actual %v expected %v", msg, d, ttd) - case d != nil && (d.LogicalBlocksize != ttd.LogicalBlocksize || d.PhysicalBlocksize != ttd.PhysicalBlocksize || d.Size != ttd.Size || d.Type != ttd.Type): + case d != nil && (d.LogicalBlocksize != ttd.LogicalBlocksize || d.PhysicalBlocksize != ttd.PhysicalBlocksize || d.Size != ttd.Size): t.Errorf("%s: mismatched disk, actual then expected", msg) t.Logf("%v", d) t.Logf("%v", ttd) @@ -103,22 +103,28 @@ func TestGPTOpen(t *testing.T) { }{ {"", nil, fmt.Errorf("must pass device name")}, {"/tmp/foo/bar/232323/23/2322/disk.img", nil, fmt.Errorf("")}, - {path, &disk.Disk{Type: disk.File, LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: size}, nil}, - {filePadded, &disk.Disk{Type: disk.File, LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: filePaddedSize}, nil}, + {path, &disk.Disk{LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: size}, nil}, + {filePadded, &disk.Disk{LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: filePaddedSize}, nil}, } for _, tt := range tests { d, err := diskfs.Open(tt.path) + msg := fmt.Sprintf("Open(%s)", tt.path) checkDiskfsErrs(t, msg, err, tt.err, d, tt.disk) + if d != nil { table, err := d.GetPartitionTable() if err != nil { t.Errorf("%s: mismatched errors, actual %v expected %v", msg, err, tt.err) } + backingFile, err := d.Backend.Writable() + if err != nil { + t.Fatal(err) + } // Verify will compare the GPT table to the disk and attempt to read the secondary header if possible - err = table.Verify(d.File, uint64(tt.disk.Size)) + err = table.Verify(backingFile, uint64(tt.disk.Size)) if err != nil { // We log this as it's epected to be an error t.Logf("%s: mismatched errors, actual %v expected %v", msg, err, tt.err) @@ -131,13 +137,13 @@ func TestGPTOpen(t *testing.T) { } // Update both tables on disk - err = table.Write(d.File, tt.disk.Size) + err = table.Write(backingFile, tt.disk.Size) if err != nil { t.Errorf("%s: mismatched errors, actual %v expected %v", msg, err, tt.err) } // Check that things are as expected. - err = table.Verify(d.File, uint64(tt.disk.Size)) + err = table.Verify(backingFile, uint64(tt.disk.Size)) if err != nil { t.Errorf("%s: mismatched errors, actual %v expected %v", msg, err, tt.err) } @@ -172,7 +178,7 @@ func TestMBROpen(t *testing.T) { }{ {"", nil, fmt.Errorf("must pass device name")}, {"/tmp/foo/bar/232323/23/2322/disk.img", nil, fmt.Errorf("")}, - {path, &disk.Disk{Type: disk.File, LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: size}, nil}, + {path, &disk.Disk{LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: size}, nil}, } // open default @@ -195,18 +201,17 @@ func TestCreate(t *testing.T) { name string path string size int64 - format diskfs.Format sectorSize diskfs.SectorSize disk *disk.Disk err error }{ - {"no file", "", 10 * oneMB, diskfs.Raw, diskfs.SectorSizeDefault, nil, fmt.Errorf("must pass device name")}, - {"zero size", "disk", 0, diskfs.Raw, diskfs.SectorSizeDefault, nil, fmt.Errorf("must pass valid device size to create")}, - {"negative size", "disk", -1, diskfs.Raw, diskfs.SectorSizeDefault, nil, fmt.Errorf("must pass valid device size to create")}, - {"directory does not exist", "foo/bar/232323/23/2322/disk", 10 * oneMB, diskfs.Raw, diskfs.SectorSizeDefault, nil, fmt.Errorf("could not create device")}, - {"10MB with default sector size", "disk", 10 * oneMB, diskfs.Raw, diskfs.SectorSizeDefault, &disk.Disk{LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: 10 * oneMB, Type: disk.File}, nil}, - {"10MB with 512 sector size", "disk", 10 * oneMB, diskfs.Raw, diskfs.SectorSize512, &disk.Disk{LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: 10 * oneMB, Type: disk.File}, nil}, - {"10MB with 2048 sector size", "disk", 10 * oneMB, diskfs.Raw, diskfs.SectorSize4k, &disk.Disk{LogicalBlocksize: 4096, PhysicalBlocksize: 4096, Size: 10 * oneMB, Type: disk.File}, nil}, + {"no file", "", 10 * oneMB, diskfs.SectorSizeDefault, nil, fmt.Errorf("must pass device name")}, + {"zero size", "disk", 0, diskfs.SectorSizeDefault, nil, fmt.Errorf("must pass valid device size to create")}, + {"negative size", "disk", -1, diskfs.SectorSizeDefault, nil, fmt.Errorf("must pass valid device size to create")}, + {"directory does not exist", "foo/bar/232323/23/2322/disk", 10 * oneMB, diskfs.SectorSizeDefault, nil, fmt.Errorf("could not create device")}, + {"10MB with default sector size", "disk", 10 * oneMB, diskfs.SectorSizeDefault, &disk.Disk{LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: 10 * oneMB}, nil}, + {"10MB with 512 sector size", "disk", 10 * oneMB, diskfs.SectorSize512, &disk.Disk{LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: 10 * oneMB}, nil}, + {"10MB with 2048 sector size", "disk", 10 * oneMB, diskfs.SectorSize4k, &disk.Disk{LogicalBlocksize: 4096, PhysicalBlocksize: 4096, Size: 10 * oneMB}, nil}, } for i, t2 := range tests { @@ -216,9 +221,9 @@ func TestCreate(t *testing.T) { if tt.path != "" { filename = testTmpFilename(t, "diskfs_test"+tt.path, ".img") } - d, err := diskfs.Create(filename, tt.size, tt.format, tt.sectorSize) + d, err := diskfs.Create(filename, tt.size, tt.sectorSize) defer os.RemoveAll(filename) - msg := fmt.Sprintf("%d: Create(%s, %d, %v, %d)", i, filename, tt.size, tt.format, tt.sectorSize) + msg := fmt.Sprintf("%d: Create(%s, %d, %v, %d)", i, filename, tt.size, 0, tt.sectorSize) checkDiskfsErrs(t, msg, err, tt.err, d, tt.disk) }) } diff --git a/example_test.go b/example_test.go index 6b99bac2..7604266c 100644 --- a/example_test.go +++ b/example_test.go @@ -6,6 +6,7 @@ import ( "os" diskfs "github.com/diskfs/go-diskfs" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/disk" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/partition/gpt" @@ -27,7 +28,7 @@ func ExampleCreate_fat32() { diskImg := "/tmp/disk.img" defer os.Remove(diskImg) - theDisk, _ := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) + theDisk, _ := diskfs.Create(diskImg, size, diskfs.SectorSizeDefault) fs, err := theDisk.CreateFilesystem(disk.FilesystemSpec{ Partition: 0, @@ -44,7 +45,7 @@ func ExampleCreate_mbr() { diskImg := "/tmp/disk.img" defer os.Remove(diskImg) - theDisk, _ := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) + theDisk, _ := diskfs.Create(diskImg, size, diskfs.SectorSizeDefault) table := &mbr.Table{ LogicalSectorSize: 512, @@ -75,7 +76,7 @@ func ExampleCreate_gpt() { diskImg := "/tmp/disk.img" defer os.Remove(diskImg) - theDisk, _ := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) + theDisk, _ := diskfs.Create(diskImg, size, diskfs.SectorSizeDefault) table := &gpt.Table{ LogicalSectorSize: 512, @@ -106,7 +107,7 @@ func ExampleCreate_fat32WithDirsAndFiles() { diskImg := "/tmp/disk.img" defer os.Remove(diskImg) - theDisk, _ := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault) + theDisk, _ := diskfs.Create(diskImg, size, diskfs.SectorSizeDefault) table := &mbr.Table{ LogicalSectorSize: 512, @@ -143,3 +144,62 @@ func ExampleCreate_fat32WithDirsAndFiles() { check(err) unused(written) } + +// Create a disk of size 20MB at provided pathname using backend abstraction. +// This is a proposed way of usage after factoring out underlying file operations to the backend. +// diskfs.Create() is slowly deprecating now. +func ExampleOpenStorage_create() { + theBackend, err := raw.CreateFromPath("/tmp/my.img", 20*1024*1024) + check(err) + + theDisk, err := diskfs.OpenStorage(theBackend) + check(err) + unused(theDisk) +} + +// Open existing image using backend abstraction +// This is a proposed way of usage after factoring out underlying file operations to the backend. +// diskfs.Open() is slowly deprecating now. +func ExampleOpenStorage_open() { + theBackend, err := raw.CreateFromPath("/tmp/my.img", 20*1024*1024) + check(err) + defer os.Remove("/tmp/my.img") + + theDisk, err := diskfs.OpenStorage(theBackend) + check(err) + unused(theDisk) +} + +// Create a disk from existing fs.File. +// All validations if provided fs.File is suitable, are delayed until the first use of corresponding ReadAt() or +// Writable() invocation. So, remember checking all errors! +func ExampleOpenStorage_file() { + // some exemplary fs.FS + dirfs := os.DirFS("/tmp/images") + // the fs.File we'll use + f, err := dirfs.Open("theImage.raw") + check(err) + + // if provided fs.File does not implement backend.Storage, it will be internally wrapped into raw backend + // implementation. + theDisk, err := diskfs.OpenStorage(f) + check(err) + unused(theDisk) +} + +// Instantiate the backend manually for later use in diskfs.OpenStorage +func ExampleOpenStorage_backend() { + // some exemplary fs.FS + dirfs := os.DirFS("/tmp/images") + // the fs.File we'll use + f, err := dirfs.Open("theImage.raw") + check(err) + + theBackend := raw.New(f, false) + + // As opposed to File example, theBackend will be used as is (i.e. not wrapped). + // This usage scenario is for upcoming support for other backends (qcow2 and such). + theDisk, err := diskfs.OpenStorage(theBackend) + check(err) + unused(theDisk) +} diff --git a/examples/bootable_iso.go b/examples/bootable_iso.go index f907a63f..2c2589f7 100644 --- a/examples/bootable_iso.go +++ b/examples/bootable_iso.go @@ -16,7 +16,7 @@ func CreateBootableIso(diskImg string) { log.Fatal("must have a valid path for diskImg") } var diskSize int64 = 10 * 1024 * 1024 // 10 MB - mydisk, err := diskfs.Create(diskImg, diskSize, diskfs.Raw, diskfs.SectorSizeDefault) + mydisk, err := diskfs.Create(diskImg, diskSize, diskfs.SectorSizeDefault) check(err) // the following line is required for an ISO, which may have logical block sizes diff --git a/examples/create-iso-from-folder/go.mod b/examples/create-iso-from-folder/go.mod index b3fe8182..b098c4ff 100644 --- a/examples/create-iso-from-folder/go.mod +++ b/examples/create-iso-from-folder/go.mod @@ -1,15 +1,21 @@ module main -go 1.19 +go 1.22 + +toolchain go1.23.2 + +replace github.com/diskfs/go-diskfs => ../.. require github.com/diskfs/go-diskfs v1.3.0 require ( + github.com/djherbis/times v1.6.0 // indirect + github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect github.com/google/uuid v1.3.0 // indirect - github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pkg/xattr v0.4.9 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect github.com/ulikunitz/xz v0.5.11 // indirect - golang.org/x/sys v0.5.0 // indirect - gopkg.in/djherbis/times.v1 v1.3.0 // indirect + golang.org/x/sys v0.19.0 // indirect ) diff --git a/examples/create-iso-from-folder/go.sum b/examples/create-iso-from-folder/go.sum index a1727cf7..bc7a3fcf 100644 --- a/examples/create-iso-from-folder/go.sum +++ b/examples/create-iso-from-folder/go.sum @@ -1,52 +1,37 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/diskfs/go-diskfs v1.3.0 h1:D3IVe1y7ybB5SjCO0pOmkWThL9lZEWeanp8rRa0q0sk= -github.com/diskfs/go-diskfs v1.3.0/go.mod h1:3pUpCAz75Q11om5RsGpVKUgXp2Z+ATw1xV500glmCP0= -github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= -github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= +github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= +github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pierrec/lz4 v2.3.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= +github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= -gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= -gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/create-iso-from-folder/main.go b/examples/create-iso-from-folder/main.go index e95f7239..3f2e11f6 100644 --- a/examples/create-iso-from-folder/main.go +++ b/examples/create-iso-from-folder/main.go @@ -45,8 +45,7 @@ func CreateIsoFromFolder(srcFolder string, outputFileName string) { var LogicalBlocksize diskfs.SectorSize = 2048 // Create the disk image - // TODO: Explain why we need to use Raw here - mydisk, err := diskfs.Create(outputFileName, folderSize, diskfs.Raw, LogicalBlocksize) + mydisk, err := diskfs.Create(outputFileName, folderSize, LogicalBlocksize) check(err) // Create the ISO filesystem on the disk image diff --git a/examples/efi_create.go b/examples/efi_create.go index 40979eac..fda04788 100644 --- a/examples/efi_create.go +++ b/examples/efi_create.go @@ -25,7 +25,7 @@ func CreateEfi(diskImg string) { ) // create a disk image - disk, err := diskfs.Create(diskImg, diskSize, diskfs.Raw, diskfs.SectorSizeDefault) + disk, err := diskfs.Create(diskImg, diskSize, diskfs.SectorSizeDefault) if err != nil { log.Panic(err) } diff --git a/examples/iso_create.go b/examples/iso_create.go index 26f5bde5..36ff123d 100644 --- a/examples/iso_create.go +++ b/examples/iso_create.go @@ -16,7 +16,7 @@ func CreateIso(diskImg string) { log.Fatal("must have a valid path for diskImg") } var diskSize int64 = 10 * 1024 * 1024 // 10 MB - mydisk, err := diskfs.Create(diskImg, diskSize, diskfs.Raw, diskfs.SectorSizeDefault) + mydisk, err := diskfs.Create(diskImg, diskSize, diskfs.SectorSizeDefault) check(err) // the following line is required for an ISO, which may have logical block sizes diff --git a/examples/squashfs_create.go b/examples/squashfs_create.go index 7f7f2ad2..6d1d127d 100644 --- a/examples/squashfs_create.go +++ b/examples/squashfs_create.go @@ -16,7 +16,7 @@ func CreateSquashfs(diskImg string) { log.Fatal("must have a valid path for diskImg") } var diskSize int64 = 10 * 1024 * 1024 // 10 MB - mydisk, err := diskfs.Create(diskImg, diskSize, diskfs.Raw, diskfs.SectorSizeDefault) + mydisk, err := diskfs.Create(diskImg, diskSize, diskfs.SectorSizeDefault) check(err) fspec := disk.FilesystemSpec{Partition: 0, FSType: filesystem.TypeSquashfs, VolumeLabel: "label"} diff --git a/filesystem/ext4/ext4.go b/filesystem/ext4/ext4.go index 8f326e6b..898182f6 100644 --- a/filesystem/ext4/ext4.go +++ b/filesystem/ext4/ext4.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/filesystem/ext4/crc" "github.com/diskfs/go-diskfs/util" @@ -97,7 +98,7 @@ type FileSystem struct { blockGroups int64 size int64 start int64 - file util.File + file backend.Storage } // Equal compare if two filesystems are equal @@ -110,8 +111,8 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // Create creates an ext4 filesystem in a given file or device // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, +// requires the backend.WritableFile where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.WritableFile to create the filesystem, // and sectorsize is is the logical sector size to use for creating the filesystem // // blocksize is the size of the ext4 blocks, and is calculated as sectorsPerBlock * sectorsize. @@ -131,7 +132,7 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // or 512, it will return an error. // //nolint:gocyclo // yes, this has high cyclomatic complexity, but we can accept it -func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, error) { +func Create(f backend.WritableFile, size, start, sectorsize int64, p *Params) (*FileSystem, error) { // be safe about the params pointer if p == nil { p = &Params{} @@ -589,14 +590,14 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, blockGroups: blockGroups, size: size, start: start, - file: f, + file: util.BackendFromFile(f, false), }, nil } // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.File where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.File the filesystem is expected to begin, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -609,7 +610,7 @@ func Create(f util.File, size, start, sectorsize int64, p *Params) (*FileSystem, // // If the provided blocksize is 0, it will use the default of 512 bytes. If it is any number other than 0 // or 512, it will return an error. -func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { +func Read(file backend.File, size, start, sectorsize int64) (*FileSystem, error) { // blocksize must be <=0 or exactly SectorSize512 or error if sectorsize != int64(SectorSize512) && sectorsize > 0 { return nil, fmt.Errorf("sectorsize for ext4 must be either 512 bytes or 0, not %d", sectorsize) @@ -680,7 +681,7 @@ func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { blockGroups: int64(sb.blockGroupCount()), size: size, start: start, - file: file, + file: util.BackendFromFile(file, false), }, nil } @@ -877,6 +878,12 @@ func (fs *FileSystem) Remove(p string) error { if entry == nil { return fmt.Errorf("file does not exist: %s", p) } + + writableFile, err := fs.file.Writable() + + if err != nil { + return err + } // if it is a directory, it must be empty if entry.fileType == dirFileTypeDirectory { // read the directory @@ -956,7 +963,7 @@ func (fs *FileSystem) Remove(p string) error { for _, e := range extents { for i := 0; i < int(e.count); i++ { b := dirBytes[i:fs.superblock.blockSize] - if _, err := fs.file.WriteAt(b, (int64(i)+int64(e.startingBlock))*int64(fs.superblock.blockSize)); err != nil { + if _, err := writableFile.WriteAt(b, (int64(i)+int64(e.startingBlock))*int64(fs.superblock.blockSize)); err != nil { return fmt.Errorf("could not write inode bitmap back to disk: %v", err) } } @@ -985,7 +992,7 @@ func (fs *FileSystem) Remove(p string) error { if fs.superblock.blockSize == 1024 { gdtBlock = 2 } - if _, err := fs.file.WriteAt(gdBytes, fs.start+int64(gdtBlock)*int64(fs.superblock.blockSize)+int64(gd.number)*int64(fs.superblock.groupDescriptorSize)); err != nil { + if _, err := writableFile.WriteAt(gdBytes, fs.start+int64(gdtBlock)*int64(fs.superblock.blockSize)+int64(gd.number)*int64(fs.superblock.groupDescriptorSize)); err != nil { return fmt.Errorf("could not write Group Descriptor bytes to file: %v", err) } @@ -1129,6 +1136,12 @@ func (fs *FileSystem) readInode(inodeNumber uint32) (*inode, error) { // writeInode write a single inode to disk func (fs *FileSystem) writeInode(i *inode) error { + writableFile, err := fs.file.Writable() + + if err != nil { + return err + } + sb := fs.superblock inodeSize := sb.inodeSize inodesPerGroup := sb.inodesPerGroup @@ -1147,7 +1160,7 @@ func (fs *FileSystem) writeInode(i *inode) error { // offset is how many bytes in our inode is offset := int64(offsetInode) * int64(inodeSize) inodeBytes := i.toBytes(sb) - wrote, err := fs.file.WriteAt(inodeBytes, int64(byteStart)+offset) + wrote, err := writableFile.WriteAt(inodeBytes, int64(byteStart)+offset) if err != nil { return fmt.Errorf("failed to write inode %d at offset %d of block %d from block group %d: %v", i.number, offset, inodeTableBlock, bg, err) } @@ -1528,6 +1541,11 @@ func (fs *FileSystem) allocateInode(parent uint32) (uint32, error) { gd groupDescriptor ) + writableFile, err := fs.file.Writable() + if err != nil { + return 0, err + } + for _, gd = range fs.groupDescriptors.descriptors { if inodeNumber != -1 { break @@ -1567,7 +1585,7 @@ func (fs *FileSystem) allocateInode(parent uint32) (uint32, error) { gdtBlock := 1 blockByteLocation := gdtBlock * int(fs.superblock.blockSize) gdOffset := fs.start + int64(blockByteLocation) + int64(bg)*int64(fs.superblock.groupDescriptorSize) - wrote, err := fs.file.WriteAt(gdBytes, gdOffset) + wrote, err := writableFile.WriteAt(gdBytes, gdOffset) if err != nil { return 0, fmt.Errorf("unable to write group descriptor bytes for blockgroup %d: %v", bg, err) } @@ -1739,12 +1757,16 @@ func (fs *FileSystem) writeInodeBitmap(bm *util.Bitmap, group int) error { if group >= len(fs.groupDescriptors.descriptors) { return fmt.Errorf("block group %d does not exist", group) } + writableFile, err := fs.file.Writable() + if err != nil { + return err + } b := bm.ToBytes() gd := fs.groupDescriptors.descriptors[group] bitmapByteCount := fs.superblock.inodesPerGroup / 8 bitmapLocation := gd.inodeBitmapLocation offset := int64(bitmapLocation*uint64(fs.superblock.blockSize) + uint64(fs.start)) - wrote, err := fs.file.WriteAt(b, offset) + wrote, err := writableFile.WriteAt(b, offset) if err != nil { return fmt.Errorf("unable to write inode bitmap for blockgroup %d: %w", gd.number, err) } @@ -1781,11 +1803,15 @@ func (fs *FileSystem) writeBlockBitmap(bm *util.Bitmap, group int) error { if group >= len(fs.groupDescriptors.descriptors) { return fmt.Errorf("block group %d does not exist", group) } + writableFile, err := fs.file.Writable() + if err != nil { + return err + } b := bm.ToBytes() gd := fs.groupDescriptors.descriptors[group] bitmapLocation := gd.blockBitmapLocation offset := int64(bitmapLocation*uint64(fs.superblock.blockSize) + uint64(fs.start)) - wrote, err := fs.file.WriteAt(b, offset) + wrote, err := writableFile.WriteAt(b, offset) if err != nil { return fmt.Errorf("unable to write block bitmap for blockgroup %d: %w", gd.number, err) } @@ -1797,11 +1823,15 @@ func (fs *FileSystem) writeBlockBitmap(bm *util.Bitmap, group int) error { } func (fs *FileSystem) writeSuperblock() error { + writableFile, err := fs.file.Writable() + if err != nil { + return err + } superblockBytes, err := fs.superblock.toBytes() if err != nil { return fmt.Errorf("could not convert superblock to bytes: %v", err) } - _, err = fs.file.WriteAt(superblockBytes, fs.start+int64(BootSectorSize)) + _, err = writableFile.WriteAt(superblockBytes, fs.start+int64(BootSectorSize)) return err } diff --git a/filesystem/ext4/extent.go b/filesystem/ext4/extent.go index e5d456e0..3e8ce913 100644 --- a/filesystem/ext4/extent.go +++ b/filesystem/ext4/extent.go @@ -680,8 +680,13 @@ func writeNodeToDisk(node extentBlockFinder, fs *FileSystem, parent *extentInter return fmt.Errorf("block number not found for node") } + writableFile, err := fs.file.Writable() + if err != nil { + return err + } + data := node.toBytes() - _, err := fs.file.WriteAt(data, int64(blockNumber)*int64(fs.superblock.blockSize)) + _, err = writableFile.WriteAt(data, int64(blockNumber)*int64(fs.superblock.blockSize)) return err } diff --git a/filesystem/ext4/file.go b/filesystem/ext4/file.go index 4dc65395..b4cc7bda 100644 --- a/filesystem/ext4/file.go +++ b/filesystem/ext4/file.go @@ -145,6 +145,12 @@ func (fl *File) Write(b []byte) (int, error) { // the offset given for reading is relative to the file, so we need to calculate // where these are in the extents relative to the file writeStartBlock := uint64(fl.offset) / blocksize + + writableFile, err := fl.filesystem.file.Writable() + if err != nil { + return -1, err + } + for _, e := range fl.extents { // if the last block of the extent is before the first block we want to write, skip it if uint64(e.fileBlock)+uint64(e.count) < writeStartBlock { @@ -164,7 +170,7 @@ func (fl *File) Write(b []byte) (int, error) { startPosOnDisk := e.startingBlock*blocksize + uint64(startPositionInExtent) b2 := make([]byte, toWriteInOffset) copy(b2, b[writtenBytes:]) - written, err := fl.filesystem.file.WriteAt(b2, int64(startPosOnDisk)) + written, err := writableFile.WriteAt(b2, int64(startPosOnDisk)) if err != nil { return int(writtenBytes), fmt.Errorf("failed to read bytes: %v", err) } @@ -175,7 +181,7 @@ func (fl *File) Write(b []byte) (int, error) { break } } - var err error + if fl.offset >= fileSize { err = io.EOF } diff --git a/filesystem/fat32/fat32.go b/filesystem/fat32/fat32.go index 5c17bffa..b36d615e 100644 --- a/filesystem/fat32/fat32.go +++ b/filesystem/fat32/fat32.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/util" ) @@ -59,7 +60,7 @@ type FileSystem struct { bytesPerCluster int size int64 start int64 - file util.File + file backend.Storage } // Equal compare if two filesystems are equal @@ -79,8 +80,8 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // Create creates a FAT32 filesystem in a given file or device // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, +// requires the backend.WritableFile where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.WritableFile to create the filesystem, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to create the filesystem on the entire disk. You could have a disk of size @@ -93,7 +94,7 @@ func (fs *FileSystem) Equal(a *FileSystem) bool { // // If the provided blocksize is 0, it will use the default of 512 bytes. If it is any number other than 0 // or 512, it will return an error. -func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*FileSystem, error) { +func Create(f backend.WritableFile, size, start, blocksize int64, volumeLabel string) (*FileSystem, error) { // blocksize must be <=0 or exactly SectorSize512 or error if blocksize != int64(SectorSize512) && blocksize > 0 { return nil, fmt.Errorf("blocksize for FAT32 must be either 512 bytes or 0, not %d", blocksize) @@ -112,6 +113,12 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil fsisPrimarySector := uint16(1) backupBootSector := uint16(6) + backingStorage := util.BackendFromFile(f, false) + writableFile, err := backingStorage.Writable() + if err != nil { + return nil, err + } + /* size calculations we have the total size of the disk from `size uint64` @@ -252,7 +259,7 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil bytesPerCluster: int(sectorsPerCluster) * int(SectorSize512), start: start, size: size, - file: f, + file: backingStorage, } // write the boot sector @@ -277,7 +284,7 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil // length of cluster in bytes tmpb := make([]byte, fs.bytesPerCluster) // zero out the root directory cluster - written, err := f.WriteAt(tmpb, clusterStart) + written, err := writableFile.WriteAt(tmpb, clusterStart) if err != nil { return nil, fmt.Errorf("failed to zero out root directory: %w", err) } @@ -310,8 +317,8 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.File where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.File the filesystem is expected to begin, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -324,7 +331,7 @@ func Create(f util.File, size, start, blocksize int64, volumeLabel string) (*Fil // // If the provided blocksize is 0, it will use the default of 512 bytes. If it is any number other than 0 // or 512, it will return an error. -func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { +func Read(f backend.File, size, start, blocksize int64) (*FileSystem, error) { // blocksize must be <=0 or exactly SectorSize512 or error if blocksize != int64(SectorSize512) && blocksize > 0 { return nil, fmt.Errorf("blocksize for FAT32 must be either 512 bytes or 0, not %d", blocksize) @@ -335,7 +342,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { if size < blocksize*4 { return nil, fmt.Errorf("requested size is smaller than minimum allowed FAT32 size %d", blocksize*4) } - + file := util.BackendFromFile(f, true) // load the information from the disk // read first 512 bytes from the file bsb := make([]byte, SectorSize512) @@ -391,7 +398,8 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { bytesPerCluster: int(sectorsPerCluster) * int(SectorSize512), start: start, size: size, - file: file, + // FIXME: how to detect readonly here?! + file: util.BackendFromFile(f, true), }, nil } @@ -403,6 +411,10 @@ func (fs *FileSystem) writeBootSector() error { return nil, fmt.Errorf("error writing MS-DOS Boot Sector: %v", err) } */ + writableFile, err := fs.file.Writable() + if err != nil { + return err + } b, err := fs.bootSector.toBytes() if err != nil { @@ -410,7 +422,7 @@ func (fs *FileSystem) writeBootSector() error { } // write main boot sector - count, err := fs.file.WriteAt(b, 0+fs.start) + count, err := writableFile.WriteAt(b, 0+fs.start) if err != nil { return fmt.Errorf("error writing MS-DOS Boot Sector to disk: %w", err) } @@ -420,7 +432,7 @@ func (fs *FileSystem) writeBootSector() error { // write backup boot sector to the file if fs.bootSector.biosParameterBlock.backupBootSector > 0 { - count, err = fs.file.WriteAt(b, int64(fs.bootSector.biosParameterBlock.backupBootSector)*int64(SectorSize512)+fs.start) + count, err = writableFile.WriteAt(b, int64(fs.bootSector.biosParameterBlock.backupBootSector)*int64(SectorSize512)+fs.start) if err != nil { return fmt.Errorf("error writing MS-DOS Boot Sector to disk: %w", err) } @@ -438,13 +450,17 @@ func (fs *FileSystem) writeFsis() error { fsisPrimary := int64(fsInformationSector * uint16(SectorSize512)) fsisBytes := fs.fsis.toBytes() + writableFile, err := fs.file.Writable() + if err != nil { + return err + } - if _, err := fs.file.WriteAt(fsisBytes, fsisPrimary+fs.start); err != nil { + if _, err := writableFile.WriteAt(fsisBytes, fsisPrimary+fs.start); err != nil { return fmt.Errorf("unable to write primary Fsis: %w", err) } if backupBootSector > 0 { - if _, err := fs.file.WriteAt(fsisBytes, int64(backupBootSector+1)*int64(SectorSize512)+fs.start); err != nil { + if _, err := writableFile.WriteAt(fsisBytes, int64(backupBootSector+1)*int64(SectorSize512)+fs.start); err != nil { return fmt.Errorf("unable to write backup Fsis: %w", err) } } @@ -458,12 +474,16 @@ func (fs *FileSystem) writeFat() error { fatSecondaryStart := fatPrimaryStart + uint64(fs.table.size) fatBytes := fs.table.bytes() + writableFile, err := fs.file.Writable() + if err != nil { + return err + } - if _, err := fs.file.WriteAt(fatBytes, int64(fatPrimaryStart)+fs.start); err != nil { + if _, err := writableFile.WriteAt(fatBytes, int64(fatPrimaryStart)+fs.start); err != nil { return fmt.Errorf("unable to write primary FAT table: %w", err) } - if _, err := fs.file.WriteAt(fatBytes, int64(fatSecondaryStart)+fs.start); err != nil { + if _, err := writableFile.WriteAt(fatBytes, int64(fatSecondaryStart)+fs.start); err != nil { return fmt.Errorf("unable to write backup FAT table: %w", err) } @@ -916,6 +936,11 @@ func (fs *FileSystem) writeDirectoryEntries(dir *Directory) error { if err != nil { return fmt.Errorf("could not create a valid byte stream for a FAT32 Entries: %w", err) } + + writableFile, err := fs.file.Writable() + if err != nil { + return err + } // now have to expand with zeros to the a multiple of cluster lengths // how many clusters do we need, how many do we have? clusterList, err := fs.getClusterList(dir.clusterLocation) @@ -936,7 +961,7 @@ func (fs *FileSystem) writeDirectoryEntries(dir *Directory) error { // bytes where the cluster starts clusterStart := fs.start + int64(fs.dataStart) + int64(cluster-2)*int64(fs.bytesPerCluster) bStart := i * fs.bytesPerCluster - written, err := fs.file.WriteAt(b[bStart:bStart+fs.bytesPerCluster], clusterStart) + written, err := writableFile.WriteAt(b[bStart:bStart+fs.bytesPerCluster], clusterStart) if err != nil { return fmt.Errorf("error writing directory entries: %w", err) } diff --git a/filesystem/fat32/fat32_internal_test.go b/filesystem/fat32/fat32_internal_test.go index d2b8242b..9e879941 100644 --- a/filesystem/fat32/fat32_internal_test.go +++ b/filesystem/fat32/fat32_internal_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/testhelper" "github.com/google/go-cmp/cmp" ) @@ -70,12 +71,12 @@ func getValidFat32FSSmall() *FileSystem { }, bytesPerCluster: 512, dataStart: 178176, - file: &testhelper.FileImpl{ + file: raw.New(&testhelper.FileImpl{ //nolint:revive // unused parameter, keeping name makes it easier to use in the future Writer: func(b []byte, offset int64) (int, error) { return len(b), nil }, - }, + }, false), fsis: FSInformationSector{}, bootSector: msDosBootSector{ biosParameterBlock: &dos71EBPB{ @@ -136,7 +137,7 @@ func TestFat32ReadDirectory(t *testing.T) { defer file.Close() fs := &FileSystem{ table: *getValidFat32Table(), - file: file, + file: raw.New(file, false), bytesPerCluster: int(fsInfo.bytesPerCluster), dataStart: fsInfo.dataStartBytes, } @@ -337,7 +338,7 @@ func TestFat32ReadDirWithMkdir(t *testing.T) { } for _, tt := range tests { - fs.file = &testhelper.FileImpl{ + fs.file = raw.New(&testhelper.FileImpl{ //nolint:revive // unused parameter, keeping name makes it easier to use in the future Writer: func(b []byte, offset int64) (int, error) { return len(b), nil @@ -346,7 +347,8 @@ func TestFat32ReadDirWithMkdir(t *testing.T) { copy(b, datab[offset:]) return len(b), nil }, - } + }, false) + dir, entries, err := fs.readDirWithMkdir(tt.path, tt.doMake) switch { case (err == nil && tt.err != nil) || (err != nil && tt.err == nil) || (err != nil && tt.err != nil && !strings.HasPrefix(err.Error(), tt.err.Error())): diff --git a/filesystem/fat32/fat32_test.go b/filesystem/fat32/fat32_test.go index 70e822f8..00142ed5 100644 --- a/filesystem/fat32/fat32_test.go +++ b/filesystem/fat32/fat32_test.go @@ -18,11 +18,12 @@ import ( "testing" "github.com/diskfs/go-diskfs" + "github.com/diskfs/go-diskfs/backend" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/disk" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/filesystem/fat32" "github.com/diskfs/go-diskfs/testhelper" - "github.com/diskfs/go-diskfs/util" ) var ( @@ -116,7 +117,7 @@ func TestFat32Mkdir(t *testing.T) { return } //nolint:thelper // this is not a helper function - runTest := func(t *testing.T, post, pre int64, fatFunc func(util.File, int64, int64, int64) (*fat32.FileSystem, error)) { + runTest := func(t *testing.T, post, pre int64, fatFunc func(backend.Storage, int64, int64, int64) (*fat32.FileSystem, error)) { // create our directories tests := []string{ "/", @@ -137,7 +138,8 @@ func TestFat32Mkdir(t *testing.T) { if err != nil { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } - fs, err := fatFunc(f, fileInfo.Size()-pre-post, pre, 512) + + fs, err := fatFunc(raw.New(f, false), fileInfo.Size()-pre-post, pre, 512) if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -161,18 +163,26 @@ func TestFat32Mkdir(t *testing.T) { } } } - t.Run("read to Mkdir", func(t *testing.T) { + t.Run("fat32.Read to Mkdir", func(t *testing.T) { + // This is to enable Read "fit" into the common testing logic + readShim := func(file backend.Storage, size int64, start int64, blocksize int64) (*fat32.FileSystem, error) { + return fat32.Read(file, size, start, blocksize) + } t.Run("entire image", func(t *testing.T) { - runTest(t, 0, 0, fat32.Read) + runTest(t, 0, 0, readShim) }) t.Run("embedded filesystem", func(t *testing.T) { - runTest(t, 500, 1000, fat32.Read) + runTest(t, 500, 1000, readShim) }) }) - t.Run("Create to Mkdir", func(t *testing.T) { + t.Run("fat32.Create to Mkdir", func(t *testing.T) { // This is to enable Create "fit" into the common testing logic - createShim := func(file util.File, size int64, start int64, blocksize int64) (*fat32.FileSystem, error) { - return fat32.Create(file, size, start, blocksize, "") + createShim := func(file backend.Storage, size int64, start int64, blocksize int64) (*fat32.FileSystem, error) { + writable, err := file.Writable() + if err != nil { + return nil, err + } + return fat32.Create(writable, size, start, blocksize, "") } t.Run("entire image", func(t *testing.T) { runTest(t, 0, 0, createShim) @@ -478,7 +488,7 @@ func TestFat32OpenFile(t *testing.T) { if err != nil { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } - fs, err := fat32.Read(f, fileInfo.Size()-pre-post, pre, 512) + fs, err := fat32.Read(raw.New(f, false), fileInfo.Size()-pre-post, pre, 512) if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -554,7 +564,12 @@ func TestFat32OpenFile(t *testing.T) { if err != nil { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } - fs, err := fat32.Create(f, fileInfo.Size()-pre-post, pre, 512, " NO NAME") + backend := raw.New(f, false) + writable, err := backend.Writable() + if err != nil { + t.Fatal("backend is not writable") + } + fs, err := fat32.Create(writable, fileInfo.Size()-pre-post, pre, 512, " NO NAME") if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -610,22 +625,24 @@ func TestFat32OpenFile(t *testing.T) { //nolint:thelper // this is not a helper function runTest := func(t *testing.T, pre, post int64) { // get a temporary working file - f, err := tmpFat32(true, pre, post) + file, err := tmpFat32(true, pre, post) if err != nil { t.Fatal(err) } + + f := raw.New(file, false) if keepTmpFiles == "" { - defer os.Remove(f.Name()) + defer os.Remove(file.Name()) } else { - fmt.Println(f.Name()) + fmt.Println(file.Name()) } fileInfo, err := f.Stat() if err != nil { - t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) + t.Fatalf("error getting file info for tmpfile %s: %v", file.Name(), err) } fs, err := fat32.Read(f, fileInfo.Size()-pre-post, pre, 512) if err != nil { - t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) + t.Fatalf("error reading fat32 filesystem from %s: %v", file.Name(), err) } path := "/abcdefghi" mode := os.O_RDWR | os.O_CREATE @@ -683,7 +700,7 @@ func TestFat32OpenFile(t *testing.T) { if err != nil { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } - fs, err := fat32.Read(f, fileInfo.Size(), 0, 512) + fs, err := fat32.Read(raw.New(f, false), fileInfo.Size(), 0, 512) if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -750,7 +767,7 @@ func TestFat32OpenFile(t *testing.T) { if err != nil { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } - fs, err := fat32.Read(f, fileInfo.Size()-pre-post, pre, 512) + fs, err := fat32.Read(raw.New(f, false), fileInfo.Size()-pre-post, pre, 512) if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -828,7 +845,7 @@ func TestFat32Label(t *testing.T) { } // read the filesystem - fs, err := fat32.Read(f, fileInfo.Size(), 0, 512) + fs, err := fat32.Read(raw.New(f, false), fileInfo.Size(), 0, 512) if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -858,8 +875,13 @@ func TestFat32Label(t *testing.T) { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } + theBackend := raw.New(f, false) + writable, err := theBackend.Writable() + if err != nil { + t.Fatal("backend not writable") + } // create an empty filesystem - fs, err := fat32.Create(f, fileInfo.Size(), 0, 512, "go-diskfs") + fs, err := fat32.Create(writable, fileInfo.Size(), 0, 512, "go-diskfs") if err != nil { t.Fatalf("error creating fat32 filesystem: %v", err) } @@ -910,8 +932,13 @@ func TestFat32Label(t *testing.T) { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } + theBackend := raw.New(f, false) + writable, err := theBackend.Writable() + if err != nil { + t.Fatal("backend not writable") + } // create an empty filesystem - fs, err := fat32.Create(f, fileInfo.Size(), 0, 512, "go-diskfs") + fs, err := fat32.Create(writable, fileInfo.Size(), 0, 512, "go-diskfs") if err != nil { t.Fatalf("error creating fat32 filesystem: %v", err) } @@ -957,7 +984,12 @@ func TestFat32MkdirCases(t *testing.T) { t.Fatal(err) } defer os.Remove(f.Name()) - fs, err := fat32.Create(f, 1048576, 0, 512, "") + theBackend := raw.New(f, false) + writable, err := theBackend.Writable() + if err != nil { + t.Fatal("backend not writable") + } + fs, err := fat32.Create(writable, 1048576, 0, 512, "") if err != nil { t.Error(err.Error()) } @@ -998,7 +1030,7 @@ func Test83Lowercase(t *testing.T) { if err != nil { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } - fs, err := fat32.Read(f, fileInfo.Size(), 0, 512) + fs, err := fat32.Read(raw.New(f, false), fileInfo.Size(), 0, 512) if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -1036,7 +1068,7 @@ func TestOpenFileCaseInsensitive(t *testing.T) { if err != nil { t.Fatalf("error getting file info for tmpfile %s: %v", f.Name(), err) } - fs, err := fat32.Read(f, fileInfo.Size(), 0, 512) + fs, err := fat32.Read(raw.New(f, false), fileInfo.Size(), 0, 512) if err != nil { t.Fatalf("error reading fat32 filesystem from %s: %v", f.Name(), err) } @@ -1084,7 +1116,7 @@ func TestCreateFileTree(t *testing.T) { // 6GB to test large disk size := int64(6 * 1024 * 1024 * 1024) - d, err := diskfs.Create(tmpImgPath, size, diskfs.Raw, diskfs.SectorSizeDefault) + d, err := diskfs.Create(tmpImgPath, size, diskfs.SectorSizeDefault) if err != nil { t.Fatalf("error creating disk: %v", err) } diff --git a/filesystem/fat32/file.go b/filesystem/fat32/file.go index d6cdb799..3e0f1be6 100644 --- a/filesystem/fat32/file.go +++ b/filesystem/fat32/file.go @@ -106,7 +106,13 @@ func (fl *File) Write(p []byte) (int, error) { if fl == nil || fl.filesystem == nil { return 0, os.ErrClosed } + totalWritten := 0 + writableFile, err := fl.filesystem.file.Writable() + if err != nil { + return totalWritten, err + } + fs := fl.filesystem // if the file was not opened RDWR, nothing we can do if !fl.isReadWrite { @@ -131,7 +137,6 @@ func (fl *File) Write(p []byte) (int, error) { } // write the content for the file bytesPerCluster := fl.filesystem.bytesPerCluster - file := fl.filesystem.file start := int(fl.filesystem.dataStart) clusterIndex := 0 @@ -148,7 +153,7 @@ func (fl *File) Write(p []byte) (int, error) { if toWrite > int64(len(p)) { toWrite = int64(len(p)) } - _, err := file.WriteAt(p[0:toWrite], offset+fs.start) + _, err := writableFile.WriteAt(p[0:toWrite], offset+fs.start) if err != nil { return totalWritten, fmt.Errorf("unable to write to file: %v", err) } @@ -164,7 +169,7 @@ func (fl *File) Write(p []byte) (int, error) { toWrite = left } offset := int64(start) + int64(clusters[i]-2)*int64(bytesPerCluster) - _, err := file.WriteAt(p[totalWritten:totalWritten+toWrite], offset+fs.start) + _, err := writableFile.WriteAt(p[totalWritten:totalWritten+toWrite], offset+fs.start) if err != nil { return totalWritten, fmt.Errorf("unable to write to file: %v", err) } diff --git a/filesystem/fat32/testdata/fat32.go b/filesystem/fat32/testdata/fat32.go index 68c024de..ee3005fb 100644 --- a/filesystem/fat32/testdata/fat32.go +++ b/filesystem/fat32/testdata/fat32.go @@ -20,7 +20,7 @@ func main() { } func mkfs(name string) filesystem.FileSystem { size := int64(10 * 1024 * 1024) - d, err := diskfs.Create(name, size, diskfs.Raw, diskfs.SectorSizeDefault) + d, err := diskfs.Create(name, size, diskfs.SectorSizeDefault) if err != nil { fmt.Printf("error creating disk: %v", err) os.Exit(1) diff --git a/filesystem/iso9660/common_internal_test.go b/filesystem/iso9660/common_internal_test.go index e9e6424f..5ae523af 100644 --- a/filesystem/iso9660/common_internal_test.go +++ b/filesystem/iso9660/common_internal_test.go @@ -4,6 +4,8 @@ import ( "os" "testing" "time" + + "github.com/diskfs/go-diskfs/backend/raw" ) const ( @@ -29,7 +31,7 @@ func GetTestFile(t *testing.T) (file *File, name string) { workspace: "", size: ISO9660Size, start: 0, - file: f, + file: raw.New(f, true), blocksize: 2048, } de := &directoryEntry{ @@ -59,7 +61,7 @@ func GetLargeTestFile(t *testing.T) (file *File, size uint32) { workspace: "", size: ISO9660Size, start: 0, - file: f, + file: raw.New(f, true), blocksize: 2048, } de := &directoryEntry{ diff --git a/filesystem/iso9660/directory_internal_test.go b/filesystem/iso9660/directory_internal_test.go index 480be109..3ee3e31c 100644 --- a/filesystem/iso9660/directory_internal_test.go +++ b/filesystem/iso9660/directory_internal_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/testhelper" ) @@ -29,7 +30,7 @@ func TestDirectoryEntriesFromBytes(t *testing.T) { return 0, fmt.Errorf("unknown area to read %d", offset) }, } - fs.file = f + fs.file = raw.New(f, true) d := &Directory{} diff --git a/filesystem/iso9660/directoryentry_internal_test.go b/filesystem/iso9660/directoryentry_internal_test.go index cc6de23f..d056007f 100644 --- a/filesystem/iso9660/directoryentry_internal_test.go +++ b/filesystem/iso9660/directoryentry_internal_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/testhelper" "github.com/go-test/deep" ) @@ -757,7 +758,7 @@ func TestDirectoryEntryParseDirEntries(t *testing.T) { return 0, fmt.Errorf("unknown area to read %d", offset) }, } - fs.file = f + fs.file = raw.New(f, true) tests := []struct { de []*directoryEntry b []byte @@ -855,7 +856,7 @@ func TestDirectoryEntryGetLocation(t *testing.T) { hasMoreEntries: false, volumeSequence: 1, filename: fmt.Sprintf("%x", 0x00), - filesystem: &FileSystem{blocksize: 2048, file: f}, + filesystem: &FileSystem{blocksize: 2048, file: raw.New(f, true)}, } for _, tt := range tests { diff --git a/filesystem/iso9660/finalize.go b/filesystem/iso9660/finalize.go index aaa32629..383c66fe 100644 --- a/filesystem/iso9660/finalize.go +++ b/filesystem/iso9660/finalize.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/util" "github.com/djherbis/times" ) @@ -463,7 +464,11 @@ func (fsm *FileSystem) Finalize(options FinalizeOptions) error { 10- write volume descriptor set terminator */ - f := fsm.file + f, err := fsm.file.Writable() + if err != nil { + return err + } + blocksize := int(fsm.blocksize) // 1- blank out sectors 0-15 @@ -816,7 +821,7 @@ func (fsm *FileSystem) Finalize(options FinalizeOptions) error { // copyFileData copy data from file `from` at offset `fromOffset` to file `to` at offset `toOffset`. // Copies `size` bytes. If `size` is 0, copies as many bytes as it can. -func copyFileData(from, to util.File, fromOffset, toOffset int64, size int) (int, error) { +func copyFileData(from backend.File, to backend.WritableFile, fromOffset, toOffset int64, size int) (int, error) { buf := make([]byte, 2048) copied := 0 for { diff --git a/filesystem/iso9660/iso9660.go b/filesystem/iso9660/iso9660.go index 6cb56df1..76c9c079 100644 --- a/filesystem/iso9660/iso9660.go +++ b/filesystem/iso9660/iso9660.go @@ -6,6 +6,7 @@ import ( "os" "path" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/util" ) @@ -23,7 +24,7 @@ type FileSystem struct { workspace string size int64 start int64 - file util.File + file backend.Storage blocksize int64 volumes volumeDescriptors pathTable *pathTable @@ -47,8 +48,8 @@ func (fsm *FileSystem) Workspace() string { // Create creates an ISO9660 filesystem in a given directory // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, +// requires the backend.WritableFile where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.WritableFile to create the filesystem, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to create the filesystem on the entire disk. You could have a disk of size @@ -60,7 +61,7 @@ func (fsm *FileSystem) Workspace() string { // where a partition starts and ends. // // If the provided blocksize is 0, it will use the default of 2 KB. -func Create(f util.File, size, start, blocksize int64, workspace string) (*FileSystem, error) { +func Create(f backend.WritableFile, size, start, blocksize int64, workspace string) (*FileSystem, error) { if blocksize == 0 { blocksize = defaultSectorSize } @@ -103,7 +104,7 @@ func Create(f util.File, size, start, blocksize int64, workspace string) (*FileS workspace: workdir, start: start, size: size, - file: f, + file: util.BackendFromFile(f, false), volumes: volumeDescriptors{}, blocksize: blocksize, }, nil @@ -111,8 +112,8 @@ func Create(f util.File, size, start, blocksize int64, workspace string) (*FileS // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.File where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.File the filesystem is expected to begin, // and blocksize is is the physical blocksize to use for reading the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -124,7 +125,7 @@ func Create(f util.File, size, start, blocksize int64, workspace string) (*FileS // where a partition starts and ends. // // If the provided blocksize is 0, it will use the default of 2K bytes -func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { +func Read(file backend.File, size, start, blocksize int64) (*FileSystem, error) { var read int if blocksize == 0 { @@ -235,7 +236,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { // parse it - we do not have any handlers yet de, err := parseDirEntry(b, &FileSystem{ suspEnabled: true, - file: file, + file: util.BackendFromFile(file, true), blocksize: blocksize, }) if err != nil { @@ -266,7 +267,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { workspace: "", // no workspace when we do nothing with it start: start, size: size, - file: file, + file: util.BackendFromFile(file, true), volumes: volumeDescriptors{ descriptors: vds, primary: pvd, diff --git a/filesystem/iso9660/iso9660_internal_test.go b/filesystem/iso9660/iso9660_internal_test.go index efb29e56..0287b0ce 100644 --- a/filesystem/iso9660/iso9660_internal_test.go +++ b/filesystem/iso9660/iso9660_internal_test.go @@ -3,6 +3,8 @@ package iso9660 import ( "os" "testing" + + "github.com/diskfs/go-diskfs/backend/raw" ) func TestIso9660ReadDirectory(t *testing.T) { @@ -23,7 +25,7 @@ func TestIso9660ReadDirectory(t *testing.T) { workspace: "", // we only ever call readDirectory with no workspace size: ISO9660Size, start: 0, - file: file, + file: raw.New(file, true), blocksize: 2048, pathTable: pathTable, } @@ -83,7 +85,7 @@ func TestRockRidgeReadDirectory(t *testing.T) { workspace: "", // we only ever call readDirectory with no workspace size: ISO9660Size, start: 0, - file: file, + file: raw.New(file, true), blocksize: 2048, pathTable: pathTable, suspEnabled: true, diff --git a/filesystem/iso9660/iso9660_test.go b/filesystem/iso9660/iso9660_test.go index a0b16338..61a8d219 100644 --- a/filesystem/iso9660/iso9660_test.go +++ b/filesystem/iso9660/iso9660_test.go @@ -545,7 +545,7 @@ func TestIso9660Finalize(t *testing.T) { // Create the disk image // TODO: Explain why we need to use Raw here - mydisk, err := diskfs.Create(outputFileName, 100*1024, diskfs.Raw, LogicalBlocksize) + mydisk, err := diskfs.Create(outputFileName, 100*1024, LogicalBlocksize) if err != nil { return err } diff --git a/filesystem/squashfs/const_internal_test.go b/filesystem/squashfs/const_internal_test.go index 86012571..c4ac3f2f 100644 --- a/filesystem/squashfs/const_internal_test.go +++ b/filesystem/squashfs/const_internal_test.go @@ -7,10 +7,11 @@ package squashfs // 3. Take the relevant sizes, locations and inodes and use them here. import ( + "io/fs" "os" "time" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend/raw" ) const ( @@ -143,7 +144,7 @@ var ( testLargeDirEntryCount = 252 ) -func testGetFilesystem(f util.File) (*FileSystem, []byte, error) { +func testGetFilesystem(f fs.File) (*FileSystem, []byte, error) { file := f var ( err error @@ -161,7 +162,7 @@ func testGetFilesystem(f util.File) (*FileSystem, []byte, error) { } blocksize := int64(testValidBlocksize) sb := testValidSuperblockUncompressed - fs := &FileSystem{ + testFs := &FileSystem{ /* TODO: Still need to add these in uidsGids []byte @@ -173,7 +174,7 @@ func testGetFilesystem(f util.File) (*FileSystem, []byte, error) { compressor: &CompressorGzip{}, size: 5251072, start: 0, - file: file, + file: raw.New(file, true), blocksize: blocksize, xattrs: nil, rootDir: &inodeImpl{ @@ -196,15 +197,15 @@ func testGetFilesystem(f util.File) (*FileSystem, []byte, error) { uidsGids: []uint32{5, 0, 1}, superblock: sb, } - return fs, b, nil + return testFs, b, nil } func testGetInodeMetabytes() (inodeBytes []byte, inodesStart uint64, err error) { - fs, b, err := testGetFilesystem(nil) + testFs, b, err := testGetFilesystem(nil) if err != nil { return nil, 0, err } - return b[fs.superblock.inodeTableStart+2:], fs.superblock.inodeTableStart, nil + return b[testFs.superblock.inodeTableStart+2:], testFs.superblock.inodeTableStart, nil } //nolint:deadcode // we need these references in the future @@ -233,12 +234,12 @@ func testGetFilesystemRoot() []*directoryEntry { } // GetTestFileSmall get a *squashfs.File to a usable and known test file -func GetTestFileSmall(f util.File, c Compressor) (*File, error) { - fs, _, err := testGetFilesystem(f) +func GetTestFileSmall(f fs.File, c Compressor) (*File, error) { + testFs, _, err := testGetFilesystem(f) if err != nil { return nil, err } - fs.compressor = c + testFs.compressor = c ef := &extendedFile{ startBlock: superblockSize, fileSize: 7, @@ -255,19 +256,19 @@ func GetTestFileSmall(f util.File, c Compressor) (*File, error) { isReadWrite: false, isAppend: false, offset: 0, - filesystem: fs, + filesystem: testFs, }, nil } // GetTestFileBig get a *squashfs.File to a usable and known test file -func GetTestFileBig(f util.File, c Compressor) (*File, error) { - fs, _, err := testGetFilesystem(f) +func GetTestFileBig(f fs.File, c Compressor) (*File, error) { + testFs, _, err := testGetFilesystem(f) if err != nil { return nil, err } - fs.compressor = c + testFs.compressor = c fragSize := uint64(5) - size := uint64(fs.blocksize) + fragSize + size := uint64(testFs.blocksize) + fragSize ef := &extendedFile{ startBlock: superblockSize, fileSize: size, @@ -277,7 +278,7 @@ func GetTestFileBig(f util.File, c Compressor) (*File, error) { fragmentOffset: 7, xAttrIndex: 0, blockSizes: []*blockData{ - {size: uint32(fs.blocksize), compressed: false}, + {size: uint32(testFs.blocksize), compressed: false}, }, } // inode 0, offset 0, name "README.md", type basic file @@ -286,6 +287,6 @@ func GetTestFileBig(f util.File, c Compressor) (*File, error) { isReadWrite: false, isAppend: false, offset: 0, - filesystem: fs, + filesystem: testFs, }, nil } diff --git a/filesystem/squashfs/finalize.go b/filesystem/squashfs/finalize.go index 7c99e4f2..34cff5a7 100644 --- a/filesystem/squashfs/finalize.go +++ b/filesystem/squashfs/finalize.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" "github.com/pkg/xattr" ) @@ -86,7 +86,11 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { */ - f := fs.file + f, err := fs.file.Writable() + if err != nil { + return err + } + blocksize := int(fs.blocksize) comp := compressionNone if options.Compression != nil { @@ -356,7 +360,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { return nil } -func copyFileData(from, to util.File, fromOffset, toOffset, blocksize int64, c Compressor) (raw, compressed int, blocks []*blockData, err error) { +func copyFileData(from backend.File, to backend.WritableFile, fromOffset, toOffset, blocksize int64, c Compressor) (raw, compressed int, blocks []*blockData, err error) { buf := make([]byte, blocksize) blocks = make([]*blockData, 0) for { @@ -392,7 +396,7 @@ func copyFileData(from, to util.File, fromOffset, toOffset, blocksize int64, c C // finalizeFragment write fragment data out to the archive, compressing if relevant. // Returns the total amount written, whether compressed, and any error. -func finalizeFragment(buf []byte, to util.File, toOffset int64, c Compressor) (raw int, compressed bool, err error) { +func finalizeFragment(buf []byte, to backend.WritableFile, toOffset int64, c Compressor) (raw int, compressed bool, err error) { // compress the block if needed if c != nil { out, err := c.compress(buf) @@ -517,7 +521,7 @@ func getTableIdx(m map[uint32]uint16, index uint32) uint16 { return m[index] } -func writeFileDataBlocks(e *finalizeFileInfo, to util.File, ws string, startBlock uint64, blocksize int, compressor Compressor, location int64) (blockCount, compressed int, err error) { +func writeFileDataBlocks(e *finalizeFileInfo, to backend.WritableFile, ws string, startBlock uint64, blocksize int, compressor Compressor, location int64) (blockCount, compressed int, err error) { from, err := os.Open(path.Join(ws, e.path)) if err != nil { return 0, 0, fmt.Errorf("failed to open file for reading %s: %v", e.path, err) @@ -541,7 +545,7 @@ func writeFileDataBlocks(e *finalizeFileInfo, to util.File, ws string, startBloc return blockCount, compressed, nil } -func writeMetadataBlock(buf []byte, to util.File, c Compressor, location int64) (int, error) { +func writeMetadataBlock(buf []byte, to backend.WritableFile, c Compressor, location int64) (int, error) { // compress the block if needed isCompressed := false if c != nil { @@ -569,7 +573,7 @@ func writeMetadataBlock(buf []byte, to util.File, c Compressor, location int64) return len(buf), nil } -func writeDataBlocks(fileList []*finalizeFileInfo, f util.File, ws string, blocksize int, compressor Compressor, location int64) (int, error) { +func writeDataBlocks(fileList []*finalizeFileInfo, f backend.WritableFile, ws string, blocksize int, compressor Compressor, location int64) (int, error) { allBlocks := 0 allWritten := 0 for _, e := range fileList { @@ -589,7 +593,7 @@ func writeDataBlocks(fileList []*finalizeFileInfo, f util.File, ws string, block } // writeFragmentBlocks writes all of the fragment blocks to the archive. Returns slice of blocks written, the total bytes written, any error -func writeFragmentBlocks(fileList []*finalizeFileInfo, f util.File, ws string, blocksize int, options FinalizeOptions, location int64) ([]fragmentBlock, int64, error) { +func writeFragmentBlocks(fileList []*finalizeFileInfo, f backend.WritableFile, ws string, blocksize int, options FinalizeOptions, location int64) ([]fragmentBlock, int64, error) { compressor := options.Compression if options.NoCompressFragments { compressor = nil @@ -683,7 +687,7 @@ func writeFragmentBlocks(fileList []*finalizeFileInfo, f util.File, ws string, b return fragmentBlocks, allWritten, nil } -func writeInodes(files []*finalizeFileInfo, f util.File, compressor Compressor, location int64) (inodesWritten int, finalLocation uint64, err error) { +func writeInodes(files []*finalizeFileInfo, f backend.WritableFile, compressor Compressor, location int64) (inodesWritten int, finalLocation uint64, err error) { var ( buf []byte maxSize = int(metadataBlockSize) @@ -717,7 +721,7 @@ func writeInodes(files []*finalizeFileInfo, f util.File, compressor Compressor, } // writeDirectories write all directories out to disk. Assumes it already has been optimized. -func writeDirectories(dirs []*finalizeFileInfo, f util.File, compressor Compressor, location int64) (directoriesWritten int, finalLocation uint64, err error) { +func writeDirectories(dirs []*finalizeFileInfo, f backend.WritableFile, compressor Compressor, location int64) (directoriesWritten int, finalLocation uint64, err error) { var ( buf []byte maxSize = int(metadataBlockSize) @@ -756,7 +760,7 @@ func writeDirectories(dirs []*finalizeFileInfo, f util.File, compressor Compress // writeFragmentTable write the fragment table // //nolint:unparam,unused,revive // this does not use fragmentBlocksStart yet, but only because we have not yet added support -func writeFragmentTable(fragmentBlocks []fragmentBlock, fragmentBlocksStart int64, f util.File, compressor Compressor, location int64) (fragmentsWritten int, finalLocation uint64, err error) { +func writeFragmentTable(fragmentBlocks []fragmentBlock, fragmentBlocksStart int64, f backend.WritableFile, compressor Compressor, location int64) (fragmentsWritten int, finalLocation uint64, err error) { // now write the actual fragment table entries var ( indexEntries []uint64 @@ -818,7 +822,7 @@ func writeFragmentTable(fragmentBlocks []fragmentBlock, fragmentBlocksStart int6 } // writeExportTable write the export table at the given location. -func writeExportTable(files []*finalizeFileInfo, f util.File, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { +func writeExportTable(files []*finalizeFileInfo, f backend.WritableFile, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { var ( maxSize = int(metadataBlockSize) ) @@ -878,7 +882,7 @@ func writeExportTable(files []*finalizeFileInfo, f util.File, compressor Compres } // writeIDTable write the uidgid table at the given location. -func writeIDTable(idtable map[uint32]uint16, f util.File, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { +func writeIDTable(idtable map[uint32]uint16, f backend.WritableFile, compressor Compressor, location int64) (entriesWritten int, finalLocation uint64, err error) { var ( maxSize = int(metadataBlockSize) ) @@ -938,7 +942,7 @@ func writeIDTable(idtable map[uint32]uint16, f util.File, compressor Compressor, } // writeXattrs write the xattrs and its lookup table at the given location. -func writeXattrs(xattrs []map[string]string, f util.File, compressor Compressor, location int64) (xattrsWritten int, finalLocation uint64, err error) { +func writeXattrs(xattrs []map[string]string, f backend.WritableFile, compressor Compressor, location int64) (xattrsWritten int, finalLocation uint64, err error) { var ( maxSize = int(metadataBlockSize) offset int diff --git a/filesystem/squashfs/squashfs.go b/filesystem/squashfs/squashfs.go index 8b769deb..50cc7a6c 100644 --- a/filesystem/squashfs/squashfs.go +++ b/filesystem/squashfs/squashfs.go @@ -8,6 +8,7 @@ import ( "os" "path" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/util" ) @@ -26,7 +27,7 @@ type FileSystem struct { superblock *superblock size int64 start int64 - file util.File + file backend.Storage blocksize int64 compressor Compressor fragments []*fragmentEntry @@ -59,8 +60,8 @@ func (fs *FileSystem) Workspace() string { // Create creates a squashfs filesystem in a given directory // -// requires the util.File where to create the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File to create the filesystem, +// requires the backend.WritableFile where to create the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.WritableFile to create the filesystem, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to create the filesystem on the entire disk. You could have a disk of size @@ -72,7 +73,7 @@ func (fs *FileSystem) Workspace() string { // where a partition starts and ends. // // If the provided blocksize is 0, it will use the default of 128 KB. -func Create(f util.File, size, start, blocksize int64) (*FileSystem, error) { +func Create(f backend.WritableFile, size, start, blocksize int64) (*FileSystem, error) { if blocksize == 0 { blocksize = defaultBlockSize } @@ -94,15 +95,15 @@ func Create(f util.File, size, start, blocksize int64) (*FileSystem, error) { workspace: tmpdir, start: start, size: size, - file: f, + file: util.BackendFromFile(f, false), blocksize: blocksize, }, nil } // Read reads a filesystem from a given disk. // -// requires the util.File where to read the filesystem, size is the size of the filesystem in bytes, -// start is how far in bytes from the beginning of the util.File the filesystem is expected to begin, +// requires the backend.File where to read the filesystem, size is the size of the filesystem in bytes, +// start is how far in bytes from the beginning of the backend.File the filesystem is expected to begin, // and blocksize is is the logical blocksize to use for creating the filesystem // // note that you are *not* required to read a filesystem on the entire disk. You could have a disk of size @@ -145,7 +146,7 @@ func Create(f util.File, size, start, blocksize int64) (*FileSystem, error) { // uses this library like this: // // rclone -P --transfers 16 --checkers 16 copy :archive:/path/to/tensorflow.sqfs /tmp/tensorflow -func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { +func Read(file backend.File, size, start, blocksize int64) (*FileSystem, error) { var ( read int err error @@ -211,7 +212,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { workspace: "", // no workspace when we do nothing with it start: start, size: size, - file: file, + file: util.BackendFromFile(file, false), superblock: s, blocksize: int64(s.blocksize), // use the blocksize in the superblock xattrs: xattrs, @@ -698,7 +699,7 @@ func validateBlocksize(blocksize int64) error { return nil } -func readFragmentTable(s *superblock, file util.File, c Compressor) ([]*fragmentEntry, error) { +func readFragmentTable(s *superblock, file backend.File, c Compressor) ([]*fragmentEntry, error) { // get the first level index, which is just the pointers to the fragment table metadata blocks blockCount := s.fragmentCount / 512 if s.fragmentCount%512 > 0 { @@ -755,7 +756,7 @@ To read the xattr table: 6- Read the id metablocks based on the indexes and uncompress if needed 7- Read all of the xattr metadata. It starts at the location indicated by the header, and ends at the id table */ -func readXattrsTable(s *superblock, file util.File, c Compressor) (*xAttrTable, error) { +func readXattrsTable(s *superblock, file backend.File, c Compressor) (*xAttrTable, error) { // first read the header b := make([]byte, xAttrHeaderSize) read, err := file.ReadAt(b, int64(s.xattrTableStart)) @@ -858,7 +859,7 @@ To read the uids/gids table: 4- Read the indexes. They are uncompressed, 8 bytes each (uint64); one index per id metablock 5- Read the id metablocks based on the indexes and uncompress if needed */ -func readUidsGids(s *superblock, file util.File, c Compressor) ([]uint32, error) { +func readUidsGids(s *superblock, file backend.File, c Compressor) ([]uint32, error) { // find out how many xattr IDs we have and where the metadata starts. The table always starts // with this information idStart := s.idTableStart diff --git a/filesystem/squashfs/squashfs_internal_test.go b/filesystem/squashfs/squashfs_internal_test.go index 6c19779f..fe864dca 100644 --- a/filesystem/squashfs/squashfs_internal_test.go +++ b/filesystem/squashfs/squashfs_internal_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/diskfs/go-diskfs/backend/raw" "github.com/diskfs/go-diskfs/filesystem" "github.com/diskfs/go-diskfs/testhelper" ) @@ -273,7 +274,7 @@ func TestReadBlock(t *testing.T) { } for i, tt := range tests { fs := &FileSystem{ - file: file, + file: raw.New(file, true), compressor: tt.compressor, } b, err := fs.readBlock(tt.location, tt.compressed, size) @@ -330,7 +331,7 @@ func TestReadFragment(t *testing.T) { for i, tt := range tests { fs := &FileSystem{ fragments: fragments, - file: file, + file: raw.New(file, true), compressor: tt.compressor, } b, err := fs.readFragment(tt.index, tt.offset, tt.size) @@ -348,7 +349,7 @@ func TestReadFragment(t *testing.T) { } func TestReadUidsGids(t *testing.T) { - // func readUidsGids(s *superblock, file util.File, c compressor) ([]uint32, error) { + // func readUidsGids(s *superblock, file backend.File, c compressor) ([]uint32, error) { expected := []uint32{ 0, 10, 100, 1000, } diff --git a/go.mod b/go.mod index 786440a8..1d174369 100644 --- a/go.mod +++ b/go.mod @@ -6,15 +6,14 @@ require ( github.com/djherbis/times v1.6.0 github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab github.com/go-test/deep v1.0.8 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.3.0 + github.com/klauspost/compress v1.17.4 github.com/pierrec/lz4/v4 v4.1.17 github.com/pkg/xattr v0.4.9 github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af github.com/ulikunitz/xz v0.5.11 - golang.org/x/sys v0.5.0 + golang.org/x/sys v0.19.0 ) -require ( - github.com/google/go-cmp v0.6.0 - github.com/klauspost/compress v1.17.4 -) +require github.com/stretchr/testify v1.7.1 // indirect diff --git a/go.sum b/go.sum index ebd27995..bc7a3fcf 100644 --- a/go.sum +++ b/go.sum @@ -22,15 +22,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/partition/gpt/partition.go b/partition/gpt/partition.go index bfa0e662..82ee7ab3 100644 --- a/partition/gpt/partition.go +++ b/partition/gpt/partition.go @@ -9,7 +9,7 @@ import ( "strings" "unicode/utf16" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" uuid "github.com/google/uuid" ) @@ -147,7 +147,7 @@ func (p *Partition) GetStart() int64 { // WriteContents fills the partition with the contents provided // reads from beginning of reader to exactly size of partition in bytes -func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, error) { +func (p *Partition) WriteContents(f backend.WritableFile, contents io.Reader) (uint64, error) { pss, lss := p.sectorSizes() total := uint64(0) // validate start/end/size @@ -202,7 +202,7 @@ func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, erro // ReadContents reads the contents of the partition into a writer // streams the entire partition to the writer -func (p *Partition) ReadContents(f util.File, out io.Writer) (int64, error) { +func (p *Partition) ReadContents(f backend.File, out io.Writer) (int64, error) { pss, _ := p.sectorSizes() total := int64(0) // chunks of physical sector size for efficient writing diff --git a/partition/gpt/table.go b/partition/gpt/table.go index 5d62311b..af01c3f3 100644 --- a/partition/gpt/table.go +++ b/partition/gpt/table.go @@ -7,8 +7,8 @@ import ( "hash/crc32" "strings" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/part" - "github.com/diskfs/go-diskfs/util" uuid "github.com/google/uuid" ) @@ -462,8 +462,8 @@ func (t *Table) Type() string { } // Write writes a GPT to disk -// Must be passed the util.File to which to write and the size of the disk -func (t *Table) Write(f util.File, size int64) error { +// Must be passed the backend.WritableFile to which to write and the size of the disk +func (t *Table) Write(f backend.WritableFile, size int64) error { // it is possible that we are given a basic new table that we need to initialize if !t.initialized { t.initTable(size) @@ -536,11 +536,11 @@ func (t *Table) Write(f util.File, size int64) error { } // Read read a partition table from a disk -// must be passed the util.File from which to read, and the logical and physical block sizes +// must be passed the backend.File from which to read, and the logical and physical block sizes // // if successful, returns a gpt.Table struct // returns errors if fails at any stage reading the disk or processing the bytes on disk as a GPT -func Read(f util.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { +func Read(f backend.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { // read the data off of the disk - first block is the compatibility MBR, ssecond is the GPT table b := make([]byte, logicalBlockSize*2) read, err := f.ReadAt(b, 0) @@ -595,7 +595,7 @@ func (t *Table) UUID() string { } // Verify will attempt to evaluate the headers -func (t *Table) Verify(f util.File, diskSize uint64) error { +func (t *Table) Verify(f backend.File, diskSize uint64) error { if t.LogicalSectorSize == 0 { // Avoid divide by zero panic. return fmt.Errorf("table is not initialized") diff --git a/partition/gpt/table_internal_test.go b/partition/gpt/table_internal_test.go index 6a3de790..1a3e86c5 100644 --- a/partition/gpt/table_internal_test.go +++ b/partition/gpt/table_internal_test.go @@ -197,6 +197,17 @@ func (b *byteBufferReader) Seek(offset int64, whence int) (int64, error) { } return int64(b.pos), nil } +func (b *byteBufferReader) Stat() (os.FileInfo, error) { + return nil, nil +} + +func (b *byteBufferReader) Read(p []byte) (int, error) { + return b.ReadAt(p, 0) +} + +func (b *byteBufferReader) Close() error { + return nil +} func TestRead(t *testing.T) { t.Run("invalid EFI Partition Checksum", func(t *testing.T) { diff --git a/partition/mbr/partition.go b/partition/mbr/partition.go index 18ecaaf6..8a5468f5 100644 --- a/partition/mbr/partition.go +++ b/partition/mbr/partition.go @@ -7,7 +7,7 @@ import ( "fmt" "io" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" ) // Partition represents the structure of a single partition on the disk @@ -123,7 +123,7 @@ func partitionFromBytes(b []byte, logicalSectorSize, physicalSectorSize int) (*P // WriteContents fills the partition with the contents provided // reads from beginning of reader to exactly size of partition in bytes -func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, error) { +func (p *Partition) WriteContents(f backend.WritableFile, contents io.Reader) (uint64, error) { pss, lss := p.sectorSizes() total := uint64(0) @@ -166,7 +166,7 @@ func (p *Partition) WriteContents(f util.File, contents io.Reader) (uint64, erro // readContents reads the contents of the partition into a writer // streams the entire partition to the writer -func (p *Partition) ReadContents(f util.File, out io.Writer) (int64, error) { +func (p *Partition) ReadContents(f backend.File, out io.Writer) (int64, error) { pss, lss := p.sectorSizes() total := int64(0) // chunks of physical sector size for efficient writing diff --git a/partition/mbr/table.go b/partition/mbr/table.go index c64dab5f..4a55f3d6 100644 --- a/partition/mbr/table.go +++ b/partition/mbr/table.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "fmt" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/part" - "github.com/diskfs/go-diskfs/util" ) // Table represents an MBR partition table to be applied to a disk or read from a disk @@ -134,7 +134,7 @@ func (t *Table) Type() string { // Read read a partition table from a disk, given the logical block size and physical block size // //nolint:unused,revive // not used in MBR, but it is important to implement the interface -func Read(f util.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { +func Read(f backend.File, logicalBlockSize, physicalBlockSize int) (*Table, error) { // read the data off of the disk b := make([]byte, mbrSize) read, err := f.ReadAt(b, 0) @@ -168,10 +168,10 @@ func (t *Table) toBytes() []byte { } // Write writes a given MBR Table to disk. -// Must be passed the util.File to write to and the size of the disk +// Must be passed the backend.WritableFile to write to and the size of the disk // //nolint:unused,revive // not used in MBR, but it is important to implement the interface -func (t *Table) Write(f util.File, size int64) error { +func (t *Table) Write(f backend.WritableFile, size int64) error { b := t.toBytes() written, err := f.WriteAt(b, partitionEntriesStart) @@ -196,7 +196,7 @@ func (t *Table) GetPartitions() []part.Partition { // Verify will attempt to evaluate the headers // //nolint:unused,revive // not used in MBR, but it is important to implement the interface -func (t *Table) Verify(f util.File, diskSize uint64) error { +func (t *Table) Verify(f backend.File, diskSize uint64) error { return nil } diff --git a/partition/part/partition.go b/partition/part/partition.go index f87ba9fa..1a34f6d3 100644 --- a/partition/part/partition.go +++ b/partition/part/partition.go @@ -3,14 +3,14 @@ package part import ( "io" - "github.com/diskfs/go-diskfs/util" + "github.com/diskfs/go-diskfs/backend" ) // Partition reference to an individual partition on disk type Partition interface { GetSize() int64 GetStart() int64 - ReadContents(util.File, io.Writer) (int64, error) - WriteContents(util.File, io.Reader) (uint64, error) + ReadContents(backend.File, io.Writer) (int64, error) + WriteContents(backend.WritableFile, io.Reader) (uint64, error) UUID() string } diff --git a/partition/partition.go b/partition/partition.go index d47ffba9..627f3a8f 100644 --- a/partition/partition.go +++ b/partition/partition.go @@ -5,13 +5,13 @@ package partition import ( "fmt" + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/gpt" "github.com/diskfs/go-diskfs/partition/mbr" - "github.com/diskfs/go-diskfs/util" ) // Read read a partition table from a disk -func Read(f util.File, logicalBlocksize, physicalBlocksize int) (Table, error) { +func Read(f backend.File, logicalBlocksize, physicalBlocksize int) (Table, error) { // just try each type gptTable, err := gpt.Read(f, logicalBlocksize, physicalBlocksize) if err == nil { diff --git a/partition/table.go b/partition/table.go index 62f56503..91d89592 100644 --- a/partition/table.go +++ b/partition/table.go @@ -1,16 +1,16 @@ package partition import ( + "github.com/diskfs/go-diskfs/backend" "github.com/diskfs/go-diskfs/partition/part" - "github.com/diskfs/go-diskfs/util" ) // Table reference to a partitioning table on disk type Table interface { Type() string - Write(util.File, int64) error + Write(backend.WritableFile, int64) error GetPartitions() []part.Partition Repair(diskSize uint64) error - Verify(f util.File, diskSize uint64) error + Verify(f backend.File, diskSize uint64) error UUID() string } diff --git a/testhelper/fileimpl.go b/testhelper/fileimpl.go index b593aeda..c67010d7 100644 --- a/testhelper/fileimpl.go +++ b/testhelper/fileimpl.go @@ -1,6 +1,9 @@ package testhelper -import "fmt" +import ( + "fmt" + "os" +) type reader func(b []byte, offset int64) (int, error) type writer func(b []byte, offset int64) (int, error) @@ -12,6 +15,18 @@ type FileImpl struct { Writer writer } +func (f *FileImpl) Stat() (os.FileInfo, error) { + return nil, nil +} + +func (f *FileImpl) Read(b []byte) (int, error) { + return f.Reader(b, 0) +} + +func (f *FileImpl) Close() error { + return nil +} + // ReadAt read at a particular offset func (f *FileImpl) ReadAt(b []byte, offset int64) (int, error) { return f.Reader(b, offset) diff --git a/util/backend.go b/util/backend.go new file mode 100644 index 00000000..a1b48e6a --- /dev/null +++ b/util/backend.go @@ -0,0 +1,16 @@ +package util + +import ( + "io/fs" + + "github.com/diskfs/go-diskfs/backend" + "github.com/diskfs/go-diskfs/backend/raw" +) + +// Returns backend.Storage or creates Raw backend around provided fs.File +func BackendFromFile(f fs.File, readOnly bool) backend.Storage { + if theBackend, storageLike := f.(backend.Storage); storageLike { + return theBackend + } + return raw.New(f, readOnly) +} diff --git a/util/file.go b/util/file.go deleted file mode 100644 index 78d0a152..00000000 --- a/util/file.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package util common utilities or other elements shared across github.com/diskfs/go-diskfs packages -package util - -import "io" - -// File interface that can be read from and written to. -// Normally implemented as actual os.File, but useful as a separate interface so can easily -// use alternate implementations. -type File interface { - io.ReaderAt - io.WriterAt - io.Seeker -}