source: mainline/tools/mkfat.py@ 1170ea3c

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 1170ea3c was a9a6d8d, checked in by Vojtech Horky <vojtechhorky@…>, 14 years ago

Support for LFN in mkfat.py utility (#400)

mkfat.py is now able to handle long filenames. Support for filenames
with characters outside of base ASCII is still missing, though.

  • Property mode set to 100755
File size: 14.7 KB
RevLine 
[5749372]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#
[3c80f2b]29
[5749372]30"""
31FAT creator
32"""
33
34import sys
35import os
36import random
37import xstruct
[04619ba]38import array
[cc1a727]39from imgutil import *
[700eaa9]40
[04619ba]41def subtree_size(root, cluster_size, dirent_size):
[700eaa9]42 "Recursive directory walk and calculate size"
43
44 size = 0
[95b730c2]45 files = 2
[700eaa9]46
[cc1a727]47 for item in listdir_items(root):
48 if item.is_file:
49 size += align_up(item.size, cluster_size)
[700eaa9]50 files += 1
[cc1a727]51 elif item.is_dir:
52 size += subtree_size(item.path, cluster_size, dirent_size)
[700eaa9]53 files += 1
54
[04619ba]55 return size + align_up(files * dirent_size, cluster_size)
[700eaa9]56
57def root_entries(root):
58 "Return number of root directory entries"
59
60 return len(os.listdir(root))
61
[cc1a727]62def write_file(item, outf, cluster_size, data_start, fat, reserved_clusters):
[04619ba]63 "Store the contents of a file"
64
65 prev = -1
[95b730c2]66 first = 0
[04619ba]67
[cc1a727]68 for data in chunks(item, cluster_size):
[04619ba]69 empty_cluster = fat.index(0)
70 fat[empty_cluster] = 0xffff
[95b730c2]71
[04619ba]72 if (prev != -1):
73 fat[prev] = empty_cluster
74 else:
75 first = empty_cluster
76
77 prev = empty_cluster
78
[95b730c2]79 outf.seek(data_start + (empty_cluster - reserved_clusters) * cluster_size)
[04619ba]80 outf.write(data)
81
[cc1a727]82 return first, item.size
[04619ba]83
[95b730c2]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
[28f4adb]104 data = bytes()
[95b730c2]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
[04619ba]120DIR_ENTRY = """little:
121 char name[8] /* file name */
122 char ext[3] /* file extension */
123 uint8_t attr /* file attributes */
[a248234]124 uint8_t lcase /* file name case (NT extension) */
[04619ba]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
[95b730c2]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
[a9a6d8d]170LFN_ENTRY = """little:
171 uint8_t pos
172 uint16_t name1[5]
173 uint8_t attr
174 uint8_t type
175 uint8_t csum
176 uint16_t name2[6]
177 uint16_t fc
178 uint16_t name3[2]
179"""
180
181# Global variable to hold the file names in 8.3 format. Needed to
182# keep track of "number" when creating a short fname from a LFN.
183name83_list = []
184
185def name83(fname):
186 "Create a 8.3 name for the given fname"
187
[04619ba]188 # FIXME: filter illegal characters
[a9a6d8d]189 parts = fname.split('.')
[95b730c2]190
[a9a6d8d]191 name = ''
192 ext = ''
193 lfn = False
194
195 if len(fname) > 11 :
196 lfn = True
197
[042fbe0]198 if len(parts) > 0:
[a9a6d8d]199 name = parts[0]
200 if len(name) > 8 :
201 lfn = True
[04619ba]202
[a9a6d8d]203 if len(parts) > 1 :
204 ext = parts[-1]
205 if len(ext) > 3 :
206 lfn = True
207
208 if len(parts) > 2 :
209 lfn = True
210
211 if (lfn == False) :
212 return (name.ljust(8)[0:8], ext.ljust(3)[0:3], False)
213
214 # For filenames with multiple extensions, we treat the last one
215 # as the actual extension. The rest of the filename is stripped
216 # of dots and concatenated to form the short name
217 for _name in parts[1:-1]:
218 name = name + _name
219
220 global name83_list
221 for number in range(1, 10000) :
222 number_str = '~' + str(number)
223
224 if len(name) + len(number_str) > 8 :
225 name = name[0:8 - len(number_str)]
226
227 name = name + number_str;
228
229 if (name + ext) not in name83_list :
230 break
231
232 name83_list.append(name + ext)
233
234 return (name.ljust(8)[0:8], ext.ljust(3)[0:3], True)
235
236def get_utf16(name, l) :
237 "Create a int array out of a string which we can store in uint16_t arrays"
238
239 bs = [0xFFFF for i in range(l)]
240
241 for i in range(len(name)) :
242 bs[i] = ord(name[i])
[042fbe0]243
[a9a6d8d]244 if (len(name) < l) :
245 bs[len(name)] = 0;
[042fbe0]246
[a9a6d8d]247 return bs
248
249def create_lfn_entry((name, index)) :
250 entry = xstruct.create(LFN_ENTRY)
251
252 entry.name1 = get_utf16(name[0:5], 5)
253 entry.name2 = get_utf16(name[5:11], 6)
254 entry.name3 = get_utf16(name[11:13], 2)
255 entry.pos = index
256
257 entry.attr = 0xF
258 entry.fc = 0
259 entry.type = 0
260
261 return entry
[04619ba]262
263def create_dirent(name, directory, cluster, size):
264
[a9a6d8d]265 dir_entry = xstruct.create(DIR_ENTRY)
[04619ba]266
[a9a6d8d]267 dir_entry.name, dir_entry.ext, lfn = name83(name)
268
269 dir_entry.name = dir_entry.name.upper().encode('ascii')
270 dir_entry.ext = dir_entry.ext.upper().encode('ascii')
271
[04619ba]272 if (directory):
273 dir_entry.attr = 0x30
274 else:
275 dir_entry.attr = 0x20
276
[a248234]277 dir_entry.lcase = 0x18
[04619ba]278 dir_entry.ctime_fine = 0 # FIXME
279 dir_entry.ctime = 0 # FIXME
280 dir_entry.cdate = 0 # FIXME
281 dir_entry.adate = 0 # FIXME
282 dir_entry.mtime = 0 # FIXME
283 dir_entry.mdate = 0 # FIXME
284 dir_entry.cluster = cluster
[95b730c2]285
286 if (directory):
287 dir_entry.size = 0
288 else:
289 dir_entry.size = size
290
[a9a6d8d]291
292 if not lfn:
293 return [dir_entry]
294
295 n = len(name) / 13 + 1
296 names = [(name[i * 13: (i + 1) * 13 + 1], i + 1) for i in range(n)]
297
298 entries = sorted(map (create_lfn_entry, names), reverse = True, key = lambda e : e.pos)
299 entries[0].pos |= 0x40
300
301 fname11 = dir_entry.name + dir_entry.ext
302
303 csum = 0
304 for i in range(0, 11) :
305 csum = ((csum & 1) << 7) + (csum >> 1) + ord(fname11[i])
306 csum = csum & 0xFF
307
308 for e in entries :
309 e.csum = csum;
310
311 entries.append(dir_entry)
312
313 return entries
[95b730c2]314
315def create_dot_dirent(empty_cluster):
316 dir_entry = xstruct.create(DOT_DIR_ENTRY)
317
318 dir_entry.signature = 0x2e
[432f68a]319 dir_entry.name = b' '
320 dir_entry.ext = b' '
[95b730c2]321 dir_entry.attr = 0x10
322
323 dir_entry.ctime_fine = 0 # FIXME
324 dir_entry.ctime = 0 # FIXME
325 dir_entry.cdate = 0 # FIXME
326 dir_entry.adate = 0 # FIXME
327 dir_entry.mtime = 0 # FIXME
328 dir_entry.mdate = 0 # FIXME
329 dir_entry.cluster = empty_cluster
330 dir_entry.size = 0
331
332 return dir_entry
333
334def create_dotdot_dirent(parent_cluster):
335 dir_entry = xstruct.create(DOTDOT_DIR_ENTRY)
336
337 dir_entry.signature = [0x2e, 0x2e]
[432f68a]338 dir_entry.name = b' '
339 dir_entry.ext = b' '
[95b730c2]340 dir_entry.attr = 0x10
341
342 dir_entry.ctime_fine = 0 # FIXME
343 dir_entry.ctime = 0 # FIXME
344 dir_entry.cdate = 0 # FIXME
345 dir_entry.adate = 0 # FIXME
346 dir_entry.mtime = 0 # FIXME
347 dir_entry.mdate = 0 # FIXME
348 dir_entry.cluster = parent_cluster
349 dir_entry.size = 0
[04619ba]350
351 return dir_entry
352
[95b730c2]353def recursion(head, root, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, parent_cluster):
[04619ba]354 "Recursive directory walk"
355
356 directory = []
[95b730c2]357
358 if (not head):
359 # Directory cluster preallocation
360 empty_cluster = fat.index(0)
361 fat[empty_cluster] = 0xffff
362
363 directory.append(create_dot_dirent(empty_cluster))
364 directory.append(create_dotdot_dirent(parent_cluster))
365 else:
366 empty_cluster = 0
367
[cc1a727]368 for item in listdir_items(root):
369 if item.is_file:
370 rv = write_file(item, outf, cluster_size, data_start, fat, reserved_clusters)
[a9a6d8d]371 directory.extend(create_dirent(item.name, False, rv[0], rv[1]))
[cc1a727]372 elif item.is_dir:
373 rv = recursion(False, item.path, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
[a9a6d8d]374 directory.extend(create_dirent(item.name, True, rv[0], rv[1]))
[04619ba]375
376 if (head):
377 outf.seek(root_start)
378 for dir_entry in directory:
379 outf.write(dir_entry.pack())
[95b730c2]380 else:
381 return write_directory(directory, outf, cluster_size, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
[04619ba]382
[5749372]383BOOT_SECTOR = """little:
384 uint8_t jmp[3] /* jump instruction */
385 char oem[8] /* OEM string */
386 uint16_t sector /* bytes per sector */
387 uint8_t cluster /* sectors per cluster */
388 uint16_t reserved /* reserved sectors */
389 uint8_t fats /* number of FATs */
390 uint16_t rootdir /* root directory entries */
391 uint16_t sectors /* total number of sectors */
392 uint8_t descriptor /* media descriptor */
393 uint16_t fat_sectors /* sectors per single FAT */
394 uint16_t track_sectors /* sectors per track */
395 uint16_t heads /* number of heads */
396 uint32_t hidden /* hidden sectors */
397 uint32_t sectors_big /* total number of sectors (if sectors == 0) */
398
399 /* Extended BIOS Parameter Block */
400 uint8_t drive /* physical drive number */
401 padding[1] /* reserved (current head) */
402 uint8_t extboot_signature /* extended boot signature */
403 uint32_t serial /* serial number */
404 char label[11] /* volume label */
405 char fstype[8] /* filesystem type */
406 padding[448] /* boot code */
407 uint8_t boot_signature[2] /* boot signature */
408"""
409
[8bc6fcf]410EMPTY_SECTOR = """little:
[04619ba]411 padding[512] /* empty sector data */
412"""
413
414FAT_ENTRY = """little:
415 uint16_t next /* FAT16 entry */
[8bc6fcf]416"""
417
[5749372]418def usage(prname):
419 "Print usage syntax"
[28f4adb]420 print(prname + " <EXTRA_BYTES> <PATH> <IMAGE>")
[5749372]421
422def main():
[14f2100]423 if (len(sys.argv) < 4):
[5749372]424 usage(sys.argv[0])
425 return
426
[14f2100]427 if (not sys.argv[1].isdigit()):
[28f4adb]428 print("<EXTRA_BYTES> must be a number")
[14f2100]429 return
430
431 extra_bytes = int(sys.argv[1])
432
433 path = os.path.abspath(sys.argv[2])
[5749372]434 if (not os.path.isdir(path)):
[28f4adb]435 print("<PATH> must be a directory")
[5749372]436 return
437
[c4702798]438 fat16_clusters = 4096
[04619ba]439
[700eaa9]440 sector_size = 512
441 cluster_size = 4096
[04619ba]442 dirent_size = 32
443 fatent_size = 2
444 fat_count = 2
445 reserved_clusters = 2
446
[f4057f5]447 # Make sure the filesystem is large enough for FAT16
[14f2100]448 size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size + extra_bytes
[28f4adb]449 while (size // cluster_size < fat16_clusters):
[ab579fa]450 if (cluster_size > sector_size):
[28f4adb]451 cluster_size = cluster_size // 2
[14f2100]452 size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size + extra_bytes
[04619ba]453 else:
454 size = fat16_clusters * cluster_size + reserved_clusters * cluster_size
[700eaa9]455
[04619ba]456 root_size = align_up(root_entries(path) * dirent_size, cluster_size)
[700eaa9]457
[28f4adb]458 fat_size = align_up(align_up(size, cluster_size) // cluster_size * fatent_size, sector_size)
[04619ba]459
[28f4adb]460 sectors = (cluster_size + fat_count * fat_size + root_size + size) // sector_size
[04619ba]461 root_start = cluster_size + fat_count * fat_size
462 data_start = root_start + root_size
[700eaa9]463
[28f4adb]464 outf = open(sys.argv[3], "wb")
[5749372]465
466 boot_sector = xstruct.create(BOOT_SECTOR)
467 boot_sector.jmp = [0xEB, 0x3C, 0x90]
[28f4adb]468 boot_sector.oem = b'MSDOS5.0'
[700eaa9]469 boot_sector.sector = sector_size
[28f4adb]470 boot_sector.cluster = cluster_size // sector_size
471 boot_sector.reserved = cluster_size // sector_size
[04619ba]472 boot_sector.fats = fat_count
[28f4adb]473 boot_sector.rootdir = root_size // dirent_size
[de4a1cf]474 if (sectors <= 65535):
475 boot_sector.sectors = sectors
476 else:
477 boot_sector.sectors = 0
[5749372]478 boot_sector.descriptor = 0xF8
[28f4adb]479 boot_sector.fat_sectors = fat_size // sector_size
[700eaa9]480 boot_sector.track_sectors = 63
481 boot_sector.heads = 6
[5749372]482 boot_sector.hidden = 0
[de4a1cf]483 if (sectors > 65535):
484 boot_sector.sectors_big = sectors
485 else:
486 boot_sector.sectors_big = 0
[5749372]487
[700eaa9]488 boot_sector.drive = 0x80
[5749372]489 boot_sector.extboot_signature = 0x29
[0951495]490 boot_sector.serial = random.randint(0, 0x7fffffff)
[28f4adb]491 boot_sector.label = b'HELENOS'
492 boot_sector.fstype = b'FAT16 '
[5749372]493 boot_sector.boot_signature = [0x55, 0xAA]
494
495 outf.write(boot_sector.pack())
496
[8bc6fcf]497 empty_sector = xstruct.create(EMPTY_SECTOR)
498
[04619ba]499 # Reserved sectors
[28f4adb]500 for i in range(1, cluster_size // sector_size):
[8bc6fcf]501 outf.write(empty_sector.pack())
502
503 # FAT tables
[04619ba]504 for i in range(0, fat_count):
[28f4adb]505 for j in range(0, fat_size // sector_size):
[8bc6fcf]506 outf.write(empty_sector.pack())
507
508 # Root directory
[28f4adb]509 for i in range(0, root_size // sector_size):
[8bc6fcf]510 outf.write(empty_sector.pack())
511
512 # Data
[28f4adb]513 for i in range(0, size // sector_size):
[8bc6fcf]514 outf.write(empty_sector.pack())
515
[28f4adb]516 fat = array.array('L', [0] * (fat_size // fatent_size))
[04619ba]517 fat[0] = 0xfff8
518 fat[1] = 0xffff
519
[95b730c2]520 recursion(True, path, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, 0)
[04619ba]521
522 # Store FAT
523 fat_entry = xstruct.create(FAT_ENTRY)
524 for i in range(0, fat_count):
525 outf.seek(cluster_size + i * fat_size)
[28f4adb]526 for j in range(0, fat_size // fatent_size):
[04619ba]527 fat_entry.next = fat[j]
528 outf.write(fat_entry.pack())
529
[5749372]530 outf.close()
531
532if __name__ == '__main__':
533 main()
Note: See TracBrowser for help on using the repository browser.