Commit 66ce1c75 authored by Alexander Viro's avatar Alexander Viro Committed by Linus Torvalds

[PATCH] nfs ->follow_link() switched to new scheme

NFS takes some thought to switch to the new symlink scheme, because we
can't rely on the pagecache lookup to find the symlink page when freeing
it - the cache might have been invalidated in the meantime. 

So we hide the page information in the symlink data area itself,
by stealing the last pointer in the page used for the cache. That
way nfs_put_link() can just look up the page directly.
Signed-off-by: default avatarAl Viro <viro@parcelfarce.linux.org.uk>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 43fffc41
...@@ -23,18 +23,28 @@ ...@@ -23,18 +23,28 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <linux/namei.h>
/* Symlink caching in the page cache is even more simplistic /* Symlink caching in the page cache is even more simplistic
* and straight-forward than readdir caching. * and straight-forward than readdir caching.
*
* We place the length at the beginning of the page, in host byte order,
* followed by the string. The XDR response verification will NUL-terminate
* it. In the very end of page we store pointer to struct page in question,
* simplifying nfs_put_link() (if inode got invalidated we can't find the page
* to be freed via pagecache lookup).
*/ */
struct nfs_symlink {
u32 length;
char body[PAGE_SIZE - sizeof(u32) - sizeof(struct page *)];
struct page *page;
} __attribute__((packed)); /* this must be page-sized */
static int nfs_symlink_filler(struct inode *inode, struct page *page) static int nfs_symlink_filler(struct inode *inode, struct page *page)
{ {
int error; int error;
/* We place the length at the beginning of the page,
* in host byte order, followed by the string. The
* XDR response verification will NULL terminate it.
*/
lock_kernel(); lock_kernel();
error = NFS_PROTO(inode)->readlink(inode, page); error = NFS_PROTO(inode)->readlink(inode, page);
unlock_kernel(); unlock_kernel();
...@@ -50,61 +60,63 @@ static int nfs_symlink_filler(struct inode *inode, struct page *page) ...@@ -50,61 +60,63 @@ static int nfs_symlink_filler(struct inode *inode, struct page *page)
return -EIO; return -EIO;
} }
static char *nfs_getlink(struct inode *inode, struct page **ppage) static int nfs_follow_link(struct dentry *dentry, struct nameidata *nd)
{ {
struct inode *inode = dentry->d_inode;
struct page *page; struct page *page;
u32 *p; struct nfs_symlink *p;
void *err = ERR_PTR(nfs_revalidate_inode(NFS_SERVER(inode), inode));
page = ERR_PTR(nfs_revalidate_inode(NFS_SERVER(inode), inode)); if (err)
if (page)
goto read_failed; goto read_failed;
page = read_cache_page(&inode->i_data, 0, page = read_cache_page(&inode->i_data, 0,
(filler_t *)nfs_symlink_filler, inode); (filler_t *)nfs_symlink_filler, inode);
if (IS_ERR(page)) if (IS_ERR(page)) {
err = page;
goto read_failed; goto read_failed;
if (!PageUptodate(page)) }
if (!PageUptodate(page)) {
err = ERR_PTR(-EIO);
goto getlink_read_error; goto getlink_read_error;
*ppage = page; }
p = kmap(page); p = kmap(page);
return (char*)(p+1); if (p->length > sizeof(p->body) - 1)
goto too_long;
p->page = page;
nd_set_link(nd, p->body);
return 0;
too_long:
err = ERR_PTR(-ENAMETOOLONG);
kunmap(page);
getlink_read_error: getlink_read_error:
page_cache_release(page); page_cache_release(page);
page = ERR_PTR(-EIO);
read_failed: read_failed:
return (char*)page; nd_set_link(nd, err);
return 0;
} }
static int nfs_readlink(struct dentry *dentry, char __user *buffer, int buflen) static void nfs_put_link(struct dentry *dentry, struct nameidata *nd)
{ {
struct inode *inode = dentry->d_inode; char *s = nd_get_link(nd);
struct page *page = NULL; if (!IS_ERR(s)) {
int res = vfs_readlink(dentry,buffer,buflen,nfs_getlink(inode,&page)); struct nfs_symlink *p;
if (page) { struct page *page;
kunmap(page);
page_cache_release(page); p = container_of(s, struct nfs_symlink, body[0]);
} page = p->page;
return res;
}
static int nfs_follow_link(struct dentry *dentry, struct nameidata *nd)
{
struct inode *inode = dentry->d_inode;
struct page *page = NULL;
int res = vfs_follow_link(nd, nfs_getlink(inode,&page));
if (page) {
kunmap(page); kunmap(page);
page_cache_release(page); page_cache_release(page);
} }
return res;
} }
/* /*
* symlinks can't do much... * symlinks can't do much...
*/ */
struct inode_operations nfs_symlink_inode_operations = { struct inode_operations nfs_symlink_inode_operations = {
.readlink = nfs_readlink, .readlink = generic_readlink,
.follow_link = nfs_follow_link, .follow_link = nfs_follow_link,
.put_link = nfs_put_link,
.getattr = nfs_getattr, .getattr = nfs_getattr,
.setattr = nfs_setattr, .setattr = nfs_setattr,
}; };
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment