From f0cba8b2e5e8c252c9f1e143a071eb58547f8770 Mon Sep 17 00:00:00 2001 From: profi200 Date: Sun, 12 Mar 2023 16:08:56 +0100 Subject: [PATCH] Allow building with clang. Improved checks. Added more documentation/definitions to fat.h. --- Makefile | 7 +++ include/fat.h | 104 +++++++++++++++++++++++++++++++++----- source/fat.cpp | 125 ++++++++++++++++++++++------------------------ source/format.cpp | 35 +++++++------ 4 files changed, 177 insertions(+), 94 deletions(-) diff --git a/Makefile b/Makefile index 06a005c..c45b32b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/include/fat.h b/include/fat.h index 0a4a4d7..2416802 100644 --- a/include/fat.h +++ b/include/fat.h @@ -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. @@ -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. @@ -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 { @@ -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 { @@ -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 ¶ms); void calcFormatFat32(FormatParams ¶ms); diff --git a/source/fat.cpp b/source/fat.cpp index 11c848f..38ad5bd 100644 --- a/source/fat.cpp +++ b/source/fat.cpp @@ -128,7 +128,7 @@ int makeFsFat(const FormatParams ¶ms, 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. @@ -137,12 +137,10 @@ int makeFsFat(const FormatParams ¶ms, 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; @@ -150,52 +148,52 @@ int makeFsFat(const FormatParams ¶ms, BufferedFsWriter &dev, const std::stri const u8 fatBits = params.fatBits; const u32 partSectors = static_cast(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(&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(&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(&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(&bs), sizeof(BootSec)); if(res != 0) return res; // There are apparently drivers based on wrong documentation stating the @@ -205,50 +203,46 @@ int makeFsFat(const FormatParams ¶ms, BufferedFsWriter &dev, const std::stri if(bytesPerSec > 512) { tmpOffset = curOffset + bytesPerSec - 2; - res = dev.fillAndWrite(reinterpret_cast(&vbr.sigWord), tmpOffset, 2); + res = dev.fillAndWrite(reinterpret_cast(&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(&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(&vbr.sigWord), tmpOffset, 2); + res = dev.fillAndWrite(reinterpret_cast(&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(&vbr), tmpOffset, sizeof(Vbr)); + res = dev.fillAndWrite(reinterpret_cast(&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(&vbr.sigWord), tmpOffset, 2); + res = dev.fillAndWrite(reinterpret_cast(&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(&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(&vbr.sigWord), tmpOffset, 2); + res = dev.fillAndWrite(reinterpret_cast(&bs.sigWord), tmpOffset, 2); if(res != 0) return res; } @@ -258,14 +252,15 @@ int makeFsFat(const FormatParams ¶ms, 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; } @@ -284,7 +279,7 @@ int makeFsFat(const FormatParams ¶ms, 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(&dir), curOffset, sizeof(FatDir)); diff --git a/source/format.cpp b/source/format.cpp index 3dc7dd7..17aa98d 100644 --- a/source/format.cpp +++ b/source/format.cpp @@ -118,8 +118,23 @@ static bool getFormatParams(const u64 totSec, const ArgFlags flags, FormatParams return false; } + // Before doing more checks based on maxClus actually check maxClus. + // fatgen103.doc: Less than 4085 is FAT12. Less than 65525 is FAT16. Otherwise FAT32. + // mkfs.fat: Up to 4084 is FAT12. 4087-65524 is FAT16. 65525-268435446 is FAT32. + // (Win) fastfat.sys, (Linux) msdos.ko/vfat.ko detect FAT32 when fatSz16 is set to zero. + // Note: mkfs uses different values because of many FAT drivers with off by X bugs. const u32 maxClus = params.maxClus; - if(params.secPerFat * bytesPerSec / (fatBits / 8) < maxClus) + if((fatBits == 12 && maxClus > FAT12_MAX_CLUS) || + (fatBits == 16 && (maxClus < 4087u || maxClus > FAT16_MAX_CLUS)) || + (fatBits == 32 && (maxClus < 65525u || maxClus > FAT32_MAX_CLUS))) + { + fputs("Error: Invalid number of clusters for FAT variant.\n", stderr); + return false; + } + + // This can be a warning since having less allocatable clusters is actually fine. + // However if we get less clusters something probably went wrong while calculating. + if(params.secPerFat * bytesPerSec / (fatBits / 8) < maxClus + 2) // Plus 2 reserved entries. { fputs("Error: FAT doesn't contain enough entries to allocate all clusters.\n", stderr); return false; @@ -129,24 +144,12 @@ static bool getFormatParams(const u64 totSec, const ArgFlags flags, FormatParams ((32 * (fatBits < 32 ? 512 : 0) + bytesPerSec - 1) / bytesPerSec); if(params.fsAreaSize != calcFsArea) { - fputs("Error: Filesystem area smaller than reserved sectors + FATs.\n", stderr); + fputs("Error: Filesystem area smaller than reserved sectors + FATs + root entries.\n", stderr); return false; } - /*if(params.fsAreaSize > params.alignment) - fputs("Warning: Filesystem area overlaps with data area. May reduce performance and lifetime.\n", stderr);*/ - - // fatgen103.doc: Less than 4085 is FAT12. Less than 65525 is FAT16. Otherwise FAT32. - // mkfs.fat: Up to 4084 is FAT12. 4087-65524 is FAT16. 65525-268435444 is FAT32. - // (Win) fastfat.sys, (Linux) msdos.ko/vfat.ko detect FAT32 when fatSz16 is set to zero. - // Note: mkfs uses different values because of many FAT drivers with off by X bugs. - const u32 upperBound = 0x0FFFFFF4u & (0xFFFFFFFFu>>(32 - fatBits)); // 0xFF4, 0xFFF4 and 0x0FFFFFF4. - if((fatBits == 12 && maxClus > 4084u) || (fatBits == 16 && maxClus < 4087u) || - (fatBits == 32 && maxClus < 65525u) || maxClus > upperBound) - { - fputs("Error: Too few/many clusters for FAT variant.\n", stderr); - return false; - } + //if(params.fsAreaSize > params.alignment) + // fputs("Warning: Filesystem area overlaps with data area. May reduce performance and lifetime.\n", stderr); } // TODO: exFAT checks.