When the size of a logical volume is not aligned to the extent size of
the volume group, LVM2 will automatically align it by rounding up[1]:
Rounding up size to full physical extent 29.80 GiB
Rounding up size to full physical extent <3.82 GiB
Since we don't take that into account when we create a new volume or
set the size of an existing one, the size for the whole volume group
will be short by that amount and thus the creation of the last volume
will fail:
Volume group <uuid> has insufficient free space (975 extents): 977 required.
To fix this a new `AlignUp` method is added to the `MountpointCreator`
creator interface. It will align a given size to the requirements of
the implementing container, like e.g. `LVMVolumeGroup`. It is then
used by a new `alignEntityBranch` which takes a size and walks the
entity path, calling `AlignUp` for all entities that implement said
`MountpointCreator` interface; thus the resulting size should fullfil
the alignment requirement for all elements in the path.
NB: `PartitionTable` already had an `AlignUp` method.
Add a corresponding test.
[1]: 8686657664/lib/metadata/metadata.c (L1072)
Co-authored-by: Achilleas Koutsou <achilleas@koutsou.net>
201 lines
6 KiB
Go
201 lines
6 KiB
Go
// Disk package contains abstract data-types to define disk-related entities.
|
|
//
|
|
// The disk package is a collection of interfaces and structs that can be used
|
|
// to represent an disk image with its layout. Various concrete types, such as
|
|
// PartitionTable, Partition and Filesystem types are defined to model a given
|
|
// disk layout. These implement a collection of interfaces that can be used to
|
|
// navigate and operate on the various possible combinations of entities in a
|
|
// generic way. The entity data model is very generic so that it can represent
|
|
// all possible layouts, which can be arbitrarily complex, since technologies
|
|
// like logical volume management, LUKS2 container and file systems, that can
|
|
// have sub-volumes, allow for complex layouts.
|
|
// Entity and Container are the two main interfaces that are used to model the
|
|
// tree structure of a disk image layout. The other entity interfaces, such as
|
|
// Sizeable and Mountable, then describe various properties and capabilities
|
|
// of a given entity.
|
|
|
|
package disk
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
|
)
|
|
|
|
const (
|
|
// Default sector size in bytes
|
|
DefaultSectorSize = 512
|
|
|
|
DefaultGrainBytes = uint64(1048576) // 1 MiB
|
|
|
|
// UUIDs
|
|
BIOSBootPartitionGUID = "21686148-6449-6E6F-744E-656564454649"
|
|
BIOSBootPartitionUUID = "FAC7F1FB-3E8D-4137-A512-961DE09A5549"
|
|
|
|
FilesystemDataGUID = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
|
|
FilesystemDataUUID = "CB07C243-BC44-4717-853E-28852021225B"
|
|
|
|
EFISystemPartitionGUID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
|
EFISystemPartitionUUID = "68B2905B-DF3E-4FB3-80FA-49D1E773AA33"
|
|
EFIFilesystemUUID = "7B77-95E7"
|
|
|
|
LVMPartitionGUID = "E6D6D379-F507-44C2-A23C-238F2A3DF928"
|
|
PRePartitionGUID = "9E1A2D38-C612-4316-AA26-8B49521E5A8B"
|
|
|
|
RootPartitionUUID = "6264D520-3FB9-423F-8AB8-7A0A8E3D3562"
|
|
|
|
// Extended Boot Loader Partition
|
|
XBootLDRPartitionGUID = "BC13C2FF-59E6-4262-A352-B275FD6F7172"
|
|
)
|
|
|
|
var MountpointPolicies = NewPathPolicies(map[string]PathPolicy{
|
|
"/": {Exact: true},
|
|
"/boot": {Exact: true},
|
|
"/var": {},
|
|
"/opt": {},
|
|
"/srv": {},
|
|
"/usr": {},
|
|
"/app": {},
|
|
"/data": {},
|
|
"/home": {},
|
|
"/tmp": {},
|
|
})
|
|
|
|
// Entity is the base interface for all disk-related entities.
|
|
type Entity interface {
|
|
// IsContainer indicates if the implementing type can
|
|
// contain any other entities.
|
|
IsContainer() bool
|
|
|
|
// Clone returns a deep copy of the entity.
|
|
Clone() Entity
|
|
}
|
|
|
|
// Container is the interface for entities that can contain other entities.
|
|
// Together with the base Entity interface this allows to model a generic
|
|
// entity tree of theoretically arbitrary depth and width.
|
|
type Container interface {
|
|
Entity
|
|
|
|
// GetItemCount returns the number of actual child entities.
|
|
GetItemCount() uint
|
|
|
|
// GetChild returns the child entity at the given index.
|
|
GetChild(n uint) Entity
|
|
}
|
|
|
|
// Sizeable is implemented by entities that carry size information.
|
|
type Sizeable interface {
|
|
// EnsureSize will resize the entity to the given size in case
|
|
// it is currently smaller. Returns if the size was changed.
|
|
EnsureSize(size uint64) bool
|
|
|
|
// GetSize returns the size of the entity in bytes.
|
|
GetSize() uint64
|
|
}
|
|
|
|
// A Mountable entity is an entity that can be mounted.
|
|
type Mountable interface {
|
|
|
|
// GetMountPoint returns the path of the mount point.
|
|
GetMountpoint() string
|
|
|
|
// GetFSType returns the file system type, e.g. 'xfs'.
|
|
GetFSType() string
|
|
|
|
// GetFSSpec returns the file system spec information.
|
|
GetFSSpec() FSSpec
|
|
|
|
// GetFSTabOptions returns options for mounting the entity.
|
|
GetFSTabOptions() FSTabOptions
|
|
}
|
|
|
|
// A MountpointCreator is a container that is able to create new volumes.
|
|
//
|
|
// CreateMountpoint creates a new mountpoint with the given size and
|
|
// returns the entity that represents the new mountpoint.
|
|
type MountpointCreator interface {
|
|
CreateMountpoint(mountpoint string, size uint64) (Entity, error)
|
|
|
|
// AlignUp will align the given bytes according to the
|
|
// requirements of the container type.
|
|
AlignUp(size uint64) uint64
|
|
}
|
|
|
|
// A UniqueEntity is an entity that can be uniquely identified via a UUID.
|
|
//
|
|
// GenUUID generates a UUID for the entity if it does not yet have one.
|
|
type UniqueEntity interface {
|
|
Entity
|
|
GenUUID(rng *rand.Rand)
|
|
}
|
|
|
|
// VolumeContainer is a specific container that contains volume entities
|
|
type VolumeContainer interface {
|
|
|
|
// MetadataSize returns the size of the container's metadata (in
|
|
// bytes), i.e. the storage space that needs to be reserved for
|
|
// the container itself, in contrast to the data it contains.
|
|
MetadataSize() uint64
|
|
}
|
|
|
|
// FSSpec for a filesystem (UUID and Label); the first field of fstab(5)
|
|
type FSSpec struct {
|
|
UUID string
|
|
Label string
|
|
}
|
|
|
|
type FSTabOptions struct {
|
|
// The fourth field of fstab(5); fs_mntops
|
|
MntOps string
|
|
// The fifth field of fstab(5); fs_freq
|
|
Freq uint64
|
|
// The sixth field of fstab(5); fs_passno
|
|
PassNo uint64
|
|
}
|
|
|
|
// uuid generator helpers
|
|
|
|
// GeneratesnewRandomUUIDFromReader generates a new random UUID (version
|
|
// 4 using) via the given random number generator.
|
|
func newRandomUUIDFromReader(r io.Reader) (uuid.UUID, error) {
|
|
var id uuid.UUID
|
|
_, err := io.ReadFull(r, id[:])
|
|
if err != nil {
|
|
return uuid.Nil, err
|
|
}
|
|
id[6] = (id[6] & 0x0f) | 0x40 // Version 4
|
|
id[8] = (id[8] & 0x3f) | 0x80 // Variant is 10
|
|
return id, nil
|
|
}
|
|
|
|
// NewVolIDFromRand creates a random 32 bit hex string to use as a
|
|
// volume ID for FAT filesystems
|
|
func NewVolIDFromRand(r *rand.Rand) string {
|
|
volid := make([]byte, 4)
|
|
len, _ := r.Read(volid)
|
|
if len != 4 {
|
|
panic("expected four random bytes")
|
|
}
|
|
return hex.EncodeToString(volid)
|
|
}
|
|
|
|
func CheckMountpoints(mountpoints []blueprint.FilesystemCustomization, mountpointAllowList *PathPolicies) error {
|
|
invalidMountpoints := []string{}
|
|
for _, m := range mountpoints {
|
|
err := mountpointAllowList.Check(m.Mountpoint)
|
|
if err != nil {
|
|
invalidMountpoints = append(invalidMountpoints, m.Mountpoint)
|
|
}
|
|
}
|
|
|
|
if len(invalidMountpoints) > 0 {
|
|
return fmt.Errorf("The following custom mountpoints are not supported %+q", invalidMountpoints)
|
|
}
|
|
|
|
return nil
|
|
}
|