2005-04-17 02:20:36 +04:00
/*
* vfsv0 quota IO operations on file
*/
# include <linux/errno.h>
# include <linux/fs.h>
# include <linux/mount.h>
# include <linux/dqblk_v2.h>
# include <linux/quotaio_v2.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <asm/byteorder.h>
MODULE_AUTHOR ( " Jan Kara " ) ;
MODULE_DESCRIPTION ( " Quota format v2 support " ) ;
MODULE_LICENSE ( " GPL " ) ;
# define __QUOTA_V2_PARANOIA
typedef char * dqbuf_t ;
# define GETIDINDEX(id, depth) (((id) >> ((V2_DQTREEDEPTH-(depth)-1)*8)) & 0xff)
# define GETENTRIES(buf) ((struct v2_disk_dqblk *)(((char *)buf)+sizeof(struct v2_disk_dqdbheader)))
/* Check whether given file is really vfsv0 quotafile */
static int v2_check_quota_file ( struct super_block * sb , int type )
{
struct v2_disk_dqheader dqhead ;
ssize_t size ;
static const uint quota_magics [ ] = V2_INITQMAGICS ;
static const uint quota_versions [ ] = V2_INITQVERSIONS ;
size = sb - > s_op - > quota_read ( sb , type , ( char * ) & dqhead , sizeof ( struct v2_disk_dqheader ) , 0 ) ;
if ( size ! = sizeof ( struct v2_disk_dqheader ) ) {
2006-02-03 14:04:03 +03:00
printk ( " quota_v2: failed read expected=%zd got=%zd \n " ,
2006-01-15 00:21:10 +03:00
sizeof ( struct v2_disk_dqheader ) , size ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
if ( le32_to_cpu ( dqhead . dqh_magic ) ! = quota_magics [ type ] | |
le32_to_cpu ( dqhead . dqh_version ) ! = quota_versions [ type ] )
return 0 ;
return 1 ;
}
/* Read information header from quota file */
static int v2_read_file_info ( struct super_block * sb , int type )
{
struct v2_disk_dqinfo dinfo ;
struct mem_dqinfo * info = sb_dqopt ( sb ) - > info + type ;
ssize_t size ;
size = sb - > s_op - > quota_read ( sb , type , ( char * ) & dinfo ,
sizeof ( struct v2_disk_dqinfo ) , V2_DQINFOOFF ) ;
if ( size ! = sizeof ( struct v2_disk_dqinfo ) ) {
printk ( KERN_WARNING " Can't read info structure on device %s. \n " ,
sb - > s_id ) ;
return - 1 ;
}
info - > dqi_bgrace = le32_to_cpu ( dinfo . dqi_bgrace ) ;
info - > dqi_igrace = le32_to_cpu ( dinfo . dqi_igrace ) ;
info - > dqi_flags = le32_to_cpu ( dinfo . dqi_flags ) ;
info - > u . v2_i . dqi_blocks = le32_to_cpu ( dinfo . dqi_blocks ) ;
info - > u . v2_i . dqi_free_blk = le32_to_cpu ( dinfo . dqi_free_blk ) ;
info - > u . v2_i . dqi_free_entry = le32_to_cpu ( dinfo . dqi_free_entry ) ;
return 0 ;
}
/* Write information header to quota file */
static int v2_write_file_info ( struct super_block * sb , int type )
{
struct v2_disk_dqinfo dinfo ;
struct mem_dqinfo * info = sb_dqopt ( sb ) - > info + type ;
ssize_t size ;
spin_lock ( & dq_data_lock ) ;
info - > dqi_flags & = ~ DQF_INFO_DIRTY ;
dinfo . dqi_bgrace = cpu_to_le32 ( info - > dqi_bgrace ) ;
dinfo . dqi_igrace = cpu_to_le32 ( info - > dqi_igrace ) ;
dinfo . dqi_flags = cpu_to_le32 ( info - > dqi_flags & DQF_MASK ) ;
spin_unlock ( & dq_data_lock ) ;
dinfo . dqi_blocks = cpu_to_le32 ( info - > u . v2_i . dqi_blocks ) ;
dinfo . dqi_free_blk = cpu_to_le32 ( info - > u . v2_i . dqi_free_blk ) ;
dinfo . dqi_free_entry = cpu_to_le32 ( info - > u . v2_i . dqi_free_entry ) ;
size = sb - > s_op - > quota_write ( sb , type , ( char * ) & dinfo ,
sizeof ( struct v2_disk_dqinfo ) , V2_DQINFOOFF ) ;
if ( size ! = sizeof ( struct v2_disk_dqinfo ) ) {
printk ( KERN_WARNING " Can't write info structure on device %s. \n " ,
sb - > s_id ) ;
return - 1 ;
}
return 0 ;
}
static void disk2memdqb ( struct mem_dqblk * m , struct v2_disk_dqblk * d )
{
m - > dqb_ihardlimit = le32_to_cpu ( d - > dqb_ihardlimit ) ;
m - > dqb_isoftlimit = le32_to_cpu ( d - > dqb_isoftlimit ) ;
m - > dqb_curinodes = le32_to_cpu ( d - > dqb_curinodes ) ;
m - > dqb_itime = le64_to_cpu ( d - > dqb_itime ) ;
m - > dqb_bhardlimit = le32_to_cpu ( d - > dqb_bhardlimit ) ;
m - > dqb_bsoftlimit = le32_to_cpu ( d - > dqb_bsoftlimit ) ;
m - > dqb_curspace = le64_to_cpu ( d - > dqb_curspace ) ;
m - > dqb_btime = le64_to_cpu ( d - > dqb_btime ) ;
}
static void mem2diskdqb ( struct v2_disk_dqblk * d , struct mem_dqblk * m , qid_t id )
{
d - > dqb_ihardlimit = cpu_to_le32 ( m - > dqb_ihardlimit ) ;
d - > dqb_isoftlimit = cpu_to_le32 ( m - > dqb_isoftlimit ) ;
d - > dqb_curinodes = cpu_to_le32 ( m - > dqb_curinodes ) ;
d - > dqb_itime = cpu_to_le64 ( m - > dqb_itime ) ;
d - > dqb_bhardlimit = cpu_to_le32 ( m - > dqb_bhardlimit ) ;
d - > dqb_bsoftlimit = cpu_to_le32 ( m - > dqb_bsoftlimit ) ;
d - > dqb_curspace = cpu_to_le64 ( m - > dqb_curspace ) ;
d - > dqb_btime = cpu_to_le64 ( m - > dqb_btime ) ;
d - > dqb_id = cpu_to_le32 ( id ) ;
}
static dqbuf_t getdqbuf ( void )
{
dqbuf_t buf = kmalloc ( V2_DQBLKSIZE , GFP_NOFS ) ;
if ( ! buf )
printk ( KERN_WARNING " VFS: Not enough memory for quota buffers. \n " ) ;
return buf ;
}
static inline void freedqbuf ( dqbuf_t buf )
{
kfree ( buf ) ;
}
static inline ssize_t read_blk ( struct super_block * sb , int type , uint blk , dqbuf_t buf )
{
memset ( buf , 0 , V2_DQBLKSIZE ) ;
return sb - > s_op - > quota_read ( sb , type , ( char * ) buf ,
V2_DQBLKSIZE , blk < < V2_DQBLKSIZE_BITS ) ;
}
static inline ssize_t write_blk ( struct super_block * sb , int type , uint blk , dqbuf_t buf )
{
return sb - > s_op - > quota_write ( sb , type , ( char * ) buf ,
V2_DQBLKSIZE , blk < < V2_DQBLKSIZE_BITS ) ;
}
/* Remove empty block from list and return it */
static int get_free_dqblk ( struct super_block * sb , int type )
{
dqbuf_t buf = getdqbuf ( ) ;
struct mem_dqinfo * info = sb_dqinfo ( sb , type ) ;
struct v2_disk_dqdbheader * dh = ( struct v2_disk_dqdbheader * ) buf ;
int ret , blk ;
if ( ! buf )
return - ENOMEM ;
if ( info - > u . v2_i . dqi_free_blk ) {
blk = info - > u . v2_i . dqi_free_blk ;
if ( ( ret = read_blk ( sb , type , blk , buf ) ) < 0 )
goto out_buf ;
info - > u . v2_i . dqi_free_blk = le32_to_cpu ( dh - > dqdh_next_free ) ;
}
else {
memset ( buf , 0 , V2_DQBLKSIZE ) ;
/* Assure block allocation... */
if ( ( ret = write_blk ( sb , type , info - > u . v2_i . dqi_blocks , buf ) ) < 0 )
goto out_buf ;
blk = info - > u . v2_i . dqi_blocks + + ;
}
mark_info_dirty ( sb , type ) ;
ret = blk ;
out_buf :
freedqbuf ( buf ) ;
return ret ;
}
/* Insert empty block to the list */
static int put_free_dqblk ( struct super_block * sb , int type , dqbuf_t buf , uint blk )
{
struct mem_dqinfo * info = sb_dqinfo ( sb , type ) ;
struct v2_disk_dqdbheader * dh = ( struct v2_disk_dqdbheader * ) buf ;
int err ;
dh - > dqdh_next_free = cpu_to_le32 ( info - > u . v2_i . dqi_free_blk ) ;
dh - > dqdh_prev_free = cpu_to_le32 ( 0 ) ;
dh - > dqdh_entries = cpu_to_le16 ( 0 ) ;
info - > u . v2_i . dqi_free_blk = blk ;
mark_info_dirty ( sb , type ) ;
/* Some strange block. We had better leave it... */
if ( ( err = write_blk ( sb , type , blk , buf ) ) < 0 )
return err ;
return 0 ;
}
/* Remove given block from the list of blocks with free entries */
static int remove_free_dqentry ( struct super_block * sb , int type , dqbuf_t buf , uint blk )
{
dqbuf_t tmpbuf = getdqbuf ( ) ;
struct mem_dqinfo * info = sb_dqinfo ( sb , type ) ;
struct v2_disk_dqdbheader * dh = ( struct v2_disk_dqdbheader * ) buf ;
uint nextblk = le32_to_cpu ( dh - > dqdh_next_free ) , prevblk = le32_to_cpu ( dh - > dqdh_prev_free ) ;
int err ;
if ( ! tmpbuf )
return - ENOMEM ;
if ( nextblk ) {
if ( ( err = read_blk ( sb , type , nextblk , tmpbuf ) ) < 0 )
goto out_buf ;
( ( struct v2_disk_dqdbheader * ) tmpbuf ) - > dqdh_prev_free = dh - > dqdh_prev_free ;
if ( ( err = write_blk ( sb , type , nextblk , tmpbuf ) ) < 0 )
goto out_buf ;
}
if ( prevblk ) {
if ( ( err = read_blk ( sb , type , prevblk , tmpbuf ) ) < 0 )
goto out_buf ;
( ( struct v2_disk_dqdbheader * ) tmpbuf ) - > dqdh_next_free = dh - > dqdh_next_free ;
if ( ( err = write_blk ( sb , type , prevblk , tmpbuf ) ) < 0 )
goto out_buf ;
}
else {
info - > u . v2_i . dqi_free_entry = nextblk ;
mark_info_dirty ( sb , type ) ;
}
freedqbuf ( tmpbuf ) ;
dh - > dqdh_next_free = dh - > dqdh_prev_free = cpu_to_le32 ( 0 ) ;
/* No matter whether write succeeds block is out of list */
if ( write_blk ( sb , type , blk , buf ) < 0 )
printk ( KERN_ERR " VFS: Can't write block (%u) with free entries. \n " , blk ) ;
return 0 ;
out_buf :
freedqbuf ( tmpbuf ) ;
return err ;
}
/* Insert given block to the beginning of list with free entries */
static int insert_free_dqentry ( struct super_block * sb , int type , dqbuf_t buf , uint blk )
{
dqbuf_t tmpbuf = getdqbuf ( ) ;
struct mem_dqinfo * info = sb_dqinfo ( sb , type ) ;
struct v2_disk_dqdbheader * dh = ( struct v2_disk_dqdbheader * ) buf ;
int err ;
if ( ! tmpbuf )
return - ENOMEM ;
dh - > dqdh_next_free = cpu_to_le32 ( info - > u . v2_i . dqi_free_entry ) ;
dh - > dqdh_prev_free = cpu_to_le32 ( 0 ) ;
if ( ( err = write_blk ( sb , type , blk , buf ) ) < 0 )
goto out_buf ;
if ( info - > u . v2_i . dqi_free_entry ) {
if ( ( err = read_blk ( sb , type , info - > u . v2_i . dqi_free_entry , tmpbuf ) ) < 0 )
goto out_buf ;
( ( struct v2_disk_dqdbheader * ) tmpbuf ) - > dqdh_prev_free = cpu_to_le32 ( blk ) ;
if ( ( err = write_blk ( sb , type , info - > u . v2_i . dqi_free_entry , tmpbuf ) ) < 0 )
goto out_buf ;
}
freedqbuf ( tmpbuf ) ;
info - > u . v2_i . dqi_free_entry = blk ;
mark_info_dirty ( sb , type ) ;
return 0 ;
out_buf :
freedqbuf ( tmpbuf ) ;
return err ;
}
/* Find space for dquot */
static uint find_free_dqentry ( struct dquot * dquot , int * err )
{
struct super_block * sb = dquot - > dq_sb ;
struct mem_dqinfo * info = sb_dqopt ( sb ) - > info + dquot - > dq_type ;
uint blk , i ;
struct v2_disk_dqdbheader * dh ;
struct v2_disk_dqblk * ddquot ;
struct v2_disk_dqblk fakedquot ;
dqbuf_t buf ;
* err = 0 ;
if ( ! ( buf = getdqbuf ( ) ) ) {
* err = - ENOMEM ;
return 0 ;
}
dh = ( struct v2_disk_dqdbheader * ) buf ;
ddquot = GETENTRIES ( buf ) ;
if ( info - > u . v2_i . dqi_free_entry ) {
blk = info - > u . v2_i . dqi_free_entry ;
if ( ( * err = read_blk ( sb , dquot - > dq_type , blk , buf ) ) < 0 )
goto out_buf ;
}
else {
blk = get_free_dqblk ( sb , dquot - > dq_type ) ;
if ( ( int ) blk < 0 ) {
* err = blk ;
freedqbuf ( buf ) ;
return 0 ;
}
memset ( buf , 0 , V2_DQBLKSIZE ) ;
/* This is enough as block is already zeroed and entry list is empty... */
info - > u . v2_i . dqi_free_entry = blk ;
mark_info_dirty ( sb , dquot - > dq_type ) ;
}
if ( le16_to_cpu ( dh - > dqdh_entries ) + 1 > = V2_DQSTRINBLK ) /* Block will be full? */
if ( ( * err = remove_free_dqentry ( sb , dquot - > dq_type , buf , blk ) ) < 0 ) {
printk ( KERN_ERR " VFS: find_free_dqentry(): Can't remove block (%u) from entry free list. \n " , blk ) ;
goto out_buf ;
}
dh - > dqdh_entries = cpu_to_le16 ( le16_to_cpu ( dh - > dqdh_entries ) + 1 ) ;
memset ( & fakedquot , 0 , sizeof ( struct v2_disk_dqblk ) ) ;
/* Find free structure in block */
for ( i = 0 ; i < V2_DQSTRINBLK & & memcmp ( & fakedquot , ddquot + i , sizeof ( struct v2_disk_dqblk ) ) ; i + + ) ;
# ifdef __QUOTA_V2_PARANOIA
if ( i = = V2_DQSTRINBLK ) {
printk ( KERN_ERR " VFS: find_free_dqentry(): Data block full but it shouldn't. \n " ) ;
* err = - EIO ;
goto out_buf ;
}
# endif
if ( ( * err = write_blk ( sb , dquot - > dq_type , blk , buf ) ) < 0 ) {
printk ( KERN_ERR " VFS: find_free_dqentry(): Can't write quota data block %u. \n " , blk ) ;
goto out_buf ;
}
dquot - > dq_off = ( blk < < V2_DQBLKSIZE_BITS ) + sizeof ( struct v2_disk_dqdbheader ) + i * sizeof ( struct v2_disk_dqblk ) ;
freedqbuf ( buf ) ;
return blk ;
out_buf :
freedqbuf ( buf ) ;
return 0 ;
}
/* Insert reference to structure into the trie */
static int do_insert_tree ( struct dquot * dquot , uint * treeblk , int depth )
{
struct super_block * sb = dquot - > dq_sb ;
dqbuf_t buf ;
int ret = 0 , newson = 0 , newact = 0 ;
__le32 * ref ;
uint newblk ;
if ( ! ( buf = getdqbuf ( ) ) )
return - ENOMEM ;
if ( ! * treeblk ) {
ret = get_free_dqblk ( sb , dquot - > dq_type ) ;
if ( ret < 0 )
goto out_buf ;
* treeblk = ret ;
memset ( buf , 0 , V2_DQBLKSIZE ) ;
newact = 1 ;
}
else {
if ( ( ret = read_blk ( sb , dquot - > dq_type , * treeblk , buf ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't read tree quota block %u. \n " , * treeblk ) ;
goto out_buf ;
}
}
ref = ( __le32 * ) buf ;
newblk = le32_to_cpu ( ref [ GETIDINDEX ( dquot - > dq_id , depth ) ] ) ;
if ( ! newblk )
newson = 1 ;
if ( depth = = V2_DQTREEDEPTH - 1 ) {
# ifdef __QUOTA_V2_PARANOIA
if ( newblk ) {
printk ( KERN_ERR " VFS: Inserting already present quota entry (block %u). \n " , le32_to_cpu ( ref [ GETIDINDEX ( dquot - > dq_id , depth ) ] ) ) ;
ret = - EIO ;
goto out_buf ;
}
# endif
newblk = find_free_dqentry ( dquot , & ret ) ;
}
else
ret = do_insert_tree ( dquot , & newblk , depth + 1 ) ;
if ( newson & & ret > = 0 ) {
ref [ GETIDINDEX ( dquot - > dq_id , depth ) ] = cpu_to_le32 ( newblk ) ;
ret = write_blk ( sb , dquot - > dq_type , * treeblk , buf ) ;
}
else if ( newact & & ret < 0 )
put_free_dqblk ( sb , dquot - > dq_type , buf , * treeblk ) ;
out_buf :
freedqbuf ( buf ) ;
return ret ;
}
/* Wrapper for inserting quota structure into tree */
static inline int dq_insert_tree ( struct dquot * dquot )
{
int tmp = V2_DQTREEOFF ;
return do_insert_tree ( dquot , & tmp , 0 ) ;
}
/*
* We don ' t have to be afraid of deadlocks as we never have quotas on quota files . . .
*/
static int v2_write_dquot ( struct dquot * dquot )
{
int type = dquot - > dq_type ;
ssize_t ret ;
struct v2_disk_dqblk ddquot , empty ;
2006-03-23 14:00:29 +03:00
/* dq_off is guarded by dqio_mutex */
2005-04-17 02:20:36 +04:00
if ( ! dquot - > dq_off )
if ( ( ret = dq_insert_tree ( dquot ) ) < 0 ) {
printk ( KERN_ERR " VFS: Error %zd occurred while creating quota. \n " , ret ) ;
return ret ;
}
spin_lock ( & dq_data_lock ) ;
mem2diskdqb ( & ddquot , & dquot - > dq_dqb , dquot - > dq_id ) ;
/* Argh... We may need to write structure full of zeroes but that would be
* treated as an empty place by the rest of the code . Format change would
* be definitely cleaner but the problems probably are not worth it */
memset ( & empty , 0 , sizeof ( struct v2_disk_dqblk ) ) ;
if ( ! memcmp ( & empty , & ddquot , sizeof ( struct v2_disk_dqblk ) ) )
ddquot . dqb_itime = cpu_to_le64 ( 1 ) ;
spin_unlock ( & dq_data_lock ) ;
ret = dquot - > dq_sb - > s_op - > quota_write ( dquot - > dq_sb , type ,
( char * ) & ddquot , sizeof ( struct v2_disk_dqblk ) , dquot - > dq_off ) ;
if ( ret ! = sizeof ( struct v2_disk_dqblk ) ) {
printk ( KERN_WARNING " VFS: dquota write failed on dev %s \n " , dquot - > dq_sb - > s_id ) ;
if ( ret > = 0 )
ret = - ENOSPC ;
}
else
ret = 0 ;
dqstats . writes + + ;
return ret ;
}
/* Free dquot entry in data block */
static int free_dqentry ( struct dquot * dquot , uint blk )
{
struct super_block * sb = dquot - > dq_sb ;
int type = dquot - > dq_type ;
struct v2_disk_dqdbheader * dh ;
dqbuf_t buf = getdqbuf ( ) ;
int ret = 0 ;
if ( ! buf )
return - ENOMEM ;
if ( dquot - > dq_off > > V2_DQBLKSIZE_BITS ! = blk ) {
printk ( KERN_ERR " VFS: Quota structure has offset to other "
" block (%u) than it should (%u). \n " , blk ,
( uint ) ( dquot - > dq_off > > V2_DQBLKSIZE_BITS ) ) ;
goto out_buf ;
}
if ( ( ret = read_blk ( sb , type , blk , buf ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't read quota data block %u \n " , blk ) ;
goto out_buf ;
}
dh = ( struct v2_disk_dqdbheader * ) buf ;
dh - > dqdh_entries = cpu_to_le16 ( le16_to_cpu ( dh - > dqdh_entries ) - 1 ) ;
if ( ! le16_to_cpu ( dh - > dqdh_entries ) ) { /* Block got free? */
if ( ( ret = remove_free_dqentry ( sb , type , buf , blk ) ) < 0 | |
( ret = put_free_dqblk ( sb , type , buf , blk ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't move quota data block (%u) "
" to free list. \n " , blk ) ;
goto out_buf ;
}
}
else {
memset ( buf + ( dquot - > dq_off & ( ( 1 < < V2_DQBLKSIZE_BITS ) - 1 ) ) , 0 ,
sizeof ( struct v2_disk_dqblk ) ) ;
if ( le16_to_cpu ( dh - > dqdh_entries ) = = V2_DQSTRINBLK - 1 ) {
/* Insert will write block itself */
if ( ( ret = insert_free_dqentry ( sb , type , buf , blk ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't insert quota data block (%u) to free entry list. \n " , blk ) ;
goto out_buf ;
}
}
else
if ( ( ret = write_blk ( sb , type , blk , buf ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't write quota data "
" block %u \n " , blk ) ;
goto out_buf ;
}
}
dquot - > dq_off = 0 ; /* Quota is now unattached */
out_buf :
freedqbuf ( buf ) ;
return ret ;
}
/* Remove reference to dquot from tree */
static int remove_tree ( struct dquot * dquot , uint * blk , int depth )
{
struct super_block * sb = dquot - > dq_sb ;
int type = dquot - > dq_type ;
dqbuf_t buf = getdqbuf ( ) ;
int ret = 0 ;
uint newblk ;
__le32 * ref = ( __le32 * ) buf ;
if ( ! buf )
return - ENOMEM ;
if ( ( ret = read_blk ( sb , type , * blk , buf ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't read quota data block %u \n " , * blk ) ;
goto out_buf ;
}
newblk = le32_to_cpu ( ref [ GETIDINDEX ( dquot - > dq_id , depth ) ] ) ;
if ( depth = = V2_DQTREEDEPTH - 1 ) {
ret = free_dqentry ( dquot , newblk ) ;
newblk = 0 ;
}
else
ret = remove_tree ( dquot , & newblk , depth + 1 ) ;
if ( ret > = 0 & & ! newblk ) {
int i ;
ref [ GETIDINDEX ( dquot - > dq_id , depth ) ] = cpu_to_le32 ( 0 ) ;
for ( i = 0 ; i < V2_DQBLKSIZE & & ! buf [ i ] ; i + + ) ; /* Block got empty? */
2005-04-17 02:25:47 +04:00
/* Don't put the root block into the free block list */
if ( i = = V2_DQBLKSIZE & & * blk ! = V2_DQTREEOFF ) {
2005-04-17 02:20:36 +04:00
put_free_dqblk ( sb , type , buf , * blk ) ;
* blk = 0 ;
}
else
if ( ( ret = write_blk ( sb , type , * blk , buf ) ) < 0 )
printk ( KERN_ERR " VFS: Can't write quota tree "
" block %u. \n " , * blk ) ;
}
out_buf :
freedqbuf ( buf ) ;
return ret ;
}
/* Delete dquot from tree */
static int v2_delete_dquot ( struct dquot * dquot )
{
uint tmp = V2_DQTREEOFF ;
if ( ! dquot - > dq_off ) /* Even not allocated? */
return 0 ;
return remove_tree ( dquot , & tmp , 0 ) ;
}
/* Find entry in block */
static loff_t find_block_dqentry ( struct dquot * dquot , uint blk )
{
dqbuf_t buf = getdqbuf ( ) ;
loff_t ret = 0 ;
int i ;
struct v2_disk_dqblk * ddquot = GETENTRIES ( buf ) ;
if ( ! buf )
return - ENOMEM ;
if ( ( ret = read_blk ( dquot - > dq_sb , dquot - > dq_type , blk , buf ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't read quota tree block %u. \n " , blk ) ;
goto out_buf ;
}
if ( dquot - > dq_id )
for ( i = 0 ; i < V2_DQSTRINBLK & &
le32_to_cpu ( ddquot [ i ] . dqb_id ) ! = dquot - > dq_id ; i + + ) ;
else { /* ID 0 as a bit more complicated searching... */
struct v2_disk_dqblk fakedquot ;
memset ( & fakedquot , 0 , sizeof ( struct v2_disk_dqblk ) ) ;
for ( i = 0 ; i < V2_DQSTRINBLK ; i + + )
if ( ! le32_to_cpu ( ddquot [ i ] . dqb_id ) & &
memcmp ( & fakedquot , ddquot + i , sizeof ( struct v2_disk_dqblk ) ) )
break ;
}
if ( i = = V2_DQSTRINBLK ) {
printk ( KERN_ERR " VFS: Quota for id %u referenced "
" but not present. \n " , dquot - > dq_id ) ;
ret = - EIO ;
goto out_buf ;
}
else
ret = ( blk < < V2_DQBLKSIZE_BITS ) + sizeof ( struct
v2_disk_dqdbheader ) + i * sizeof ( struct v2_disk_dqblk ) ;
out_buf :
freedqbuf ( buf ) ;
return ret ;
}
/* Find entry for given id in the tree */
static loff_t find_tree_dqentry ( struct dquot * dquot , uint blk , int depth )
{
dqbuf_t buf = getdqbuf ( ) ;
loff_t ret = 0 ;
__le32 * ref = ( __le32 * ) buf ;
if ( ! buf )
return - ENOMEM ;
if ( ( ret = read_blk ( dquot - > dq_sb , dquot - > dq_type , blk , buf ) ) < 0 ) {
printk ( KERN_ERR " VFS: Can't read quota tree block %u. \n " , blk ) ;
goto out_buf ;
}
ret = 0 ;
blk = le32_to_cpu ( ref [ GETIDINDEX ( dquot - > dq_id , depth ) ] ) ;
if ( ! blk ) /* No reference? */
goto out_buf ;
if ( depth < V2_DQTREEDEPTH - 1 )
ret = find_tree_dqentry ( dquot , blk , depth + 1 ) ;
else
ret = find_block_dqentry ( dquot , blk ) ;
out_buf :
freedqbuf ( buf ) ;
return ret ;
}
/* Find entry for given id in the tree - wrapper function */
static inline loff_t find_dqentry ( struct dquot * dquot )
{
return find_tree_dqentry ( dquot , V2_DQTREEOFF , 0 ) ;
}
static int v2_read_dquot ( struct dquot * dquot )
{
int type = dquot - > dq_type ;
loff_t offset ;
struct v2_disk_dqblk ddquot , empty ;
int ret = 0 ;
# ifdef __QUOTA_V2_PARANOIA
/* Invalidated quota? */
if ( ! dquot - > dq_sb | | ! sb_dqopt ( dquot - > dq_sb ) - > files [ type ] ) {
printk ( KERN_ERR " VFS: Quota invalidated while reading! \n " ) ;
return - EIO ;
}
# endif
offset = find_dqentry ( dquot ) ;
if ( offset < = 0 ) { /* Entry not present? */
if ( offset < 0 )
printk ( KERN_ERR " VFS: Can't read quota "
" structure for id %u. \n " , dquot - > dq_id ) ;
dquot - > dq_off = 0 ;
set_bit ( DQ_FAKE_B , & dquot - > dq_flags ) ;
memset ( & dquot - > dq_dqb , 0 , sizeof ( struct mem_dqblk ) ) ;
ret = offset ;
}
else {
dquot - > dq_off = offset ;
if ( ( ret = dquot - > dq_sb - > s_op - > quota_read ( dquot - > dq_sb , type ,
( char * ) & ddquot , sizeof ( struct v2_disk_dqblk ) , offset ) )
! = sizeof ( struct v2_disk_dqblk ) ) {
if ( ret > = 0 )
ret = - EIO ;
printk ( KERN_ERR " VFS: Error while reading quota "
" structure for id %u. \n " , dquot - > dq_id ) ;
memset ( & ddquot , 0 , sizeof ( struct v2_disk_dqblk ) ) ;
}
else {
ret = 0 ;
/* We need to escape back all-zero structure */
memset ( & empty , 0 , sizeof ( struct v2_disk_dqblk ) ) ;
empty . dqb_itime = cpu_to_le64 ( 1 ) ;
if ( ! memcmp ( & empty , & ddquot , sizeof ( struct v2_disk_dqblk ) ) )
ddquot . dqb_itime = 0 ;
}
disk2memdqb ( & dquot - > dq_dqb , & ddquot ) ;
if ( ! dquot - > dq_dqb . dqb_bhardlimit & &
! dquot - > dq_dqb . dqb_bsoftlimit & &
! dquot - > dq_dqb . dqb_ihardlimit & &
! dquot - > dq_dqb . dqb_isoftlimit )
set_bit ( DQ_FAKE_B , & dquot - > dq_flags ) ;
}
dqstats . reads + + ;
return ret ;
}
/* Check whether dquot should not be deleted. We know we are
* the only one operating on dquot ( thanks to dq_lock ) */
static int v2_release_dquot ( struct dquot * dquot )
{
if ( test_bit ( DQ_FAKE_B , & dquot - > dq_flags ) & & ! ( dquot - > dq_dqb . dqb_curinodes | dquot - > dq_dqb . dqb_curspace ) )
return v2_delete_dquot ( dquot ) ;
return 0 ;
}
static struct quota_format_ops v2_format_ops = {
. check_quota_file = v2_check_quota_file ,
. read_file_info = v2_read_file_info ,
. write_file_info = v2_write_file_info ,
. free_file_info = NULL ,
. read_dqblk = v2_read_dquot ,
. commit_dqblk = v2_write_dquot ,
. release_dqblk = v2_release_dquot ,
} ;
static struct quota_format_type v2_quota_format = {
. qf_fmt_id = QFMT_VFS_V0 ,
. qf_ops = & v2_format_ops ,
. qf_owner = THIS_MODULE
} ;
static int __init init_v2_quota_format ( void )
{
return register_quota_format ( & v2_quota_format ) ;
}
static void __exit exit_v2_quota_format ( void )
{
unregister_quota_format ( & v2_quota_format ) ;
}
module_init ( init_v2_quota_format ) ;
module_exit ( exit_v2_quota_format ) ;