Subversion Repositories HelenOS

Rev

Rev 3535 | Go to most recent revision | 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
3535 svoboda 37
import array
3264 decky 38
 
3561 svoboda 39
exclude_names = set(['.svn'])
40
 
3535 svoboda 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
 
49
def subtree_size(root, cluster_size, dirent_size):
50
    "Recursive directory walk and calculate size"
51
 
52
    size = 0
53
    files = 2
54
 
55
    for name in os.listdir(root):
56
        canon = os.path.join(root, name)
57
 
3561 svoboda 58
        if (os.path.isfile(canon) and (not name in exclude_names)):
3535 svoboda 59
            size += align_up(os.path.getsize(canon), cluster_size)
60
            files += 1
61
 
3561 svoboda 62
        if (os.path.isdir(canon) and (not name in exclude_names)):
3535 svoboda 63
            size += subtree_size(canon, cluster_size, dirent_size)
64
            files += 1
65
 
66
    return size + align_up(files * dirent_size, cluster_size)
67
 
68
def root_entries(root):
69
    "Return number of root directory entries"
70
 
71
    return len(os.listdir(root))
72
 
73
def write_file(path, outf, cluster_size, data_start, fat, reserved_clusters):
74
    "Store the contents of a file"
75
 
76
    size = os.path.getsize(path)
77
    prev = -1
78
    first = 0
79
 
80
    inf = file(path, "r")
81
    rd = 0;
82
    while (rd < size):
83
        empty_cluster = fat.index(0)
84
        fat[empty_cluster] = 0xffff
85
 
86
        if (prev != -1):
87
            fat[prev] = empty_cluster
88
        else:
89
            first = empty_cluster
90
 
91
        prev = empty_cluster
92
 
93
        data = inf.read(cluster_size);
94
        outf.seek(data_start + (empty_cluster - reserved_clusters) * cluster_size)
95
        outf.write(data)
96
        rd += len(data)
97
    inf.close()
98
 
99
    return first, size
100
 
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
 
137
DIR_ENTRY = """little:
138
    char name[8]               /* file name */
139
    char ext[3]                /* file extension */
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
 
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
 
187
def mangle_fname(name):
188
    # FIXME: filter illegal characters
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]
197
 
198
def mangle_ext(name):
199
    # FIXME: filter illegal characters
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]
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
 
220
    dir_entry.ctime_fine = 0 # FIXME
221
    dir_entry.ctime = 0 # FIXME
222
    dir_entry.cdate = 0 # FIXME
223
    dir_entry.adate = 0 # FIXME
224
    dir_entry.mtime = 0 # FIXME
225
    dir_entry.mdate = 0 # FIXME
226
    dir_entry.cluster = cluster
227
 
228
    if (directory):
229
        dir_entry.size = 0
230
    else:
231
        dir_entry.size = size
232
 
233
    return dir_entry
234
 
235
def create_dot_dirent(empty_cluster):
236
    dir_entry = xstruct.create(DOT_DIR_ENTRY)
237
 
238
    dir_entry.signature = 0x2e
239
    dir_entry.name = '       '
240
    dir_entry.ext = '   '
241
    dir_entry.attr = 0x10
242
 
243
    dir_entry.ctime_fine = 0 # FIXME
244
    dir_entry.ctime = 0 # FIXME
245
    dir_entry.cdate = 0 # FIXME
246
    dir_entry.adate = 0 # FIXME
247
    dir_entry.mtime = 0 # FIXME
248
    dir_entry.mdate = 0 # FIXME
249
    dir_entry.cluster = empty_cluster
250
    dir_entry.size = 0
251
 
252
    return dir_entry
253
 
254
def create_dotdot_dirent(parent_cluster):
255
    dir_entry = xstruct.create(DOTDOT_DIR_ENTRY)
256
 
257
    dir_entry.signature = [0x2e, 0x2e]
258
    dir_entry.name = '      '
259
    dir_entry.ext = '   '
260
    dir_entry.attr = 0x10
261
 
262
    dir_entry.ctime_fine = 0 # FIXME
263
    dir_entry.ctime = 0 # FIXME
264
    dir_entry.cdate = 0 # FIXME
265
    dir_entry.adate = 0 # FIXME
266
    dir_entry.mtime = 0 # FIXME
267
    dir_entry.mdate = 0 # FIXME
268
    dir_entry.cluster = parent_cluster
269
    dir_entry.size = 0
270
 
271
    return dir_entry
272
 
273
def recursion(head, root, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, parent_cluster):
274
    "Recursive directory walk"
275
 
276
    directory = []
277
 
278
    if (not head):
279
        # Directory cluster preallocation
280
        empty_cluster = fat.index(0)
281
        fat[empty_cluster] = 0xffff
282
 
283
        directory.append(create_dot_dirent(empty_cluster))
284
        directory.append(create_dotdot_dirent(parent_cluster))
285
    else:
286
        empty_cluster = 0
287
 
288
    for name in os.listdir(root):
289
        canon = os.path.join(root, name)
290
 
3561 svoboda 291
        if (os.path.isfile(canon) and (not name in exclude_names)):
3535 svoboda 292
            rv = write_file(canon, outf, cluster_size, data_start, fat, reserved_clusters)
293
            directory.append(create_dirent(name, False, rv[0], rv[1]))
294
 
3561 svoboda 295
        if (os.path.isdir(canon) and (not name in exclude_names)):
3535 svoboda 296
            rv = recursion(False, canon, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
297
            directory.append(create_dirent(name, True, rv[0], rv[1]))
298
 
299
    if (head):
300
        outf.seek(root_start)
301
        for dir_entry in directory:
302
            outf.write(dir_entry.pack())
303
    else:
304
        return write_directory(directory, outf, cluster_size, data_start, fat, reserved_clusters, dirent_size, empty_cluster)
305
 
3264 decky 306
BOOT_SECTOR = """little:
307
    uint8_t jmp[3]             /* jump instruction */
308
    char oem[8]                /* OEM string */
309
    uint16_t sector            /* bytes per sector */
310
    uint8_t cluster            /* sectors per cluster */
311
    uint16_t reserved          /* reserved sectors */
312
    uint8_t fats               /* number of FATs */
313
    uint16_t rootdir           /* root directory entries */
314
    uint16_t sectors           /* total number of sectors */
315
    uint8_t descriptor         /* media descriptor */
316
    uint16_t fat_sectors       /* sectors per single FAT */
317
    uint16_t track_sectors     /* sectors per track */
318
    uint16_t heads             /* number of heads */
319
    uint32_t hidden            /* hidden sectors */
320
    uint32_t sectors_big       /* total number of sectors (if sectors == 0) */
321
 
322
    /* Extended BIOS Parameter Block */
323
    uint8_t drive              /* physical drive number */
324
    padding[1]                 /* reserved (current head) */
325
    uint8_t extboot_signature  /* extended boot signature */
326
    uint32_t serial            /* serial number */
327
    char label[11]             /* volume label */
328
    char fstype[8]             /* filesystem type */
329
    padding[448]               /* boot code */
330
    uint8_t boot_signature[2]  /* boot signature */
331
"""
332
 
3535 svoboda 333
EMPTY_SECTOR = """little:
334
    padding[512]               /* empty sector data */
335
"""
336
 
337
FAT_ENTRY = """little:
338
    uint16_t next              /* FAT16 entry */
339
"""
340
 
3264 decky 341
def usage(prname):
342
    "Print usage syntax"
343
    print prname + " <PATH> <IMAGE>"
344
 
345
def main():
346
    if (len(sys.argv) < 3):
347
        usage(sys.argv[0])
348
        return
349
 
350
    path = os.path.abspath(sys.argv[1])
351
    if (not os.path.isdir(path)):
352
        print "<PATH> must be a directory"
353
        return
354
 
3535 svoboda 355
    fat16_clusters = 4096
356
 
357
    sector_size = 512
358
    cluster_size = 4096
359
    dirent_size = 32
360
    fatent_size = 2
361
    fat_count = 2
362
    reserved_clusters = 2
363
 
364
    # Make sure the filesystem is large enought for FAT16
365
    size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size
366
    while (size / cluster_size < fat16_clusters):
3561 svoboda 367
        if (cluster_size > sector_size):
3535 svoboda 368
            cluster_size /= 2
369
            size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size
370
        else:
371
            size = fat16_clusters * cluster_size + reserved_clusters * cluster_size
372
 
373
    root_size = align_up(root_entries(path) * dirent_size, cluster_size)
374
 
375
    fat_size = align_up(align_up(size, cluster_size) / cluster_size * fatent_size, sector_size)
376
 
377
    sectors = (cluster_size + fat_count * fat_size + root_size + size) / sector_size
378
    root_start = cluster_size + fat_count * fat_size
379
    data_start = root_start + root_size
380
 
3264 decky 381
    outf = file(sys.argv[2], "w")
382
 
383
    boot_sector = xstruct.create(BOOT_SECTOR)
384
    boot_sector.jmp = [0xEB, 0x3C, 0x90]
385
    boot_sector.oem = "MSDOS5.0"
3535 svoboda 386
    boot_sector.sector = sector_size
387
    boot_sector.cluster = cluster_size / sector_size
388
    boot_sector.reserved = cluster_size / sector_size
389
    boot_sector.fats = fat_count
390
    boot_sector.rootdir = root_size / dirent_size
391
    if (sectors <= 65535):
392
        boot_sector.sectors = sectors
393
    else:
394
        boot_sector.sectors = 0
3264 decky 395
    boot_sector.descriptor = 0xF8
3535 svoboda 396
    boot_sector.fat_sectors = fat_size / sector_size
397
    boot_sector.track_sectors = 63
398
    boot_sector.heads = 6
3264 decky 399
    boot_sector.hidden = 0
3535 svoboda 400
    if (sectors > 65535):
401
        boot_sector.sectors_big = sectors
402
    else:
403
        boot_sector.sectors_big = 0
3264 decky 404
 
3535 svoboda 405
    boot_sector.drive = 0x80
3264 decky 406
    boot_sector.extboot_signature = 0x29
3535 svoboda 407
    boot_sector.serial = random.randint(0, 0x7fffffff)
3264 decky 408
    boot_sector.label = "HELENOS"
409
    boot_sector.fstype = "FAT16   "
410
    boot_sector.boot_signature = [0x55, 0xAA]
411
 
412
    outf.write(boot_sector.pack())
413
 
3535 svoboda 414
    empty_sector = xstruct.create(EMPTY_SECTOR)
415
 
416
    # Reserved sectors
417
    for i in range(1, cluster_size / sector_size):
418
        outf.write(empty_sector.pack())
419
 
420
    # FAT tables
421
    for i in range(0, fat_count):
422
        for j in range(0, fat_size / sector_size):
423
            outf.write(empty_sector.pack())
424
 
425
    # Root directory
426
    for i in range(0, root_size / sector_size):
427
        outf.write(empty_sector.pack())
428
 
429
    # Data
430
    for i in range(0, size / sector_size):
431
        outf.write(empty_sector.pack())
432
 
433
    fat = array.array('L', [0] * (fat_size / fatent_size))
434
    fat[0] = 0xfff8
435
    fat[1] = 0xffff
436
 
437
    recursion(True, path, outf, cluster_size, root_start, data_start, fat, reserved_clusters, dirent_size, 0)
438
 
439
    # Store FAT
440
    fat_entry = xstruct.create(FAT_ENTRY)
441
    for i in range(0, fat_count):
442
        outf.seek(cluster_size + i * fat_size)
443
        for j in range(0, fat_size / fatent_size):
444
            fat_entry.next = fat[j]
445
            outf.write(fat_entry.pack())
446
 
3264 decky 447
    outf.close()
448
 
449
if __name__ == '__main__':
450
    main()