Skip to content

Commit

Permalink
Allow building with clang.
Browse files Browse the repository at this point in the history
Improved checks.
Added more documentation/definitions to fat.h.
  • Loading branch information
profi200 committed Mar 12, 2023
1 parent 68c5965 commit f0cba8b
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 94 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ ARFLAGS := -rcs
LDFLAGS := $(ARCH) -O2 -s -pie -fPIE -Wl,--gc-sections,-z,relro,-z,now,-z,noexecstack

PREFIX :=
ifneq ($(strip $(USE_CLANG)),)
CC := $(PREFIX)clang
CXX := $(PREFIX)clang++
AS := $(PREFIX)clang
AR := $(PREFIX)gcc-ar
else
CC := $(PREFIX)gcc
CXX := $(PREFIX)g++
AS := $(PREFIX)gcc
AR := $(PREFIX)gcc-ar
endif


# Do not change anything after this
Expand Down
104 changes: 91 additions & 13 deletions include/fat.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
// FAT12/16/32: http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc


// Volume Boot Record.
typedef struct
// Boot sector.
typedef struct __attribute__((packed))
{
u8 jmpBoot[3]; // {0xEB, 0xXX, 0x90} or {0xE9, 0xXX, 0xXX}.
char oemName[8]; // Usually system name that formatted the volume.
Expand All @@ -37,13 +37,13 @@ typedef struct
struct __attribute__((packed)) // FAT12/FAT16.
{
u8 drvNum; // 0x80 or 0x00.
u8 reserved1; // Must be 0.
u8 reserved1; // Must be 0. Used by Windows for dirty flag (bit 0 set = dirty).
u8 bootSig; // 0x29 if one or both of the following 2 fields are non-zero.
u32 volId; // Volume serial number generated from date and time.
char volLab[11]; // "NO NAME " or space padded label.
char filSysType[8]; // "FAT12 ", "FAT16 " or "FAT ".
u8 bootCode[448];
} fat16;
} ebpb;
struct __attribute__((packed)) // FAT32.
{
u32 fatSz32; // Must be non-zero.
Expand All @@ -54,20 +54,19 @@ typedef struct
u16 bkBootSec; // 0 or 6. Backup boot sector must be present if the later.
u8 reserved[12]; // Must be 0.
u8 drvNum; // 0x80 or 0x00.
u8 reserved1; // Must be 0.
u8 reserved1; // Must be 0. Used by Windows for dirty flag (bit 0 set = dirty).
u8 bootSig; // 0x29 if one or both of the following 2 fields are non-zero.
u32 volId; // Volume serial number generated from date and time.
char volLab[11]; // "NO NAME " or space padded label.
char filSysType[8]; // "FAT32 ".
u8 bootCode[420];
} fat32;
} ebpb32;
};

u16 sigWord; // 0xAA55.
} __attribute__((packed)) Vbr;
static_assert(offsetof(Vbr, fat16.bootCode) == 62, "Member fat16.bootCode of Vbr not at offsetof 62.");
static_assert(offsetof(Vbr, fat32.bootCode) == 90, "Member fat32.bootCode of Vbr not at offsetof 90.");
static_assert(offsetof(Vbr, sigWord) == 510, "Member sigWord of Vbr not at offsetof 510.");
} BootSec;
static_assert(offsetof(BootSec, ebpb.bootCode) == 62, "Member ebpb.bootCode of BootSec not at offset 62.");
static_assert(offsetof(BootSec, ebpb32.bootCode) == 90, "Member ebpb32.bootCode of BootSec not at offset 90.");
static_assert(offsetof(BootSec, sigWord) == 510, "Member sigWord of BootSec not at offset 510.");

typedef struct
{
Expand All @@ -79,7 +78,7 @@ typedef struct
u8 reserved2[12]; // Must be 0.
u32 trailSig; // Must be 0xAA550000.
} FsInfo;
static_assert(offsetof(FsInfo, trailSig) == 508, "Member trailSig of FsInfo not at offsetof 508.");
static_assert(offsetof(FsInfo, trailSig) == 508, "Member trailSig of FsInfo not at offset 508.");

typedef struct
{
Expand All @@ -96,9 +95,88 @@ typedef struct
u16 fstClusLo; // Low u16 of first data cluster.
u32 fileSize; // File/directory size in bytes.
} FatDir;
static_assert(offsetof(FatDir, fileSize) == 28, "Member fileSize of FatDir not at offsetof 28.");
static_assert(offsetof(FatDir, fileSize) == 28, "Member fileSize of FatDir not at offset 28.");

typedef struct __attribute__((packed))
{
u8 ord; // Order of LDIR entries. Last entry (which comes first) must have LAST_LONG_ENTRY (0x40) set.
u16 name1[5]; // UTF-16 character 1-5 of long name.
u8 attr; // Must be ATTR_LONG_NAME (DIR_ATTR_VOLUME_ID | DIR_ATTR_SYSTEM | DIR_ATTR_HIDDEN | DIR_ATTR_READ_ONLY).
u8 type; // Must be 0 (sub-component of long name). Other values unknown (extensions).
u8 chksum; // Checksum of 11 characters short name.
u16 name2[6]; // UTF-16 character 6-11 of long name.
u16 fstClusLo; // Must be 0.
u16 name3[2]; // UTF-16 character 12-13 of long name.
} FatLdir;
static_assert(offsetof(FatLdir, name3) == 28, "Member name3 of FatLdir not at offset 28.");


// Boot sector.
#define BS_JMP_BOOT_FAT "\xEB\x3C\x90"
#define BS_JMP_BOOT_FAT32 "\xEB\x58\x90"
#define BS_DEFAULT_OEM_NAME "MSWIN4.1" // Recommended default OEM name.

// BIOS Parameter Block.
#define BPB_DEFAULT_MEDIA (0xF8u)

// Extended BIOS Parameter Block.
#define EBPB_DEFAULT_DRV_NUM (0x80u)
#define EBPB_BOOT_SIG (0x29u)
#define EBPB_VOL_LAB_NO_NAME "NO NAME "
#define EBPB_FIL_SYS_TYPE_FAT12 "FAT12 "
#define EBPB_FIL_SYS_TYPE_FAT16 "FAT16 "
#define EBPB_FIL_SYS_TYPE_FAT32 "FAT32 "
#define EBPB_SIG_WORD (0xAA55u)

// FSInfo.
#define FS_INFO_LEAD_SIG (0x41615252u)
#define FS_INFO_STRUC_SIG (0x61417272u)
#define FS_INFO_UNK_FREE_COUNT (0xFFFFFFFFu)
#define FS_INFO_UNK_NXT_FREE (0xFFFFFFFFu)
#define FS_INFO_TRAIL_SIG (0xAA550000u)

// FAT directory entry.
#define DIR_ATTR_READ_ONLY (1u)
#define DIR_ATTR_HIDDEN (1u<<1)
#define DIR_ATTR_SYSTEM (1u<<2)
#define DIR_ATTR_VOLUME_ID (1u<<3)
#define DIR_ATTR_DIRECTORY (1u<<4)
#define DIR_ATTR_ARCHIVE (1u<<5)

// File allocation table.
// Note: MAX_CLUS actually means number of clusters, not index!
#define FAT_FIRST_ENT (2u) // Index 0 and 1 are reserved.
#define FAT12_MAX_CLUS (0xFF4u) // Specification limit.
#define FAT16_MAX_CLUS (0xFFF4u) // Specification limit.
#define FAT32_MAX_CLUS (0x0FFFFFF6u) // Theoretical limit. 2 clusters will not be allocatable. Spec limit is 0x0FFFFFF4?

#define FAT_FREE (0u) // FAT entry is unallocated/free. Common for all 3 variants.
// 0xXXXXXFF6 is reserved.
#define FAT12_BAD (0xFF7u)
#define FAT16_BAD (0xFFF7u)
#define FAT32_BAD (0x0FFFFFF7u)
// 0xXXXXXFF8 to 0xXXXXXFFE is reserved.
#define FAT12_EOF (0xFFFu)
#define FAT16_EOF (0xFFFFu)
#define FAT32_EOF (0x0FFFFFFFu)

// FAT long directory entry.
#define LDIR_LAST_LONG_ENTRY (1u<<6)
#define LDIR_ATTR_LONG_NAME (DIR_ATTR_VOLUME_ID | DIR_ATTR_SYSTEM | DIR_ATTR_HIDDEN | DIR_ATTR_READ_ONLY)
#define LDIR_ATTR_LONG_NAME_MASK (DIR_ATTR_ARCHIVE | DIR_ATTR_DIRECTORY | LDIR_ATTR_LONG_NAME)



static inline u8 calcLdirChksum(const char *shortName)
{
u8 chksum = 0;
for(unsigned i = 0; i < 11; i++)
{
chksum = ((chksum & 1u ? 0x80u : 0u) | chksum>>1) + *shortName++;
}

return chksum;
}

void calcFormatFat(FormatParams &params);
void calcFormatFat32(FormatParams &params);
Expand Down
125 changes: 60 additions & 65 deletions source/fat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ int makeFsFat(const FormatParams &params, BufferedFsWriter &dev, const std::stri
if(res != 0) return res;

// Prepare label.
char labelBuf[12] = "NO NAME ";
char labelBuf[12] = EBPB_VOL_LAB_NO_NAME;
if(!label.empty())
{
memset(labelBuf, ' ', 11); // Padding must be spaces.
Expand All @@ -137,65 +137,63 @@ int makeFsFat(const FormatParams &params, BufferedFsWriter &dev, const std::stri
memcpy(labelBuf, label.c_str(), labelLen);
}

// Volume Boot Record (VBR).
Vbr vbr{};
vbr.jmpBoot[0] = 0xEB;
vbr.jmpBoot[1] = 0x00;
vbr.jmpBoot[2] = 0x90;
memcpy(vbr.oemName, "MSWIN4.1", 8); // SDFormatter hardcodes this.
// Boot sector.
BootSec bs{};
memcpy(bs.jmpBoot, "\xEB\x00\x90", 3); // Doesn't jump to bootCode but it's what SDFormatter uses.
memcpy(bs.oemName, BS_DEFAULT_OEM_NAME, 8);

// BIOS Parameter Block (BPB).
const u32 secPerClus = params.secPerClus;
const u32 rsvdSecCnt = params.rsvdSecCnt;
const u8 fatBits = params.fatBits;
const u32 partSectors = static_cast<u32>(params.totSec - partStart);
const u32 secPerFat = params.secPerFat;
vbr.bytesPerSec = bytesPerSec;
vbr.secPerClus = secPerClus;
vbr.rsvdSecCnt = rsvdSecCnt;
vbr.numFats = 2;
vbr.rootEntCnt = (fatBits == 32 ? 0 : 512);
vbr.totSec16 = (partSectors > 0xFFFF || fatBits == 32 ? 0 : partSectors); // Not used for FAT32.
vbr.media = 0xF8;
vbr.fatSz16 = (secPerFat > 0xFFFF || fatBits == 32 ? 0 : secPerFat); // Not used for FAT32.
vbr.secPerTrk = params.secPerTrk;
vbr.numHeads = params.heads;
vbr.hiddSec = partStart;
vbr.totSec32 = (partSectors > 0xFFFF || fatBits == 32 ? partSectors : 0);
vbr.sigWord = 0xAA55;
bs.bytesPerSec = bytesPerSec;
bs.secPerClus = secPerClus;
bs.rsvdSecCnt = rsvdSecCnt;
bs.numFats = 2;
bs.rootEntCnt = (fatBits == 32 ? 0 : 512);
bs.totSec16 = (partSectors > 0xFFFF || fatBits == 32 ? 0 : partSectors); // Not used for FAT32.
bs.media = BPB_DEFAULT_MEDIA;
bs.fatSz16 = (secPerFat > 0xFFFF || fatBits == 32 ? 0 : secPerFat); // Not used for FAT32.
bs.secPerTrk = params.secPerTrk;
bs.numHeads = params.heads;
bs.hiddSec = partStart;
bs.totSec32 = (partSectors > 0xFFFF || fatBits == 32 ? partSectors : 0);
bs.sigWord = EBPB_SIG_WORD;

if(fatBits < 32)
{
// Extended BIOS Parameter Block FAT12/FAT16.
vbr.fat16.drvNum = 0x80;
vbr.fat16.bootSig = 0x29;
vbr.fat16.volId = makeVolId();
memcpy(vbr.fat16.volLab, labelBuf, 11);
memcpy(vbr.fat16.filSysType, (fatBits == 12 ? "FAT12 " : "FAT16 "), 8);
memset(vbr.fat16.bootCode, 0xF4, sizeof(vbr.fat16.bootCode));

// Write Vbr.
res = dev.write(reinterpret_cast<u8*>(&vbr), sizeof(Vbr));
bs.ebpb.drvNum = EBPB_DEFAULT_DRV_NUM;
bs.ebpb.bootSig = EBPB_BOOT_SIG;
bs.ebpb.volId = makeVolId();
memcpy(bs.ebpb.volLab, labelBuf, 11);
memcpy(bs.ebpb.filSysType, (fatBits == 12 ? EBPB_FIL_SYS_TYPE_FAT12 : EBPB_FIL_SYS_TYPE_FAT16), 8);
memset(bs.ebpb.bootCode, 0xF4, sizeof(bs.ebpb.bootCode)); // Fill with x86 hlt instructions.

// Write boot sector.
res = dev.write(reinterpret_cast<u8*>(&bs), sizeof(BootSec));
if(res != 0) return res;
}
else
{
// Extended BIOS Parameter Block FAT32.
vbr.fat32.fatSz32 = secPerFat;
vbr.fat32.extFlags = 0;
vbr.fat32.fsVer = 0; // 0.0.
vbr.fat32.rootClus = 2; // 2 or the first cluster not marked as defective.
vbr.fat32.fsInfoSector = 1;
vbr.fat32.bkBootSec = 6;
vbr.fat32.drvNum = 0x80;
vbr.fat32.bootSig = 0x29;
vbr.fat32.volId = makeVolId();
memcpy(vbr.fat32.volLab, labelBuf, 11);
memcpy(vbr.fat32.filSysType, "FAT32 ", 8);
memset(vbr.fat32.bootCode, 0xF4, sizeof(vbr.fat32.bootCode));

// Write Vbr.
res = dev.write(reinterpret_cast<u8*>(&vbr), sizeof(Vbr));
bs.ebpb32.fatSz32 = secPerFat;
bs.ebpb32.extFlags = 0;
bs.ebpb32.fsVer = 0; // 0.0.
bs.ebpb32.rootClus = 2; // 2 or the first cluster not marked as defective.
bs.ebpb32.fsInfoSector = 1;
bs.ebpb32.bkBootSec = 6;
bs.ebpb32.drvNum = EBPB_DEFAULT_DRV_NUM;
bs.ebpb32.bootSig = EBPB_BOOT_SIG;
bs.ebpb32.volId = makeVolId();
memcpy(bs.ebpb32.volLab, labelBuf, 11);
memcpy(bs.ebpb32.filSysType, EBPB_FIL_SYS_TYPE_FAT32, 8);
memset(bs.ebpb32.bootCode, 0xF4, sizeof(bs.ebpb32.bootCode)); // Fill with x86 hlt instructions.

// Write boot sector.
res = dev.write(reinterpret_cast<u8*>(&bs), sizeof(BootSec));
if(res != 0) return res;

// There are apparently drivers based on wrong documentation stating the
Expand All @@ -205,50 +203,46 @@ int makeFsFat(const FormatParams &params, BufferedFsWriter &dev, const std::stri
if(bytesPerSec > 512)
{
tmpOffset = curOffset + bytesPerSec - 2;
res = dev.fillAndWrite(reinterpret_cast<u8*>(&vbr.sigWord), tmpOffset, 2);
res = dev.fillAndWrite(reinterpret_cast<u8*>(&bs.sigWord), tmpOffset, 2);
if(res != 0) return res;
}

// Write FSInfo.
FsInfo fsInfo{};
fsInfo.leadSig = 0x41615252;
fsInfo.strucSig = 0x61417272;
fsInfo.leadSig = FS_INFO_LEAD_SIG;
fsInfo.strucSig = FS_INFO_STRUC_SIG;
fsInfo.freeCount = params.maxClus - 1;
fsInfo.nxtFree = 3;
fsInfo.trailSig = 0xAA550000;
fsInfo.trailSig = FS_INFO_TRAIL_SIG;
res = dev.write(reinterpret_cast<u8*>(&fsInfo), sizeof(FsInfo));
if(res != 0) return res;

// TODO: FSInfo sector signature word needed?

// The FAT spec says there is actually a third boot sector with just a signature word.
tmpOffset = curOffset + (2 * bytesPerSec) + bytesPerSec - 2;
res = dev.fillAndWrite(reinterpret_cast<u8*>(&vbr.sigWord), tmpOffset, 2);
res = dev.fillAndWrite(reinterpret_cast<u8*>(&bs.sigWord), tmpOffset, 2);
if(res != 0) return res;

// Write copy of Vbr.
// Write copy of boot sector.
tmpOffset += 2 + (3 * bytesPerSec);
res = dev.fillAndWrite(reinterpret_cast<u8*>(&vbr), tmpOffset, sizeof(Vbr));
res = dev.fillAndWrite(reinterpret_cast<u8*>(&bs), tmpOffset, sizeof(BootSec));
if(res != 0) return res;

// Write sector signature word of VBR copy.
// Write sector signature word of boot sector copy.
if(bytesPerSec > 512)
{
tmpOffset += bytesPerSec - 2;
res = dev.fillAndWrite(reinterpret_cast<u8*>(&vbr.sigWord), tmpOffset, 2);
res = dev.fillAndWrite(reinterpret_cast<u8*>(&bs.sigWord), tmpOffset, 2);
if(res != 0) return res;
}

// Free cluster count is 0xFFFFFFFF (unknown) for FSInfo copy.
fsInfo.freeCount = 0xFFFFFFFF;
// Free cluster count is unknown for FSInfo copy.
fsInfo.freeCount = FS_INFO_UNK_FREE_COUNT;
res = dev.write(reinterpret_cast<u8*>(&fsInfo), sizeof(FsInfo));
if(res != 0) return res;

// TODO: FSInfo sector signature word needed?

// Write copy of third sector signature word.
tmpOffset = curOffset + (8 * bytesPerSec) + bytesPerSec - 2;
res = dev.fillAndWrite(reinterpret_cast<u8*>(&vbr.sigWord), tmpOffset, 2);
res = dev.fillAndWrite(reinterpret_cast<u8*>(&bs.sigWord), tmpOffset, 2);
if(res != 0) return res;
}

Expand All @@ -258,14 +252,15 @@ int makeFsFat(const FormatParams &params, BufferedFsWriter &dev, const std::stri
if(fatBits < 32)
{
// Reserve first 2 FAT entries.
*fat = (fatBits == 16 ? 0xFFFFFFF8 : 0x00FFFFF8);
u32 rsvdEnt = (fatBits == 16 ? FAT16_EOF<<16 | FAT16_EOF : FAT12_EOF<<12 | FAT12_EOF);
*fat = (rsvdEnt & ~0xFFu) | BPB_DEFAULT_MEDIA;
}
else
{
// Reserve first 2 FAT entries. A third entry for the root directory cluster.
fat[0] = 0x0FFFFFF8;
fat[1] = 0x0FFFFFFF;
fat[2] = 0x0FFFFFFF;
fat[0] = (FAT32_EOF & ~0xFFu) | BPB_DEFAULT_MEDIA;
fat[1] = FAT32_EOF;
fat[2] = FAT32_EOF;
rsvdEntrySize = 3 * 4;
}

Expand All @@ -284,7 +279,7 @@ int makeFsFat(const FormatParams &params, BufferedFsWriter &dev, const std::stri
{
FatDir dir{}; // Make sure all other fields are zero.
memcpy(dir.name, labelBuf, 11);
dir.attr = 0x08; // ATTR_VOLUME_ID.
dir.attr = DIR_ATTR_VOLUME_ID;

curOffset += secPerFat * bytesPerSec;
res = dev.fillAndWrite(reinterpret_cast<u8*>(&dir), curOffset, sizeof(FatDir));
Expand Down
Loading

0 comments on commit f0cba8b

Please sign in to comment.