osbase/fat32: implement SizeFS
This function allows the user to find out if a filesystem is fitting on
the given blockdevice. This will be used to allow osimage to validate if
an install will fail because of missing disk space
Change-Id: I05638c91ba7ec9ba835b7b0e3811ee7404df4087
Reviewed-on: https://review.monogon.dev/c/monogon/+/3211
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/osbase/fat32/fat32.go b/osbase/fat32/fat32.go
index 7a45aa4..a0b8414 100644
--- a/osbase/fat32/fat32.go
+++ b/osbase/fat32/fat32.go
@@ -345,26 +345,91 @@
// WriteFS writes a filesystem described by a root inode and its children to a
// given io.Writer.
func WriteFS(w io.Writer, rootInode Inode, opts Options) error {
+ bs, fsi, p, err := prepareFS(&opts, rootInode)
+ if err != nil {
+ return err
+ }
+
+ wb := newBlockWriter(w)
+
+ // Write superblock
+ if err := binary.Write(wb, binary.LittleEndian, bs); err != nil {
+ return err
+ }
+ if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
+ return err
+ }
+ if err := binary.Write(wb, binary.LittleEndian, fsi); err != nil {
+ return err
+ }
+ if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
+ return err
+ }
+
+ block := make([]byte, opts.BlockSize)
+ for i := 0; i < 4; i++ {
+ if _, err := wb.Write(block); err != nil {
+ return err
+ }
+ }
+ // Backup of superblock at block 6
+ if err := binary.Write(wb, binary.LittleEndian, bs); err != nil {
+ return err
+ }
+ if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
+ return err
+ }
+ if err := binary.Write(wb, binary.LittleEndian, fsi); err != nil {
+ return err
+ }
+ if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
+ return err
+ }
+
+ for i := uint8(0); i < bs.NumFATs; i++ {
+ if err := binary.Write(wb, binary.LittleEndian, p.fat); err != nil {
+ return err
+ }
+ if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
+ return err
+ }
+ }
+
+ for _, i := range p.orderedInodes {
+ if err := i.writeData(wb, bs.Label); err != nil {
+ return fmt.Errorf("failed to write inode %q: %v", i.Name, err)
+ }
+ if err := wb.FinishBlock(int64(opts.BlockSize)*int64(bs.BlocksPerCluster), false); err != nil {
+ return err
+ }
+ }
+ // Creatively use block writer to write out all empty data at the end
+ if err := wb.FinishBlock(int64(opts.BlockSize)*int64(bs.TotalBlocks), false); err != nil {
+ return err
+ }
+ return nil
+}
+
+func prepareFS(opts *Options, rootInode Inode) (*bootSector, *fsinfo, *planningState, error) {
if opts.BlockSize == 0 {
opts.BlockSize = 512
}
if bits.OnesCount16(opts.BlockSize) != 1 {
- return fmt.Errorf("option BlockSize is not a power of two")
+ return nil, nil, nil, fmt.Errorf("option BlockSize is not a power of two")
}
if opts.BlockSize < 512 {
- return fmt.Errorf("option BlockSize must be at least 512 bytes")
+ return nil, nil, nil, fmt.Errorf("option BlockSize must be at least 512 bytes")
}
if opts.ID == 0 {
var buf [4]byte
if _, err := rand.Read(buf[:]); err != nil {
- return fmt.Errorf("failed to assign random FAT ID: %v", err)
+ return nil, nil, nil, fmt.Errorf("failed to assign random FAT ID: %v", err)
}
opts.ID = binary.BigEndian.Uint32(buf[:])
}
if rootInode.Attrs&AttrDirectory == 0 {
- return errors.New("root inode must be a directory (i.e. have AttrDirectory set)")
+ return nil, nil, nil, errors.New("root inode must be a directory (i.e. have AttrDirectory set)")
}
- wb := newBlockWriter(w)
bs := bootSector{
// Assembled x86_32 machine code corresponding to
// jmp $
@@ -401,7 +466,7 @@
copy(bs.Label[:], opts.Label)
- fs := fsinfo{
+ fsi := fsinfo{
// Signatures
LeadSignature: [4]byte{0x52, 0x52, 0x61, 0x41},
StructSignature: [4]byte{0x72, 0x72, 0x41, 0x61},
@@ -426,12 +491,12 @@
p.fat = append(p.fat, 0x0fffff00|uint32(bs.MediaCode), 0x0fffffff)
err := rootInode.placeRecursively(&p)
if err != nil {
- return err
+ return nil, nil, nil, err
}
allocClusters := len(p.fat)
if allocClusters >= fatMask&math.MaxUint32 {
- return fmt.Errorf("filesystem contains more than 2^28 FAT entries, this is unsupported. Note that this package currently always creates minimal clusters")
+ return nil, nil, nil, fmt.Errorf("filesystem contains more than 2^28 FAT entries, this is unsupported. Note that this package currently always creates minimal clusters")
}
// Fill out FAT to minimum size for FAT32
@@ -446,7 +511,7 @@
if bs.TotalBlocks == 0 {
bs.TotalBlocks = occupiedBlocks
} else if bs.TotalBlocks < occupiedBlocks {
- return fmt.Errorf("content (minimum %d blocks) would exceed number of blocks specified (%d blocks)", occupiedBlocks, bs.TotalBlocks)
+ return nil, nil, nil, fmt.Errorf("content (minimum %d blocks) would exceed number of blocks specified (%d blocks)", occupiedBlocks, bs.TotalBlocks)
} else { // Fixed-size file system with enough space
blocksToDistribute := bs.TotalBlocks - uint32(bs.ReservedBlocks)
// Number of data blocks which can be described by one metadata/FAT
@@ -469,65 +534,21 @@
p.fat = append(p.fat, fatFree)
}
}
- fs.FreeCount = uint32(len(p.fat) - allocClusters)
- if fs.FreeCount > 1 {
- fs.NextFreeCluster = uint32(allocClusters) + 1
+ fsi.FreeCount = uint32(len(p.fat) - allocClusters)
+ if fsi.FreeCount > 1 {
+ fsi.NextFreeCluster = uint32(allocClusters) + 1
+ }
+ return &bs, &fsi, &p, nil
+}
+
+// SizeFS returns the number of blocks required to hold the filesystem defined
+// by rootInode and opts. This can be used for sizing calculations before
+// calling WriteFS.
+func SizeFS(rootInode Inode, opts Options) (int64, error) {
+ bs, _, _, err := prepareFS(&opts, rootInode)
+ if err != nil {
+ return 0, err
}
- // Write superblock
- if err := binary.Write(wb, binary.LittleEndian, bs); err != nil {
- return err
- }
- if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
- return err
- }
- if err := binary.Write(wb, binary.LittleEndian, fs); err != nil {
- return err
- }
- if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
- return err
- }
-
- block := make([]byte, opts.BlockSize)
- for i := 0; i < 4; i++ {
- if _, err := wb.Write(block); err != nil {
- return err
- }
- }
- // Backup of superblock at block 6
- if err := binary.Write(wb, binary.LittleEndian, bs); err != nil {
- return err
- }
- if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
- return err
- }
- if err := binary.Write(wb, binary.LittleEndian, fs); err != nil {
- return err
- }
- if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
- return err
- }
-
- for i := uint8(0); i < bs.NumFATs; i++ {
- if err := binary.Write(wb, binary.LittleEndian, p.fat); err != nil {
- return err
- }
- if err := wb.FinishBlock(int64(opts.BlockSize), true); err != nil {
- return err
- }
- }
-
- for _, i := range p.orderedInodes {
- if err := i.writeData(wb, bs.Label); err != nil {
- return fmt.Errorf("failed to write inode %q: %v", i.Name, err)
- }
- if err := wb.FinishBlock(int64(opts.BlockSize)*int64(bs.BlocksPerCluster), false); err != nil {
- return err
- }
- }
- // Creatively use block writer to write out all empty data at the end
- if err := wb.FinishBlock(int64(opts.BlockSize)*int64(bs.TotalBlocks), false); err != nil {
- return err
- }
- return nil
+ return int64(bs.TotalBlocks), nil
}