/** * extlinux.c * * */ #include "ext2_fs.h" #include "cache.h" #include "types.h" #include #include #include #include /* * File structure, This holds the information for each currently open file */ struct open_file_t { __u32 file_bytesleft; /* Number of bytes left ( 0 = free ) */ __u32 file_sector; /* Next linear sector to read */ __u32 file_in_sec; /* Sector where inode lives */ __u16 file_in_off; __u16 file_mode; }; /* struct getc_file { __u16 gc_file; __u16 gc_bufbytes; __u16 gc_bufdata; __u8 gc_unget_cnt; __u8 gc_unget_buf[9]; }; */ struct getc_file { struct open_file_t * gc_file; /* file pointer */ __u16 gc_bufbytes; /* Bytes left in buffer */ __u16 gc_bufdata; /* pointer to data in buffer */ __u8 gc_unget_cnt; /* Character pushed back count */ __u8 gc_unget_buf[7]; /* Character pushed back buffer */ }; #define FILENAME_MAX_LG2 8 #define FILENAME_MAX_ ( 1 << FILENAME_MAX_LG2 ) #define LDLINUX_MAGIC 0x3eb202fex #define MAX_OPEN_LG2 6 #define MAX_OPEN ( 1 << MAX_OPEN_LG2 ) #define SECTOR_SHIFT 9 #define SECTOR_SIZE ( 1 << SECTOR_SHIFT ) #define MAX_SYMLINKS 64 #define SYMLINK_SECTORS 2 #define ROOT_DIR_WORD 0x002F #define CUR_DIR_DWORD 0x0000002F2E #define MAX_GETC_LG2 4 #define MAX_GETC ( 1 << MAX_GETC_LG2 ) #define bytes_per_getc_lg2 ( 16 - MAX_GETC_LG2 ) #define bytes_per_getc ( 1 << bytes_per_getc_lg2 ) #define secs_per_getc ( bytes_per_getc / SECTOR_SIZE ) #define MAX_UNGET 7 #define getc_file_lg2 4 #define getc_file_size ( 1 << getc_file_lg2 ) char GetCStack[getc_file_size * MAX_GETC] = {0,}; __u32 CurrentGetC = (__u32) &GetCStack[getc_file_size * MAX_GETC]; extern struct ext2_super_block sb; struct ext2_inode ThisInode; struct open_file_t Files[MAX_OPEN]; __u32 ClustBytesShift, ClustSize, ClustShift; __u32 SecPerClust, ClustMask; __u32 PtrsPerBlock1, PtrsPerBlock2, PtrsPerBlock3; __u32 DescPerBlock, InodePerBlock; /* a static value, which is the boot/extlinux's inode number */ __u32 CurrentDir = 13; /* for EXTLINUX, we make blk_size be the size of a block */ int blk_size; #define TRACKBUFSIZE 8192 char trackbuf[TRACKBUFSIZE]; __u32 EndBlock; char SymlinkBuf[SYMLINK_SECTORS * SECTOR_SIZE + 64]; char *SymlinkTmpBuf = trackbuf; char *lnk_end; char *SymlinkTmpBufEnd = trackbuf + SYMLINK_SECTORS * SECTOR_SIZE+64; /** * strecpy: * * just like the function strcpy(), except it returns non-zero if overflow. * * well, in Syslinux, strcpy() will advance both the dst and src string pointer. * */ int strecpy(char *dst, char *src, char *end) { while ( *src != '\0' ) *dst++ = *src++; *dst = '\0'; if ( dst > end ) return 1; else return 0; } /** * 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_bytesleft == 0 ) /* found it */ return file; file ++; } return NULL; /* not found */ } /** * getlinsec_ext: * * same as getlinsec, except load any sector from the zero * block as all zeros; use to load any data derived from * n ext2 block pointer, i.e. anything *except the superblock * */ void getlinsec_ext(char *buf, int sector, int sector_cnt) { int ext_cnt = 0; if ( sector < SecPerClust ) { ext_cnt = SecPerClust - sector; memset(buf, 0, ext_cnt << SECTOR_SHIFT); buf += ext_cnt << SECTOR_SHIFT; } sector += ext_cnt; sector_cnt -= ext_cnt; getlinsec(buf, sector, sector_cnt); } void getonesec_ext(char *buf, int sector) { getlinsec_ext(buf, sector, 1); } /** * get_group_desc: * * get the group's descriptor of group_num * * @param: group_num, the group number; * * @return: the pointer of the group's descriptor * */ struct ext2_group_desc *get_group_desc(__u32 group_num) { __u32 block_num; __u32 offset; struct ext2_group_desc *desc; struct cache_struct *cs; block_num = group_num / DescPerBlock; offset = group_num % DescPerBlock; block_num += sb.s_first_data_block + 1; cs = get_cache_block(block_num); desc = (struct ext2_group_desc *)cs->data + offset; return desc; } /** * read_inode: * * read the right inode structure to _dst_. * * @param: inode_offset, the inode offset within a group; * @prarm: dst, wher we will store the inode structure; * @param: desc, the pointer to the group's descriptor * @param: block, a pointer used for retruning the blk number for file structure * @param: offset, same as block * */ void read_inode(__u32 inode_offset, struct ext2_inode *dst, struct ext2_group_desc *desc, __u32 *block, __u32 *offset) { struct cache_struct *cs; struct ext2_inode *inode; *block = inode_offset / InodePerBlock + desc->bg_inode_table; *offset = inode_offset % InodePerBlock; cs = get_cache_block(*block); /* well, in EXT4, the inode structure usually be 256 */ inode = (struct ext2_inode *)(cs->data + (*offset * (sb.s_inode_size))); memcpy(dst, inode, EXT2_GOOD_OLD_INODE_SIZE); /* for file structure */ *offset = (inode_offset * sb.s_inode_size) % ClustSize; } /** * open_inode: * * open a file indicated by an inode number in INR * * @param : inr, the inode number * @return: a open_file_t structure pointer * file length in bytes * the first 128 bytes of the inode * */ struct open_file_t * open_inode(unsigned int inr, __u32 *file_len) { struct open_file_t *file; struct ext2_group_desc *desc; struct cache_struct *cs; __u32 inode_group, inode_offset; __u32 block_num, block_off; file = allocate_file(); if ( !file ) return NULL; file->file_sector = 0; inr --; inode_group = inr / sb.s_inodes_per_group; /* get the group desc */ desc = get_group_desc(inode_group); inode_offset = inr % sb.s_inodes_per_group; read_inode(inode_offset, &ThisInode, desc, &block_num, &block_off); /* Finally, we need to convet it to sector for now */ file->file_in_sec = (block_num<>SECTOR_SHIFT); file->file_in_off = block_off & (SECTOR_SIZE - 1); file->file_mode = ThisInode.i_mode; *file_len = file->file_bytesleft = ThisInode.i_size; if ( *file_len == 0 ) return NULL; return file; } /** * close_file: * * Deallocates a file structure point by FILE * * @param: file, the file structure we want deallocate * */ void close_file(struct open_file_t *file) { if ( file == NULL ) return; file->file_bytesleft = 0; } struct ext4_extent_header * ext4_find_leaf (struct ext4_extent_header *eh, __u32 block) { struct ext4_extent_idx *index; struct cache_struct *cs; __u64 blk; int i; char buf[ClustSize]; while (1) { if (eh->eh_magic != EXT4_EXT_MAGIC) return NULL; /* got it */ if (eh->eh_depth == 0) return eh; index = EXT4_FIRST_INDEX(eh); for ( i = 0; i < eh->eh_entries; i++ ) { if ( block < index[i].ei_block ) break; } if ( --i < 0 ) return NULL; blk = index[i].ei_leaf_hi; blk = (blk << 32) + index[i].ei_leaf_lo; /* read the blk to memeory */ cs = get_cache_block(blk); eh = (struct ext4_extent_header *)(cs->data); } } /* handle the ext4 extents to get the phsical block number */ __u64 linsector_extent(__u32 block, struct ext2_inode *inode) { struct ext4_extent_header *leaf; struct ext4_extent *ext; int i; __u64 start; leaf = ext4_find_leaf((struct ext4_extent_header*)inode->i_block,block); if (! leaf) { printf("ERROR, extent leaf not found\n"); return 0; } ext = EXT4_FIRST_EXTENT(leaf); for ( i = 0; i < leaf->eh_entries; i++ ) { if ( block < ext[i].ee_block) break; } if ( --i < 0 ) { printf("ERROR, not find the right block\n"); return 0; } /* got it */ block -= ext[i].ee_block; if ( block >= ext[i].ee_len) return 0; start = ext[i].ee_start_hi; start = (start << 32) + ext[i].ee_start_lo; return start + block; } /** * linsector_direct: * * @param: block, the block index * @param: inode, the inode structure * * @return: the physic block number */ __u32 linsector_direct(__u32 block, struct ext2_inode *inode) { struct cache_struct *cs; __u32 offset; /* direct blocks */ if ( block < EXT2_NDIR_BLOCKS ) return inode->i_block[block]; /* indirect blocks */ block -= EXT2_NDIR_BLOCKS; if ( block < PtrsPerBlock1 ) { __u32 ind_block = inode->i_block[EXT2_IND_BLOCK]; cs = get_cache_block(ind_block); return ((__u32 *)cs->data)[block]; } /* double indirect blocks */ block -= PtrsPerBlock1; if ( block < PtrsPerBlock2 ) { __u32 dou_block = inode->i_block[EXT2_DIND_BLOCK]; cs = get_cache_block(dou_block); dou_block = ( (__u32 *)cs->data )[block / PtrsPerBlock1]; cs = get_cache_block(dou_block); return ( (__u32*)cs->data )[block % PtrsPerBlock1]; } /* triple indirect block */ block -= PtrsPerBlock2; if ( block < PtrsPerBlock3 ) { __u32 tri_block = inode->i_block[EXT2_TIND_BLOCK]; cs = get_cache_block(tri_block); tri_block = ((__u32*)cs->data)[block / PtrsPerBlock2]; cs = get_cache_block(tri_block); tri_block = ((__u32*)cs->data)[block % PtrsPerBlock2]; cs = get_cache_block(tri_block); return ((__u32*)cs->data)[block % PtrsPerBlock1]; } /* File too big, can not handle */ printf("ERROR, file too big\n"); return 0; } /** * linsector: * * Convert a linear sector index in a file to linear sector number * * well, alought this function converts a linear sector number to * physic sector number, it uses block cache in the implemention. * * @param: lin_sector, the lineral sector index * * @return: physic sector number */ __u32 linsector(__u32 lin_sector) { __u32 block = lin_sector >> ClustShift; struct ext2_inode *inode; /* well, this is what I think the variable ThisInode used for */ inode = &ThisInode; if (inode->i_flags & EXT4_EXTENTS_FLAG) block = linsector_extent(block, inode); else block = (__u32)linsector_direct(block, inode); if ( !block ) { printf("WARNING: something error happend at linsector..\n"); return 0; } /* finally convert it to sector */ return ( (block << ClustShift) + (lin_sector & ClustMask) ); } /** * getfssec: * * Get multiple sectors from a file * * Alought we have made the buffer data based on block size, * we use sector for implemention; because reading multiple * sectors (then can be multiple blocks) is what the function * do. So, let's be based on sectors. * * same as above, execpt si is a pointer to a open_file_t * * @param: buf, the buffer to store data * @param: file, the pointer to open_file_t * @param: sectors, number of sectors to read * @param: have_more, used to indicate if there are more in the file * * @return: number of bytes read * */ int getfssec(char *buf, struct open_file_t *file, int sectors, int *have_more) { int sector_left, next_sector, sector_idx; int frag_start, con_sec_cnt; int bytes_read = sectors << SECTOR_SHIFT; sector_left = (file->file_bytesleft + SECTOR_SIZE - 1) >> SECTOR_SHIFT; if ( sectors > sector_left ) sectors = sector_left; do { /* get the frament */ sector_idx = file->file_sector; next_sector = frag_start = linsector(sector_idx); con_sec_cnt = 0; do { /* get the consective sectors count */ con_sec_cnt ++; sectors --; if ( sectors <= 0 ) break; /*** * well, in Syslinux that means in real mode, there's a * 64k block limit, so we should need handle it here. * * But it's a user program that test the driver, so we * don't need to do this here but should remember do it * when we move into Sylinux. */ sector_idx ++; next_sector ++; }while( next_sector == linsector(sectors) ); #if 0 /* 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 getlinsec_ext(buf, frag_start, con_sec_cnt); buf += con_sec_cnt << 9; file->file_sector += con_sec_cnt; /* next sector index */ }while(sectors); if ( bytes_read >= file->file_bytesleft ) { bytes_read = file->file_bytesleft; close_file(file); *have_more = 0; return bytes_read; } file->file_bytesleft -= bytes_read; *have_more = 1; return bytes_read; } /* * NOTE! unlike strncmp, ext2_match_entry returns 1 for success, 0 for failure. * * len <= EXT2_NAME_LEN and de != NULL are guaranteed by caller. */ static inline int ext2_match_entry (const char * const name, struct ext2_dir_entry * de) { if (!de->d_inode) return 0; return !strncmp(name, de->d_name, de->d_name_len); } /* * p is at least 6 bytes before the end of page */ inline struct ext2_dir_entry *ext2_next_entry(struct ext2_dir_entry *p) { return (struct ext2_dir_entry *)((char*)p + p->d_rec_len); } /** * find_dir_entry: * * find a dir entry, if find return it or return NULL * */ struct ext2_dir_entry* find_dir_entry(struct open_file_t *file, char *filename) { int have_more; struct ext2_dir_entry *de; EndBlock = (__u32 )trackbuf + (SecPerClust << SECTOR_SHIFT); /* read a clust at a time */ getfssec(trackbuf, file, SecPerClust, &have_more); de = (struct ext2_dir_entry *)trackbuf; while ( 1 ) { if ( (char *)de >= (char *)EndBlock ) { if (have_more) { getfssec(trackbuf, file,SecPerClust,&have_more); de = (struct ext2_dir_entry *)trackbuf; } else return NULL; } /* Zero inode == void entry */ if ( de->d_inode == 0 ) { de = ext2_next_entry(de); continue; } if ( ext2_match_entry (filename, de) ) { filename += de->d_name_len; if ( (*filename == 0) || (*filename == '/') ) return de; /* got it */ /* not match, restore the filename then try next */ filename -= de->d_name_len; } de = ext2_next_entry(de); } } char* do_symlink(struct open_file_t *file, __u32 file_len, char *filename) { int flag, have_more; flag = ThisInode.i_file_acl ? SecPerClust : 0; if ( ThisInode.i_blocks == flag ) { /* fast symlink */ close_file(file); /* we've got all we need */ memcpy(SymlinkTmpBuf, ThisInode.i_block, file_len); lnk_end = SymlinkTmpBuf + file_len; } else { /* slow symlink */ getfssec(SymlinkTmpBuf,file,SYMLINK_SECTORS,&have_more); lnk_end = SymlinkTmpBuf + file_len; } /* * well, this happens like: * "/boot/xxx/y/z.abc" where xxx is a symlink to "/other/here" * so, we should get a new path name like: * "/other/here/y/z.abc" */ if ( *filename != 0 ) *lnk_end++ = '/'; if ( strecpy(lnk_end, filename, SymlinkTmpBufEnd) ) return NULL; /* buffer overflow */ /* * now copy it to the "real" buffer; we need to have * two buffers so we avoid overwriting the tail on * the next copy. */ strcpy(SymlinkBuf, SymlinkTmpBuf); /* return the new path */ return SymlinkBuf; } /** * searchdir: * * Search the root directory for a pre-mangle filename in FILENAME. * * @param: filename, the filename we want to search. * * @out : a file pointer * @out : file lenght in bytes * */ struct open_file_t* searchdir(char * filename, __u32 *file_len) { struct open_file_t *file; struct ext2_dir_entry *de; __u8 file_mode; __u8 SymlinkCtr = MAX_SYMLINKS; __u32 inr = CurrentDir; __u32 ThisDir; begin_path: while ( *filename == '/' ) { /* Absolute filename */ inr = EXT2_ROOT_INO; filename ++; } open: if ( (file = open_inode(inr, file_len) ) == NULL ) return NULL; file_mode = file->file_mode >> S_IFSHIFT; /* It's a file */ if ( file_mode == T_IFREG ) { if ( *filename == '\0' ) goto done; else goto err; } /* It's a directory */ if ( file_mode == T_IFDIR ) { ThisDir = inr; if ( *filename == 0 ) goto err; while ( *filename == '/' ) filename ++; de = find_dir_entry(file, filename); if ( !de ) return NULL; inr = de->d_inode; filename += de->d_name_len; close(file); goto open; } /* * It's a symlink. We have to determine if it's a fast symlink * (data stored in the inode) or not (data stored as a regular * file.) Either which way, we start from the directory * which we just visited if relative, or from the root directory * if absolute, and append any remaining part of the path. */ if ( file_mode == T_IFLNK ) { if ( --SymlinkCtr==0 || *file_len>=SYMLINK_SECTORS*SECTOR_SIZE) goto err; /* too many links or symlink too long */ filename = do_symlink(file, *file_len, filename); if ( !filename ) goto err_noclose;/* buffer overflow */ inr = ThisDir; goto begin_path; /* we got a new path, so search it again */ } /* Otherwise, something bad ... */ err: close_file(file); err_noclose: *file_len = 0; file = NULL; done: return file; } /** * open: * * open the file * * @param: filename * * @return: return 1 one successful, or 0 * */ struct open_file_t* ext2_open(char *filename) { struct open_file_t *file; struct getc_file *gc_file; int file_len; file = searchdir(filename, &file_len); if ( !file ) return NULL; CurrentGetC -= getc_file_size; if ( (char *)CurrentGetC < GetCStack ) { /* Stack full */ close_file(file); return NULL; } gc_file = (struct getc_file *) CurrentGetC; gc_file->gc_file = file; gc_file->gc_bufbytes = 0; gc_file->gc_unget_cnt = 0; return file; } /** * read function: * return the bytes read */ int ext2_read(struct open_file_t *file, char *buf, int size, int *have_more) { int sector = (size + SECTOR_SIZE - 1) >> SECTOR_SHIFT; return getfssec(buf, file, sector, have_more); } /** * init. the fs meta data */ void init_fs(struct ext2_super_block *sb) { /* read the super block */ getlinsec(sb, 2, 2); ClustBytesShift = sb->s_log_block_size + 10; ClustSize = 1 << ClustBytesShift; ClustShift = ClustBytesShift - SECTOR_SHIFT; blk_size = ClustSize; DescPerBlock = blk_size / ( 1 << ext2_group_desc_lg2size); InodePerBlock = blk_size / sb->s_inode_size; SecPerClust = ClustSize >> SECTOR_SHIFT; ClustMask = SecPerClust - 1; PtrsPerBlock1 = 1 << (ClustBytesShift - 2 ); PtrsPerBlock2 = 1 << ( (ClustBytesShift - 2) * 2); PtrsPerBlock3 = 1 << ( (ClustBytesShift - 2) * 3); }