|
@@ -505,13 +505,13 @@ struct nameidata {
|
|
|
int total_link_count;
|
|
|
struct saved {
|
|
|
struct path link;
|
|
|
- void *cookie;
|
|
|
+ struct delayed_call done;
|
|
|
const char *name;
|
|
|
- struct inode *inode;
|
|
|
unsigned seq;
|
|
|
} *stack, internal[EMBEDDED_LEVELS];
|
|
|
struct filename *name;
|
|
|
struct nameidata *saved;
|
|
|
+ struct inode *link_inode;
|
|
|
unsigned root_seq;
|
|
|
int dfd;
|
|
|
};
|
|
@@ -592,11 +592,8 @@ static void drop_links(struct nameidata *nd)
|
|
|
int i = nd->depth;
|
|
|
while (i--) {
|
|
|
struct saved *last = nd->stack + i;
|
|
|
- struct inode *inode = last->inode;
|
|
|
- if (last->cookie && inode->i_op->put_link) {
|
|
|
- inode->i_op->put_link(inode, last->cookie);
|
|
|
- last->cookie = NULL;
|
|
|
- }
|
|
|
+ do_delayed_call(&last->done);
|
|
|
+ clear_delayed_call(&last->done);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -842,7 +839,7 @@ static inline void path_to_nameidata(const struct path *path,
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Helper to directly jump to a known parsed path from ->follow_link,
|
|
|
+ * Helper to directly jump to a known parsed path from ->get_link,
|
|
|
* caller must have taken a reference to path beforehand.
|
|
|
*/
|
|
|
void nd_jump_link(struct path *path)
|
|
@@ -858,9 +855,7 @@ void nd_jump_link(struct path *path)
|
|
|
static inline void put_link(struct nameidata *nd)
|
|
|
{
|
|
|
struct saved *last = nd->stack + --nd->depth;
|
|
|
- struct inode *inode = last->inode;
|
|
|
- if (last->cookie && inode->i_op->put_link)
|
|
|
- inode->i_op->put_link(inode, last->cookie);
|
|
|
+ do_delayed_call(&last->done);
|
|
|
if (!(nd->flags & LOOKUP_RCU))
|
|
|
path_put(&last->link);
|
|
|
}
|
|
@@ -892,7 +887,7 @@ static inline int may_follow_link(struct nameidata *nd)
|
|
|
return 0;
|
|
|
|
|
|
/* Allowed if owner and follower match. */
|
|
|
- inode = nd->stack[0].inode;
|
|
|
+ inode = nd->link_inode;
|
|
|
if (uid_eq(current_cred()->fsuid, inode->i_uid))
|
|
|
return 0;
|
|
|
|
|
@@ -983,7 +978,7 @@ const char *get_link(struct nameidata *nd)
|
|
|
{
|
|
|
struct saved *last = nd->stack + nd->depth - 1;
|
|
|
struct dentry *dentry = last->link.dentry;
|
|
|
- struct inode *inode = last->inode;
|
|
|
+ struct inode *inode = nd->link_inode;
|
|
|
int error;
|
|
|
const char *res;
|
|
|
|
|
@@ -1004,15 +999,21 @@ const char *get_link(struct nameidata *nd)
|
|
|
nd->last_type = LAST_BIND;
|
|
|
res = inode->i_link;
|
|
|
if (!res) {
|
|
|
+ const char * (*get)(struct dentry *, struct inode *,
|
|
|
+ struct delayed_call *);
|
|
|
+ get = inode->i_op->get_link;
|
|
|
if (nd->flags & LOOKUP_RCU) {
|
|
|
- if (unlikely(unlazy_walk(nd, NULL, 0)))
|
|
|
- return ERR_PTR(-ECHILD);
|
|
|
+ res = get(NULL, inode, &last->done);
|
|
|
+ if (res == ERR_PTR(-ECHILD)) {
|
|
|
+ if (unlikely(unlazy_walk(nd, NULL, 0)))
|
|
|
+ return ERR_PTR(-ECHILD);
|
|
|
+ res = get(dentry, inode, &last->done);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ res = get(dentry, inode, &last->done);
|
|
|
}
|
|
|
- res = inode->i_op->follow_link(dentry, &last->cookie);
|
|
|
- if (IS_ERR_OR_NULL(res)) {
|
|
|
- last->cookie = NULL;
|
|
|
+ if (IS_ERR_OR_NULL(res))
|
|
|
return res;
|
|
|
- }
|
|
|
}
|
|
|
if (*res == '/') {
|
|
|
if (nd->flags & LOOKUP_RCU) {
|
|
@@ -1691,8 +1692,8 @@ static int pick_link(struct nameidata *nd, struct path *link,
|
|
|
|
|
|
last = nd->stack + nd->depth++;
|
|
|
last->link = *link;
|
|
|
- last->cookie = NULL;
|
|
|
- last->inode = inode;
|
|
|
+ clear_delayed_call(&last->done);
|
|
|
+ nd->link_inode = inode;
|
|
|
last->seq = seq;
|
|
|
return 1;
|
|
|
}
|
|
@@ -4495,72 +4496,73 @@ EXPORT_SYMBOL(readlink_copy);
|
|
|
|
|
|
/*
|
|
|
* A helper for ->readlink(). This should be used *ONLY* for symlinks that
|
|
|
- * have ->follow_link() touching nd only in nd_set_link(). Using (or not
|
|
|
- * using) it for any given inode is up to filesystem.
|
|
|
+ * have ->get_link() not calling nd_jump_link(). Using (or not using) it
|
|
|
+ * for any given inode is up to filesystem.
|
|
|
*/
|
|
|
int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
|
|
|
{
|
|
|
- void *cookie;
|
|
|
+ DEFINE_DELAYED_CALL(done);
|
|
|
struct inode *inode = d_inode(dentry);
|
|
|
const char *link = inode->i_link;
|
|
|
int res;
|
|
|
|
|
|
if (!link) {
|
|
|
- link = inode->i_op->follow_link(dentry, &cookie);
|
|
|
+ link = inode->i_op->get_link(dentry, inode, &done);
|
|
|
if (IS_ERR(link))
|
|
|
return PTR_ERR(link);
|
|
|
}
|
|
|
res = readlink_copy(buffer, buflen, link);
|
|
|
- if (inode->i_op->put_link)
|
|
|
- inode->i_op->put_link(inode, cookie);
|
|
|
+ do_delayed_call(&done);
|
|
|
return res;
|
|
|
}
|
|
|
EXPORT_SYMBOL(generic_readlink);
|
|
|
|
|
|
/* get the link contents into pagecache */
|
|
|
-static char *page_getlink(struct dentry * dentry, struct page **ppage)
|
|
|
+const char *page_get_link(struct dentry *dentry, struct inode *inode,
|
|
|
+ struct delayed_call *callback)
|
|
|
{
|
|
|
char *kaddr;
|
|
|
struct page *page;
|
|
|
- struct address_space *mapping = dentry->d_inode->i_mapping;
|
|
|
- page = read_mapping_page(mapping, 0, NULL);
|
|
|
- if (IS_ERR(page))
|
|
|
- return (char*)page;
|
|
|
- *ppage = page;
|
|
|
- kaddr = kmap(page);
|
|
|
- nd_terminate_link(kaddr, dentry->d_inode->i_size, PAGE_SIZE - 1);
|
|
|
+ struct address_space *mapping = inode->i_mapping;
|
|
|
+
|
|
|
+ if (!dentry) {
|
|
|
+ page = find_get_page(mapping, 0);
|
|
|
+ if (!page)
|
|
|
+ return ERR_PTR(-ECHILD);
|
|
|
+ if (!PageUptodate(page)) {
|
|
|
+ put_page(page);
|
|
|
+ return ERR_PTR(-ECHILD);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ page = read_mapping_page(mapping, 0, NULL);
|
|
|
+ if (IS_ERR(page))
|
|
|
+ return (char*)page;
|
|
|
+ }
|
|
|
+ set_delayed_call(callback, page_put_link, page);
|
|
|
+ BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
|
|
|
+ kaddr = page_address(page);
|
|
|
+ nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
|
|
|
return kaddr;
|
|
|
}
|
|
|
|
|
|
-int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
|
|
|
-{
|
|
|
- struct page *page = NULL;
|
|
|
- int res = readlink_copy(buffer, buflen, page_getlink(dentry, &page));
|
|
|
- if (page) {
|
|
|
- kunmap(page);
|
|
|
- page_cache_release(page);
|
|
|
- }
|
|
|
- return res;
|
|
|
-}
|
|
|
-EXPORT_SYMBOL(page_readlink);
|
|
|
+EXPORT_SYMBOL(page_get_link);
|
|
|
|
|
|
-const char *page_follow_link_light(struct dentry *dentry, void **cookie)
|
|
|
+void page_put_link(void *arg)
|
|
|
{
|
|
|
- struct page *page = NULL;
|
|
|
- char *res = page_getlink(dentry, &page);
|
|
|
- if (!IS_ERR(res))
|
|
|
- *cookie = page;
|
|
|
- return res;
|
|
|
+ put_page(arg);
|
|
|
}
|
|
|
-EXPORT_SYMBOL(page_follow_link_light);
|
|
|
+EXPORT_SYMBOL(page_put_link);
|
|
|
|
|
|
-void page_put_link(struct inode *unused, void *cookie)
|
|
|
+int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
|
|
|
{
|
|
|
- struct page *page = cookie;
|
|
|
- kunmap(page);
|
|
|
- page_cache_release(page);
|
|
|
+ DEFINE_DELAYED_CALL(done);
|
|
|
+ int res = readlink_copy(buffer, buflen,
|
|
|
+ page_get_link(dentry, d_inode(dentry),
|
|
|
+ &done));
|
|
|
+ do_delayed_call(&done);
|
|
|
+ return res;
|
|
|
}
|
|
|
-EXPORT_SYMBOL(page_put_link);
|
|
|
+EXPORT_SYMBOL(page_readlink);
|
|
|
|
|
|
/*
|
|
|
* The nofs argument instructs pagecache_write_begin to pass AOP_FLAG_NOFS
|
|
@@ -4571,7 +4573,6 @@ int __page_symlink(struct inode *inode, const char *symname, int len, int nofs)
|
|
|
struct page *page;
|
|
|
void *fsdata;
|
|
|
int err;
|
|
|
- char *kaddr;
|
|
|
unsigned int flags = AOP_FLAG_UNINTERRUPTIBLE;
|
|
|
if (nofs)
|
|
|
flags |= AOP_FLAG_NOFS;
|
|
@@ -4582,9 +4583,7 @@ retry:
|
|
|
if (err)
|
|
|
goto fail;
|
|
|
|
|
|
- kaddr = kmap_atomic(page);
|
|
|
- memcpy(kaddr, symname, len-1);
|
|
|
- kunmap_atomic(kaddr);
|
|
|
+ memcpy(page_address(page), symname, len-1);
|
|
|
|
|
|
err = pagecache_write_end(NULL, mapping, 0, len-1, len-1,
|
|
|
page, fsdata);
|
|
@@ -4609,7 +4608,6 @@ EXPORT_SYMBOL(page_symlink);
|
|
|
|
|
|
const struct inode_operations page_symlink_inode_operations = {
|
|
|
.readlink = generic_readlink,
|
|
|
- .follow_link = page_follow_link_light,
|
|
|
- .put_link = page_put_link,
|
|
|
+ .get_link = page_get_link,
|
|
|
};
|
|
|
EXPORT_SYMBOL(page_symlink_inode_operations);
|