Skip to content

Commit

Permalink
better gpt logic and add fat entry formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Feb 5, 2024
1 parent d854131 commit 21c4afb
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 24 deletions.
21 changes: 18 additions & 3 deletions fat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ var fatInit = map[int64][512]byte{
484: 0x72, 0x72, 0x41, 0x61, 0xfb, 0xf1, 0x1d, 0x00, 0x02,
510: 0x55, 0xaa},

// Below is the FAT.
32: {0xf8, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xf8, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f,
0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f},
15368: {0xf8, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xf8, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f,
Expand Down Expand Up @@ -387,9 +388,23 @@ const rootFileContents = "this is\nthe root file\n"
const dirFileContents = "this is not\nnot the root\nnot the root file\nnope. \nThis file has 5 lines.\n"

func TestBootSector(t *testing.T) {
var bs bootsector
// Print out the boot sector data.
dat := fatInit[0]
bs.data = dat[:]

bs := bootsector{data: dat[:]}
fsiLBA := bs.FSInfo()
fatLBA := 0 + bs.ReservedSectors()
fatSz := bs.SectorsPerFAT() * uint32(bs.SectorSize())
t.Log(bs.String())

// Print out the FSInfo sector data.
datfsi := fatInit[int64(fsiLBA)]
fsi := fsinfoSector{data: datfsi[:]}
t.Log(fsi.String())

// Print out the FAT.
datFAT := fatInit[int64(fatLBA)]
fat := fat32Sector{data: datFAT[:]}
t.Log(fat.String())
_ = fatSz

}
166 changes: 152 additions & 14 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ func (bs *bootsector) SetSectorsPerCluster(spclus uint16) {
}

// ReservedSectors returns the number of reserved sectors at the beginning of the volume.
// Should be at least 1.
// Should be at least 1. Reserved sectors include the boot sector, FS information sector and
// redundant sectors with these first two. The number of reserved sectors is usually
// 32 for FAT32 systems (~16k for 512 byte sectors).
// Sectors 6 and 7 are usually the backup boot sector and the FS information sector, respectively.
func (bs *bootsector) ReservedSectors() uint16 {
return binary.LittleEndian.Uint16(bs.data[bpbRsvdSecCnt:])
}
Expand Down Expand Up @@ -272,21 +275,35 @@ func (bs *bootsector) String() string {
return string(bs.Appendf(nil, '\n'))
}

func labelAppend(dst []byte, label string, data []byte, sep byte) []byte {
if len(data) == 0 {
return dst
}
dst = append(dst, label...)
dst = append(dst, ':')
dst = append(dst, data...)
dst = append(dst, sep)
return dst
}

func labelAppendUint(label string, dst []byte, data uint64, sep byte) []byte {
dst = append(dst, label...)
dst = append(dst, ':')
dst = strconv.AppendUint(dst, data, 10)
dst = append(dst, sep)
return dst
}

func labelAppendUint32(label string, dst []byte, data uint32, sep byte) []byte {
return labelAppendUint(label, dst, uint64(data), sep)
}

func (bs *bootsector) Appendf(dst []byte, separator byte) []byte {
appendData := func(name string, data []byte, sep byte) {
if len(data) == 0 {
return
}
dst = append(dst, name...)
dst = append(dst, ':')
dst = append(dst, data...)
dst = append(dst, sep)
dst = labelAppend(dst, name, data, sep)
}
appendInt := func(name string, data uint32, sep byte) {
dst = append(dst, name...)
dst = append(dst, ':')
dst = strconv.AppendUint(dst, uint64(data), 10)
dst = append(dst, sep)
dst = labelAppendUint32(name, dst, data, sep)
}
oem := bs.OEMName()
appendData("OEM", clipname(oem[:]), separator)
Expand Down Expand Up @@ -314,7 +331,128 @@ func (bs *bootsector) Appendf(dst []byte, separator byte) []byte {
return dst
}

// BootCode returns the boot code at the end of the boot sector.
func (bs *bootsector) BootCode() []byte {
// bootcode returns the boot code at the end of the boot sector.
func (bs *bootsector) bootcode() []byte {
return bs.data[bsBootCode32:bs55AA]
}

// fsinfoSector is the FS Information Sector for FAT32 volumes.
type fsinfoSector struct {
data []byte
}

// Signatures returns the 3 signatures at the beginning, middle and end of the sector.
// Expect them to be 0x41615252, 0x61417272, 0xAA550000 respectively.
func (fsi *fsinfoSector) Signatures() (sigStart, sigMid, sigEnd uint32) {
return binary.LittleEndian.Uint32(fsi.data[0:]),
binary.LittleEndian.Uint32(fsi.data[0x1e4:]),
binary.LittleEndian.Uint32(fsi.data[0x1fc:])
}

// SetSignatures sets the 3 signatures at the beginning, middle and end of the sector.
// Should be called as follows to set valid signatures expected by most implementations:
//
// fsi.SetSignatures(0x41615252, 0x61417272, 0xAA550000)
func (fsi *fsinfoSector) SetSignatures(sigStart, sigMid, sigEnd uint32) {
binary.LittleEndian.PutUint32(fsi.data[0:], sigStart)
binary.LittleEndian.PutUint32(fsi.data[0x1e4:], sigMid)
binary.LittleEndian.PutUint32(fsi.data[0x1fc:], sigEnd)
}

// FreeClusterCount is the last known number of free data clusters on the volume,
// or 0xFFFFFFFF if unknown. Should be set to 0xFFFFFFFF during format and updated by
// the operating system later on. Must not be absolutely relied upon to be correct in all scenarios.
// Before using this value, the operating system should sanity check this value to
// be less than or equal to the volume's count of clusters.
func (fsi *fsinfoSector) FreeClusterCount() uint32 {
return binary.LittleEndian.Uint32(fsi.data[0x1e8:])
}

// SetFreeClusterCount sets the last known number of free data clusters on the volume.
func (fsi *fsinfoSector) SetFreeClusterCount(count uint32) {
binary.LittleEndian.PutUint32(fsi.data[0x1e8:], count)
}

// LastAllocatedCluster is the number of the most recently known to be allocated data cluster.
// Should be set to 0xFFFFFFFF during format and updated by the operating system later on.
// With 0xFFFFFFFF the system should start at cluster 0x00000002. Must not be absolutely
// relied upon to be correct in all scenarios. Before using this value, the operating system
// should sanity check this value to be a valid cluster number on the volume.
func (fsi *fsinfoSector) LastAllocatedCluster() uint32 {
return binary.LittleEndian.Uint32(fsi.data[0x1ec:])
}

// SetLastAllocatedCluster sets the number of the most recently known to be allocated data cluster.
func (fsi *fsinfoSector) SetLastAllocatedCluster(cluster uint32) {
binary.LittleEndian.PutUint32(fsi.data[0x1ec:], cluster)
}

func (fsi *fsinfoSector) String() string {
return string(fsi.Appendf(nil, '\n'))
}

func (fsi *fsinfoSector) Appendf(dst []byte, separator byte) []byte {
lo, mid, hi := fsi.Signatures()
if lo != 0x41615252 || mid != 0x61417272 || hi != 0xAA550000 {
dst = append(dst, "invalid fsi signatures"...)
dst = append(dst, separator)
}
dst = labelAppendUint32("FreeClusterCount", dst, fsi.FreeClusterCount(), separator)
dst = labelAppendUint32("LastAllocatedCluster", dst, fsi.LastAllocatedCluster(), separator)
return dst
}

// fatSector is a File Allocation Table sector.
type fat32Sector struct {
data []byte
}

type entry uint32

func (fs *fat32Sector) Entry(idx int) entry {
return entry(binary.LittleEndian.Uint32(fs.data[idx*4:]))
}

func (fs *fat32Sector) SetEntry(idx int, ent entry) {
binary.LittleEndian.PutUint32(fs.data[idx*4:], uint32(ent))
}

func (fs entry) Cluster() uint32 {
return uint32(fs) & 0x0FFF_FFFF
}

func (e entry) Appendf(dst []byte, separator byte) []byte {
if e.IsEOF() {
dst = labelAppendUint32("entry", dst, e.Cluster(), ' ')
return append(dst, "EOF"...)
}
return labelAppendUint32("entry", dst, e.Cluster(), separator)
}

func (e entry) IsEOF() bool {
return e&0x0FFF_FFFF >= 0x0FFF_FFF8
}

func (fs *fat32Sector) String() string {
return string(fs.AppendfEntries(nil, " -> ", '\n'))
}

func (fs *fat32Sector) AppendfEntries(dst []byte, entrySep string, chainSep byte) []byte {
var inChain bool
for i := 0; i < len(fs.data)/4; i++ {
entry := fs.Entry(i)
if entry == 0 {
break
}
dst = entry.Appendf(dst, chainSep)
if entry.IsEOF() {
dst = append(dst, chainSep)
inChain = false
} else if inChain {
dst = append(dst, entrySep...)
} else {
inChain = true
}
}
return dst
}
17 changes: 10 additions & 7 deletions internal/gpt/gpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,10 @@ func (p *PartitionEntry) SetAttributes(attr PartitionAttributes) {
binary.LittleEndian.PutUint64(p.data[48:56], uint64(attr))
}

// ReadName reads the partition name from the partition entry and
// ReadNameAsUTF8 reads the partition name from the partition entry and
// encodes it as utf-8 into the provided slice. The number of bytes
// read is returned along with any error.
func (p *PartitionEntry) ReadName(b []byte) (int, error) {
func (p *PartitionEntry) ReadNameAsUTF8(b []byte) (int, error) {
// Find the length of the name.
nameLen := 0
for nameLen < pteNameLen && p.data[pteNameOff+nameLen] != 0 {
Expand All @@ -242,18 +242,21 @@ func (p *PartitionEntry) ReadName(b []byte) (int, error) {
}

func (p *PartitionEntry) ClearName() {
p.data[pteNameOff] = 0
p.clearNameAfter(0)
}

// WriteName writes a utf-8 encoded string as the Partition Entry's name.
func (p *PartitionEntry) WriteName(name []byte) error {
// SetNameUTF8 writes a utf-8 encoded string as the Partition Entry's name.
func (p *PartitionEntry) SetNameUTF8(name []byte) error {
n, err := utf16x.FromUTF8(p.data[pteNameOff:pteNameOff+pteNameLen], name, binary.LittleEndian)
if err != nil {
return err
}
p.clearNameAfter(n)
return nil
}

for i := n; i < pteNameLen; i++ {
func (p *PartitionEntry) clearNameAfter(idx int) {
for i := idx; i < pteNameLen; i++ {
p.data[pteNameOff+i] = 0
}
return nil
}

0 comments on commit 21c4afb

Please sign in to comment.