file.c
/*
* fs/logfs/file.c - prepare_write, commit_write and friends
*
* As should be obvious for Linux kernel code, license is GPLv2
*
* Copyright (c) 2005-2008 Joern Engel <joern@logfs.org>
*/
#include "logfs.h"
#include <linux/sched.h>
#include <linux/writeback.h>
static int logfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
{
struct inode *inode = mapping->host;
struct page *page;
pgoff_t index = pos >> PAGE_SHIFT;
page = grab_cache_page_write_begin(mapping, index, flags);
if (!page)
return -ENOMEM;
*pagep = page;
if ((len == PAGE_SIZE) || PageUptodate(page))
return 0;
if ((pos & PAGE_MASK) >= i_size_read(inode)) {
unsigned start = pos & (PAGE_SIZE - 1);
unsigned end = start + len;
/* Reading beyond i_size is simple: memset to zero */
zero_user_segments(page, 0, start, end, PAGE_SIZE);
return 0;
}
return logfs_readpage_nolock(page);
}
static int logfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied, struct page *page,
void *fsdata)
{
struct inode *inode = mapping->host;
pgoff_t index = page->index;
unsigned start = pos & (PAGE_SIZE - 1);
unsigned end = start + copied;
int ret = 0;
BUG_ON(PAGE_SIZE != inode->i_sb->s_blocksize);
BUG_ON(page->index > I3_BLOCKS);
if (copied < len) {
/*
* Short write of a non-initialized paged. Just tell userspace
* to retry the entire page.
*/
if (!PageUptodate(page)) {
copied = 0;
goto out;
}
}
if (copied == 0)
goto out; /* FIXME: do we need to update inode? */
if (i_size_read(inode) < (index << PAGE_SHIFT) + end) {
i_size_write(inode, (index << PAGE_SHIFT) + end);
mark_inode_dirty_sync(inode);
}
SetPageUptodate(page);
if (!PageDirty(page)) {
if (!get_page_reserve(inode, page))
__set_page_dirty_nobuffers(page);
else
ret = logfs_write_buf(inode, page, WF_LOCK);
}
out:
unlock_page(page);
put_page(page);
return ret ? ret : copied;
}
int logfs_readpage(struct file *file, struct page *page)
{
int ret;
ret = logfs_readpage_nolock(page);
unlock_page(page);
return ret;
}
/* Clear the page's dirty flag in the radix tree. */
/* TODO: mucking with PageWriteback is silly. Add a generic function to clear
* the dirty bit from the radix tree for filesystems that don't have to wait
* for page writeback to finish (i.e. any compressing filesystem).
*/
static void clear_radix_tree_dirty(struct page *page)
{
BUG_ON(PagePrivate(page) || page->private);
set_page_writeback(page);
end_page_writeback(page);
}
static int __logfs_writepage(struct page *page)
{
struct inode *inode = page->mapping->host;
int err;
err = logfs_write_buf(inode, page, WF_LOCK);
if (err)
set_page_dirty(page);
else
clear_radix_tree_dirty(page);
unlock_page(page);
return err;
}
static int logfs_writepage(struct page *page, struct writeback_control *wbc)
{
struct inode *inode = page->mapping->host;
loff_t i_size = i_size_read(inode);
pgoff_t end_index = i_size >> PAGE_SHIFT;
unsigned offset;
u64 bix;
level_t level;
log_file("logfs_writepage(%lx, %lx, %p)\n", inode->i_ino, page->index,
page);
logfs_unpack_index(page->index, &bix, &level);
/* Indirect blocks are never truncated */
if (level != 0)
return __logfs_writepage(page);
/*
* TODO: everything below is a near-verbatim copy of nobh_writepage().
* The relevant bits should be factored out after logfs is merged.
*/
/* Is the page fully inside i_size? */
if (bix < end_index)
return __logfs_writepage(page);
/* Is the page fully outside i_size? (truncate in progress) */
offset = i_size & (PAGE_SIZE-1);
if (bix > end_index || offset == 0) {
unlock_page(page);
return 0; /* don't care */
}
/*
* The page straddles i_size. It must be zeroed out on each and every
* writepage invokation because it may be mmapped. "A file is mapped
* in multiples of the page size. For a file that is not a multiple of
* the page size, the remaining memory is zeroed when mapped, and
* writes to that region are not written out to the file."
*/
zero_user_segment(page, offset, PAGE_SIZE);
return __logfs_writepage(page);
}
static void logfs_invalidatepage(struct page *page, unsigned int offset,
unsigned int length)
{
struct logfs_block *block = logfs_block(page);
if (block->reserved_bytes) {
struct super_block *sb = page->mapping->host->i_sb;
struct logfs_super *super = logfs_super(sb);
super->s_dirty_pages -= block->reserved_bytes;
block->ops->free_block(sb, block);
BUG_ON(bitmap_weight(block->alias_map, LOGFS_BLOCK_FACTOR));
} else
move_page_to_btree(page);
BUG_ON(PagePrivate(page) || page->private);
}
static int logfs_releasepage(struct page *page, gfp_t only_xfs_uses_this)
{
return 0; /* None of these are easy to release */
}
long logfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(file);
struct logfs_inode *li = logfs_inode(inode);
unsigned int oldflags, flags;
int err;
switch (cmd) {
case FS_IOC_GETFLAGS:
flags = li->li_flags & LOGFS_FL_USER_VISIBLE;
return put_user(flags, (int __user *)arg);
case FS_IOC_SETFLAGS:
if (IS_RDONLY(inode))
return -EROFS;
if (!inode_owner_or_capable(inode))
return -EACCES;
err = get_user(flags, (int __user *)arg);
if (err)
return err;
inode_lock(inode);
oldflags = li->li_flags;
flags &= LOGFS_FL_USER_MODIFIABLE;
flags |= oldflags & ~LOGFS_FL_USER_MODIFIABLE;
li->li_flags = flags;
inode_unlock(inode);
inode->i_ctime = current_time(inode);
mark_inode_dirty_sync(inode);
return 0;
default:
return -ENOTTY;
}
}
int logfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
{
struct super_block *sb = file->f_mapping->host->i_sb;
struct inode *inode = file->f_mapping->host;
int ret;
ret = filemap_write_and_wait_range(inode->i_mapping, start, end);
if (ret)
return ret;
inode_lock(inode);
logfs_get_wblocks(sb, NULL, WF_LOCK);
logfs_write_anchor(sb);
logfs_put_wblocks(sb, NULL, WF_LOCK);
inode_unlock(inode);
return 0;
}
static int logfs_setattr(struct dentry *dentry, struct iattr *attr)
{
struct inode *inode = d_inode(dentry);
int err = 0;
err = setattr_prepare(dentry, attr);
if (err)
return err;
if (attr->ia_valid & ATTR_SIZE) {
err = logfs_truncate(inode, attr->ia_size);
if (err)
return err;
}
setattr_copy(inode, attr);
mark_inode_dirty(inode);
return 0;
}
const struct inode_operations logfs_reg_iops = {
.setattr = logfs_setattr,
};
const struct file_operations logfs_reg_fops = {
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.fsync = logfs_fsync,
.unlocked_ioctl = logfs_ioctl,
.llseek = generic_file_llseek,
.mmap = generic_file_readonly_mmap,
.open = generic_file_open,
};
const struct address_space_operations logfs_reg_aops = {
.invalidatepage = logfs_invalidatepage,
.readpage = logfs_readpage,
.releasepage = logfs_releasepage,
.set_page_dirty = __set_page_dirty_nobuffers,
.writepage = logfs_writepage,
.writepages = generic_writepages,
.write_begin = logfs_write_begin,
.write_end = logfs_write_end,
};