#include "fat_fs.h" #include "cache.h" #include "disklab.h" #include #include #include /* some generic defines */ #define FILENAME_MAX_LG2 6 #define FILENAME_MAX_ ( 1 << FILENAME_MAX_LG2 ) #define NULLFILE 0 #define NULLOFFSET 0 #define retry_count 16 #define LDLINUX_MAGIC 0x3eb202fe #define MAX_OPEN_LG2 6 #define MAX_OPEN ( 1 << MAX_OPEN_LG2 ) #define SECTOR_SHIFT 9 #define SECTOR_SIZE ( 1 << SECTOR_SHIFT ) #define DIRENT_SHIFT 5 #define DIRENT_SIZE ( 1 << DIRENT_SHIFT ) #define ROOT_DIR_WORD 0x002f enum fat_type{ FAT12, FAT16, FAT32 }; __u8 FATType; /* generic information about FAT fs */ __u32 FAT; /* Location of (first) FAT */ __u32 RootDirArea; /* Location of root directory area */ __u32 RootDir; /* Location of root directory proper */ __u32 DataArea; /* Location of data area */ __u32 TotalSectors; /* Total number of sectors */ __u32 ClustSize; /* Bytes/cluster */ __u32 ClustMask; /* Sector/cluster - 1 */ __u8 CopySuper; /* Distinguish .bs versus .bss */ __u8 DriveNumber; /* BIOS drive number */ __u8 ClustShift; /* Shift count for sectors/cluster */ __u8 ClustByteShift; /* Shift count for bytes/cluster */ /* for SYSLINUX, the block size equal to the sector size */ __u32 blk_size = 1 << SECTOR_SHIFT; /* used for long name dir entry */ char *NameStart; int NameLen; /* file structure. This holds the information for each currently open file */ struct open_file_t { __u32 file_sector; /* sector pointer ( 0 = structure free ) */ __u32 file_bytesleft; /* number of bytes left */ __u32 file_left; /* number of sectors left */ __u32 pad; /* padding */ }; struct open_file_t Files[MAX_OPEN]; #define trackbufsize 8192 char trackbuf[trackbufsize]; char MangleBuf[12]; /* the fat bpb data */ struct fat_bpb fat; /** * allocate_file: * * Allocate a file structure * * @return: if successful return the file pointer, or return NULL * */ struct open_file_t *allocate_file() { struct open_file_t *file; int i = 0; file = Files; for (; i < MAX_OPEN; i ++ ) { if ( file->file_sector == 0 ) /* found it */ return file; file ++; } return NULL; /* not found */ } /** * alloc_fill_dir: * * Allocate then fill a file structure for a directory starting in * sector SECTOR. if successful, return the pointer of filled file * structure, or return NULL. * */ struct open_file_t* alloc_fill_dir(__u32 sector) { struct open_file_t *file; file = allocate_file(); if ( !file ) return NULL; file->file_sector = sector; /* current sector */ file->file_bytesleft = 0; /* current offset */ file->file_left = sector; /* beginning sector */ return file; } /* Deallocates a file structure */ void close_file(struct open_file_t *file) { if ( file ) file->file_sector = 0; } /* Deallocates a directory structure */ void close_dir(struct fat_dir_entry *dir) { if ( dir ) *(__u32*)dir = 0; } /** * getfatsector: * * check for a particular sector in the FAT cache. * */ struct cache_struct *getfatsector(__u32 sector) { return get_cache_block(FAT + sector); } /** * nextcluster: * * Advance a cluster pointer in clust_num to the next cluster * pointer at in the FAT tables. CF = 0 on return if end of file. * * @param: clust_num; * * @return: the next cluster number * */ __u32 nextcluster(__u32 clust_num) { __u32 next_cluster; __u32 fat_sector; struct cache_struct *cs; #if FATType == FAT12 int offset; int lo, hi; #endif switch(FATType) { case FAT12: fat_sector = (clust_num + clust_num / 2) >> SECTOR_SHIFT; cs = getfatsector(fat_sector); offset = (clust_num * 3 / 2) & ( SECTOR_SIZE -1 ); if ( offset == 0x1ff ) { /* * we got the end of the one fat sector, * but we don't got we have(just one byte, we need two), * so store the low part, then read the next fat * sector, read the high part, then combine it. */ lo = *(__u8 *)(cs->data + offset); cs = getfatsector(fat_sector + 1); hi = *(__u8 *)cs->data; next_cluster = (hi << 8) + lo; } else next_cluster = *(__u16 *)(cs->data + offset); if ( clust_num & 0x0001 ) next_cluster >>= 4; /* cluster number is ODD */ else next_cluster &= 0x0fff; /* cluster number is EVEN */ if ( next_cluster > 0x0ff0 ) goto fail; break; case FAT16: fat_sector = clust_num >> (SECTOR_SHIFT - 2); cs = getfatsector(fat_sector); next_cluster = ((__u16 *)cs->data)[clust_num]; if ( next_cluster > 0xfff0 ) goto fail; break; case FAT32: fat_sector = clust_num >> (SECTOR_SHIFT - 2); cs = getfatsector(fat_sector); next_cluster = ((__u32 *)cs->data)[clust_num] & 0x0fffffff; if ( next_cluster > 0x0ffffff0 ) goto fail; break; } return next_cluster; fail: /* got an unexcepted cluster number, so return ZERO */ return 0; } /** * nextsector: * * given a sector on input, return the next sector of the * same filesystem object, which may be the root directory or a * cluster chain. Returns EOF. * */ __u32 nextsector(__u32 sector) { __u32 data_sector; __u32 cluster; if ( sector < DataArea ) { sector ++; /* if we reached the end of root area */ if ( sector == DataArea ) sector = 0; /* return 0 */ return sector; } data_sector = sector - DataArea; if ( !data_sector & ClustMask ) /* in a cluster */ return (++sector); /* got a new cluster */ cluster = nextcluster( (data_sector >> ClustShift) + 2 ); if ( !cluster ) return 0; /* return the start of the new cluster */ sector = ( (cluster - 2) << ClustShift ) + DataArea; 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. * * @param: buf * @param: file structure * @param: sectors * */ void __getfssec(char *buf, struct open_file_t *file, __u32 sectors) { __u32 curr_sector = file->file_sector; __u32 frag_start , next_sector; __u32 con_sec_cnt; do { /* get fragment */ con_sec_cnt = 0; frag_start = curr_sector; do { /* get consective sector count */ con_sec_cnt ++; sectors --; if ( sectors == 0 ) break; next_sector = nextsector(curr_sector); if ( !next_sector ) break; }while( next_sector == (++curr_sector) ); #if 1 /* Debug message */ printf("/**************************************************\n"); printf("You are reading stores at sector --0x%x--0x%x\n", frag_start, frag_start + con_sec_cnt -1); printf("**************************************************/\n"); #endif /* do read */ getlinsec(buf, frag_start, con_sec_cnt); buf += con_sec_cnt << 9;/* adjust buffer pointer */ if ( !sectors ) break; //curr_sector --; /* this is the last sector actually read */ curr_sector = next_sector; }while( sectors ); /* update the file_sector filed for the next read */ file->file_sector = nextsector(curr_sector); } /** * getfssec: * * get multiple sectors from a file * * * @param: buf * @param: file * @param: sectors * @param: have_more * * @return: number of bytes read * */ __u32 getfssec(char *buf, struct open_file_t *file, __u32 sectors, int *have_more) { __u32 bytes_read = sectors << SECTOR_SHIFT; if ( sectors > file->file_left ) sectors = file->file_left; __getfssec(buf, file, sectors); if ( bytes_read >= file->file_bytesleft ) { bytes_read = file->file_bytesleft; file->file_bytesleft = 0; *have_more = 0; close_file(file); } else { file->file_bytesleft -= bytes_read; *have_more = 1; } file->file_left -= sectors; return bytes_read; } /** * mangle_dos_name: * * Mangle a dos filename component pointed to by FILENAME * into MangleBuf; ends on encountering any whitespace or * slash. * * WARNING: saves pointers into the buffer for longname matchs! * * @param: filename * @param: MangleBuf * */ /** * for now, it can't handle this case: * xyxzxyxjfdkfjdjf.txt as it will just output the first 11 chars * but not care the dot char at the later, so I think we need do * this, but it seems that the SYSLINUX doesn't do it, so I will * make it stay as what it was orignal. * */ void mangle_dos_name(char *MangleBuf, char *filename) { char *dst = MangleBuf; char *src = filename; char c; int i = 0; NameStart = filename; for (; i < 11; i ++) MangleBuf[i] = ' '; for (i = 0; i < 11; i++) { c = *src ++; if ( (c <= ' ') || (c == '/') ) break; if ( c == '.' ) { dst = &MangleBuf[8]; i = 7; continue; } if (c >= 'a' && c <= 'z') c -= 32; if ( (c == 0xe5) && (i == 11) ) c = 0x05; *dst++ = c; } MangleBuf[12] = '\0'; while( (*src != '/') && (*src > ' ') ) src ++; NameLen = src - filename; } char entry_name[13]; void unicode_to_ascii(char *entry_name, __u16 *unicode_buf) { int i = 0; for (; i < 13; i++) { if ( unicode_buf[i] == 0xffff ) { entry_name[i] = '\0'; return; } entry_name[i] = (char)unicode_buf[i]; } } /** * long_entry_name: * * get the long entry name * */ void long_entry_name(struct fat_long_name_entry *dir) { int id = dir->id & 0x3f; __u16 unicode_buf[13]; memcpy(unicode_buf, dir->name1, 5 * 2); memcpy(unicode_buf + 5, dir->name2, 6 * 2); memcpy(unicode_buf + 11,dir->name3, 2 * 2); unicode_to_ascii(entry_name, unicode_buf); } __u8 get_checksum(char *dir_name) { int i; __u8 sum=0; for (i=11; i; i--) sum = ((sum & 1) << 7) + (sum >> 1) + *dir_name++; return sum; } __u32 first_sector(struct fat_dir_entry *dir) { __u32 first_clust, sector; first_clust = (dir->first_cluster_high << 16) + dir->first_cluster_low; sector = ((first_clust - 2) << ClustShift) + DataArea; return sector; } /** * search_dos_dir: * * search a specific directory for a pre-mangled filename in * MangleBuf, in the directory starting in sector SECTOR * * NOTE: This file considers finding a zero-length file an * error. This is so we don't have to deal with that special * case elsewhere in the program (most loops have the test * at the end). * * @param: MangleBuf * @param: dir_sector, directory sector * * @out: file pointer * @out: file length (MAY BE ZERO!) * @out: file attribute * @out: dh, clobbered. * */ struct open_file_t* search_dos_dir(char *MangleBuf, __u32 dir_sector, \ __u32 *file_len, __u8 *attr) { struct open_file_t* file; struct cache_struct* cs; struct fat_dir_entry *dir; struct fat_long_name_entry *long_dir; __u8 VFATInit, VFATNext, VFATCsum; __u8 id; __u16 *unicode_buf; __u32 slots; __u32 entries; int checksum; int have_more; char *long_name; file = allocate_file(); if ( !file ) return NULL; /* * Compute the value of a possible VFAT longname * "last" entry (which, of coures, comes first ...) */ slots = (NameLen + 12) / 13; long_name = (char *)malloc(13 * slots); slots |= 0x40; VFATInit = slots; VFATNext = slots; do { cs = get_cache_block(dir_sector); dir = (struct fat_dir_entry *)cs->data; entries = SECTOR_SIZE / 32; /* scan all the entries in a sector */ do { if ( dir->name[0] == 0 ) return NULL; /* Hit directory high water mark */ if ( dir->attr == 0x0f ) { /* it's a long name entry */ long_dir = (struct fat_long_name_entry *)dir; id = long_dir->id; if ( id !=VFATNext ) goto not_match; if ( id & 0x40 ) { /*get the initial checksum value*/ VFATCsum = long_dir->checksum; } else { if ( long_dir->checksum != VFATCsum ) goto not_match; } id &= 0x3f; VFATNext = --id; /* got the long entry name */ long_entry_name(long_dir); memcpy(long_name + id * 13, entry_name, 13); /* * if we got the last entry? * if so, check it, or go on with the next entry */ if ( id == 0 ) { if ( strcmp(long_name, NameStart) ) goto not_match; } goto next_entry; } else { /* it's a short entry */ if ( dir->attr & 0x08 ) /* ingore volume labels */ goto not_match; /* If we have a long name match, then VFATNext must be 0 */ if ( !VFATNext ) { /* * we already have a VFAT long name match, however, * the match is only valid if the checksum matchs. */ checksum = get_checksum(dir->name); if ( checksum == VFATCsum ) goto found; /* got a match on long name */ } else { if ( strncmp(MangleBuf, dir->name, 11) == 0 ) goto found; } } not_match:/* find it again */ VFATNext = VFATInit; next_entry: dir ++; }while ( --entries ); dir_sector = nextsector(dir_sector); }while ( dir_sector ); /* scan another secotr */ found: *file_len = file->file_bytesleft = dir->file_size; file->file_sector = first_sector(dir); *attr = dir->attr; return file; } __u32 CurrentDir = 0; /** * searchdir: * * open a file * * @param: filename, the file we wanna open * @param: file_len, to return the file length * * @return: return the file structure on successful, or NULL. * */ struct open_file_t* searchdir(char *filename) { __u32 dir_sector, prev_dir; __u32 file_len; __u8 attr; char *p; struct open_file_t *file; dir_sector = CurrentDir; if ( *filename == '/' ) { dir_sector = RootDir; filename ++; } while ( *filename ) { p = filename; /* try to find the end */ while ( (*p > ' ') && (*p != '/') ) p ++; if (filename == p) return NULL; prev_dir = dir_sector; mangle_dos_name(MangleBuf, filename); file = search_dos_dir(MangleBuf, dir_sector, &file_len, &attr); if ( !file ) { file_len = 0; return NULL; } if ( *p != '/' ) /* we got a file */ break; if ( (attr & 0x10) == 0 ) /* subdirectory */ return NULL; dir_sector = file->file_sector; close_file(file); filename = p + 1; /* search again */ } if ( (attr & 0x18) || (file_len == 0) ) return NULL; file->file_bytesleft = file_len; file->file_left = ( file_len + SECTOR_SIZE -1 ) >> SECTOR_SHIFT; return file; } /** * readdir: * * read one file from a directory * * returns the file's name in the filename string buffer * * @param: filename * @param: file * */ struct open_file_t * readdir(struct open_file_t* dir_file, char* filename, __u32 *file_len, __u8 *attr) { __u32 sector, sec_off; /* make it to be 1 to check if we have met a long name entry before */ __u8 id = 1; __u8 init_id, next_id; __u8 entries_left; int i; int have_more; struct cache_struct *cs; struct fat_dir_entry *dir; struct fat_long_name_entry *long_dir; struct open_file_t *file; sector = dir_file->file_sector; sec_off = dir_file->file_bytesleft; if ( !sector ) goto fail; entries_left = (SECTOR_SIZE - sec_off) >> 5; cs = get_cache_block(sector); dir = (struct fat_dir_entry *)(cs->data + sec_off);/* resume last position in sector */ while ( 1 ) { if ( dir->name[0] == 0 ) goto fail; if ( dir->attr == 0x0f ) { /* it's a long name */ long_dir = (struct fat_long_name_entry *)dir; if ( long_dir->id & 0x40 ) init_id = id = long_dir->id & 0x3f; else next_id = (long_dir->id & 0x3f) - 1; id --; if ( id != next_id ) goto next_entry; long_entry_name(long_dir); memcpy(filename + id * 13, entry_name, 13); /* * we need go on with the next entry * and we will fall through to next entry */ } else { /* it's a short entry */ if ( !id ) /* we got a long name match */ break; if ( dir->attr & 0x08 ) goto next_entry; for( i = 0; i < 8; i ++) { if ( dir->name[i] == ' ' ) break; *filename++ = dir->name[i]; } *filename++ = '.'; for ( i = 8; i < 11; i ++) { if ( dir->name[i] == ' ' ) break; *filename ++ = dir->name[i]; } /* check if we have got an extention */ if ( ! *(filename - 1) ) *(filename -2) = '\0'; else *filename = '\0'; break; } next_entry: dir ++; entries_left --; if ( !entries_left ) { sector = nextsector(sector); if ( !sector ) goto fail; cs = (struct cache_struct *)get_cache_block(sector); dir = (struct fat_dir_entry *)cs->data; } } /* finally , we get what we want */ entries_left --; if ( !entries_left ) { sector = nextsector(sector); if ( !sector ) goto fail; } file->file_sector = sector; file->file_bytesleft = (SECTOR_SIZE - (entries_left << DIRENT_SHIFT) ) & 0xffff; *file_len = dir->file_size; *attr = dir->attr; return file; fail: close_dir(dir); return NULL; } struct open_file_t* open_file(char *filename) { struct open_file_t *file; file = searchdir(filename); /* if we failed, we also will get a NULL value */ return file; } __u32 read_file(struct open_file_t *file, char *buf, int size, int *have_more) { __u32 sectors = ( size + SECTOR_SIZE - 1 ) >> SECTOR_SHIFT; return getfssec(buf, file, sectors, have_more); } void bsr(__u8 *res, int num) { *res = 0; } /* init. the fs meta data */ void init_fs() { int sectors_per_fat; __u32 clust_num; int RootDirSize; /* get the fat bpb information */ getlinsec(&fat, 0, 1); TotalSectors = fat.bxSectors ? : fat.bsHugeSectors; FAT = fat.bxResSectors; sectors_per_fat = fat.bxFATsecs ? : fat.u.fat32.bxFATsecs_32; RootDir = RootDirArea = FAT + sectors_per_fat * fat.bxFATs; RootDirSize = (fat.bxRootDirEnts+SECTOR_SIZE/32-1) >> (SECTOR_SHIFT-5); DataArea = RootDirArea + RootDirSize; bsr(&ClustShift, fat.bxSecPerClust); ClustByteShift = ClustShift + SECTOR_SHIFT; ClustMask = fat.bxSecPerClust - 1; ClustSize = fat.bxSecPerClust << SECTOR_SHIFT; blk_size = SECTOR_SIZE; clust_num = (TotalSectors - DataArea) >> ClustShift; if ( clust_num < 4085 ) FATType = FAT12; else if ( clust_num < 65525 ) FATType = FAT16; else FATType = FAT32; }