Ok, here is my first attempt at reiserfs specific functions for this kind
of thing. It includes a writepage that can update packed tails in place,
and a reiserfs_truncate that locks the new tail page of the file during a
truncate. This locked page is either used for packing a 4k block into a
tail, or zeroing the truncated bytes in the last page.
Mostly I'm sending it so people can throw the clue stick at me if I'm
breaking any rules. Plus, seeing one set of FS specific needs might help
us make a generic version.
I'm not including the worker functions that do the truncate or the tail
searching/modification. These are just the wrappers that interface with
the page cache.
-chris
static int reiserfs_write_full_page(struct page *page) {
struct inode *inode = (struct inode *)page->mapping->host ;
unsigned long end_index = inode->i_size >> PAGE_CACHE_SHIFT ;
unsigned last_offset = PAGE_CACHE_SIZE;
int error = 0;
unsigned long block ;
unsigned cur_offset = 0 ;
struct buffer_head *head, *bh ;
int partial = 0 ;
if (!page->buffers) {
block_prepare_write(page, 0, 0, NULL) ;
kunmap(page) ;
}
/* last page in the file */
if (page->index >= end_index) {
last_offset = inode->i_size & (PAGE_CACHE_SIZE - 1) ;
/* no file contents in this page */
if (page->index >= end_index + 1 || !last_offset) {
return -EIO ;
}
memset((char *)kmap(page)+last_offset, 0, PAGE_CACHE_SIZE-last_offset) ;
flush_dcache_page(page) ;
kunmap(page) ;
}
head = page->buffers ;
bh = head ;
block = page->index << (PAGE_CACHE_SHIFT - inode->i_sb->s_blocksize_bits) ;
do {
/* if this offset in the page is outside the file */
if (cur_offset >= last_offset) {
if (!buffer_uptodate(bh))
partial = 1 ;
} else {
/* this end_io handler is exactly the same as end_buffer_io_sync */
bh->b_end_io = reiserfs_journal_end_io ;
/* buffer mapped to an unformatted node */
if (buffer_mapped(bh) && bh->b_blocknr != 0) {
mark_buffer_dirty(bh) ;
} else {
/* buffer not mapped yet, or points to a direct item.
**
** map_and_dirty_block is just a short version of
** reiserfs_get_block. It calls mark_buffer_dirty
** for unformatted nodes, or changes and logs the direct item
** It never tries to read in blocks, so if things aren't
** already up to date, something is wrong.
*/
if ((error = map_and_dirty_block(inode, bh, block))) {
goto fail ;
}
}
}
bh = bh->b_this_page ;
cur_offset += bh->b_size ;
block++ ;
} while(bh != head) ;
if (!partial)
SetPageUptodate(page) ;
return 0 ;
fail:
ClearPageUptodate(page) ;
return error ;
}
/*
** finds the tail page in the page cache,
** reads the last block in.
**
** On success, page_result is set to a locked, pinned page, and bh_result
** is set to an up to date buffer for the last block in the file. returns 0.
**
** tail conversion is not done, so bh_result might not be valid for writing
** check buffer_mapped(bh_result) and bh_result->b_blocknr != 0 before
** trying to write the block.
**
** on failure, nonzero is returned, page_result and bh_result are untouched.
*/
static int grab_tail_page(struct inode *p_s_inode,
struct page **page_result,
struct buffer_head **bh_result) {
/* we want the page with the last byte in the file,
** not the page that will hold the next byte for appending
*/
unsigned long index = (p_s_inode->i_size-1) >> PAGE_CACHE_SHIFT ;
unsigned long pos = 0 ;
unsigned long start = 0 ;
unsigned long blocksize = p_s_inode->i_sb->s_blocksize ;
unsigned long offset = (p_s_inode->i_size) & (PAGE_CACHE_SIZE - 1) ;
struct buffer_head *bh ;
struct buffer_head *head ;
struct page * page ;
int error ;
/* we know that we are only called with inode->i_size > 0.
** we also know that a file tail can never be as big as a block
** If i_size % blocksize == 0, our file is currently block aligned
** and it won't need converting or zeroing after a truncate.
*/
if ((offset & (blocksize - 1)) == 0) {
return -ENOENT ;
}
page = grab_cache_page(p_s_inode->i_mapping, index) ;
error = PTR_ERR(page) ;
if (IS_ERR(page)) {
goto out ;
}
/* start within the page of the last block in the file */
start = (offset / blocksize) * blocksize ;
error = block_prepare_write(page, start, offset,
reiserfs_get_block_create_0) ;
kunmap(page) ; /* mapped by block_prepare_write */
if (error)
goto unlock ;
head = page->buffers ;
bh = head;
do {
if (pos >= start) {
break ;
}
bh = bh->b_this_page ;
pos += blocksize ;
} while(bh != head) ;
if (!buffer_uptodate(bh)) {
/* note, this should never happen, prepare_write should
** be taking care of this for us. If the buffer isn't up to date,
** I've screwed up the code to find the buffer, or the code to
** call prepare_write
*/
reiserfs_warning("clm-6000: error reading block %lu on dev %s\n",
bh->b_blocknr, kdevname(bh->b_dev)) ;
error = -EIO ;
goto unlock ;
}
*bh_result = bh ;
*page_result = page ;
out:
return error ;
unlock:
UnlockPage(page) ;
page_cache_release(page) ;
return error ;
}
/*
** vfs version of truncate file. Must NOT be called with
** a transaction already started.
*/
void reiserfs_truncate_file(struct inode *p_s_inode) {
struct reiserfs_transaction_handle th ;
int windex ;
/* we want the offset for the first byte after the end of the file */
unsigned long offset = p_s_inode->i_size & (PAGE_CACHE_SIZE - 1) ;
unsigned blocksize = p_s_inode->i_sb->s_blocksize ;
unsigned length ;
struct page *page = NULL ;
int error ;
struct buffer_head *bh = NULL ;
if (p_s_inode->i_size > 0) {
if ((error = grab_tail_page(p_s_inode, &page, &bh))) {
// -ENOENT means we truncated past the end of the file,
// and get_block_create_0 could not find a block to read in,
// which is ok.
if (error != -ENOENT)
reiserfs_warning("clm-6001: grab_tail_page failed %d\n", error);
page = NULL ;
bh = NULL ;
}
}
/* so, if page != NULL, we have a buffer head for the offset at
** the end of the file. if the bh is mapped, and bh->b_blocknr != 0,
** then we have an unformatted node. Otherwise, we have a direct item,
** and no zeroing is required. We zero after the truncate, because the
** truncate might pack the item anyway (it will unmap bh if it packs).
*/
/* some transaction setup steps removed for clarity */
reiserfs_do_truncate (&th, p_s_inode, page, 1/*update timestamps*/) ;
if (page && buffer_mapped(bh) && bh->b_blocknr != 0) {
length = offset & (blocksize - 1) ;
/* if we are not on a block boundary */
if (length) {
length = blocksize - length ;
memset((char *)kmap(page) + offset, 0, length) ;
flush_dcache_page(page) ;
kunmap(page) ;
mark_buffer_dirty(bh) ;
}
}
if (page) {
UnlockPage(page) ;
page_cache_release(page) ;
}
return ;
}