15b4650e55
Cannot assume writes will fully complete, so this conversion goes the easy way and always brings the page uptodate before the write. [dhowells@redhat.com: style tweaks] Signed-off-by: Nick Piggin <npiggin@suse.de> Acked-by: David Howells <dhowells@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
297 lines
6.4 KiB
C
297 lines
6.4 KiB
C
/* AFS filesystem file handling
|
|
*
|
|
* Copyright (C) 2002, 2007 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/writeback.h>
|
|
#include "internal.h"
|
|
|
|
static int afs_readpage(struct file *file, struct page *page);
|
|
static void afs_invalidatepage(struct page *page, unsigned long offset);
|
|
static int afs_releasepage(struct page *page, gfp_t gfp_flags);
|
|
static int afs_launder_page(struct page *page);
|
|
|
|
const struct file_operations afs_file_operations = {
|
|
.open = afs_open,
|
|
.release = afs_release,
|
|
.llseek = generic_file_llseek,
|
|
.read = do_sync_read,
|
|
.write = do_sync_write,
|
|
.aio_read = generic_file_aio_read,
|
|
.aio_write = afs_file_write,
|
|
.mmap = generic_file_readonly_mmap,
|
|
.splice_read = generic_file_splice_read,
|
|
.fsync = afs_fsync,
|
|
.lock = afs_lock,
|
|
.flock = afs_flock,
|
|
};
|
|
|
|
const struct inode_operations afs_file_inode_operations = {
|
|
.getattr = afs_getattr,
|
|
.setattr = afs_setattr,
|
|
.permission = afs_permission,
|
|
};
|
|
|
|
const struct address_space_operations afs_fs_aops = {
|
|
.readpage = afs_readpage,
|
|
.set_page_dirty = afs_set_page_dirty,
|
|
.launder_page = afs_launder_page,
|
|
.releasepage = afs_releasepage,
|
|
.invalidatepage = afs_invalidatepage,
|
|
.write_begin = afs_write_begin,
|
|
.write_end = afs_write_end,
|
|
.writepage = afs_writepage,
|
|
.writepages = afs_writepages,
|
|
};
|
|
|
|
/*
|
|
* open an AFS file or directory and attach a key to it
|
|
*/
|
|
int afs_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct afs_vnode *vnode = AFS_FS_I(inode);
|
|
struct key *key;
|
|
int ret;
|
|
|
|
_enter("{%x:%u},", vnode->fid.vid, vnode->fid.vnode);
|
|
|
|
key = afs_request_key(vnode->volume->cell);
|
|
if (IS_ERR(key)) {
|
|
_leave(" = %ld [key]", PTR_ERR(key));
|
|
return PTR_ERR(key);
|
|
}
|
|
|
|
ret = afs_validate(vnode, key);
|
|
if (ret < 0) {
|
|
_leave(" = %d [val]", ret);
|
|
return ret;
|
|
}
|
|
|
|
file->private_data = key;
|
|
_leave(" = 0");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* release an AFS file or directory and discard its key
|
|
*/
|
|
int afs_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct afs_vnode *vnode = AFS_FS_I(inode);
|
|
|
|
_enter("{%x:%u},", vnode->fid.vid, vnode->fid.vnode);
|
|
|
|
key_put(file->private_data);
|
|
_leave(" = 0");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* deal with notification that a page was read from the cache
|
|
*/
|
|
#ifdef AFS_CACHING_SUPPORT
|
|
static void afs_readpage_read_complete(void *cookie_data,
|
|
struct page *page,
|
|
void *data,
|
|
int error)
|
|
{
|
|
_enter("%p,%p,%p,%d", cookie_data, page, data, error);
|
|
|
|
if (error)
|
|
SetPageError(page);
|
|
else
|
|
SetPageUptodate(page);
|
|
unlock_page(page);
|
|
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* deal with notification that a page was written to the cache
|
|
*/
|
|
#ifdef AFS_CACHING_SUPPORT
|
|
static void afs_readpage_write_complete(void *cookie_data,
|
|
struct page *page,
|
|
void *data,
|
|
int error)
|
|
{
|
|
_enter("%p,%p,%p,%d", cookie_data, page, data, error);
|
|
|
|
unlock_page(page);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* AFS read page from file, directory or symlink
|
|
*/
|
|
static int afs_readpage(struct file *file, struct page *page)
|
|
{
|
|
struct afs_vnode *vnode;
|
|
struct inode *inode;
|
|
struct key *key;
|
|
size_t len;
|
|
off_t offset;
|
|
int ret;
|
|
|
|
inode = page->mapping->host;
|
|
|
|
ASSERT(file != NULL);
|
|
key = file->private_data;
|
|
ASSERT(key != NULL);
|
|
|
|
_enter("{%x},{%lu},{%lu}", key_serial(key), inode->i_ino, page->index);
|
|
|
|
vnode = AFS_FS_I(inode);
|
|
|
|
BUG_ON(!PageLocked(page));
|
|
|
|
ret = -ESTALE;
|
|
if (test_bit(AFS_VNODE_DELETED, &vnode->flags))
|
|
goto error;
|
|
|
|
#ifdef AFS_CACHING_SUPPORT
|
|
/* is it cached? */
|
|
ret = cachefs_read_or_alloc_page(vnode->cache,
|
|
page,
|
|
afs_file_readpage_read_complete,
|
|
NULL,
|
|
GFP_KERNEL);
|
|
#else
|
|
ret = -ENOBUFS;
|
|
#endif
|
|
|
|
switch (ret) {
|
|
/* read BIO submitted and wb-journal entry found */
|
|
case 1:
|
|
BUG(); // TODO - handle wb-journal match
|
|
|
|
/* read BIO submitted (page in cache) */
|
|
case 0:
|
|
break;
|
|
|
|
/* no page available in cache */
|
|
case -ENOBUFS:
|
|
case -ENODATA:
|
|
default:
|
|
offset = page->index << PAGE_CACHE_SHIFT;
|
|
len = min_t(size_t, i_size_read(inode) - offset, PAGE_SIZE);
|
|
|
|
/* read the contents of the file from the server into the
|
|
* page */
|
|
ret = afs_vnode_fetch_data(vnode, key, offset, len, page);
|
|
if (ret < 0) {
|
|
if (ret == -ENOENT) {
|
|
_debug("got NOENT from server"
|
|
" - marking file deleted and stale");
|
|
set_bit(AFS_VNODE_DELETED, &vnode->flags);
|
|
ret = -ESTALE;
|
|
}
|
|
#ifdef AFS_CACHING_SUPPORT
|
|
cachefs_uncache_page(vnode->cache, page);
|
|
#endif
|
|
goto error;
|
|
}
|
|
|
|
SetPageUptodate(page);
|
|
|
|
#ifdef AFS_CACHING_SUPPORT
|
|
if (cachefs_write_page(vnode->cache,
|
|
page,
|
|
afs_file_readpage_write_complete,
|
|
NULL,
|
|
GFP_KERNEL) != 0
|
|
) {
|
|
cachefs_uncache_page(vnode->cache, page);
|
|
unlock_page(page);
|
|
}
|
|
#else
|
|
unlock_page(page);
|
|
#endif
|
|
}
|
|
|
|
_leave(" = 0");
|
|
return 0;
|
|
|
|
error:
|
|
SetPageError(page);
|
|
unlock_page(page);
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* invalidate part or all of a page
|
|
*/
|
|
static void afs_invalidatepage(struct page *page, unsigned long offset)
|
|
{
|
|
int ret = 1;
|
|
|
|
_enter("{%lu},%lu", page->index, offset);
|
|
|
|
BUG_ON(!PageLocked(page));
|
|
|
|
if (PagePrivate(page)) {
|
|
/* We release buffers only if the entire page is being
|
|
* invalidated.
|
|
* The get_block cached value has been unconditionally
|
|
* invalidated, so real IO is not possible anymore.
|
|
*/
|
|
if (offset == 0) {
|
|
BUG_ON(!PageLocked(page));
|
|
|
|
ret = 0;
|
|
if (!PageWriteback(page))
|
|
ret = page->mapping->a_ops->releasepage(page,
|
|
0);
|
|
/* possibly should BUG_ON(!ret); - neilb */
|
|
}
|
|
}
|
|
|
|
_leave(" = %d", ret);
|
|
}
|
|
|
|
/*
|
|
* write back a dirty page
|
|
*/
|
|
static int afs_launder_page(struct page *page)
|
|
{
|
|
_enter("{%lu}", page->index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* release a page and cleanup its private data
|
|
*/
|
|
static int afs_releasepage(struct page *page, gfp_t gfp_flags)
|
|
{
|
|
struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
|
|
struct afs_writeback *wb;
|
|
|
|
_enter("{{%x:%u}[%lu],%lx},%x",
|
|
vnode->fid.vid, vnode->fid.vnode, page->index, page->flags,
|
|
gfp_flags);
|
|
|
|
if (PagePrivate(page)) {
|
|
wb = (struct afs_writeback *) page_private(page);
|
|
ASSERT(wb != NULL);
|
|
set_page_private(page, 0);
|
|
ClearPagePrivate(page);
|
|
afs_put_writeback(wb);
|
|
}
|
|
|
|
_leave(" = 0");
|
|
return 0;
|
|
}
|