From 8853e3700a9c532e615cf1b4bead690d532188ff Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Thu, 12 Dec 2024 19:10:39 +0400 Subject: [PATCH] feat: support reading VFAT volume labels See https://github.com/siderolabs/talos/issues/9936 Signed-off-by: Andrey Smirnov --- blkid/blkid_linux_test.go | 29 +++-- blkid/internal/filesystems/vfat/direntry.go | 127 ++++++++++++++++++++ blkid/internal/filesystems/vfat/direntry.h | 15 +++ blkid/internal/filesystems/vfat/vfat.go | 127 ++++++++++++++++++-- blkid/internal/filesystems/vfat/vfat.h | 2 +- blkid/internal/filesystems/vfat/vfatsb.go | 8 +- 6 files changed, 281 insertions(+), 27 deletions(-) create mode 100644 blkid/internal/filesystems/vfat/direntry.go create mode 100644 blkid/internal/filesystems/vfat/direntry.h diff --git a/blkid/blkid_linux_test.go b/blkid/blkid_linux_test.go index 34c1721..5684d27 100644 --- a/blkid/blkid_linux_test.go +++ b/blkid/blkid_linux_test.go @@ -19,6 +19,7 @@ import ( "os/exec" "path/filepath" "regexp" + "strconv" "strings" "testing" "time" @@ -83,14 +84,16 @@ func ext4Setup(t *testing.T, path string) { require.NoError(t, cmd.Run()) } -func vfatSetup(t *testing.T, path string) { - t.Helper() +func vfatSetup(bits int) func(t *testing.T, path string) { + return func(t *testing.T, path string) { + t.Helper() - cmd := exec.Command("mkfs.vfat", "-v", path) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr + cmd := exec.Command("mkfs.vfat", "-F", strconv.Itoa(bits), "-n", "TALOS_V1", "-v", path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr - require.NoError(t, cmd.Run()) + require.NoError(t, cmd.Run()) + } } func luksSetup(t *testing.T, path string) { @@ -325,7 +328,7 @@ func TestProbePathFilesystems(t *testing.T) { name: "vfat small", size: 100 * MiB, - setup: vfatSetup, + setup: vfatSetup(16), expectedName: "vfat", expectedBlockSize: []uint32{512}, @@ -334,20 +337,22 @@ func TestProbePathFilesystems(t *testing.T) { expectedSignatures: []blkid.SignatureRange{ {Offset: 54, Size: 8}, }, + expectedLabel: "TALOS_V1", }, { name: "vfat big", size: 500 * MiB, - setup: vfatSetup, + setup: vfatSetup(32), expectedName: "vfat", expectedBlockSize: []uint32{512}, - expectedFSBlockSize: []uint32{8192}, + expectedFSBlockSize: []uint32{4096}, expectedFSSize: 524256768, expectedSignatures: []blkid.SignatureRange{ - {Offset: 54, Size: 8}, + {Offset: 82, Size: 8}, }, + expectedLabel: "TALOS_V1", }, { name: "luks", @@ -887,7 +892,7 @@ func setupNestedGPT(t *testing.T, path string) { require.NoError(t, exec.Command("partprobe", path).Run()) - vfatSetup(t, path+"p1") + vfatSetup(16)(t, path+"p1") ext4Setup(t, path+"p3") xfsSetup(t, path+"p6") } @@ -1066,7 +1071,7 @@ func setupOurGPT(t *testing.T, path string, createFilesystems bool) { require.NoError(t, part.Write()) if createFilesystems { - vfatSetup(t, path+"p1") + vfatSetup(16)(t, path+"p1") xfsSetup(t, path+"p3") xfsSetup(t, path+"p5") xfsSetup(t, path+"p6") diff --git a/blkid/internal/filesystems/vfat/direntry.go b/blkid/internal/filesystems/vfat/direntry.go new file mode 100644 index 0000000..4485846 --- /dev/null +++ b/blkid/internal/filesystems/vfat/direntry.go @@ -0,0 +1,127 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Code generated by "cstruct -pkg vfat -struct DirEntry -input direntry.h -endianness LittleEndian"; DO NOT EDIT. + +package vfat + +import "encoding/binary" + +var _ = binary.LittleEndian + +// DirEntry is a byte slice representing the direntry.h C header. +type DirEntry []byte + +// Get_name returns name. +func (s DirEntry) Get_name() []byte { + return s[0:11] +} + +// Put_name sets name. +func (s DirEntry) Put_name(v []byte) { + copy(s[0:11], v) +} + +// Get_attr returns attr. +func (s DirEntry) Get_attr() byte { + return s[11] +} + +// Put_attr sets attr. +func (s DirEntry) Put_attr(v byte) { + s[11] = v +} + +// Get_time_creat returns time_creat. +func (s DirEntry) Get_time_creat() uint16 { + return binary.LittleEndian.Uint16(s[12:14]) +} + +// Put_time_creat sets time_creat. +func (s DirEntry) Put_time_creat(v uint16) { + binary.LittleEndian.PutUint16(s[12:14], v) +} + +// Get_date_creat returns date_creat. +func (s DirEntry) Get_date_creat() uint16 { + return binary.LittleEndian.Uint16(s[14:16]) +} + +// Put_date_creat sets date_creat. +func (s DirEntry) Put_date_creat(v uint16) { + binary.LittleEndian.PutUint16(s[14:16], v) +} + +// Get_time_acc returns time_acc. +func (s DirEntry) Get_time_acc() uint16 { + return binary.LittleEndian.Uint16(s[16:18]) +} + +// Put_time_acc sets time_acc. +func (s DirEntry) Put_time_acc(v uint16) { + binary.LittleEndian.PutUint16(s[16:18], v) +} + +// Get_date_acc returns date_acc. +func (s DirEntry) Get_date_acc() uint16 { + return binary.LittleEndian.Uint16(s[18:20]) +} + +// Put_date_acc sets date_acc. +func (s DirEntry) Put_date_acc(v uint16) { + binary.LittleEndian.PutUint16(s[18:20], v) +} + +// Get_cluster_high returns cluster_high. +func (s DirEntry) Get_cluster_high() uint16 { + return binary.LittleEndian.Uint16(s[20:22]) +} + +// Put_cluster_high sets cluster_high. +func (s DirEntry) Put_cluster_high(v uint16) { + binary.LittleEndian.PutUint16(s[20:22], v) +} + +// Get_time_write returns time_write. +func (s DirEntry) Get_time_write() uint16 { + return binary.LittleEndian.Uint16(s[22:24]) +} + +// Put_time_write sets time_write. +func (s DirEntry) Put_time_write(v uint16) { + binary.LittleEndian.PutUint16(s[22:24], v) +} + +// Get_date_write returns date_write. +func (s DirEntry) Get_date_write() uint16 { + return binary.LittleEndian.Uint16(s[24:26]) +} + +// Put_date_write sets date_write. +func (s DirEntry) Put_date_write(v uint16) { + binary.LittleEndian.PutUint16(s[24:26], v) +} + +// Get_cluster_low returns cluster_low. +func (s DirEntry) Get_cluster_low() uint16 { + return binary.LittleEndian.Uint16(s[26:28]) +} + +// Put_cluster_low sets cluster_low. +func (s DirEntry) Put_cluster_low(v uint16) { + binary.LittleEndian.PutUint16(s[26:28], v) +} + +// Get_size returns size. +func (s DirEntry) Get_size() uint32 { + return binary.LittleEndian.Uint32(s[28:32]) +} + +// Put_size sets size. +func (s DirEntry) Put_size(v uint32) { + binary.LittleEndian.PutUint32(s[28:32], v) +} + +// DIRENTRY_SIZE is the size of the DirEntry struct. +const DIRENTRY_SIZE = 32 diff --git a/blkid/internal/filesystems/vfat/direntry.h b/blkid/internal/filesystems/vfat/direntry.h new file mode 100644 index 0000000..277e04c --- /dev/null +++ b/blkid/internal/filesystems/vfat/direntry.h @@ -0,0 +1,15 @@ +#include + +struct vfat_dir_entry { + uint8_t name[11]; + uint8_t attr; + uint16_t time_creat; + uint16_t date_creat; + uint16_t time_acc; + uint16_t date_acc; + uint16_t cluster_high; + uint16_t time_write; + uint16_t date_write; + uint16_t cluster_low; + uint32_t size; +} __attribute__((packed)); diff --git a/blkid/internal/filesystems/vfat/vfat.go b/blkid/internal/filesystems/vfat/vfat.go index 27b9eca..0f89414 100644 --- a/blkid/internal/filesystems/vfat/vfat.go +++ b/blkid/internal/filesystems/vfat/vfat.go @@ -9,7 +9,12 @@ package vfat //go:generate go run ../../../../internal/cstruct/cstruct.go -pkg vfat -struct VFATSB -input vfat.h -endianness LittleEndian +//go:generate go run ../../../../internal/cstruct/cstruct.go -pkg vfat -struct DirEntry -input direntry.h -endianness LittleEndian + import ( + "encoding/binary" + "strings" + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/magic" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/probe" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/utils" @@ -83,7 +88,9 @@ func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { vfatSB := VFATSB(vfatBuf) msdosSB := MSDOSSB(msdosBuf) - if !isValid(msdosSB) { + fatSize, valid := isValid(msdosSB, vfatSB) + + if !valid { return nil, nil //nolint:nilnil } @@ -94,39 +101,139 @@ func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { sectorSize := uint32(msdosSB.Get_ms_sector_size()) + var label *string + + if msdosSB.Get_ms_fat_length() > 0 { + rootStart := int64(uint32(msdosSB.Get_ms_reserved())+fatSize) * int64(sectorSize) + rootDirEntries := uint32(vfatSB.Get_vs_dir_entries()) + + dosLabel, err := p.searchFATLabel(r, rootStart, rootDirEntries) + if err == nil { + dosLabel = strings.TrimRight(dosLabel, " ") + + label = &dosLabel + } + } else if vfatSB.Get_vs_fat32_length() > 0 { + maxLoops := 100 + + // search the FAT32 root dir for the label attribute + bufSize := uint32(vfatSB.Get_vs_cluster_size()) * sectorSize + buf := make([]byte, bufSize) + startDataSector := uint32(msdosSB.Get_ms_reserved()) + fatSize + entries := uint32((uint64(vfatSB.Get_vs_fat32_length()) * uint64(sectorSize)) / 4) + next := vfatSB.Get_vs_root_cluster() + + for next >= 2 && next < entries && maxLoops > 0 { + nextSectOff := (next - 2) * uint32(vfatSB.Get_vs_cluster_size()) + nextOff := uint64(startDataSector+nextSectOff) * uint64(sectorSize) + + count := bufSize / DIRENTRY_SIZE + + vfatLabel, err := p.searchFATLabel(r, int64(nextOff), count) + if err == nil { + vfatLabel = strings.TrimRight(vfatLabel, " ") + label = &vfatLabel + + break + } + + // get FAT entry + fatEntryOff := ((uint64(msdosSB.Get_ms_reserved()) * uint64(sectorSize)) + (uint64(next) * 4)) + + if _, err := r.ReadAt(buf, int64(fatEntryOff)); err != nil { + // ignore error + break + } + + // set next cluster + next = binary.LittleEndian.Uint32(buf) & 0x0fffffff + } + } + res := &probe.Result{ BlockSize: sectorSize, FilesystemBlockSize: uint32(vfatSB.Get_vs_cluster_size()) * sectorSize, ProbedSize: uint64(sectorCount) * uint64(sectorSize), + Label: label, } - return res, nil + return res, nil //nolint:nilerr } -func isValid(msdosSB MSDOSSB) bool { +func isValid(msdosSB MSDOSSB, vfatSB VFATSB) (uint32, bool) { if msdosSB.Get_ms_fats() == 0 { - return false + return 0, false } if msdosSB.Get_ms_reserved() == 0 { - return false + return 0, false } if !(0xf8 <= msdosSB.Get_ms_media() || msdosSB.Get_ms_media() == 0xf0) { - return false + return 0, false } if !utils.IsPowerOf2(msdosSB.Get_ms_cluster_size()) { - return false + return 0, false } if !utils.IsPowerOf2(msdosSB.Get_ms_sector_size()) { - return false + return 0, false } if msdosSB.Get_ms_sector_size() < 512 || msdosSB.Get_ms_sector_size() > 4096 { - return false + return 0, false + } + + fatLength := uint32(msdosSB.Get_ms_fat_length()) + if fatLength == 0 { + fatLength = vfatSB.Get_vs_fat32_length() + } + + fatSize := fatLength * uint32(msdosSB.Get_ms_fats()) + + return fatSize, true +} + +// FAT directory constants. +// +//nolint:revive,stylecheck +const ( + FAT_ENTRY_FREE = 0xe5 + FAT_ATTR_LONG_NAME = 0x0f + FAT_ATTR_MASK = 0x3f + FAT_ATTR_VOLUME_ID = 0x08 + FAT_ATTR_DIR = 0x10 +) + +func (p *Probe) searchFATLabel(r probe.Reader, offset int64, entries uint32) (string, error) { + buf := make([]byte, entries*DIRENTRY_SIZE) + + if _, err := r.ReadAt(buf, offset); err != nil { + return "", err + } + + for i := range entries { + dir := DirEntry(buf[i*DIRENTRY_SIZE : (i+1)*DIRENTRY_SIZE]) + + if dir.Get_name()[0] == 0x00 { + break + } + + if dir.Get_name()[0] == FAT_ENTRY_FREE || dir.Get_cluster_high() != 0 || dir.Get_cluster_low() != 0 || (dir.Get_attr()&FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME { + continue + } + + if (dir.Get_attr() & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) == FAT_ATTR_VOLUME_ID { + label := string(dir.Get_name()) + + if label[0] == 0x05 { + label = "\xe5" + label[1:] + } + + return label, nil + } } - return true + return "", nil } diff --git a/blkid/internal/filesystems/vfat/vfat.h b/blkid/internal/filesystems/vfat/vfat.h index e848ff3..e55f45e 100644 --- a/blkid/internal/filesystems/vfat/vfat.h +++ b/blkid/internal/filesystems/vfat/vfat.h @@ -7,7 +7,7 @@ struct vfat_super_block { /* 0d*/ uint8_t vs_cluster_size; /* 0e*/ uint16_t vs_reserved; /* 10*/ uint8_t vs_fats; -/* 11*/ unsigned char vs_dir_entries[2]; +/* 11*/ uint16_t vs_dir_entries; /* 13*/ unsigned char vs_sectors[2]; /* 15*/ unsigned char vs_media; /* 16*/ uint16_t vs_fat_length; diff --git a/blkid/internal/filesystems/vfat/vfatsb.go b/blkid/internal/filesystems/vfat/vfatsb.go index 47649fa..aa44800 100644 --- a/blkid/internal/filesystems/vfat/vfatsb.go +++ b/blkid/internal/filesystems/vfat/vfatsb.go @@ -74,13 +74,13 @@ func (s VFATSB) Put_vs_fats(v byte) { } // Get_vs_dir_entries returns vs_dir_entries. -func (s VFATSB) Get_vs_dir_entries() []byte { - return s[17:19] +func (s VFATSB) Get_vs_dir_entries() uint16 { + return binary.LittleEndian.Uint16(s[17:19]) } // Put_vs_dir_entries sets vs_dir_entries. -func (s VFATSB) Put_vs_dir_entries(v []byte) { - copy(s[17:19], v) +func (s VFATSB) Put_vs_dir_entries(v uint16) { + binary.LittleEndian.PutUint16(s[17:19], v) } // Get_vs_sectors returns vs_sectors.