/* * Squashfs - a compressed read only filesystem for Linux * * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007 * Phillip Lougher * * 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 2, * 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, write to the Free Software * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * inode.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_SQUASHFS_CRYPT) #include #include #include int aes_reinit(struct crypto_tfm *tfm); int aes_set_key(struct crypto_tfm *tfm, const unsigned char *in_key, unsigned int key_len); void aes_decrypt(struct crypto_tfm *tfm, void *out, const void *in); #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ #include "squashfs.h" /*--- #undef TRACE ---*/ /*--- #undef ERROR ---*/ /*--- #undef SERROR ---*/ /*--- #undef WARNING ---*/ /*--- #define TRACE printk ---*/ /*--- #define ERROR printk ---*/ /*--- #define SERROR printk ---*/ /*--- #define WARNING printk ---*/ static void squashfs_put_super(struct super_block *); static int squashfs_statfs(struct dentry *, struct kstatfs *); static int squashfs_symlink_readpage(struct file *file, struct page *page); static int squashfs_readpage(struct file *file, struct page *page); static int squashfs_readpage4K(struct file *file, struct page *page); static int squashfs_readdir(struct file *, void *, filldir_t); static struct inode *squashfs_alloc_inode(struct super_block *sb); static void squashfs_destroy_inode(struct inode *inode); static int init_inodecache(void); static void destroy_inodecache(void); static struct dentry *squashfs_lookup(struct inode *, struct dentry *, struct nameidata *); static int squashfs_read_inode(struct inode *i, squashfs_inode_t inode); static long long read_blocklist(struct inode *inode, int index, int readahead_blks, char *block_list, unsigned short **block_p, unsigned int *bsize); static int squashfs_get_sb(struct file_system_type *,int, const char *, void *, struct vfsmount *); static void vfs_read_inode(struct inode *i); static struct dentry *squashfs_get_parent(struct dentry *child); static struct file_system_type squashfs_fs_type = { .owner = THIS_MODULE, .name = "squashfs", .get_sb = squashfs_get_sb, .kill_sb = kill_block_super, .fs_flags = FS_REQUIRES_DEV }; static unsigned char squashfs_filetype_table[] = { DT_UNKNOWN, DT_DIR, DT_REG, DT_LNK, DT_BLK, DT_CHR, DT_FIFO, DT_SOCK }; static struct super_operations squashfs_super_ops = { .alloc_inode = squashfs_alloc_inode, .destroy_inode = squashfs_destroy_inode, .statfs = squashfs_statfs, .put_super = squashfs_put_super, }; static struct super_operations squashfs_export_super_ops = { .alloc_inode = squashfs_alloc_inode, .destroy_inode = squashfs_destroy_inode, .statfs = squashfs_statfs, .put_super = squashfs_put_super, .read_inode = vfs_read_inode }; struct export_operations squashfs_export_ops = { .get_parent = squashfs_get_parent }; SQSH_EXTERN struct address_space_operations squashfs_symlink_aops = { .readpage = squashfs_symlink_readpage }; SQSH_EXTERN struct address_space_operations squashfs_aops = { .readpage = squashfs_readpage }; SQSH_EXTERN struct address_space_operations squashfs_aops_4K = { .readpage = squashfs_readpage4K }; static struct file_operations squashfs_dir_ops = { .read = generic_read_dir, .readdir = squashfs_readdir }; SQSH_EXTERN struct inode_operations squashfs_dir_inode_ops = { .lookup = squashfs_lookup }; #if defined(CONFIG_SQUASHFS_CRYPT) /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void squashfs_dump_block(char *text, unsigned char *blk, unsigned int anz) { #if 1 int i, sum; unsigned char *start = blk; printk("%s", text); #if 1 for(i = 0 ; i < anz ; i += sizeof(unsigned int)) { if(!(i & 0x1F)) printk("\n\t"); printk("0x%08x ", *(unsigned int *)blk); blk += sizeof(unsigned int); } printk("\n"); #endif for(i = 0, sum = 0 ; i < anz ; i += sizeof(unsigned int)) { sum += ((unsigned int *)start)[i / sizeof(unsigned int)]; } printk("sum = 0x%08x\n", sum); #endif } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ #if defined(CONFIG_SQUASHFS_CRYPT) /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void squashfs_decrypt_block(struct squashfs_sb_info *msblk, unsigned char *_out, unsigned char *_in, unsigned int len) { unsigned int p; struct _aes_block { unsigned int data[4]; }; struct _aes_block *in = (struct _aes_block *)_in; struct _aes_block *out = (struct _aes_block *)_out; struct _aes_block prev_c; struct _aes_block strich; len >>= 4; switch(msblk->crypt_type) { case squash_fs_crypt_type_non: return; case squash_fs_crypt_type_aes: for(p = 0 ; p < len ; p++) { aes_decrypt(msblk->tfm, out++, in++); } return; case squash_fs_crypt_type_cbc: memcpy(&prev_c, msblk->serial, sizeof(prev_c)); for(p = 0 ; p < len ; p++) { unsigned int tmp; aes_decrypt(msblk->tfm, &strich, in); tmp = prev_c.data[0] ^ strich.data[0]; prev_c.data[0] = in->data[0]; /* out und in koennen der gleichen pointer sein, deshalb zwischenspeichern */ out->data[0] = tmp; tmp = prev_c.data[1] ^ strich.data[1]; prev_c.data[1] = in->data[1]; out->data[1] = tmp; tmp = prev_c.data[2] ^ strich.data[2]; prev_c.data[2] = in->data[2]; out->data[2] = tmp; tmp = prev_c.data[3] ^ strich.data[3]; prev_c.data[3] = in->data[3]; out->data[3] = tmp; in++, out++; } return; } } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ static struct buffer_head *get_block_length(struct super_block *s, int *cur_index, int *offset, int *c_byte) { struct squashfs_sb_info *msblk = s->s_fs_info; unsigned short temp; struct buffer_head *bh; if (!(bh = sb_bread(s, *cur_index))) goto out; #if defined(CONFIG_SQUASHFS_CRYPT) if (msblk->tfm && *cur_index) { TRACE("[get_block_length] first (cur_index %u offset %u) ", *cur_index, *offset); /*--- squashfs_dump_block(bh->squashfs_flags ? "raw" : "compressed", bh->b_data, bh->b_size); ---*/ if(bh->squashfs_flags == 0) { unsigned int i; /*--- aes_reinit(msblk->tfm); ---*/ aes_set_key(msblk->tfm, msblk->key, CONFIG_SQUASHFS_CRYPT_KEY_LEN >> 3); squashfs_decrypt_block(msblk, bh->b_data, bh->b_data, bh->b_size); bh->squashfs_flags = 1; } } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ if (msblk->devblksize - *offset == 1) { if (msblk->swap) ((unsigned char *) &temp)[1] = *((unsigned char *) (bh->b_data + *offset)); else ((unsigned char *) &temp)[0] = *((unsigned char *) (bh->b_data + *offset)); brelse(bh); if (!(bh = sb_bread(s, ++(*cur_index)))) goto out; #if defined(CONFIG_SQUASHFS_CRYPT) if (msblk->tfm && *cur_index) { TRACE("[get_block_length] second (cur_index %u offset %u) ", *cur_index, *offset); /*--- squashfs_dump_block(bh->squashfs_flags ? "raw" : "compressed", bh->b_data, bh->b_size); ---*/ if(bh->squashfs_flags == 0) { /*--- aes_reinit(msblk->tfm); ---*/ aes_set_key(msblk->tfm, msblk->key, CONFIG_SQUASHFS_CRYPT_KEY_LEN >> 3); squashfs_decrypt_block(msblk, bh->b_data, bh->b_data, bh->b_size); bh->squashfs_flags = 1; } } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ if (msblk->swap) ((unsigned char *) &temp)[0] = *((unsigned char *) bh->b_data); else ((unsigned char *) &temp)[1] = *((unsigned char *) bh->b_data); *c_byte = temp; *offset = 1; } else { if (msblk->swap) { ((unsigned char *) &temp)[1] = *((unsigned char *) (bh->b_data + *offset)); ((unsigned char *) &temp)[0] = *((unsigned char *) (bh->b_data + *offset + 1)); } else { ((unsigned char *) &temp)[0] = *((unsigned char *) (bh->b_data + *offset)); ((unsigned char *) &temp)[1] = *((unsigned char *) (bh->b_data + *offset + 1)); } *c_byte = temp; *offset += 2; } if (SQUASHFS_CHECK_DATA(msblk->sblk.flags)) { if (*offset == msblk->devblksize) { brelse(bh); if (!(bh = sb_bread(s, ++(*cur_index)))) goto out; #if defined(CONFIG_SQUASHFS_CRYPT) if (msblk->tfm && *cur_index) { TRACE("[get_block_length] thired (cur_index %u offset %u) ", *cur_index, *offset); /*--- squashfs_dump_block(bh->squashfs_flags ? "raw" : "compressed", bh->b_data, bh->b_size); ---*/ if(bh->squashfs_flags == 0) { /*--- aes_reinit(msblk->tfm); ---*/ aes_set_key(msblk->tfm, msblk->key, CONFIG_SQUASHFS_CRYPT_KEY_LEN >> 3); squashfs_decrypt_block(msblk, bh->b_data, bh->b_data, bh->b_size); bh->squashfs_flags = 1; } } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ *offset = 0; } if (*((unsigned char *) (bh->b_data + *offset)) != SQUASHFS_MARKER_BYTE) { ERROR("Metadata block marker corrupt @ %x\n", *cur_index); brelse(bh); goto out; } (*offset)++; } return bh; out: return NULL; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ /*--- void *debug_msblk; ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ SQSH_EXTERN unsigned int squashfs_read_data(struct super_block *s, char *buffer, long long index, unsigned int length, long long *next_index, int srclength) { struct squashfs_sb_info *msblk = s->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; struct buffer_head *bh[((SQUASHFS_FILE_MAX_SIZE - 1) >> msblk->devblksize_log2) + 2]; unsigned int offset = index & ((1 << msblk->devblksize_log2) - 1); unsigned int cur_index = index >> msblk->devblksize_log2; int bytes, avail_bytes, b = 0, k = 0; unsigned int compressed; unsigned int c_byte = length; unsigned char *lzma_buffer = NULL; unsigned int lzma_buffer_len = 0; /*--- debug_msblk = msblk; ---*/ if(msblk->use_lzma) { lzma_buffer = vmalloc(0x10000); if(lzma_buffer == NULL) { printk("ERROR: no memory\n"); return 0; } } /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ if (c_byte) { bytes = msblk->devblksize - offset; compressed = SQUASHFS_COMPRESSED_BLOCK(c_byte); c_byte = SQUASHFS_COMPRESSED_SIZE_BLOCK(c_byte); TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n", index, compressed ? "" : "un", (unsigned int) c_byte, srclength); if (c_byte > srclength || index < 0 || (index + c_byte) > sblk->bytes_used) { TRACE("[read_failure] %s %u\n", __FILE__, __LINE__); goto read_failure; } /*--- TRACE("[squashfs_read_data/first] cur_index = %u (0x%x) blksize %u\n", cur_index, cur_index, msblk->devblksize); ---*/ if (!(bh[0] = sb_getblk(s, cur_index))) { TRACE("[block_release] %s %u\n", __FILE__, __LINE__); goto block_release; } #if defined(CONFIG_SQUASHFS_CRYPT) if(cur_index == 0) bh[0]->squashfs_flags = 1; #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ for (b = 1; bytes < c_byte; b++) { if (!(bh[b] = sb_getblk(s, ++cur_index))) { TRACE("[block_release] %s %u\n", __FILE__, __LINE__); goto block_release; } bytes += msblk->devblksize; } TRACE("[squashfs_read_data] devblocksize %d\n", msblk->devblksize); ll_rw_block(READ, b, bh); } else { if (index < 0 || (index + 2) > sblk->bytes_used) { ERROR("[read_failure] %s %u\n", __FILE__, __LINE__); goto read_failure; } if (!(bh[0] = get_block_length(s, &cur_index, &offset, &c_byte))) { ERROR("[read_failure] %s %u\n", __FILE__, __LINE__); goto read_failure; } bytes = msblk->devblksize - offset; compressed = SQUASHFS_COMPRESSED(c_byte); c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte); TRACE("Block @ 0x%llx, %scompressed size %d\n", index, compressed ? "" : "un", (unsigned int) c_byte); if (c_byte > srclength || (index + c_byte) > sblk->bytes_used) { ERROR("[read_failure] %s %u (c_byte %u > srclength %u) || (index %llu + c_byte %u) > sblk->bytes_used %llu \n", __FILE__, __LINE__, c_byte, srclength, index, c_byte, sblk->bytes_used); goto read_failure; } /*--- TRACE("[squashfs_read_data/continue] cur_index = %u (0x%x) blksize %u\n", cur_index, cur_index, msblk->devblksize); ---*/ for (b = 1; bytes < c_byte; b++) { if (!(bh[b] = sb_getblk(s, ++cur_index))) { ERROR("[block_release] %s %u\n", __FILE__, __LINE__); goto block_release; } bytes += msblk->devblksize; } /*--- TRACE("[squashfs_read_data] b = %u \n", b); ---*/ ll_rw_block(READ, b - 1, bh + 1); } /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ if (compressed) { int zlib_err = 0; /* * uncompress block */ down(&msblk->read_data_mutex); msblk->stream.next_out = buffer; msblk->stream.avail_out = srclength; /*----------------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------------*/ for (bytes = 0; k < b; k++) { /*--- static unsigned char last_crypted_block[10][1024]; ---*/ /*--- static unsigned int last_block_nr[10]; ---*/ /*--- static unsigned int last_block_crypt[10]; ---*/ /*--- static unsigned int current_last_block = 0; ---*/ avail_bytes = (c_byte - bytes) > (msblk->devblksize - offset) ? msblk->devblksize - offset : c_byte - bytes; wait_on_buffer(bh[k]); if (!buffer_uptodate(bh[k])) { ERROR("[release_mutex] %s %u\n", __FILE__, __LINE__); goto release_mutex; } /*------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------*/ #if defined(CONFIG_SQUASHFS_CRYPT) if (msblk->tfm) { TRACE("[squashfs_read_data] (cur_index %u offset %u, k %u) ", cur_index - b, offset, k); /*--- squashfs_dump_block(bh[k]->squashfs_flags ? "raw" : "compressed", bh[k]->b_data, bh[k]->b_size); ---*/ /*--- last_block_crypt[current_last_block] = bh[k]->squashfs_flags; ---*/ if(bh[k]->squashfs_flags == 0) { /*--- aes_reinit(msblk->tfm); ---*/ aes_set_key(msblk->tfm, msblk->key, CONFIG_SQUASHFS_CRYPT_KEY_LEN >> 3); squashfs_decrypt_block(msblk, bh[k]->b_data, bh[k]->b_data, bh[k]->b_size); bh[k]->squashfs_flags = 1; } /*--- last_block_nr[current_last_block] = cur_index - b + k; ---*/ /*--- memcpy(last_crypted_block[current_last_block], bh[k]->b_data, 1024); ---*/ /*--- if(++current_last_block > 9) current_last_block = 0; ---*/ } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ msblk->stream.next_in = bh[k]->b_data + offset; msblk->stream.avail_in = avail_bytes; if(msblk->use_lzma) { /*--- static unsigned char lzma_buffer[0x10000]; ---*/ /*--- static unsigned int lzma_buffer_len; ---*/ unsigned int processed_in = 0, processed_out = 0; if(k == 0) { lzma_buffer_len = 0; msblk->stream.total_out = 0; if (avail_bytes == 0) { offset = 0; brelse(bh[k]); continue; } } if(avail_bytes < c_byte) { TRACE("[LzmaDecode] not enough bytes avail %u c_byte %u\n", avail_bytes, c_byte); memcpy(lzma_buffer + lzma_buffer_len, msblk->stream.next_in, msblk->stream.avail_in); msblk->stream.next_in = lzma_buffer; lzma_buffer_len += msblk->stream.avail_in; msblk->stream.avail_in = lzma_buffer_len; } else { lzma_buffer_len = avail_bytes; } TRACE("[LzmaDecode] next_in 0x%x avail_in %u next_out 0x%x avail_out %u\n", (unsigned int)msblk->stream.next_in, msblk->stream.avail_in, (unsigned int)msblk->stream.next_out, msblk->stream.avail_out); TRACE("[LzmaDecode] lzma_buffer_len %u\n", lzma_buffer_len); if(lzma_buffer_len >= c_byte) { /*--- static unsigned int lzma_used = 0; ---*/ /*--- lzma_used++; ---*/ zlib_err = LzmaDecode(&msblk->lzma_decoder_state, msblk->stream.next_in, msblk->stream.avail_in, &processed_in, msblk->stream.next_out, msblk->stream.avail_out, &processed_out); TRACE(" processed_in %u processed_out %u\n", processed_in, processed_out); /*--- dump_block("LzmaDecode", msblk->stream.next_in, msblk->stream.avail_in); ---*/ if ((zlib_err != LZMA_RESULT_OK) || !processed_in) { int ii; ERROR("\n", zlib_err); /*--- printk("LZMA: avail_out 0x%x avail_in 0x%x lzma_buffer_len 0x%x c_byte 0x%x\n", msblk->stream.avail_out, msblk->stream.avail_in, lzma_buffer_len, c_byte); ---*/ /*--- printk("LZAM: processed: in 0x%x out 0x%x lzma_used %d\n", processed_in, processed_out, lzma_used); ---*/ /*--- for(ii = 0 ; ii < 10 ; ii++) { ---*/ /*--- if(current_last_block == 0) ---*/ /*--- current_last_block = 9; ---*/ /*--- else ---*/ /*--- current_last_block--; ---*/ /*--- printk("block %u %s crypted\n", ---*/ /*--- last_block_nr[current_last_block], ---*/ /*--- last_block_crypt[current_last_block] ? "was" : " was not"); ---*/ /*--- squashfs_dump_block("cryped", last_crypted_block[current_last_block], 1024); ---*/ /*--- } ---*/ /*--- lzma_used--; ---*/ /*--- panic("LZMA ERROR !!!!"); ---*/ /*--- for(k = 0 ; k < b ; k++) { ---*/ /*--- if (atomic_read(&(bh[k]->b_count))) { ---*/ /*--- brelse(bh[k]); ---*/ /*--- } ---*/ /*--- } ---*/ goto release_mutex; } /*--- lzma_used--; ---*/ zlib_err = Z_OK; msblk->stream.next_out += processed_out; msblk->stream.avail_out -= processed_out; msblk->stream.total_out += processed_out; } } else { if (k == 0) { zlib_err = zlib_inflateInit(&msblk->stream); if (zlib_err != Z_OK) { ERROR("zlib_inflateInit returned unexpected result 0x%x, srclength %d\n", zlib_err, srclength); goto release_mutex; } if (avail_bytes == 0) { offset = 0; brelse(bh[k]); continue; } } zlib_err = zlib_inflate(&msblk->stream, Z_NO_FLUSH); if (zlib_err != Z_OK && zlib_err != Z_STREAM_END) { ERROR("zlib_inflate returned unexpected result 0x%x, srclength %d, avail_in %d, avail_out %d\n", zlib_err, srclength, msblk->stream.avail_in, msblk->stream.avail_out); goto release_mutex; } } bytes += avail_bytes; offset = 0; brelse(bh[k]); } /*----------------------------------------------------------------------------------*\ \*----------------------------------------------------------------------------------*/ if(msblk->use_lzma == 0) { if (zlib_err != Z_STREAM_END) goto release_mutex; zlib_err = zlib_inflateEnd(&msblk->stream); if (zlib_err != Z_OK) { ERROR("zlib_inflateEnd returned unexpected result 0x%x, srclength %d\n", zlib_err, srclength); goto release_mutex; } } bytes = msblk->stream.total_out; up(&msblk->read_data_mutex); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ } else { int i; for(i = 0; i < b; i++) { wait_on_buffer(bh[i]); if(!buffer_uptodate(bh[i])) { ERROR("[block_release] %s %u\n", __FILE__, __LINE__); goto block_release; } /*------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------*/ #if defined(CONFIG_SQUASHFS_CRYPT) if (msblk->tfm) { TRACE("[squashfs_read_data] (cur_index %u offset %u, i %u) ", cur_index - b, offset, i); /*--- squashfs_dump_block(bh[i]->squashfs_flags ? "raw" : "compressed", bh[i]->b_data, bh[i]->b_size); ---*/ if(bh[i]->squashfs_flags == 0) { /*--- aes_reinit(msblk->tfm); ---*/ aes_set_key(msblk->tfm, msblk->key, CONFIG_SQUASHFS_CRYPT_KEY_LEN >> 3); squashfs_decrypt_block(msblk, bh[i]->b_data, bh[i]->b_data, bh[i]->b_size); bh[i]->squashfs_flags = 1; } } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ } for (bytes = 0; k < b; k++) { avail_bytes = (c_byte - bytes) > (msblk->devblksize - offset) ? msblk->devblksize - offset : c_byte - bytes; memcpy(buffer + bytes, bh[k]->b_data + offset, avail_bytes); bytes += avail_bytes; offset = 0; brelse(bh[k]); } } if (next_index) { *next_index = index + c_byte + (length ? 0 : (SQUASHFS_CHECK_DATA(msblk->sblk.flags) ? 3 : 2)); } if(lzma_buffer) vfree(lzma_buffer); return bytes; release_mutex: up(&msblk->read_data_mutex); block_release: for (; k < b; k++) brelse(bh[k]); read_failure: ERROR("sb_bread failed reading block 0x%x\n", cur_index); if(lzma_buffer) vfree(lzma_buffer); return 0; } SQSH_EXTERN int squashfs_get_cached_block(struct super_block *s, char *buffer, long long block, unsigned int offset, int length, long long *next_block, unsigned int *next_offset) { struct squashfs_sb_info *msblk = s->s_fs_info; int n, i, bytes, return_length = length; long long next_index; TRACE("Entered squashfs_get_cached_block [%llx:%x]\n", block, offset); while ( 1 ) { for (i = 0; i < SQUASHFS_CACHED_BLKS; i++) if (msblk->block_cache[i].block == block) break; down(&msblk->block_cache_mutex); if (i == SQUASHFS_CACHED_BLKS) { /* read inode header block */ for (i = msblk->next_cache, n = SQUASHFS_CACHED_BLKS; n ; n --, i = (i + 1) % SQUASHFS_CACHED_BLKS) if (msblk->block_cache[i].block != SQUASHFS_USED_BLK) break; if (n == 0) { wait_queue_t wait; init_waitqueue_entry(&wait, current); add_wait_queue(&msblk->waitq, &wait); set_current_state(TASK_UNINTERRUPTIBLE); up(&msblk->block_cache_mutex); schedule(); set_current_state(TASK_RUNNING); remove_wait_queue(&msblk->waitq, &wait); continue; } msblk->next_cache = (i + 1) % SQUASHFS_CACHED_BLKS; if (msblk->block_cache[i].block == SQUASHFS_INVALID_BLK) { if (!(msblk->block_cache[i].data = kmalloc(SQUASHFS_METADATA_SIZE, GFP_KERNEL))) { ERROR("Failed to allocate cache" "block\n"); up(&msblk->block_cache_mutex); goto out; } } msblk->block_cache[i].block = SQUASHFS_USED_BLK; up(&msblk->block_cache_mutex); msblk->block_cache[i].length = squashfs_read_data(s, msblk->block_cache[i].data, block, 0, &next_index, SQUASHFS_METADATA_SIZE); if (msblk->block_cache[i].length == 0) { ERROR("Unable to read cache block [%llx:%x]\n", block, offset); down(&msblk->block_cache_mutex); msblk->block_cache[i].block = SQUASHFS_INVALID_BLK; kfree(msblk->block_cache[i].data); wake_up(&msblk->waitq); up(&msblk->block_cache_mutex); goto out; } down(&msblk->block_cache_mutex); wake_up(&msblk->waitq); msblk->block_cache[i].block = block; msblk->block_cache[i].next_index = next_index; TRACE("Read cache block [%llx:%x]\n", block, offset); } if (msblk->block_cache[i].block != block) { up(&msblk->block_cache_mutex); continue; } bytes = msblk->block_cache[i].length - offset; if (bytes < 1) { up(&msblk->block_cache_mutex); goto out; } else if (bytes >= length) { if (buffer) memcpy(buffer, msblk->block_cache[i].data + offset, length); if (msblk->block_cache[i].length - offset == length) { *next_block = msblk->block_cache[i].next_index; *next_offset = 0; } else { *next_block = block; *next_offset = offset + length; } up(&msblk->block_cache_mutex); goto finish; } else { if (buffer) { memcpy(buffer, msblk->block_cache[i].data + offset, bytes); buffer += bytes; } block = msblk->block_cache[i].next_index; up(&msblk->block_cache_mutex); length -= bytes; offset = 0; } } finish: return return_length; out: return 0; } static int get_fragment_location(struct super_block *s, unsigned int fragment, long long *fragment_start_block, unsigned int *fragment_size) { struct squashfs_sb_info *msblk = s->s_fs_info; long long start_block = msblk->fragment_index[SQUASHFS_FRAGMENT_INDEX(fragment)]; int offset = SQUASHFS_FRAGMENT_INDEX_OFFSET(fragment); struct squashfs_fragment_entry fragment_entry; if (msblk->swap) { struct squashfs_fragment_entry sfragment_entry; if (!squashfs_get_cached_block(s, (char *) &sfragment_entry, start_block, offset, sizeof(sfragment_entry), &start_block, &offset)) goto out; SQUASHFS_SWAP_FRAGMENT_ENTRY(&fragment_entry, &sfragment_entry); } else if (!squashfs_get_cached_block(s, (char *) &fragment_entry, start_block, offset, sizeof(fragment_entry), &start_block, &offset)) goto out; *fragment_start_block = fragment_entry.start_block; *fragment_size = fragment_entry.size; return 1; out: return 0; } SQSH_EXTERN void release_cached_fragment(struct squashfs_sb_info *msblk, struct squashfs_fragment_cache *fragment) { down(&msblk->fragment_mutex); fragment->locked --; wake_up(&msblk->fragment_wait_queue); up(&msblk->fragment_mutex); } SQSH_EXTERN struct squashfs_fragment_cache *get_cached_fragment(struct super_block *s, long long start_block, int length) { int i, n; struct squashfs_sb_info *msblk = s->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; while ( 1 ) { down(&msblk->fragment_mutex); for (i = 0; i < SQUASHFS_CACHED_FRAGMENTS && msblk->fragment[i].block != start_block; i++); if (i == SQUASHFS_CACHED_FRAGMENTS) { for (i = msblk->next_fragment, n = SQUASHFS_CACHED_FRAGMENTS; n && msblk->fragment[i].locked; n--, i = (i + 1) % SQUASHFS_CACHED_FRAGMENTS); if (n == 0) { wait_queue_t wait; init_waitqueue_entry(&wait, current); add_wait_queue(&msblk->fragment_wait_queue, &wait); set_current_state(TASK_UNINTERRUPTIBLE); up(&msblk->fragment_mutex); schedule(); set_current_state(TASK_RUNNING); remove_wait_queue(&msblk->fragment_wait_queue, &wait); continue; } msblk->next_fragment = (msblk->next_fragment + 1) % SQUASHFS_CACHED_FRAGMENTS; if (msblk->fragment[i].data == NULL) if (!(msblk->fragment[i].data = SQUASHFS_ALLOC (SQUASHFS_FILE_MAX_SIZE))) { ERROR("Failed to allocate fragment " "cache block\n"); up(&msblk->fragment_mutex); goto out; } msblk->fragment[i].block = SQUASHFS_INVALID_BLK; msblk->fragment[i].locked = 1; up(&msblk->fragment_mutex); if (!(msblk->fragment[i].length = squashfs_read_data(s, msblk->fragment[i].data, start_block, length, NULL, sblk->block_size))) { ERROR("Unable to read fragment cache block " "[%llx]\n", start_block); msblk->fragment[i].locked = 0; goto out; } down(&msblk->fragment_mutex); msblk->fragment[i].block = start_block; TRACE("New fragment %d, start block %lld, locked %d\n", i, msblk->fragment[i].block, msblk->fragment[i].locked); up(&msblk->fragment_mutex); break; } msblk->fragment[i].locked++; up(&msblk->fragment_mutex); TRACE("Got fragment %d, start block %lld, locked %d\n", i, msblk->fragment[i].block, msblk->fragment[i].locked); break; } return &msblk->fragment[i]; out: return NULL; } static void squashfs_new_inode(struct squashfs_sb_info *msblk, struct inode *i, struct squashfs_base_inode_header *inodeb) { i->i_ino = inodeb->inode_number; i->i_mtime.tv_sec = inodeb->mtime; i->i_atime.tv_sec = inodeb->mtime; i->i_ctime.tv_sec = inodeb->mtime; i->i_uid = msblk->uid[inodeb->uid]; i->i_mode = inodeb->mode; i->i_size = 0; if (inodeb->guid == SQUASHFS_GUIDS) i->i_gid = i->i_uid; else i->i_gid = msblk->guid[inodeb->guid]; } static squashfs_inode_t squashfs_inode_lookup(struct super_block *s, int ino) { struct squashfs_sb_info *msblk = s->s_fs_info; long long start = msblk->inode_lookup_table[SQUASHFS_LOOKUP_BLOCK(ino - 1)]; int offset = SQUASHFS_LOOKUP_BLOCK_OFFSET(ino - 1); squashfs_inode_t inode; TRACE("Entered squashfs_inode_lookup, inode_number = %d\n", ino); if (msblk->swap) { squashfs_inode_t sinode; if (!squashfs_get_cached_block(s, (char *) &sinode, start, offset, sizeof(sinode), &start, &offset)) goto out; SQUASHFS_SWAP_INODE_T((&inode), &sinode); } else if (!squashfs_get_cached_block(s, (char *) &inode, start, offset, sizeof(inode), &start, &offset)) goto out; TRACE("squashfs_inode_lookup, inode = 0x%llx\n", inode); return inode; out: return SQUASHFS_INVALID_BLK; } static void vfs_read_inode(struct inode *i) { struct squashfs_sb_info *msblk = i->i_sb->s_fs_info; squashfs_inode_t inode = squashfs_inode_lookup(i->i_sb, i->i_ino); TRACE("Entered vfs_read_inode\n"); if(inode != SQUASHFS_INVALID_BLK) (msblk->read_inode)(i, inode); } static struct dentry *squashfs_get_parent(struct dentry *child) { struct inode *i = child->d_inode; struct inode *parent = iget(i->i_sb, SQUASHFS_I(i)->u.s2.parent_inode); struct dentry *rv; TRACE("Entered squashfs_get_parent\n"); if(parent == NULL) { rv = ERR_PTR(-EACCES); goto out; } rv = d_alloc_anon(parent); if(rv == NULL) rv = ERR_PTR(-ENOMEM); out: return rv; } SQSH_EXTERN struct inode *squashfs_iget(struct super_block *s, squashfs_inode_t inode, unsigned int inode_number) { struct squashfs_sb_info *msblk = s->s_fs_info; struct inode *i = iget_locked(s, inode_number); TRACE("Entered squashfs_iget\n"); if(i && (i->i_state & I_NEW)) { (msblk->read_inode)(i, inode); unlock_new_inode(i); } return i; } static int squashfs_read_inode(struct inode *i, squashfs_inode_t inode) { struct super_block *s = i->i_sb; struct squashfs_sb_info *msblk = s->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; long long block = SQUASHFS_INODE_BLK(inode) + sblk->inode_table_start; unsigned int offset = SQUASHFS_INODE_OFFSET(inode); long long next_block; unsigned int next_offset; union squashfs_inode_header id, sid; struct squashfs_base_inode_header *inodeb = &id.base, *sinodeb = &sid.base; TRACE("Entered squashfs_read_inode\n"); if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodeb, block, offset, sizeof(*sinodeb), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_BASE_INODE_HEADER(inodeb, sinodeb, sizeof(*sinodeb)); } else if (!squashfs_get_cached_block(s, (char *) inodeb, block, offset, sizeof(*inodeb), &next_block, &next_offset)) goto failed_read; squashfs_new_inode(msblk, i, inodeb); switch(inodeb->inode_type) { case SQUASHFS_FILE_TYPE: { unsigned int frag_size; long long frag_blk; struct squashfs_reg_inode_header *inodep = &id.reg; struct squashfs_reg_inode_header *sinodep = &sid.reg; if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodep, block, offset, sizeof(*sinodep), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_REG_INODE_HEADER(inodep, sinodep); } else if (!squashfs_get_cached_block(s, (char *) inodep, block, offset, sizeof(*inodep), &next_block, &next_offset)) goto failed_read; frag_blk = SQUASHFS_INVALID_BLK; if (inodep->fragment != SQUASHFS_INVALID_FRAG && !get_fragment_location(s, inodep->fragment, &frag_blk, &frag_size)) goto failed_read; i->i_nlink = 1; i->i_size = inodep->file_size; i->i_fop = &generic_ro_fops; i->i_mode |= S_IFREG; i->i_blocks = ((i->i_size - 1) >> 9) + 1; SQUASHFS_I(i)->u.s1.fragment_start_block = frag_blk; SQUASHFS_I(i)->u.s1.fragment_size = frag_size; SQUASHFS_I(i)->u.s1.fragment_offset = inodep->offset; SQUASHFS_I(i)->start_block = inodep->start_block; SQUASHFS_I(i)->u.s1.block_list_start = next_block; SQUASHFS_I(i)->offset = next_offset; if (sblk->block_size > 4096) i->i_data.a_ops = &squashfs_aops; else i->i_data.a_ops = &squashfs_aops_4K; TRACE("File inode %x:%x, start_block %llx, " "block_list_start %llx, offset %x\n", SQUASHFS_INODE_BLK(inode), offset, inodep->start_block, next_block, next_offset); break; } case SQUASHFS_LREG_TYPE: { unsigned int frag_size; long long frag_blk; struct squashfs_lreg_inode_header *inodep = &id.lreg; struct squashfs_lreg_inode_header *sinodep = &sid.lreg; if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodep, block, offset, sizeof(*sinodep), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_LREG_INODE_HEADER(inodep, sinodep); } else if (!squashfs_get_cached_block(s, (char *) inodep, block, offset, sizeof(*inodep), &next_block, &next_offset)) goto failed_read; frag_blk = SQUASHFS_INVALID_BLK; if (inodep->fragment != SQUASHFS_INVALID_FRAG && !get_fragment_location(s, inodep->fragment, &frag_blk, &frag_size)) goto failed_read; i->i_nlink = inodep->nlink; i->i_size = inodep->file_size; i->i_fop = &generic_ro_fops; i->i_mode |= S_IFREG; i->i_blocks = ((i->i_size - 1) >> 9) + 1; SQUASHFS_I(i)->u.s1.fragment_start_block = frag_blk; SQUASHFS_I(i)->u.s1.fragment_size = frag_size; SQUASHFS_I(i)->u.s1.fragment_offset = inodep->offset; SQUASHFS_I(i)->start_block = inodep->start_block; SQUASHFS_I(i)->u.s1.block_list_start = next_block; SQUASHFS_I(i)->offset = next_offset; if (sblk->block_size > 4096) i->i_data.a_ops = &squashfs_aops; else i->i_data.a_ops = &squashfs_aops_4K; TRACE("File inode %x:%x, start_block %llx, " "block_list_start %llx, offset %x\n", SQUASHFS_INODE_BLK(inode), offset, inodep->start_block, next_block, next_offset); break; } case SQUASHFS_DIR_TYPE: { struct squashfs_dir_inode_header *inodep = &id.dir; struct squashfs_dir_inode_header *sinodep = &sid.dir; if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodep, block, offset, sizeof(*sinodep), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_DIR_INODE_HEADER(inodep, sinodep); } else if (!squashfs_get_cached_block(s, (char *) inodep, block, offset, sizeof(*inodep), &next_block, &next_offset)) goto failed_read; i->i_nlink = inodep->nlink; i->i_size = inodep->file_size; i->i_op = &squashfs_dir_inode_ops; i->i_fop = &squashfs_dir_ops; i->i_mode |= S_IFDIR; SQUASHFS_I(i)->start_block = inodep->start_block; SQUASHFS_I(i)->offset = inodep->offset; SQUASHFS_I(i)->u.s2.directory_index_count = 0; SQUASHFS_I(i)->u.s2.parent_inode = inodep->parent_inode; TRACE("Directory inode %x:%x, start_block %x, offset " "%x\n", SQUASHFS_INODE_BLK(inode), offset, inodep->start_block, inodep->offset); break; } case SQUASHFS_LDIR_TYPE: { struct squashfs_ldir_inode_header *inodep = &id.ldir; struct squashfs_ldir_inode_header *sinodep = &sid.ldir; if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodep, block, offset, sizeof(*sinodep), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_LDIR_INODE_HEADER(inodep, sinodep); } else if (!squashfs_get_cached_block(s, (char *) inodep, block, offset, sizeof(*inodep), &next_block, &next_offset)) goto failed_read; i->i_nlink = inodep->nlink; i->i_size = inodep->file_size; i->i_op = &squashfs_dir_inode_ops; i->i_fop = &squashfs_dir_ops; i->i_mode |= S_IFDIR; SQUASHFS_I(i)->start_block = inodep->start_block; SQUASHFS_I(i)->offset = inodep->offset; SQUASHFS_I(i)->u.s2.directory_index_start = next_block; SQUASHFS_I(i)->u.s2.directory_index_offset = next_offset; SQUASHFS_I(i)->u.s2.directory_index_count = inodep->i_count; SQUASHFS_I(i)->u.s2.parent_inode = inodep->parent_inode; TRACE("Long directory inode %x:%x, start_block %x, " "offset %x\n", SQUASHFS_INODE_BLK(inode), offset, inodep->start_block, inodep->offset); break; } case SQUASHFS_SYMLINK_TYPE: { struct squashfs_symlink_inode_header *inodep = &id.symlink; struct squashfs_symlink_inode_header *sinodep = &sid.symlink; if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodep, block, offset, sizeof(*sinodep), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_SYMLINK_INODE_HEADER(inodep, sinodep); } else if (!squashfs_get_cached_block(s, (char *) inodep, block, offset, sizeof(*inodep), &next_block, &next_offset)) goto failed_read; i->i_nlink = inodep->nlink; i->i_size = inodep->symlink_size; i->i_op = &page_symlink_inode_operations; i->i_data.a_ops = &squashfs_symlink_aops; i->i_mode |= S_IFLNK; SQUASHFS_I(i)->start_block = next_block; SQUASHFS_I(i)->offset = next_offset; TRACE("Symbolic link inode %x:%x, start_block %llx, " "offset %x\n", SQUASHFS_INODE_BLK(inode), offset, next_block, next_offset); break; } case SQUASHFS_BLKDEV_TYPE: case SQUASHFS_CHRDEV_TYPE: { struct squashfs_dev_inode_header *inodep = &id.dev; struct squashfs_dev_inode_header *sinodep = &sid.dev; if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodep, block, offset, sizeof(*sinodep), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_DEV_INODE_HEADER(inodep, sinodep); } else if (!squashfs_get_cached_block(s, (char *) inodep, block, offset, sizeof(*inodep), &next_block, &next_offset)) goto failed_read; i->i_nlink = inodep->nlink; i->i_mode |= (inodeb->inode_type == SQUASHFS_CHRDEV_TYPE) ? S_IFCHR : S_IFBLK; init_special_inode(i, i->i_mode, old_decode_dev(inodep->rdev)); TRACE("Device inode %x:%x, rdev %x\n", SQUASHFS_INODE_BLK(inode), offset, inodep->rdev); break; } case SQUASHFS_FIFO_TYPE: case SQUASHFS_SOCKET_TYPE: { struct squashfs_ipc_inode_header *inodep = &id.ipc; struct squashfs_ipc_inode_header *sinodep = &sid.ipc; if (msblk->swap) { if (!squashfs_get_cached_block(s, (char *) sinodep, block, offset, sizeof(*sinodep), &next_block, &next_offset)) goto failed_read; SQUASHFS_SWAP_IPC_INODE_HEADER(inodep, sinodep); } else if (!squashfs_get_cached_block(s, (char *) inodep, block, offset, sizeof(*inodep), &next_block, &next_offset)) goto failed_read; i->i_nlink = inodep->nlink; i->i_mode |= (inodeb->inode_type == SQUASHFS_FIFO_TYPE) ? S_IFIFO : S_IFSOCK; init_special_inode(i, i->i_mode, 0); break; } default: ERROR("Unknown inode type %d in squashfs_iget!\n", inodeb->inode_type); goto failed_read1; } return 1; failed_read: ERROR("Unable to read inode [%llx:%x]\n", block, offset); failed_read1: make_bad_inode(i); return 0; } static int read_inode_lookup_table(struct super_block *s) { struct squashfs_sb_info *msblk = s->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; unsigned int length = SQUASHFS_LOOKUP_BLOCK_BYTES(sblk->inodes); TRACE("In read_inode_lookup_table, length %d\n", length); /* Allocate inode lookup table */ if (!(msblk->inode_lookup_table = kmalloc(length, GFP_KERNEL))) { ERROR("Failed to allocate inode lookup table\n"); return 0; } if (!squashfs_read_data(s, (char *) msblk->inode_lookup_table, sblk->lookup_table_start, length | SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, length)) { ERROR("unable to read inode lookup table\n"); return 0; } if (msblk->swap) { int i; long long block; for (i = 0; i < SQUASHFS_LOOKUP_BLOCKS(sblk->inodes); i++) { SQUASHFS_SWAP_LOOKUP_BLOCKS((&block), &msblk->inode_lookup_table[i], 1); msblk->inode_lookup_table[i] = block; } } return 1; } static int read_fragment_index_table(struct super_block *s) { struct squashfs_sb_info *msblk = s->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; unsigned int length = SQUASHFS_FRAGMENT_INDEX_BYTES(sblk->fragments); if(length == 0) return 1; /* Allocate fragment index table */ if (!(msblk->fragment_index = kmalloc(length, GFP_KERNEL))) { ERROR("Failed to allocate fragment index table\n"); return 0; } if (!squashfs_read_data(s, (char *) msblk->fragment_index, sblk->fragment_table_start, length | SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, length)) { ERROR("unable to read fragment index table\n"); return 0; } if (msblk->swap) { int i; long long fragment; for (i = 0; i < SQUASHFS_FRAGMENT_INDEXES(sblk->fragments); i++) { SQUASHFS_SWAP_FRAGMENT_INDEXES((&fragment), &msblk->fragment_index[i], 1); msblk->fragment_index[i] = fragment; } } return 1; } static int supported_squashfs_filesystem(struct squashfs_sb_info *msblk, int silent) { struct squashfs_super_block *sblk = &msblk->sblk; msblk->read_inode = squashfs_read_inode; msblk->read_blocklist = read_blocklist; msblk->read_fragment_index_table = read_fragment_index_table; if (sblk->s_major == 1) { if (!squashfs_1_0_supported(msblk)) { SERROR("Major/Minor mismatch, Squashfs 1.0 filesystems " "are unsupported\n"); SERROR("Please recompile with " "Squashfs 1.0 support enabled\n"); return 0; } } else if (sblk->s_major == 2) { if (!squashfs_2_0_supported(msblk)) { SERROR("Major/Minor mismatch, Squashfs 2.0 filesystems " "are unsupported\n"); SERROR("Please recompile with " "Squashfs 2.0 support enabled\n"); return 0; } } else if(sblk->s_major != SQUASHFS_MAJOR || ((sblk->s_minor > SQUASHFS_MINOR) && (sblk->s_minor != SQUASHFS_MINOR_LZMA) && (sblk->s_minor != SQUASHFS_MINOR_AES_LZMA) && (sblk->s_minor != SQUASHFS_MINOR_CBC_LZMA) && (sblk->s_minor != SQUASHFS_MINOR_AES_ZIP) && (sblk->s_minor != SQUASHFS_MINOR_CBC_ZIP))) { SERROR("Major/Minor mismatch, trying to mount newer %d.%d " "filesystem\n", sblk->s_major, sblk->s_minor); SERROR("Please update your kernel\n"); return 0; } return 1; } static int squashfs_fill_super(struct super_block *s, void *data, int silent) { struct squashfs_sb_info *msblk; struct squashfs_super_block *sblk; int i; char b[BDEVNAME_SIZE]; struct inode *root; TRACE("Entered squashfs_read_superblock\n"); if (!(s->s_fs_info = kmalloc(sizeof(struct squashfs_sb_info), GFP_KERNEL))) { ERROR("Failed to allocate superblock\n"); goto failure; } memset(s->s_fs_info, 0, sizeof(struct squashfs_sb_info)); msblk = s->s_fs_info; if (!(msblk->stream.workspace = vmalloc(zlib_inflate_workspacesize()))) { ERROR("Failed to allocate zlib workspace\n"); goto failure; } sblk = &msblk->sblk; msblk->devblksize = sb_min_blocksize(s, BLOCK_SIZE); msblk->devblksize_log2 = ffz(~msblk->devblksize); init_MUTEX(&msblk->read_data_mutex); init_MUTEX(&msblk->read_page_mutex); init_MUTEX(&msblk->block_cache_mutex); init_MUTEX(&msblk->fragment_mutex); init_MUTEX(&msblk->meta_index_mutex); init_waitqueue_head(&msblk->waitq); init_waitqueue_head(&msblk->fragment_wait_queue); sblk->bytes_used = sizeof(struct squashfs_super_block); if (!squashfs_read_data(s, (char *) sblk, SQUASHFS_START, sizeof(struct squashfs_super_block) | SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, sizeof(struct squashfs_super_block))) { SERROR("unable to read superblock\n"); goto failed_mount; } /*--- dump_block("squashfs_fill_super", (unsigned char *)sblk, sizeof(*sblk)); ---*/ /* Check it is a SQUASHFS superblock */ msblk->swap = 0; if ((s->s_magic = sblk->s_magic) != SQUASHFS_MAGIC) { if (sblk->s_magic == SQUASHFS_MAGIC_SWAP) { struct squashfs_super_block ssblk; WARNING("Mounting a different endian SQUASHFS " "filesystem on %s\n", bdevname(s->s_bdev, b)); SQUASHFS_SWAP_SUPER_BLOCK(&ssblk, sblk); memcpy(sblk, &ssblk, sizeof(struct squashfs_super_block)); msblk->swap = 1; } else { SERROR("Can't find a SQUASHFS superblock on %s\n", bdevname(s->s_bdev, b)); goto failed_mount; } } /* Check the MAJOR & MINOR versions */ if(!supported_squashfs_filesystem(msblk, silent)) { ERROR("invallid MAJOR & MINOR\n"); goto failed_mount; } /* Check the filesystem does not extend beyond the end of the block device */ if(sblk->bytes_used < 0 || sblk->bytes_used > i_size_read(s->s_bdev->bd_inode)) { ERROR("Check the filesystem does not extend beyond the end of the block device \n"); ERROR("bytes_used %llu i_size_read(s->s_bdev->bd_inode) %lu\n", sblk->bytes_used, (unsigned long) i_size_read(s->s_bdev->bd_inode)); goto failed_mount; } /* Check the root inode for sanity */ if (SQUASHFS_INODE_OFFSET(sblk->root_inode) > SQUASHFS_METADATA_SIZE) { ERROR("Check the root inode for sanity failed\n"); goto failed_mount; } TRACE("Found valid superblock on %s\n", bdevname(s->s_bdev, b)); TRACE("Inodes are %scompressed\n", SQUASHFS_UNCOMPRESSED_INODES (sblk->flags) ? "un" : ""); TRACE("Data is %scompressed\n", SQUASHFS_UNCOMPRESSED_DATA(sblk->flags) ? "un" : ""); TRACE("Check data is %s present in the filesystem\n", SQUASHFS_CHECK_DATA(sblk->flags) ? "" : "not"); TRACE("Filesystem size %lld bytes\n", sblk->bytes_used); TRACE("Block size %d\n", sblk->block_size); TRACE("Number of inodes %d\n", sblk->inodes); if (sblk->s_major > 1) TRACE("Number of fragments %d\n", sblk->fragments); TRACE("Number of uids %d\n", sblk->no_uids); TRACE("Number of gids %d\n", sblk->no_guids); TRACE("sblk->inode_table_start %llx\n", sblk->inode_table_start); TRACE("sblk->directory_table_start %llx\n", sblk->directory_table_start); if (sblk->s_major > 1) TRACE("sblk->fragment_table_start %llx\n", sblk->fragment_table_start); TRACE("sblk->uid_start %llx\n", sblk->uid_start); TRACE("sblk->s_major %x\n", sblk->s_major); TRACE("sblk->s_minor %x\n", sblk->s_minor); if((sblk->s_minor == SQUASHFS_MINOR_LZMA) || (sblk->s_minor == SQUASHFS_MINOR_AES_LZMA) || (sblk->s_minor == SQUASHFS_MINOR_CBC_LZMA)) { TRACE("[squashfs] use lzma compression \n"); vfree(msblk->stream.workspace); /*--- alloc new buffer for decompression, free zlib ---*/ memset(&msblk->lzma_decoder_state, 0, sizeof(msblk->lzma_decoder_state)); msblk->lzma_decoder_state.Properties.lc = 3; /*--- parameter mit denen komprimiert wurde ---*/ msblk->lzma_decoder_state.Properties.lp = 0; msblk->lzma_decoder_state.Properties.pb = 2; TRACE("alloc %d Bytes memory for lzma\n", LzmaGetNumProbs(&msblk->lzma_decoder_state.Properties) * sizeof(CProb)); msblk->lzma_decoder_state.Probs = (CProb *)vmalloc(LzmaGetNumProbs(&msblk->lzma_decoder_state.Properties) * sizeof(CProb)); if (!msblk->lzma_decoder_state.Probs) { ERROR("Failed to allocate memory for lzma\n"); goto failed_mount; } msblk->use_lzma = 1; } else { printk("[squashfs] use zip compression \n"); msblk->use_lzma = 0; } #if defined(CONFIG_SQUASHFS_CRYPT) if( (sblk->s_minor == SQUASHFS_MINOR_AES_LZMA) || (sblk->s_minor == SQUASHFS_MINOR_CBC_LZMA) || (sblk->s_minor == SQUASHFS_MINOR_AES_ZIP) || (sblk->s_minor == SQUASHFS_MINOR_CBC_ZIP)) { if((sblk->s_minor == SQUASHFS_MINOR_CBC_LZMA) || (sblk->s_minor == SQUASHFS_MINOR_CBC_ZIP)) { msblk->crypt_type = squash_fs_crypt_type_cbc; printk("[squashfs] crypto cbc selected\n"); } else { msblk->crypt_type = squash_fs_crypt_type_aes; printk("[squashfs] crypto aes selected\n"); } { extern unsigned char *squash_fs_key; /*--- msblk->key = "default-key-1234"; ---*/ msblk->key = squash_fs_key; } msblk->tfm = (struct crypto_tfm *)kzalloc(sizeof(struct crypto_tfm) + sizeof(unsigned int) * 140 + sizeof(unsigned char) * 32, GFP_KERNEL); if((msblk->tfm) == NULL) { ERROR("[squashfs] crypto (aes) not avail\n"); panic("squashfs: decrypt failed\n"); } msblk->tfm->__crt_ctx[0] = ((unsigned char *)msblk->tfm + sizeof(struct crypto_tfm) + sizeof(void *) + sizeof(unsigned char) * 32); msblk->tfm->__crt_ctx[0] = (void *)((unsigned int)msblk->tfm->__crt_ctx[0] & ~0x1F); /* 32 Byte align */ { char *serial = prom_getenv("SerialNumber"); int i; msblk->serial = (char *)kzalloc(16, GFP_KERNEL); if (msblk->serial && serial) { memcpy(msblk->serial, serial, 16); } else { ERROR("[squashfs] serial no available\n"); panic("squashfs: memory serial failed\n"); } } } else { msblk->crypt_type = squash_fs_crypt_type_non; } #endif /*--- #if defined(CONFIG_SQUASHFS_CRYPT) ---*/ s->s_flags |= MS_RDONLY; s->s_op = &squashfs_super_ops; /* Init inode_table block pointer array */ if (!(msblk->block_cache = kmalloc(sizeof(struct squashfs_cache) * SQUASHFS_CACHED_BLKS, GFP_KERNEL))) { ERROR("Failed to allocate block cache\n"); goto failed_mount; } for (i = 0; i < SQUASHFS_CACHED_BLKS; i++) msblk->block_cache[i].block = SQUASHFS_INVALID_BLK; msblk->next_cache = 0; /* Allocate read_page block */ if (!(msblk->read_page = kmalloc(sblk->block_size, GFP_KERNEL))) { ERROR("Failed to allocate read_page block\n"); goto failed_mount; } /* Allocate uid and gid tables */ if (!(msblk->uid = kmalloc((sblk->no_uids + sblk->no_guids) * sizeof(unsigned int), GFP_KERNEL))) { ERROR("Failed to allocate uid/gid table\n"); goto failed_mount; } msblk->guid = msblk->uid + sblk->no_uids; if (msblk->swap) { unsigned int suid[sblk->no_uids + sblk->no_guids]; if (!squashfs_read_data(s, (char *) &suid, sblk->uid_start, ((sblk->no_uids + sblk->no_guids) * sizeof(unsigned int)) | SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, (sblk->no_uids + sblk->no_guids) * sizeof(unsigned int))) { ERROR("unable to read uid/gid table\n"); goto failed_mount; } SQUASHFS_SWAP_DATA(msblk->uid, suid, (sblk->no_uids + sblk->no_guids), (sizeof(unsigned int) * 8)); } else if (!squashfs_read_data(s, (char *) msblk->uid, sblk->uid_start, ((sblk->no_uids + sblk->no_guids) * sizeof(unsigned int)) | SQUASHFS_COMPRESSED_BIT_BLOCK, NULL, (sblk->no_uids + sblk->no_guids) * sizeof(unsigned int))) { ERROR("unable to read uid/gid table\n"); goto failed_mount; } if (sblk->s_major == 1 && squashfs_1_0_supported(msblk)) goto allocate_root; if (!(msblk->fragment = kmalloc(sizeof(struct squashfs_fragment_cache) * SQUASHFS_CACHED_FRAGMENTS, GFP_KERNEL))) { ERROR("Failed to allocate fragment block cache\n"); goto failed_mount; } for (i = 0; i < SQUASHFS_CACHED_FRAGMENTS; i++) { msblk->fragment[i].locked = 0; msblk->fragment[i].block = SQUASHFS_INVALID_BLK; msblk->fragment[i].data = NULL; } msblk->next_fragment = 0; /* Allocate and read fragment index table */ if (msblk->read_fragment_index_table(s) == 0) goto failed_mount; if(sblk->s_major < 3 || sblk->lookup_table_start == SQUASHFS_INVALID_BLK) goto allocate_root; /* Allocate and read inode lookup table */ if (read_inode_lookup_table(s) == 0) goto failed_mount; s->s_op = &squashfs_export_super_ops; s->s_export_op = &squashfs_export_ops; allocate_root: root = new_inode(s); if ((msblk->read_inode)(root, sblk->root_inode) == 0) goto failed_mount; insert_inode_hash(root); if ((s->s_root = d_alloc_root(root)) == NULL) { ERROR("Root inode create failed\n"); iput(root); goto failed_mount; } TRACE("Leaving squashfs_read_super\n"); return 0; failed_mount: kfree(msblk->inode_lookup_table); kfree(msblk->fragment_index); kfree(msblk->fragment); kfree(msblk->uid); kfree(msblk->read_page); kfree(msblk->block_cache); kfree(msblk->fragment_index_2); vfree(msblk->stream.workspace); kfree(s->s_fs_info); s->s_fs_info = NULL; return -EINVAL; failure: return -ENOMEM; } static int squashfs_statfs(struct dentry *dentry, struct kstatfs *buf) { struct squashfs_sb_info *msblk = dentry->d_sb->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; TRACE("Entered squashfs_statfs\n"); buf->f_type = SQUASHFS_MAGIC; buf->f_bsize = sblk->block_size; buf->f_blocks = ((sblk->bytes_used - 1) >> sblk->block_log) + 1; buf->f_bfree = buf->f_bavail = 0; buf->f_files = sblk->inodes; buf->f_ffree = 0; buf->f_namelen = SQUASHFS_NAME_LEN; return 0; } static int squashfs_symlink_readpage(struct file *file, struct page *page) { struct inode *inode = page->mapping->host; int index = page->index << PAGE_CACHE_SHIFT, length, bytes; long long block = SQUASHFS_I(inode)->start_block; int offset = SQUASHFS_I(inode)->offset; void *pageaddr = kmap(page); TRACE("Entered squashfs_symlink_readpage, page index %ld, start block " "%llx, offset %x\n", page->index, SQUASHFS_I(inode)->start_block, SQUASHFS_I(inode)->offset); for (length = 0; length < index; length += bytes) { if (!(bytes = squashfs_get_cached_block(inode->i_sb, NULL, block, offset, PAGE_CACHE_SIZE, &block, &offset))) { ERROR("Unable to read symbolic link [%llx:%x]\n", block, offset); goto skip_read; } } if (length != index) { ERROR("(squashfs_symlink_readpage) length != index\n"); bytes = 0; goto skip_read; } bytes = (i_size_read(inode) - length) > PAGE_CACHE_SIZE ? PAGE_CACHE_SIZE : i_size_read(inode) - length; if (!(bytes = squashfs_get_cached_block(inode->i_sb, pageaddr, block, offset, bytes, &block, &offset))) ERROR("Unable to read symbolic link [%llx:%x]\n", block, offset); skip_read: memset(pageaddr + bytes, 0, PAGE_CACHE_SIZE - bytes); kunmap(page); flush_dcache_page(page); SetPageUptodate(page); unlock_page(page); return 0; } struct meta_index *locate_meta_index(struct inode *inode, int index, int offset) { struct meta_index *meta = NULL; struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; int i; down(&msblk->meta_index_mutex); TRACE("locate_meta_index: index %d, offset %d\n", index, offset); if(msblk->meta_index == NULL) goto not_allocated; for (i = 0; i < SQUASHFS_META_NUMBER; i ++) if (msblk->meta_index[i].inode_number == inode->i_ino && msblk->meta_index[i].offset >= offset && msblk->meta_index[i].offset <= index && msblk->meta_index[i].locked == 0) { TRACE("locate_meta_index: entry %d, offset %d\n", i, msblk->meta_index[i].offset); meta = &msblk->meta_index[i]; offset = meta->offset; } if (meta) meta->locked = 1; not_allocated: up(&msblk->meta_index_mutex); return meta; } struct meta_index *empty_meta_index(struct inode *inode, int offset, int skip) { struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; struct meta_index *meta = NULL; int i; down(&msblk->meta_index_mutex); TRACE("empty_meta_index: offset %d, skip %d\n", offset, skip); if(msblk->meta_index == NULL) { if (!(msblk->meta_index = kmalloc(sizeof(struct meta_index) * SQUASHFS_META_NUMBER, GFP_KERNEL))) { ERROR("Failed to allocate meta_index\n"); goto failed; } for(i = 0; i < SQUASHFS_META_NUMBER; i++) { msblk->meta_index[i].inode_number = 0; msblk->meta_index[i].locked = 0; } msblk->next_meta_index = 0; } for(i = SQUASHFS_META_NUMBER; i && msblk->meta_index[msblk->next_meta_index].locked; i --) msblk->next_meta_index = (msblk->next_meta_index + 1) % SQUASHFS_META_NUMBER; if(i == 0) { TRACE("empty_meta_index: failed!\n"); goto failed; } TRACE("empty_meta_index: returned meta entry %d, %p\n", msblk->next_meta_index, &msblk->meta_index[msblk->next_meta_index]); meta = &msblk->meta_index[msblk->next_meta_index]; msblk->next_meta_index = (msblk->next_meta_index + 1) % SQUASHFS_META_NUMBER; meta->inode_number = inode->i_ino; meta->offset = offset; meta->skip = skip; meta->entries = 0; meta->locked = 1; failed: up(&msblk->meta_index_mutex); return meta; } void release_meta_index(struct inode *inode, struct meta_index *meta) { meta->locked = 0; } static int read_block_index(struct super_block *s, int blocks, char *block_list, long long *start_block, int *offset) { struct squashfs_sb_info *msblk = s->s_fs_info; unsigned int *block_listp; int block = 0; if (msblk->swap) { char sblock_list[blocks << 2]; if (!squashfs_get_cached_block(s, sblock_list, *start_block, *offset, blocks << 2, start_block, offset)) { ERROR("Unable to read block list [%llx:%x]\n", *start_block, *offset); goto failure; } SQUASHFS_SWAP_INTS(((unsigned int *)block_list), ((unsigned int *)sblock_list), blocks); } else if (!squashfs_get_cached_block(s, block_list, *start_block, *offset, blocks << 2, start_block, offset)) { ERROR("Unable to read block list [%llx:%x]\n", *start_block, *offset); goto failure; } for (block_listp = (unsigned int *) block_list; blocks; block_listp++, blocks --) block += SQUASHFS_COMPRESSED_SIZE_BLOCK(*block_listp); return block; failure: return -1; } #define SIZE 256 static inline int calculate_skip(int blocks) { int skip = (blocks - 1) / ((SQUASHFS_SLOTS * SQUASHFS_META_ENTRIES + 1) * SQUASHFS_META_INDEXES); return skip >= 7 ? 7 : skip + 1; } static int get_meta_index(struct inode *inode, int index, long long *index_block, int *index_offset, long long *data_block, char *block_list) { struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; int skip = calculate_skip(i_size_read(inode) >> sblk->block_log); int offset = 0; struct meta_index *meta; struct meta_entry *meta_entry; long long cur_index_block = SQUASHFS_I(inode)->u.s1.block_list_start; int cur_offset = SQUASHFS_I(inode)->offset; long long cur_data_block = SQUASHFS_I(inode)->start_block; int i; index /= SQUASHFS_META_INDEXES * skip; while ( offset < index ) { meta = locate_meta_index(inode, index, offset + 1); if (meta == NULL) { if ((meta = empty_meta_index(inode, offset + 1, skip)) == NULL) goto all_done; } else { if(meta->entries == 0) goto failed; offset = index < meta->offset + meta->entries ? index : meta->offset + meta->entries - 1; meta_entry = &meta->meta_entry[offset - meta->offset]; cur_index_block = meta_entry->index_block + sblk->inode_table_start; cur_offset = meta_entry->offset; cur_data_block = meta_entry->data_block; TRACE("get_meta_index: offset %d, meta->offset %d, " "meta->entries %d\n", offset, meta->offset, meta->entries); TRACE("get_meta_index: index_block 0x%llx, offset 0x%x" " data_block 0x%llx\n", cur_index_block, cur_offset, cur_data_block); } for (i = meta->offset + meta->entries; i <= index && i < meta->offset + SQUASHFS_META_ENTRIES; i++) { int blocks = skip * SQUASHFS_META_INDEXES; while (blocks) { int block = blocks > (SIZE >> 2) ? (SIZE >> 2) : blocks; int res = read_block_index(inode->i_sb, block, block_list, &cur_index_block, &cur_offset); if (res == -1) goto failed; cur_data_block += res; blocks -= block; } meta_entry = &meta->meta_entry[i - meta->offset]; meta_entry->index_block = cur_index_block - sblk->inode_table_start; meta_entry->offset = cur_offset; meta_entry->data_block = cur_data_block; meta->entries ++; offset ++; } TRACE("get_meta_index: meta->offset %d, meta->entries %d\n", meta->offset, meta->entries); release_meta_index(inode, meta); } all_done: *index_block = cur_index_block; *index_offset = cur_offset; *data_block = cur_data_block; return offset * SQUASHFS_META_INDEXES * skip; failed: release_meta_index(inode, meta); return -1; } static long long read_blocklist(struct inode *inode, int index, int readahead_blks, char *block_list, unsigned short **block_p, unsigned int *bsize) { long long block_ptr; int offset; long long block; int res = get_meta_index(inode, index, &block_ptr, &offset, &block, block_list); TRACE("read_blocklist: res %d, index %d, block_ptr 0x%llx, offset" " 0x%x, block 0x%llx\n", res, index, block_ptr, offset, block); if(res == -1) goto failure; index -= res; while ( index ) { int blocks = index > (SIZE >> 2) ? (SIZE >> 2) : index; int res = read_block_index(inode->i_sb, blocks, block_list, &block_ptr, &offset); if (res == -1) goto failure; block += res; index -= blocks; } if (read_block_index(inode->i_sb, 1, block_list, &block_ptr, &offset) == -1) goto failure; *bsize = *((unsigned int *) block_list); return block; failure: return 0; } static int squashfs_readpage(struct file *file, struct page *page) { struct inode *inode = page->mapping->host; struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; unsigned char *block_list; long long block; unsigned int bsize, i = 0, bytes = 0, byte_offset = 0; int index = page->index >> (sblk->block_log - PAGE_CACHE_SHIFT); void *pageaddr; struct squashfs_fragment_cache *fragment = NULL; char *data_ptr = msblk->read_page; int mask = (1 << (sblk->block_log - PAGE_CACHE_SHIFT)) - 1; int start_index = page->index & ~mask; int end_index = start_index | mask; TRACE("Entered squashfs_readpage, page index %lx, start block %llx\n", page->index, SQUASHFS_I(inode)->start_block); if (!(block_list = kmalloc(SIZE, GFP_KERNEL))) { ERROR("Failed to allocate block_list\n"); goto skip_read; } if (page->index >= ((i_size_read(inode) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT)) goto skip_read; if (SQUASHFS_I(inode)->u.s1.fragment_start_block == SQUASHFS_INVALID_BLK || index < (i_size_read(inode) >> sblk->block_log)) { if ((block = (msblk->read_blocklist)(inode, index, 1, block_list, NULL, &bsize)) == 0) goto skip_read; down(&msblk->read_page_mutex); if (!(bytes = squashfs_read_data(inode->i_sb, msblk->read_page, block, bsize, NULL, sblk->block_size))) { ERROR("Unable to read page, block %llx, size %x\n", block, bsize); up(&msblk->read_page_mutex); goto skip_read; } } else { if ((fragment = get_cached_fragment(inode->i_sb, SQUASHFS_I(inode)-> u.s1.fragment_start_block, SQUASHFS_I(inode)->u.s1.fragment_size)) == NULL) { ERROR("Unable to read page, block %llx, size %x\n", SQUASHFS_I(inode)-> u.s1.fragment_start_block, (int) SQUASHFS_I(inode)-> u.s1.fragment_size); goto skip_read; } bytes = SQUASHFS_I(inode)->u.s1.fragment_offset + (i_size_read(inode) & (sblk->block_size - 1)); byte_offset = SQUASHFS_I(inode)->u.s1.fragment_offset; data_ptr = fragment->data; } for (i = start_index; i <= end_index && byte_offset < bytes; i++, byte_offset += PAGE_CACHE_SIZE) { struct page *push_page; int avail = (bytes - byte_offset) > PAGE_CACHE_SIZE ? PAGE_CACHE_SIZE : bytes - byte_offset; TRACE("bytes %d, i %d, byte_offset %d, available_bytes %d\n", bytes, i, byte_offset, avail); push_page = (i == page->index) ? page : grab_cache_page_nowait(page->mapping, i); if (!push_page) continue; if (PageUptodate(push_page)) goto skip_page; pageaddr = kmap_atomic(push_page, KM_USER0); memcpy(pageaddr, data_ptr + byte_offset, avail); memset(pageaddr + avail, 0, PAGE_CACHE_SIZE - avail); kunmap_atomic(pageaddr, KM_USER0); flush_dcache_page(push_page); SetPageUptodate(push_page); skip_page: unlock_page(push_page); if(i != page->index) page_cache_release(push_page); } if (SQUASHFS_I(inode)->u.s1.fragment_start_block == SQUASHFS_INVALID_BLK || index < (i_size_read(inode) >> sblk->block_log)) up(&msblk->read_page_mutex); else release_cached_fragment(msblk, fragment); kfree(block_list); return 0; skip_read: pageaddr = kmap_atomic(page, KM_USER0); memset(pageaddr + bytes, 0, PAGE_CACHE_SIZE - bytes); kunmap_atomic(pageaddr, KM_USER0); flush_dcache_page(page); SetPageUptodate(page); unlock_page(page); kfree(block_list); return 0; } static int squashfs_readpage4K(struct file *file, struct page *page) { struct inode *inode = page->mapping->host; struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; unsigned char *block_list; long long block; unsigned int bsize, bytes = 0; void *pageaddr; TRACE("Entered squashfs_readpage4K, page index %lx, start block %llx\n", page->index, SQUASHFS_I(inode)->start_block); if (page->index >= ((i_size_read(inode) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT)) { block_list = NULL; goto skip_read; } if (!(block_list = kmalloc(SIZE, GFP_KERNEL))) { ERROR("Failed to allocate block_list\n"); goto skip_read; } if (SQUASHFS_I(inode)->u.s1.fragment_start_block == SQUASHFS_INVALID_BLK || page->index < (i_size_read(inode) >> sblk->block_log)) { block = (msblk->read_blocklist)(inode, page->index, 1, block_list, NULL, &bsize); if(block == 0) goto skip_read; down(&msblk->read_page_mutex); bytes = squashfs_read_data(inode->i_sb, msblk->read_page, block, bsize, NULL, sblk->block_size); if (bytes) { pageaddr = kmap_atomic(page, KM_USER0); memcpy(pageaddr, msblk->read_page, bytes); kunmap_atomic(pageaddr, KM_USER0); } else ERROR("Unable to read page, block %llx, size %x\n", block, bsize); up(&msblk->read_page_mutex); } else { struct squashfs_fragment_cache *fragment = get_cached_fragment(inode->i_sb, SQUASHFS_I(inode)-> u.s1.fragment_start_block, SQUASHFS_I(inode)-> u.s1.fragment_size); if (fragment) { bytes = i_size_read(inode) & (sblk->block_size - 1); pageaddr = kmap_atomic(page, KM_USER0); memcpy(pageaddr, fragment->data + SQUASHFS_I(inode)-> u.s1.fragment_offset, bytes); kunmap_atomic(pageaddr, KM_USER0); release_cached_fragment(msblk, fragment); } else ERROR("Unable to read page, block %llx, size %x\n", SQUASHFS_I(inode)-> u.s1.fragment_start_block, (int) SQUASHFS_I(inode)-> u.s1.fragment_size); } skip_read: pageaddr = kmap_atomic(page, KM_USER0); memset(pageaddr + bytes, 0, PAGE_CACHE_SIZE - bytes); kunmap_atomic(pageaddr, KM_USER0); flush_dcache_page(page); SetPageUptodate(page); unlock_page(page); kfree(block_list); return 0; } static int get_dir_index_using_offset(struct super_block *s, long long *next_block, unsigned int *next_offset, long long index_start, unsigned int index_offset, int i_count, long long f_pos) { struct squashfs_sb_info *msblk = s->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; int i, length = 0; struct squashfs_dir_index index; TRACE("Entered get_dir_index_using_offset, i_count %d, f_pos %d\n", i_count, (unsigned int) f_pos); f_pos =- 3; if (f_pos == 0) goto finish; for (i = 0; i < i_count; i++) { if (msblk->swap) { struct squashfs_dir_index sindex; squashfs_get_cached_block(s, (char *) &sindex, index_start, index_offset, sizeof(sindex), &index_start, &index_offset); SQUASHFS_SWAP_DIR_INDEX(&index, &sindex); } else squashfs_get_cached_block(s, (char *) &index, index_start, index_offset, sizeof(index), &index_start, &index_offset); if (index.index > f_pos) break; squashfs_get_cached_block(s, NULL, index_start, index_offset, index.size + 1, &index_start, &index_offset); length = index.index; *next_block = index.start_block + sblk->directory_table_start; } *next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE; finish: return length + 3; } static int get_dir_index_using_name(struct super_block *s, long long *next_block, unsigned int *next_offset, long long index_start, unsigned int index_offset, int i_count, const char *name, int size) { struct squashfs_sb_info *msblk = s->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; int i, length = 0; struct squashfs_dir_index *index; char *str; TRACE("Entered get_dir_index_using_name, i_count %d\n", i_count); if (!(str = kmalloc(sizeof(struct squashfs_dir_index) + (SQUASHFS_NAME_LEN + 1) * 2, GFP_KERNEL))) { ERROR("Failed to allocate squashfs_dir_index\n"); goto failure; } index = (struct squashfs_dir_index *) (str + SQUASHFS_NAME_LEN + 1); strncpy(str, name, size); str[size] = '\0'; for (i = 0; i < i_count; i++) { if (msblk->swap) { struct squashfs_dir_index sindex; squashfs_get_cached_block(s, (char *) &sindex, index_start, index_offset, sizeof(sindex), &index_start, &index_offset); SQUASHFS_SWAP_DIR_INDEX(index, &sindex); } else squashfs_get_cached_block(s, (char *) index, index_start, index_offset, sizeof(struct squashfs_dir_index), &index_start, &index_offset); squashfs_get_cached_block(s, index->name, index_start, index_offset, index->size + 1, &index_start, &index_offset); index->name[index->size + 1] = '\0'; if (strcmp(index->name, str) > 0) break; length = index->index; *next_block = index->start_block + sblk->directory_table_start; } *next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE; kfree(str); failure: return length + 3; } static int squashfs_readdir(struct file *file, void *dirent, filldir_t filldir) { struct inode *i = file->f_dentry->d_inode; struct squashfs_sb_info *msblk = i->i_sb->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; long long next_block = SQUASHFS_I(i)->start_block + sblk->directory_table_start; int next_offset = SQUASHFS_I(i)->offset, length = 0, dir_count; struct squashfs_dir_header dirh; struct squashfs_dir_entry *dire; TRACE("Entered squashfs_readdir [%llx:%x]\n", next_block, next_offset); if (!(dire = kmalloc(sizeof(struct squashfs_dir_entry) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL))) { ERROR("Failed to allocate squashfs_dir_entry\n"); goto finish; } while(file->f_pos < 3) { char *name; int size, i_ino; if(file->f_pos == 0) { name = "."; size = 1; i_ino = i->i_ino; } else { name = ".."; size = 2; i_ino = SQUASHFS_I(i)->u.s2.parent_inode; } TRACE("Calling filldir(%x, %s, %d, %d, %d, %d)\n", (unsigned int) dirent, name, size, (int) file->f_pos, i_ino, squashfs_filetype_table[1]); if (filldir(dirent, name, size, file->f_pos, i_ino, squashfs_filetype_table[1]) < 0) { TRACE("Filldir returned less than 0\n"); goto finish; } file->f_pos += size; } length = get_dir_index_using_offset(i->i_sb, &next_block, &next_offset, SQUASHFS_I(i)->u.s2.directory_index_start, SQUASHFS_I(i)->u.s2.directory_index_offset, SQUASHFS_I(i)->u.s2.directory_index_count, file->f_pos); while (length < i_size_read(i)) { /* read directory header */ if (msblk->swap) { struct squashfs_dir_header sdirh; if (!squashfs_get_cached_block(i->i_sb, (char *) &sdirh, next_block, next_offset, sizeof(sdirh), &next_block, &next_offset)) goto failed_read; length += sizeof(sdirh); SQUASHFS_SWAP_DIR_HEADER(&dirh, &sdirh); } else { if (!squashfs_get_cached_block(i->i_sb, (char *) &dirh, next_block, next_offset, sizeof(dirh), &next_block, &next_offset)) goto failed_read; length += sizeof(dirh); } dir_count = dirh.count + 1; while (dir_count--) { if (msblk->swap) { struct squashfs_dir_entry sdire; if (!squashfs_get_cached_block(i->i_sb, (char *) &sdire, next_block, next_offset, sizeof(sdire), &next_block, &next_offset)) goto failed_read; length += sizeof(sdire); SQUASHFS_SWAP_DIR_ENTRY(dire, &sdire); } else { if (!squashfs_get_cached_block(i->i_sb, (char *) dire, next_block, next_offset, sizeof(*dire), &next_block, &next_offset)) goto failed_read; length += sizeof(*dire); } if (!squashfs_get_cached_block(i->i_sb, dire->name, next_block, next_offset, dire->size + 1, &next_block, &next_offset)) goto failed_read; length += dire->size + 1; if (file->f_pos >= length) continue; dire->name[dire->size + 1] = '\0'; TRACE("Calling filldir(%x, %s, %d, %d, %x:%x, %d, %d)\n", (unsigned int) dirent, dire->name, dire->size + 1, (int) file->f_pos, dirh.start_block, dire->offset, dirh.inode_number + dire->inode_number, squashfs_filetype_table[dire->type]); if (filldir(dirent, dire->name, dire->size + 1, file->f_pos, dirh.inode_number + dire->inode_number, squashfs_filetype_table[dire->type]) < 0) { TRACE("Filldir returned less than 0\n"); goto finish; } file->f_pos = length; } } finish: kfree(dire); return 0; failed_read: ERROR("Unable to read directory block [%llx:%x]\n", next_block, next_offset); kfree(dire); return 0; } static struct dentry *squashfs_lookup(struct inode *i, struct dentry *dentry, struct nameidata *nd) { const unsigned char *name = dentry->d_name.name; int len = dentry->d_name.len; struct inode *inode = NULL; struct squashfs_sb_info *msblk = i->i_sb->s_fs_info; struct squashfs_super_block *sblk = &msblk->sblk; long long next_block = SQUASHFS_I(i)->start_block + sblk->directory_table_start; int next_offset = SQUASHFS_I(i)->offset, length = 0, dir_count; struct squashfs_dir_header dirh; struct squashfs_dir_entry *dire; TRACE("Entered squashfs_lookup [%llx:%x]\n", next_block, next_offset); if (!(dire = kmalloc(sizeof(struct squashfs_dir_entry) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL))) { ERROR("Failed to allocate squashfs_dir_entry\n"); goto exit_lookup; } if (len > SQUASHFS_NAME_LEN) goto exit_lookup; length = get_dir_index_using_name(i->i_sb, &next_block, &next_offset, SQUASHFS_I(i)->u.s2.directory_index_start, SQUASHFS_I(i)->u.s2.directory_index_offset, SQUASHFS_I(i)->u.s2.directory_index_count, name, len); while (length < i_size_read(i)) { /* read directory header */ if (msblk->swap) { struct squashfs_dir_header sdirh; if (!squashfs_get_cached_block(i->i_sb, (char *) &sdirh, next_block, next_offset, sizeof(sdirh), &next_block, &next_offset)) goto failed_read; length += sizeof(sdirh); SQUASHFS_SWAP_DIR_HEADER(&dirh, &sdirh); } else { if (!squashfs_get_cached_block(i->i_sb, (char *) &dirh, next_block, next_offset, sizeof(dirh), &next_block, &next_offset)) goto failed_read; length += sizeof(dirh); } dir_count = dirh.count + 1; while (dir_count--) { if (msblk->swap) { struct squashfs_dir_entry sdire; if (!squashfs_get_cached_block(i->i_sb, (char *) &sdire, next_block,next_offset, sizeof(sdire), &next_block, &next_offset)) goto failed_read; length += sizeof(sdire); SQUASHFS_SWAP_DIR_ENTRY(dire, &sdire); } else { if (!squashfs_get_cached_block(i->i_sb, (char *) dire, next_block,next_offset, sizeof(*dire), &next_block, &next_offset)) goto failed_read; length += sizeof(*dire); } if (!squashfs_get_cached_block(i->i_sb, dire->name, next_block, next_offset, dire->size + 1, &next_block, &next_offset)) goto failed_read; length += dire->size + 1; if (name[0] < dire->name[0]) goto exit_lookup; if ((len == dire->size + 1) && !strncmp(name, dire->name, len)) { squashfs_inode_t ino = SQUASHFS_MKINODE(dirh.start_block, dire->offset); TRACE("calling squashfs_iget for directory " "entry %s, inode %x:%x, %d\n", name, dirh.start_block, dire->offset, dirh.inode_number + dire->inode_number); inode = squashfs_iget(i->i_sb, ino, dirh.inode_number + dire->inode_number); goto exit_lookup; } } } exit_lookup: kfree(dire); if (inode) return d_splice_alias(inode, dentry); d_add(dentry, inode); return ERR_PTR(0); failed_read: ERROR("Unable to read directory block [%llx:%x]\n", next_block, next_offset); goto exit_lookup; } static void squashfs_put_super(struct super_block *s) { int i; if (s->s_fs_info) { struct squashfs_sb_info *sbi = s->s_fs_info; if (sbi->block_cache) for (i = 0; i < SQUASHFS_CACHED_BLKS; i++) if (sbi->block_cache[i].block != SQUASHFS_INVALID_BLK) kfree(sbi->block_cache[i].data); if (sbi->fragment) for (i = 0; i < SQUASHFS_CACHED_FRAGMENTS; i++) SQUASHFS_FREE(sbi->fragment[i].data); kfree(sbi->fragment); kfree(sbi->block_cache); kfree(sbi->read_page); kfree(sbi->uid); kfree(sbi->fragment_index); kfree(sbi->fragment_index_2); kfree(sbi->meta_index); vfree(sbi->stream.workspace); kfree(s->s_fs_info); s->s_fs_info = NULL; } } static int squashfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt) { return get_sb_bdev(fs_type, flags, dev_name, data, squashfs_fill_super, mnt); } static int __init init_squashfs_fs(void) { int err = init_inodecache(); if (err) goto out; printk(KERN_INFO "squashfs: version 3.2 (2007/01/02) " "Phillip Lougher\n"); if ((err = register_filesystem(&squashfs_fs_type))) destroy_inodecache(); out: return err; } static void __exit exit_squashfs_fs(void) { unregister_filesystem(&squashfs_fs_type); destroy_inodecache(); } static kmem_cache_t * squashfs_inode_cachep; static struct inode *squashfs_alloc_inode(struct super_block *sb) { struct squashfs_inode_info *ei; ei = kmem_cache_alloc(squashfs_inode_cachep, SLAB_KERNEL); if (!ei) return NULL; return &ei->vfs_inode; } static void squashfs_destroy_inode(struct inode *inode) { kmem_cache_free(squashfs_inode_cachep, SQUASHFS_I(inode)); } static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags) { struct squashfs_inode_info *ei = foo; if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == SLAB_CTOR_CONSTRUCTOR) inode_init_once(&ei->vfs_inode); } static int __init init_inodecache(void) { squashfs_inode_cachep = kmem_cache_create("squashfs_inode_cache", sizeof(struct squashfs_inode_info), 0, SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT, init_once, NULL); if (squashfs_inode_cachep == NULL) return -ENOMEM; return 0; } static void destroy_inodecache(void) { kmem_cache_destroy(squashfs_inode_cachep); } module_init(init_squashfs_fs); module_exit(exit_squashfs_fs); MODULE_DESCRIPTION("squashfs 3.2, a compressed read-only filesystem"); MODULE_AUTHOR("Phillip Lougher "); MODULE_LICENSE("GPL");