2005-04-17 02:20:36 +04:00
/*
* linux / fs / ext2 / namei . c
*
* Rewrite to pagecache . Almost all code had been changed , so blame me
* if the things go wrong . Please , send bug reports to
* viro @ parcelfarce . linux . theplanet . co . uk
*
* Stuff here is basically a glue between the VFS and generic UNIXish
* filesystem that keeps everything in pagecache . All knowledge of the
* directory layout is in fs / ext2 / dir . c - it turned out to be easily separatable
* and it ' s easier to debug that way . In principle we might want to
* generalize that a bit and turn it into a library . Or not .
*
* The only non - static object here is ext2_dir_inode_operations .
*
* TODO : get rid of kmap ( ) use , add readahead .
*
* Copyright ( C ) 1992 , 1993 , 1994 , 1995
* Remy Card ( card @ masi . ibp . fr )
* Laboratoire MASI - Institut Blaise Pascal
* Universite Pierre et Marie Curie ( Paris VI )
*
* from
*
* linux / fs / minix / namei . c
*
* Copyright ( C ) 1991 , 1992 Linus Torvalds
*
* Big - endian to little - endian byte - swapping / bitmaps by
* David S . Miller ( davem @ caip . rutgers . edu ) , 1995
*/
# include <linux/pagemap.h>
# include "ext2.h"
# include "xattr.h"
# include "acl.h"
2005-06-24 09:05:26 +04:00
# include "xip.h"
2005-04-17 02:20:36 +04:00
/*
* Couple of helper functions - make the code slightly cleaner .
*/
static inline void ext2_inc_count ( struct inode * inode )
{
inode - > i_nlink + + ;
mark_inode_dirty ( inode ) ;
}
static inline void ext2_dec_count ( struct inode * inode )
{
inode - > i_nlink - - ;
mark_inode_dirty ( inode ) ;
}
static inline int ext2_add_nondir ( struct dentry * dentry , struct inode * inode )
{
int err = ext2_add_link ( dentry , inode ) ;
if ( ! err ) {
d_instantiate ( dentry , inode ) ;
return 0 ;
}
ext2_dec_count ( inode ) ;
iput ( inode ) ;
return err ;
}
/*
* Methods themselves .
*/
static struct dentry * ext2_lookup ( struct inode * dir , struct dentry * dentry , struct nameidata * nd )
{
struct inode * inode ;
ino_t ino ;
if ( dentry - > d_name . len > EXT2_NAME_LEN )
return ERR_PTR ( - ENAMETOOLONG ) ;
ino = ext2_inode_by_name ( dir , dentry ) ;
inode = NULL ;
if ( ino ) {
inode = iget ( dir - > i_sb , ino ) ;
if ( ! inode )
return ERR_PTR ( - EACCES ) ;
}
if ( inode )
return d_splice_alias ( inode , dentry ) ;
d_add ( dentry , inode ) ;
return NULL ;
}
struct dentry * ext2_get_parent ( struct dentry * child )
{
unsigned long ino ;
struct dentry * parent ;
struct inode * inode ;
struct dentry dotdot ;
dotdot . d_name . name = " .. " ;
dotdot . d_name . len = 2 ;
ino = ext2_inode_by_name ( child - > d_inode , & dotdot ) ;
if ( ! ino )
return ERR_PTR ( - ENOENT ) ;
inode = iget ( child - > d_inode - > i_sb , ino ) ;
if ( ! inode )
return ERR_PTR ( - EACCES ) ;
parent = d_alloc_anon ( inode ) ;
if ( ! parent ) {
iput ( inode ) ;
parent = ERR_PTR ( - ENOMEM ) ;
}
return parent ;
}
/*
* By the time this is called , we already have created
* the directory cache entry for the new file , but it
* is so far negative - it has no inode .
*
* If the create succeeds , we fill in the inode information
* with d_instantiate ( ) .
*/
static int ext2_create ( struct inode * dir , struct dentry * dentry , int mode , struct nameidata * nd )
{
struct inode * inode = ext2_new_inode ( dir , mode ) ;
int err = PTR_ERR ( inode ) ;
if ( ! IS_ERR ( inode ) ) {
inode - > i_op = & ext2_file_inode_operations ;
2005-06-24 09:05:26 +04:00
if ( ext2_use_xip ( inode - > i_sb ) ) {
inode - > i_mapping - > a_ops = & ext2_aops_xip ;
inode - > i_fop = & ext2_xip_file_operations ;
} else if ( test_opt ( inode - > i_sb , NOBH ) ) {
2005-04-17 02:20:36 +04:00
inode - > i_mapping - > a_ops = & ext2_nobh_aops ;
2005-06-24 09:05:26 +04:00
inode - > i_fop = & ext2_file_operations ;
} else {
2005-04-17 02:20:36 +04:00
inode - > i_mapping - > a_ops = & ext2_aops ;
2005-06-24 09:05:26 +04:00
inode - > i_fop = & ext2_file_operations ;
}
2005-04-17 02:20:36 +04:00
mark_inode_dirty ( inode ) ;
err = ext2_add_nondir ( dentry , inode ) ;
}
return err ;
}
static int ext2_mknod ( struct inode * dir , struct dentry * dentry , int mode , dev_t rdev )
{
struct inode * inode ;
int err ;
if ( ! new_valid_dev ( rdev ) )
return - EINVAL ;
inode = ext2_new_inode ( dir , mode ) ;
err = PTR_ERR ( inode ) ;
if ( ! IS_ERR ( inode ) ) {
init_special_inode ( inode , inode - > i_mode , rdev ) ;
# ifdef CONFIG_EXT2_FS_XATTR
inode - > i_op = & ext2_special_inode_operations ;
# endif
mark_inode_dirty ( inode ) ;
err = ext2_add_nondir ( dentry , inode ) ;
}
return err ;
}
static int ext2_symlink ( struct inode * dir , struct dentry * dentry ,
const char * symname )
{
struct super_block * sb = dir - > i_sb ;
int err = - ENAMETOOLONG ;
unsigned l = strlen ( symname ) + 1 ;
struct inode * inode ;
if ( l > sb - > s_blocksize )
goto out ;
inode = ext2_new_inode ( dir , S_IFLNK | S_IRWXUGO ) ;
err = PTR_ERR ( inode ) ;
if ( IS_ERR ( inode ) )
goto out ;
if ( l > sizeof ( EXT2_I ( inode ) - > i_data ) ) {
/* slow symlink */
inode - > i_op = & ext2_symlink_inode_operations ;
if ( test_opt ( inode - > i_sb , NOBH ) )
inode - > i_mapping - > a_ops = & ext2_nobh_aops ;
else
inode - > i_mapping - > a_ops = & ext2_aops ;
err = page_symlink ( inode , symname , l ) ;
if ( err )
goto out_fail ;
} else {
/* fast symlink */
inode - > i_op = & ext2_fast_symlink_inode_operations ;
memcpy ( ( char * ) ( EXT2_I ( inode ) - > i_data ) , symname , l ) ;
inode - > i_size = l - 1 ;
}
mark_inode_dirty ( inode ) ;
err = ext2_add_nondir ( dentry , inode ) ;
out :
return err ;
out_fail :
ext2_dec_count ( inode ) ;
iput ( inode ) ;
goto out ;
}
static int ext2_link ( struct dentry * old_dentry , struct inode * dir ,
struct dentry * dentry )
{
struct inode * inode = old_dentry - > d_inode ;
if ( inode - > i_nlink > = EXT2_LINK_MAX )
return - EMLINK ;
inode - > i_ctime = CURRENT_TIME_SEC ;
ext2_inc_count ( inode ) ;
atomic_inc ( & inode - > i_count ) ;
return ext2_add_nondir ( dentry , inode ) ;
}
static int ext2_mkdir ( struct inode * dir , struct dentry * dentry , int mode )
{
struct inode * inode ;
int err = - EMLINK ;
if ( dir - > i_nlink > = EXT2_LINK_MAX )
goto out ;
ext2_inc_count ( dir ) ;
inode = ext2_new_inode ( dir , S_IFDIR | mode ) ;
err = PTR_ERR ( inode ) ;
if ( IS_ERR ( inode ) )
goto out_dir ;
inode - > i_op = & ext2_dir_inode_operations ;
inode - > i_fop = & ext2_dir_operations ;
if ( test_opt ( inode - > i_sb , NOBH ) )
inode - > i_mapping - > a_ops = & ext2_nobh_aops ;
else
inode - > i_mapping - > a_ops = & ext2_aops ;
ext2_inc_count ( inode ) ;
err = ext2_make_empty ( inode , dir ) ;
if ( err )
goto out_fail ;
err = ext2_add_link ( dentry , inode ) ;
if ( err )
goto out_fail ;
d_instantiate ( dentry , inode ) ;
out :
return err ;
out_fail :
ext2_dec_count ( inode ) ;
ext2_dec_count ( inode ) ;
iput ( inode ) ;
out_dir :
ext2_dec_count ( dir ) ;
goto out ;
}
static int ext2_unlink ( struct inode * dir , struct dentry * dentry )
{
struct inode * inode = dentry - > d_inode ;
struct ext2_dir_entry_2 * de ;
struct page * page ;
int err = - ENOENT ;
de = ext2_find_entry ( dir , dentry , & page ) ;
if ( ! de )
goto out ;
err = ext2_delete_entry ( de , page ) ;
if ( err )
goto out ;
inode - > i_ctime = dir - > i_ctime ;
ext2_dec_count ( inode ) ;
err = 0 ;
out :
return err ;
}
static int ext2_rmdir ( struct inode * dir , struct dentry * dentry )
{
struct inode * inode = dentry - > d_inode ;
int err = - ENOTEMPTY ;
if ( ext2_empty_dir ( inode ) ) {
err = ext2_unlink ( dir , dentry ) ;
if ( ! err ) {
inode - > i_size = 0 ;
ext2_dec_count ( inode ) ;
ext2_dec_count ( dir ) ;
}
}
return err ;
}
static int ext2_rename ( struct inode * old_dir , struct dentry * old_dentry ,
struct inode * new_dir , struct dentry * new_dentry )
{
struct inode * old_inode = old_dentry - > d_inode ;
struct inode * new_inode = new_dentry - > d_inode ;
struct page * dir_page = NULL ;
struct ext2_dir_entry_2 * dir_de = NULL ;
struct page * old_page ;
struct ext2_dir_entry_2 * old_de ;
int err = - ENOENT ;
old_de = ext2_find_entry ( old_dir , old_dentry , & old_page ) ;
if ( ! old_de )
goto out ;
if ( S_ISDIR ( old_inode - > i_mode ) ) {
err = - EIO ;
dir_de = ext2_dotdot ( old_inode , & dir_page ) ;
if ( ! dir_de )
goto out_old ;
}
if ( new_inode ) {
struct page * new_page ;
struct ext2_dir_entry_2 * new_de ;
err = - ENOTEMPTY ;
if ( dir_de & & ! ext2_empty_dir ( new_inode ) )
goto out_dir ;
err = - ENOENT ;
new_de = ext2_find_entry ( new_dir , new_dentry , & new_page ) ;
if ( ! new_de )
goto out_dir ;
ext2_inc_count ( old_inode ) ;
ext2_set_link ( new_dir , new_de , new_page , old_inode ) ;
new_inode - > i_ctime = CURRENT_TIME_SEC ;
if ( dir_de )
new_inode - > i_nlink - - ;
ext2_dec_count ( new_inode ) ;
} else {
if ( dir_de ) {
err = - EMLINK ;
if ( new_dir - > i_nlink > = EXT2_LINK_MAX )
goto out_dir ;
}
ext2_inc_count ( old_inode ) ;
err = ext2_add_link ( new_dentry , old_inode ) ;
if ( err ) {
ext2_dec_count ( old_inode ) ;
goto out_dir ;
}
if ( dir_de )
ext2_inc_count ( new_dir ) ;
}
/*
* Like most other Unix systems , set the ctime for inodes on a
* rename .
* ext2_dec_count ( ) will mark the inode dirty .
*/
old_inode - > i_ctime = CURRENT_TIME_SEC ;
ext2_delete_entry ( old_de , old_page ) ;
ext2_dec_count ( old_inode ) ;
if ( dir_de ) {
ext2_set_link ( old_inode , dir_de , dir_page , new_dir ) ;
ext2_dec_count ( old_dir ) ;
}
return 0 ;
out_dir :
if ( dir_de ) {
kunmap ( dir_page ) ;
page_cache_release ( dir_page ) ;
}
out_old :
kunmap ( old_page ) ;
page_cache_release ( old_page ) ;
out :
return err ;
}
struct inode_operations ext2_dir_inode_operations = {
. create = ext2_create ,
. lookup = ext2_lookup ,
. link = ext2_link ,
. unlink = ext2_unlink ,
. symlink = ext2_symlink ,
. mkdir = ext2_mkdir ,
. rmdir = ext2_rmdir ,
. mknod = ext2_mknod ,
. rename = ext2_rename ,
# ifdef CONFIG_EXT2_FS_XATTR
. setxattr = generic_setxattr ,
. getxattr = generic_getxattr ,
. listxattr = ext2_listxattr ,
. removexattr = generic_removexattr ,
# endif
. setattr = ext2_setattr ,
. permission = ext2_permission ,
} ;
struct inode_operations ext2_special_inode_operations = {
# ifdef CONFIG_EXT2_FS_XATTR
. setxattr = generic_setxattr ,
. getxattr = generic_getxattr ,
. listxattr = ext2_listxattr ,
. removexattr = generic_removexattr ,
# endif
. setattr = ext2_setattr ,
. permission = ext2_permission ,
} ;