This is a function that checks if a valid FAT12 image would be created. Since all the information to create it is available on the "boot sector", this is the parameter. I wanted the code to be pretty readable, but I may have overdone it.
fat.h
/* This file defines some FAT structures, and functions for validating a FAT12 * objects. */ #ifndef YMFAT12_FAT_H #define YMFAT12_FAT_H #include <stdio.h> /* The names are similar to the specs. */ /* BIOS parameter block (BPB) */ struct fat_bpb { unsigned short bytsPerSec; unsigned char secPerClus; unsigned short rsvdSecCnt; unsigned char numFATs; unsigned short rootEntCnt; unsigned short totSec16; unsigned char media; unsigned short FATSz16; unsigned short secPerTrk; unsigned short numHeads; unsigned long hiddSec; unsigned long totSec32; }; /* Extended BPB (EBPB) */ struct fat_ebpb { unsigned char drvNum; unsigned char reserved1; unsigned char bootSig; unsigned char volID; char volLab[11]; char filSysType[8]; }; struct fat_bootsector { unsigned char jmpBoot[3]; char oemName[8]; struct fat_bpb bpb; struct fat_ebpb ebpb; unsigned char code[448]; unsigned char signature_word[2]; }; /* Returns whether the bootsector information would make a valid FAT12 image. * @param [in] bs The bootsector with the sizes of the FAT12 * objects, and volume information. * @return Whether the FAT12 image would be valid. */ bool check_bs(struct fat_bootsector *bs); #endif /* YMFAT12_FAT_H */
fat.c
/* Implements what is declared on "fat.h". * * The "robustness principle" is generally used here: * >> Be conservative in what you send, be liberal in what you accept * It is mostly broken when compatibility with MS-DOS would restrict the specs. * * The specification used was from "August 30 2005" on * "https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf" * (accessed 2024/01/28) * When Wikipedia is mentioned the following article is being mentioned: * "https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system" (accessed * 2024/01/28). * When OSDev is mentioned, the following article is being mentioned: * https://wiki.osdev.org/FAT (accessed 2024/01/28). * * Since values besides `char` can vary in length, the value size are checked * to be n-bytes long. This does assume 1 byte is 8-bits long, which is wild. */ #include "fat.h" #include <string.h> #include <limits.h> #include "error.h" #include "common.h" /* For the specs, a FAT is 12, 16 or 32 according to its number of clusters. * Different specs define different limits. The used spec defines 4084 for * FAT12, as mkfs.dos and Linux driver. The following limit was decided for the * following reasons. * 1. Cluster _values_ 0 and 1 are reserved, so 4096-2=4094 would be the limit. * 2. For Wikipedia, cluster values 0xFF0-0xFF5 are reserved or used, so * for portability the limit would be 4094-6=4088. * 3. For Wikipedia, cluster value 0xFF6 is reserved. Then the limit would be * 4088-1 = 4087. * 4. Cluster values 0xFF7-0xFFF have special meaning, so the limit would be * 4087-9=4078. * Q.E.D. */ #define FAT12_MAX_CLUSTERS 4078 static bool overflows3_add_ul( unsigned long n1, unsigned long n2, unsigned long n3 ) { if (n1 + n2 < n1) return false; return n1 + n2 + n3 < n1 + n2; } static inline bool overflows_mult_ul(unsigned long n1, unsigned long n2) { return n2 > ULONG_MAX / n1; } static inline bool is_16bit(unsigned short n) { return (n & 0xFFFF) == n; } static inline bool is_32bit(unsigned long n) { return (n & 0xFFFFFFFF) == n; } static inline bool is_lowercase_ascii(char n) { return n >= 0x61 && n <= 0x7A; } /* Whether is a valid 8.3 name */ static bool is_83name(const char *n) { const size_t sz = 11; size_t i; if (n[0] == 0x20) return false; for (i = 0; i < sz; i++) { if (is_lowercase_ascii(n[i])) return false; if (n[i] < 0x20) return false; if ( n[i] == 0x22 || n[i] == 0x2A || n[i] == 0x2B || n[i] == 0x2C || n[i] == 0x2E || n[i] == 0x2F || n[i] == 0x3A || n[i] == 0x3B || n[i] == 0x3C || n[i] == 0x3D || n[i] == 0x3E || n[i] == 0x3F || n[i] == 0x5B || n[i] == 0x5C || n[i] == 0x5D || n[i] == 0x7C) { return false; } } return true; } /* Checks if each field is within specs. */ static bool check_bs_fields(struct fat_bootsector *bs) { /* The function is long, but breaking it would not help, so I added * marks for what was being checked. */ /* jmpBoot */ /* For OSDev, Windows and OS X do not recognize the volume if the * bootjump does not follow the standard. */ if (bs->jmpBoot[0] == 0xEB) { if (bs->jmpBoot[2] != 0x90) return false; } else if (bs->jmpBoot[0] != 0xE9) return false; /* oemName does not need checking. It can be whatever. */ /* bytsPerSec */ if ( bs->bpb.bytsPerSec != 512 && bs->bpb.bytsPerSec != 1024 && bs->bpb.bytsPerSec != 2048 && bs->bpb.bytsPerSec != 4096) { return false; } /* secPerClus */ if ( bs->bpb.secPerClus != 1 && bs->bpb.secPerClus != 2 && bs->bpb.secPerClus != 4 && bs->bpb.secPerClus != 8 && bs->bpb.secPerClus != 16 && bs->bpb.secPerClus != 32 && bs->bpb.secPerClus != 64 && bs->bpb.secPerClus != 128) { return false; } /* rsvdSecCnt */ if (!is_16bit(bs->bpb.rsvdSecCnt)) return false; if (bs->bpb.rsvdSecCnt == 0) return false; /* numFATs */ /* Maybe only 1 and 2 values should be allowed. */ if (!is_16bit(bs->bpb.numFATs)) return false; if (bs->bpb.numFATs == 0) return false; /* rootEntCnt */ if (!is_16bit(bs->bpb.rootEntCnt)) return false; /* Specs does not exclude 0, but it is natural. */ if (bs->bpb.rootEntCnt == 0) return false; if ((long)bs->bpb.rootEntCnt * 32 % bs->bpb.bytsPerSec != 0) return false; /* totSec16 */ /* If it is 0, then totSec32 is used instead. */ if (!is_16bit(bs->bpb.totSec16)) return false; /* media */ /* Wikipedia asserts there is more. */ if ( bs->bpb.media != 0xF0 && bs->bpb.media != 0xF8 && bs->bpb.media != 0xF9 && bs->bpb.media != 0xFA && bs->bpb.media != 0xFB && bs->bpb.media != 0xFC && bs->bpb.media != 0xFD && bs->bpb.media != 0xFE && bs->bpb.media != 0xFF) { return false; } /* FATSz16 */ if (!is_16bit(bs->bpb.media)) return false; /* Specs does not exclude 0, but it is natural. */ if (bs->bpb.FATSz16 == 0) return false; /* secPerTrk */ if (!is_16bit(bs->bpb.secPerTrk)) return false; /* numHeads */ if (!is_16bit(bs->bpb.numHeads)) return false; /* Wikipedia says MS-DOS had a bug for CHS with 256 heads up to * including 7.1. So most BIOSes do not support more than 255 heads. */ if (bs->bpb.numHeads >= 256) return false; /* hiddSec */ if (!is_16bit(bs->bpb.hiddSec)) return false; /* totSec32 */ if (!is_32bit(bs->bpb.totSec32)) return false; if (bs->bpb.totSec32 == 0 && bs->bpb.totSec16 == 0) return false; /* drvNum */ if (bs->ebpb.drvNum != 0x00 && bs->ebpb.drvNum != 0x80) return false; /* reserved1 */ if (bs->ebpb.reserved1 != 0) return false; /* bootSig */ /* For Wikipedia, it can also be 0x28, in which case the following * fields are not used. */ if (bs->ebpb.bootSig != 0x29) return false; /* volID does not need checking. */ /* volLab */ /* The specs do not affirm its format, but the examples align with * 8.3 names, and that it should be same as the volume file. */ if (!is_83name(bs->ebpb.volLab)) return false; /* filSysType */ /* Since it is informational, it could be allowed to be whatever. */ if (memcmp(bs->ebpb.filSysType, "FAT12 ", 8) != 0) return false; /* signature_word */ /* For Wikipedia, MS-DOS <3.3 checked if it was 0x55 0xAA. */ if (bs->signature_word[0] != 0x55 || bs->signature_word[1] != 0xAA) return false; return true; } bool check_bs(struct fat_bootsector *bs) { unsigned long fatTot, res, fat, rDir, data; if (!check_bs_fields(bs)) return false; /* Some ugly overflow checks were needed. */ fatTot = bs->bpb.totSec16 == 0 ? bs->bpb.totSec32 : bs->bpb.totSec16; res = (unsigned long)bs->bpb.rsvdSecCnt * bs->bpb.bytsPerSec; fat = (unsigned long)bs->bpb.numFATs * bs->bpb.FATSz16; /* On section 4 of the spec, "The size of the FAT is limited to 6K * sectors on volumes formatted FAT12". */ if (fat > 6 * 1028) return false; if (overflows_mult_ul(fat, bs->bpb.bytsPerSec)) return false; fat *= bs->bpb.bytsPerSec; rDir = (unsigned long)bs->bpb.rootEntCnt * 32; if (res + fat + rDir > fatTot) return false; if (overflows3_add_ul(res, fat, rDir)) return false; data = fatTot - (res + fat + rDir); if (data > FAT12_MAX_CLUSTERS) return false; return true; }