Skip to content

Commit

Permalink
better *_mkfs code
Browse files Browse the repository at this point in the history
- clusters computation is now greatly faster in many scenarios
- more accurate floppy media byte detection
- mkfat --fat-copies can set the number of FAT tables to write
  • Loading branch information
maxpat78 committed Jun 26, 2023
1 parent 0c0edbf commit 0ad50fd
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 26 deletions.
4 changes: 2 additions & 2 deletions FATtools/FAT.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class boot_fat32(object):
0x34: ('chReserved', '12s'),
0x40: ('chPhysDriveNumber', 'B'),
0x41: ('chFlags', 'B'),
0x42: ('chExtBootSignature', 'B'),
0x42: ('chExtBootSignature', 'B'), # 0x28 or 0x29 (zero if following id, label and FS type absent)
0x43: ('dwVolumeID', '<I'),
0x47: ('sVolumeLabel', '11s'),
0x52: ('sFSType', '8s'),
Expand Down Expand Up @@ -161,7 +161,7 @@ class boot_fat16(object):
0x20: ('dwTotalSectors', '<I'),
0x24: ('chPhysDriveNumber', 'B'),
0x25: ('uchCurrentHead', 'B'), # unused
0x26: ('uchSignature', 'B'), # 0x28 or 0x29
0x26: ('uchSignature', 'B'), # 0x28 or 0x29 (zero if following id, label and FS type absent)
0x27: ('dwVolumeID', '<I'),
0x2B: ('sVolumeLabel', '11s'),
0x36: ('sFSType', '8s'),
Expand Down
2 changes: 1 addition & 1 deletion FATtools/exFAT.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class boot_exfat(object):
0x03: ('chOemID', '8s'),
0x0B: ('chDummy', '53s'),
0x40: ('u64PartOffset', '<Q'),
0x48: ('u64VolumeLength', '<Q'), # sectors
0x48: ('u64VolumeLength', '<Q'), # length of partition (sectors) where FS is applied (unlike FAT)
0x50: ('dwFATOffset', '<I'), # sectors
0x54: ('dwFATLength', '<I'), # sectors
0x58: ('dwDataRegionOffset', '<I'), # sectors
Expand Down
50 changes: 28 additions & 22 deletions FATtools/mkfat.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
-3 bad volume size (160K < size < 2T [16T with 4K sectors])
-4 no possible format with current parameters (try exFAT?)
-5 specified FAT type can't be applied
-6 specified cluster can't be applied """
-6 specified cluster can't be applied
-7 zero FAT copies """

def fat_mkfs(stream, size, sector=512, params={}):
"Creates a FAT 12/16/32 File System on stream. Returns 0 for success."
Expand All @@ -119,6 +120,7 @@ def fat_mkfs(stream, size, sector=512, params={}):
return -3

fat_copies = params.get('fat_copies', 2) # default: best setting
if fat_copies < 1: return -7
reserved_clusters = 11 # (0,1, ..F7h-..FFh are always reserved)
if params.get('fat_ff0_reserved') or sectors < 5761: # if floppy or requested
reserved_clusters = 18
Expand All @@ -140,16 +142,17 @@ def fat_mkfs(stream, size, sector=512, params={}):
rreserved_size = reserved_size + root_entries_size # in FAT12/16 this space resides outside the cluster area
cluster_size = (2**i)
clusters = (size - rreserved_size) // cluster_size
while 1:
fat_size = ((fat_slot_size*(clusters+2))//8+sector-1)//sector * sector # FAT sectors according to slot size (12, 16 or 32 bit)
required_size = cluster_size*clusters + fat_copies*fat_size + rreserved_size
if required_size <= size: break
clusters -= 1
if clusters > (2**fat_slot_size)-reserved_clusters: continue # increase cluster size
if clusters > (2**fat_slot_size)-reserved_clusters: continue # too many clusters, increase size
if fat_slot_size == 32:
if clusters > (2**28)-reserved_clusters: continue # FAT32 uses 28 bits only
if params.get('fat32_forbids_low_clusters') and clusters < 65526: continue
if params.get('fat32_forbids_high_clusters') and clusters > 4177917: continue
while 1:
fat_size = ((fat_slot_size*(clusters+2))//8+sector-1)//sector * sector # FAT sectors according to slot size (12, 16 or 32 bit)
required_size = cluster_size*clusters + fat_copies*fat_size + rreserved_size
if required_size <= size or clusters==0: break
clusters -= 1
if not clusters: continue
fsinfo['required_size'] = required_size # space occupied by FS
fsinfo['reserved_size'] = reserved_size # space reserved before FAT#1
fsinfo['cluster_size'] = cluster_size
Expand Down Expand Up @@ -190,9 +193,8 @@ def fat_mkfs(stream, size, sector=512, params={}):
allowed = fat_fs[fat_bits]
K = list(allowed.keys())
i = len(K) // 2
if i < 0: i=0
fsinfo = allowed[K[i]]
if verbose: print("Selected %d bytes cluster." % K[i])
if verbose: print("%dK cluster selected." % (int(K[i])/1024.0))

if not params.get('fat_bits') and verbose: print("Selected FAT%d file system."%fat_bits)
params['fat_bits'] = fat_bits
Expand All @@ -215,14 +217,14 @@ def fat_mkfs(stream, size, sector=512, params={}):
boot.uchFATCopies = fat_copies
boot.wMaxRootEntries = fsinfo['root_entries'] # fixed root (not used in FAT32)
if fat_bits == 12 and sectors < 5761:
boot.uchMediaDescriptor = 0xF0 # typical floppy (also F9h)
boot.uchMediaDescriptor = utils.get_media(size)
boot.dwHiddenSectors = 0 # assume NOT partitioned
else:
boot.uchMediaDescriptor = 0xF8 # HDD
if sectors < 65536:
boot.wTotalSectors = sectors
boot.wTotalSectors = fsinfo['required_size']//sector # effective sectors occupied by FAT Volume
else:
boot.dwTotalSectors = sectors
boot.dwTotalSectors = fsinfo['required_size']//sector
if fat_bits != 32:
boot.wSectorsPerFAT = fsinfo['fat_size']//sector
else:
Expand Down Expand Up @@ -250,13 +252,13 @@ def fat_mkfs(stream, size, sector=512, params={}):
boot.wFSISector = 1
boot.wBootCopySector = params.get('fat32_backup_sector', 6)

boot.pack()
buf = boot.pack()
#~ print(boot)
#~ print('FAT, root, cluster #2 offsets', hex(boot.fat()), hex(boot.fat(1)), hex(boot.root()), hex(boot.dataoffs))

stream.seek(0)
# Write boot sector
stream.write(boot.pack())
stream.write(buf)

if fat_bits == 32:
fsi = fat32_fsinfo(offset=sector)
Expand All @@ -270,14 +272,21 @@ def fat_mkfs(stream, size, sector=512, params={}):
# Write backup copies of Boot and FSI
if boot.wBootCopySector:
stream.seek(boot.wBootCopySector*boot.wBytesPerSector)
stream.write(boot.pack())
stream.write(buf)
stream.write(fsi.pack())

# Blank FAT1&2 area
stream.seek(boot.fat())
blank = bytearray(boot.wBytesPerSector)
for i in range(boot.wSectorsPerFAT*2):
stream.write(blank)
if 0:
to_blank = boot.uchFATCopies * boot.wSectorsPerFAT * boot.wBytesPerSector
blank = bytearray(2<<20)
while to_blank:
n = min(2<<20, to_blank)
stream.write(blank[:n])
to_blank -= n
# Initializes FAT1...
if fat_bits == 12:
clus_0_2 = b'%c'%boot.uchMediaDescriptor + b'\xFF\xFF'
Expand Down Expand Up @@ -465,15 +474,12 @@ def exfat_mkfs(stream, size, sector=512, params={}):
fsinfo = {}
cluster_size = (2**i)
clusters = (size - reserved_size) // cluster_size
# cluster_size increase? FORMAT seems to reserve more space than minimum
if clusters > 0xFFFFFFF6: continue
fat_size = (4*(clusters+2)+sector-1)//sector * sector
# round it to cluster_size, or memory page size or something?
fat_size = (fat_size+cluster_size-1)//cluster_size * cluster_size
required_size = cluster_size*clusters + fat_copies*fat_size + reserved_size + dataregion_padding
while required_size > size:
clusters -= 1
clusters -= (required_size-size+cluster_size-1)//cluster_size
fat_size = (4*(clusters+2)+sector-1)//sector * sector
fat_size = (fat_size+cluster_size-1)//cluster_size * cluster_size
required_size = cluster_size*clusters + fat_copies*fat_size + reserved_size + dataregion_padding
if clusters < 1 or clusters > 0xFFFFFFF6: continue
fsinfo['required_size'] = required_size # space occupied by FS
Expand Down Expand Up @@ -515,8 +521,8 @@ def exfat_mkfs(stream, size, sector=512, params={}):
boot.u64PartOffset = 0x800
boot.u64VolumeLength = sectors
# We can put FAT far away from reserved area, if we want...
boot.dwFATOffset = (reserved_size+sector-1)//sector
boot.dwFATLength = (fsinfo['fat_size']+sector-1)//sector
boot.dwFATOffset = reserved_size//sector
boot.dwFATLength = fsinfo['fat_size']//sector
# Again, we can put clusters heap far away from usual
boot.dwDataRegionOffset = boot.dwFATOffset + boot.dwFATLength + dataregion_padding
boot.dwDataRegionLength = fsinfo['clusters']
Expand Down
4 changes: 4 additions & 0 deletions FATtools/scripts/mkfat.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def create_parser(parser_create_fn=argparse.ArgumentParser,parser_create_args=No
par.add_argument("-t", "--fstype", dest="fs_type", help="try to apply the specified File System between FAT12, FAT16, FAT32 or EXFAT. Default: based on medium size.", metavar="FSTYPE")
par.add_argument("-c", "--cluster", dest="cluster_size", help="force a specified cluster size between 512, 1024, 2048, 4096, 8192, 16384, 32768 (since DOS) or 65536 bytes (Windows NT+; 128K and 256K clusters are allowed with 4K sectors) for FAT. exFAT permits clusters up to 32M. Default: based on medium size. Accepts 'k' and 'm' postfix for Kibibytes and Mebibytes.", metavar="CLUSTER")
par.add_argument("-p", "--partition", dest="part_type", help="create a single partition from all disk space before formatting. Accepts MBR (up to 2 TB; 16 TB with 4K sectors), GPT or MBR_OLD (2 GB max, MS-DOS <7.1 compatible)", metavar="PARTTYPE")
par.add_argument("--fat-copies", dest="fat_copies", help="set the number of FAT tables (default: 2)", metavar="COPIES")
par.add_argument("--fat32compat", action="store_true", dest="fat32_compat", help="FAT32 is applied in Windows XP compatibility mode, i.e. only if 65525 < clusters < 4177918 (otherwise: 2^28-11 clusters allowed)")
par.add_argument("--no-fat12", action="store_true", dest="fat12_disable", help="FAT12 is never applied to small hard disks (~127/254M on DOS/NT systems)")
par.add_argument("--no-64k-cluster", action="store_true", dest="disable_64k", help="cluster size is limited to 32K (DOS compatibility)")
Expand Down Expand Up @@ -106,6 +107,9 @@ def call(args):
if args.disable_64k:
params['fat_no_64K_cluster'] = 1

if args.fat_copies:
params['fat_copies'] = int(args.fat_copies)

params['show_info'] = 1
ret = format(dsk, dsk.size, SECTOR, params)
if ret != 0:
Expand Down
17 changes: 17 additions & 0 deletions FATtools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ def FSguess(boot):
return 'FAT12'
return 'FAT16'

def get_media(size):
"Returns the media id byte for a given floppy size or 0xF0 if not found"
mids = {
320: 0xFE, # 5.25" DS/DD 160KB
360: 0xFC, # 5.25" DS/DD 180KB
640: 0xFA, # 3.5" DS/DD 320KB
720: 0xFD, # 3.5" DS/DD 360KB
1280: 0xFB, # 3.5" DS/DD 640KB
1440: 0xF9, # 3.5" DS/DD 720KB
2400: 0xF9, # 5.25" DS/HD 1200KB
2880: 0xF0, # 3.5" DS/HD 1440KB
3360: 0xF0, # 3.5" DS/HD 1680KB (MS-DMF)
3440: 0xF0, # 3.5" DS/HD 1720KB
5760: 0xF0 # 3.5" DS/XD 2880KB
}
return mids.get(size // sector, 0xF0)

def get_geometry(size, sector=512):
"Returns the CHS geometry that fits a disk size"
# Heads and Sectors Per track are always needed in a FAT boot sector.
Expand Down
2 changes: 1 addition & 1 deletion FATtools/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.0.22'
__version__ = '1.0.25'

0 comments on commit 0ad50fd

Please sign in to comment.