/* * Copyright © 2013 Keith Packard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef AO_FAT_TEST #include "ao.h" #endif #include "ao_fat.h" #include "ao_bufio.h" /* Partition information, block numbers */ static uint8_t partition_type; static uint32_t partition_start, partition_end; /* File system parameters */ static uint8_t sectors_per_cluster; static uint32_t bytes_per_cluster; static uint16_t reserved_sector_count; static uint8_t number_fat; static uint16_t root_entries; static uint16_t sectors_per_fat; static uint32_t fat_start; static uint32_t root_start; static uint32_t data_start; static uint32_t get_u32(uint8_t *base) { return ((uint32_t) base[0] | ((uint32_t) base[1] << 8) | ((uint32_t) base[2] << 16) | ((uint32_t) base[3] << 24)); } static void put_u32(uint8_t *base, uint32_t value) { base[0] = value; base[1] = value >> 8; base[2] = value >> 16; base[3] = value >> 24; } static uint16_t get_u16(uint8_t *base) { return ((uint16_t) base[0] | ((uint16_t) base[1] << 8)); } static void put_u16(uint8_t *base, uint16_t value) { base[0] = value; base[1] = value >> 8; } static uint8_t ao_fat_cluster_valid(uint16_t cluster) { return (2 <= cluster && cluster < 0xfff0); } /* Start using a block */ static uint8_t * ao_fat_block_get(uint32_t block) { block += partition_start; if (block >= partition_end) return NULL; return ao_bufio_get(block); } /* Finish using a block, 'w' is 1 if modified */ #define ao_fat_block_put(b,w) ao_bufio_put(b,w) /* Start using a root directory entry */ static uint8_t * ao_fat_root_get(uint16_t e) { uint32_t byte = e * 0x20; uint32_t sector = byte >> 9; uint16_t offset = byte & 0x1ff; uint8_t *buf; buf = ao_fat_block_get(root_start + sector); if (!buf) return NULL; return buf + offset; } /* Finish using a root directory entry, 'w' is 1 if modified */ static void ao_fat_root_put(uint8_t *root, uint16_t e, uint8_t write) { uint16_t offset = ((e * 0x20) & 0x1ff); uint8_t *buf = root - offset; ao_fat_block_put(buf, write); } /* Get the next cluster entry in the chain */ static uint16_t ao_fat_entry_read(uint16_t cluster) { uint32_t sector; uint16_t offset; uint8_t *buf; uint16_t ret; if (!ao_fat_cluster_valid(cluster)) return 0xfff7; cluster -= 2; sector = cluster >> 8; offset = (cluster << 1) & 0x1ff; buf = ao_fat_block_get(fat_start + sector); if (!buf) return 0; ret = buf[offset] | (buf[offset+1] << 8); ao_fat_block_put(buf, 0); return ret; } static uint16_t ao_fat_entry_replace(uint16_t cluster, uint16_t new_value) { uint32_t sector; uint16_t offset; uint8_t *buf; uint16_t ret; uint8_t other_fats; if (!ao_fat_cluster_valid(cluster)) return 0; cluster -= 2; sector = cluster >> 8; offset = (cluster << 1) & 0x1ff; buf = ao_fat_block_get(fat_start + sector); if (!buf) return 0; ret = get_u16(buf + offset); put_u16(buf + offset, new_value); ao_fat_block_put(buf, 1); for (other_fats = 1; other_fats < number_fat; other_fats++) { buf = ao_fat_block_get(fat_start + other_fats * sectors_per_fat); if (buf) { put_u16(buf + offset, new_value); ao_fat_block_put(buf, 1); } } return ret; } static void ao_fat_clear_cluster_chain(uint16_t cluster) { while (ao_fat_cluster_valid(cluster)) cluster = ao_fat_entry_replace(cluster, 0x0000); } static uint16_t ao_fat_cluster_seek(uint16_t cluster, uint16_t distance) { while (distance) { cluster = ao_fat_entry_read(cluster); if (!ao_fat_cluster_valid(cluster)) break; distance--; } return cluster; } static uint32_t ao_fat_sector_seek(uint16_t cluster, uint32_t sector) { cluster = ao_fat_cluster_seek(cluster, sector / sectors_per_cluster); if (!ao_fat_cluster_valid(cluster)) return 0xffffffff; return data_start + (cluster-2) * sectors_per_cluster + sector % sectors_per_cluster; } /* Load the boot block and find the first partition */ static uint8_t ao_fat_setup_partition(void) { uint8_t *mbr; uint8_t *partition; uint32_t partition_size; mbr = ao_bufio_get(0); if (!mbr) return 0; /* Check the signature */ if (mbr[0x1fe] != 0x55 || mbr[0x1ff] != 0xaa) { printf ("Invalid MBR signature %02x %02x\n", mbr[0x1fe], mbr[0x1ff]); ao_bufio_put(mbr, 0); return 0; } /* Just use the first partition */ partition = &mbr[0x1be]; partition_type = partition[4]; switch (partition_type) { case 4: /* FAT16 up to 32M */ case 6: /* FAT16 over 32M */ break; case 0x0b: /* FAT32 up to 2047GB */ case 0x0c: /* FAT32 LBA */ break; default: printf ("Invalid partition type %02x\n", partition_type); ao_bufio_put(mbr, 0); return 0; } partition_start = get_u32(partition+8); partition_size = get_u32(partition+12); if (partition_size == 0) { printf ("Zero-sized partition\n"); ao_bufio_put(mbr, 0); return 0; } partition_end = partition_start + partition_size; printf ("Partition type %02x start %08x end %08x\n", partition_type, partition_start, partition_end); ao_bufio_put(mbr, 0); return 1; } static uint8_t ao_fat_setup_fs(void) { uint8_t *boot = ao_fat_block_get(0); if (!boot) return 0; /* Check the signature */ if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) { printf ("Invalid BOOT signature %02x %02x\n", boot[0x1fe], boot[0x1ff]); ao_bufio_put(boot, 0); return 0; } /* Check the sector size */ if (get_u16(boot + 0xb) != 0x200) { printf ("Invalid sector size %d\n", get_u16(boot + 0xb)); ao_bufio_put(boot, 0); return 0; } sectors_per_cluster = boot[0xd]; bytes_per_cluster = sectors_per_cluster << 9; reserved_sector_count = get_u16(boot+0xe); number_fat = boot[0x10]; root_entries = get_u16(boot + 0x11); sectors_per_fat = get_u16(boot+0x16); printf ("sectors per cluster %d\n", sectors_per_cluster); printf ("reserved sectors %d\n", reserved_sector_count); printf ("number of FATs %d\n", number_fat); printf ("root entries %d\n", root_entries); printf ("sectors per fat %d\n", sectors_per_fat); fat_start = reserved_sector_count;; root_start = fat_start + number_fat * sectors_per_fat; data_start = root_start + ((root_entries * 0x20 + 0x1ff) >> 9); printf ("fat start %d\n", fat_start); printf ("root start %d\n", root_start); printf ("data start %d\n", data_start); return 1; } static uint8_t ao_fat_setup(void) { if (!ao_fat_setup_partition()) return 0; if (!ao_fat_setup_fs()) return 0; return 1; } /* * Low-level directory operations */ /* * Basic file operations */ static struct ao_fat_dirent ao_file_dirent; static uint32_t ao_file_offset; static uint32_t ao_file_offset_to_sector(uint32_t offset) { if (offset > ao_file_dirent.size) return 0xffffffff; return ao_fat_sector_seek(ao_file_dirent.cluster, offset >> 9); } uint8_t ao_fat_open(char name[11]) { uint16_t entry = 0; struct ao_fat_dirent dirent; while (ao_fat_readdir(&entry, &dirent)) { if (!memcmp(name, dirent.name, 11)) { ao_file_dirent = dirent; ao_file_offset = 0; return 1; } } return 0; } static uint8_t ao_fat_set_size(uint32_t size) { uint16_t clear_cluster = 0; uint8_t *dent; uint16_t first_cluster; first_cluster = ao_file_dirent.cluster; printf ("set size to %d\n", size); if (size == ao_file_dirent.size) return 1; if (size == 0) { printf ("erase file\n"); clear_cluster = ao_file_dirent.cluster; first_cluster = 0; } else { uint16_t new_num; uint16_t old_num; new_num = (size + bytes_per_cluster - 1) / bytes_per_cluster; old_num = (ao_file_dirent.size + bytes_per_cluster - 1) / bytes_per_cluster; if (new_num < old_num) { uint16_t last_cluster; printf("Remove %d clusters\n", old_num - new_num); /* Go find the last cluster we want to preserve in the file */ last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, new_num - 1); printf ("Last cluster is now %04x\n", last_cluster); /* Rewrite that cluster entry with 0xffff to mark the end of the chain */ clear_cluster = ao_fat_entry_replace(last_cluster, 0xffff); } else if (new_num > old_num) { uint16_t need; uint16_t free; uint16_t last_cluster; if (old_num) last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, old_num - 1); else last_cluster = 0; need = new_num - old_num; printf ("Need %d clusters\n", need); /* See if there are enough free clusters in the file system */ for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) { if (!ao_fat_entry_read(free)) { printf ("\tCluster %04x available\n", free); need--; } } /* Still need some, tell the user that we've failed */ if (need) { printf ("File system full\n"); return 0; } need = new_num - old_num; /* Now go allocate those clusters */ for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) { if (!ao_fat_entry_read(free)) { printf ("\tAllocate %04x\n", free); if (last_cluster) ao_fat_entry_replace(last_cluster, free); else first_cluster = free; last_cluster = free; need--; } } /* Mark the new end of the chain */ ao_fat_entry_replace(last_cluster, 0xffff); } } /* Deallocate clusters off the end of the file */ if (ao_fat_cluster_valid(clear_cluster)) { printf ("Clear clusters starting with %04x\n", clear_cluster); ao_fat_clear_cluster_chain(clear_cluster); } dent = ao_fat_root_get(ao_file_dirent.entry); if (!dent) return 0; put_u32(dent + 0x1c, size); put_u16(dent + 0x1a, first_cluster); ao_fat_root_put(dent, ao_file_dirent.entry, 1); ao_file_dirent.size = size; ao_file_dirent.cluster = first_cluster; return 1; } uint8_t ao_fat_creat(char name[11]) { uint16_t entry; if (ao_fat_open(name)) return ao_fat_set_size(0); for (entry = 0; entry < root_entries; entry++) { uint8_t *dent = ao_fat_root_get(entry); if (dent[0] == AO_FAT_DENT_EMPTY || dent[0] == AO_FAT_DENT_END) { memmove(dent, name, 11); dent[0x0b] = 0x00; dent[0x0c] = 0x00; dent[0x0d] = 0x00; /* XXX fix time */ put_u16(dent + 0x0e, 0); /* XXX fix date */ put_u16(dent + 0x10, 0); /* XXX fix date */ put_u16(dent + 0x12, 0); /* XXX FAT32 high cluster bytes */ put_u16(dent + 0x14, 0); /* XXX fix time */ put_u16(dent + 0x16, 0); /* XXX fix date */ put_u16(dent + 0x18, 0); /* cluster number */ put_u16(dent + 0x1a, 0); /* size */ put_u32(dent + 0x1c, 0); ao_fat_root_put(dent, entry, 1); return ao_fat_open(name); } } return 0; } void ao_fat_close(void) { memset(&ao_file_dirent, '\0', sizeof (struct ao_fat_dirent)); ao_bufio_flush(); } int ao_fat_read(uint8_t *dest, int len) { uint32_t sector; uint16_t this_time; uint16_t offset; uint8_t *buf; int ret = 0; if (ao_file_offset + len > ao_file_dirent.size) len = ao_file_dirent.size - ao_file_offset; while (len) { offset = ao_file_offset & 0x1ff; if (offset + len < 512) this_time = len; else this_time = 512 - offset; sector = ao_file_offset_to_sector(ao_file_offset); if (sector == 0xffffffff) break; buf = ao_fat_block_get(sector); if (!buf) break; memcpy(dest, buf + offset, this_time); ao_fat_block_put(buf, 0); ret += this_time; len -= this_time; dest += this_time; ao_file_offset += this_time; } return ret; } int ao_fat_write(uint8_t *src, int len) { uint32_t sector; uint16_t this_time; uint16_t offset; uint8_t *buf; int ret = 0; if (ao_file_offset + len > ao_file_dirent.size) { if (!ao_fat_set_size(ao_file_offset + len)) return 0; } while (len) { offset = ao_file_offset & 0x1ff; if (offset + len < 512) this_time = len; else this_time = 512 - offset; sector = ao_file_offset_to_sector(ao_file_offset); if (sector == 0xffffffff) break; buf = ao_fat_block_get(sector); if (!buf) break; memcpy(buf + offset, src, this_time); ao_fat_block_put(buf, 1); ret += this_time; len -= this_time; src += this_time; ao_file_offset += this_time; } return 0; } uint32_t ao_fat_seek(int32_t pos, uint8_t whence) { switch (whence) { case AO_FAT_SEEK_SET: ao_file_offset = pos; break; case AO_FAT_SEEK_CUR: ao_file_offset += pos; break; case AO_FAT_SEEK_END: ao_file_offset = ao_file_dirent.size + pos; break; } if (ao_file_offset > ao_file_dirent.size) ao_fat_set_size(ao_file_offset); return ao_file_offset; } uint8_t ao_fat_unlink(char name[11]) { uint16_t entry = 0; struct ao_fat_dirent dirent; while (ao_fat_readdir(&entry, &dirent)) { if (memcmp(name, dirent.name, 11) == 0) { uint8_t *next; uint8_t *ent; uint8_t delete; ao_fat_clear_cluster_chain(dirent.cluster); next = ao_fat_root_get(dirent.entry + 1); if (next && next[0] != AO_FAT_DENT_END) delete = AO_FAT_DENT_EMPTY; else delete = AO_FAT_DENT_END; if (next) ao_fat_root_put(next, dirent.entry + 1, 0); ent = ao_fat_root_get(dirent.entry); if (ent) { memset(ent, '\0', 0x20); *ent = delete; ao_fat_root_put(ent, dirent.entry, 1); } ao_bufio_flush(); return 1; } } return 0; } uint8_t ao_fat_rename(char old[11], char new[11]) { return 0; } uint8_t ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent) { uint8_t *dent; if (*entry >= root_entries) return 0; for (;;) { dent = ao_fat_root_get(*entry); if (dent[0] == AO_FAT_DENT_END) { ao_fat_root_put(dent, *entry, 0); return 0; } if (dent[0] != AO_FAT_DENT_EMPTY && (dent[0x0b] & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == 0) break; ao_fat_root_put(dent, *entry, 0); (*entry)++; } memcpy(dirent->name, dent, 11); dirent->attr = dent[0xb]; dirent->size = get_u32(dent+0x1c); dirent->cluster = get_u16(dent+0x1a); dirent->entry = *entry; ao_fat_root_put(dent, *entry, 0); (*entry)++; return 1; } static void ao_fat_list(void) { uint16_t entry = 0; struct ao_fat_dirent dirent; while (ao_fat_readdir(&entry, &dirent)) { printf ("%-8.8s.%-3.3s %02x %d\n", dirent.name, dirent.name + 8, dirent.attr, dirent.size); } } static void ao_fat_test(void) { ao_fat_setup(); ao_fat_list(); } static const struct ao_cmds ao_fat_cmds[] = { { ao_fat_test, "F\0Test FAT" }, { 0, NULL }, }; void ao_fat_init(void) { ao_bufio_init(); ao_cmd_register(&ao_fat_cmds[0]); }