linuxkit/pkg/metadata/vendor/github.com/diskfs/go-diskfs/diskfs.go
Avi Deitcher 3aeac872a0 update pkg/metadata with better logging
Signed-off-by: Avi Deitcher <avi@deitcher.net>
2023-06-13 12:10:48 +03:00

356 lines
10 KiB
Go
Vendored

// Package diskfs implements methods for creating and manipulating disks and filesystems
//
// methods for creating and manipulating disks and filesystems, whether block devices
// in /dev or direct disk images. This does **not**
// mount any disks or filesystems, neither directly locally nor via a VM. Instead, it manipulates the
// bytes directly.
//
// This is not intended as a replacement for operating system filesystem and disk drivers. Instead,
// it is intended to make it easy to work with partitions, partition tables and filesystems directly
// without requiring operating system mounts.
//
// Some examples:
//
// 1. Create a disk image of size 10MB with a FAT32 filesystem spanning the entire disk.
//
// import diskfs "github.com/diskfs/go-diskfs"
// size := 10*1024*1024 // 10 MB
//
// diskImg := "/tmp/disk.img"
// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault)
//
// fs, err := disk.CreateFilesystem(0, diskfs.TypeFat32)
//
// 2. Create a disk of size 20MB with an MBR partition table, a single partition beginning at block 2048 (1MB),
// of size 10MB filled with a FAT32 filesystem.
//
// import diskfs "github.com/diskfs/go-diskfs"
//
// diskSize := 10*1024*1024 // 10 MB
//
// diskImg := "/tmp/disk.img"
// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault)
//
// table := &mbr.Table{
// LogicalSectorSize: 512,
// PhysicalSectorSize: 512,
// Partitions: []*mbr.Partition{
// {
// Bootable: false,
// Type: Linux,
// Start: 2048,
// Size: 20480,
// },
// },
// }
//
// fs, err := disk.CreateFilesystem(1, diskfs.TypeFat32)
//
// 3. Create a disk of size 20MB with a GPT partition table, a single partition beginning at block 2048 (1MB),
// of size 10MB, and fill with the contents from the 10MB file "/root/contents.dat"
//
// import diskfs "github.com/diskfs/go-diskfs"
//
// diskSize := 10*1024*1024 // 10 MB
//
// diskImg := "/tmp/disk.img"
// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault)
//
// table := &gpt.Table{
// LogicalSectorSize: 512,
// PhysicalSectorSize: 512,
// Partitions: []*gpt.Partition{
// {
// LogicalSectorSize: 512,
// PhysicalSectorSize: 512,
// ProtectiveMBR: true,
// },
// },
// }
//
// f, err := os.Open("/root/contents.dat")
// written, err := disk.WritePartitionContents(1, f)
//
// 4. Create a disk of size 20MB with an MBR partition table, a single partition beginning at block 2048 (1MB),
// of size 10MB filled with a FAT32 filesystem, and create some directories and files in that filesystem.
//
// import diskfs "github.com/diskfs/go-diskfs"
//
// diskSize := 10*1024*1024 // 10 MB
//
// diskImg := "/tmp/disk.img"
// disk := diskfs.Create(diskImg, size, diskfs.Raw, diskfs.SectorSizeDefault)
//
// table := &mbr.Table{
// LogicalSectorSize: 512,
// PhysicalSectorSize: 512,
// Partitions: []*mbr.Partition{
// {
// Bootable: false,
// Type: Linux,
// Start: 2048,
// Size: 20480,
// },
// },
// }
//
// fs, err := disk.CreateFilesystem(1, diskfs.TypeFat32)
// err := fs.Mkdir("/FOO/BAR")
// rw, err := fs.OpenFile("/FOO/BAR/AFILE.EXE", os.O_CREATE|os.O_RDRWR)
// b := make([]byte, 1024, 1024)
// rand.Read(b)
// err := rw.Write(b)
package diskfs
import (
"errors"
"fmt"
"os"
log "github.com/sirupsen/logrus"
"github.com/diskfs/go-diskfs/disk"
)
// when we use a disk image with a GPT, we cannot get the logical sector size from the disk via the kernel
//
// so we use the default sector size of 512, per Rod Smith
const (
defaultBlocksize int = 512
// firstblock = 2048
// blksszGet = 0x1268
// 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
const (
// ReadOnly open file in read only mode
ReadOnly OpenModeOption = iota
// ReadWriteExclusive open file in read-write exclusive mode
ReadWriteExclusive
// ReadWrite open file in read-write mode
ReadWrite
)
// OpenModeOption.String()
func (m OpenModeOption) String() string {
switch m {
case ReadOnly:
return "read-only"
case ReadWriteExclusive:
return "read-write exclusive"
case ReadWrite:
return "read-write"
default:
return "unknown"
}
}
var openModeOptions = map[OpenModeOption]int{
ReadOnly: os.O_RDONLY,
ReadWriteExclusive: os.O_RDWR | os.O_EXCL,
ReadWrite: os.O_RDWR,
}
// SectorSize represents the sector size to use
type SectorSize int
const (
// SectorSizeDefault default behavior, defaulting to defaultBlocksize
SectorSizeDefault SectorSize = 0
// SectorSize512 override sector size to 512
SectorSize512 SectorSize = 512
// SectorSize4k override sector size to 4k
SectorSize4k SectorSize = 4096
)
func writableMode(mode OpenModeOption) bool {
m, ok := openModeOptions[mode]
if ok {
if m&os.O_RDWR != 0 || m&os.O_WRONLY != 0 {
return true
}
}
return false
}
func initDisk(f *os.File, openMode OpenModeOption, sectorSize SectorSize) (*disk.Disk, error) {
var (
diskType disk.Type
size int64
lblksize = int64(defaultBlocksize)
pblksize = int64(defaultBlocksize)
defaultBlocks = true
)
log.Debug("initDisk(): start")
if sectorSize != SectorSizeDefault {
lblksize = int64(sectorSize)
pblksize = int64(sectorSize)
}
// 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)
}
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())
}
case mode&os.ModeDevice != 0:
log.Debug("initDisk(): block device")
diskType = disk.Device
size, err = getBlockDeviceSize(f)
if err != nil {
return nil, fmt.Errorf("error getting block device %s size: %s", f.Name(), err)
}
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)
}
default:
return nil, fmt.Errorf("device %s is neither a block device nor a regular file", f.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
}
func checkDevice(device string) error {
if device == "" {
return errors.New("must pass device name")
}
if _, err := os.Stat(device); os.IsNotExist(err) {
return fmt.Errorf("provided device %s does not exist", device)
}
return nil
}
type openOpts struct {
mode OpenModeOption
sectorSize SectorSize
}
func openOptsDefaults() *openOpts {
return &openOpts{
mode: ReadWriteExclusive,
sectorSize: SectorSizeDefault,
}
}
// OpenOpt func that process Open options
type OpenOpt func(o *openOpts) error
// WithOpenMode sets the opening mode to the requested mode of type OpenModeOption.
// Default is ReadWriteExclusive, i.e. os.O_RDWR | os.O_EXCL
func WithOpenMode(mode OpenModeOption) OpenOpt {
return func(o *openOpts) error {
o.mode = mode
return nil
}
}
// WithSectorSize opens the disk file or block device with the provided sector size.
// Defaults to the physical block size.
func WithSectorSize(sectorSize SectorSize) OpenOpt {
return func(o *openOpts) error {
o.sectorSize = sectorSize
return nil
}
}
// 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().
// Use OpenOpt to control options, such as sector size or open mode.
func Open(device string, opts ...OpenOpt) (*disk.Disk, error) {
err := checkDevice(device)
if err != nil {
return nil, err
}
opt := openOptsDefaults()
for _, o := range opts {
if err := o(opt); err != nil {
return nil, err
}
}
m, ok := openModeOptions[opt.mode]
if !ok {
return nil, errors.New("unsupported file open mode")
}
f, err := os.OpenFile(device, m, 0o600)
if err != nil {
return nil, fmt.Errorf("could not open device %s with mode %v: %w", device, m, err)
}
// return our disk
return initDisk(f, ReadWriteExclusive, opt.sectorSize)
}
// 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 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)
if err != nil {
return nil, fmt.Errorf("could not create device %s: %v", device, errors.Unwrap(err))
}
err = os.Truncate(device, size)
if err != nil {
return nil, fmt.Errorf("could not expand device %s to size %d: %v", device, size, errors.Unwrap(err))
}
// return our disk
return initDisk(f, ReadWriteExclusive, sectorSize)
}