mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-27 04:28:20 +00:00
356 lines
10 KiB
Go
Vendored
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)
|
|
}
|