source: mainline/tools/mkfat.py@ 77578e8

Last change on this file since 77578e8 was 27dd0e6, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 3 years ago

update files with python3 shebang

  • Property mode set to 100755
File size: 14.2 KB
Line 
1#!/usr/bin/env python3
2#
3# SPDX-FileCopyrightText: 2008 Martin Decky
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8"""
9FAT creator
10"""
11
12import sys
13import os
14import random
15import xstruct
16import array
17from imgutil import *
18
19def subtree_size(root, cluster_size, dirent_size):
20 "Recursive directory walk and calculate size"
21
22 size = 0
23 files = 2
24
25 for item in listdir_items(root):
26 if item.is_file:
27 size += align_up(item.size, cluster_size)
28 files += 1
29 elif item.is_dir:
30 size += subtree_size(item.path, cluster_size, dirent_size)
31 files += 1
32
33 return size + align_up(files * dirent_size, cluster_size)
34
35def root_entries(root):
36 "Return number of root directory entries"
37
38 return len(os.listdir(root))
39
40def write_file(item, outf, cluster_size, data_start, fat, reserved_clusters):
41 "Store the contents of a file"
42
43 prev = -1
44 first = 0
45
46 for data in chunks(item, cluster_size):
47 empty_cluster = fat.index(0)
48 fat[empty_cluster] = 0xffff
49
50 if (prev != -1):
51 fat[prev] = empty_cluster
52 else:
53 first = empty_cluster
54
55 prev = empty_cluster
56
57 outf.seek(data_start + (empty_cluster - reserved_clusters) * cluster_size)
58 outf.write(data)
59
60 return first, item.size
61
62def write_directory(directory, outf, cluster_size, data_start, fat, reserved_clusters, dirent_size, empty_cluster):
63 "Store the contents of a directory"
64
65 length = len(directory)
66 size = length * dirent_size
67 prev = -1
68 first = 0
69
70 i = 0
71 rd = 0;
72 while (rd < size):
73 if (prev != -1):
74 empty_cluster = fat.index(0)
75 fat[empty_cluster] = 0xffff
76 fat[prev] = empty_cluster
77 else:
78 first = empty_cluster
79
80 prev = empty_cluster
81
82 data = bytes()
83 data_len = 0
84 while ((i < length) and (data_len < cluster_size)):
85 if (i == 0):
86 directory[i].cluster = empty_cluster
87
88 data += directory[i].pack()
89 data_len += dirent_size
90 i += 1
91
92 outf.seek(data_start + (empty_cluster - reserved_clusters) * cluster_size)
93 outf.write(data)
94 rd += len(data)
95
96 return first, size
97
98DIR_ENTRY = """little:
99 char name[8] /* file name */
100 char ext[3] /* file extension */
101 uint8_t attr /* file attributes */
102 uint8_t lcase /* file name case (NT extension) */
103 uint8_t ctime_fine /* create time (fine resolution) */
104 uint16_t ctime /* create time */
105 uint16_t cdate /* create date */
106 uint16_t adate /* access date */
107 padding[2] /* EA-index */
108 uint16_t mtime /* modification time */
109 uint16_t mdate /* modification date */
110 uint16_t cluster /* first cluster */
111 uint32_t size /* file size */
112"""
113
114DOT_DIR_ENTRY = """little:
115 uint8_t signature /* 0x2e signature */
116 char name[7] /* empty */
117 char ext[3] /* empty */
118 uint8_t attr /* file attributes */
119 padding[1] /* reserved for NT */
120 uint8_t ctime_fine /* create time (fine resolution) */
121 uint16_t ctime /* create time */
122 uint16_t cdate /* create date */
123 uint16_t adate /* access date */
124 padding[2] /* EA-index */
125 uint16_t mtime /* modification time */
126 uint16_t mdate /* modification date */
127 uint16_t cluster /* first cluster */
128 uint32_t size /* file size */
129"""
130
131DOTDOT_DIR_ENTRY = """little:
132 uint8_t signature[2] /* 0x2e signature */
133 char name[6] /* empty */
134 char ext[3] /* empty */
135 uint8_t attr /* file attributes */
136 padding[1] /* reserved for NT */
137 uint8_t ctime_fine /* create time (fine resolution) */
138 uint16_t ctime /* create time */
139 uint16_t cdate /* create date */
140 uint16_t adate /* access date */
141 padding[2] /* EA-index */
142 uint16_t mtime /* modification time */
143 uint16_t mdate /* modification date */
144 uint16_t cluster /* first cluster */
145 uint32_t size /* file size */
146"""
147
148LFN_DIR_ENTRY = """little:
149 uint8_t seq /* sequence number */
150 char name1[10] /* first part of the name */
151 uint8_t attr /* attributes */
152 uint8_t rec_type /* LFN record type */
153 uint8_t checksum /* LFN checksum */
154 char name2[12] /* second part of the name */
155 uint16_t cluster /* cluster */
156 char name3[4] /* third part of the name */
157"""
158
159lchars = set(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
160 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
161 'U', 'V', 'W', 'X', 'Y', 'Z',
162 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
163 '!', '#', '$', '%', '&', '\'', '(', ')', '-', '@',
164 '^', '_', '`', '{', '}', '~', '.'])
165
166def fat_lchars(name):
167 "Filter FAT legal characters"
168
169 filtered_name = b''
170 filtered = False
171
172 for char in name.encode('ascii', 'replace').upper():
173 if chr(char) in lchars:
174 filtered_name += str.encode(chr(char))
175 else:
176 filtered_name += b'_'
177 filtered = True
178
179 return (filtered_name, filtered)
180
181def fat_name83(name, name83_list):
182 "Create a 8.3 name for the given name"
183
184 ascii_name, lfn = fat_lchars(name)
185 # Splitting works only on strings, not on bytes
186 ascii_parts = ascii_name.decode('utf8').split('.')
187
188 short_name = ''
189 short_ext = ''
190
191 if len(ascii_name) > 11:
192 lfn = True
193
194 if len(ascii_parts) > 0:
195 short_name = ascii_parts[0]
196 if len(short_name) > 8:
197 lfn = True
198
199 if len(ascii_parts) > 1:
200 short_ext = ascii_parts[-1]
201 if len(short_ext) > 3:
202 lfn = True
203
204 if len(ascii_parts) > 2:
205 lfn = True
206
207 if lfn == False:
208 name83_list.append(short_name + '.' + short_ext)
209 return (short_name.ljust(8)[0:8], short_ext.ljust(3)[0:3], False)
210
211 # For filenames with multiple extensions, we treat the last one
212 # as the actual extension. The rest of the filename is stripped
213 # of dots and concatenated to form the short name
214 for part in ascii_parts[1:-1]:
215 short_name += part
216
217 for number in range(1, 999999):
218 number_str = ('~' + str(number)).upper()
219
220 if len(short_name) + len(number_str) > 8:
221 short_name = short_name[0:8 - len(number_str)]
222
223 short_name += number_str;
224
225 if not (short_name + '.' + short_ext) in name83_list:
226 break
227
228 name83_list.append(short_name + '.' + short_ext)
229 return (short_name.ljust(8)[0:8], short_ext.ljust(3)[0:3], True)
230
231def create_lfn_dirent(name, seq, checksum):
232 "Create LFN directory entry"
233
234 entry = xstruct.create(LFN_DIR_ENTRY)
235 name_rest = name[26:]
236
237 if len(name_rest) > 0:
238 entry.seq = seq
239 else:
240 entry.seq = seq | 0x40
241
242 entry.name1 = name[0:10]
243 entry.name2 = name[10:22]
244 entry.name3 = name[22:26]
245
246 entry.attr = 0x0F
247 entry.rec_type = 0
248 entry.checksum = checksum
249 entry.cluster = 0
250
251 return (entry, name_rest)
252
253def lfn_checksum(name):
254 "Calculate LFN checksum"
255
256 checksum = 0
257 for i in range(0, 11):
258 checksum = (((checksum & 1) << 7) + (checksum >> 1) + ord(name[i])) & 0xFF
259
260 return checksum
261
262def create_dirent(name, name83_list, directory, cluster, size):
263 short_name, short_ext, lfn = fat_name83(name, name83_list)
264
265 dir_entry = xstruct.create(DIR_ENTRY)
266
267 dir_entry.name = short_name
268 dir_entry.ext = short_ext
269
270 if (directory):
271 dir_entry.attr = 0x30
272 else:
273 dir_entry.attr = 0x20
274
275 dir_entry.lcase = 0x18
276 dir_entry.ctime_fine = 0 # FIXME
277 dir_entry.ctime = 0 # FIXME
278 dir_entry.cdate = 0 # FIXME
279 dir_entry.adate = 0 # FIXME
280 dir_entry.mtime = 0 # FIXME
281 dir_entry.mdate = 0 # FIXME
282 dir_entry.cluster = cluster
283
284 if (directory):
285 dir_entry.size = 0
286 else:
287 dir_entry.size = size
288
289 if not lfn:
290 return [dir_entry]
291
292 long_name = name.encode('utf_16_le')
293 entries = [dir_entry]
294
295 seq = 1
296 checksum = lfn_checksum(dir_entry.name + dir_entry.ext)
297
298 while len(long_name) > 0:
299 long_entry, long_name = create_lfn_dirent(long_name, seq, checksum)
300 entries.append(long_entry)
301 seq += 1
302
303 entries.reverse()
304 return entries
305
306def create_dot_dirent(empty_cluster):
307 dir_entry = xstruct.create(DOT_DIR_ENTRY)
308
309 dir_entry.signature = 0x2e
310 dir_entry.name = b' '
311 dir_entry.ext = b' '
312 dir_entry.attr = 0x10
313
314 dir_entry.ctime_fine = 0 # FIXME
315 dir_entry.ctime = 0 # FIXME
316 dir_entry.cdate = 0 # FIXME
317 dir_entry.adate = 0 # FIXME
318 dir_entry.mtime = 0 # FIXME
319 dir_entry.mdate = 0 # FIXME
320 dir_entry.cluster = empty_cluster
321 dir_entry.size = 0
322
323 return dir_entry
324
325def create_dotdot_dirent(parent_cluster):
326 dir_entry = xstruct.create(DOTDOT_DIR_ENTRY)
327
328 dir_entry.signature = [0x2e, 0x2e]
329 dir_entry.name = b' '
330 dir_entry.ext = b' '
331 dir_entry.attr = 0x10
332
333 dir_entry.ctime_fine = 0 # FIXME
334 dir_entry.ctime = 0 # FIXME
335 dir_entry.cdate = 0 # FIXME
336 dir_entry.adate = 0 # FIXME
337 dir_entry.mtime = 0 # FIXME
338 dir_entry.mdate = 0 # FIXME
339 dir_entry.cluster = parent_cluster
340 dir_entry.size = 0
341
342 return dir_entry
343
344def recursion(head, root, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, parent_cluster):
345 "Recursive directory walk"
346
347 directory = []
348 name83_list = []
349
350 if not head:
351 # Directory cluster preallocation
352 empty_cluster = fat.index(0)
353 fat[empty_cluster] = 0xFFFF
354
355 directory.append(create_dot_dirent(empty_cluster))
356 directory.append(create_dotdot_dirent(parent_cluster))
357 else:
358 empty_cluster = 0
359
360 for item in listdir_items(root):
361 if item.is_file:
362 rv = write_file(item, outf, cluster_size, data_start, fat, reserved_clusters)
363 directory.extend(create_dirent(item.name, name83_list, False, rv[0], rv[1]))
364 elif item.is_dir:
365 rv = recursion(False, item.path, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
366 directory.extend(create_dirent(item.name, name83_list, True, rv[0], rv[1]))
367
368 if head:
369 outf.seek(root_start)
370 for dir_entry in directory:
371 outf.write(dir_entry.pack())
372 else:
373 return write_directory(directory, outf, cluster_size, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
374
375BOOT_SECTOR = """little:
376 uint8_t jmp[3] /* jump instruction */
377 char oem[8] /* OEM string */
378 uint16_t sector /* bytes per sector */
379 uint8_t cluster /* sectors per cluster */
380 uint16_t reserved /* reserved sectors */
381 uint8_t fats /* number of FATs */
382 uint16_t rootdir /* root directory entries */
383 uint16_t sectors /* total number of sectors */
384 uint8_t descriptor /* media descriptor */
385 uint16_t fat_sectors /* sectors per single FAT */
386 uint16_t track_sectors /* sectors per track */
387 uint16_t heads /* number of heads */
388 uint32_t hidden /* hidden sectors */
389 uint32_t sectors_big /* total number of sectors (if sectors == 0) */
390
391 /* Extended BIOS Parameter Block */
392 uint8_t drive /* physical drive number */
393 padding[1] /* reserved (current head) */
394 uint8_t extboot_signature /* extended boot signature */
395 uint32_t serial /* serial number */
396 char label[11] /* volume label */
397 char fstype[8] /* filesystem type */
398 padding[448] /* boot code */
399 uint8_t boot_signature[2] /* boot signature */
400"""
401
402EMPTY_SECTOR = """little:
403 padding[512] /* empty sector data */
404"""
405
406FAT_ENTRY = """little:
407 uint16_t next /* FAT16 entry */
408"""
409
410def usage(prname):
411 "Print usage syntax"
412 print(prname + " <EXTRA_BYTES> <PATH> <IMAGE>")
413
414def main():
415 if (len(sys.argv) < 4):
416 usage(sys.argv[0])
417 return
418
419 if (not sys.argv[1].isdigit()):
420 print("<EXTRA_BYTES> must be a number")
421 return
422
423 extra_bytes = int(sys.argv[1])
424
425 path = os.path.abspath(sys.argv[2])
426 if (not os.path.isdir(path)):
427 print("<PATH> must be a directory")
428 return
429
430 fat16_clusters = 4096
431
432 sector_size = 512
433 cluster_size = 4096
434 dirent_size = 32
435 fatent_size = 2
436 fat_count = 2
437 reserved_clusters = 2
438
439 # Make sure the filesystem is large enough for FAT16
440 size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size + extra_bytes
441 while (size // cluster_size < fat16_clusters):
442 if (cluster_size > sector_size):
443 cluster_size = cluster_size // 2
444 size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size + extra_bytes
445 else:
446 size = fat16_clusters * cluster_size + reserved_clusters * cluster_size
447
448 root_size = align_up(root_entries(path) * dirent_size, cluster_size)
449
450 fat_size = align_up(align_up(size, cluster_size) // cluster_size * fatent_size, sector_size)
451
452 sectors = (cluster_size + fat_count * fat_size + root_size + size) // sector_size
453 root_start = cluster_size + fat_count * fat_size
454 data_start = root_start + root_size
455
456 outf = open(sys.argv[3], "wb")
457
458 boot_sector = xstruct.create(BOOT_SECTOR)
459 boot_sector.jmp = [0xEB, 0x3C, 0x90]
460 boot_sector.oem = b'MSDOS5.0'
461 boot_sector.sector = sector_size
462 boot_sector.cluster = cluster_size // sector_size
463 boot_sector.reserved = cluster_size // sector_size
464 boot_sector.fats = fat_count
465 boot_sector.rootdir = root_size // dirent_size
466 if (sectors <= 65535):
467 boot_sector.sectors = sectors
468 else:
469 boot_sector.sectors = 0
470 boot_sector.descriptor = 0xF8
471 boot_sector.fat_sectors = fat_size // sector_size
472 boot_sector.track_sectors = 63
473 boot_sector.heads = 6
474 boot_sector.hidden = 0
475 if (sectors > 65535):
476 boot_sector.sectors_big = sectors
477 else:
478 boot_sector.sectors_big = 0
479
480 boot_sector.drive = 0x80
481 boot_sector.extboot_signature = 0x29
482 boot_sector.serial = random.randint(0, 0x7fffffff)
483 boot_sector.label = b'HELENOS'
484 boot_sector.fstype = b'FAT16 '
485 boot_sector.boot_signature = [0x55, 0xAA]
486
487 outf.write(boot_sector.pack())
488
489 empty_sector = xstruct.create(EMPTY_SECTOR)
490
491 # Reserved sectors
492 for i in range(1, cluster_size // sector_size):
493 outf.write(empty_sector.pack())
494
495 # FAT tables
496 for i in range(0, fat_count):
497 for j in range(0, fat_size // sector_size):
498 outf.write(empty_sector.pack())
499
500 # Root directory
501 for i in range(0, root_size // sector_size):
502 outf.write(empty_sector.pack())
503
504 # Data
505 for i in range(0, size // sector_size):
506 outf.write(empty_sector.pack())
507
508 fat = array.array('L', [0] * (fat_size // fatent_size))
509 fat[0] = 0xfff8
510 fat[1] = 0xffff
511
512 recursion(True, path, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, 0)
513
514 # Store FAT
515 fat_entry = xstruct.create(FAT_ENTRY)
516 for i in range(0, fat_count):
517 outf.seek(cluster_size + i * fat_size)
518 for j in range(0, fat_size // fatent_size):
519 fat_entry.next = fat[j]
520 outf.write(fat_entry.pack())
521
522 outf.close()
523
524if __name__ == '__main__':
525 main()
Note: See TracBrowser for help on using the repository browser.