r25972@plastic: rob | 2007-04-15 01:04:52 +1000
[cake.git] / workbench / fs / fat / fat.c
blob2da2915be21c03bdc939a95b8325248a5247522a
1 /*
2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007 The AROS Development Team
7 * This program is free software; you can redistribute it and/or modify it
8 * under the same terms as AROS itself.
10 * $Id$
13 #include <exec/types.h>
14 #include <dos/dos.h>
15 #include <dos/dosextens.h>
16 #include <dos/filehandler.h>
18 #include <proto/exec.h>
19 #include <proto/dos.h>
21 #include <clib/macros.h>
23 #include <string.h>
24 #include <ctype.h>
26 #include "fat_fs.h"
27 #include "fat_protos.h"
29 /* helper function to get the location of a fat entry for a cluster. it used
30 * to be a define until it got too crazy */
31 static UBYTE *GetFatEntryPtr(struct FSSuper *sb, ULONG offset, struct cache_block **rb) {
32 ULONG entry_cache_block = offset >> sb->fat_cachesize_bits;
33 ULONG entry_cache_offset = offset & (sb->fat_cachesize - 1);
35 /* if the target cluster is not within the currently loaded chunk of fat,
36 * we need to get the right data in */
37 if (sb->fat_cache_block != entry_cache_block) {
38 D(bug("[fat] loading %ld FAT sectors starting at sector %ld\n", sb->fat_blocks_count, entry_cache_block));
39 /* put the old ones back */
40 if (sb->fat_cache_block != 0xffffffff)
41 cache_put_blocks(glob->cache, sb->fat_blocks, sb->fat_blocks_count, 0);
43 /* load some more */
44 cache_get_blocks(glob->cache,
45 glob->diskioreq->iotd_Req.io_Device,
46 glob->diskioreq->iotd_Req.io_Unit,
47 sb->first_fat_sector +
48 (entry_cache_block << (sb->fat_cachesize_bits - sb->sectorsize_bits)),
49 sb->fat_blocks_count,
51 sb->fat_blocks);
53 /* remember where we are for next time */
54 sb->fat_cache_block = entry_cache_block;
57 /* give the block back if they asked for it (needed to mark the block
58 * dirty if they're writing */
59 if (rb != NULL)
60 *rb = sb->fat_blocks[entry_cache_offset >> sb->sectorsize_bits];
62 /* compute the pointer location and return it */
63 return sb->fat_blocks[entry_cache_offset >> sb->sectorsize_bits]->data +
64 (entry_cache_offset & (sb->sectorsize-1));
67 /* FAT12 has, as the name suggests, 12-bit FAT entries. This means that two
68 * entries are condensed into three bytes, like so:
70 * entry: aaaaaaaa aaaabbbb bbbbbbbb
71 * bytes: xxxxxxxx xxxxxxxx xxxxxxxx
73 * To get at the entry we want, we find and grab the word starting at either
74 * byte 0 or 1 of the three-byte set, then shift up or down as needed. FATdoc
75 * 1.03 p16-17 describes the method
77 * The only tricky bit is if the word falls such that the first byte is the
78 * last byte of the block and the second byte is the first byte of the next
79 * block. Since our block data are stored within cache block structures, a
80 * simple cast won't do (hell, the second block may not even be in memory if
81 * we're at the end of the FAT cache). So we get it a byte at a time, and
82 * build the word ourselves.
84 static ULONG GetFat12Entry(struct FSSuper *sb, ULONG n) {
85 ULONG offset = n + n/2;
86 UWORD val;
88 if ((offset & (sb->sectorsize-1)) == sb->sectorsize-1) {
89 D(bug("[fat] fat12 cluster pair on block boundary, compensating\n"));
91 val = ((UWORD) *GetFatEntryPtr(sb, offset, NULL)) << 8;
92 val |= ((UWORD) *GetFatEntryPtr(sb, offset+1, NULL));
94 val = AROS_LE2WORD(val);
96 else
97 val = AROS_LE2WORD(*((UWORD *) GetFatEntryPtr(sb, n + n/2, NULL)));
99 if (n & 1)
100 val >>= 4;
101 else
102 val &= 0xfff;
104 return val;
108 * FAT16 and FAT32, on the other hand, have nice neat entry widths, so simple
109 * word/long casts are fine. There's also no chance that the entry can be
110 * split across blocks. Why can't everything be this simple?
112 static ULONG GetFat16Entry(struct FSSuper *sb, ULONG n) {
113 return AROS_LE2WORD(*((UWORD *) GetFatEntryPtr(sb, n << 1, NULL)));
116 static ULONG GetFat32Entry(struct FSSuper *sb, ULONG n) {
117 return AROS_LE2LONG(*((ULONG *) GetFatEntryPtr(sb, n << 2, NULL)));
120 static void SetFat12Entry(struct FSSuper *sb, ULONG n, ULONG val) {
121 struct cache_block *b;
122 ULONG offset = n + n/2;
123 BOOL boundary = FALSE;
124 UWORD *fat, newval;
126 if ((offset & (sb->sectorsize-1)) == sb->sectorsize-1) {
127 boundary = TRUE;
129 D(bug("[fat] fat12 cluster pair on block boundary, compensating\n"));
131 newval = ((UWORD) *GetFatEntryPtr(sb, offset, NULL)) << 8;
132 newval |= ((UWORD) *GetFatEntryPtr(sb, offset+1, NULL));
134 newval = AROS_LE2WORD(newval);
136 else {
137 fat = (UWORD *) GetFatEntryPtr(sb, offset, &b);
138 newval = AROS_LE2WORD(*fat);
141 if (n & 1) {
142 n <<= 4;
143 newval = (newval & 0xf) | val;
145 else {
146 n &= 0xfff;
147 newval = (newval & 0xf000) | val;
150 if (boundary) {
151 newval = AROS_WORD2LE(newval);
153 /* XXX ideally we'd mark both blocks dirty at the same time or only do
154 * it once if they're same block. unfortunately b is essentially
155 * invalid after a call to GetFatEntryPtr, as it may have swapped the
156 * previous cache out. This is probably safe enough. */
157 *GetFatEntryPtr(sb, offset+1, &b) = newval >> 8;
158 cache_mark_block_dirty(glob->cache, b);
159 *GetFatEntryPtr(sb, offset, &b) = newval & 0xff;
160 cache_mark_block_dirty(glob->cache, b);
162 else {
163 *fat = AROS_WORD2LE(newval);
164 cache_mark_block_dirty(glob->cache, b);
168 static void SetFat16Entry(struct FSSuper *sb, ULONG n, ULONG val) {
169 struct cache_block *b;
171 *((UWORD *) GetFatEntryPtr(sb, n << 1, &b)) = AROS_WORD2LE((UWORD) val);
173 cache_mark_block_dirty(glob->cache, b);
176 static void SetFat32Entry(struct FSSuper *sb, ULONG n, ULONG val) {
177 struct cache_block *b;
178 ULONG *fat = (ULONG *) GetFatEntryPtr(sb, n << 2, &b);
180 *fat = (*fat & 0xf0000000) | val;
182 cache_mark_block_dirty(glob->cache, b);
185 LONG ReadFATSuper(struct FSSuper *sb ) {
186 LONG err;
187 UBYTE raw[512];
188 struct FATBootSector *boot = (struct FATBootSector *) &raw;
189 BOOL invalid = FALSE;
191 D(bug("[fat] reading boot sector\n"));
194 * Read the boot sector. We go direct because we don't have a cache yet,
195 * and can't create one until we know the sector size, which is held in
196 * the boot sector. In practice it doesn't matter - we're going use this
197 * once and once only.
200 glob->diskioreq->iotd_Req.io_Command = CMD_READ;
201 glob->diskioreq->iotd_Req.io_Offset = 0;
202 glob->diskioreq->iotd_Req.io_Length = 512;
203 glob->diskioreq->iotd_Req.io_Data = &raw;
204 glob->diskioreq->iotd_Req.io_Flags = IOF_QUICK;
206 if ((err = DoIO((struct IORequest *) glob->diskioreq)) != 0) {
207 D(bug("[fat] couldn't read boot block (%ld)\n", err));
208 return err;
211 kprintf("\tBoot sector:\n");
213 sb->sectorsize = AROS_LE2WORD(boot->bpb_bytes_per_sect);
214 sb->sectorsize_bits = log2(sb->sectorsize);
215 kprintf("\tSectorSize = %ld\n", sb->sectorsize);
216 kprintf("\tSectorSize Bits = %ld\n", sb->sectorsize_bits);
218 sb->cluster_sectors = boot->bpb_sect_per_clust;
219 sb->clustersize = sb->sectorsize * boot->bpb_sect_per_clust;
220 sb->clustersize_bits = log2(sb->clustersize);
221 sb->cluster_sectors_bits = sb->clustersize_bits - sb->sectorsize_bits;
223 kprintf("\tSectorsPerCluster = %ld\n", (ULONG)boot->bpb_sect_per_clust);
224 kprintf("\tClusterSize = %ld\n", sb->clustersize);
225 kprintf("\tClusterSize Bits = %ld\n", sb->clustersize_bits);
226 kprintf("\tCluster Sectors Bits = %ld\n", sb->cluster_sectors_bits);
228 sb->first_fat_sector = AROS_LE2WORD(boot->bpb_rsvd_sect_count);
229 kprintf("\tFirst FAT Sector = %ld\n", sb->first_fat_sector);
231 if (boot->bpb_fat_size_16 != 0)
232 sb->fat_size = AROS_LE2WORD(boot->bpb_fat_size_16);
233 else
234 sb->fat_size = AROS_LE2LONG(boot->type.fat32.bpb_fat_size_32);
235 kprintf("\tFAT Size = %ld\n", sb->fat_size);
237 if (boot->bpb_total_sectors_16 != 0)
238 sb->total_sectors = AROS_LE2WORD(boot->bpb_total_sectors_16);
239 else
240 sb->total_sectors = AROS_LE2LONG(boot->bpb_total_sectors_32);
241 kprintf("\tTotal Sectors = %ld\n", sb->total_sectors);
243 sb->rootdir_sectors = ((AROS_LE2WORD(boot->bpb_root_entries_count) * sizeof(struct FATDirEntry)) + (sb->sectorsize - 1)) >> sb->sectorsize_bits;
244 kprintf("\tRootDir Sectors = %ld\n", sb->rootdir_sectors);
246 sb->data_sectors = sb->total_sectors - (sb->first_fat_sector + (boot->bpb_num_fats * sb->fat_size) + sb->rootdir_sectors);
247 kprintf("\tData Sectors = %ld\n", sb->data_sectors);
249 sb->clusters_count = sb->data_sectors >> sb->cluster_sectors_bits;
250 kprintf("\tClusters Count = %ld\n", sb->clusters_count);
252 sb->first_rootdir_sector = sb->first_fat_sector + (boot->bpb_num_fats * sb->fat_size);
253 kprintf("\tFirst RootDir Sector = %ld\n", sb->first_rootdir_sector);
255 sb->first_data_sector = sb->first_fat_sector + (boot->bpb_num_fats * sb->fat_size) + sb->rootdir_sectors;
256 kprintf("\tFirst Data Sector = %ld\n", sb->first_data_sector);
258 sb->free_clusters = 0xffffffff;
260 /* check if disk is in fact FAT filesystem */
262 /* valid sector size: 512, 1024, 2048, 4096 */
263 if (sb->sectorsize != 512 && sb->sectorsize != 1024 && sb->sectorsize != 2048 && sb->sectorsize != 4096)
264 invalid = TRUE;
266 /* valid bpb_sect_per_clust: 1, 2, 4, 8, 16, 32, 64, 128 */
267 if ((boot->bpb_sect_per_clust & (boot->bpb_sect_per_clust - 1)) != 0 || boot->bpb_sect_per_clust == 0 || boot->bpb_sect_per_clust > 128)
268 invalid = TRUE;
270 /* valid cluster size: 512, 1024, 2048, 4096, 8192, 16k, 32k, 64k */
271 if (sb->clustersize > 64 * 1024)
272 invalid = TRUE;
274 if (sb->first_fat_sector == 0)
275 invalid = TRUE;
277 if (boot->bpb_num_fats == 0)
278 invalid = TRUE;
280 if (boot->bpb_media < 0xF0)
281 invalid = TRUE;
283 /* FAT "signature" */
284 if (raw[510] != 0x55 || raw[511] != 0xaa)
285 invalid = TRUE;
287 if (invalid) {
288 kprintf("\tInvalid FAT Boot Sector\n");
289 return ERROR_NOT_A_DOS_DISK;
292 if ((err = InitDevice(glob->fssm, sb->sectorsize) != 0))
293 return err;
295 if (sb->clusters_count < 4085) {
296 kprintf("\tFAT12 filesystem detected\n");
297 sb->type = 12;
298 sb->eoc_mark = 0x0FF8;
299 sb->func_get_fat_entry = GetFat12Entry;
300 sb->func_set_fat_entry = SetFat12Entry;
302 else if (sb->clusters_count < 65525) {
303 kprintf("\tFAT16 filesystem detected\n");
304 sb->type = 16;
305 sb->eoc_mark = 0xFFF8;
306 sb->func_get_fat_entry = GetFat16Entry;
307 sb->func_set_fat_entry = SetFat16Entry;
309 else {
310 kprintf("\tFAT32 filesystem detected\n");
311 sb->type = 32;
312 sb->eoc_mark = 0x0FFFFFF8;
313 sb->func_get_fat_entry = GetFat32Entry;
314 sb->func_set_fat_entry = SetFat32Entry;
317 /* setup the FAT cache and load the first blocks */
318 sb->fat_cachesize = 4096;
319 sb->fat_cachesize_bits = log2(sb->fat_cachesize);
320 sb->fat_cache_block = 0xffffffff;
322 sb->fat_blocks_count = MIN(sb->fat_size, sb->fat_cachesize >> sb->sectorsize_bits);
323 sb->fat_blocks = AllocVecPooled(glob->mempool, sizeof(struct cache_block *) * sb->fat_blocks_count);
325 if (sb->type != 32) { /* FAT 12/16 */
326 /* setup volume id */
327 sb->volume_id = AROS_LE2LONG(boot->type.fat16.bs_volid);
329 /* location of root directory */
330 sb->rootdir_cluster = 0;
331 sb->rootdir_sector = sb->first_rootdir_sector;
333 else {
334 /* setup volume id */
335 sb->volume_id = AROS_LE2LONG(boot->type.fat32.bs_volid);
337 /* location of root directory */
338 sb->rootdir_cluster = AROS_LE2LONG(boot->type.fat32.bpb_root_cluster);
339 sb->rootdir_sector = 0;
342 D(bug("[fat] rootdir at cluster %ld sector %ld\n", sb->rootdir_cluster, sb->rootdir_sector));
344 if (GetVolumeInfo(sb, &(sb->volume)) != 0) {
345 LONG i;
346 UBYTE *uu = (void *)&sb->volume_id;
348 for (i=1; i<10;) {
349 int d;
351 if (i==5)
352 sb->volume.name[i++]='-';
354 d = (*uu) & 0x0f;
355 sb->volume.name[i++] = (d < 10) ? '0' + d : 'A' - 10 + d;
356 d = ((*uu) & 0xf0)>>4;
357 sb->volume.name[i++] = (d < 10) ? '0' + d : 'A' - 10 + d;
359 uu++;
362 sb->volume.name[i] = '\0';
363 sb->volume.name[0] = 9;
366 kprintf("\tFAT Filesystem succesfully detected.\n");
368 return 0;
371 LONG GetVolumeInfo(struct FSSuper *sb, struct VolumeInfo *volume) {
372 struct DirHandle dh;
373 struct DirEntry de;
374 LONG err;
375 int i;
377 D(bug("[fat] searching root directory for volume name\n"));
379 /* search the directory for the volume id entry. it would've been nice to
380 * just use GetNextDirEntry but I didn't want a flag or something to tell
381 * it not to skip the volume name */
382 InitDirHandle(sb, 0, &dh);
384 while ((err = GetDirEntry(&dh, dh.cur_index + 1, &de)) == 0) {
386 /* match the volume id entry */
387 if ((de.e.entry.attr & ATTR_LONG_NAME_MASK) == ATTR_VOLUME_ID) {
388 D(bug("[fat] found volume id entry %ld\n", dh.cur_index));
390 /* copy the name in. volume->name is a BSTR */
392 volume->name[1] = de.e.entry.name[0];
393 volume->name[12] = '\0';
395 for (i = 1; i < 11; i++)
396 volume->name[i+1] = tolower(de.e.entry.name[i]);
398 for (i = 10; i > 1; i--)
399 if (volume->name[i+1] == ' ')
400 volume->name[i+1] = '\0';
402 volume->name[0] = strlen(&(volume->name[1]));
404 /* get the volume creation date date too */
405 ConvertDate(de.e.entry.create_date, de.e.entry.create_time, &volume->create_time);
407 D(bug("[fat] volume name is '%s'\n", &(volume->name[1])));
409 return 0;
412 /* bail out if we hit the end of the dir */
413 if (de.e.entry.name[0] == 0x00) {
414 D(bug("[fat] found end-of-directory marker, volume name entry not found\n"));
415 err = ERROR_OBJECT_NOT_FOUND;
416 break;
420 ReleaseDirHandle(&dh);
421 return err;
424 void FreeFATSuper(struct FSSuper *sb) {
425 kprintf("\tRemoving Super Block from memory\n");
426 FreeVecPooled(glob->mempool, sb->fat_blocks);
427 sb->fat_blocks = NULL;
430 LONG CompareFATSuper(struct FSSuper *s1, struct FSSuper *s2) {
431 LONG res;
433 if ((res = memcmp(s1->volume.name, s2->volume.name, s1->volume.name[0])) != 0)
434 return res;
436 return s1->volume_id - s2->volume_id;
440 /* see how many unused clusters are available */
441 void CountFreeClusters(struct FSSuper *sb) {
442 ULONG cluster = 0;
443 ULONG free = 0;
445 /* don't calculate this if we already have it */
446 if (sb->free_clusters != 0xffffffff)
447 return;
449 /* loop over all the data clusters */
450 for (cluster = 2; cluster < sb->clusters_count + 2; cluster++)
452 /* record the free ones */
453 if (GET_NEXT_CLUSTER(sb, cluster) == 0)
454 free++;
456 /* put the value away for later */
457 sb->free_clusters = free;
459 kprintf("\tfree clusters: %ld\n", free);
462 static const UWORD mdays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 143, 273, 304, 334 };
464 void ConvertDate(UWORD date, UWORD time, struct DateStamp *ds) {
465 UBYTE year, month, day, hours, mins, secs;
466 UBYTE nleap;
468 /* date bits: yyyy yyym mmmd dddd */
469 year = (date & 0xfe00) >> 9; /* bits 15-9 */
470 month = (date & 0x01e0) >> 5; /* bits 8-5 */
471 day = date & 0x001f; /* bits 4-0 */
473 /* time bits: hhhh hmmm mmms ssss */
474 hours = (time & 0xf800) >> 11; /* bits 15-11 */
475 mins = (time & 0x07e0) >> 5; /* bits 8-5 */
476 secs = time & 0x001f; /* bits 4-0 */
478 D(bug("[fat] convert date: year %d month %d day %d hours %d mins %d secs %d\n", year, month, day, hours, mins, secs));
480 /* number of leap years in before this year. note this is only dividing by
481 * four, which is fine because FAT dates range 1980-2107. The only year in
482 * that range that is divisible by four but not a leap year is 2100. If
483 * this code is still being used then, feel free to fix it :) */
484 nleap = year >> 2;
486 /* if this year is a leap year and its March or later, adjust for this
487 * year too */
488 if (year & 0x03 && month >= 3)
489 nleap++;
491 /* calculate days since 1978-01-01 (DOS epoch):
492 * 730 days in 1978+1979, getting us to the FAT epoch 1980-01-01
493 * years * 365 days
494 * leap days
495 * days in all the months before this one
496 * day of this month */
497 ds->ds_Days = 730 + year * 365 + nleap + mdays[month-1] + day-1;
499 /* minutes since midnight */
500 ds->ds_Minute = hours * 60 + mins;
502 /* 1/50 sec ticks. FAT dates are 0-29, so we have to multiply them by two
503 * as well */
504 ds->ds_Tick = (secs << 1) * 50;
506 D(bug("[fat] convert date: days %ld minutes %ld ticks %ld\n", ds->ds_Days, ds->ds_Minute, ds->ds_Tick));