X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=bootcensus;a=blobdiff_plain;f=src%2Ffsfat.c;fp=src%2Ffsfat.c;h=d0b347ad537d77413d97e66e4e78b90a9dfce8b3;hp=0000000000000000000000000000000000000000;hb=137aca58851528e77004dc3909826f24bbdbde87;hpb=81c11bdd80190ec319a82b0402173cfb65fcbf72 diff --git a/src/fsfat.c b/src/fsfat.c new file mode 100644 index 0000000..d0b347a --- /dev/null +++ b/src/fsfat.c @@ -0,0 +1,965 @@ +/* +pcboot - bootable PC demo/game kernel +Copyright (C) 2018-2019 John Tsiombikas + +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, either version 3 of the License, or +(at your option) any later version. + +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, see . +*/ +#include +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "bootdev.h" +#include "boot.h" +#include "panic.h" + +#define MAX_NAME 195 + +#define DIRENT_UNUSED 0xe5 + +#define DENT_IS_NULL(dent) (((unsigned char*)(dent))[0] == 0) +#define DENT_IS_UNUSED(dent) (((unsigned char*)(dent))[0] == DIRENT_UNUSED) + +#define ATTR_RO 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLID 0x08 +#define ATTR_DIR 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LFN 0xf + + +enum { FAT12, FAT16, FAT32, EXFAT }; +static const char *typestr[] = { "fat12", "fat16", "fat32", "exfat" }; + +struct fat_dirent; +struct fat_dir; + +struct fatfs { + int type; + int dev; + uint64_t start_sect; + uint32_t size; + int cluster_size; + int fat_size; + uint32_t fat_sect; + uint32_t root_sect; + int root_size; + uint32_t first_data_sect; + uint32_t num_data_sect; + uint32_t num_clusters; + char label[12]; + + void *fat; + struct fat_dir *rootdir; + unsigned int clust_mask; + int clust_shift; +}; + +struct bparam { + uint8_t jmp[3]; + unsigned char fmtid[8]; + uint16_t sect_bytes; + uint8_t cluster_size; + uint16_t reserved_sect; + uint8_t num_fats; + uint16_t num_dirent; + uint16_t num_sectors; + uint8_t medium; + uint16_t fat_size; + uint16_t track_size; + uint16_t num_heads; + uint32_t num_hidden; + uint32_t num_sectors32; +} __attribute__((packed)); + +struct bparam_ext16 { + uint8_t driveno; + uint8_t ntflags; + uint8_t signature; + uint32_t volume_id; + char label[11]; + char sysid[8]; +} __attribute__((packed)); + +struct bparam_ext32 { + uint32_t fat_size; + uint16_t flags; + uint16_t version; + uint32_t root_clust; + uint16_t fsinfo_sect; + uint16_t backup_boot_sect; + char junk[12]; + uint8_t driveno; + uint8_t ntflags; + uint8_t signature; + uint32_t volume_id; + char label[11]; + char sysid[8]; +} __attribute__((packed)); + +struct fat_dirent { + char name[11]; + uint8_t attr; + uint8_t junk; + uint8_t ctime_tenths; + uint16_t ctime_halfsec; + uint16_t ctime_date; + uint16_t atime_date; + uint16_t first_cluster_high; /* 0 for FAT12 and FAT16 */ + uint16_t mtime_hsec; + uint16_t mtime_date; + uint16_t first_cluster_low; + uint32_t size_bytes; +} __attribute__((packed)); + +struct fat_lfnent { + uint8_t seq; + uint16_t part1[5]; + uint8_t attr; + uint8_t type; + uint8_t csum; + uint16_t part2[6]; + uint16_t zero; + uint16_t part3[2]; +} __attribute__((packed)); + + +struct fat_dir { + struct fatfs *fatfs; + + struct fat_dirent *ent; + int max_nent; + + struct fs_dirent *fsent; + int fsent_size; + int cur_ent; + + int ref; +}; + +struct fat_file { + struct fat_dirent ent; + int32_t first_clust; + int64_t cur_pos; + int32_t cur_clust; /* cluster number corresponding to cur_pos */ + + char *clustbuf; + int buf_valid; +}; + + +static void destroy(struct filesys *fs); + +static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags); +static void close(struct fs_node *node); +static long fsize(struct fs_node *node); +static int seek(struct fs_node *node, int offs, int whence); +static long tell(struct fs_node *node); +static int read(struct fs_node *node, void *buf, int sz); +static int write(struct fs_node *node, void *buf, int sz); +static int rewinddir(struct fs_node *node); +static struct fs_dirent *readdir(struct fs_node *node); +static int rename(struct fs_node *node, const char *name); +static int remove(struct fs_node *node); + +static struct fat_dir *load_dir(struct fatfs *fs, struct fat_dirent *dent); +static void parse_dir_entries(struct fat_dir *dir); +static void free_dir(struct fat_dir *dir); + +static struct fat_file *init_file(struct fatfs *fatfs, struct fat_dirent *dent); +static void free_file(struct fat_file *file); + +static int read_sectors(int dev, uint64_t sidx, int count, void *sect); +static int read_cluster(struct fatfs *fatfs, uint32_t addr, void *clust); +static int dent_filename(struct fat_dirent *dent, struct fat_dirent *prev, char *buf); +static struct fs_dirent *find_entry(struct fat_dir *dir, const char *name); + +static uint32_t read_fat(struct fatfs *fatfs, uint32_t addr); +static int32_t next_cluster(struct fatfs *fatfs, int32_t addr); +static int32_t find_cluster(struct fatfs *fatfs, int count, int32_t clust); + +/* static void dbg_printdir(struct fat_dirent *dir, int max_entries); */ +static void clean_trailws(char *s); + +static struct fs_operations fs_fat_ops = { + destroy, + open, close, + + fsize, + seek, tell, + read, write, + + rewinddir, readdir, + + rename, remove +}; + +static unsigned char sectbuf[512]; +static int max_sect_once; + +struct filesys *fsfat_create(int dev, uint64_t start, uint64_t size) +{ + int num_read; + char *endp, *ptr; + struct filesys *fs; + struct fatfs *fatfs; + struct fat_dir *rootdir; + struct bparam *bpb; + struct bparam_ext16 *bpb16; + struct bparam_ext32 *bpb32; + + max_sect_once = ((unsigned char*)0xa0000 - low_mem_buffer) / 512; + /* some BIOS implementations have a maximum limit of 127 sectors */ + if(max_sect_once > 127) max_sect_once = 127; + + if(read_sectors(dev, start, 1, sectbuf) == -1) { + return 0; + } + bpb = (struct bparam*)sectbuf; + bpb16 = (struct bparam_ext16*)(sectbuf + sizeof *bpb); + bpb32 = (struct bparam_ext32*)(sectbuf + sizeof *bpb); + + if(bpb->jmp[0] != 0xeb || bpb->jmp[2] != 0x90) { + return 0; + } + if(bpb->sect_bytes != 512) { + return 0; + } + + if(!(fatfs = malloc(sizeof *fatfs))) { + panic("FAT: create failed to allocate memory for the fat filesystem data\n"); + } + memset(fatfs, 0, sizeof *fatfs); + fatfs->dev = dev < 0 ? boot_drive_number : dev; + fatfs->start_sect = start; + fatfs->size = bpb->num_sectors ? bpb->num_sectors : bpb->num_sectors32; + fatfs->cluster_size = bpb->cluster_size; + fatfs->fat_size = bpb->fat_size ? bpb->fat_size : bpb32->fat_size; + fatfs->fat_sect = bpb->reserved_sect; + fatfs->root_sect = fatfs->fat_sect + fatfs->fat_size * bpb->num_fats; + fatfs->root_size = (bpb->num_dirent * sizeof(struct fat_dirent) + 511) / 512; + fatfs->first_data_sect = bpb->reserved_sect + bpb->num_fats * fatfs->fat_size + fatfs->root_size; + fatfs->num_data_sect = fatfs->size - (bpb->reserved_sect + bpb->num_fats * fatfs->fat_size + fatfs->root_size); + fatfs->num_clusters = fatfs->num_data_sect / fatfs->cluster_size; + + if(fatfs->num_clusters < 4085) { + fatfs->type = FAT12; + } else if(fatfs->num_clusters < 65525) { + fatfs->type = FAT16; + } else if(fatfs->num_clusters < 268435445) { + fatfs->type = FAT32; + } else { + fatfs->type = EXFAT; + } + + switch(fatfs->type) { + case FAT16: + memcpy(fatfs->label, bpb16->label, sizeof bpb16->label); + break; + + case FAT32: + case EXFAT: + fatfs->root_sect = bpb32->root_clust / fatfs->cluster_size; + fatfs->root_size = 0; + memcpy(fatfs->label, bpb32->label, sizeof bpb32->label); + break; + + default: + break; + } + + endp = fatfs->label + sizeof fatfs->label - 2; + while(endp >= fatfs->label && isspace(*endp)) { + *endp-- = 0; + } + + /* read the FAT */ + if(!(fatfs->fat = malloc(fatfs->fat_size * 512))) { + panic("FAT: failed to allocate memory for the FAT (%lu bytes)\n", (unsigned long)fatfs->fat_size * 512); + } + ptr = fatfs->fat; + num_read = 0; + while(num_read < fatfs->fat_size) { + int count = fatfs->fat_size - num_read; + if(count > max_sect_once) count = max_sect_once; + + read_sectors(dev, fatfs->start_sect + fatfs->fat_sect + num_read, count, ptr); + ptr += count * 512; + num_read += count; + } + + /* open root directory */ + if(fatfs->type == FAT32) { + struct fat_dirent ent; + ent.attr = ATTR_DIR; + ent.first_cluster_low = bpb32->root_clust; + ent.first_cluster_high = bpb32->root_clust >> 16; + if(!(rootdir = load_dir(fatfs, &ent))) { + panic("FAT: failed to load FAT32 root directory\n"); + } + + } else { + if(!(rootdir = malloc(sizeof *rootdir))) { + panic("FAT: failed to allocate root directory structure\n"); + } + rootdir->fatfs = fatfs; + + rootdir->max_nent = fatfs->root_size * 512 / sizeof(struct fat_dirent); + if(!(rootdir->ent = malloc(fatfs->root_size * 512))) { + panic("FAT: failed to allocate memory for the root directory\n"); + } + ptr = (char*)rootdir->ent; + + num_read = 0; + while(num_read < fatfs->root_size) { + int count = fatfs->root_size - num_read; + if(count > max_sect_once) count = max_sect_once; + + read_sectors(dev, fatfs->start_sect + fatfs->root_sect + num_read, count, ptr); + ptr += count * 512; + num_read += count; + } + + parse_dir_entries(rootdir); + } + rootdir->ref = 1; + fatfs->rootdir = rootdir; + + /* assume cluster_size is a power of two */ + fatfs->clust_mask = (fatfs->cluster_size * 512) - 1; + fatfs->clust_shift = 0; + while((1 << fatfs->clust_shift) < (fatfs->cluster_size * 512)) { + fatfs->clust_shift++; + } + + /* fill generic fs structure */ + if(!(fs = malloc(sizeof *fs))) { + panic("FAT: create failed to allocate memory for the filesystem structure\n"); + } + fs->type = FSTYPE_FAT; + fs->name = fatfs->label; + fs->fsop = &fs_fat_ops; + fs->data = fatfs; + + + printf("opened %s filesystem dev: %x, start: %lld\n", typestr[fatfs->type], fatfs->dev, start); + if(fatfs->label[0]) { + printf(" volume label: %s\n", fatfs->label); + } + + return fs; +} + +static void destroy(struct filesys *fs) +{ + struct fatfs *fatfs = fs->data; + free(fatfs); + free(fs); +} + +static struct fs_node *open(struct filesys *fs, const char *path, unsigned int flags) +{ + char name[MAX_NAME]; + struct fatfs *fatfs = fs->data; + struct fat_dir *dir, *newdir; + struct fs_dirent *dent; + struct fat_dirent *fatdent; + struct fs_node *node; + + if(path[0] == '/') { + dir = fatfs->rootdir; + path = fs_path_skipsep((char*)path); + } else { + if(cwdnode->fs->type != FSTYPE_FAT) { + return 0; + } + dir = cwdnode->data; + } + + while(*path) { + if(!dir) { + /* we have more path components, yet the last one wasn't a dir */ + errno = ENOTDIR; + return 0; + } + + path = fs_path_next((char*)path, name, sizeof name); + + if(name[0] == '.' && name[1] == 0) { + continue; + } + + if(!(dent = find_entry(dir, name))) { + errno = ENOENT; + return 0; + } + fatdent = dent->data; + + if((fatdent->first_cluster_low | fatdent->first_cluster_high) == 0) { + if(fatdent->attr == ATTR_DIR) { + /* ".." entries back to the root directory seem to have a 0 + * cluster address as a special case + */ + newdir = fatfs->rootdir; + } else { + return 0; /* but we can't have 0-address files (right?) */ + } + } else { + newdir = fatdent->attr == ATTR_DIR ? load_dir(fatfs, fatdent) : 0; + } + if(dir != fatfs->rootdir && dir != cwdnode->data) { + free_dir(dir); + } + dir = newdir; + } + + + if(!(node = malloc(sizeof *node))) { + panic("FAT: open failed to allocate fs_node structure\n"); + } + node->fs = fs; + if(dir) { + if(dir == fatfs->rootdir) { + if(!(newdir = malloc(sizeof *newdir))) { + panic("FAT: failed to allocate directory structure\n"); + } + *newdir = *dir; + dir = newdir; + dir->ref = 0; + } + node->type = FSNODE_DIR; + node->data = dir; + dir->cur_ent = 0; + dir->ref++; + } else { + node->type = FSNODE_FILE; + if(!(node->data = init_file(fatfs, fatdent))) { + panic("FAT: failed to allocate file entry structure\n"); + } + } + + return node; +} + +static void close(struct fs_node *node) +{ + switch(node->type) { + case FSNODE_FILE: + free_file(node->data); + break; + + case FSNODE_DIR: + free_dir(node->data); + break; + + default: + panic("FAT: close node is not a file nor a dir\n"); + } + + free(node); +} + +static long fsize(struct fs_node *node) +{ + struct fat_file *file; + + if(node->type != FSNODE_FILE) { + return -1; + } + file = node->data; + return file->ent.size_bytes; +} + +static int seek(struct fs_node *node, int offs, int whence) +{ + struct fatfs *fatfs; + struct fat_file *file; + int64_t new_pos; + unsigned int cur_clust_idx, new_clust_idx; + + if(node->type != FSNODE_FILE) { + return -1; + } + + fatfs = node->fs->data; + file = node->data; + + switch(whence) { + case FSSEEK_SET: + new_pos = offs; + break; + + case FSSEEK_CUR: + new_pos = file->cur_pos + offs; + break; + + case FSSEEK_END: + new_pos = file->ent.size_bytes + offs; + break; + + default: + return -1; + } + + if(new_pos < 0) new_pos = 0; + + cur_clust_idx = file->cur_pos >> fatfs->clust_shift; + new_clust_idx = new_pos >> fatfs->clust_shift; + /* if the new position does not fall in the same cluster as the previous one + * re-calculate cur_clust + */ + if(new_clust_idx != cur_clust_idx) { + if(new_clust_idx < cur_clust_idx) { + file->cur_clust = find_cluster(fatfs, new_clust_idx, file->first_clust); + } else { + file->cur_clust = find_cluster(fatfs, new_clust_idx - cur_clust_idx, file->cur_clust); + } + file->buf_valid = 0; + } + file->cur_pos = new_pos; + return 0; +} + +static long tell(struct fs_node *node) +{ + struct fat_file *file; + + if(!node || node->type != FSNODE_FILE) { + return -1; + } + + file = node->data; + return file->cur_pos; +} + +static int read(struct fs_node *node, void *buf, int sz) +{ + struct fatfs *fatfs; + struct fat_file *file; + char *bufptr = buf; + int num_read = 0; + int offs, len, buf_left, rd_left; + unsigned int cur_clust_idx, new_clust_idx; + + if(!node || !buf || sz < 0 || node->type != FSNODE_FILE) { + return -1; + } + + fatfs = node->fs->data; + file = node->data; + + if(file->cur_clust < 0) { + return 0; /* EOF */ + } + + cur_clust_idx = file->cur_pos >> fatfs->clust_shift; + + while(num_read < sz) { + if(!file->buf_valid) { + read_cluster(fatfs, file->cur_clust, file->clustbuf); + file->buf_valid = 1; + } + + offs = file->cur_pos & fatfs->clust_mask; + buf_left = fatfs->cluster_size * 512 - offs; + rd_left = sz - num_read; + len = buf_left < rd_left ? buf_left : rd_left; + + if(file->cur_pos + len > file->ent.size_bytes) { + len = file->ent.size_bytes - file->cur_pos; + } + + memcpy(bufptr, file->clustbuf + offs, len); + num_read += len; + bufptr += len; + + file->cur_pos += len; + if(file->cur_pos >= file->ent.size_bytes) { + file->cur_clust = -1; + file->buf_valid = 0; + break; /* reached EOF */ + } + + new_clust_idx = file->cur_pos >> fatfs->clust_shift; + if(new_clust_idx != cur_clust_idx) { + file->buf_valid = 0; + if((file->cur_clust = next_cluster(fatfs, file->cur_clust)) < 0) { + break; /* reached EOF */ + } + cur_clust_idx = new_clust_idx; + } + } + return num_read; +} + +static int write(struct fs_node *node, void *buf, int sz) +{ + return -1; /* TODO */ +} + +static int rewinddir(struct fs_node *node) +{ + struct fat_dir *dir; + + if(node->type != FSNODE_DIR) { + return -1; + } + + dir = node->data; + dir->cur_ent = 0; + return 0; +} + +static struct fs_dirent *readdir(struct fs_node *node) +{ + struct fat_dir *dir; + + if(node->type != FSNODE_DIR) { + return 0; + } + + dir = node->data; + if(dir->cur_ent >= dir->fsent_size) { + return 0; + } + + return dir->fsent + dir->cur_ent++; +} + +static int rename(struct fs_node *node, const char *name) +{ + return -1; /* TODO */ +} + +static int remove(struct fs_node *node) +{ + errno = EPERM; + return -1; /* TODO */ +} + +static struct fat_dir *load_dir(struct fatfs *fs, struct fat_dirent *dent) +{ + int32_t addr; + struct fat_dir *dir; + char *buf = 0; + int bufsz = 0; + + if(dent->attr != ATTR_DIR) return 0; + + addr = dent->first_cluster_low; + if(fs->type >= FAT32) { + addr |= (uint32_t)dent->first_cluster_high << 16; + } + + do { + int prevsz = bufsz; + bufsz += fs->cluster_size * 512; + if(!(buf = realloc(buf, bufsz))) { + panic("FAT: failed to allocate cluster buffer (%d bytes)\n", bufsz); + } + + if(read_cluster(fs, addr, buf + prevsz) == -1) { + printf("load_dir: failed to read cluster: %lu\n", (unsigned long)addr); + free(buf); + return 0; + } + } while((addr = next_cluster(fs, addr)) >= 0); + + if(!(dir = malloc(sizeof *dir))) { + panic("FAT: failed to allocate directory structure\n"); + } + dir->fatfs = fs; + dir->ent = (struct fat_dirent*)buf; + dir->max_nent = bufsz / sizeof *dir->ent; + dir->cur_ent = 0; + dir->ref = 0; + + parse_dir_entries(dir); + return dir; +} + +static void parse_dir_entries(struct fat_dir *dir) +{ + int i; + struct fat_dirent *dent, *prev_dent; + struct fs_dirent *eptr; + char entname[MAX_NAME]; + + /* create an fs_dirent array with one element for each actual entry + * (disregarding volume labels, and LFN entries). + */ + if(!(dir->fsent = malloc(dir->max_nent * sizeof *dir->fsent))) { + panic("FAT: failed to allocate dirent array\n"); + } + eptr = dir->fsent; + dent = dir->ent; + prev_dent = dent - 1; + + for(i=0; imax_nent; i++) { + if(DENT_IS_NULL(dent)) break; + + if(!DENT_IS_UNUSED(dent) && dent->attr != ATTR_VOLID && dent->attr != ATTR_LFN) { + if(dent_filename(dent, prev_dent, entname) > 0) { + if(!(eptr->name = malloc(strlen(entname) + 1))) { + panic("FAT: failed to allocate dirent name\n"); + } + strcpy(eptr->name, entname); + eptr->data = dent; + eptr->type = dent->attr == ATTR_DIR ? FSNODE_DIR : FSNODE_FILE; + eptr->fsize = dent->size_bytes; + eptr++; + } + } + if(dent->attr != ATTR_LFN) { + prev_dent = dent; + } + dent++; + } + dir->fsent_size = eptr - dir->fsent; + dir->cur_ent = 0; +} + +static void free_dir(struct fat_dir *dir) +{ + int i; + struct fat_dir *root = dir->fatfs->rootdir; + + if(dir) { + if(--dir->ref > 0) return; + + if(dir->ent != root->ent) { + free(dir->ent); + if(dir->fsent) { + for(i=0; ifsent_size; i++) { + free(dir->fsent[i].name); + } + free(dir->fsent); + } + } + free(dir); + } +} + +static struct fat_file *init_file(struct fatfs *fatfs, struct fat_dirent *dent) +{ + struct fat_file *file; + + if(!(file = calloc(1, sizeof *file))) { + panic("FAT: failed to allocate file structure\n"); + } + if(!(file->clustbuf = malloc(fatfs->cluster_size * 512))) { + panic("FAT: failed to allocate file cluster buffer\n"); + } + file->ent = *dent; + file->first_clust = dent->first_cluster_low | ((int32_t)dent->first_cluster_high << 16); + file->cur_clust = file->first_clust; + return file; +} + +static void free_file(struct fat_file *file) +{ + if(file) { + free(file->clustbuf); + free(file); + } +} + +static int read_sectors(int dev, uint64_t sidx, int count, void *sect) +{ + if(dev == -1 || dev == boot_drive_number) { + if(bdev_read_range(sidx, count, sect) == -1) { + return -1; + } + return 0; + } + + printf("BUG: reading sectors from drives other than the boot drive not implemented yet\n"); + return -1; +} + +static int read_cluster(struct fatfs *fatfs, uint32_t addr, void *clust) +{ + char *ptr = clust; + uint64_t saddr = (uint64_t)(addr - 2) * fatfs->cluster_size + fatfs->first_data_sect + fatfs->start_sect; + + if(read_sectors(fatfs->dev, saddr, fatfs->cluster_size, ptr) == -1) { + return -1; + } + return 0; +} + +static int dent_filename(struct fat_dirent *dent, struct fat_dirent *prev, char *buf) +{ + int len = 0; + char *ptr = buf; + struct fat_lfnent *lfn = (struct fat_lfnent*)(dent - 1); + + if(lfn > (struct fat_lfnent*)prev && lfn->attr == ATTR_LFN) { + /* found a long filename entry, use that */ + do { + uint16_t ustr[14], *uptr = ustr; + memcpy(uptr, lfn->part1, sizeof lfn->part1); + uptr += sizeof lfn->part1 / sizeof *lfn->part1; + memcpy(uptr, lfn->part2, sizeof lfn->part2); + uptr += sizeof lfn->part2 / sizeof *lfn->part2; + memcpy(uptr, lfn->part3, sizeof lfn->part3); + ustr[13] = 0; + + uptr = ustr; + while(*uptr) { + *ptr++ = *(char*)uptr++; + len++; + } + *ptr = 0; + + if(uptr < ustr + 13 || (lfn->seq & 0xf0) == 0x40) break; + lfn -= 1; + } while(lfn > (struct fat_lfnent*)prev && lfn->attr == ATTR_LFN); + + } else { + /* regular 8.3 filename */ + memcpy(buf, dent->name, 8); + buf[8] = 0; + clean_trailws(buf); + if(!buf[0]) return 0; + + if(dent->name[8] && dent->name[8] != ' ') { + ptr = buf + strlen(buf); + *ptr++ = '.'; + memcpy(ptr, dent->name + 8, 3); + ptr[3] = 0; + clean_trailws(ptr); + } + + len = strlen(buf); + } + return len; +} + +static struct fs_dirent *find_entry(struct fat_dir *dir, const char *name) +{ + int i; + struct fs_dirent *dent = dir->fsent; + + for(i=0; ifsent_size; i++) { + if(strcasecmp(dent->name, name) == 0) { + return dent; + } + dent++; + } + return 0; +} + +static uint32_t read_fat(struct fatfs *fatfs, uint32_t addr) +{ + uint32_t res = 0xffffffff; + + switch(fatfs->type) { + case FAT12: + { + uint32_t idx = addr + addr / 2; + res = ((uint16_t*)fatfs->fat)[idx]; + + if(idx & 1) { + res >>= 4; /* odd entries end up on the high 12 bits */ + } else { + res &= 0xfff; /* even entries end up on the low 12 bits */ + } + } + break; + + case FAT16: + res = ((uint16_t*)fatfs->fat)[addr]; + break; + + case FAT32: + case EXFAT: + res = ((uint32_t*)fatfs->fat)[addr]; + break; + + default: + break; + } + + return res; +} + +static int32_t next_cluster(struct fatfs *fatfs, int32_t addr) +{ + uint32_t fatval = read_fat(fatfs, addr); + + if(fatval == 0) return -1; + + switch(fatfs->type) { + case FAT12: + if(fatval >= 0xff8) return -1; + break; + + case FAT16: + if(fatval >= 0xfff8) return -1; + break; + + case FAT32: + case EXFAT: /* XXX ? */ + if(fatval >= 0xffffff8) return -1; + break; + + default: + break; + } + + return fatval; +} + +static int32_t find_cluster(struct fatfs *fatfs, int count, int32_t clust) +{ + while(count-- > 0 && (clust = next_cluster(fatfs, clust)) >= 0); + return clust; +} + +/* +static void dbg_printdir(struct fat_dirent *dir, int max_entries) +{ + char name[MAX_NAME]; + struct fat_dirent *prev = dir - 1; + struct fat_dirent *end = max_entries > 0 ? dir + max_entries : 0; + + while(!DENT_IS_NULL(dir) && (!end || dir < end)) { + if(!DENT_IS_UNUSED(dir) && dir->attr != ATTR_VOLID && dir->attr != ATTR_LFN) { + if(dent_filename(dir, prev, name) > 0) { + printf("%s%c\n", name, dir->attr == ATTR_DIR ? '/' : ' '); + } + } + if(dir->attr != ATTR_LFN) { + prev = dir; + } + dir++; + } +} +*/ + +static void clean_trailws(char *s) +{ + char *p; + + if(!s || !*s) return; + + p = s + strlen(s) - 1; + while(p >= s && isspace(*p)) p--; + p[1] = 0; +}