source: mainline/tools/mkfat.py@ c4b0317

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c4b0317 was 87608a5, checked in by Martin Decky <martin@…>, 13 years ago

replace illegal 8+3 characters with underscore to be consistent with the HelenOS FAT driver

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