2006-01-16 19:50:04 +03:00
/*
* Copyright ( C ) Sistina Software , Inc . 1997 - 2003 All rights reserved .
* Copyright ( C ) 2004 - 2005 Red Hat , Inc . All rights reserved .
*
* This copyrighted material is made available to anyone wishing to use ,
* modify , copy , or redistribute it subject to the terms and conditions
* of the GNU General Public License v .2 .
*/
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/completion.h>
# include <linux/buffer_head.h>
# include <linux/pagemap.h>
# include <asm/semaphore.h>
# include "gfs2.h"
# include "bmap.h"
# include "glock.h"
# include "inode.h"
# include "jdata.h"
# include "log.h"
# include "meta_io.h"
# include "ops_address.h"
# include "page.h"
# include "quota.h"
# include "trans.h"
/**
2006-01-30 12:39:10 +03:00
* gfs2_get_block - Fills in a buffer head with details about a block
2006-01-16 19:50:04 +03:00
* @ inode : The inode
* @ lblock : The block number to look up
* @ bh_result : The buffer head to return the result in
* @ create : Non - zero if we may add block to the file
*
* Returns : errno
*/
2006-01-30 12:39:10 +03:00
int gfs2_get_block ( struct inode * inode , sector_t lblock ,
struct buffer_head * bh_result , int create )
2006-01-16 19:50:04 +03:00
{
struct gfs2_inode * ip = get_v2ip ( inode ) ;
int new = create ;
uint64_t dblock ;
int error ;
error = gfs2_block_map ( ip , lblock , & new , & dblock , NULL ) ;
if ( error )
return error ;
if ( ! dblock )
return 0 ;
map_bh ( bh_result , inode - > i_sb , dblock ) ;
if ( new )
set_buffer_new ( bh_result ) ;
return 0 ;
}
/**
* get_block_noalloc - Fills in a buffer head with details about a block
* @ inode : The inode
* @ lblock : The block number to look up
* @ bh_result : The buffer head to return the result in
* @ create : Non - zero if we may add block to the file
*
* Returns : errno
*/
static int get_block_noalloc ( struct inode * inode , sector_t lblock ,
struct buffer_head * bh_result , int create )
{
struct gfs2_inode * ip = get_v2ip ( inode ) ;
int new = 0 ;
uint64_t dblock ;
int error ;
error = gfs2_block_map ( ip , lblock , & new , & dblock , NULL ) ;
if ( error )
return error ;
if ( dblock )
map_bh ( bh_result , inode - > i_sb , dblock ) ;
else if ( gfs2_assert_withdraw ( ip - > i_sbd , ! create ) )
error = - EIO ;
return error ;
}
static int get_blocks ( struct inode * inode , sector_t lblock ,
unsigned long max_blocks , struct buffer_head * bh_result ,
int create )
{
struct gfs2_inode * ip = get_v2ip ( inode ) ;
int new = create ;
uint64_t dblock ;
uint32_t extlen ;
int error ;
error = gfs2_block_map ( ip , lblock , & new , & dblock , & extlen ) ;
if ( error )
return error ;
if ( ! dblock )
return 0 ;
map_bh ( bh_result , inode - > i_sb , dblock ) ;
if ( new )
set_buffer_new ( bh_result ) ;
if ( extlen > max_blocks )
extlen = max_blocks ;
bh_result - > b_size = extlen < < inode - > i_blkbits ;
return 0 ;
}
static int get_blocks_noalloc ( struct inode * inode , sector_t lblock ,
unsigned long max_blocks ,
struct buffer_head * bh_result , int create )
{
struct gfs2_inode * ip = get_v2ip ( inode ) ;
int new = 0 ;
uint64_t dblock ;
uint32_t extlen ;
int error ;
error = gfs2_block_map ( ip , lblock , & new , & dblock , & extlen ) ;
if ( error )
return error ;
if ( dblock ) {
map_bh ( bh_result , inode - > i_sb , dblock ) ;
if ( extlen > max_blocks )
extlen = max_blocks ;
bh_result - > b_size = extlen < < inode - > i_blkbits ;
} else if ( gfs2_assert_withdraw ( ip - > i_sbd , ! create ) )
error = - EIO ;
return error ;
}
/**
* gfs2_writepage - Write complete page
* @ page : Page to write
*
* Returns : errno
*
* Use Linux VFS block_write_full_page ( ) to write one page ,
* using GFS2 ' s get_block_noalloc to find which blocks to write .
*/
static int gfs2_writepage ( struct page * page , struct writeback_control * wbc )
{
struct gfs2_inode * ip = get_v2ip ( page - > mapping - > host ) ;
struct gfs2_sbd * sdp = ip - > i_sbd ;
int error ;
atomic_inc ( & sdp - > sd_ops_address ) ;
if ( gfs2_assert_withdraw ( sdp , gfs2_glock_is_held_excl ( ip - > i_gl ) ) ) {
unlock_page ( page ) ;
return - EIO ;
}
if ( get_transaction ) {
redirty_page_for_writepage ( wbc , page ) ;
unlock_page ( page ) ;
return 0 ;
}
error = block_write_full_page ( page , get_block_noalloc , wbc ) ;
gfs2_meta_cache_flush ( ip ) ;
return error ;
}
/**
* stuffed_readpage - Fill in a Linux page with stuffed file data
* @ ip : the inode
* @ page : the page
*
* Returns : errno
*/
static int stuffed_readpage ( struct gfs2_inode * ip , struct page * page )
{
struct buffer_head * dibh ;
void * kaddr ;
int error ;
error = gfs2_meta_inode_buffer ( ip , & dibh ) ;
if ( error )
return error ;
kaddr = kmap ( page ) ;
memcpy ( ( char * ) kaddr ,
dibh - > b_data + sizeof ( struct gfs2_dinode ) ,
ip - > i_di . di_size ) ;
memset ( ( char * ) kaddr + ip - > i_di . di_size ,
0 ,
PAGE_CACHE_SIZE - ip - > i_di . di_size ) ;
kunmap ( page ) ;
brelse ( dibh ) ;
SetPageUptodate ( page ) ;
return 0 ;
}
static int zero_readpage ( struct page * page )
{
void * kaddr ;
kaddr = kmap ( page ) ;
memset ( kaddr , 0 , PAGE_CACHE_SIZE ) ;
kunmap ( page ) ;
SetPageUptodate ( page ) ;
unlock_page ( page ) ;
return 0 ;
}
/**
* jdata_readpage - readpage that goes through gfs2_jdata_read_mem ( )
* @ ip :
* @ page : The page to read
*
* Returns : errno
*/
static int jdata_readpage ( struct gfs2_inode * ip , struct page * page )
{
void * kaddr ;
int ret ;
kaddr = kmap ( page ) ;
ret = gfs2_jdata_read_mem ( ip , kaddr ,
( uint64_t ) page - > index < < PAGE_CACHE_SHIFT ,
PAGE_CACHE_SIZE ) ;
if ( ret > = 0 ) {
if ( ret < PAGE_CACHE_SIZE )
memset ( kaddr + ret , 0 , PAGE_CACHE_SIZE - ret ) ;
SetPageUptodate ( page ) ;
ret = 0 ;
}
kunmap ( page ) ;
unlock_page ( page ) ;
return ret ;
}
/**
* gfs2_readpage - readpage with locking
* @ file : The file to read a page for
* @ page : The page to read
*
* Returns : errno
*/
static int gfs2_readpage ( struct file * file , struct page * page )
{
struct gfs2_inode * ip = get_v2ip ( page - > mapping - > host ) ;
struct gfs2_sbd * sdp = ip - > i_sbd ;
int error ;
atomic_inc ( & sdp - > sd_ops_address ) ;
if ( gfs2_assert_warn ( sdp , gfs2_glock_is_locked_by_me ( ip - > i_gl ) ) ) {
unlock_page ( page ) ;
return - EOPNOTSUPP ;
}
if ( ! gfs2_is_jdata ( ip ) ) {
if ( gfs2_is_stuffed ( ip ) ) {
if ( ! page - > index ) {
error = stuffed_readpage ( ip , page ) ;
unlock_page ( page ) ;
} else
error = zero_readpage ( page ) ;
} else
2006-01-30 12:39:10 +03:00
error = block_read_full_page ( page , gfs2_get_block ) ;
2006-01-16 19:50:04 +03:00
} else
error = jdata_readpage ( ip , page ) ;
if ( unlikely ( test_bit ( SDF_SHUTDOWN , & sdp - > sd_flags ) ) )
error = - EIO ;
return error ;
}
/**
* gfs2_prepare_write - Prepare to write a page to a file
* @ file : The file to write to
* @ page : The page which is to be prepared for writing
* @ from : From ( byte range within page )
* @ to : To ( byte range within page )
*
* Returns : errno
*/
static int gfs2_prepare_write ( struct file * file , struct page * page ,
unsigned from , unsigned to )
{
struct gfs2_inode * ip = get_v2ip ( page - > mapping - > host ) ;
struct gfs2_sbd * sdp = ip - > i_sbd ;
int error = 0 ;
atomic_inc ( & sdp - > sd_ops_address ) ;
if ( gfs2_assert_warn ( sdp , gfs2_glock_is_locked_by_me ( ip - > i_gl ) ) )
return - EOPNOTSUPP ;
if ( gfs2_is_stuffed ( ip ) ) {
uint64_t file_size ;
file_size = ( ( uint64_t ) page - > index < < PAGE_CACHE_SHIFT ) + to ;
if ( file_size > sdp - > sd_sb . sb_bsize -
sizeof ( struct gfs2_dinode ) ) {
error = gfs2_unstuff_dinode ( ip , gfs2_unstuffer_page ,
page ) ;
if ( ! error )
error = block_prepare_write ( page , from , to ,
2006-01-30 12:39:10 +03:00
gfs2_get_block ) ;
2006-01-16 19:50:04 +03:00
} else if ( ! PageUptodate ( page ) )
error = stuffed_readpage ( ip , page ) ;
} else
2006-01-30 12:39:10 +03:00
error = block_prepare_write ( page , from , to , gfs2_get_block ) ;
2006-01-16 19:50:04 +03:00
return error ;
}
/**
* gfs2_commit_write - Commit write to a file
* @ file : The file to write to
* @ page : The page containing the data
* @ from : From ( byte range within page )
* @ to : To ( byte range within page )
*
* Returns : errno
*/
static int gfs2_commit_write ( struct file * file , struct page * page ,
unsigned from , unsigned to )
{
struct inode * inode = page - > mapping - > host ;
struct gfs2_inode * ip = get_v2ip ( inode ) ;
struct gfs2_sbd * sdp = ip - > i_sbd ;
int error ;
atomic_inc ( & sdp - > sd_ops_address ) ;
if ( gfs2_is_stuffed ( ip ) ) {
struct buffer_head * dibh ;
uint64_t file_size ;
void * kaddr ;
file_size = ( ( uint64_t ) page - > index < < PAGE_CACHE_SHIFT ) + to ;
error = gfs2_meta_inode_buffer ( ip , & dibh ) ;
if ( error )
goto fail ;
2006-01-18 14:19:28 +03:00
gfs2_trans_add_bh ( ip - > i_gl , dibh , 1 ) ;
2006-01-16 19:50:04 +03:00
kaddr = kmap ( page ) ;
memcpy ( dibh - > b_data + sizeof ( struct gfs2_dinode ) + from ,
( char * ) kaddr + from ,
to - from ) ;
kunmap ( page ) ;
brelse ( dibh ) ;
SetPageUptodate ( page ) ;
if ( inode - > i_size < file_size )
i_size_write ( inode , file_size ) ;
} else {
if ( sdp - > sd_args . ar_data = = GFS2_DATA_ORDERED )
gfs2_page_add_databufs ( sdp , page , from , to ) ;
error = generic_commit_write ( file , page , from , to ) ;
if ( error )
goto fail ;
}
return 0 ;
fail :
ClearPageUptodate ( page ) ;
return error ;
}
/**
* gfs2_bmap - Block map function
* @ mapping : Address space info
* @ lblock : The block to map
*
* Returns : The disk address for the block or 0 on hole or error
*/
static sector_t gfs2_bmap ( struct address_space * mapping , sector_t lblock )
{
struct gfs2_inode * ip = get_v2ip ( mapping - > host ) ;
struct gfs2_holder i_gh ;
sector_t dblock = 0 ;
int error ;
atomic_inc ( & ip - > i_sbd - > sd_ops_address ) ;
error = gfs2_glock_nq_init ( ip - > i_gl , LM_ST_SHARED , LM_FLAG_ANY , & i_gh ) ;
if ( error )
return 0 ;
if ( ! gfs2_is_stuffed ( ip ) )
2006-01-30 12:39:10 +03:00
dblock = generic_block_bmap ( mapping , lblock , gfs2_get_block ) ;
2006-01-16 19:50:04 +03:00
gfs2_glock_dq_uninit ( & i_gh ) ;
return dblock ;
}
static void discard_buffer ( struct gfs2_sbd * sdp , struct buffer_head * bh )
{
2006-01-18 16:14:40 +03:00
struct gfs2_bufdata * bd ;
2006-01-16 19:50:04 +03:00
gfs2_log_lock ( sdp ) ;
2006-01-18 16:14:40 +03:00
bd = get_v2bd ( bh ) ;
if ( bd ) {
bd - > bd_bh = NULL ;
set_v2bd ( bh , NULL ) ;
2006-01-16 19:50:04 +03:00
gfs2_log_unlock ( sdp ) ;
brelse ( bh ) ;
} else
gfs2_log_unlock ( sdp ) ;
lock_buffer ( bh ) ;
clear_buffer_dirty ( bh ) ;
bh - > b_bdev = NULL ;
clear_buffer_mapped ( bh ) ;
clear_buffer_req ( bh ) ;
clear_buffer_new ( bh ) ;
clear_buffer_delay ( bh ) ;
unlock_buffer ( bh ) ;
}
static int gfs2_invalidatepage ( struct page * page , unsigned long offset )
{
struct gfs2_sbd * sdp = get_v2sdp ( page - > mapping - > host - > i_sb ) ;
struct buffer_head * head , * bh , * next ;
unsigned int curr_off = 0 ;
int ret = 1 ;
BUG_ON ( ! PageLocked ( page ) ) ;
if ( ! page_has_buffers ( page ) )
return 1 ;
bh = head = page_buffers ( page ) ;
do {
unsigned int next_off = curr_off + bh - > b_size ;
next = bh - > b_this_page ;
if ( offset < = curr_off )
discard_buffer ( sdp , bh ) ;
curr_off = next_off ;
bh = next ;
} while ( bh ! = head ) ;
if ( ! offset )
ret = try_to_release_page ( page , 0 ) ;
return ret ;
}
static ssize_t gfs2_direct_IO ( int rw , struct kiocb * iocb , const struct iovec * iov ,
loff_t offset , unsigned long nr_segs )
{
struct file * file = iocb - > ki_filp ;
struct inode * inode = file - > f_mapping - > host ;
struct gfs2_inode * ip = get_v2ip ( inode ) ;
struct gfs2_sbd * sdp = ip - > i_sbd ;
get_blocks_t * gb = get_blocks ;
atomic_inc ( & sdp - > sd_ops_address ) ;
if ( gfs2_assert_warn ( sdp , gfs2_glock_is_locked_by_me ( ip - > i_gl ) ) | |
gfs2_assert_warn ( sdp , ! gfs2_is_stuffed ( ip ) ) )
return - EINVAL ;
if ( rw = = WRITE & & ! get_transaction )
gb = get_blocks_noalloc ;
return blockdev_direct_IO ( rw , iocb , inode , inode - > i_sb - > s_bdev , iov ,
offset , nr_segs , gb , NULL ) ;
}
struct address_space_operations gfs2_file_aops = {
. writepage = gfs2_writepage ,
. readpage = gfs2_readpage ,
. sync_page = block_sync_page ,
. prepare_write = gfs2_prepare_write ,
. commit_write = gfs2_commit_write ,
. bmap = gfs2_bmap ,
. invalidatepage = gfs2_invalidatepage ,
. direct_IO = gfs2_direct_IO ,
} ;