2005-12-16 01:31:24 +03:00
/* -*- mode: c; c-basic-offset: 8; -*-
* vim : noexpandtab sw = 8 ts = 8 sts = 0 :
*
* dir . c
*
* Creates , reads , walks and deletes directory - nodes
*
* Copyright ( C ) 2002 , 2004 Oracle . All rights reserved .
*
* Portions of this code from linux / fs / ext3 / dir . c
*
* 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 / dir . c
*
* Copyright ( C ) 1991 , 1992 Linux Torvalds
*
* 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 of the License , 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 , Inc . , 59 Temple Place - Suite 330 ,
* Boston , MA 021110 - 1307 , USA .
*/
# include <linux/fs.h>
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/highmem.h>
# define MLOG_MASK_PREFIX ML_NAMEI
# include <cluster/masklog.h>
# include "ocfs2.h"
# include "alloc.h"
# include "dir.h"
# include "dlmglue.h"
# include "extent_map.h"
# include "file.h"
# include "inode.h"
# include "journal.h"
# include "namei.h"
# include "suballoc.h"
# include "uptodate.h"
# include "buffer_head_io.h"
static unsigned char ocfs2_filetype_table [ ] = {
DT_UNKNOWN , DT_REG , DT_DIR , DT_CHR , DT_BLK , DT_FIFO , DT_SOCK , DT_LNK
} ;
static int ocfs2_extend_dir ( struct ocfs2_super * osb ,
struct inode * dir ,
struct buffer_head * parent_fe_bh ,
struct buffer_head * * new_de_bh ) ;
/*
* ocfs2_readdir ( )
*
*/
int ocfs2_readdir ( struct file * filp , void * dirent , filldir_t filldir )
{
int error = 0 ;
unsigned long offset , blk ;
int i , num , stored ;
struct buffer_head * bh , * tmp ;
struct ocfs2_dir_entry * de ;
int err ;
struct inode * inode = filp - > f_dentry - > d_inode ;
struct super_block * sb = inode - > i_sb ;
int have_disk_lock = 0 ;
mlog_entry ( " dirino=% " MLFu64 " \n " , OCFS2_I ( inode ) - > ip_blkno ) ;
stored = 0 ;
bh = NULL ;
error = ocfs2_meta_lock ( inode , NULL , NULL , 0 ) ;
if ( error < 0 ) {
if ( error ! = - ENOENT )
mlog_errno ( error ) ;
/* we haven't got any yet, so propagate the error. */
stored = error ;
goto bail ;
}
have_disk_lock = 1 ;
offset = filp - > f_pos & ( sb - > s_blocksize - 1 ) ;
while ( ! error & & ! stored & & filp - > f_pos < i_size_read ( inode ) ) {
blk = ( filp - > f_pos ) > > sb - > s_blocksize_bits ;
bh = ocfs2_bread ( inode , blk , & err , 0 ) ;
if ( ! bh ) {
mlog ( ML_ERROR , " directory #% " MLFu64 " contains a hole "
" at offset %lld \n " ,
OCFS2_I ( inode ) - > ip_blkno ,
filp - > f_pos ) ;
filp - > f_pos + = sb - > s_blocksize - offset ;
continue ;
}
/*
* Do the readahead ( 8 k )
*/
if ( ! offset ) {
for ( i = 16 > > ( sb - > s_blocksize_bits - 9 ) , num = 0 ;
i > 0 ; i - - ) {
tmp = ocfs2_bread ( inode , + + blk , & err , 1 ) ;
if ( tmp )
brelse ( tmp ) ;
}
}
revalidate :
/* If the dir block has changed since the last call to
* readdir ( 2 ) , then we might be pointing to an invalid
* dirent right now . Scan from the start of the block
* to make sure . */
if ( filp - > f_version ! = inode - > i_version ) {
for ( i = 0 ; i < sb - > s_blocksize & & i < offset ; ) {
de = ( struct ocfs2_dir_entry * ) ( bh - > b_data + i ) ;
/* It's too expensive to do a full
* dirent test each time round this
* loop , but we do have to test at
* least that it is non - zero . A
* failure will be detected in the
* dirent test below . */
if ( le16_to_cpu ( de - > rec_len ) <
OCFS2_DIR_REC_LEN ( 1 ) )
break ;
i + = le16_to_cpu ( de - > rec_len ) ;
}
offset = i ;
filp - > f_pos = ( filp - > f_pos & ~ ( sb - > s_blocksize - 1 ) )
| offset ;
filp - > f_version = inode - > i_version ;
}
while ( ! error & & filp - > f_pos < i_size_read ( inode )
& & offset < sb - > s_blocksize ) {
de = ( struct ocfs2_dir_entry * ) ( bh - > b_data + offset ) ;
if ( ! ocfs2_check_dir_entry ( inode , de , bh , offset ) ) {
/* On error, skip the f_pos to the
next block . */
filp - > f_pos = ( filp - > f_pos |
( sb - > s_blocksize - 1 ) ) + 1 ;
brelse ( bh ) ;
goto bail ;
}
offset + = le16_to_cpu ( de - > rec_len ) ;
if ( le64_to_cpu ( de - > inode ) ) {
/* We might block in the next section
* if the data destination is
* currently swapped out . So , use a
* version stamp to detect whether or
* not the directory has been modified
* during the copy operation .
*/
unsigned long version = filp - > f_version ;
unsigned char d_type = DT_UNKNOWN ;
if ( de - > file_type < OCFS2_FT_MAX )
d_type = ocfs2_filetype_table [ de - > file_type ] ;
error = filldir ( dirent , de - > name ,
de - > name_len ,
filp - > f_pos ,
ino_from_blkno ( sb , le64_to_cpu ( de - > inode ) ) ,
d_type ) ;
if ( error )
break ;
if ( version ! = filp - > f_version )
goto revalidate ;
stored + + ;
}
filp - > f_pos + = le16_to_cpu ( de - > rec_len ) ;
}
offset = 0 ;
brelse ( bh ) ;
}
stored = 0 ;
bail :
if ( have_disk_lock )
ocfs2_meta_unlock ( inode , 0 ) ;
mlog_exit ( stored ) ;
return stored ;
}
/*
2006-01-10 02:59:24 +03:00
* NOTE : this should always be called with parent dir i_mutex taken .
2005-12-16 01:31:24 +03:00
*/
int ocfs2_find_files_on_disk ( const char * name ,
int namelen ,
u64 * blkno ,
struct inode * inode ,
struct buffer_head * * dirent_bh ,
struct ocfs2_dir_entry * * dirent )
{
int status = - ENOENT ;
struct ocfs2_super * osb = OCFS2_SB ( inode - > i_sb ) ;
mlog_entry ( " (osb=%p, parent=% " MLFu64 " , name='%.*s', blkno=%p, "
" inode=%p) \n " ,
osb , OCFS2_I ( inode ) - > ip_blkno , namelen , name , blkno , inode ) ;
* dirent_bh = ocfs2_find_entry ( name , namelen , inode , dirent ) ;
if ( ! * dirent_bh | | ! * dirent ) {
status = - ENOENT ;
goto leave ;
}
* blkno = le64_to_cpu ( ( * dirent ) - > inode ) ;
status = 0 ;
leave :
if ( status < 0 ) {
* dirent = NULL ;
if ( * dirent_bh ) {
brelse ( * dirent_bh ) ;
* dirent_bh = NULL ;
}
}
mlog_exit ( status ) ;
return status ;
}
/* Check for a name within a directory.
*
* Return 0 if the name does not exist
* Return - EEXIST if the directory contains the name
*
2006-01-10 02:59:24 +03:00
* Callers should have i_mutex + a cluster lock on dir
2005-12-16 01:31:24 +03:00
*/
int ocfs2_check_dir_for_entry ( struct inode * dir ,
const char * name ,
int namelen )
{
int ret ;
struct buffer_head * dirent_bh = NULL ;
struct ocfs2_dir_entry * dirent = NULL ;
mlog_entry ( " dir % " MLFu64 " , name '%.*s' \n " , OCFS2_I ( dir ) - > ip_blkno ,
namelen , name ) ;
ret = - EEXIST ;
dirent_bh = ocfs2_find_entry ( name , namelen , dir , & dirent ) ;
if ( dirent_bh )
goto bail ;
ret = 0 ;
bail :
if ( dirent_bh )
brelse ( dirent_bh ) ;
mlog_exit ( ret ) ;
return ret ;
}
/*
* routine to check that the specified directory is empty ( for rmdir )
*/
int ocfs2_empty_dir ( struct inode * inode )
{
unsigned long offset ;
struct buffer_head * bh ;
struct ocfs2_dir_entry * de , * de1 ;
struct super_block * sb ;
int err ;
sb = inode - > i_sb ;
if ( ( i_size_read ( inode ) <
( OCFS2_DIR_REC_LEN ( 1 ) + OCFS2_DIR_REC_LEN ( 2 ) ) ) | |
! ( bh = ocfs2_bread ( inode , 0 , & err , 0 ) ) ) {
mlog ( ML_ERROR , " bad directory (dir #% " MLFu64 " ) - "
" no data block \n " ,
OCFS2_I ( inode ) - > ip_blkno ) ;
return 1 ;
}
de = ( struct ocfs2_dir_entry * ) bh - > b_data ;
de1 = ( struct ocfs2_dir_entry * )
( ( char * ) de + le16_to_cpu ( de - > rec_len ) ) ;
if ( ( le64_to_cpu ( de - > inode ) ! = OCFS2_I ( inode ) - > ip_blkno ) | |
! le64_to_cpu ( de1 - > inode ) | |
strcmp ( " . " , de - > name ) | |
strcmp ( " .. " , de1 - > name ) ) {
mlog ( ML_ERROR , " bad directory (dir #% " MLFu64 " ) - "
" no `.' or `..' \n " ,
OCFS2_I ( inode ) - > ip_blkno ) ;
brelse ( bh ) ;
return 1 ;
}
offset = le16_to_cpu ( de - > rec_len ) + le16_to_cpu ( de1 - > rec_len ) ;
de = ( struct ocfs2_dir_entry * ) ( ( char * ) de1 + le16_to_cpu ( de1 - > rec_len ) ) ;
while ( offset < i_size_read ( inode ) ) {
if ( ! bh | | ( void * ) de > = ( void * ) ( bh - > b_data + sb - > s_blocksize ) ) {
brelse ( bh ) ;
bh = ocfs2_bread ( inode ,
offset > > sb - > s_blocksize_bits , & err , 0 ) ;
if ( ! bh ) {
mlog ( ML_ERROR , " directory #% " MLFu64 " contains "
" a hole at offset %lu \n " ,
OCFS2_I ( inode ) - > ip_blkno , offset ) ;
offset + = sb - > s_blocksize ;
continue ;
}
de = ( struct ocfs2_dir_entry * ) bh - > b_data ;
}
if ( ! ocfs2_check_dir_entry ( inode , de , bh , offset ) ) {
brelse ( bh ) ;
return 1 ;
}
if ( le64_to_cpu ( de - > inode ) ) {
brelse ( bh ) ;
return 0 ;
}
offset + = le16_to_cpu ( de - > rec_len ) ;
de = ( struct ocfs2_dir_entry * )
( ( char * ) de + le16_to_cpu ( de - > rec_len ) ) ;
}
brelse ( bh ) ;
return 1 ;
}
/* returns a bh of the 1st new block in the allocation. */
int ocfs2_do_extend_dir ( struct super_block * sb ,
struct ocfs2_journal_handle * handle ,
struct inode * dir ,
struct buffer_head * parent_fe_bh ,
struct ocfs2_alloc_context * data_ac ,
struct ocfs2_alloc_context * meta_ac ,
struct buffer_head * * new_bh )
{
int status ;
int extend ;
u64 p_blkno ;
spin_lock ( & OCFS2_I ( dir ) - > ip_lock ) ;
extend = ( i_size_read ( dir ) = = ocfs2_clusters_to_bytes ( sb , OCFS2_I ( dir ) - > ip_clusters ) ) ;
spin_unlock ( & OCFS2_I ( dir ) - > ip_lock ) ;
if ( extend ) {
status = ocfs2_do_extend_allocation ( OCFS2_SB ( sb ) , dir , 1 ,
parent_fe_bh , handle ,
data_ac , meta_ac , NULL ) ;
BUG_ON ( status = = - EAGAIN ) ;
if ( status < 0 ) {
mlog_errno ( status ) ;
goto bail ;
}
}
status = ocfs2_extent_map_get_blocks ( dir , ( dir - > i_blocks > >
( sb - > s_blocksize_bits - 9 ) ) ,
1 , & p_blkno , NULL ) ;
if ( status < 0 ) {
mlog_errno ( status ) ;
goto bail ;
}
* new_bh = sb_getblk ( sb , p_blkno ) ;
if ( ! * new_bh ) {
status = - EIO ;
mlog_errno ( status ) ;
goto bail ;
}
status = 0 ;
bail :
mlog_exit ( status ) ;
return status ;
}
/* assumes you already have a cluster lock on the directory. */
static int ocfs2_extend_dir ( struct ocfs2_super * osb ,
struct inode * dir ,
struct buffer_head * parent_fe_bh ,
struct buffer_head * * new_de_bh )
{
int status = 0 ;
int credits , num_free_extents ;
loff_t dir_i_size ;
struct ocfs2_dinode * fe = ( struct ocfs2_dinode * ) parent_fe_bh - > b_data ;
struct ocfs2_alloc_context * data_ac = NULL ;
struct ocfs2_alloc_context * meta_ac = NULL ;
struct ocfs2_journal_handle * handle = NULL ;
struct buffer_head * new_bh = NULL ;
struct ocfs2_dir_entry * de ;
struct super_block * sb = osb - > sb ;
mlog_entry_void ( ) ;
dir_i_size = i_size_read ( dir ) ;
mlog ( 0 , " extending dir % " MLFu64 " (i_size = %lld) \n " ,
OCFS2_I ( dir ) - > ip_blkno , dir_i_size ) ;
handle = ocfs2_alloc_handle ( osb ) ;
if ( handle = = NULL ) {
status = - ENOMEM ;
mlog_errno ( status ) ;
goto bail ;
}
/* dir->i_size is always block aligned. */
spin_lock ( & OCFS2_I ( dir ) - > ip_lock ) ;
if ( dir_i_size = = ocfs2_clusters_to_bytes ( sb , OCFS2_I ( dir ) - > ip_clusters ) ) {
spin_unlock ( & OCFS2_I ( dir ) - > ip_lock ) ;
num_free_extents = ocfs2_num_free_extents ( osb , dir , fe ) ;
if ( num_free_extents < 0 ) {
status = num_free_extents ;
mlog_errno ( status ) ;
goto bail ;
}
if ( ! num_free_extents ) {
status = ocfs2_reserve_new_metadata ( osb , handle ,
fe , & meta_ac ) ;
if ( status < 0 ) {
if ( status ! = - ENOSPC )
mlog_errno ( status ) ;
goto bail ;
}
}
status = ocfs2_reserve_clusters ( osb , handle , 1 , & data_ac ) ;
if ( status < 0 ) {
if ( status ! = - ENOSPC )
mlog_errno ( status ) ;
goto bail ;
}
credits = ocfs2_calc_extend_credits ( sb , fe , 1 ) ;
} else {
spin_unlock ( & OCFS2_I ( dir ) - > ip_lock ) ;
credits = OCFS2_SIMPLE_DIR_EXTEND_CREDITS ;
}
handle = ocfs2_start_trans ( osb , handle , credits ) ;
if ( IS_ERR ( handle ) ) {
status = PTR_ERR ( handle ) ;
handle = NULL ;
mlog_errno ( status ) ;
goto bail ;
}
status = ocfs2_do_extend_dir ( osb - > sb , handle , dir , parent_fe_bh ,
data_ac , meta_ac , & new_bh ) ;
if ( status < 0 ) {
mlog_errno ( status ) ;
goto bail ;
}
ocfs2_set_new_buffer_uptodate ( dir , new_bh ) ;
status = ocfs2_journal_access ( handle , dir , new_bh ,
OCFS2_JOURNAL_ACCESS_CREATE ) ;
if ( status < 0 ) {
mlog_errno ( status ) ;
goto bail ;
}
memset ( new_bh - > b_data , 0 , sb - > s_blocksize ) ;
de = ( struct ocfs2_dir_entry * ) new_bh - > b_data ;
de - > inode = 0 ;
de - > rec_len = cpu_to_le16 ( sb - > s_blocksize ) ;
status = ocfs2_journal_dirty ( handle , new_bh ) ;
if ( status < 0 ) {
mlog_errno ( status ) ;
goto bail ;
}
dir_i_size + = dir - > i_sb - > s_blocksize ;
i_size_write ( dir , dir_i_size ) ;
dir - > i_blocks = ocfs2_align_bytes_to_sectors ( dir_i_size ) ;
status = ocfs2_mark_inode_dirty ( handle , dir , parent_fe_bh ) ;
if ( status < 0 ) {
mlog_errno ( status ) ;
goto bail ;
}
* new_de_bh = new_bh ;
get_bh ( * new_de_bh ) ;
bail :
if ( handle )
ocfs2_commit_trans ( handle ) ;
if ( data_ac )
ocfs2_free_alloc_context ( data_ac ) ;
if ( meta_ac )
ocfs2_free_alloc_context ( meta_ac ) ;
if ( new_bh )
brelse ( new_bh ) ;
mlog_exit ( status ) ;
return status ;
}
/*
* Search the dir for a good spot , extending it if necessary . The
* block containing an appropriate record is returned in ret_de_bh .
*/
int ocfs2_prepare_dir_for_insert ( struct ocfs2_super * osb ,
struct inode * dir ,
struct buffer_head * parent_fe_bh ,
const char * name ,
int namelen ,
struct buffer_head * * ret_de_bh )
{
unsigned long offset ;
struct buffer_head * bh = NULL ;
unsigned short rec_len ;
struct ocfs2_dinode * fe ;
struct ocfs2_dir_entry * de ;
struct super_block * sb ;
int status ;
mlog_entry_void ( ) ;
mlog ( 0 , " getting ready to insert namelen %d into dir % " MLFu64 " \n " ,
namelen , OCFS2_I ( dir ) - > ip_blkno ) ;
BUG_ON ( ! S_ISDIR ( dir - > i_mode ) ) ;
fe = ( struct ocfs2_dinode * ) parent_fe_bh - > b_data ;
BUG_ON ( le64_to_cpu ( fe - > i_size ) ! = i_size_read ( dir ) ) ;
sb = dir - > i_sb ;
if ( ! namelen ) {
status = - EINVAL ;
mlog_errno ( status ) ;
goto bail ;
}
bh = ocfs2_bread ( dir , 0 , & status , 0 ) ;
if ( ! bh ) {
mlog_errno ( status ) ;
goto bail ;
}
rec_len = OCFS2_DIR_REC_LEN ( namelen ) ;
offset = 0 ;
de = ( struct ocfs2_dir_entry * ) bh - > b_data ;
while ( 1 ) {
if ( ( char * ) de > = sb - > s_blocksize + bh - > b_data ) {
brelse ( bh ) ;
bh = NULL ;
if ( i_size_read ( dir ) < = offset ) {
status = ocfs2_extend_dir ( osb ,
dir ,
parent_fe_bh ,
& bh ) ;
if ( status < 0 ) {
mlog_errno ( status ) ;
goto bail ;
}
BUG_ON ( ! bh ) ;
* ret_de_bh = bh ;
get_bh ( * ret_de_bh ) ;
goto bail ;
}
bh = ocfs2_bread ( dir ,
offset > > sb - > s_blocksize_bits ,
& status ,
0 ) ;
if ( ! bh ) {
mlog_errno ( status ) ;
goto bail ;
}
/* move to next block */
de = ( struct ocfs2_dir_entry * ) bh - > b_data ;
}
if ( ! ocfs2_check_dir_entry ( dir , de , bh , offset ) ) {
status = - ENOENT ;
goto bail ;
}
if ( ocfs2_match ( namelen , name , de ) ) {
status = - EEXIST ;
goto bail ;
}
if ( ( ( le64_to_cpu ( de - > inode ) = = 0 ) & &
( le16_to_cpu ( de - > rec_len ) > = rec_len ) ) | |
( le16_to_cpu ( de - > rec_len ) > =
( OCFS2_DIR_REC_LEN ( de - > name_len ) + rec_len ) ) ) {
/* Ok, we found a spot. Return this bh and let
* the caller actually fill it in . */
* ret_de_bh = bh ;
get_bh ( * ret_de_bh ) ;
status = 0 ;
goto bail ;
}
offset + = le16_to_cpu ( de - > rec_len ) ;
de = ( struct ocfs2_dir_entry * ) ( ( char * ) de + le16_to_cpu ( de - > rec_len ) ) ;
}
status = 0 ;
bail :
if ( bh )
brelse ( bh ) ;
mlog_exit ( status ) ;
return status ;
}