2005-04-17 02:20:36 +04:00
/*
* linux / fs / sysv / dir . c
*
* minix / dir . c
* Copyright ( C ) 1991 , 1992 Linus Torvalds
*
* coh / dir . c
* Copyright ( C ) 1993 Pascal Haible , Bruno Haible
*
* sysv / dir . c
* Copyright ( C ) 1993 Bruno Haible
*
* SystemV / Coherent directory handling functions
*/
# include <linux/pagemap.h>
# include <linux/highmem.h>
2007-10-16 12:25:21 +04:00
# include <linux/swap.h>
2005-04-17 02:20:36 +04:00
# include "sysv.h"
static int sysv_readdir ( struct file * , void * , filldir_t ) ;
2006-03-28 13:56:42 +04:00
const struct file_operations sysv_dir_operations = {
2009-06-17 07:59:37 +04:00
. llseek = generic_file_llseek ,
2005-04-17 02:20:36 +04:00
. read = generic_read_dir ,
. readdir = sysv_readdir ,
2010-05-26 19:53:41 +04:00
. fsync = generic_file_fsync ,
2005-04-17 02:20:36 +04:00
} ;
static inline void dir_put_page ( struct page * page )
{
kunmap ( page ) ;
page_cache_release ( page ) ;
}
static inline unsigned long dir_pages ( struct inode * inode )
{
return ( inode - > i_size + PAGE_CACHE_SIZE - 1 ) > > PAGE_CACHE_SHIFT ;
}
2007-10-16 12:25:21 +04:00
static int dir_commit_chunk ( struct page * page , loff_t pos , unsigned len )
2005-04-17 02:20:36 +04:00
{
2007-10-16 12:25:21 +04:00
struct address_space * mapping = page - > mapping ;
struct inode * dir = mapping - > host ;
2005-04-17 02:20:36 +04:00
int err = 0 ;
2007-10-16 12:25:21 +04:00
block_write_end ( NULL , mapping , pos , len , len , page , NULL ) ;
if ( pos + len > dir - > i_size ) {
i_size_write ( dir , pos + len ) ;
mark_inode_dirty ( dir ) ;
}
2005-04-17 02:20:36 +04:00
if ( IS_DIRSYNC ( dir ) )
err = write_one_page ( page , 1 ) ;
else
unlock_page ( page ) ;
return err ;
}
static struct page * dir_get_page ( struct inode * dir , unsigned long n )
{
struct address_space * mapping = dir - > i_mapping ;
2006-06-23 13:05:08 +04:00
struct page * page = read_mapping_page ( mapping , n , NULL ) ;
2007-05-07 01:49:04 +04:00
if ( ! IS_ERR ( page ) )
2005-04-17 02:20:36 +04:00
kmap ( page ) ;
return page ;
}
static int sysv_readdir ( struct file * filp , void * dirent , filldir_t filldir )
{
unsigned long pos = filp - > f_pos ;
2006-12-08 13:37:43 +03:00
struct inode * inode = filp - > f_path . dentry - > d_inode ;
2005-04-17 02:20:36 +04:00
struct super_block * sb = inode - > i_sb ;
unsigned offset = pos & ~ PAGE_CACHE_MASK ;
unsigned long n = pos > > PAGE_CACHE_SHIFT ;
unsigned long npages = dir_pages ( inode ) ;
pos = ( pos + SYSV_DIRSIZE - 1 ) & ~ ( SYSV_DIRSIZE - 1 ) ;
if ( pos > = inode - > i_size )
goto done ;
for ( ; n < npages ; n + + , offset = 0 ) {
char * kaddr , * limit ;
struct sysv_dir_entry * de ;
struct page * page = dir_get_page ( inode , n ) ;
if ( IS_ERR ( page ) )
continue ;
kaddr = ( char * ) page_address ( page ) ;
de = ( struct sysv_dir_entry * ) ( kaddr + offset ) ;
limit = kaddr + PAGE_CACHE_SIZE - SYSV_DIRSIZE ;
for ( ; ( char * ) de < = limit ; de + + ) {
char * name = de - > name ;
int over ;
if ( ! de - > inode )
continue ;
offset = ( char * ) de - kaddr ;
over = filldir ( dirent , name , strnlen ( name , SYSV_NAMELEN ) ,
2006-01-08 12:03:05 +03:00
( ( loff_t ) n < < PAGE_CACHE_SHIFT ) | offset ,
2005-04-17 02:20:36 +04:00
fs16_to_cpu ( SYSV_SB ( sb ) , de - > inode ) ,
DT_UNKNOWN ) ;
if ( over ) {
dir_put_page ( page ) ;
goto done ;
}
}
dir_put_page ( page ) ;
}
done :
2006-01-08 12:03:05 +03:00
filp - > f_pos = ( ( loff_t ) n < < PAGE_CACHE_SHIFT ) | offset ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/* compare strings: name[0..len-1] (not zero-terminated) and
* buffer [ 0. . ] ( filled with zeroes up to buffer [ 0. . maxlen - 1 ] )
*/
static inline int namecompare ( int len , int maxlen ,
const char * name , const char * buffer )
{
if ( len < maxlen & & buffer [ len ] )
return 0 ;
return ! memcmp ( name , buffer , len ) ;
}
/*
* sysv_find_entry ( )
*
* finds an entry in the specified directory with the wanted name . It
* returns the cache buffer in which the entry was found , and the entry
* itself ( as a parameter - res_dir ) . It does NOT read the inode of the
* entry - you ' ll have to do that yourself if you want to .
*/
struct sysv_dir_entry * sysv_find_entry ( struct dentry * dentry , struct page * * res_page )
{
const char * name = dentry - > d_name . name ;
int namelen = dentry - > d_name . len ;
struct inode * dir = dentry - > d_parent - > d_inode ;
unsigned long start , n ;
unsigned long npages = dir_pages ( dir ) ;
struct page * page = NULL ;
struct sysv_dir_entry * de ;
* res_page = NULL ;
start = SYSV_I ( dir ) - > i_dir_start_lookup ;
if ( start > = npages )
start = 0 ;
n = start ;
do {
char * kaddr ;
page = dir_get_page ( dir , n ) ;
if ( ! IS_ERR ( page ) ) {
kaddr = ( char * ) page_address ( page ) ;
de = ( struct sysv_dir_entry * ) kaddr ;
kaddr + = PAGE_CACHE_SIZE - SYSV_DIRSIZE ;
for ( ; ( char * ) de < = kaddr ; de + + ) {
if ( ! de - > inode )
continue ;
if ( namecompare ( namelen , SYSV_NAMELEN ,
name , de - > name ) )
goto found ;
}
2010-04-21 14:30:32 +04:00
dir_put_page ( page ) ;
2005-04-17 02:20:36 +04:00
}
if ( + + n > = npages )
n = 0 ;
} while ( n ! = start ) ;
return NULL ;
found :
SYSV_I ( dir ) - > i_dir_start_lookup = n ;
* res_page = page ;
return de ;
}
int sysv_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 page * page = NULL ;
struct sysv_dir_entry * de ;
unsigned long npages = dir_pages ( dir ) ;
unsigned long n ;
char * kaddr ;
2007-10-16 12:25:21 +04:00
loff_t pos ;
2005-04-17 02:20:36 +04:00
int err ;
/* We take care of directory expansion in the same loop */
for ( n = 0 ; n < = npages ; n + + ) {
page = dir_get_page ( dir , n ) ;
err = PTR_ERR ( page ) ;
if ( IS_ERR ( page ) )
goto out ;
kaddr = ( char * ) page_address ( page ) ;
de = ( struct sysv_dir_entry * ) kaddr ;
kaddr + = PAGE_CACHE_SIZE - SYSV_DIRSIZE ;
while ( ( char * ) de < = kaddr ) {
if ( ! de - > inode )
goto got_it ;
err = - EEXIST ;
if ( namecompare ( namelen , SYSV_NAMELEN , name , de - > name ) )
goto out_page ;
de + + ;
}
dir_put_page ( page ) ;
}
BUG ( ) ;
return - EINVAL ;
got_it :
2007-10-16 12:25:21 +04:00
pos = page_offset ( page ) +
( char * ) de - ( char * ) page_address ( page ) ;
2005-04-17 02:20:36 +04:00
lock_page ( page ) ;
2010-06-04 13:29:56 +04:00
err = sysv_prepare_chunk ( page , pos , SYSV_DIRSIZE ) ;
2005-04-17 02:20:36 +04:00
if ( err )
goto out_unlock ;
memcpy ( de - > name , name , namelen ) ;
memset ( de - > name + namelen , 0 , SYSV_DIRSIZE - namelen - 2 ) ;
de - > inode = cpu_to_fs16 ( SYSV_SB ( inode - > i_sb ) , inode - > i_ino ) ;
2007-10-16 12:25:21 +04:00
err = dir_commit_chunk ( page , pos , SYSV_DIRSIZE ) ;
2005-04-17 02:20:36 +04:00
dir - > i_mtime = dir - > i_ctime = CURRENT_TIME_SEC ;
mark_inode_dirty ( dir ) ;
out_page :
dir_put_page ( page ) ;
out :
return err ;
out_unlock :
unlock_page ( page ) ;
goto out_page ;
}
int sysv_delete_entry ( struct sysv_dir_entry * de , struct page * page )
{
2010-06-04 13:29:56 +04:00
struct inode * inode = page - > mapping - > host ;
2005-04-17 02:20:36 +04:00
char * kaddr = ( char * ) page_address ( page ) ;
2007-10-16 12:25:21 +04:00
loff_t pos = page_offset ( page ) + ( char * ) de - kaddr ;
2005-04-17 02:20:36 +04:00
int err ;
lock_page ( page ) ;
2010-06-04 13:29:56 +04:00
err = sysv_prepare_chunk ( page , pos , SYSV_DIRSIZE ) ;
2006-04-02 15:39:21 +04:00
BUG_ON ( err ) ;
2005-04-17 02:20:36 +04:00
de - > inode = 0 ;
2007-10-16 12:25:21 +04:00
err = dir_commit_chunk ( page , pos , SYSV_DIRSIZE ) ;
2005-04-17 02:20:36 +04:00
dir_put_page ( page ) ;
inode - > i_ctime = inode - > i_mtime = CURRENT_TIME_SEC ;
mark_inode_dirty ( inode ) ;
return err ;
}
int sysv_make_empty ( struct inode * inode , struct inode * dir )
{
2010-06-04 13:29:56 +04:00
struct page * page = grab_cache_page ( inode - > i_mapping , 0 ) ;
2005-04-17 02:20:36 +04:00
struct sysv_dir_entry * de ;
char * base ;
int err ;
if ( ! page )
return - ENOMEM ;
2010-06-04 13:29:56 +04:00
err = sysv_prepare_chunk ( page , 0 , 2 * SYSV_DIRSIZE ) ;
2005-04-17 02:20:36 +04:00
if ( err ) {
unlock_page ( page ) ;
goto fail ;
}
2007-10-16 12:25:21 +04:00
kmap ( page ) ;
2005-04-17 02:20:36 +04:00
base = ( char * ) page_address ( page ) ;
memset ( base , 0 , PAGE_CACHE_SIZE ) ;
de = ( struct sysv_dir_entry * ) base ;
de - > inode = cpu_to_fs16 ( SYSV_SB ( inode - > i_sb ) , inode - > i_ino ) ;
strcpy ( de - > name , " . " ) ;
de + + ;
de - > inode = cpu_to_fs16 ( SYSV_SB ( inode - > i_sb ) , dir - > i_ino ) ;
strcpy ( de - > name , " .. " ) ;
2007-10-16 12:25:21 +04:00
kunmap ( page ) ;
2005-04-17 02:20:36 +04:00
err = dir_commit_chunk ( page , 0 , 2 * SYSV_DIRSIZE ) ;
fail :
page_cache_release ( page ) ;
return err ;
}
/*
* routine to check that the specified directory is empty ( for rmdir )
*/
int sysv_empty_dir ( struct inode * inode )
{
struct super_block * sb = inode - > i_sb ;
struct page * page = NULL ;
unsigned long i , npages = dir_pages ( inode ) ;
for ( i = 0 ; i < npages ; i + + ) {
char * kaddr ;
struct sysv_dir_entry * de ;
page = dir_get_page ( inode , i ) ;
if ( IS_ERR ( page ) )
continue ;
kaddr = ( char * ) page_address ( page ) ;
de = ( struct sysv_dir_entry * ) kaddr ;
kaddr + = PAGE_CACHE_SIZE - SYSV_DIRSIZE ;
for ( ; ( char * ) de < = kaddr ; de + + ) {
if ( ! de - > inode )
continue ;
/* check for . and .. */
if ( de - > name [ 0 ] ! = ' . ' )
goto not_empty ;
if ( ! de - > name [ 1 ] ) {
if ( de - > inode = = cpu_to_fs16 ( SYSV_SB ( sb ) ,
inode - > i_ino ) )
continue ;
goto not_empty ;
}
if ( de - > name [ 1 ] ! = ' . ' | | de - > name [ 2 ] )
goto not_empty ;
}
dir_put_page ( page ) ;
}
return 1 ;
not_empty :
dir_put_page ( page ) ;
return 0 ;
}
/* Releases the page */
void sysv_set_link ( struct sysv_dir_entry * de , struct page * page ,
struct inode * inode )
{
2010-06-04 13:29:56 +04:00
struct inode * dir = page - > mapping - > host ;
2007-10-16 12:25:21 +04:00
loff_t pos = page_offset ( page ) +
( char * ) de - ( char * ) page_address ( page ) ;
2005-04-17 02:20:36 +04:00
int err ;
lock_page ( page ) ;
2010-06-04 13:29:56 +04:00
err = sysv_prepare_chunk ( page , pos , SYSV_DIRSIZE ) ;
2006-04-02 15:39:21 +04:00
BUG_ON ( err ) ;
2005-04-17 02:20:36 +04:00
de - > inode = cpu_to_fs16 ( SYSV_SB ( inode - > i_sb ) , inode - > i_ino ) ;
2007-10-16 12:25:21 +04:00
err = dir_commit_chunk ( page , pos , SYSV_DIRSIZE ) ;
2005-04-17 02:20:36 +04:00
dir_put_page ( page ) ;
dir - > i_mtime = dir - > i_ctime = CURRENT_TIME_SEC ;
mark_inode_dirty ( dir ) ;
}
struct sysv_dir_entry * sysv_dotdot ( struct inode * dir , struct page * * p )
{
struct page * page = dir_get_page ( dir , 0 ) ;
struct sysv_dir_entry * de = NULL ;
if ( ! IS_ERR ( page ) ) {
de = ( struct sysv_dir_entry * ) page_address ( page ) + 1 ;
* p = page ;
}
return de ;
}
ino_t sysv_inode_by_name ( struct dentry * dentry )
{
struct page * page ;
struct sysv_dir_entry * de = sysv_find_entry ( dentry , & page ) ;
ino_t res = 0 ;
if ( de ) {
res = fs16_to_cpu ( SYSV_SB ( dentry - > d_sb ) , de - > inode ) ;
dir_put_page ( page ) ;
}
return res ;
}