Subversion Repositories HelenOS

Rev

Rev 3545 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3264 decky 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
FAT creator
31
"""
32
 
33
import sys
34
import os
35
import random
36
import xstruct
3520 decky 37
import array
3264 decky 38
 
3541 svoboda 39
exclude_names = set(['.svn'])
40
 
3509 decky 41
def align_up(size, alignment):
42
	"Return size aligned up to alignment"
43
 
44
	if (size % alignment == 0):
45
		return size
46
 
47
	return (((size / alignment) + 1) * alignment)
48
 
3520 decky 49
def subtree_size(root, cluster_size, dirent_size):
3509 decky 50
	"Recursive directory walk and calculate size"
51
 
52
	size = 0
3523 decky 53
	files = 2
3509 decky 54
 
55
	for name in os.listdir(root):
56
		canon = os.path.join(root, name)
57
 
3545 decky 58
		if (os.path.isfile(canon) and (not name in exclude_names)):
3509 decky 59
			size += align_up(os.path.getsize(canon), cluster_size)
60
			files += 1
61
 
3545 decky 62
		if (os.path.isdir(canon) and (not name in exclude_names)):
3520 decky 63
			size += subtree_size(canon, cluster_size, dirent_size)
3509 decky 64
			files += 1
65
 
3520 decky 66
	return size + align_up(files * dirent_size, cluster_size)
3509 decky 67
 
68
def root_entries(root):
69
	"Return number of root directory entries"
70
 
71
	return len(os.listdir(root))
72
 
3523 decky 73
def write_file(path, outf, cluster_size, data_start, fat, reserved_clusters):
3520 decky 74
	"Store the contents of a file"
75
 
76
	size = os.path.getsize(path)
77
	prev = -1
3523 decky 78
	first = 0
3520 decky 79
 
80
	inf = file(path, "r")
81
	rd = 0;
82
	while (rd < size):
83
		empty_cluster = fat.index(0)
3523 decky 84
		fat[empty_cluster] = 0xffff
3520 decky 85
 
86
		if (prev != -1):
87
			fat[prev] = empty_cluster
88
		else:
89
			first = empty_cluster
90
 
91
		prev = empty_cluster
92
 
3523 decky 93
		data = inf.read(cluster_size);
94
		outf.seek(data_start + (empty_cluster - reserved_clusters) * cluster_size)
3520 decky 95
		outf.write(data)
96
		rd += len(data)
97
	inf.close()
98
 
99
	return first, size
100
 
3523 decky 101
def write_directory(directory, outf, cluster_size, data_start, fat, reserved_clusters, dirent_size, empty_cluster):
102
	"Store the contents of a directory"
103
 
104
	length = len(directory)
105
	size = length * dirent_size
106
	prev = -1
107
	first = 0
108
 
109
	i = 0
110
	rd = 0;
111
	while (rd < size):
112
		if (prev != -1):
113
			empty_cluster = fat.index(0)
114
			fat[empty_cluster] = 0xffff
115
			fat[prev] = empty_cluster
116
		else:
117
			first = empty_cluster
118
 
119
		prev = empty_cluster
120
 
121
		data = ''
122
		data_len = 0
123
		while ((i < length) and (data_len < cluster_size)):
124
			if (i == 0):
125
				directory[i].cluster = empty_cluster
126
 
127
			data += directory[i].pack()
128
			data_len += dirent_size
129
			i += 1
130
 
131
		outf.seek(data_start + (empty_cluster - reserved_clusters) * cluster_size)
132
		outf.write(data)
133
		rd += len(data)
134
 
135
	return first, size
136
 
3520 decky 137
DIR_ENTRY = """little:
138
	char name[8]               /* file name */
139
	char ext[3]                /* file extension */
140
	uint8_t attr               /* file attributes */
4360 decky 141
	uint8_t lcase              /* file name case (NT extension) */
3520 decky 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
 
3523 decky 153
DOT_DIR_ENTRY = """little:
154
	uint8_t signature          /* 0x2e signature */
155
	char name[7]               /* 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
 
170
DOTDOT_DIR_ENTRY = """little:
171
	uint8_t signature[2]       /* 0x2e signature */
172
	char name[6]               /* empty */
173
	char ext[3]                /* empty */
174
	uint8_t attr               /* file attributes */
175
	padding[1]                 /* reserved for NT */
176
	uint8_t ctime_fine         /* create time (fine resolution) */
177
	uint16_t ctime             /* create time */
178
	uint16_t cdate             /* create date */
179
	uint16_t adate             /* access date */
180
	padding[2]                 /* EA-index */
181
	uint16_t mtime             /* modification time */
182
	uint16_t mdate             /* modification date */
183
	uint16_t cluster           /* first cluster */
184
	uint32_t size              /* file size */
185
"""
186
 
3520 decky 187
def mangle_fname(name):
188
	# FIXME: filter illegal characters
3523 decky 189
	parts = name.split('.')
190
 
191
	if (len(parts) > 0):
192
		fname = parts[0]
193
	else:
194
		fname = ''
195
 
196
	return (fname + '          ').upper()[0:8]
3520 decky 197
 
198
def mangle_ext(name):
199
	# FIXME: filter illegal characters
3523 decky 200
	parts = name.split('.')
201
 
202
	if (len(parts) > 1):
203
		ext = parts[1]
204
	else:
205
		ext = ''
206
 
207
	return (ext + '   ').upper()[0:3]
3520 decky 208
 
209
def create_dirent(name, directory, cluster, size):
210
	dir_entry = xstruct.create(DIR_ENTRY)
211
 
212
	dir_entry.name = mangle_fname(name)
213
	dir_entry.ext = mangle_ext(name)
214
 
215
	if (directory):
216
		dir_entry.attr = 0x30
217
	else:
218
		dir_entry.attr = 0x20
219
 
4360 decky 220
	dir_entry.lcase = 0x18
3520 decky 221
	dir_entry.ctime_fine = 0 # FIXME
222
	dir_entry.ctime = 0 # FIXME
223
	dir_entry.cdate = 0 # FIXME
224
	dir_entry.adate = 0 # FIXME
225
	dir_entry.mtime = 0 # FIXME
226
	dir_entry.mdate = 0 # FIXME
227
	dir_entry.cluster = cluster
228
 
3523 decky 229
	if (directory):
230
		dir_entry.size = 0
231
	else:
232
		dir_entry.size = size
233
 
3520 decky 234
	return dir_entry
235
 
3523 decky 236
def create_dot_dirent(empty_cluster):
237
	dir_entry = xstruct.create(DOT_DIR_ENTRY)
238
 
239
	dir_entry.signature = 0x2e
240
	dir_entry.name = '       '
241
	dir_entry.ext = '   '
242
	dir_entry.attr = 0x10
243
 
244
	dir_entry.ctime_fine = 0 # FIXME
245
	dir_entry.ctime = 0 # FIXME
246
	dir_entry.cdate = 0 # FIXME
247
	dir_entry.adate = 0 # FIXME
248
	dir_entry.mtime = 0 # FIXME
249
	dir_entry.mdate = 0 # FIXME
250
	dir_entry.cluster = empty_cluster
251
	dir_entry.size = 0
252
 
253
	return dir_entry
254
 
255
def create_dotdot_dirent(parent_cluster):
256
	dir_entry = xstruct.create(DOTDOT_DIR_ENTRY)
257
 
258
	dir_entry.signature = [0x2e, 0x2e]
259
	dir_entry.name = '      '
260
	dir_entry.ext = '   '
261
	dir_entry.attr = 0x10
262
 
263
	dir_entry.ctime_fine = 0 # FIXME
264
	dir_entry.ctime = 0 # FIXME
265
	dir_entry.cdate = 0 # FIXME
266
	dir_entry.adate = 0 # FIXME
267
	dir_entry.mtime = 0 # FIXME
268
	dir_entry.mdate = 0 # FIXME
269
	dir_entry.cluster = parent_cluster
270
	dir_entry.size = 0
271
 
272
	return dir_entry
273
 
274
def recursion(head, root, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, parent_cluster):
3520 decky 275
	"Recursive directory walk"
276
 
277
	directory = []
3523 decky 278
 
279
	if (not head):
280
		# Directory cluster preallocation
281
		empty_cluster = fat.index(0)
282
		fat[empty_cluster] = 0xffff
283
 
284
		directory.append(create_dot_dirent(empty_cluster))
285
		directory.append(create_dotdot_dirent(parent_cluster))
286
	else:
287
		empty_cluster = 0
288
 
3520 decky 289
	for name in os.listdir(root):
290
		canon = os.path.join(root, name)
291
 
3541 svoboda 292
		if (os.path.isfile(canon) and (not name in exclude_names)):
3523 decky 293
			rv = write_file(canon, outf, cluster_size, data_start, fat, reserved_clusters)
3520 decky 294
			directory.append(create_dirent(name, False, rv[0], rv[1]))
3523 decky 295
 
3541 svoboda 296
		if (os.path.isdir(canon) and (not name in exclude_names)):
3523 decky 297
			rv = recursion(False, canon, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
298
			directory.append(create_dirent(name, True, rv[0], rv[1]))
3520 decky 299
 
300
	if (head):
301
		outf.seek(root_start)
302
		for dir_entry in directory:
303
			outf.write(dir_entry.pack())
3523 decky 304
	else:
305
		return write_directory(directory, outf, cluster_size, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
3520 decky 306
 
3264 decky 307
BOOT_SECTOR = """little:
308
	uint8_t jmp[3]             /* jump instruction */
309
	char oem[8]                /* OEM string */
310
	uint16_t sector            /* bytes per sector */
311
	uint8_t cluster            /* sectors per cluster */
312
	uint16_t reserved          /* reserved sectors */
313
	uint8_t fats               /* number of FATs */
314
	uint16_t rootdir           /* root directory entries */
315
	uint16_t sectors           /* total number of sectors */
316
	uint8_t descriptor         /* media descriptor */
317
	uint16_t fat_sectors       /* sectors per single FAT */
318
	uint16_t track_sectors     /* sectors per track */
319
	uint16_t heads             /* number of heads */
320
	uint32_t hidden            /* hidden sectors */
321
	uint32_t sectors_big       /* total number of sectors (if sectors == 0) */
322
 
323
	/* Extended BIOS Parameter Block */
324
	uint8_t drive              /* physical drive number */
325
	padding[1]                 /* reserved (current head) */
326
	uint8_t extboot_signature  /* extended boot signature */
327
	uint32_t serial            /* serial number */
328
	char label[11]             /* volume label */
329
	char fstype[8]             /* filesystem type */
330
	padding[448]               /* boot code */
331
	uint8_t boot_signature[2]  /* boot signature */
332
"""
333
 
3511 decky 334
EMPTY_SECTOR = """little:
3520 decky 335
	padding[512]               /* empty sector data */
3511 decky 336
"""
337
 
3520 decky 338
FAT_ENTRY = """little:
339
	uint16_t next              /* FAT16 entry */
340
"""
341
 
3264 decky 342
def usage(prname):
343
	"Print usage syntax"
344
	print prname + " <PATH> <IMAGE>"
345
 
346
def main():
347
	if (len(sys.argv) < 3):
348
		usage(sys.argv[0])
349
		return
350
 
351
	path = os.path.abspath(sys.argv[1])
352
	if (not os.path.isdir(path)):
353
		print "<PATH> must be a directory"
354
		return
355
 
3520 decky 356
	fat16_clusters = 4096
357
 
3509 decky 358
	sector_size = 512
359
	cluster_size = 4096
3520 decky 360
	dirent_size = 32
361
	fatent_size = 2
362
	fat_count = 2
363
	reserved_clusters = 2
3509 decky 364
 
3520 decky 365
	# Make sure the filesystem is large enought for FAT16
366
	size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size
367
	while (size / cluster_size < fat16_clusters):
3545 decky 368
		if (cluster_size > sector_size):
3520 decky 369
			cluster_size /= 2
370
			size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size
371
		else:
372
			size = fat16_clusters * cluster_size + reserved_clusters * cluster_size
3509 decky 373
 
3520 decky 374
	root_size = align_up(root_entries(path) * dirent_size, cluster_size)
3509 decky 375
 
3520 decky 376
	fat_size = align_up(align_up(size, cluster_size) / cluster_size * fatent_size, sector_size)
377
 
378
	sectors = (cluster_size + fat_count * fat_size + root_size + size) / sector_size
379
	root_start = cluster_size + fat_count * fat_size
380
	data_start = root_start + root_size
381
 
3264 decky 382
	outf = file(sys.argv[2], "w")
383
 
384
	boot_sector = xstruct.create(BOOT_SECTOR)
385
	boot_sector.jmp = [0xEB, 0x3C, 0x90]
386
	boot_sector.oem = "MSDOS5.0"
3509 decky 387
	boot_sector.sector = sector_size
388
	boot_sector.cluster = cluster_size / sector_size
389
	boot_sector.reserved = cluster_size / sector_size
3520 decky 390
	boot_sector.fats = fat_count
391
	boot_sector.rootdir = root_size / dirent_size
3524 decky 392
	if (sectors <= 65535):
393
		boot_sector.sectors = sectors
394
	else:
395
		boot_sector.sectors = 0
3264 decky 396
	boot_sector.descriptor = 0xF8
3509 decky 397
	boot_sector.fat_sectors = fat_size / sector_size
398
	boot_sector.track_sectors = 63
399
	boot_sector.heads = 6
3264 decky 400
	boot_sector.hidden = 0
3524 decky 401
	if (sectors > 65535):
402
		boot_sector.sectors_big = sectors
403
	else:
404
		boot_sector.sectors_big = 0
3264 decky 405
 
3509 decky 406
	boot_sector.drive = 0x80
3264 decky 407
	boot_sector.extboot_signature = 0x29
3525 decky 408
	boot_sector.serial = random.randint(0, 0x7fffffff)
3264 decky 409
	boot_sector.label = "HELENOS"
410
	boot_sector.fstype = "FAT16   "
411
	boot_sector.boot_signature = [0x55, 0xAA]
412
 
413
	outf.write(boot_sector.pack())
414
 
3511 decky 415
	empty_sector = xstruct.create(EMPTY_SECTOR)
416
 
3520 decky 417
	# Reserved sectors
418
	for i in range(1, cluster_size / sector_size):
3511 decky 419
		outf.write(empty_sector.pack())
420
 
421
	# FAT tables
3520 decky 422
	for i in range(0, fat_count):
3523 decky 423
		for j in range(0, fat_size / sector_size):
3511 decky 424
			outf.write(empty_sector.pack())
425
 
426
	# Root directory
427
	for i in range(0, root_size / sector_size):
428
		outf.write(empty_sector.pack())
429
 
430
	# Data
431
	for i in range(0, size / sector_size):
432
		outf.write(empty_sector.pack())
433
 
3520 decky 434
	fat = array.array('L', [0] * (fat_size / fatent_size))
435
	fat[0] = 0xfff8
436
	fat[1] = 0xffff
437
 
3523 decky 438
	recursion(True, path, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, 0)
3520 decky 439
 
440
	# Store FAT
441
	fat_entry = xstruct.create(FAT_ENTRY)
442
	for i in range(0, fat_count):
443
		outf.seek(cluster_size + i * fat_size)
444
		for j in range(0, fat_size / fatent_size):
445
			fat_entry.next = fat[j]
446
			outf.write(fat_entry.pack())
447
 
3264 decky 448
	outf.close()
449
 
450
if __name__ == '__main__':
451
	main()