2008-07-26 06:45:16 +04:00
/*
* OMFS ( as used by RIO Karma ) directory operations .
* Copyright ( C ) 2005 Bob Copeland < me @ bobcopeland . com >
* Released under GPL v2 .
*/
# include <linux/fs.h>
# include <linux/ctype.h>
# include <linux/buffer_head.h>
# include "omfs.h"
static int omfs_hash ( const char * name , int namelen , int mod )
{
int i , hash = 0 ;
for ( i = 0 ; i < namelen ; i + + )
hash ^ = tolower ( name [ i ] ) < < ( i % 24 ) ;
return hash % mod ;
}
/*
* Finds the bucket for a given name and reads the containing block ;
* * ofs is set to the offset of the first list entry .
*/
static struct buffer_head * omfs_get_bucket ( struct inode * dir ,
const char * name , int namelen , int * ofs )
{
int nbuckets = ( dir - > i_size - OMFS_DIR_START ) / 8 ;
int bucket = omfs_hash ( name , namelen , nbuckets ) ;
* ofs = OMFS_DIR_START + bucket * 8 ;
2008-09-07 01:51:53 +04:00
return omfs_bread ( dir - > i_sb , dir - > i_ino ) ;
2008-07-26 06:45:16 +04:00
}
static struct buffer_head * omfs_scan_list ( struct inode * dir , u64 block ,
const char * name , int namelen ,
u64 * prev_block )
{
struct buffer_head * bh ;
struct omfs_inode * oi ;
int err = - ENOENT ;
* prev_block = ~ 0 ;
while ( block ! = ~ 0 ) {
2008-09-07 01:51:53 +04:00
bh = omfs_bread ( dir - > i_sb , block ) ;
2008-07-26 06:45:16 +04:00
if ( ! bh ) {
err = - EIO ;
goto err ;
}
oi = ( struct omfs_inode * ) bh - > b_data ;
if ( omfs_is_bad ( OMFS_SB ( dir - > i_sb ) , & oi - > i_head , block ) ) {
brelse ( bh ) ;
goto err ;
}
if ( strncmp ( oi - > i_name , name , namelen ) = = 0 )
return bh ;
* prev_block = block ;
block = be64_to_cpu ( oi - > i_sibling ) ;
brelse ( bh ) ;
}
err :
return ERR_PTR ( err ) ;
}
static struct buffer_head * omfs_find_entry ( struct inode * dir ,
const char * name , int namelen )
{
struct buffer_head * bh ;
int ofs ;
u64 block , dummy ;
bh = omfs_get_bucket ( dir , name , namelen , & ofs ) ;
if ( ! bh )
return ERR_PTR ( - EIO ) ;
block = be64_to_cpu ( * ( ( __be64 * ) & bh - > b_data [ ofs ] ) ) ;
brelse ( bh ) ;
return omfs_scan_list ( dir , block , name , namelen , & dummy ) ;
}
int omfs_make_empty ( struct inode * inode , struct super_block * sb )
{
struct omfs_sb_info * sbi = OMFS_SB ( sb ) ;
struct buffer_head * bh ;
struct omfs_inode * oi ;
2008-09-07 01:51:53 +04:00
bh = omfs_bread ( sb , inode - > i_ino ) ;
2008-07-26 06:45:16 +04:00
if ( ! bh )
return - ENOMEM ;
memset ( bh - > b_data , 0 , sizeof ( struct omfs_inode ) ) ;
2011-07-26 10:34:33 +04:00
if ( S_ISDIR ( inode - > i_mode ) ) {
2008-07-26 06:45:16 +04:00
memset ( & bh - > b_data [ OMFS_DIR_START ] , 0xff ,
sbi - > s_sys_blocksize - OMFS_DIR_START ) ;
} else
omfs_make_empty_table ( bh , OMFS_EXTENT_START ) ;
oi = ( struct omfs_inode * ) bh - > b_data ;
oi - > i_head . h_self = cpu_to_be64 ( inode - > i_ino ) ;
2008-07-30 09:33:46 +04:00
oi - > i_sibling = ~ cpu_to_be64 ( 0ULL ) ;
2008-07-26 06:45:16 +04:00
mark_buffer_dirty ( bh ) ;
brelse ( bh ) ;
return 0 ;
}
static int omfs_add_link ( struct dentry * dentry , struct inode * inode )
{
struct inode * dir = dentry - > d_parent - > d_inode ;
const char * name = dentry - > d_name . name ;
int namelen = dentry - > d_name . len ;
struct omfs_inode * oi ;
struct buffer_head * bh ;
u64 block ;
__be64 * entry ;
int ofs ;
/* just prepend to head of queue in proper bucket */
bh = omfs_get_bucket ( dir , name , namelen , & ofs ) ;
if ( ! bh )
goto out ;
entry = ( __be64 * ) & bh - > b_data [ ofs ] ;
block = be64_to_cpu ( * entry ) ;
* entry = cpu_to_be64 ( inode - > i_ino ) ;
mark_buffer_dirty ( bh ) ;
brelse ( bh ) ;
/* now set the sibling and parent pointers on the new inode */
2008-09-07 01:51:53 +04:00
bh = omfs_bread ( dir - > i_sb , inode - > i_ino ) ;
2008-07-26 06:45:16 +04:00
if ( ! bh )
goto out ;
oi = ( struct omfs_inode * ) bh - > b_data ;
memcpy ( oi - > i_name , name , namelen ) ;
memset ( oi - > i_name + namelen , 0 , OMFS_NAMELEN - namelen ) ;
oi - > i_sibling = cpu_to_be64 ( block ) ;
oi - > i_parent = cpu_to_be64 ( dir - > i_ino ) ;
mark_buffer_dirty ( bh ) ;
brelse ( bh ) ;
dir - > i_ctime = CURRENT_TIME_SEC ;
/* mark affected inodes dirty to rebuild checksums */
mark_inode_dirty ( dir ) ;
mark_inode_dirty ( inode ) ;
return 0 ;
out :
return - ENOMEM ;
}
static int omfs_delete_entry ( struct dentry * dentry )
{
struct inode * dir = dentry - > d_parent - > d_inode ;
struct inode * dirty ;
const char * name = dentry - > d_name . name ;
int namelen = dentry - > d_name . len ;
struct omfs_inode * oi ;
struct buffer_head * bh , * bh2 ;
__be64 * entry , next ;
u64 block , prev ;
int ofs ;
int err = - ENOMEM ;
/* delete the proper node in the bucket's linked list */
bh = omfs_get_bucket ( dir , name , namelen , & ofs ) ;
if ( ! bh )
goto out ;
entry = ( __be64 * ) & bh - > b_data [ ofs ] ;
block = be64_to_cpu ( * entry ) ;
bh2 = omfs_scan_list ( dir , block , name , namelen , & prev ) ;
if ( IS_ERR ( bh2 ) ) {
err = PTR_ERR ( bh2 ) ;
goto out_free_bh ;
}
oi = ( struct omfs_inode * ) bh2 - > b_data ;
next = oi - > i_sibling ;
brelse ( bh2 ) ;
if ( prev ! = ~ 0 ) {
/* found in middle of list, get list ptr */
brelse ( bh ) ;
2008-09-07 01:51:53 +04:00
bh = omfs_bread ( dir - > i_sb , prev ) ;
2008-07-26 06:45:16 +04:00
if ( ! bh )
goto out ;
oi = ( struct omfs_inode * ) bh - > b_data ;
entry = & oi - > i_sibling ;
}
* entry = next ;
mark_buffer_dirty ( bh ) ;
if ( prev ! = ~ 0 ) {
dirty = omfs_iget ( dir - > i_sb , prev ) ;
if ( ! IS_ERR ( dirty ) ) {
mark_inode_dirty ( dirty ) ;
iput ( dirty ) ;
}
}
err = 0 ;
out_free_bh :
brelse ( bh ) ;
out :
return err ;
}
static int omfs_dir_is_empty ( struct inode * inode )
{
int nbuckets = ( inode - > i_size - OMFS_DIR_START ) / 8 ;
struct buffer_head * bh ;
u64 * ptr ;
int i ;
2008-09-07 01:51:53 +04:00
bh = omfs_bread ( inode - > i_sb , inode - > i_ino ) ;
2008-07-26 06:45:16 +04:00
if ( ! bh )
return 0 ;
ptr = ( u64 * ) & bh - > b_data [ OMFS_DIR_START ] ;
for ( i = 0 ; i < nbuckets ; i + + , ptr + + )
if ( * ptr ! = ~ 0 )
break ;
brelse ( bh ) ;
return * ptr ! = ~ 0 ;
}
2011-03-04 09:31:03 +03:00
static int omfs_remove ( struct inode * dir , struct dentry * dentry )
2008-07-26 06:45:16 +04:00
{
struct inode * inode = dentry - > d_inode ;
2011-03-04 09:31:03 +03:00
int ret ;
2011-05-25 00:06:06 +04:00
2011-05-28 00:42:04 +04:00
if ( S_ISDIR ( inode - > i_mode ) & &
! omfs_dir_is_empty ( inode ) )
return - ENOTEMPTY ;
2008-07-26 06:45:16 +04:00
ret = omfs_delete_entry ( dentry ) ;
if ( ret )
2011-03-04 09:31:03 +03:00
return ret ;
clear_nlink ( inode ) ;
mark_inode_dirty ( inode ) ;
2008-07-26 06:45:16 +04:00
mark_inode_dirty ( dir ) ;
2011-03-04 09:31:03 +03:00
return 0 ;
2008-07-26 06:45:16 +04:00
}
2011-07-25 06:58:10 +04:00
static int omfs_add_node ( struct inode * dir , struct dentry * dentry , umode_t mode )
2008-07-26 06:45:16 +04:00
{
int err ;
struct inode * inode = omfs_new_inode ( dir , mode ) ;
if ( IS_ERR ( inode ) )
return PTR_ERR ( inode ) ;
err = omfs_make_empty ( inode , dir - > i_sb ) ;
if ( err )
goto out_free_inode ;
err = omfs_add_link ( dentry , inode ) ;
if ( err )
goto out_free_inode ;
d_instantiate ( dentry , inode ) ;
return 0 ;
out_free_inode :
iput ( inode ) ;
return err ;
}
2011-07-26 09:41:39 +04:00
static int omfs_mkdir ( struct inode * dir , struct dentry * dentry , umode_t mode )
2008-07-26 06:45:16 +04:00
{
return omfs_add_node ( dir , dentry , mode | S_IFDIR ) ;
}
2011-07-26 09:42:34 +04:00
static int omfs_create ( struct inode * dir , struct dentry * dentry , umode_t mode ,
2012-06-11 02:05:36 +04:00
bool excl )
2008-07-26 06:45:16 +04:00
{
return omfs_add_node ( dir , dentry , mode | S_IFREG ) ;
}
static struct dentry * omfs_lookup ( struct inode * dir , struct dentry * dentry ,
2012-06-11 01:13:09 +04:00
unsigned int flags )
2008-07-26 06:45:16 +04:00
{
struct buffer_head * bh ;
struct inode * inode = NULL ;
if ( dentry - > d_name . len > OMFS_NAMELEN )
return ERR_PTR ( - ENAMETOOLONG ) ;
bh = omfs_find_entry ( dir , dentry - > d_name . name , dentry - > d_name . len ) ;
if ( ! IS_ERR ( bh ) ) {
struct omfs_inode * oi = ( struct omfs_inode * ) bh - > b_data ;
ino_t ino = be64_to_cpu ( oi - > i_head . h_self ) ;
brelse ( bh ) ;
inode = omfs_iget ( dir - > i_sb , ino ) ;
if ( IS_ERR ( inode ) )
return ERR_CAST ( inode ) ;
}
d_add ( dentry , inode ) ;
return NULL ;
}
/* sanity check block's self pointer */
int omfs_is_bad ( struct omfs_sb_info * sbi , struct omfs_header * header ,
u64 fsblock )
{
int is_bad ;
u64 ino = be64_to_cpu ( header - > h_self ) ;
is_bad = ( ( ino ! = fsblock ) | | ( ino < sbi - > s_root_ino ) | |
( ino > sbi - > s_num_blocks ) ) ;
if ( is_bad )
printk ( KERN_WARNING " omfs: bad hash chain detected \n " ) ;
return is_bad ;
}
2013-05-17 23:05:25 +04:00
static bool omfs_fill_chain ( struct inode * dir , struct dir_context * ctx ,
2008-07-26 06:45:16 +04:00
u64 fsblock , int hindex )
{
/* follow chain in this bucket */
while ( fsblock ! = ~ 0 ) {
2013-05-17 23:05:25 +04:00
struct buffer_head * bh = omfs_bread ( dir - > i_sb , fsblock ) ;
struct omfs_inode * oi ;
u64 self ;
unsigned char d_type ;
2008-07-26 06:45:16 +04:00
if ( ! bh )
2013-05-17 23:05:25 +04:00
return true ;
2008-07-26 06:45:16 +04:00
oi = ( struct omfs_inode * ) bh - > b_data ;
if ( omfs_is_bad ( OMFS_SB ( dir - > i_sb ) , & oi - > i_head , fsblock ) ) {
brelse ( bh ) ;
2013-05-17 23:05:25 +04:00
return true ;
2008-07-26 06:45:16 +04:00
}
self = fsblock ;
fsblock = be64_to_cpu ( oi - > i_sibling ) ;
/* skip visited nodes */
if ( hindex ) {
hindex - - ;
brelse ( bh ) ;
continue ;
}
d_type = ( oi - > i_type = = OMFS_DIR ) ? DT_DIR : DT_REG ;
2013-05-17 23:05:25 +04:00
if ( ! dir_emit ( ctx , oi - > i_name ,
strnlen ( oi - > i_name , OMFS_NAMELEN ) ,
self , d_type ) ) {
brelse ( bh ) ;
return false ;
}
2008-07-26 06:45:16 +04:00
brelse ( bh ) ;
2013-05-17 23:05:25 +04:00
ctx - > pos + + ;
2008-07-26 06:45:16 +04:00
}
2013-05-17 23:05:25 +04:00
return true ;
2008-07-26 06:45:16 +04:00
}
static int omfs_rename ( struct inode * old_dir , struct dentry * old_dentry ,
struct inode * new_dir , struct dentry * new_dentry )
{
struct inode * new_inode = new_dentry - > d_inode ;
struct inode * old_inode = old_dentry - > d_inode ;
int err ;
if ( new_inode ) {
/* overwriting existing file/dir */
2011-03-04 09:31:03 +03:00
err = omfs_remove ( new_dir , new_dentry ) ;
2008-07-26 06:45:16 +04:00
if ( err )
goto out ;
}
/* since omfs locates files by name, we need to unlink _before_
* adding the new link or we won ' t find the old one */
2011-03-04 09:18:19 +03:00
err = omfs_delete_entry ( old_dentry ) ;
if ( err )
2008-07-26 06:45:16 +04:00
goto out ;
2011-03-04 09:18:19 +03:00
mark_inode_dirty ( old_dir ) ;
2008-07-26 06:45:16 +04:00
err = omfs_add_link ( new_dentry , old_inode ) ;
if ( err )
goto out ;
old_inode - > i_ctime = CURRENT_TIME_SEC ;
2011-03-04 09:14:55 +03:00
mark_inode_dirty ( old_inode ) ;
2008-07-26 06:45:16 +04:00
out :
return err ;
}
2013-05-17 23:05:25 +04:00
static int omfs_readdir ( struct file * file , struct dir_context * ctx )
2008-07-26 06:45:16 +04:00
{
2013-05-17 23:05:25 +04:00
struct inode * dir = file_inode ( file ) ;
2008-07-26 06:45:16 +04:00
struct buffer_head * bh ;
2013-05-17 23:05:25 +04:00
__be64 * p ;
2008-07-26 06:45:16 +04:00
unsigned int hchain , hindex ;
int nbuckets ;
2013-05-17 23:05:25 +04:00
if ( ctx - > pos > > 32 )
return - EINVAL ;
if ( ctx - > pos < 1 < < 20 ) {
if ( ! dir_emit_dots ( file , ctx ) )
return 0 ;
ctx - > pos = 1 < < 20 ;
2008-07-26 06:45:16 +04:00
}
nbuckets = ( dir - > i_size - OMFS_DIR_START ) / 8 ;
/* high 12 bits store bucket + 1 and low 20 bits store hash index */
2013-05-17 23:05:25 +04:00
hchain = ( ctx - > pos > > 20 ) - 1 ;
hindex = ctx - > pos & 0xfffff ;
2008-07-26 06:45:16 +04:00
2008-09-07 01:51:53 +04:00
bh = omfs_bread ( dir - > i_sb , dir - > i_ino ) ;
2008-07-26 06:45:16 +04:00
if ( ! bh )
2013-05-17 23:05:25 +04:00
return - EINVAL ;
2008-07-26 06:45:16 +04:00
2013-05-17 23:05:25 +04:00
p = ( __be64 * ) ( bh - > b_data + OMFS_DIR_START ) + hchain ;
2008-07-26 06:45:16 +04:00
2013-05-17 23:05:25 +04:00
for ( ; hchain < nbuckets ; hchain + + ) {
__u64 fsblock = be64_to_cpu ( * p + + ) ;
if ( ! omfs_fill_chain ( dir , ctx , fsblock , hindex ) )
2008-07-26 06:45:16 +04:00
break ;
2013-05-17 23:05:25 +04:00
hindex = 0 ;
ctx - > pos = ( hchain + 2 ) < < 20 ;
2008-07-26 06:45:16 +04:00
}
brelse ( bh ) ;
2013-05-17 23:05:25 +04:00
return 0 ;
2008-07-26 06:45:16 +04:00
}
2009-09-22 04:01:11 +04:00
const struct inode_operations omfs_dir_inops = {
2008-07-26 06:45:16 +04:00
. lookup = omfs_lookup ,
. mkdir = omfs_mkdir ,
. rename = omfs_rename ,
. create = omfs_create ,
2011-03-04 09:31:03 +03:00
. unlink = omfs_remove ,
. rmdir = omfs_remove ,
2008-07-26 06:45:16 +04:00
} ;
2009-10-02 02:43:56 +04:00
const struct file_operations omfs_dir_operations = {
2008-07-26 06:45:16 +04:00
. read = generic_read_dir ,
2013-05-17 23:05:25 +04:00
. iterate = omfs_readdir ,
2008-09-03 23:53:01 +04:00
. llseek = generic_file_llseek ,
2008-07-26 06:45:16 +04:00
} ;