diff options
Diffstat (limited to 'core/fs/fat/fat.c')
-rw-r--r-- | core/fs/fat/fat.c | 860 |
1 files changed, 860 insertions, 0 deletions
diff --git a/core/fs/fat/fat.c b/core/fs/fat/fat.c new file mode 100644 index 00000000..13cf674d --- /dev/null +++ b/core/fs/fat/fat.c @@ -0,0 +1,860 @@ +#include <stdio.h> +#include <string.h> +#include <sys/dirent.h> +#include <cache.h> +#include <core.h> +#include <disk.h> +#include <fs.h> +#include <klibc/compiler.h> +#include "codepage.h" +#include "fat_fs.h" + +static struct inode * new_fat_inode(struct fs_info *fs) +{ + struct inode *inode = alloc_inode(fs, 0, sizeof(struct fat_pvt_inode)); + if (!inode) + malloc_error("inode structure"); + + return inode; +} + + +static void vfat_close_file(struct file *file) +{ + if (file->inode) { + file->offset = 0; + free_inode(file->inode); + } +} + + +/* + * Check for a particular sector in the FAT cache + */ +static struct cache_struct * get_fat_sector(struct fs_info *fs, sector_t sector) +{ + return get_cache_block(fs->fs_dev, FAT_SB(fs)->fat + sector); +} + +static uint32_t get_next_cluster(struct fs_info *fs, uint32_t clust_num) +{ + uint32_t next_cluster; + sector_t fat_sector; + uint32_t offset; + int lo, hi; + struct cache_struct *cs; + uint32_t sector_mask = SECTOR_SIZE(fs) - 1; + + switch(FAT_SB(fs)->fat_type) { + case FAT12: + offset = clust_num + (clust_num >> 1); + fat_sector = offset >> SECTOR_SHIFT(fs); + offset &= sector_mask; + cs = get_fat_sector(fs, fat_sector); + if (offset == sector_mask) { + /* + * we got the end of the one fat sector, + * but we have just one byte and we need two, + * so store the low part, then read the next fat + * sector, read the high part, then combine it. + */ + lo = *(uint8_t *)(cs->data + offset); + cs = get_fat_sector(fs, fat_sector + 1); + hi = *(uint8_t *)cs->data; + next_cluster = (hi << 8) + lo; + } else { + next_cluster = *(uint16_t *)(cs->data + offset); + } + + if (clust_num & 0x0001) + next_cluster >>= 4; /* cluster number is ODD */ + else + next_cluster &= 0x0fff; /* cluster number is EVEN */ + break; + + case FAT16: + offset = clust_num << 1; + fat_sector = offset >> SECTOR_SHIFT(fs); + offset &= sector_mask; + cs = get_fat_sector(fs, fat_sector); + next_cluster = *(uint16_t *)(cs->data + offset); + break; + + case FAT32: + offset = clust_num << 2; + fat_sector = offset >> SECTOR_SHIFT(fs); + offset &= sector_mask; + cs = get_fat_sector(fs, fat_sector); + next_cluster = *(uint32_t *)(cs->data + offset); + next_cluster &= 0x0fffffff; + break; + } + + return next_cluster; +} + + +static sector_t get_next_sector(struct fs_info* fs, uint32_t sector) +{ + struct fat_sb_info *sbi = FAT_SB(fs); + sector_t data_area = sbi->data; + sector_t data_sector; + uint32_t cluster; + int clust_shift = sbi->clust_shift; + + if (sector < data_area) { + /* Root directory sector... */ + sector++; + if (sector >= data_area) + sector = 0; /* Ran out of root directory, return EOF */ + return sector; + } + + data_sector = sector - data_area; + if ((data_sector + 1) & sbi->clust_mask) /* Still in the same cluster */ + return sector + 1; /* Next sector inside cluster */ + + /* get a new cluster */ + cluster = data_sector >> clust_shift; + cluster = get_next_cluster(fs, cluster + 2) - 2; + + if (cluster >= sbi->clusters) + return 0; + + /* return the start of the new cluster */ + sector = (cluster << clust_shift) + data_area; + return sector; +} + +/* + * Here comes the place I don't like VFAT fs most; if we need seek + * the file to the right place, we need get the right sector address + * from begining everytime! Since it's a kind a signle link list, we + * need to traver from the head-node to find the right node in that list. + * + * What a waste of time! + */ +static sector_t get_the_right_sector(struct file *file) +{ + struct inode *inode = file->inode; + uint32_t sector_pos = file->offset >> SECTOR_SHIFT(file->fs); + uint32_t where; + sector_t sector; + + if (sector_pos < PVT(inode)->offset) { + /* Reverse seek */ + where = 0; + sector = PVT(inode)->start; + } else { + where = PVT(inode)->offset; + sector = PVT(inode)->here; + } + + while (where < sector_pos) { + sector = get_next_sector(file->fs, sector); + where++; + } + + PVT(inode)->offset = sector_pos; + PVT(inode)->here = sector; + + return sector; +} + +/* + * Get the next sector in sequence + */ +static sector_t next_sector(struct file *file) +{ + struct inode *inode = file->inode; + sector_t sector = get_next_sector(file->fs, PVT(inode)->here); + PVT(inode)->offset++; + PVT(inode)->here = sector; + + return sector; +} + +/** + * __getfssec: + * + * get multiple sectors from a file + * + * This routine makes sure the subransfers do not cross a 64K boundary + * and will correct the situation if it does, UNLESS *sectos* cross + * 64K boundaries. + * + */ +static void __getfssec(struct fs_info *fs, char *buf, + struct file *file, uint32_t sectors) +{ + sector_t curr_sector = get_the_right_sector(file); + sector_t frag_start , next_sector; + uint32_t con_sec_cnt; + struct disk *disk = fs->fs_dev->disk; + + while (sectors) { + /* get fragment */ + con_sec_cnt = 0; + frag_start = curr_sector; + + do { + /* get consective sector count */ + con_sec_cnt++; + sectors--; + next_sector = get_next_sector(fs, curr_sector); + curr_sector++; + } while (sectors && next_sector == curr_sector); + + PVT(file->inode)->offset += con_sec_cnt; + PVT(file->inode)->here = next_sector; + + /* do read */ + disk->rdwr_sectors(disk, buf, frag_start, con_sec_cnt, 0); + buf += con_sec_cnt << SECTOR_SHIFT(fs);/* adjust buffer pointer */ + + curr_sector = next_sector; + } +} + + + +/** + * get multiple sectors from a file + * + * @param: buf, the buffer to store the read data + * @param: file, the file structure pointer + * @param: sectors, number of sectors wanna read + * @param: have_more, set one if has more + * + * @return: number of bytes read + * + */ +static uint32_t vfat_getfssec(struct file *file, char *buf, int sectors, + bool *have_more) +{ + struct fs_info *fs = file->fs; + uint32_t bytes_left = file->inode->size - file->offset; + uint32_t bytes_read = sectors << fs->sector_shift; + int sector_left; + + sector_left = (bytes_left + SECTOR_SIZE(fs) - 1) >> fs->sector_shift; + if (sectors > sector_left) + sectors = sector_left; + + __getfssec(fs, buf, file, sectors); + + if (bytes_read >= bytes_left) { + bytes_read = bytes_left; + *have_more = 0; + } else { + *have_more = 1; + } + file->offset += bytes_read; + + return bytes_read; +} + +/* + * Mangle a filename pointed to by src into a buffer pointed to by dst; + * ends on encountering any whitespace. + * + */ +static void vfat_mangle_name(char *dst, const char *src) +{ + char *p = dst; + char c; + int i = FILENAME_MAX -1; + + /* + * Copy the filename, converting backslash to slash and + * collapsing duplicate separators. + */ + while (not_whitespace(c = *src)) { + if (c == '\\') + c = '/'; + + if (c == '/') { + if (src[1] == '/' || src[1] == '\\') { + src++; + i--; + continue; + } + } + i--; + *dst++ = *src++; + } + + /* Strip terminal slashes or whitespace */ + while (1) { + if (dst == p) + break; + if (*(dst-1) == '/' && dst-1 == p) /* it's the '/' case */ + break; + if ((*(dst-1) != '/') && (*(dst-1) != '.')) + break; + + dst--; + i++; + } + + i++; + for (; i > 0; i --) + *dst++ = '\0'; +} + +/* + * Mangle a normal style string to DOS style string. + */ +static void mangle_dos_name(char *mangle_buf, char *src) +{ + int i; + unsigned char c; + + i = 0; + while (i < 11) { + c = *src++; + + if ((c <= ' ') || (c == '/')) + break; + + if (c == '.') { + while (i < 8) + mangle_buf[i++] = ' '; + i = 8; + continue; + } + + c = codepage.upper[c]; + if (i == 0 && c == 0xe5) + c = 0x05; /* Special hack for the first byte only! */ + + mangle_buf[i++] = c; + } + while (i < 11) + mangle_buf[i++] = ' '; + + mangle_buf[i] = '\0'; +} + +/* + * Match a string name against a longname. "len" is the number of + * codepoints in the input; including padding. + * + * Returns true on match. + */ +static bool vfat_match_longname(const char *str, const uint16_t *match, + int len) +{ + unsigned char c; + uint16_t cp; + + while (len) { + cp = *match++; + c = *str++; + if (cp != codepage.uni[0][c] && cp != codepage.uni[1][c]) + return false; + if (!c) + break; + } + + if (c) + return false; + + /* Any padding entries must be FFFF */ + while (len) + if (*match++ != 0xffff) + return false; + + return true; +} + +/* + * Convert an UTF-16 longname to the system codepage; return + * the length on success or -1 on failure. + */ +static int vfat_cvt_longname(char *entry_name, const uint16_t *long_name) +{ + struct unicache { + uint16_t utf16; + uint8_t cp; + }; + static struct unicache unicache[256]; + struct unicache *uc; + uint16_t cp; + unsigned int c; + char *p = entry_name; + + do { + cp = *long_name++; + uc = &unicache[cp % 256]; + + if (__likely(uc->utf16 == cp)) { + *p++ = uc->cp; + } else { + for (c = 0; c < 512; c++) { + /* This is a bit hacky... */ + if (codepage.uni[0][c] == cp) { + uc->utf16 = cp; + *p++ = uc->cp = (uint8_t)c; + goto found; + } + } + return -1; /* Impossible character */ + found: + ; + } + } while (cp); + + return (p-entry_name)-1; +} + +static void copy_long_chunk(uint16_t *buf, const struct fat_dir_entry *de) +{ + const struct fat_long_name_entry *le = + (const struct fat_long_name_entry *)de; + + memcpy(buf, le->name1, 5 * 2); + memcpy(buf + 5, le->name2, 6 * 2); + memcpy(buf + 11, le->name3, 2 * 2); +} + +static uint8_t get_checksum(char *dir_name) +{ + int i; + uint8_t sum = 0; + + for (i = 11; i; i--) + sum = ((sum & 1) << 7) + (sum >> 1) + *dir_name++; + return sum; +} + + +/* compute the first sector number of one dir where the data stores */ +static inline sector_t first_sector(struct fs_info *fs, + const struct fat_dir_entry *dir) +{ + const struct fat_sb_info *sbi = FAT_SB(fs); + sector_t first_clust; + sector_t sector; + + first_clust = (dir->first_cluster_high << 16) + dir->first_cluster_low; + sector = ((first_clust - 2) << sbi->clust_shift) + sbi->data; + + return sector; +} + +static inline int get_inode_mode(uint8_t attr) +{ + if (attr == FAT_ATTR_DIRECTORY) + return I_DIR; + else + return I_FILE; +} + + +static struct inode *vfat_find_entry(char *dname, struct inode *dir) +{ + struct fs_info *fs = dir->fs; + struct inode *inode; + struct fat_dir_entry *de; + struct fat_long_name_entry *long_de; + struct cache_struct *cs; + + char mangled_name[12]; + uint16_t long_name[260]; /* == 20*13 */ + int long_len; + + sector_t dir_sector = PVT(dir)->start; + uint8_t vfat_init, vfat_next, vfat_csum = 0; + uint8_t id; + int slots; + int entries; + int checksum; + int long_match = 0; + + slots = (strlen(dname) + 12) / 13; + if (slots > 20) + return NULL; /* Name too long */ + + slots |= 0x40; + vfat_init = vfat_next = slots; + long_len = slots*13; + + /* Produce the shortname version, in case we need it. */ + mangle_dos_name(mangled_name, dname); + + while (dir_sector) { + cs = get_cache_block(fs->fs_dev, dir_sector); + de = (struct fat_dir_entry *)cs->data; + entries = 1 << (fs->sector_shift - 5); + + while (entries--) { + if (de->name[0] == 0) + return NULL; + + if (de->attr == 0x0f) { + /* + * It's a long name entry. + */ + long_de = (struct fat_long_name_entry *)de; + id = long_de->id; + if (id != vfat_next) + goto not_match; + + if (id & 0x40) { + /* get the initial checksum value */ + vfat_csum = long_de->checksum; + id &= 0x3f; + long_len = id * 13; + + /* ZERO the long_name buffer */ + memset(long_name, 0, sizeof long_name); + } else { + if (long_de->checksum != vfat_csum) + goto not_match; + } + + vfat_next = --id; + + /* got the long entry name */ + copy_long_chunk(long_name + id*13, de); + + /* + * If we got the last entry, check it. + * Or, go on with the next entry. + */ + if (id == 0) { + if (!vfat_match_longname(dname, long_name, long_len)) + goto not_match; + long_match = 1; + } + de++; + continue; /* Try the next entry */ + } else { + /* + * It's a short entry + */ + if (de->attr & 0x08) /* ignore volume labels */ + goto not_match; + + if (long_match) { + /* + * We already have a VFAT long name match. However, the + * match is only valid if the checksum matches. + */ + checksum = get_checksum(de->name); + if (checksum == vfat_csum) + goto found; /* Got it */ + } else { + if (!memcmp(mangled_name, de->name, 11)) + goto found; + } + } + + not_match: + vfat_next = vfat_init; + long_match = 0; + + de++; + } + + /* Try with the next sector */ + dir_sector = get_next_sector(fs, dir_sector); + } + return NULL; /* Nothing found... */ + +found: + inode = new_fat_inode(fs); + inode->size = de->file_size; + PVT(inode)->start = PVT(inode)->here = first_sector(fs, de); + inode->mode = get_inode_mode(de->attr); + + return inode; +} + +static struct inode *vfat_iget_root(struct fs_info *fs) +{ + struct inode *inode = new_fat_inode(fs); + int root_size = FAT_SB(fs)->root_size; + + /* + * For FAT32, the only way to get the root directory size is to + * follow the entire FAT chain to the end... which seems pointless. + */ + inode->size = root_size ? root_size << fs->sector_shift : ~0; + PVT(inode)->start = PVT(inode)->here = FAT_SB(fs)->root; + inode->mode = I_DIR; + + return inode; +} + +static struct inode *vfat_iget(char *dname, struct inode *parent) +{ + return vfat_find_entry(dname, parent); +} + +static struct dirent * vfat_readdir(struct file *file) +{ + struct fs_info *fs = file->fs; + struct dirent *dirent; + struct fat_dir_entry *de; + struct fat_long_name_entry *long_de; + struct cache_struct *cs; + + sector_t sector = get_the_right_sector(file); + + uint16_t long_name[261]; /* == 20*13 + 1 (to guarantee null) */ + char filename[261]; + + uint8_t vfat_init, vfat_next, vfat_csum; + uint8_t id; + int entries_left; + int checksum; + int long_entry = 0; + int sec_off = file->offset & ((1 << fs->sector_shift) - 1); + + cs = get_cache_block(fs->fs_dev, sector); + de = (struct fat_dir_entry *)(cs->data + sec_off); + entries_left = ((1 << fs->sector_shift) - sec_off) >> 5; + + vfat_next = vfat_csum = 0xff; + + while (1) { + while(entries_left--) { + if (de->name[0] == 0) + return NULL; + if ((uint8_t)de->name[0] == 0xe5) + goto invalid; + + if (de->attr == 0x0f) { + /* + * It's a long name entry. + */ + long_de = (struct fat_long_name_entry *)de; + id = long_de->id; + + if (id & 0x40) { + /* init vfat_csum and vfat_init */ + vfat_csum = long_de->checksum; + id &= 0x3f; + if (id >= 20) + goto invalid; /* Too long! */ + + vfat_init = id; + + /* ZERO the long_name buffer */ + memset(long_name, 0, sizeof long_name); + } else { + if (long_de->checksum != vfat_csum || + id != vfat_next) + goto invalid; + } + + vfat_next = --id; + + /* got the long entry name */ + copy_long_chunk(long_name + id*13, de); + + if (id == 0) { + int longlen = + vfat_cvt_longname(filename, long_name); + if (longlen > 0 && longlen < sizeof(dirent->d_name)) + long_entry = 1; + } + + de++; + file->offset += sizeof(struct fat_dir_entry); + continue; /* Try the next entry */ + } else { + /* + * It's a short entry + */ + if (de->attr & 0x08) /* ignore volume labels */ + goto invalid; + + if (long_entry == 1) { + /* Got a long entry */ + checksum = get_checksum(de->name); + if (checksum == vfat_csum) + goto got; + } else { + /* Use the shortname */ + int i; + uint8_t c; + char *p = filename; + + for (i = 0; i < 8; i++) { + c = de->name[i]; + if (c == ' ') + break; + if (de->lcase & LCASE_BASE) + c = codepage.lower[c]; + *p++ = c; + } + if (de->name[8] != ' ') { + *p++ = '.'; + for (i = 8; i < 11; i++) { + c = de->name[i]; + if (c == ' ') + break; + if (de->lcase & LCASE_EXT) + c = codepage.lower[c]; + *p++ = c; + } + } + *p = '\0'; + + goto got; + } + } + + invalid: + de++; + file->offset += sizeof(struct fat_dir_entry); + } + + /* Try with the next sector */ + sector = next_sector(file); + if (!sector) + return NULL; + cs = get_cache_block(fs->fs_dev, sector); + de = (struct fat_dir_entry *)cs->data; + entries_left = 1 << (fs->sector_shift - 5); + } + +got: + if (!(dirent = malloc(sizeof(*dirent)))) { + malloc_error("dirent structure in vfat_readdir"); + return NULL; + } + dirent->d_ino = de->first_cluster_low | (de->first_cluster_high << 16); + dirent->d_off = file->offset; + dirent->d_reclen = 0; + dirent->d_type = get_inode_mode(de->attr); + strcpy(dirent->d_name, filename); + + file->offset += sizeof(*de); /* Update for next reading */ + + return dirent; +} + +/* Load the config file, return 1 if failed, or 0 */ +static int vfat_load_config(void) +{ + const char * const syslinux_cfg[] = { + "/boot/syslinux/syslinux.cfg", + "/syslinux/syslinux.cfg", + "/syslinux.cfg" + }; + com32sys_t regs; + char *p; + int i = 0; + + /* + * we use the ConfigName to pass the config path because + * it is under the address 0xffff + */ + memset(®s, 0, sizeof regs); + regs.edi.w[0] = OFFS_WRT(ConfigName, 0); + for (; i < 3; i++) { + strcpy(ConfigName, syslinux_cfg[i]); + call16(core_open, ®s, ®s); + + /* if zf flag set, then failed; try another */ + if (! (regs.eflags.l & EFLAGS_ZF)) + break; + } + if (i == 3) { + printf("no config file found\n"); + return 1; /* no config file */ + } + + strcpy(ConfigName, "syslinux.cfg"); + strcpy(CurrentDirName, syslinux_cfg[i]); + p = strrchr(CurrentDirName, '/'); + *(p + 1) = '\0'; /* In case we met '/syslinux.cfg' */ + + return 0; +} + +static inline __constfunc uint32_t bsr(uint32_t num) +{ + asm("bsrl %1,%0" : "=r" (num) : "rm" (num)); + return num; +} + +/* init. the fs meta data, return the block size in bits */ +static int vfat_fs_init(struct fs_info *fs) +{ + struct fat_bpb fat; + struct fat_sb_info *sbi; + struct disk *disk = fs->fs_dev->disk; + int sectors_per_fat; + uint32_t clusters; + sector_t total_sectors; + + fs->sector_shift = fs->block_shift = disk->sector_shift; + fs->sector_size = 1 << fs->sector_shift; + fs->block_size = 1 << fs->block_shift; + + disk->rdwr_sectors(disk, &fat, 0, 1, 0); + + sbi = malloc(sizeof(*sbi)); + if (!sbi) + malloc_error("fat_sb_info structure"); + fs->fs_info = sbi; + + sectors_per_fat = fat.bxFATsecs ? : fat.fat32.bxFATsecs_32; + total_sectors = fat.bxSectors ? : fat.bsHugeSectors; + + sbi->fat = fat.bxResSectors; + sbi->root = sbi->fat + sectors_per_fat * fat.bxFATs; + sbi->root_size = root_dir_size(fs, &fat); + sbi->data = sbi->root + sbi->root_size; + + sbi->clust_shift = bsr(fat.bxSecPerClust); + sbi->clust_byte_shift = sbi->clust_shift + fs->sector_shift; + sbi->clust_mask = fat.bxSecPerClust - 1; + sbi->clust_size = fat.bxSecPerClust << fs->sector_shift; + + clusters = (total_sectors - sbi->data) >> sbi->clust_shift; + if (clusters <= 0xff4) { + sbi->fat_type = FAT12; + } else if (clusters <= 0xfff4) { + sbi->fat_type = FAT16; + } else { + sbi->fat_type = FAT32; + + if (clusters > 0x0ffffff4) + clusters = 0x0ffffff4; /* Maximum possible */ + + if (fat.fat32.extended_flags & 0x80) { + /* Non-mirrored FATs, we need to read the active one */ + sbi->fat += (fat.fat32.extended_flags & 0x0f) * sectors_per_fat; + } + + /* FAT32: root directory is a cluster chain */ + sbi->root = sbi->data + + ((fat.fat32.root_cluster-2) << sbi->clust_shift); + } + sbi->clusters = clusters; + + /* for SYSLINUX, the cache is based on sector size */ + return fs->sector_shift; +} + +const struct fs_ops vfat_fs_ops = { + .fs_name = "vfat", + .fs_flags = FS_USEMEM | FS_THISIND, + .fs_init = vfat_fs_init, + .searchdir = NULL, + .getfssec = vfat_getfssec, + .close_file = vfat_close_file, + .mangle_name = vfat_mangle_name, + .unmangle_name = generic_unmangle_name, + .load_config = vfat_load_config, + .readdir = vfat_readdir, + .iget_root = vfat_iget_root, + .iget_current = NULL, + .iget = vfat_iget, +}; |