2009-01-05 11:46:23 +03:00
/*
* Squashfs - a compressed read only filesystem for Linux
*
* Copyright ( c ) 2002 , 2003 , 2004 , 2005 , 2006 , 2007 , 2008
2011-05-26 13:39:56 +04:00
* Phillip Lougher < phillip @ squashfs . org . uk >
2009-01-05 11:46:23 +03:00
*
* 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 , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*
* namei . c
*/
/*
* This file implements code to do filename lookup in directories .
*
* Like inodes , directories are packed into compressed metadata blocks , stored
* in a directory table . Directories are accessed using the start address of
* the metablock containing the directory and the offset into the
* decompressed block ( < block , offset > ) .
*
* Directories are organised in a slightly complex way , and are not simply
* a list of file names . The organisation takes advantage of the
* fact that ( in most cases ) the inodes of the files will be in the same
* compressed metadata block , and therefore , can share the start block .
* Directories are therefore organised in a two level list , a directory
* header containing the shared start block value , and a sequence of directory
* entries , each of which share the shared start block . A new directory header
* is written once / if the inode start block changes . The directory
* header / directory entry list is repeated as many times as necessary .
*
* Directories are sorted , and can contain a directory index to speed up
* file lookup . Directory indexes store one entry per metablock , each entry
* storing the index / filename mapping to the first directory header
* in each metadata block . Directories are sorted in alphabetical order ,
* and at lookup the index is scanned linearly looking for the first filename
* alphabetically larger than the filename being looked up . At this point the
* location of the metadata block the filename is in has been found .
* The general idea of the index is ensure only one metadata block needs to be
* decompressed to do a lookup irrespective of the length of the directory .
* This scheme has the advantage that it doesn ' t require extra memory overhead
* and doesn ' t require much extra storage on disk .
*/
# include <linux/fs.h>
# include <linux/vfs.h>
# include <linux/slab.h>
# include <linux/string.h>
# include <linux/dcache.h>
2010-05-17 07:06:56 +04:00
# include <linux/xattr.h>
2009-01-05 11:46:23 +03:00
# include "squashfs_fs.h"
# include "squashfs_fs_sb.h"
# include "squashfs_fs_i.h"
# include "squashfs.h"
2010-05-17 22:39:02 +04:00
# include "xattr.h"
2009-01-05 11:46:23 +03:00
/*
* Lookup name in the directory index , returning the location of the metadata
* block containing it , and the directory index this represents .
*
* If we get an error reading the index then return the part of the index
* ( if any ) we have managed to read - the index isn ' t essential , just
* quicker .
*/
static int get_dir_index_using_name ( struct super_block * sb ,
u64 * next_block , int * next_offset , u64 index_start ,
int index_offset , int i_count , const char * name ,
int len )
{
struct squashfs_sb_info * msblk = sb - > s_fs_info ;
2013-07-17 16:20:25 +04:00
int i , length = 0 , err ;
unsigned int size ;
2009-01-05 11:46:23 +03:00
struct squashfs_dir_index * index ;
char * str ;
TRACE ( " Entered get_dir_index_using_name, i_count %d \n " , i_count ) ;
index = kmalloc ( sizeof ( * index ) + SQUASHFS_NAME_LEN * 2 + 2 , GFP_KERNEL ) ;
if ( index = = NULL ) {
ERROR ( " Failed to allocate squashfs_dir_index \n " ) ;
goto out ;
}
str = & index - > name [ SQUASHFS_NAME_LEN + 1 ] ;
strncpy ( str , name , len ) ;
str [ len ] = ' \0 ' ;
for ( i = 0 ; i < i_count ; i + + ) {
err = squashfs_read_metadata ( sb , index , & index_start ,
& index_offset , sizeof ( * index ) ) ;
if ( err < 0 )
break ;
size = le32_to_cpu ( index - > size ) + 1 ;
2013-09-03 07:02:53 +04:00
if ( size > SQUASHFS_NAME_LEN )
2013-07-17 16:20:25 +04:00
break ;
2009-01-05 11:46:23 +03:00
err = squashfs_read_metadata ( sb , index - > name , & index_start ,
& index_offset , size ) ;
if ( err < 0 )
break ;
index - > name [ size ] = ' \0 ' ;
if ( strcmp ( index - > name , str ) > 0 )
break ;
length = le32_to_cpu ( index - > index ) ;
* next_block = le32_to_cpu ( index - > start_block ) +
msblk - > directory_table ;
}
* next_offset = ( length + * next_offset ) % SQUASHFS_METADATA_SIZE ;
kfree ( index ) ;
out :
/*
* Return index ( f_pos ) of the looked up metadata block . Translate
* from internal f_pos to external f_pos which is offset by 3 because
* we invent " . " and " .. " entries which are not actually stored in the
* directory .
*/
return length + 3 ;
}
static struct dentry * squashfs_lookup ( struct inode * dir , struct dentry * dentry ,
2012-06-11 01:13:09 +04:00
unsigned int flags )
2009-01-05 11:46:23 +03:00
{
const unsigned char * name = dentry - > d_name . name ;
int len = dentry - > d_name . len ;
struct inode * inode = NULL ;
struct squashfs_sb_info * msblk = dir - > i_sb - > s_fs_info ;
struct squashfs_dir_header dirh ;
struct squashfs_dir_entry * dire ;
u64 block = squashfs_i ( dir ) - > start + msblk - > directory_table ;
int offset = squashfs_i ( dir ) - > offset ;
2013-09-03 07:21:52 +04:00
int err , length ;
unsigned int dir_count , size ;
2009-01-05 11:46:23 +03:00
TRACE ( " Entered squashfs_lookup [%llx:%x] \n " , block , offset ) ;
dire = kmalloc ( sizeof ( * dire ) + SQUASHFS_NAME_LEN + 1 , GFP_KERNEL ) ;
if ( dire = = NULL ) {
ERROR ( " Failed to allocate squashfs_dir_entry \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
if ( len > SQUASHFS_NAME_LEN ) {
err = - ENAMETOOLONG ;
goto failed ;
}
length = get_dir_index_using_name ( dir - > i_sb , & block , & offset ,
squashfs_i ( dir ) - > dir_idx_start ,
squashfs_i ( dir ) - > dir_idx_offset ,
squashfs_i ( dir ) - > dir_idx_cnt , name , len ) ;
while ( length < i_size_read ( dir ) ) {
/*
* Read directory header .
*/
err = squashfs_read_metadata ( dir - > i_sb , & dirh , & block ,
& offset , sizeof ( dirh ) ) ;
if ( err < 0 )
goto read_failure ;
length + = sizeof ( dirh ) ;
dir_count = le32_to_cpu ( dirh . count ) + 1 ;
2011-03-16 01:09:55 +03:00
2012-02-02 11:34:49 +04:00
if ( dir_count > SQUASHFS_DIR_COUNT )
2011-03-16 01:09:55 +03:00
goto data_error ;
2009-01-05 11:46:23 +03:00
while ( dir_count - - ) {
/*
* Read directory entry .
*/
err = squashfs_read_metadata ( dir - > i_sb , dire , & block ,
& offset , sizeof ( * dire ) ) ;
if ( err < 0 )
goto read_failure ;
size = le16_to_cpu ( dire - > size ) + 1 ;
2011-03-16 01:09:55 +03:00
/* size should never be larger than SQUASHFS_NAME_LEN */
if ( size > SQUASHFS_NAME_LEN )
goto data_error ;
2009-01-05 11:46:23 +03:00
err = squashfs_read_metadata ( dir - > i_sb , dire - > name ,
& block , & offset , size ) ;
if ( err < 0 )
goto read_failure ;
length + = sizeof ( * dire ) + size ;
if ( name [ 0 ] < dire - > name [ 0 ] )
goto exit_lookup ;
if ( len = = size & & ! strncmp ( name , dire - > name , len ) ) {
unsigned int blk , off , ino_num ;
long long ino ;
blk = le32_to_cpu ( dirh . start_block ) ;
off = le16_to_cpu ( dire - > offset ) ;
ino_num = le32_to_cpu ( dirh . inode_number ) +
( short ) le16_to_cpu ( dire - > inode_number ) ;
ino = SQUASHFS_MKINODE ( blk , off ) ;
TRACE ( " calling squashfs_iget for directory "
" entry %s, inode %x:%x, %d \n " , name ,
blk , off , ino_num ) ;
inode = squashfs_iget ( dir - > i_sb , ino , ino_num ) ;
goto exit_lookup ;
}
}
}
exit_lookup :
kfree ( dire ) ;
2011-07-09 04:57:47 +04:00
return d_splice_alias ( inode , dentry ) ;
2009-01-05 11:46:23 +03:00
2011-03-16 01:09:55 +03:00
data_error :
err = - EIO ;
2009-01-05 11:46:23 +03:00
read_failure :
ERROR ( " Unable to read directory block [%llx:%x] \n " ,
squashfs_i ( dir ) - > start + msblk - > directory_table ,
squashfs_i ( dir ) - > offset ) ;
failed :
kfree ( dire ) ;
return ERR_PTR ( err ) ;
}
const struct inode_operations squashfs_dir_inode_ops = {
2010-05-17 07:06:56 +04:00
. lookup = squashfs_lookup ,
. listxattr = squashfs_listxattr
2009-01-05 11:46:23 +03:00
} ;