2 * Copyright © 2013 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
25 /* Partition information, block numbers */
27 static uint8_t partition_type;
28 static uint32_t partition_start, partition_end;
30 /* File system parameters */
31 static uint8_t sectors_per_cluster;
32 static uint32_t bytes_per_cluster;
33 static uint16_t reserved_sector_count;
34 static uint8_t number_fat;
35 static uint16_t root_entries;
36 static uint16_t sectors_per_fat;
37 static uint32_t fat_start;
38 static uint32_t root_start;
39 static uint32_t data_start;
42 get_u32(uint8_t *base)
44 return ((uint32_t) base[0] |
45 ((uint32_t) base[1] << 8) |
46 ((uint32_t) base[2] << 16) |
47 ((uint32_t) base[3] << 24));
51 put_u32(uint8_t *base, uint32_t value)
55 base[2] = value >> 16;
56 base[3] = value >> 24;
60 get_u16(uint8_t *base)
62 return ((uint16_t) base[0] | ((uint16_t) base[1] << 8));
66 put_u16(uint8_t *base, uint16_t value)
73 ao_fat_cluster_valid(uint16_t cluster)
75 return (2 <= cluster && cluster < 0xfff0);
78 /* Start using a block */
80 ao_fat_block_get(uint32_t block)
82 block += partition_start;
83 if (block >= partition_end)
85 return ao_bufio_get(block);
88 /* Finish using a block, 'w' is 1 if modified */
89 #define ao_fat_block_put(b,w) ao_bufio_put(b,w)
91 /* Start using a root directory entry */
93 ao_fat_root_get(uint16_t e)
95 uint32_t byte = e * 0x20;
96 uint32_t sector = byte >> 9;
97 uint16_t offset = byte & 0x1ff;
100 buf = ao_fat_block_get(root_start + sector);
106 /* Finish using a root directory entry, 'w' is 1 if modified */
108 ao_fat_root_put(uint8_t *root, uint16_t e, uint8_t write)
110 uint16_t offset = ((e * 0x20) & 0x1ff);
111 uint8_t *buf = root - offset;
113 ao_fat_block_put(buf, write);
116 /* Get the next cluster entry in the chain */
118 ao_fat_entry_read(uint16_t cluster)
125 if (!ao_fat_cluster_valid(cluster))
129 sector = cluster >> 8;
130 offset = (cluster << 1) & 0x1ff;
131 buf = ao_fat_block_get(fat_start + sector);
134 ret = buf[offset] | (buf[offset+1] << 8);
135 ao_fat_block_put(buf, 0);
140 ao_fat_entry_replace(uint16_t cluster, uint16_t new_value)
148 if (!ao_fat_cluster_valid(cluster))
152 sector = cluster >> 8;
153 offset = (cluster << 1) & 0x1ff;
154 buf = ao_fat_block_get(fat_start + sector);
157 ret = get_u16(buf + offset);
158 put_u16(buf + offset, new_value);
159 ao_fat_block_put(buf, 1);
160 for (other_fats = 1; other_fats < number_fat; other_fats++) {
161 buf = ao_fat_block_get(fat_start + other_fats * sectors_per_fat);
163 put_u16(buf + offset, new_value);
164 ao_fat_block_put(buf, 1);
172 ao_fat_clear_cluster_chain(uint16_t cluster)
174 while (ao_fat_cluster_valid(cluster))
175 cluster = ao_fat_entry_replace(cluster, 0x0000);
179 ao_fat_cluster_seek(uint16_t cluster, uint16_t distance)
182 cluster = ao_fat_entry_read(cluster);
183 if (!ao_fat_cluster_valid(cluster))
191 ao_fat_sector_seek(uint16_t cluster, uint32_t sector)
193 cluster = ao_fat_cluster_seek(cluster, sector / sectors_per_cluster);
194 if (!ao_fat_cluster_valid(cluster))
196 return data_start + (cluster-2) * sectors_per_cluster + sector % sectors_per_cluster;
199 /* Load the boot block and find the first partition */
201 ao_fat_setup_partition(void)
205 uint32_t partition_size;
207 mbr = ao_bufio_get(0);
211 /* Check the signature */
212 if (mbr[0x1fe] != 0x55 || mbr[0x1ff] != 0xaa) {
213 printf ("Invalid MBR signature %02x %02x\n",
214 mbr[0x1fe], mbr[0x1ff]);
215 ao_bufio_put(mbr, 0);
219 /* Just use the first partition */
220 partition = &mbr[0x1be];
222 partition_type = partition[4];
223 switch (partition_type) {
224 case 4: /* FAT16 up to 32M */
225 case 6: /* FAT16 over 32M */
227 case 0x0b: /* FAT32 up to 2047GB */
228 case 0x0c: /* FAT32 LBA */
231 printf ("Invalid partition type %02x\n", partition_type);
232 ao_bufio_put(mbr, 0);
236 partition_start = get_u32(partition+8);
237 partition_size = get_u32(partition+12);
238 if (partition_size == 0) {
239 printf ("Zero-sized partition\n");
240 ao_bufio_put(mbr, 0);
243 partition_end = partition_start + partition_size;
244 printf ("Partition type %02x start %08x end %08x\n",
245 partition_type, partition_start, partition_end);
246 ao_bufio_put(mbr, 0);
251 ao_fat_setup_fs(void)
253 uint8_t *boot = ao_fat_block_get(0);
258 /* Check the signature */
259 if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) {
260 printf ("Invalid BOOT signature %02x %02x\n",
261 boot[0x1fe], boot[0x1ff]);
262 ao_bufio_put(boot, 0);
266 /* Check the sector size */
267 if (get_u16(boot + 0xb) != 0x200) {
268 printf ("Invalid sector size %d\n",
269 get_u16(boot + 0xb));
270 ao_bufio_put(boot, 0);
274 sectors_per_cluster = boot[0xd];
275 bytes_per_cluster = sectors_per_cluster << 9;
276 reserved_sector_count = get_u16(boot+0xe);
277 number_fat = boot[0x10];
278 root_entries = get_u16(boot + 0x11);
279 sectors_per_fat = get_u16(boot+0x16);
281 printf ("sectors per cluster %d\n", sectors_per_cluster);
282 printf ("reserved sectors %d\n", reserved_sector_count);
283 printf ("number of FATs %d\n", number_fat);
284 printf ("root entries %d\n", root_entries);
285 printf ("sectors per fat %d\n", sectors_per_fat);
287 fat_start = reserved_sector_count;;
288 root_start = fat_start + number_fat * sectors_per_fat;
289 data_start = root_start + ((root_entries * 0x20 + 0x1ff) >> 9);
291 printf ("fat start %d\n", fat_start);
292 printf ("root start %d\n", root_start);
293 printf ("data start %d\n", data_start);
301 if (!ao_fat_setup_partition())
303 if (!ao_fat_setup_fs())
309 * Low-level directory operations
313 * Basic file operations
316 static struct ao_fat_dirent ao_file_dirent;
317 static uint32_t ao_file_offset;
320 ao_file_offset_to_sector(uint32_t offset)
322 if (offset > ao_file_dirent.size)
324 return ao_fat_sector_seek(ao_file_dirent.cluster, offset >> 9);
328 ao_fat_open(char name[11])
331 struct ao_fat_dirent dirent;
333 while (ao_fat_readdir(&entry, &dirent)) {
334 if (!memcmp(name, dirent.name, 11)) {
335 ao_file_dirent = dirent;
346 ao_fat_set_size(uint32_t size)
348 uint16_t clear_cluster = 0;
350 uint16_t first_cluster;
352 first_cluster = ao_file_dirent.cluster;
353 printf ("set size to %d\n", size);
354 if (size == ao_file_dirent.size)
357 printf ("erase file\n");
358 clear_cluster = ao_file_dirent.cluster;
364 new_num = (size + bytes_per_cluster - 1) / bytes_per_cluster;
365 old_num = (ao_file_dirent.size + bytes_per_cluster - 1) / bytes_per_cluster;
366 if (new_num < old_num) {
367 uint16_t last_cluster;
369 printf("Remove %d clusters\n", old_num - new_num);
370 /* Go find the last cluster we want to preserve in the file */
371 last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, new_num - 1);
373 printf ("Last cluster is now %04x\n", last_cluster);
374 /* Rewrite that cluster entry with 0xffff to mark the end of the chain */
375 clear_cluster = ao_fat_entry_replace(last_cluster, 0xffff);
376 } else if (new_num > old_num) {
379 uint16_t last_cluster;
382 last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, old_num - 1);
386 need = new_num - old_num;
387 printf ("Need %d clusters\n", need);
388 /* See if there are enough free clusters in the file system */
389 for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) {
390 if (!ao_fat_entry_read(free)) {
391 printf ("\tCluster %04x available\n", free);
395 /* Still need some, tell the user that we've failed */
397 printf ("File system full\n");
401 need = new_num - old_num;
402 /* Now go allocate those clusters */
403 for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) {
404 if (!ao_fat_entry_read(free)) {
405 printf ("\tAllocate %04x\n", free);
407 ao_fat_entry_replace(last_cluster, free);
409 first_cluster = free;
414 /* Mark the new end of the chain */
415 ao_fat_entry_replace(last_cluster, 0xffff);
419 /* Deallocate clusters off the end of the file */
420 if (ao_fat_cluster_valid(clear_cluster)) {
421 printf ("Clear clusters starting with %04x\n", clear_cluster);
422 ao_fat_clear_cluster_chain(clear_cluster);
425 dent = ao_fat_root_get(ao_file_dirent.entry);
428 put_u32(dent + 0x1c, size);
429 put_u16(dent + 0x1a, first_cluster);
430 ao_fat_root_put(dent, ao_file_dirent.entry, 1);
431 ao_file_dirent.size = size;
432 ao_file_dirent.cluster = first_cluster;
437 ao_fat_creat(char name[11])
441 if (ao_fat_open(name))
442 return ao_fat_set_size(0);
444 for (entry = 0; entry < root_entries; entry++) {
445 uint8_t *dent = ao_fat_root_get(entry);
447 if (dent[0] == AO_FAT_DENT_EMPTY ||
448 dent[0] == AO_FAT_DENT_END) {
449 memmove(dent, name, 11);
454 put_u16(dent + 0x0e, 0);
456 put_u16(dent + 0x10, 0);
458 put_u16(dent + 0x12, 0);
459 /* XXX FAT32 high cluster bytes */
460 put_u16(dent + 0x14, 0);
462 put_u16(dent + 0x16, 0);
464 put_u16(dent + 0x18, 0);
466 put_u16(dent + 0x1a, 0);
468 put_u32(dent + 0x1c, 0);
469 ao_fat_root_put(dent, entry, 1);
470 return ao_fat_open(name);
479 memset(&ao_file_dirent, '\0', sizeof (struct ao_fat_dirent));
484 ao_fat_read(uint8_t *dest, int len)
492 if (ao_file_offset + len > ao_file_dirent.size)
493 len = ao_file_dirent.size - ao_file_offset;
496 offset = ao_file_offset & 0x1ff;
497 if (offset + len < 512)
500 this_time = 512 - offset;
502 sector = ao_file_offset_to_sector(ao_file_offset);
503 if (sector == 0xffffffff)
505 buf = ao_fat_block_get(sector);
508 memcpy(dest, buf + offset, this_time);
509 ao_fat_block_put(buf, 0);
514 ao_file_offset += this_time;
520 ao_fat_write(uint8_t *src, int len)
528 if (ao_file_offset + len > ao_file_dirent.size) {
529 if (!ao_fat_set_size(ao_file_offset + len))
534 offset = ao_file_offset & 0x1ff;
535 if (offset + len < 512)
538 this_time = 512 - offset;
540 sector = ao_file_offset_to_sector(ao_file_offset);
541 if (sector == 0xffffffff)
543 buf = ao_fat_block_get(sector);
546 memcpy(buf + offset, src, this_time);
547 ao_fat_block_put(buf, 1);
552 ao_file_offset += this_time;
558 ao_fat_seek(int32_t pos, uint8_t whence)
561 case AO_FAT_SEEK_SET:
562 ao_file_offset = pos;
564 case AO_FAT_SEEK_CUR:
565 ao_file_offset += pos;
567 case AO_FAT_SEEK_END:
568 ao_file_offset = ao_file_dirent.size + pos;
571 if (ao_file_offset > ao_file_dirent.size)
572 ao_fat_set_size(ao_file_offset);
573 return ao_file_offset;
577 ao_fat_unlink(char name[11])
580 struct ao_fat_dirent dirent;
582 while (ao_fat_readdir(&entry, &dirent)) {
583 if (memcmp(name, dirent.name, 11) == 0) {
587 ao_fat_clear_cluster_chain(dirent.cluster);
588 next = ao_fat_root_get(dirent.entry + 1);
589 if (next && next[0] != AO_FAT_DENT_END)
590 delete = AO_FAT_DENT_EMPTY;
592 delete = AO_FAT_DENT_END;
594 ao_fat_root_put(next, dirent.entry + 1, 0);
595 ent = ao_fat_root_get(dirent.entry);
597 memset(ent, '\0', 0x20);
599 ao_fat_root_put(ent, dirent.entry, 1);
609 ao_fat_rename(char old[11], char new[11])
615 ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent)
619 if (*entry >= root_entries)
622 dent = ao_fat_root_get(*entry);
624 if (dent[0] == AO_FAT_DENT_END) {
625 ao_fat_root_put(dent, *entry, 0);
628 if (dent[0] != AO_FAT_DENT_EMPTY &&
629 (dent[0x0b] & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == 0)
631 ao_fat_root_put(dent, *entry, 0);
634 memcpy(dirent->name, dent, 11);
635 dirent->attr = dent[0xb];
636 dirent->size = get_u32(dent+0x1c);
637 dirent->cluster = get_u16(dent+0x1a);
638 dirent->entry = *entry;
639 ao_fat_root_put(dent, *entry, 0);
648 struct ao_fat_dirent dirent;
650 while (ao_fat_readdir(&entry, &dirent)) {
651 printf ("%-8.8s.%-3.3s %02x %d\n",
652 dirent.name, dirent.name + 8, dirent.attr, dirent.size);
663 static const struct ao_cmds ao_fat_cmds[] = {
664 { ao_fat_test, "F\0Test FAT" },
672 ao_cmd_register(&ao_fat_cmds[0]);