All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCHSET] ->follow_link() without dropping from RCU mode
@ 2015-11-17 22:57 Al Viro
  2015-11-17 23:00 ` [PATCH 01/10] switch befs long symlinks to page_symlink_operations Al Viro
                   ` (10 more replies)
  0 siblings, 11 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 22:57 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-fsdevel, Linus Torvalds, Neil Brown

	Right now we stay in RCU mode for fast symlink traversal.
However, anything trickier drops out of RCU mode - back in 4.2
the symlink-related pile had grown too large to add this on top
of everything else.  Below is an attempt to do that now.

Those who prefer to use git for review can find that series in
git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git work.symlinks
The branch is 4.4-rc1-based, only lightly tested so far (ltp and
xfstests pass without regressions, though).

What's in there:
1) switch befs long symlinks to page_symlink_operations
2) logfs: don't duplicate page_symlink_inode_operations
3) udf: don't duplicate page_symlink_inode_operations
4) ufs: get rid of ->setattr() for symlinks
	Simplifying things a bit by switching them to page_symlink_operations
where possible.

5) namei: page_getlink() and page_follow_link_light() are the same thing
	Get rid of some code duplication

6) [vfs] don't put symlink bodies in pagecache into highmem
	Bugfix for a long-standing mess.  For pagecache-based symlinks we end
up with kmap() of the body for the duration of traversal.  Which could take
a long time if we walk into e.g. slow automount along the way.  It's DoSable,
actually.  Not hard to fix - there's no reason to use GFP_HIGHUSER_MOVABLE
for those guys; GFP_USER serves just as well.

7) [vfs] replace ->follow_link() with new method that could stay in RCU mode
	Meat of the series - we switch to a new method (->get_link()) that
differs from ->follow_link() in getting dentry and inode separately; it
can be called in RCU mode (with NULL dentry) and it should bail out with
ERR_PTR(-ECHILD) if it needs non-RCU.  All instances converted, most of them
by making them bail out immediately in RCU mode.  Some are trivialy
RCU-safe, though; those do not bail out at all.

8) teach page_get_link() to work in RCU mode
9) teach shmem_get_link() to work in RCU mode
10) teach proc_self_get_link()/proc_thread_self_get_link() to work in RCU mode
	Teaching more instances to stay in RCU mode if they can.  (8) is
similar to what Neil had done back in March, except for the lack of kmap
mess to deal with.

	One thing I'd left out is NFS; something similar to Neil's patch from
the March patchset would probably do, though.

^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH 01/10] switch befs long symlinks to page_symlink_operations
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 02/10] logfs: don't duplicate page_symlink_inode_operations Al Viro
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

just give them the right ->readpage()...

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/befs/linuxvfs.c | 41 ++++++++++++++++++++++-------------------
 1 file changed, 22 insertions(+), 19 deletions(-)

diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c
index 46aedac..1c8b0dc 100644
--- a/fs/befs/linuxvfs.c
+++ b/fs/befs/linuxvfs.c
@@ -42,7 +42,7 @@ static struct inode *befs_iget(struct super_block *, unsigned long);
 static struct inode *befs_alloc_inode(struct super_block *sb);
 static void befs_destroy_inode(struct inode *inode);
 static void befs_destroy_inodecache(void);
-static const char *befs_follow_link(struct dentry *, void **);
+static int befs_symlink_readpage(struct file *, struct page *);
 static int befs_utf2nls(struct super_block *sb, const char *in, int in_len,
 			char **out, int *out_len);
 static int befs_nls2utf(struct super_block *sb, const char *in, int in_len,
@@ -79,10 +79,8 @@ static const struct address_space_operations befs_aops = {
 	.bmap		= befs_bmap,
 };
 
-static const struct inode_operations befs_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= befs_follow_link,
-	.put_link	= kfree_put_link,
+static const struct address_space_operations befs_symlink_aops = {
+	.readpage	= befs_symlink_readpage,
 };
 
 /* 
@@ -398,7 +396,8 @@ static struct inode *befs_iget(struct super_block *sb, unsigned long ino)
 		inode->i_fop = &befs_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
-			inode->i_op = &befs_symlink_inode_operations;
+			inode->i_op = &page_symlink_inode_operations;
+			inode->i_mapping->a_ops = &befs_symlink_aops;
 		} else {
 			inode->i_link = befs_ino->i_data.symlink;
 			inode->i_op = &simple_symlink_inode_operations;
@@ -463,31 +462,35 @@ befs_destroy_inodecache(void)
  * The data stream become link name. Unless the LONG_SYMLINK
  * flag is set.
  */
-static const char *
-befs_follow_link(struct dentry *dentry, void **cookie)
+static int befs_symlink_readpage(struct file *unused, struct page *page)
 {
-	struct super_block *sb = dentry->d_sb;
-	struct befs_inode_info *befs_ino = BEFS_I(d_inode(dentry));
+	struct inode *inode = page->mapping->host;
+	struct super_block *sb = inode->i_sb;
+	struct befs_inode_info *befs_ino = BEFS_I(inode);
 	befs_data_stream *data = &befs_ino->i_data.ds;
 	befs_off_t len = data->size;
-	char *link;
+	char *link = kmap(page);
 
-	if (len == 0) {
+	if (len == 0 || len > PAGE_SIZE) {
 		befs_error(sb, "Long symlink with illegal length");
-		return ERR_PTR(-EIO);
+		goto fail;
 	}
 	befs_debug(sb, "Follow long symlink");
 
-	link = kmalloc(len, GFP_NOFS);
-	if (!link)
-		return ERR_PTR(-ENOMEM);
 	if (befs_read_lsymlink(sb, data, link, len) != len) {
-		kfree(link);
 		befs_error(sb, "Failed to read entire long symlink");
-		return ERR_PTR(-EIO);
+		goto fail;
 	}
 	link[len - 1] = '\0';
-	return *cookie = link;
+	SetPageUptodate(page);
+	kunmap(page);
+	unlock_page(page);
+	return 0;
+fail:
+	SetPageError(page);
+	kunmap(page);
+	unlock_page(page);
+	return -EIO;
 }
 
 /*
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 02/10] logfs: don't duplicate page_symlink_inode_operations
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
  2015-11-17 23:00 ` [PATCH 01/10] switch befs long symlinks to page_symlink_operations Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 03/10] udf: " Al Viro
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/logfs/dir.c   | 8 +-------
 fs/logfs/inode.c | 2 +-
 fs/logfs/logfs.h | 1 -
 3 files changed, 2 insertions(+), 9 deletions(-)

diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c
index f9b45d4..99944a4 100644
--- a/fs/logfs/dir.c
+++ b/fs/logfs/dir.c
@@ -528,7 +528,7 @@ static int logfs_symlink(struct inode *dir, struct dentry *dentry,
 	if (IS_ERR(inode))
 		return PTR_ERR(inode);
 
-	inode->i_op = &logfs_symlink_iops;
+	inode->i_op = &page_symlink_inode_operations;
 	inode->i_mapping->a_ops = &logfs_reg_aops;
 
 	return __logfs_create(dir, dentry, inode, target, destlen);
@@ -776,12 +776,6 @@ fail:
 	return -EIO;
 }
 
-const struct inode_operations logfs_symlink_iops = {
-	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
-	.put_link	= page_put_link,
-};
-
 const struct inode_operations logfs_dir_iops = {
 	.create		= logfs_create,
 	.link		= logfs_link,
diff --git a/fs/logfs/inode.c b/fs/logfs/inode.c
index af49e2d..06baa92 100644
--- a/fs/logfs/inode.c
+++ b/fs/logfs/inode.c
@@ -64,7 +64,7 @@ static void logfs_inode_setops(struct inode *inode)
 		inode->i_mapping->a_ops = &logfs_reg_aops;
 		break;
 	case S_IFLNK:
-		inode->i_op = &logfs_symlink_iops;
+		inode->i_op = &page_symlink_inode_operations;
 		inode->i_mapping->a_ops = &logfs_reg_aops;
 		break;
 	case S_IFSOCK:	/* fall through */
diff --git a/fs/logfs/logfs.h b/fs/logfs/logfs.h
index 5f09376..209a26d 100644
--- a/fs/logfs/logfs.h
+++ b/fs/logfs/logfs.h
@@ -495,7 +495,6 @@ static inline int logfs_get_sb_mtd(struct logfs_super *s, int mtdnr)
 #endif
 
 /* dir.c */
-extern const struct inode_operations logfs_symlink_iops;
 extern const struct inode_operations logfs_dir_iops;
 extern const struct file_operations logfs_dir_fops;
 int logfs_replay_journal(struct super_block *sb);
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 03/10] udf: don't duplicate page_symlink_inode_operations
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
  2015-11-17 23:00 ` [PATCH 01/10] switch befs long symlinks to page_symlink_operations Al Viro
  2015-11-17 23:00 ` [PATCH 02/10] logfs: don't duplicate page_symlink_inode_operations Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 04/10] ufs: get rid of ->setattr() for symlinks Al Viro
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/udf/inode.c   | 2 +-
 fs/udf/namei.c   | 7 +------
 fs/udf/udfdecl.h | 1 -
 3 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index 8d0b3ad..8675c2b 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -1540,7 +1540,7 @@ reread:
 		break;
 	case ICBTAG_FILE_TYPE_SYMLINK:
 		inode->i_data.a_ops = &udf_symlink_aops;
-		inode->i_op = &udf_symlink_inode_operations;
+		inode->i_op = &page_symlink_inode_operations;
 		inode->i_mode = S_IFLNK | S_IRWXUGO;
 		break;
 	case ICBTAG_FILE_TYPE_MAIN:
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index c97b5a8..d0e6de1 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -921,7 +921,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
 	}
 
 	inode->i_data.a_ops = &udf_symlink_aops;
-	inode->i_op = &udf_symlink_inode_operations;
+	inode->i_op = &page_symlink_inode_operations;
 
 	if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
 		struct kernel_lb_addr eloc;
@@ -1344,8 +1344,3 @@ const struct inode_operations udf_dir_inode_operations = {
 	.rename				= udf_rename,
 	.tmpfile			= udf_tmpfile,
 };
-const struct inode_operations udf_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
-	.put_link	= page_put_link,
-};
diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h
index 47bb3f5..ce169b4 100644
--- a/fs/udf/udfdecl.h
+++ b/fs/udf/udfdecl.h
@@ -85,7 +85,6 @@ extern const struct inode_operations udf_dir_inode_operations;
 extern const struct file_operations udf_dir_operations;
 extern const struct inode_operations udf_file_inode_operations;
 extern const struct file_operations udf_file_operations;
-extern const struct inode_operations udf_symlink_inode_operations;
 extern const struct address_space_operations udf_aops;
 extern const struct address_space_operations udf_adinicb_aops;
 extern const struct address_space_operations udf_symlink_aops;
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 04/10] ufs: get rid of ->setattr() for symlinks
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (2 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 03/10] udf: " Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 05/10] namei: page_getlink() and page_follow_link_light() are the same thing Al Viro
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

It was to needed for a couple of months in 2010, until UFS
quota support got dropped.  Since then it's equivalent to
simple_setattr() (i.e. the default) for everything except the
regular files.  And dropping it there allows to convert all
UFS symlinks to {page,simple}_symlink_inode_operations, getting
rid of fs/ufs/symlink.c completely.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/ufs/Makefile  |  2 +-
 fs/ufs/inode.c   |  4 ++--
 fs/ufs/namei.c   |  4 ++--
 fs/ufs/symlink.c | 42 ------------------------------------------
 fs/ufs/ufs.h     |  4 ----
 5 files changed, 5 insertions(+), 51 deletions(-)
 delete mode 100644 fs/ufs/symlink.c

diff --git a/fs/ufs/Makefile b/fs/ufs/Makefile
index 392db25..ec4a6b4 100644
--- a/fs/ufs/Makefile
+++ b/fs/ufs/Makefile
@@ -5,5 +5,5 @@
 obj-$(CONFIG_UFS_FS) += ufs.o
 
 ufs-objs := balloc.o cylinder.o dir.o file.o ialloc.o inode.o \
-	    namei.o super.o symlink.o util.o
+	    namei.o super.o util.o
 ccflags-$(CONFIG_UFS_DEBUG)    += -DDEBUG
diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c
index a064cf44..737160a 100644
--- a/fs/ufs/inode.c
+++ b/fs/ufs/inode.c
@@ -528,11 +528,11 @@ static void ufs_set_inode_ops(struct inode *inode)
 		inode->i_mapping->a_ops = &ufs_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (!inode->i_blocks) {
-			inode->i_op = &ufs_fast_symlink_inode_operations;
 			inode->i_link = (char *)UFS_I(inode)->i_u1.i_symlink;
+			inode->i_op = &simple_symlink_inode_operations;
 		} else {
-			inode->i_op = &ufs_symlink_inode_operations;
 			inode->i_mapping->a_ops = &ufs_aops;
+			inode->i_op = &page_symlink_inode_operations;
 		}
 	} else
 		init_special_inode(inode, inode->i_mode,
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 4796655..24b0cbd 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -123,14 +123,14 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry,
 
 	if (l > UFS_SB(sb)->s_uspi->s_maxsymlinklen) {
 		/* slow symlink */
-		inode->i_op = &ufs_symlink_inode_operations;
+		inode->i_op = &page_symlink_inode_operations;
 		inode->i_mapping->a_ops = &ufs_aops;
 		err = page_symlink(inode, symname, l);
 		if (err)
 			goto out_fail;
 	} else {
 		/* fast symlink */
-		inode->i_op = &ufs_fast_symlink_inode_operations;
+		inode->i_op = &simple_symlink_inode_operations;
 		inode->i_link = (char *)UFS_I(inode)->i_u1.i_symlink;
 		memcpy(inode->i_link, symname, l);
 		inode->i_size = l-1;
diff --git a/fs/ufs/symlink.c b/fs/ufs/symlink.c
deleted file mode 100644
index 874480b..0000000
--- a/fs/ufs/symlink.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *  linux/fs/ufs/symlink.c
- *
- * Only fast symlinks left here - the rest is done by generic code. AV, 1999
- *
- * Copyright (C) 1998
- * Daniel Pirkl <daniel.pirkl@emai.cz>
- * Charles University, Faculty of Mathematics and Physics
- *
- *  from
- *
- *  linux/fs/ext2/symlink.c
- *
- * Copyright (C) 1992, 1993, 1994, 1995
- * Remy Card (card@masi.ibp.fr)
- * Laboratoire MASI - Institut Blaise Pascal
- * Universite Pierre et Marie Curie (Paris VI)
- *
- *  from
- *
- *  linux/fs/minix/symlink.c
- *
- *  Copyright (C) 1991, 1992  Linus Torvalds
- *
- *  ext2 symlink handling code
- */
-
-#include "ufs_fs.h"
-#include "ufs.h"
-
-const struct inode_operations ufs_fast_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
-	.setattr	= ufs_setattr,
-};
-
-const struct inode_operations ufs_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
-	.put_link	= page_put_link,
-	.setattr	= ufs_setattr,
-};
diff --git a/fs/ufs/ufs.h b/fs/ufs/ufs.h
index 7da4aca..c87f4c3 100644
--- a/fs/ufs/ufs.h
+++ b/fs/ufs/ufs.h
@@ -136,10 +136,6 @@ extern __printf(3, 4)
 void ufs_panic(struct super_block *, const char *, const char *, ...);
 void ufs_mark_sb_dirty(struct super_block *sb);
 
-/* symlink.c */
-extern const struct inode_operations ufs_fast_symlink_inode_operations;
-extern const struct inode_operations ufs_symlink_inode_operations;
-
 static inline struct ufs_sb_info *UFS_SB(struct super_block *sb)
 {
 	return sb->s_fs_info;
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 05/10] namei: page_getlink() and page_follow_link_light() are the same thing
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (3 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 04/10] ufs: get rid of ->setattr() for symlinks Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 06/10] [vfs] don't put symlink bodies in pagecache into highmem Al Viro
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/namei.c | 32 +++++++++++++-------------------
 1 file changed, 13 insertions(+), 19 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index d84d7c7..2bc5de3 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4519,7 +4519,7 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 EXPORT_SYMBOL(generic_readlink);
 
 /* get the link contents into pagecache */
-static char *page_getlink(struct dentry * dentry, struct page **ppage)
+static const char *page_getlink(struct dentry * dentry, void **cookie)
 {
 	char *kaddr;
 	struct page *page;
@@ -4527,31 +4527,15 @@ static char *page_getlink(struct dentry * dentry, struct page **ppage)
 	page = read_mapping_page(mapping, 0, NULL);
 	if (IS_ERR(page))
 		return (char*)page;
-	*ppage = page;
+	*cookie = page;
 	kaddr = kmap(page);
 	nd_terminate_link(kaddr, dentry->d_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);
-
 const char *page_follow_link_light(struct dentry *dentry, void **cookie)
 {
-	struct page *page = NULL;
-	char *res = page_getlink(dentry, &page);
-	if (!IS_ERR(res))
-		*cookie = page;
-	return res;
+	return page_getlink(dentry, cookie);
 }
 EXPORT_SYMBOL(page_follow_link_light);
 
@@ -4563,6 +4547,16 @@ void page_put_link(struct inode *unused, void *cookie)
 }
 EXPORT_SYMBOL(page_put_link);
 
+int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
+{
+	void *cookie = NULL;
+	int res = readlink_copy(buffer, buflen, page_getlink(dentry, &cookie));
+	if (cookie)
+		page_put_link(NULL, cookie);
+	return res;
+}
+EXPORT_SYMBOL(page_readlink);
+
 /*
  * The nofs argument instructs pagecache_write_begin to pass AOP_FLAG_NOFS
  */
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 06/10] [vfs] don't put symlink bodies in pagecache into highmem
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (4 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 05/10] namei: page_getlink() and page_follow_link_light() are the same thing Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-19 23:02   ` Dave Chinner
  2015-11-17 23:00 ` [PATCH 07/10] [vfs] replace ->follow_link() with new method that could stay in RCU mode Al Viro
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

kmap() in page_follow_link_light() needed to go - allowing to hold
an arbitrary number of kmaps for long is a great way to deadlocking
the system.

new helper (inode_nohigh(inode)) needs to be used for pagecache
symlinks inodes; done for all in-tree cases.  page_follow_link_light()
instrumented to yell about anything missed.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 Documentation/filesystems/porting |  5 +++++
 fs/affs/inode.c                   |  1 +
 fs/affs/namei.c                   |  1 +
 fs/affs/symlink.c                 |  4 +---
 fs/afs/inode.c                    |  1 +
 fs/befs/linuxvfs.c                |  5 ++---
 fs/btrfs/inode.c                  |  2 ++
 fs/coda/cnode.c                   |  1 +
 fs/coda/symlink.c                 |  4 +---
 fs/cramfs/inode.c                 |  1 +
 fs/efs/inode.c                    |  1 +
 fs/efs/symlink.c                  |  4 +---
 fs/exofs/inode.c                  |  1 +
 fs/exofs/namei.c                  |  1 +
 fs/ext2/inode.c                   |  1 +
 fs/ext2/namei.c                   |  1 +
 fs/ext4/inode.c                   |  1 +
 fs/ext4/namei.c                   |  1 +
 fs/ext4/symlink.c                 | 10 +++-------
 fs/f2fs/inode.c                   |  1 +
 fs/f2fs/namei.c                   |  5 ++---
 fs/freevxfs/vxfs_inode.c          |  1 +
 fs/hfsplus/inode.c                |  2 ++
 fs/hpfs/inode.c                   |  1 +
 fs/hpfs/namei.c                   |  5 ++---
 fs/hugetlbfs/inode.c              |  1 +
 fs/isofs/inode.c                  |  1 +
 fs/isofs/rock.c                   |  4 +---
 fs/jfs/inode.c                    |  1 +
 fs/jfs/namei.c                    |  1 +
 fs/logfs/dir.c                    |  1 +
 fs/logfs/inode.c                  |  1 +
 fs/minix/inode.c                  |  1 +
 fs/namei.c                        |  9 +++------
 fs/ncpfs/inode.c                  |  1 +
 fs/nfs/inode.c                    |  5 +++--
 fs/nfs/symlink.c                  |  2 +-
 fs/nilfs2/inode.c                 |  1 +
 fs/nilfs2/namei.c                 |  1 +
 fs/ocfs2/inode.c                  |  1 +
 fs/ocfs2/namei.c                  |  1 +
 fs/qnx4/inode.c                   |  1 +
 fs/qnx6/inode.c                   |  1 +
 fs/ramfs/inode.c                  |  1 +
 fs/reiserfs/inode.c               |  1 +
 fs/reiserfs/namei.c               |  1 +
 fs/romfs/super.c                  |  1 +
 fs/squashfs/inode.c               |  2 ++
 fs/sysv/inode.c                   |  1 +
 fs/udf/inode.c                    |  1 +
 fs/udf/namei.c                    |  1 +
 fs/udf/symlink.c                  |  4 +---
 fs/ufs/inode.c                    |  1 +
 fs/ufs/namei.c                    |  1 +
 include/linux/fs.h                |  4 ++++
 mm/shmem.c                        |  9 +++------
 56 files changed, 77 insertions(+), 46 deletions(-)

diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting
index f24d1b8..da398d87 100644
--- a/Documentation/filesystems/porting
+++ b/Documentation/filesystems/porting
@@ -504,3 +504,8 @@ in your dentry operations instead.
 [mandatory]
 	__fd_install() & fd_install() can now sleep. Callers should not
 	hold a spinlock	or other resources that do not allow a schedule.
+--
+[mandatory]
+	any symlink that might use page_follow_link_light/page_put_link() must
+	have inode_nohigh(inode) called before anything might start playing with
+	its pagecache.
diff --git a/fs/affs/inode.c b/fs/affs/inode.c
index 1734950..f3b8b92 100644
--- a/fs/affs/inode.c
+++ b/fs/affs/inode.c
@@ -140,6 +140,7 @@ struct inode *affs_iget(struct super_block *sb, unsigned long ino)
 		break;
 	case ST_SOFTLINK:
 		inode->i_mode |= S_IFLNK;
+		inode_nohigh(inode);
 		inode->i_op = &affs_symlink_inode_operations;
 		inode->i_data.a_ops = &affs_symlink_aops;
 		break;
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 181e05b..f88c768 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -344,6 +344,7 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 		return -ENOSPC;
 
 	inode->i_op = &affs_symlink_inode_operations;
+	inode_nohigh(inode);
 	inode->i_data.a_ops = &affs_symlink_aops;
 	inode->i_mode = S_IFLNK | 0777;
 	mode_to_prot(inode);
diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
index ea5b69a..e3f9dc3 100644
--- a/fs/affs/symlink.c
+++ b/fs/affs/symlink.c
@@ -14,7 +14,7 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
 {
 	struct buffer_head *bh;
 	struct inode *inode = page->mapping->host;
-	char *link = kmap(page);
+	char *link = page_address(page);
 	struct slink_front *lf;
 	int			 i, j;
 	char			 c;
@@ -57,12 +57,10 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
 	link[i] = '\0';
 	affs_brelse(bh);
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return -EIO;
 }
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index e06f5a2..04d4cef 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -56,6 +56,7 @@ static int afs_inode_map_status(struct afs_vnode *vnode, struct key *key)
 	case AFS_FTYPE_SYMLINK:
 		inode->i_mode	= S_IFLNK | vnode->status.mode;
 		inode->i_op	= &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		break;
 	default:
 		printk("kAFS: AFS vnode with undefined type\n");
diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c
index 1c8b0dc..b90e0bf 100644
--- a/fs/befs/linuxvfs.c
+++ b/fs/befs/linuxvfs.c
@@ -397,6 +397,7 @@ static struct inode *befs_iget(struct super_block *sb, unsigned long ino)
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 			inode->i_mapping->a_ops = &befs_symlink_aops;
 		} else {
 			inode->i_link = befs_ino->i_data.symlink;
@@ -469,7 +470,7 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
 	struct befs_inode_info *befs_ino = BEFS_I(inode);
 	befs_data_stream *data = &befs_ino->i_data.ds;
 	befs_off_t len = data->size;
-	char *link = kmap(page);
+	char *link = page_address(page);
 
 	if (len == 0 || len > PAGE_SIZE) {
 		befs_error(sb, "Long symlink with illegal length");
@@ -483,12 +484,10 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
 	}
 	link[len - 1] = '\0';
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return -EIO;
 }
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 994490d..cf917d7 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3774,6 +3774,7 @@ cache_acl:
 		break;
 	case S_IFLNK:
 		inode->i_op = &btrfs_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &btrfs_symlink_aops;
 		break;
 	default:
@@ -9727,6 +9728,7 @@ static int btrfs_symlink(struct inode *dir, struct dentry *dentry,
 	btrfs_free_path(path);
 
 	inode->i_op = &btrfs_symlink_inode_operations;
+	inode_nohigh(inode);
 	inode->i_mapping->a_ops = &btrfs_symlink_aops;
 	inode_set_bytes(inode, name_len);
 	btrfs_i_size_write(inode, name_len);
diff --git a/fs/coda/cnode.c b/fs/coda/cnode.c
index 7740b1c..e37d242 100644
--- a/fs/coda/cnode.c
+++ b/fs/coda/cnode.c
@@ -35,6 +35,7 @@ static void coda_fill_inode(struct inode *inode, struct coda_vattr *attr)
                 inode->i_fop = &coda_dir_operations;
         } else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &coda_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_data.a_ops = &coda_symlink_aops;
 		inode->i_mapping = &inode->i_data;
 	} else
diff --git a/fs/coda/symlink.c b/fs/coda/symlink.c
index ab94ef6..03736e2 100644
--- a/fs/coda/symlink.c
+++ b/fs/coda/symlink.c
@@ -26,7 +26,7 @@ static int coda_symlink_filler(struct file *file, struct page *page)
 	int error;
 	struct coda_inode_info *cii;
 	unsigned int len = PAGE_SIZE;
-	char *p = kmap(page);
+	char *p = page_address(page);
 
 	cii = ITOC(inode);
 
@@ -34,13 +34,11 @@ static int coda_symlink_filler(struct file *file, struct page *page)
 	if (error)
 		goto fail;
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return error;
 }
diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c
index 355c522..7fc43c0 100644
--- a/fs/cramfs/inode.c
+++ b/fs/cramfs/inode.c
@@ -100,6 +100,7 @@ static struct inode *get_cramfs_inode(struct super_block *sb,
 		break;
 	case S_IFLNK:
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_data.a_ops = &cramfs_aops;
 		break;
 	default:
diff --git a/fs/efs/inode.c b/fs/efs/inode.c
index 079d203..2a476c7 100644
--- a/fs/efs/inode.c
+++ b/fs/efs/inode.c
@@ -151,6 +151,7 @@ struct inode *efs_iget(struct super_block *super, unsigned long ino)
 			break;
 		case S_IFLNK:
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 			inode->i_data.a_ops = &efs_symlink_aops;
 			break;
 		case S_IFCHR:
diff --git a/fs/efs/symlink.c b/fs/efs/symlink.c
index 75117d0..4870cc8 100644
--- a/fs/efs/symlink.c
+++ b/fs/efs/symlink.c
@@ -13,7 +13,7 @@
 
 static int efs_symlink_readpage(struct file *file, struct page *page)
 {
-	char *link = kmap(page);
+	char *link = page_address(page);
 	struct buffer_head * bh;
 	struct inode * inode = page->mapping->host;
 	efs_block_t size = inode->i_size;
@@ -39,12 +39,10 @@ static int efs_symlink_readpage(struct file *file, struct page *page)
 	}
 	link[size] = '\0';
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return err;
 }
diff --git a/fs/exofs/inode.c b/fs/exofs/inode.c
index 73c64da..ce96073 100644
--- a/fs/exofs/inode.c
+++ b/fs/exofs/inode.c
@@ -1227,6 +1227,7 @@ struct inode *exofs_iget(struct super_block *sb, unsigned long ino)
 			inode->i_link = (char *)oi->i_data;
 		} else {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 			inode->i_mapping->a_ops = &exofs_aops;
 		}
 	} else {
diff --git a/fs/exofs/namei.c b/fs/exofs/namei.c
index 994e078..f173d31 100644
--- a/fs/exofs/namei.c
+++ b/fs/exofs/namei.c
@@ -111,6 +111,7 @@ static int exofs_symlink(struct inode *dir, struct dentry *dentry,
 	if (l > sizeof(oi->i_data)) {
 		/* slow symlink */
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &exofs_aops;
 		memset(oi->i_data, 0, sizeof(oi->i_data));
 
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 0aa9bf6..27a709a 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -1420,6 +1420,7 @@ struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
 				sizeof(ei->i_data) - 1);
 		} else {
 			inode->i_op = &ext2_symlink_inode_operations;
+			inode_nohigh(inode);
 			if (test_opt(inode->i_sb, NOBH))
 				inode->i_mapping->a_ops = &ext2_nobh_aops;
 			else
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index 3267a80d..5c77d41 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -183,6 +183,7 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry,
 	if (l > sizeof (EXT2_I(inode)->i_data)) {
 		/* slow symlink */
 		inode->i_op = &ext2_symlink_inode_operations;
+		inode_nohigh(inode);
 		if (test_opt(inode->i_sb, NOBH))
 			inode->i_mapping->a_ops = &ext2_nobh_aops;
 		else
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index ea433a7..44d3b95 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4283,6 +4283,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 			inode->i_op = &ext4_symlink_inode_operations;
 			ext4_set_aops(inode);
 		}
+		inode_nohigh(inode);
 	} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
 	      S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
 		inode->i_op = &ext4_special_inode_operations;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index a969ab3..656c223 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3132,6 +3132,7 @@ static int ext4_symlink(struct inode *dir,
 	if ((disk_link.len > EXT4_N_BLOCKS * 4)) {
 		if (!encryption_required)
 			inode->i_op = &ext4_symlink_inode_operations;
+		inode_nohigh(inode);
 		ext4_set_aops(inode);
 		/*
 		 * We cannot call page_symlink() with transaction started
diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
index abe2401..0e6dc44 100644
--- a/fs/ext4/symlink.c
+++ b/fs/ext4/symlink.c
@@ -45,7 +45,7 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
 		cpage = read_mapping_page(inode->i_mapping, 0, NULL);
 		if (IS_ERR(cpage))
 			return ERR_CAST(cpage);
-		caddr = kmap(cpage);
+		caddr = page_address(cpage);
 		caddr[size] = 0;
 	}
 
@@ -75,16 +75,12 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
 	/* Null-terminate the name */
 	if (res <= plen)
 		paddr[res] = '\0';
-	if (cpage) {
-		kunmap(cpage);
+	if (cpage)
 		page_cache_release(cpage);
-	}
 	return *cookie = paddr;
 errout:
-	if (cpage) {
-		kunmap(cpage);
+	if (cpage)
 		page_cache_release(cpage);
-	}
 	kfree(paddr);
 	return ERR_PTR(res);
 }
diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
index 97e20de..1188eef 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -202,6 +202,7 @@ make_now:
 			inode->i_op = &f2fs_encrypted_symlink_inode_operations;
 		else
 			inode->i_op = &f2fs_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &f2fs_dblock_aops;
 	} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
 			S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index 2c32110..1aea1c3 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -351,6 +351,7 @@ static int f2fs_symlink(struct inode *dir, struct dentry *dentry,
 		inode->i_op = &f2fs_encrypted_symlink_inode_operations;
 	else
 		inode->i_op = &f2fs_symlink_inode_operations;
+	inode_nohigh(inode);
 	inode->i_mapping->a_ops = &f2fs_dblock_aops;
 
 	f2fs_lock_op(sbi);
@@ -942,7 +943,7 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
 	cpage = read_mapping_page(inode->i_mapping, 0, NULL);
 	if (IS_ERR(cpage))
 		return ERR_CAST(cpage);
-	caddr = kmap(cpage);
+	caddr = page_address(cpage);
 	caddr[size] = 0;
 
 	/* Symlink is encrypted */
@@ -982,13 +983,11 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
 	/* Null-terminate the name */
 	paddr[res] = '\0';
 
-	kunmap(cpage);
 	page_cache_release(cpage);
 	return *cookie = paddr;
 errout:
 	kfree(cstr.name);
 	f2fs_fname_crypto_free_buffer(&pstr);
-	kunmap(cpage);
 	page_cache_release(cpage);
 	return ERR_PTR(res);
 }
diff --git a/fs/freevxfs/vxfs_inode.c b/fs/freevxfs/vxfs_inode.c
index ef73ed6..18f8a43 100644
--- a/fs/freevxfs/vxfs_inode.c
+++ b/fs/freevxfs/vxfs_inode.c
@@ -326,6 +326,7 @@ vxfs_iget(struct super_block *sbp, ino_t ino)
 	} else if (S_ISLNK(ip->i_mode)) {
 		if (!VXFS_ISIMMED(vip)) {
 			ip->i_op = &page_symlink_inode_operations;
+			inode_nohigh(ip);
 			ip->i_mapping->a_ops = &vxfs_aops;
 		} else {
 			ip->i_op = &simple_symlink_inode_operations;
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index 6dd107d..3a6d14b 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -403,6 +403,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, umode_t mode)
 	} else if (S_ISLNK(inode->i_mode)) {
 		sbi->file_count++;
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &hfsplus_aops;
 		hip->clump_blocks = 1;
 	} else
@@ -526,6 +527,7 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)
 			inode->i_mapping->a_ops = &hfsplus_aops;
 		} else if (S_ISLNK(inode->i_mode)) {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 			inode->i_mapping->a_ops = &hfsplus_aops;
 		} else {
 			init_special_inode(inode, inode->i_mode,
diff --git a/fs/hpfs/inode.c b/fs/hpfs/inode.c
index 933c737..0a9d741 100644
--- a/fs/hpfs/inode.c
+++ b/fs/hpfs/inode.c
@@ -77,6 +77,7 @@ void hpfs_read_inode(struct inode *i)
 			kfree(ea);
 			i->i_mode = S_IFLNK | 0777;
 			i->i_op = &page_symlink_inode_operations;
+			inode_nohigh(i);
 			i->i_data.a_ops = &hpfs_symlink_aops;
 			set_nlink(i, 1);
 			i->i_size = ea_size;
diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c
index ae4d5a1..2f90058 100644
--- a/fs/hpfs/namei.c
+++ b/fs/hpfs/namei.c
@@ -332,6 +332,7 @@ static int hpfs_symlink(struct inode *dir, struct dentry *dentry, const char *sy
 	result->i_blocks = 1;
 	set_nlink(result, 1);
 	result->i_size = strlen(symlink);
+	inode_nohigh(result);
 	result->i_op = &page_symlink_inode_operations;
 	result->i_data.a_ops = &hpfs_symlink_aops;
 
@@ -500,7 +501,7 @@ out:
 
 static int hpfs_symlink_readpage(struct file *file, struct page *page)
 {
-	char *link = kmap(page);
+	char *link = page_address(page);
 	struct inode *i = page->mapping->host;
 	struct fnode *fnode;
 	struct buffer_head *bh;
@@ -516,14 +517,12 @@ static int hpfs_symlink_readpage(struct file *file, struct page *page)
 		goto fail;
 	hpfs_unlock(i->i_sb);
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
 fail:
 	hpfs_unlock(i->i_sb);
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return err;
 }
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 316adb9..f5613fc 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -761,6 +761,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb,
 			break;
 		case S_IFLNK:
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 			break;
 		}
 		lockdep_annotate_inode_mutex_key(inode);
diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
index d67a16f..9560526 100644
--- a/fs/isofs/inode.c
+++ b/fs/isofs/inode.c
@@ -1417,6 +1417,7 @@ static int isofs_read_inode(struct inode *inode, int relocated)
 		inode->i_fop = &isofs_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_data.a_ops = &isofs_symlink_aops;
 	} else
 		/* XXX - parse_rock_ridge_inode() had already set i_rdev. */
diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c
index 735d752..5384ceb 100644
--- a/fs/isofs/rock.c
+++ b/fs/isofs/rock.c
@@ -687,7 +687,7 @@ static int rock_ridge_symlink_readpage(struct file *file, struct page *page)
 	struct inode *inode = page->mapping->host;
 	struct iso_inode_info *ei = ISOFS_I(inode);
 	struct isofs_sb_info *sbi = ISOFS_SB(inode->i_sb);
-	char *link = kmap(page);
+	char *link = page_address(page);
 	unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
 	struct buffer_head *bh;
 	char *rpnt = link;
@@ -774,7 +774,6 @@ repeat:
 	brelse(bh);
 	*rpnt = '\0';
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
@@ -791,7 +790,6 @@ fail:
 	brelse(bh);
 error:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return -EIO;
 }
diff --git a/fs/jfs/inode.c b/fs/jfs/inode.c
index 41aa3ca..cb31f2d 100644
--- a/fs/jfs/inode.c
+++ b/fs/jfs/inode.c
@@ -60,6 +60,7 @@ struct inode *jfs_iget(struct super_block *sb, unsigned long ino)
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (inode->i_size >= IDATASIZE) {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 			inode->i_mapping->a_ops = &jfs_aops;
 		} else {
 			inode->i_op = &jfs_fast_symlink_inode_operations;
diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c
index 9d7551f..560dbea 100644
--- a/fs/jfs/namei.c
+++ b/fs/jfs/namei.c
@@ -983,6 +983,7 @@ static int jfs_symlink(struct inode *dip, struct dentry *dentry,
 		jfs_info("jfs_symlink: allocate extent ip:0x%p", ip);
 
 		ip->i_op = &jfs_symlink_inode_operations;
+		inode_nohigh(ip);
 		ip->i_mapping->a_ops = &jfs_aops;
 
 		/*
diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c
index 99944a4..8367a87 100644
--- a/fs/logfs/dir.c
+++ b/fs/logfs/dir.c
@@ -529,6 +529,7 @@ static int logfs_symlink(struct inode *dir, struct dentry *dentry,
 		return PTR_ERR(inode);
 
 	inode->i_op = &page_symlink_inode_operations;
+	inode_nohigh(inode);
 	inode->i_mapping->a_ops = &logfs_reg_aops;
 
 	return __logfs_create(dir, dentry, inode, target, destlen);
diff --git a/fs/logfs/inode.c b/fs/logfs/inode.c
index 06baa92..d10c07d 100644
--- a/fs/logfs/inode.c
+++ b/fs/logfs/inode.c
@@ -65,6 +65,7 @@ static void logfs_inode_setops(struct inode *inode)
 		break;
 	case S_IFLNK:
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &logfs_reg_aops;
 		break;
 	case S_IFSOCK:	/* fall through */
diff --git a/fs/minix/inode.c b/fs/minix/inode.c
index 086cd0a..a28ae96 100644
--- a/fs/minix/inode.c
+++ b/fs/minix/inode.c
@@ -452,6 +452,7 @@ void minix_set_inode(struct inode *inode, dev_t rdev)
 		inode->i_mapping->a_ops = &minix_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &minix_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &minix_aops;
 	} else
 		init_special_inode(inode, inode->i_mode, rdev);
diff --git a/fs/namei.c b/fs/namei.c
index 2bc5de3..924c6a5e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4528,7 +4528,8 @@ static const char *page_getlink(struct dentry * dentry, void **cookie)
 	if (IS_ERR(page))
 		return (char*)page;
 	*cookie = page;
-	kaddr = kmap(page);
+	BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
+	kaddr = page_address(page);
 	nd_terminate_link(kaddr, dentry->d_inode->i_size, PAGE_SIZE - 1);
 	return kaddr;
 }
@@ -4542,7 +4543,6 @@ EXPORT_SYMBOL(page_follow_link_light);
 void page_put_link(struct inode *unused, void *cookie)
 {
 	struct page *page = cookie;
-	kunmap(page);
 	page_cache_release(page);
 }
 EXPORT_SYMBOL(page_put_link);
@@ -4566,7 +4566,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;
@@ -4577,9 +4576,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);
diff --git a/fs/ncpfs/inode.c b/fs/ncpfs/inode.c
index 9605a2f..2bb5d3d3 100644
--- a/fs/ncpfs/inode.c
+++ b/fs/ncpfs/inode.c
@@ -283,6 +283,7 @@ ncp_iget(struct super_block *sb, struct ncp_entry_info *info)
 #if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
 		} else if (S_ISLNK(inode->i_mode)) {
 			inode->i_op = &ncp_symlink_inode_operations;
+			inode_nohigh(inode);
 			inode->i_data.a_ops = &ncp_symlink_aops;
 #endif
 		} else {
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 326d9e1..f8a9d5c 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -408,9 +408,10 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr, st
 				inode->i_fop = NULL;
 				inode->i_flags |= S_AUTOMOUNT;
 			}
-		} else if (S_ISLNK(inode->i_mode))
+		} else if (S_ISLNK(inode->i_mode)) {
 			inode->i_op = &nfs_symlink_inode_operations;
-		else
+			inode_nohigh(inode);
+		} else
 			init_special_inode(inode, inode->i_mode, fattr->rdev);
 
 		memset(&inode->i_atime, 0, sizeof(inode->i_atime));
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index b6de433..abd93bf 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -56,7 +56,7 @@ static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
 	if (IS_ERR(page))
 		return ERR_CAST(page);
 	*cookie = page;
-	return kmap(page);
+	return page_address(page);
 }
 
 /*
diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c
index ac2f649..2714501 100644
--- a/fs/nilfs2/inode.c
+++ b/fs/nilfs2/inode.c
@@ -510,6 +510,7 @@ static int __nilfs_read_inode(struct super_block *sb,
 		inode->i_mapping->a_ops = &nilfs_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &nilfs_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &nilfs_aops;
 	} else {
 		inode->i_op = &nilfs_special_inode_operations;
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index c9a1a49..c7c4df5 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -161,6 +161,7 @@ static int nilfs_symlink(struct inode *dir, struct dentry *dentry,
 
 	/* slow symlink */
 	inode->i_op = &nilfs_symlink_inode_operations;
+	inode_nohigh(inode);
 	inode->i_mapping->a_ops = &nilfs_aops;
 	err = page_symlink(inode, symname, l);
 	if (err)
diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 8f87e05..edce6bd 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -361,6 +361,7 @@ void ocfs2_populate_inode(struct inode *inode, struct ocfs2_dinode *fe,
 		    break;
 	    case S_IFLNK:
 		    inode->i_op = &ocfs2_symlink_inode_operations;
+		    inode_nohigh(inode);
 		    i_size_write(inode, le64_to_cpu(fe->i_size));
 		    break;
 	    default:
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index 3b48ac2..c55bcecc 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -1958,6 +1958,7 @@ static int ocfs2_symlink(struct inode *dir,
 	inode->i_rdev = 0;
 	newsize = l - 1;
 	inode->i_op = &ocfs2_symlink_inode_operations;
+	inode_nohigh(inode);
 	if (l > ocfs2_fast_symlink_chars(sb)) {
 		u32 offset = 0;
 
diff --git a/fs/qnx4/inode.c b/fs/qnx4/inode.c
index c4bcb77..e41190e 100644
--- a/fs/qnx4/inode.c
+++ b/fs/qnx4/inode.c
@@ -316,6 +316,7 @@ struct inode *qnx4_iget(struct super_block *sb, unsigned long ino)
 		inode->i_fop = &qnx4_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &qnx4_aops;
 		qnx4_i(inode)->mmu_private = inode->i_size;
 	} else {
diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 32d2e1a..14b435f 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -582,6 +582,7 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
 		inode->i_mapping->a_ops = &qnx6_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &qnx6_aops;
 	} else
 		init_special_inode(inode, inode->i_mode, 0);
diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c
index 889d558..dcd7c63 100644
--- a/fs/ramfs/inode.c
+++ b/fs/ramfs/inode.c
@@ -79,6 +79,7 @@ struct inode *ramfs_get_inode(struct super_block *sb,
 			break;
 		case S_IFLNK:
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 			break;
 		}
 	}
diff --git a/fs/reiserfs/inode.c b/fs/reiserfs/inode.c
index 3d8e7e6..894a36a 100644
--- a/fs/reiserfs/inode.c
+++ b/fs/reiserfs/inode.c
@@ -1361,6 +1361,7 @@ static void init_inode(struct inode *inode, struct treepath *path)
 		inode->i_fop = &reiserfs_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &reiserfs_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &reiserfs_address_space_operations;
 	} else {
 		inode->i_blocks = 0;
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index 47f9698..e78f59b 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -1170,6 +1170,7 @@ static int reiserfs_symlink(struct inode *parent_dir,
 	reiserfs_update_inode_transaction(parent_dir);
 
 	inode->i_op = &reiserfs_symlink_inode_operations;
+	inode_nohigh(inode);
 	inode->i_mapping->a_ops = &reiserfs_address_space_operations;
 
 	retval = reiserfs_add_entry(&th, parent_dir, dentry->d_name.name,
diff --git a/fs/romfs/super.c b/fs/romfs/super.c
index 268733c..af20b89 100644
--- a/fs/romfs/super.c
+++ b/fs/romfs/super.c
@@ -360,6 +360,7 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
 		break;
 	case ROMFH_SYM:
 		i->i_op = &page_symlink_inode_operations;
+		inode_nohigh(i);
 		i->i_data.a_ops = &romfs_aops;
 		mode |= S_IRWXUGO;
 		break;
diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c
index a1ce5ce..fd546f8 100644
--- a/fs/squashfs/inode.c
+++ b/fs/squashfs/inode.c
@@ -41,6 +41,7 @@
 #include <linux/fs.h>
 #include <linux/vfs.h>
 #include <linux/xattr.h>
+#include <linux/pagemap.h>
 
 #include "squashfs_fs.h"
 #include "squashfs_fs_sb.h"
@@ -291,6 +292,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
 		set_nlink(inode, le32_to_cpu(sqsh_ino->nlink));
 		inode->i_size = le32_to_cpu(sqsh_ino->symlink_size);
 		inode->i_op = &squashfs_symlink_inode_ops;
+		inode_nohigh(inode);
 		inode->i_data.a_ops = &squashfs_symlink_aops;
 		inode->i_mode |= S_IFLNK;
 		squashfs_i(inode)->start = block;
diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c
index 590ad92..9a92895 100644
--- a/fs/sysv/inode.c
+++ b/fs/sysv/inode.c
@@ -164,6 +164,7 @@ void sysv_set_inode(struct inode *inode, dev_t rdev)
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (inode->i_blocks) {
 			inode->i_op = &sysv_symlink_inode_operations;
+			inode_nohigh(inode);
 			inode->i_mapping->a_ops = &sysv_aops;
 		} else {
 			inode->i_op = &simple_symlink_inode_operations;
diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index 8675c2b..ff99636 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -1541,6 +1541,7 @@ reread:
 	case ICBTAG_FILE_TYPE_SYMLINK:
 		inode->i_data.a_ops = &udf_symlink_aops;
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mode = S_IFLNK | S_IRWXUGO;
 		break;
 	case ICBTAG_FILE_TYPE_MAIN:
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index d0e6de1..a13df2a 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -922,6 +922,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
 
 	inode->i_data.a_ops = &udf_symlink_aops;
 	inode->i_op = &page_symlink_inode_operations;
+	inode_nohigh(inode);
 
 	if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
 		struct kernel_lb_addr eloc;
diff --git a/fs/udf/symlink.c b/fs/udf/symlink.c
index 862535b..8d61977 100644
--- a/fs/udf/symlink.c
+++ b/fs/udf/symlink.c
@@ -107,7 +107,7 @@ static int udf_symlink_filler(struct file *file, struct page *page)
 	struct buffer_head *bh = NULL;
 	unsigned char *symlink;
 	int err;
-	unsigned char *p = kmap(page);
+	unsigned char *p = page_address(page);
 	struct udf_inode_info *iinfo;
 	uint32_t pos;
 
@@ -141,7 +141,6 @@ static int udf_symlink_filler(struct file *file, struct page *page)
 
 	up_read(&iinfo->i_data_sem);
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
@@ -149,7 +148,6 @@ out_unlock_inode:
 	up_read(&iinfo->i_data_sem);
 	SetPageError(page);
 out_unmap:
-	kunmap(page);
 	unlock_page(page);
 	return err;
 }
diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c
index 737160a..01be73d 100644
--- a/fs/ufs/inode.c
+++ b/fs/ufs/inode.c
@@ -533,6 +533,7 @@ static void ufs_set_inode_ops(struct inode *inode)
 		} else {
 			inode->i_mapping->a_ops = &ufs_aops;
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohigh(inode);
 		}
 	} else
 		init_special_inode(inode, inode->i_mode,
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 24b0cbd..30e0366 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -124,6 +124,7 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry,
 	if (l > UFS_SB(sb)->s_uspi->s_maxsymlinklen) {
 		/* slow symlink */
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohigh(inode);
 		inode->i_mapping->a_ops = &ufs_aops;
 		err = page_symlink(inode, symname, l);
 		if (err)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3aa5142..0b8d4d7 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3026,4 +3026,8 @@ static inline bool dir_relax(struct inode *inode)
 
 extern bool path_noexec(const struct path *path);
 
+/* macro to avoid the mess with include dependencies */
+#define inode_nohigh(inode) \
+	mapping_set_gfp_mask((inode)->i_mapping, GFP_USER)
+
 #endif /* _LINUX_FS_H */
diff --git a/mm/shmem.c b/mm/shmem.c
index 9187eee..9e701be 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2444,7 +2444,6 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	int len;
 	struct inode *inode;
 	struct page *page;
-	char *kaddr;
 	struct shmem_inode_info *info;
 
 	len = strlen(symname) + 1;
@@ -2483,9 +2482,8 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 		}
 		inode->i_mapping->a_ops = &shmem_aops;
 		inode->i_op = &shmem_symlink_inode_operations;
-		kaddr = kmap_atomic(page);
-		memcpy(kaddr, symname, len);
-		kunmap_atomic(kaddr);
+		inode_nohigh(inode);
+		memcpy(page_address(page), symname, len);
 		SetPageUptodate(page);
 		set_page_dirty(page);
 		unlock_page(page);
@@ -2506,13 +2504,12 @@ static const char *shmem_follow_link(struct dentry *dentry, void **cookie)
 		return ERR_PTR(error);
 	unlock_page(page);
 	*cookie = page;
-	return kmap(page);
+	return page_address(page);
 }
 
 static void shmem_put_link(struct inode *unused, void *cookie)
 {
 	struct page *page = cookie;
-	kunmap(page);
 	mark_page_accessed(page);
 	page_cache_release(page);
 }
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 07/10] [vfs] replace ->follow_link() with new method that could stay in RCU mode
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (5 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 06/10] [vfs] don't put symlink bodies in pagecache into highmem Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 08/10] teach page_get_link() to work " Al Viro
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

new method: ->get_link(); replacement of ->follow_link().  The differences
are:
	* inode and dentry are passed separately
	* might be called both in RCU and non-RCU mode;
the former is indicated by passing it a NULL dentry.
	* when called that way it isn't allowed to block
and should return ERR_PTR(-ECHILD) if it needs to be called
in non-RCU mode.

It's a flagday change - the old method is gone, all in-tree instances
converted.  Conversion isn't hard; said that, so far very few instances
do not immediately bail out when called in RCU mode.  That'll change
in the next commits.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 Documentation/filesystems/Locking             |  4 +--
 Documentation/filesystems/porting             |  6 ++++
 drivers/staging/lustre/lustre/llite/symlink.c |  8 +++--
 fs/9p/vfs_inode.c                             | 17 +++++++---
 fs/9p/vfs_inode_dotl.c                        | 14 ++++++---
 fs/affs/symlink.c                             |  4 +--
 fs/autofs4/symlink.c                          | 13 +++++---
 fs/btrfs/inode.c                              |  2 +-
 fs/ceph/inode.c                               |  2 +-
 fs/cifs/cifsfs.c                              |  2 +-
 fs/cifs/cifsfs.h                              |  4 +--
 fs/cifs/link.c                                |  6 ++--
 fs/coda/cnode.c                               |  2 +-
 fs/configfs/symlink.c                         | 11 +++++--
 fs/dcache.c                                   |  2 +-
 fs/ecryptfs/inode.c                           | 12 +++++--
 fs/ext2/symlink.c                             |  4 +--
 fs/ext4/symlink.c                             | 13 +++++---
 fs/f2fs/namei.c                               | 16 ++++++----
 fs/fuse/dir.c                                 |  9 ++++--
 fs/gfs2/inode.c                               | 15 ++++++---
 fs/hostfs/hostfs_kern.c                       | 10 ++++--
 fs/jffs2/symlink.c                            |  2 +-
 fs/jfs/symlink.c                              |  4 +--
 fs/kernfs/symlink.c                           | 11 +++++--
 fs/libfs.c                                    |  9 +++---
 fs/minix/inode.c                              |  2 +-
 fs/namei.c                                    | 45 +++++++++++++++++----------
 fs/ncpfs/inode.c                              |  2 +-
 fs/nfs/symlink.c                              |  9 ++++--
 fs/nilfs2/namei.c                             |  2 +-
 fs/ocfs2/symlink.c                            |  2 +-
 fs/overlayfs/inode.c                          | 12 ++++---
 fs/proc/base.c                                | 22 +++++++------
 fs/proc/inode.c                               |  7 +++--
 fs/proc/namespaces.c                          |  9 ++++--
 fs/proc/self.c                                |  7 +++--
 fs/proc/thread_self.c                         |  7 +++--
 fs/reiserfs/namei.c                           |  2 +-
 fs/squashfs/symlink.c                         |  2 +-
 fs/sysv/inode.c                               |  2 +-
 fs/ubifs/file.c                               |  2 +-
 fs/xfs/xfs_iops.c                             |  8 +++--
 include/linux/fs.h                            |  6 ++--
 mm/shmem.c                                    | 12 ++++---
 45 files changed, 232 insertions(+), 130 deletions(-)

diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking
index 06d4434..4fba54b 100644
--- a/Documentation/filesystems/Locking
+++ b/Documentation/filesystems/Locking
@@ -50,7 +50,7 @@ prototypes:
 	int (*rename2) (struct inode *, struct dentry *,
 			struct inode *, struct dentry *, unsigned int);
 	int (*readlink) (struct dentry *, char __user *,int);
-	const char *(*follow_link) (struct dentry *, void **);
+	const char *(*get_link) (struct dentry *, struct inode *, void **);
 	void (*put_link) (struct inode *, void *);
 	void (*truncate) (struct inode *);
 	int (*permission) (struct inode *, int, unsigned int);
@@ -83,7 +83,7 @@ rmdir:		yes (both)	(see below)
 rename:		yes (all)	(see below)
 rename2:	yes (all)	(see below)
 readlink:	no
-follow_link:	no
+get_link:	no
 put_link:	no
 setattr:	yes
 permission:	no (may not block if called in rcu-walk mode)
diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting
index da398d87..77ca24d 100644
--- a/Documentation/filesystems/porting
+++ b/Documentation/filesystems/porting
@@ -509,3 +509,9 @@ in your dentry operations instead.
 	any symlink that might use page_follow_link_light/page_put_link() must
 	have inode_nohigh(inode) called before anything might start playing with
 	its pagecache.
+--
+[mandatory]
+	->follow_link() is replaced with ->get_link(); same API, except that
+		* ->get_link() gets inode as a separate argument
+		* ->get_link() may be called in RCU mode - in that case NULL
+		  dentry is passed
diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c
index 69b2036..153fdf9 100644
--- a/drivers/staging/lustre/lustre/llite/symlink.c
+++ b/drivers/staging/lustre/lustre/llite/symlink.c
@@ -118,12 +118,14 @@ failed:
 	return rc;
 }
 
-static const char *ll_follow_link(struct dentry *dentry, void **cookie)
+static const char *ll_get_link(struct dentry *dentry,
+			       struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct ptlrpc_request *request = NULL;
 	int rc;
 	char *symname = NULL;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
 
 	CDEBUG(D_VFSTRACE, "VFS Op\n");
 	ll_inode_size_lock(inode);
@@ -149,7 +151,7 @@ static void ll_put_link(struct inode *unused, void *cookie)
 struct inode_operations ll_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.setattr	= ll_setattr,
-	.follow_link	= ll_follow_link,
+	.get_link	= ll_get_link,
 	.put_link	= ll_put_link,
 	.getattr	= ll_getattr,
 	.permission	= ll_inode_permission,
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 699941e..8ba5a89 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -1223,18 +1223,25 @@ ino_t v9fs_qid2ino(struct p9_qid *qid)
 }
 
 /**
- * v9fs_vfs_follow_link - follow a symlink path
+ * v9fs_vfs_get_link - follow a symlink path
  * @dentry: dentry for symlink
+ * @inode: inode for symlink
  * @cookie: place to pass the data to put_link()
  */
 
-static const char *v9fs_vfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *v9fs_vfs_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
-	struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry);
-	struct p9_fid *fid = v9fs_fid_lookup(dentry);
+	struct v9fs_session_info *v9ses;
+	struct p9_fid *fid;
 	struct p9_wstat *st;
 	char *res;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	v9ses = v9fs_dentry2v9ses(dentry);
+	fid = v9fs_fid_lookup(dentry);
 	p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
 
 	if (IS_ERR(fid))
@@ -1452,7 +1459,7 @@ static const struct inode_operations v9fs_file_inode_operations = {
 
 static const struct inode_operations v9fs_symlink_inode_operations = {
 	.readlink = generic_readlink,
-	.follow_link = v9fs_vfs_follow_link,
+	.get_link = v9fs_vfs_get_link,
 	.put_link = kfree_put_link,
 	.getattr = v9fs_vfs_getattr,
 	.setattr = v9fs_vfs_setattr,
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index cb899af..0cc105d 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -899,20 +899,26 @@ error:
 }
 
 /**
- * v9fs_vfs_follow_link_dotl - follow a symlink path
+ * v9fs_vfs_get_link_dotl - follow a symlink path
  * @dentry: dentry for symlink
+ * @inode: inode for symlink
  * @cookie: place to pass the data to put_link()
  */
 
 static const char *
-v9fs_vfs_follow_link_dotl(struct dentry *dentry, void **cookie)
+v9fs_vfs_get_link_dotl(struct dentry *dentry,
+		       struct inode *inode, void **cookie)
 {
-	struct p9_fid *fid = v9fs_fid_lookup(dentry);
+	struct p9_fid *fid;
 	char *target;
 	int retval;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
 
+	fid = v9fs_fid_lookup(dentry);
 	if (IS_ERR(fid))
 		return ERR_CAST(fid);
 	retval = p9_client_readlink(fid, &target);
@@ -984,7 +990,7 @@ const struct inode_operations v9fs_file_inode_operations_dotl = {
 
 const struct inode_operations v9fs_symlink_inode_operations_dotl = {
 	.readlink = generic_readlink,
-	.follow_link = v9fs_vfs_follow_link_dotl,
+	.get_link = v9fs_vfs_get_link_dotl,
 	.put_link = kfree_put_link,
 	.getattr = v9fs_vfs_getattr_dotl,
 	.setattr = v9fs_vfs_setattr_dotl,
diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
index e3f9dc3..39d1194 100644
--- a/fs/affs/symlink.c
+++ b/fs/affs/symlink.c
@@ -20,7 +20,7 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
 	char			 c;
 	char			 lc;
 
-	pr_debug("follow_link(ino=%lu)\n", inode->i_ino);
+	pr_debug("get_link(ino=%lu)\n", inode->i_ino);
 
 	bh = affs_bread(inode->i_sb, inode->i_ino);
 	if (!bh)
@@ -71,7 +71,7 @@ const struct address_space_operations affs_symlink_aops = {
 
 const struct inode_operations affs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= affs_notify_change,
 };
diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c
index da0c334..39e6f0b 100644
--- a/fs/autofs4/symlink.c
+++ b/fs/autofs4/symlink.c
@@ -12,10 +12,15 @@
 
 #include "autofs_i.h"
 
-static const char *autofs4_follow_link(struct dentry *dentry, void **cookie)
+static const char *autofs4_get_link(struct dentry *dentry,
+				    struct inode *inode, void **cookie)
 {
-	struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
-	struct autofs_info *ino = autofs4_dentry_ino(dentry);
+	struct autofs_sb_info *sbi;
+	struct autofs_info *ino;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	sbi = autofs4_sbi(dentry->d_sb);
+	ino = autofs4_dentry_ino(dentry);
 	if (ino && !autofs4_oz_mode(sbi))
 		ino->last_used = jiffies;
 	return d_inode(dentry)->i_private;
@@ -23,5 +28,5 @@ static const char *autofs4_follow_link(struct dentry *dentry, void **cookie)
 
 const struct inode_operations autofs4_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= autofs4_follow_link
+	.get_link	= autofs4_get_link
 };
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index cf917d7..48e13fd 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -10118,7 +10118,7 @@ static const struct inode_operations btrfs_special_inode_operations = {
 };
 static const struct inode_operations btrfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= btrfs_getattr,
 	.setattr	= btrfs_setattr,
diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
index 498dcfa..da55eb8 100644
--- a/fs/ceph/inode.c
+++ b/fs/ceph/inode.c
@@ -1756,7 +1756,7 @@ retry:
  */
 static const struct inode_operations ceph_symlink_iops = {
 	.readlink = generic_readlink,
-	.follow_link = simple_follow_link,
+	.get_link = simple_get_link,
 	.setattr = ceph_setattr,
 	.getattr = ceph_getattr,
 	.setxattr = ceph_setxattr,
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index cbc0f4b..4593f416 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -900,7 +900,7 @@ const struct inode_operations cifs_file_inode_ops = {
 
 const struct inode_operations cifs_symlink_inode_ops = {
 	.readlink = generic_readlink,
-	.follow_link = cifs_follow_link,
+	.get_link = cifs_get_link,
 	.put_link = kfree_put_link,
 	.permission = cifs_permission,
 	/* BB add the following two eventually */
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index c3cc160..6886328 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -120,9 +120,7 @@ extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
 #endif
 
 /* Functions related to symlinks */
-extern const char *cifs_follow_link(struct dentry *direntry, void **cookie);
-extern int cifs_readlink(struct dentry *direntry, char __user *buffer,
-			 int buflen);
+extern const char *cifs_get_link(struct dentry *, struct inode *, void **);
 extern int cifs_symlink(struct inode *inode, struct dentry *direntry,
 			const char *symname);
 extern int	cifs_removexattr(struct dentry *, const char *);
diff --git a/fs/cifs/link.c b/fs/cifs/link.c
index e3548f7..6f2439b5 100644
--- a/fs/cifs/link.c
+++ b/fs/cifs/link.c
@@ -627,9 +627,8 @@ cifs_hl_exit:
 }
 
 const char *
-cifs_follow_link(struct dentry *direntry, void **cookie)
+cifs_get_link(struct dentry *direntry, struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(direntry);
 	int rc = -ENOMEM;
 	unsigned int xid;
 	char *full_path = NULL;
@@ -639,6 +638,9 @@ cifs_follow_link(struct dentry *direntry, void **cookie)
 	struct cifs_tcon *tcon;
 	struct TCP_Server_Info *server;
 
+	if (!direntry)
+		return ERR_PTR(-ECHILD);
+
 	xid = get_xid();
 
 	tlink = cifs_sb_tlink(cifs_sb);
diff --git a/fs/coda/cnode.c b/fs/coda/cnode.c
index e37d242..f5872bd 100644
--- a/fs/coda/cnode.c
+++ b/fs/coda/cnode.c
@@ -17,7 +17,7 @@ static inline int coda_fideq(struct CodaFid *fid1, struct CodaFid *fid2)
 
 static const struct inode_operations coda_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= coda_setattr,
 };
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index ec5c832..b91c01e 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -279,11 +279,16 @@ static int configfs_getlink(struct dentry *dentry, char * path)
 
 }
 
-static const char *configfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *configfs_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
-	unsigned long page = get_zeroed_page(GFP_KERNEL);
+	unsigned long page;
 	int error;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	page = get_zeroed_page(GFP_KERNEL);
 	if (!page)
 		return ERR_PTR(-ENOMEM);
 
@@ -297,7 +302,7 @@ static const char *configfs_follow_link(struct dentry *dentry, void **cookie)
 }
 
 const struct inode_operations configfs_symlink_inode_operations = {
-	.follow_link = configfs_follow_link,
+	.get_link = configfs_get_link,
 	.readlink = generic_readlink,
 	.put_link = free_page_put_link,
 	.setattr = configfs_setattr,
diff --git a/fs/dcache.c b/fs/dcache.c
index 5c33aeb..d27f090 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1734,7 +1734,7 @@ static unsigned d_flags_for_inode(struct inode *inode)
 	}
 
 	if (unlikely(!(inode->i_opflags & IOP_NOFOLLOW))) {
-		if (unlikely(inode->i_op->follow_link)) {
+		if (unlikely(inode->i_op->get_link)) {
 			add_flags = DCACHE_SYMLINK_TYPE;
 			goto type_determined;
 		}
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index e2e47ba..5a05559 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -674,10 +674,16 @@ out:
 	return rc ? ERR_PTR(rc) : buf;
 }
 
-static const char *ecryptfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *ecryptfs_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
 	size_t len;
-	char *buf = ecryptfs_readlink_lower(dentry, &len);
+	char *buf;
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	buf = ecryptfs_readlink_lower(dentry, &len);
 	if (IS_ERR(buf))
 		return buf;
 	fsstack_copy_attr_atime(d_inode(dentry),
@@ -1095,7 +1101,7 @@ out:
 
 const struct inode_operations ecryptfs_symlink_iops = {
 	.readlink = generic_readlink,
-	.follow_link = ecryptfs_follow_link,
+	.get_link = ecryptfs_get_link,
 	.put_link = kfree_put_link,
 	.permission = ecryptfs_permission,
 	.setattr = ecryptfs_setattr,
diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c
index ae17179..4690511 100644
--- a/fs/ext2/symlink.c
+++ b/fs/ext2/symlink.c
@@ -22,7 +22,7 @@
 
 const struct inode_operations ext2_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= ext2_setattr,
 #ifdef CONFIG_EXT2_FS_XATTR
@@ -35,7 +35,7 @@ const struct inode_operations ext2_symlink_inode_operations = {
  
 const struct inode_operations ext2_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
+	.get_link	= simple_get_link,
 	.setattr	= ext2_setattr,
 #ifdef CONFIG_EXT2_FS_XATTR
 	.setxattr	= generic_setxattr,
diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
index 0e6dc44..3b4bfe2 100644
--- a/fs/ext4/symlink.c
+++ b/fs/ext4/symlink.c
@@ -23,17 +23,20 @@
 #include "xattr.h"
 
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
-static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cookie)
+static const char *ext4_encrypted_get_link(struct dentry *dentry,
+					   struct inode *inode, void **cookie)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
 	struct ext4_str cstr, pstr;
-	struct inode *inode = d_inode(dentry);
 	struct ext4_encrypted_symlink_data *sd;
 	loff_t size = min_t(loff_t, i_size_read(inode), PAGE_SIZE - 1);
 	int res;
 	u32 plen, max_size = inode->i_sb->s_blocksize;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	res = ext4_get_encryption_info(inode);
 	if (res)
 		return ERR_PTR(res);
@@ -87,7 +90,7 @@ errout:
 
 const struct inode_operations ext4_encrypted_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link    = ext4_encrypted_follow_link,
+	.get_link	= ext4_encrypted_get_link,
 	.put_link       = kfree_put_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
@@ -99,7 +102,7 @@ const struct inode_operations ext4_encrypted_symlink_inode_operations = {
 
 const struct inode_operations ext4_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
@@ -110,7 +113,7 @@ const struct inode_operations ext4_symlink_inode_operations = {
 
 const struct inode_operations ext4_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link    = simple_follow_link,
+	.get_link	= simple_get_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
 	.getxattr	= generic_getxattr,
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index 1aea1c3..ced65443 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -315,9 +315,10 @@ fail:
 	return err;
 }
 
-static const char *f2fs_follow_link(struct dentry *dentry, void **cookie)
+static const char *f2fs_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	const char *link = page_follow_link_light(dentry, cookie);
+	const char *link = page_get_link(dentry, inode, cookie);
 	if (!IS_ERR(link) && !*link) {
 		/* this is broken symlink case */
 		page_put_link(NULL, *cookie);
@@ -924,18 +925,21 @@ static int f2fs_rename2(struct inode *old_dir, struct dentry *old_dentry,
 }
 
 #ifdef CONFIG_F2FS_FS_ENCRYPTION
-static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cookie)
+static const char *f2fs_encrypted_get_link(struct dentry *dentry,
+					   struct inode *inode, void **cookie)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
 	struct f2fs_str cstr;
 	struct f2fs_str pstr = FSTR_INIT(NULL, 0);
-	struct inode *inode = d_inode(dentry);
 	struct f2fs_encrypted_symlink_data *sd;
 	loff_t size = min_t(loff_t, i_size_read(inode), PAGE_SIZE - 1);
 	u32 max_size = inode->i_sb->s_blocksize;
 	int res;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	res = f2fs_get_encryption_info(inode);
 	if (res)
 		return ERR_PTR(res);
@@ -994,7 +998,7 @@ errout:
 
 const struct inode_operations f2fs_encrypted_symlink_inode_operations = {
 	.readlink       = generic_readlink,
-	.follow_link    = f2fs_encrypted_follow_link,
+	.get_link       = f2fs_encrypted_get_link,
 	.put_link       = kfree_put_link,
 	.getattr	= f2fs_getattr,
 	.setattr	= f2fs_setattr,
@@ -1030,7 +1034,7 @@ const struct inode_operations f2fs_dir_inode_operations = {
 
 const struct inode_operations f2fs_symlink_inode_operations = {
 	.readlink       = generic_readlink,
-	.follow_link    = f2fs_follow_link,
+	.get_link       = f2fs_get_link,
 	.put_link       = page_put_link,
 	.getattr	= f2fs_getattr,
 	.setattr	= f2fs_setattr,
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 5e2e087..148e8ef 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1365,14 +1365,17 @@ static int fuse_readdir(struct file *file, struct dir_context *ctx)
 	return err;
 }
 
-static const char *fuse_follow_link(struct dentry *dentry, void **cookie)
+static const char *fuse_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	FUSE_ARGS(args);
 	char *link;
 	ssize_t ret;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	link = (char *) __get_free_page(GFP_KERNEL);
 	if (!link)
 		return ERR_PTR(-ENOMEM);
@@ -1909,7 +1912,7 @@ static const struct inode_operations fuse_common_inode_operations = {
 
 static const struct inode_operations fuse_symlink_inode_operations = {
 	.setattr	= fuse_setattr,
-	.follow_link	= fuse_follow_link,
+	.get_link	= fuse_get_link,
 	.put_link	= free_page_put_link,
 	.readlink	= generic_readlink,
 	.getattr	= fuse_getattr,
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 063fdfc..10950560 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1712,24 +1712,29 @@ static int gfs2_rename2(struct inode *odir, struct dentry *odentry,
 }
 
 /**
- * gfs2_follow_link - Follow a symbolic link
+ * gfs2_get_link - Follow a symbolic link
  * @dentry: The dentry of the link
- * @nd: Data that we pass to vfs_follow_link()
+ * @inode: The inode of the link
+ * @cookie: place to store the information for ->put_link()
  *
  * This can handle symlinks of any size.
  *
  * Returns: 0 on success or error code
  */
 
-static const char *gfs2_follow_link(struct dentry *dentry, void **cookie)
+static const char *gfs2_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	struct gfs2_inode *ip = GFS2_I(d_inode(dentry));
+	struct gfs2_inode *ip = GFS2_I(inode);
 	struct gfs2_holder i_gh;
 	struct buffer_head *dibh;
 	unsigned int size;
 	char *buf;
 	int error;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &i_gh);
 	error = gfs2_glock_nq(&i_gh);
 	if (error) {
@@ -2132,7 +2137,7 @@ const struct inode_operations gfs2_dir_iops = {
 
 const struct inode_operations gfs2_symlink_iops = {
 	.readlink = generic_readlink,
-	.follow_link = gfs2_follow_link,
+	.get_link = gfs2_get_link,
 	.put_link = kfree_put_link,
 	.permission = gfs2_permission,
 	.setattr = gfs2_setattr,
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index 2ac99db..6ce5309 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -892,9 +892,13 @@ static const struct inode_operations hostfs_dir_iops = {
 	.setattr	= hostfs_setattr,
 };
 
-static const char *hostfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *hostfs_get_link(struct dentry *dentry,
+				   struct inode *inode, void **cookie)
 {
-	char *link = __getname();
+	char *link;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	link = __getname();
 	if (link) {
 		char *path = dentry_name(dentry);
 		int err = -ENOMEM;
@@ -922,7 +926,7 @@ static void hostfs_put_link(struct inode *unused, void *cookie)
 
 static const struct inode_operations hostfs_link_iops = {
 	.readlink	= generic_readlink,
-	.follow_link	= hostfs_follow_link,
+	.get_link	= hostfs_get_link,
 	.put_link	= hostfs_put_link,
 };
 
diff --git a/fs/jffs2/symlink.c b/fs/jffs2/symlink.c
index 8ce2f24..2cabd64 100644
--- a/fs/jffs2/symlink.c
+++ b/fs/jffs2/symlink.c
@@ -14,7 +14,7 @@
 const struct inode_operations jffs2_symlink_inode_operations =
 {
 	.readlink =	generic_readlink,
-	.follow_link =	simple_follow_link,
+	.get_link =	simple_get_link,
 	.setattr =	jffs2_setattr,
 	.setxattr =	jffs2_setxattr,
 	.getxattr =	jffs2_getxattr,
diff --git a/fs/jfs/symlink.c b/fs/jfs/symlink.c
index 5929e23..0211328 100644
--- a/fs/jfs/symlink.c
+++ b/fs/jfs/symlink.c
@@ -23,7 +23,7 @@
 
 const struct inode_operations jfs_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
+	.get_link	= simple_get_link,
 	.setattr	= jfs_setattr,
 	.setxattr	= jfs_setxattr,
 	.getxattr	= jfs_getxattr,
@@ -33,7 +33,7 @@ const struct inode_operations jfs_fast_symlink_inode_operations = {
 
 const struct inode_operations jfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= jfs_setattr,
 	.setxattr	= jfs_setxattr,
diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c
index db27252..ffae857 100644
--- a/fs/kernfs/symlink.c
+++ b/fs/kernfs/symlink.c
@@ -112,10 +112,15 @@ static int kernfs_getlink(struct dentry *dentry, char *path)
 	return error;
 }
 
-static const char *kernfs_iop_follow_link(struct dentry *dentry, void **cookie)
+static const char *kernfs_iop_get_link(struct dentry *dentry,
+				       struct inode *inode, void **cookie)
 {
 	int error = -ENOMEM;
-	unsigned long page = get_zeroed_page(GFP_KERNEL);
+	unsigned long page;
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	page = get_zeroed_page(GFP_KERNEL);
 	if (!page)
 		return ERR_PTR(-ENOMEM);
 	error = kernfs_getlink(dentry, (char *)page);
@@ -132,7 +137,7 @@ const struct inode_operations kernfs_symlink_iops = {
 	.getxattr	= kernfs_iop_getxattr,
 	.listxattr	= kernfs_iop_listxattr,
 	.readlink	= generic_readlink,
-	.follow_link	= kernfs_iop_follow_link,
+	.get_link	= kernfs_iop_get_link,
 	.put_link	= free_page_put_link,
 	.setattr	= kernfs_iop_setattr,
 	.getattr	= kernfs_iop_getattr,
diff --git a/fs/libfs.c b/fs/libfs.c
index c7cbfb0..8dc37fc 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -1092,14 +1092,15 @@ simple_nosetlease(struct file *filp, long arg, struct file_lock **flp,
 }
 EXPORT_SYMBOL(simple_nosetlease);
 
-const char *simple_follow_link(struct dentry *dentry, void **cookie)
+const char *simple_get_link(struct dentry *dentry, struct inode *inode,
+			    void **cookie)
 {
-	return d_inode(dentry)->i_link;
+	return inode->i_link;
 }
-EXPORT_SYMBOL(simple_follow_link);
+EXPORT_SYMBOL(simple_get_link);
 
 const struct inode_operations simple_symlink_inode_operations = {
-	.follow_link = simple_follow_link,
+	.get_link = simple_get_link,
 	.readlink = generic_readlink
 };
 EXPORT_SYMBOL(simple_symlink_inode_operations);
diff --git a/fs/minix/inode.c b/fs/minix/inode.c
index a28ae96..fced09c 100644
--- a/fs/minix/inode.c
+++ b/fs/minix/inode.c
@@ -435,7 +435,7 @@ static const struct address_space_operations minix_aops = {
 
 static const struct inode_operations minix_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= minix_getattr,
 };
diff --git a/fs/namei.c b/fs/namei.c
index 924c6a5e..089c37f 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -842,7 +842,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)
@@ -1005,10 +1005,18 @@ const char *get_link(struct nameidata *nd)
 	res = inode->i_link;
 	if (!res) {
 		if (nd->flags & LOOKUP_RCU) {
-			if (unlikely(unlazy_walk(nd, NULL, 0)))
-				return ERR_PTR(-ECHILD);
+			res = inode->i_op->get_link(NULL, inode,
+						    &last->cookie);
+			if (res == ERR_PTR(-ECHILD)) {
+				if (unlikely(unlazy_walk(nd, NULL, 0)))
+					return ERR_PTR(-ECHILD);
+				res = inode->i_op->get_link(dentry, inode,
+						            &last->cookie);
+			}
+		} else {
+			res = inode->i_op->get_link(dentry, inode,
+						    &last->cookie);
 		}
-		res = inode->i_op->follow_link(dentry, &last->cookie);
 		if (IS_ERR_OR_NULL(res)) {
 			last->cookie = NULL;
 			return res;
@@ -4496,8 +4504,8 @@ 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)
 {
@@ -4507,7 +4515,7 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 	int res;
 
 	if (!link) {
-		link = inode->i_op->follow_link(dentry, &cookie);
+		link = inode->i_op->get_link(dentry, inode, &cookie);
 		if (IS_ERR(link))
 			return PTR_ERR(link);
 	}
@@ -4519,26 +4527,27 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 EXPORT_SYMBOL(generic_readlink);
 
 /* get the link contents into pagecache */
-static const char *page_getlink(struct dentry * dentry, void **cookie)
+const char *page_get_link(struct dentry *dentry, struct inode *inode,
+				 void **cookie)
 {
 	char *kaddr;
 	struct page *page;
-	struct address_space *mapping = dentry->d_inode->i_mapping;
+	struct address_space *mapping = inode->i_mapping;
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	page = read_mapping_page(mapping, 0, NULL);
 	if (IS_ERR(page))
 		return (char*)page;
 	*cookie = page;
 	BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
 	kaddr = page_address(page);
-	nd_terminate_link(kaddr, dentry->d_inode->i_size, PAGE_SIZE - 1);
+	nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
 	return kaddr;
 }
 
-const char *page_follow_link_light(struct dentry *dentry, void **cookie)
-{
-	return page_getlink(dentry, cookie);
-}
-EXPORT_SYMBOL(page_follow_link_light);
+EXPORT_SYMBOL(page_get_link);
 
 void page_put_link(struct inode *unused, void *cookie)
 {
@@ -4550,7 +4559,9 @@ EXPORT_SYMBOL(page_put_link);
 int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 {
 	void *cookie = NULL;
-	int res = readlink_copy(buffer, buflen, page_getlink(dentry, &cookie));
+	int res = readlink_copy(buffer, buflen,
+				page_get_link(dentry, d_inode(dentry),
+					      &cookie));
 	if (cookie)
 		page_put_link(NULL, cookie);
 	return res;
@@ -4601,7 +4612,7 @@ EXPORT_SYMBOL(page_symlink);
 
 const struct inode_operations page_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 };
 EXPORT_SYMBOL(page_symlink_inode_operations);
diff --git a/fs/ncpfs/inode.c b/fs/ncpfs/inode.c
index 2bb5d3d3..028cacf 100644
--- a/fs/ncpfs/inode.c
+++ b/fs/ncpfs/inode.c
@@ -244,7 +244,7 @@ static void ncp_set_attr(struct inode *inode, struct ncp_entry_info *nwinfo)
 #if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
 static const struct inode_operations ncp_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= ncp_notify_change,
 };
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index abd93bf..8ade8a8 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -42,12 +42,15 @@ error:
 	return -EIO;
 }
 
-static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *nfs_get_link(struct dentry *dentry,
+				struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct page *page;
 	void *err;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	err = ERR_PTR(nfs_revalidate_mapping(inode, inode->i_mapping));
 	if (err)
 		return err;
@@ -64,7 +67,7 @@ static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
  */
 const struct inode_operations nfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= nfs_follow_link,
+	.get_link	= nfs_get_link,
 	.put_link	= page_put_link,
 	.getattr	= nfs_getattr,
 	.setattr	= nfs_setattr,
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index c7c4df5..8daa40f 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -569,7 +569,7 @@ const struct inode_operations nilfs_special_inode_operations = {
 
 const struct inode_operations nilfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.permission     = nilfs_permission,
 };
diff --git a/fs/ocfs2/symlink.c b/fs/ocfs2/symlink.c
index 66edce7..b4e79bc 100644
--- a/fs/ocfs2/symlink.c
+++ b/fs/ocfs2/symlink.c
@@ -88,7 +88,7 @@ const struct address_space_operations ocfs2_fast_symlink_aops = {
 
 const struct inode_operations ocfs2_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= ocfs2_getattr,
 	.setattr	= ocfs2_setattr,
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index ec0c2a0..4c24a44 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -140,17 +140,21 @@ struct ovl_link_data {
 	void *cookie;
 };
 
-static const char *ovl_follow_link(struct dentry *dentry, void **cookie)
+static const char *ovl_get_link(struct dentry *dentry,
+				struct inode *inode, void **cookie)
 {
 	struct dentry *realdentry;
 	struct inode *realinode;
 	struct ovl_link_data *data = NULL;
 	const char *ret;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	realdentry = ovl_dentry_real(dentry);
 	realinode = realdentry->d_inode;
 
-	if (WARN_ON(!realinode->i_op->follow_link))
+	if (WARN_ON(!realinode->i_op->get_link))
 		return ERR_PTR(-EPERM);
 
 	if (realinode->i_op->put_link) {
@@ -160,7 +164,7 @@ static const char *ovl_follow_link(struct dentry *dentry, void **cookie)
 		data->realdentry = realdentry;
 	}
 
-	ret = realinode->i_op->follow_link(realdentry, cookie);
+	ret = realinode->i_op->get_link(realdentry, realinode, cookie);
 	if (IS_ERR_OR_NULL(ret)) {
 		kfree(data);
 		return ret;
@@ -381,7 +385,7 @@ static const struct inode_operations ovl_file_inode_operations = {
 
 static const struct inode_operations ovl_symlink_inode_operations = {
 	.setattr	= ovl_setattr,
-	.follow_link	= ovl_follow_link,
+	.get_link	= ovl_get_link,
 	.put_link	= ovl_put_link,
 	.readlink	= ovl_readlink,
 	.getattr	= ovl_getattr,
diff --git a/fs/proc/base.c b/fs/proc/base.c
index bd3e9e6..1a489e2 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -1564,12 +1564,15 @@ static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
 		return -ENOENT;
 }
 
-static const char *proc_pid_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_pid_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct path path;
 	int error = -EACCES;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	/* Are we allowed to snoop on the tasks file descriptors? */
 	if (!proc_fd_access_allowed(inode))
 		goto out;
@@ -1630,7 +1633,7 @@ out:
 
 const struct inode_operations proc_pid_link_inode_operations = {
 	.readlink	= proc_pid_readlink,
-	.follow_link	= proc_pid_follow_link,
+	.get_link	= proc_pid_get_link,
 	.setattr	= proc_setattr,
 };
 
@@ -1895,7 +1898,7 @@ static const struct dentry_operations tid_map_files_dentry_operations = {
 	.d_delete	= pid_delete_dentry,
 };
 
-static int proc_map_files_get_link(struct dentry *dentry, struct path *path)
+static int map_files_get_link(struct dentry *dentry, struct path *path)
 {
 	unsigned long vm_start, vm_end;
 	struct vm_area_struct *vma;
@@ -1945,20 +1948,21 @@ struct map_files_info {
  * path to the file in question.
  */
 static const char *
-proc_map_files_follow_link(struct dentry *dentry, void **cookie)
+proc_map_files_get_link(struct dentry *dentry,
+			struct inode *inode, void **cookie)
 {
 	if (!capable(CAP_SYS_ADMIN))
 		return ERR_PTR(-EPERM);
 
-	return proc_pid_follow_link(dentry, NULL);
+	return proc_pid_get_link(dentry, inode, NULL);
 }
 
 /*
- * Identical to proc_pid_link_inode_operations except for follow_link()
+ * Identical to proc_pid_link_inode_operations except for get_link()
  */
 static const struct inode_operations proc_map_files_link_inode_operations = {
 	.readlink	= proc_pid_readlink,
-	.follow_link	= proc_map_files_follow_link,
+	.get_link	= proc_map_files_get_link,
 	.setattr	= proc_setattr,
 };
 
@@ -1975,7 +1979,7 @@ proc_map_files_instantiate(struct inode *dir, struct dentry *dentry,
 		return -ENOENT;
 
 	ei = PROC_I(inode);
-	ei->op.proc_get_link = proc_map_files_get_link;
+	ei->op.proc_get_link = map_files_get_link;
 
 	inode->i_op = &proc_map_files_link_inode_operations;
 	inode->i_size = 64;
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index bd95b9f..10360b2 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -393,9 +393,10 @@ static const struct file_operations proc_reg_file_ops_no_compat = {
 };
 #endif
 
-static const char *proc_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	struct proc_dir_entry *pde = PDE(d_inode(dentry));
+	struct proc_dir_entry *pde = PDE(inode);
 	if (unlikely(!use_pde(pde)))
 		return ERR_PTR(-EINVAL);
 	*cookie = pde;
@@ -409,7 +410,7 @@ static void proc_put_link(struct inode *unused, void *p)
 
 const struct inode_operations proc_link_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= proc_follow_link,
+	.get_link	= proc_get_link,
 	.put_link	= proc_put_link,
 };
 
diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c
index f6e8354..63861c1 100644
--- a/fs/proc/namespaces.c
+++ b/fs/proc/namespaces.c
@@ -30,14 +30,17 @@ static const struct proc_ns_operations *ns_entries[] = {
 	&mntns_operations,
 };
 
-static const char *proc_ns_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_ns_get_link(struct dentry *dentry,
+				    struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops;
 	struct task_struct *task;
 	struct path ns_path;
 	void *error = ERR_PTR(-EACCES);
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	task = get_proc_task(inode);
 	if (!task)
 		return error;
@@ -74,7 +77,7 @@ static int proc_ns_readlink(struct dentry *dentry, char __user *buffer, int bufl
 
 static const struct inode_operations proc_ns_link_inode_operations = {
 	.readlink	= proc_ns_readlink,
-	.follow_link	= proc_ns_follow_link,
+	.get_link	= proc_ns_get_link,
 	.setattr	= proc_setattr,
 };
 
diff --git a/fs/proc/self.c b/fs/proc/self.c
index 113b8d0..bc599fb 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -18,12 +18,15 @@ static int proc_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static const char *proc_self_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_self_get_link(struct dentry *dentry,
+				      struct inode *inode, void **cookie)
 {
 	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	char *name;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
 	if (!tgid)
 		return ERR_PTR(-ENOENT);
 	/* 11 for max length of signed int in decimal + NULL term */
@@ -36,7 +39,7 @@ static const char *proc_self_follow_link(struct dentry *dentry, void **cookie)
 
 static const struct inode_operations proc_self_inode_operations = {
 	.readlink	= proc_self_readlink,
-	.follow_link	= proc_self_follow_link,
+	.get_link	= proc_self_get_link,
 	.put_link	= kfree_put_link,
 };
 
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 947b0f4..1c78507 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -19,13 +19,16 @@ static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static const char *proc_thread_self_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_thread_self_get_link(struct dentry *dentry,
+					     struct inode *inode, void **cookie)
 {
 	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	pid_t pid = task_pid_nr_ns(current, ns);
 	char *name;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
 	if (!pid)
 		return ERR_PTR(-ENOENT);
 	name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL);
@@ -37,7 +40,7 @@ static const char *proc_thread_self_follow_link(struct dentry *dentry, void **co
 
 static const struct inode_operations proc_thread_self_inode_operations = {
 	.readlink	= proc_thread_self_readlink,
-	.follow_link	= proc_thread_self_follow_link,
+	.get_link	= proc_thread_self_get_link,
 	.put_link	= kfree_put_link,
 };
 
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index e78f59b..091e2ac 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -1665,7 +1665,7 @@ const struct inode_operations reiserfs_dir_inode_operations = {
  */
 const struct inode_operations reiserfs_symlink_inode_operations = {
 	.readlink = generic_readlink,
-	.follow_link = page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link = page_put_link,
 	.setattr = reiserfs_setattr,
 	.setxattr = reiserfs_setxattr,
diff --git a/fs/squashfs/symlink.c b/fs/squashfs/symlink.c
index 12806df..7c635a5 100644
--- a/fs/squashfs/symlink.c
+++ b/fs/squashfs/symlink.c
@@ -119,7 +119,7 @@ const struct address_space_operations squashfs_symlink_aops = {
 
 const struct inode_operations squashfs_symlink_inode_ops = {
 	.readlink = generic_readlink,
-	.follow_link = page_follow_link_light,
+	.get_link = page_get_link,
 	.put_link = page_put_link,
 	.getxattr = generic_getxattr,
 	.listxattr = squashfs_listxattr
diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c
index 9a92895..cc7f559 100644
--- a/fs/sysv/inode.c
+++ b/fs/sysv/inode.c
@@ -146,7 +146,7 @@ static inline void write3byte(struct sysv_sb_info *sbi,
 
 static const struct inode_operations sysv_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= sysv_getattr,
 };
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index 0edc128..eff6280 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1608,7 +1608,7 @@ const struct inode_operations ubifs_file_inode_operations = {
 
 const struct inode_operations ubifs_symlink_inode_operations = {
 	.readlink    = generic_readlink,
-	.follow_link = simple_follow_link,
+	.get_link    = simple_get_link,
 	.setattr     = ubifs_setattr,
 	.getattr     = ubifs_getattr,
 	.setxattr    = ubifs_setxattr,
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 245268a..f638fd5 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -414,13 +414,17 @@ xfs_vn_rename(
  * uio is kmalloced for this reason...
  */
 STATIC const char *
-xfs_vn_follow_link(
+xfs_vn_get_link(
 	struct dentry		*dentry,
+	struct inode		*inode,
 	void			**cookie)
 {
 	char			*link;
 	int			error = -ENOMEM;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	link = kmalloc(MAXPATHLEN+1, GFP_KERNEL);
 	if (!link)
 		goto out_err;
@@ -1172,7 +1176,7 @@ static const struct inode_operations xfs_dir_ci_inode_operations = {
 
 static const struct inode_operations xfs_symlink_inode_operations = {
 	.readlink		= generic_readlink,
-	.follow_link		= xfs_vn_follow_link,
+	.get_link		= xfs_vn_get_link,
 	.put_link		= kfree_put_link,
 	.getattr		= xfs_vn_getattr,
 	.setattr		= xfs_vn_setattr,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 0b8d4d7..e2be8e9 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1633,7 +1633,7 @@ struct file_operations {
 
 struct inode_operations {
 	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
-	const char * (*follow_link) (struct dentry *, void **);
+	const char * (*get_link) (struct dentry *, struct inode *, void **);
 	int (*permission) (struct inode *, int);
 	struct posix_acl * (*get_acl)(struct inode *, int);
 
@@ -2736,7 +2736,7 @@ extern const struct file_operations generic_ro_fops;
 
 extern int readlink_copy(char __user *, int, const char *);
 extern int page_readlink(struct dentry *, char __user *, int);
-extern const char *page_follow_link_light(struct dentry *, void **);
+extern const char *page_get_link(struct dentry *, struct inode *, void **);
 extern void page_put_link(struct inode *, void *);
 extern int __page_symlink(struct inode *inode, const char *symname, int len,
 		int nofs);
@@ -2754,7 +2754,7 @@ void __inode_sub_bytes(struct inode *inode, loff_t bytes);
 void inode_sub_bytes(struct inode *inode, loff_t bytes);
 loff_t inode_get_bytes(struct inode *inode);
 void inode_set_bytes(struct inode *inode, loff_t bytes);
-const char *simple_follow_link(struct dentry *, void **);
+const char *simple_get_link(struct dentry *, struct inode *, void **);
 extern const struct inode_operations simple_symlink_inode_operations;
 
 extern int iterate_dir(struct file *, struct dir_context *);
diff --git a/mm/shmem.c b/mm/shmem.c
index 9e701be..e8220cf 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2496,10 +2496,14 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	return 0;
 }
 
-static const char *shmem_follow_link(struct dentry *dentry, void **cookie)
+static const char *shmem_get_link(struct dentry *dentry,
+				  struct inode *inode, void **cookie)
 {
 	struct page *page = NULL;
-	int error = shmem_getpage(d_inode(dentry), 0, &page, SGP_READ, NULL);
+	int error;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	error = shmem_getpage(inode, 0, &page, SGP_READ, NULL);
 	if (error)
 		return ERR_PTR(error);
 	unlock_page(page);
@@ -2656,7 +2660,7 @@ static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
 
 static const struct inode_operations shmem_short_symlink_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
+	.get_link	= simple_get_link,
 #ifdef CONFIG_TMPFS_XATTR
 	.setxattr	= shmem_setxattr,
 	.getxattr	= shmem_getxattr,
@@ -2667,7 +2671,7 @@ static const struct inode_operations shmem_short_symlink_operations = {
 
 static const struct inode_operations shmem_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= shmem_follow_link,
+	.get_link	= shmem_get_link,
 	.put_link	= shmem_put_link,
 #ifdef CONFIG_TMPFS_XATTR
 	.setxattr	= shmem_setxattr,
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 08/10] teach page_get_link() to work in RCU mode
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (6 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 07/10] [vfs] replace ->follow_link() with new method that could stay in RCU mode Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 09/10] teach shmem_get_link() " Al Viro
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

more or less along the lines of Neil's patchset, sans the insanity
around kmap().

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/namei.c            | 19 +++++++++++++------
 fs/proc/self.c        |  2 +-
 fs/proc/thread_self.c |  2 +-
 3 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 089c37f..76cc4a2 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4534,12 +4534,19 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode,
 	struct page *page;
 	struct address_space *mapping = inode->i_mapping;
 
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
-
-	page = read_mapping_page(mapping, 0, NULL);
-	if (IS_ERR(page))
-		return (char*)page;
+	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;
+	}
 	*cookie = page;
 	BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
 	kaddr = page_address(page);
diff --git a/fs/proc/self.c b/fs/proc/self.c
index bc599fb..9dd0ae6 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -21,7 +21,7 @@ static int proc_self_readlink(struct dentry *dentry, char __user *buffer,
 static const char *proc_self_get_link(struct dentry *dentry,
 				      struct inode *inode, void **cookie)
 {
-	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
+	struct pid_namespace *ns = inode->i_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	char *name;
 
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 1c78507..50eef6f 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -22,7 +22,7 @@ static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer,
 static const char *proc_thread_self_get_link(struct dentry *dentry,
 					     struct inode *inode, void **cookie)
 {
-	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
+	struct pid_namespace *ns = inode->i_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	pid_t pid = task_pid_nr_ns(current, ns);
 	char *name;
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 09/10] teach shmem_get_link() to work in RCU mode
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (7 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 08/10] teach page_get_link() to work " Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-11-17 23:00 ` [PATCH 10/10] teach proc_self_get_link()/proc_thread_self_get_link() " Al Viro
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 mm/shmem.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/mm/shmem.c b/mm/shmem.c
index e8220cf..e1ea5b5 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2501,12 +2501,20 @@ static const char *shmem_get_link(struct dentry *dentry,
 {
 	struct page *page = NULL;
 	int error;
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
-	error = shmem_getpage(inode, 0, &page, SGP_READ, NULL);
-	if (error)
-		return ERR_PTR(error);
-	unlock_page(page);
+	if (!dentry) {
+		page = find_get_page(inode->i_mapping, 0);
+		if (!page)
+			return ERR_PTR(-ECHILD);
+		if (!PageUptodate(page)) {
+			put_page(page);
+			return ERR_PTR(-ECHILD);
+		}
+	} else {
+		error = shmem_getpage(inode, 0, &page, SGP_READ, NULL);
+		if (error)
+			return ERR_PTR(error);
+		unlock_page(page);
+	}
 	*cookie = page;
 	return page_address(page);
 }
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH 10/10] teach proc_self_get_link()/proc_thread_self_get_link() to work in RCU mode
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (8 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 09/10] teach shmem_get_link() " Al Viro
@ 2015-11-17 23:00 ` Al Viro
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
  10 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-11-17 23:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/proc/self.c        | 8 +++-----
 fs/proc/thread_self.c | 9 ++++-----
 2 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/fs/proc/self.c b/fs/proc/self.c
index 9dd0ae6..7a8b19e 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -25,14 +25,12 @@ static const char *proc_self_get_link(struct dentry *dentry,
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	char *name;
 
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
 	if (!tgid)
 		return ERR_PTR(-ENOENT);
 	/* 11 for max length of signed int in decimal + NULL term */
-	name = kmalloc(12, GFP_KERNEL);
-	if (!name)
-		return ERR_PTR(-ENOMEM);
+	name = kmalloc(12, dentry ? GFP_KERNEL : GFP_ATOMIC);
+	if (unlikely(!name))
+		return dentry ? ERR_PTR(-ENOMEM) : ERR_PTR(-ECHILD);
 	sprintf(name, "%d", tgid);
 	return *cookie = name;
 }
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 50eef6f..03eaa84 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -27,13 +27,12 @@ static const char *proc_thread_self_get_link(struct dentry *dentry,
 	pid_t pid = task_pid_nr_ns(current, ns);
 	char *name;
 
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
 	if (!pid)
 		return ERR_PTR(-ENOENT);
-	name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL);
-	if (!name)
-		return ERR_PTR(-ENOMEM);
+	name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF,
+				dentry ? GFP_KERNEL : GFP_ATOMIC);
+	if (unlikely(!name))
+		return dentry ? ERR_PTR(-ENOMEM) : ERR_PTR(-ECHILD);
 	sprintf(name, "%d/task/%d", tgid, pid);
 	return *cookie = name;
 }
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH 06/10] [vfs] don't put symlink bodies in pagecache into highmem
  2015-11-17 23:00 ` [PATCH 06/10] [vfs] don't put symlink bodies in pagecache into highmem Al Viro
@ 2015-11-19 23:02   ` Dave Chinner
  0 siblings, 0 replies; 48+ messages in thread
From: Dave Chinner @ 2015-11-19 23:02 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On Tue, Nov 17, 2015 at 11:00:42PM +0000, Al Viro wrote:
> From: Al Viro <viro@zeniv.linux.org.uk>
> 
> kmap() in page_follow_link_light() needed to go - allowing to hold
> an arbitrary number of kmaps for long is a great way to deadlocking
> the system.
> 
> new helper (inode_nohigh(inode)) needs to be used for pagecache

inode_nohighmem() seems like a better name as it's self-documenting
at the call site.

Cheers,

Dave.
-- 
Dave Chinner
david@fromorbit.com

^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
                   ` (9 preceding siblings ...)
  2015-11-17 23:00 ` [PATCH 10/10] teach proc_self_get_link()/proc_thread_self_get_link() " Al Viro
@ 2015-12-09  5:32 ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 01/11] switch befs long symlinks to page_symlink_operations Al Viro
                     ` (12 more replies)
  10 siblings, 13 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:32 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-fsdevel, Linus Torvalds, Neil Brown

On Tue, Nov 17, 2015 at 10:57:52PM +0000, Al Viro wrote:

> 	Right now we stay in RCU mode for fast symlink traversal.
> However, anything trickier drops out of RCU mode - back in 4.2
> the symlink-related pile had grown too large to add this on top
> of everything else.  Below is an attempt to do that now.
> 
> Those who prefer to use git for review can find that series in
> git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git work.symlinks

Compared to the previous iteration:
	* rebased to 4.4-rc4.
	* NFS (in case of pagecache hit, of course) also stays in RCU mode.
	* renamed inode_nohigh() as suggested by dchinner, moved to
fs/inode.c - less header PITA that way.

What's there:
01/11: switch befs long symlinks to page_symlink_operations
02/11: logfs: don't duplicate page_symlink_inode_operations
03/11: udf: don't duplicate page_symlink_inode_operations
04/11: ufs: get rid of ->setattr() for symlinks
        Simplifying things a bit by switching them to page_symlink_operations
where possible.

05/11: namei: page_getlink() and page_follow_link_light() are the same thing
        Get rid of some code duplication

06/11: don't put symlink bodies in pagecache into highmem
        Bugfix for a long-standing mess.  For pagecache-based symlinks we end
up with kmap() of the body for the duration of traversal.  Which could take
a long time if we walk into e.g. slow automount along the way.  It's DoSable,
actually.  Not hard to fix - there's no reason to use GFP_HIGHUSER_MOVABLE
for those guys; GFP_USER serves just as well.

07/11: replace ->follow_link() with new method that could stay
        Meat of the series - we switch to a new method (->get_link()) that
differs from ->follow_link() in getting dentry and inode separately; it
can be called in RCU mode (with NULL dentry) and it should bail out with
ERR_PTR(-ECHILD) if it needs non-RCU.  All instances converted, most of them
by making them bail out immediately in RCU mode.  Some are trivialy
RCU-safe, though; those do not bail out at all.

08/11: teach page_get_link() to work in RCU mode
09/11: teach shmem_get_link() to work in RCU mode
10/11: teach proc_self_get_link()/proc_thread_self_get_link()
11/11: teach nfs_get_link() to work in RCU mode
        Teaching more instances to stay in RCU mode if they can.  (8) and
(11) are similar to what Neil had done back in March, except for the lack of
kmap mess to deal with - that's one of the benefits of having (6) done...

	Missing, probably worth adding: XFS, overlayfs.  Maybe gfs2 - if it's
at all possible (not sure if it is).  Maybe autofs4 as well.

	Procfs magical symlinks definitely are *not* worth trying to handle
in RCU mode - ->d_revalidate() on them is such that it'll kick us out of
RCU mode before we even see that it's a symlink, let alone try to follow it.

Any help with review and testing would be welcome.  FWIW, it seems to survive
the beating here.

^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH v2 01/11] switch befs long symlinks to page_symlink_operations
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 02/11] logfs: don't duplicate page_symlink_inode_operations Al Viro
                     ` (11 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

just give them the right ->readpage()...

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/befs/linuxvfs.c | 41 ++++++++++++++++++++++-------------------
 1 file changed, 22 insertions(+), 19 deletions(-)

diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c
index 46aedac..1c8b0dc 100644
--- a/fs/befs/linuxvfs.c
+++ b/fs/befs/linuxvfs.c
@@ -42,7 +42,7 @@ static struct inode *befs_iget(struct super_block *, unsigned long);
 static struct inode *befs_alloc_inode(struct super_block *sb);
 static void befs_destroy_inode(struct inode *inode);
 static void befs_destroy_inodecache(void);
-static const char *befs_follow_link(struct dentry *, void **);
+static int befs_symlink_readpage(struct file *, struct page *);
 static int befs_utf2nls(struct super_block *sb, const char *in, int in_len,
 			char **out, int *out_len);
 static int befs_nls2utf(struct super_block *sb, const char *in, int in_len,
@@ -79,10 +79,8 @@ static const struct address_space_operations befs_aops = {
 	.bmap		= befs_bmap,
 };
 
-static const struct inode_operations befs_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= befs_follow_link,
-	.put_link	= kfree_put_link,
+static const struct address_space_operations befs_symlink_aops = {
+	.readpage	= befs_symlink_readpage,
 };
 
 /* 
@@ -398,7 +396,8 @@ static struct inode *befs_iget(struct super_block *sb, unsigned long ino)
 		inode->i_fop = &befs_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
-			inode->i_op = &befs_symlink_inode_operations;
+			inode->i_op = &page_symlink_inode_operations;
+			inode->i_mapping->a_ops = &befs_symlink_aops;
 		} else {
 			inode->i_link = befs_ino->i_data.symlink;
 			inode->i_op = &simple_symlink_inode_operations;
@@ -463,31 +462,35 @@ befs_destroy_inodecache(void)
  * The data stream become link name. Unless the LONG_SYMLINK
  * flag is set.
  */
-static const char *
-befs_follow_link(struct dentry *dentry, void **cookie)
+static int befs_symlink_readpage(struct file *unused, struct page *page)
 {
-	struct super_block *sb = dentry->d_sb;
-	struct befs_inode_info *befs_ino = BEFS_I(d_inode(dentry));
+	struct inode *inode = page->mapping->host;
+	struct super_block *sb = inode->i_sb;
+	struct befs_inode_info *befs_ino = BEFS_I(inode);
 	befs_data_stream *data = &befs_ino->i_data.ds;
 	befs_off_t len = data->size;
-	char *link;
+	char *link = kmap(page);
 
-	if (len == 0) {
+	if (len == 0 || len > PAGE_SIZE) {
 		befs_error(sb, "Long symlink with illegal length");
-		return ERR_PTR(-EIO);
+		goto fail;
 	}
 	befs_debug(sb, "Follow long symlink");
 
-	link = kmalloc(len, GFP_NOFS);
-	if (!link)
-		return ERR_PTR(-ENOMEM);
 	if (befs_read_lsymlink(sb, data, link, len) != len) {
-		kfree(link);
 		befs_error(sb, "Failed to read entire long symlink");
-		return ERR_PTR(-EIO);
+		goto fail;
 	}
 	link[len - 1] = '\0';
-	return *cookie = link;
+	SetPageUptodate(page);
+	kunmap(page);
+	unlock_page(page);
+	return 0;
+fail:
+	SetPageError(page);
+	kunmap(page);
+	unlock_page(page);
+	return -EIO;
 }
 
 /*
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 02/11] logfs: don't duplicate page_symlink_inode_operations
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
  2015-12-09  5:34   ` [PATCH v2 01/11] switch befs long symlinks to page_symlink_operations Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 03/11] udf: " Al Viro
                     ` (10 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/logfs/dir.c   | 8 +-------
 fs/logfs/inode.c | 2 +-
 fs/logfs/logfs.h | 1 -
 3 files changed, 2 insertions(+), 9 deletions(-)

diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c
index f9b45d4..99944a4 100644
--- a/fs/logfs/dir.c
+++ b/fs/logfs/dir.c
@@ -528,7 +528,7 @@ static int logfs_symlink(struct inode *dir, struct dentry *dentry,
 	if (IS_ERR(inode))
 		return PTR_ERR(inode);
 
-	inode->i_op = &logfs_symlink_iops;
+	inode->i_op = &page_symlink_inode_operations;
 	inode->i_mapping->a_ops = &logfs_reg_aops;
 
 	return __logfs_create(dir, dentry, inode, target, destlen);
@@ -776,12 +776,6 @@ fail:
 	return -EIO;
 }
 
-const struct inode_operations logfs_symlink_iops = {
-	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
-	.put_link	= page_put_link,
-};
-
 const struct inode_operations logfs_dir_iops = {
 	.create		= logfs_create,
 	.link		= logfs_link,
diff --git a/fs/logfs/inode.c b/fs/logfs/inode.c
index af49e2d..06baa92 100644
--- a/fs/logfs/inode.c
+++ b/fs/logfs/inode.c
@@ -64,7 +64,7 @@ static void logfs_inode_setops(struct inode *inode)
 		inode->i_mapping->a_ops = &logfs_reg_aops;
 		break;
 	case S_IFLNK:
-		inode->i_op = &logfs_symlink_iops;
+		inode->i_op = &page_symlink_inode_operations;
 		inode->i_mapping->a_ops = &logfs_reg_aops;
 		break;
 	case S_IFSOCK:	/* fall through */
diff --git a/fs/logfs/logfs.h b/fs/logfs/logfs.h
index 5f09376..209a26d 100644
--- a/fs/logfs/logfs.h
+++ b/fs/logfs/logfs.h
@@ -495,7 +495,6 @@ static inline int logfs_get_sb_mtd(struct logfs_super *s, int mtdnr)
 #endif
 
 /* dir.c */
-extern const struct inode_operations logfs_symlink_iops;
 extern const struct inode_operations logfs_dir_iops;
 extern const struct file_operations logfs_dir_fops;
 int logfs_replay_journal(struct super_block *sb);
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 03/11] udf: don't duplicate page_symlink_inode_operations
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
  2015-12-09  5:34   ` [PATCH v2 01/11] switch befs long symlinks to page_symlink_operations Al Viro
  2015-12-09  5:34   ` [PATCH v2 02/11] logfs: don't duplicate page_symlink_inode_operations Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 04/11] ufs: get rid of ->setattr() for symlinks Al Viro
                     ` (9 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/udf/inode.c   | 2 +-
 fs/udf/namei.c   | 7 +------
 fs/udf/udfdecl.h | 1 -
 3 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index 8d0b3ad..8675c2b 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -1540,7 +1540,7 @@ reread:
 		break;
 	case ICBTAG_FILE_TYPE_SYMLINK:
 		inode->i_data.a_ops = &udf_symlink_aops;
-		inode->i_op = &udf_symlink_inode_operations;
+		inode->i_op = &page_symlink_inode_operations;
 		inode->i_mode = S_IFLNK | S_IRWXUGO;
 		break;
 	case ICBTAG_FILE_TYPE_MAIN:
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index c97b5a8..d0e6de1 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -921,7 +921,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
 	}
 
 	inode->i_data.a_ops = &udf_symlink_aops;
-	inode->i_op = &udf_symlink_inode_operations;
+	inode->i_op = &page_symlink_inode_operations;
 
 	if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
 		struct kernel_lb_addr eloc;
@@ -1344,8 +1344,3 @@ const struct inode_operations udf_dir_inode_operations = {
 	.rename				= udf_rename,
 	.tmpfile			= udf_tmpfile,
 };
-const struct inode_operations udf_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
-	.put_link	= page_put_link,
-};
diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h
index 47bb3f5..ce169b4 100644
--- a/fs/udf/udfdecl.h
+++ b/fs/udf/udfdecl.h
@@ -85,7 +85,6 @@ extern const struct inode_operations udf_dir_inode_operations;
 extern const struct file_operations udf_dir_operations;
 extern const struct inode_operations udf_file_inode_operations;
 extern const struct file_operations udf_file_operations;
-extern const struct inode_operations udf_symlink_inode_operations;
 extern const struct address_space_operations udf_aops;
 extern const struct address_space_operations udf_adinicb_aops;
 extern const struct address_space_operations udf_symlink_aops;
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 04/11] ufs: get rid of ->setattr() for symlinks
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (2 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 03/11] udf: " Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 05/11] namei: page_getlink() and page_follow_link_light() are the same thing Al Viro
                     ` (8 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

It was to needed for a couple of months in 2010, until UFS
quota support got dropped.  Since then it's equivalent to
simple_setattr() (i.e. the default) for everything except the
regular files.  And dropping it there allows to convert all
UFS symlinks to {page,simple}_symlink_inode_operations, getting
rid of fs/ufs/symlink.c completely.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/ufs/Makefile  |  2 +-
 fs/ufs/inode.c   |  4 ++--
 fs/ufs/namei.c   |  4 ++--
 fs/ufs/symlink.c | 42 ------------------------------------------
 fs/ufs/ufs.h     |  4 ----
 5 files changed, 5 insertions(+), 51 deletions(-)
 delete mode 100644 fs/ufs/symlink.c

diff --git a/fs/ufs/Makefile b/fs/ufs/Makefile
index 392db25..ec4a6b4 100644
--- a/fs/ufs/Makefile
+++ b/fs/ufs/Makefile
@@ -5,5 +5,5 @@
 obj-$(CONFIG_UFS_FS) += ufs.o
 
 ufs-objs := balloc.o cylinder.o dir.o file.o ialloc.o inode.o \
-	    namei.o super.o symlink.o util.o
+	    namei.o super.o util.o
 ccflags-$(CONFIG_UFS_DEBUG)    += -DDEBUG
diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c
index a064cf44..737160a 100644
--- a/fs/ufs/inode.c
+++ b/fs/ufs/inode.c
@@ -528,11 +528,11 @@ static void ufs_set_inode_ops(struct inode *inode)
 		inode->i_mapping->a_ops = &ufs_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (!inode->i_blocks) {
-			inode->i_op = &ufs_fast_symlink_inode_operations;
 			inode->i_link = (char *)UFS_I(inode)->i_u1.i_symlink;
+			inode->i_op = &simple_symlink_inode_operations;
 		} else {
-			inode->i_op = &ufs_symlink_inode_operations;
 			inode->i_mapping->a_ops = &ufs_aops;
+			inode->i_op = &page_symlink_inode_operations;
 		}
 	} else
 		init_special_inode(inode, inode->i_mode,
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 4796655..24b0cbd 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -123,14 +123,14 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry,
 
 	if (l > UFS_SB(sb)->s_uspi->s_maxsymlinklen) {
 		/* slow symlink */
-		inode->i_op = &ufs_symlink_inode_operations;
+		inode->i_op = &page_symlink_inode_operations;
 		inode->i_mapping->a_ops = &ufs_aops;
 		err = page_symlink(inode, symname, l);
 		if (err)
 			goto out_fail;
 	} else {
 		/* fast symlink */
-		inode->i_op = &ufs_fast_symlink_inode_operations;
+		inode->i_op = &simple_symlink_inode_operations;
 		inode->i_link = (char *)UFS_I(inode)->i_u1.i_symlink;
 		memcpy(inode->i_link, symname, l);
 		inode->i_size = l-1;
diff --git a/fs/ufs/symlink.c b/fs/ufs/symlink.c
deleted file mode 100644
index 874480b..0000000
--- a/fs/ufs/symlink.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *  linux/fs/ufs/symlink.c
- *
- * Only fast symlinks left here - the rest is done by generic code. AV, 1999
- *
- * Copyright (C) 1998
- * Daniel Pirkl <daniel.pirkl@emai.cz>
- * Charles University, Faculty of Mathematics and Physics
- *
- *  from
- *
- *  linux/fs/ext2/symlink.c
- *
- * Copyright (C) 1992, 1993, 1994, 1995
- * Remy Card (card@masi.ibp.fr)
- * Laboratoire MASI - Institut Blaise Pascal
- * Universite Pierre et Marie Curie (Paris VI)
- *
- *  from
- *
- *  linux/fs/minix/symlink.c
- *
- *  Copyright (C) 1991, 1992  Linus Torvalds
- *
- *  ext2 symlink handling code
- */
-
-#include "ufs_fs.h"
-#include "ufs.h"
-
-const struct inode_operations ufs_fast_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
-	.setattr	= ufs_setattr,
-};
-
-const struct inode_operations ufs_symlink_inode_operations = {
-	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
-	.put_link	= page_put_link,
-	.setattr	= ufs_setattr,
-};
diff --git a/fs/ufs/ufs.h b/fs/ufs/ufs.h
index 7da4aca..c87f4c3 100644
--- a/fs/ufs/ufs.h
+++ b/fs/ufs/ufs.h
@@ -136,10 +136,6 @@ extern __printf(3, 4)
 void ufs_panic(struct super_block *, const char *, const char *, ...);
 void ufs_mark_sb_dirty(struct super_block *sb);
 
-/* symlink.c */
-extern const struct inode_operations ufs_fast_symlink_inode_operations;
-extern const struct inode_operations ufs_symlink_inode_operations;
-
 static inline struct ufs_sb_info *UFS_SB(struct super_block *sb)
 {
 	return sb->s_fs_info;
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 05/11] namei: page_getlink() and page_follow_link_light() are the same thing
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (3 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 04/11] ufs: get rid of ->setattr() for symlinks Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem Al Viro
                     ` (7 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/namei.c | 32 +++++++++++++-------------------
 1 file changed, 13 insertions(+), 19 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 0c3974c..4bae5cb 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4518,7 +4518,7 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 EXPORT_SYMBOL(generic_readlink);
 
 /* get the link contents into pagecache */
-static char *page_getlink(struct dentry * dentry, struct page **ppage)
+static const char *page_getlink(struct dentry * dentry, void **cookie)
 {
 	char *kaddr;
 	struct page *page;
@@ -4526,31 +4526,15 @@ static char *page_getlink(struct dentry * dentry, struct page **ppage)
 	page = read_mapping_page(mapping, 0, NULL);
 	if (IS_ERR(page))
 		return (char*)page;
-	*ppage = page;
+	*cookie = page;
 	kaddr = kmap(page);
 	nd_terminate_link(kaddr, dentry->d_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);
-
 const char *page_follow_link_light(struct dentry *dentry, void **cookie)
 {
-	struct page *page = NULL;
-	char *res = page_getlink(dentry, &page);
-	if (!IS_ERR(res))
-		*cookie = page;
-	return res;
+	return page_getlink(dentry, cookie);
 }
 EXPORT_SYMBOL(page_follow_link_light);
 
@@ -4562,6 +4546,16 @@ void page_put_link(struct inode *unused, void *cookie)
 }
 EXPORT_SYMBOL(page_put_link);
 
+int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
+{
+	void *cookie = NULL;
+	int res = readlink_copy(buffer, buflen, page_getlink(dentry, &cookie));
+	if (cookie)
+		page_put_link(NULL, cookie);
+	return res;
+}
+EXPORT_SYMBOL(page_readlink);
+
 /*
  * The nofs argument instructs pagecache_write_begin to pass AOP_FLAG_NOFS
  */
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (4 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 05/11] namei: page_getlink() and page_follow_link_light() are the same thing Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2016-01-14 13:22     ` Tomeu Vizoso
  2015-12-09  5:34   ` [PATCH v2 07/11] replace ->follow_link() with new method that could stay in RCU mode Al Viro
                     ` (6 subsequent siblings)
  12 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

kmap() in page_follow_link_light() needed to go - allowing to hold
an arbitrary number of kmaps for long is a great way to deadlocking
the system.

new helper (inode_nohighmem(inode)) needs to be used for pagecache
symlinks inodes; done for all in-tree cases.  page_follow_link_light()
instrumented to yell about anything missed.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 Documentation/filesystems/porting |  5 +++++
 fs/affs/inode.c                   |  1 +
 fs/affs/namei.c                   |  1 +
 fs/affs/symlink.c                 |  4 +---
 fs/afs/inode.c                    |  1 +
 fs/befs/linuxvfs.c                |  5 ++---
 fs/btrfs/inode.c                  |  2 ++
 fs/coda/cnode.c                   |  2 ++
 fs/coda/symlink.c                 |  4 +---
 fs/cramfs/inode.c                 |  1 +
 fs/efs/inode.c                    |  1 +
 fs/efs/symlink.c                  |  4 +---
 fs/exofs/inode.c                  |  1 +
 fs/exofs/namei.c                  |  1 +
 fs/ext2/inode.c                   |  1 +
 fs/ext2/namei.c                   |  1 +
 fs/ext4/inode.c                   |  1 +
 fs/ext4/namei.c                   |  1 +
 fs/ext4/symlink.c                 | 10 +++-------
 fs/f2fs/inode.c                   |  1 +
 fs/f2fs/namei.c                   |  5 ++---
 fs/freevxfs/vxfs_inode.c          |  1 +
 fs/hfsplus/inode.c                |  2 ++
 fs/hpfs/inode.c                   |  1 +
 fs/hpfs/namei.c                   |  5 ++---
 fs/hugetlbfs/inode.c              |  1 +
 fs/inode.c                        |  6 ++++++
 fs/isofs/inode.c                  |  1 +
 fs/isofs/rock.c                   |  4 +---
 fs/jfs/inode.c                    |  1 +
 fs/jfs/namei.c                    |  1 +
 fs/logfs/dir.c                    |  1 +
 fs/logfs/inode.c                  |  1 +
 fs/minix/inode.c                  |  1 +
 fs/namei.c                        |  9 +++------
 fs/ncpfs/inode.c                  |  1 +
 fs/nfs/inode.c                    |  5 +++--
 fs/nfs/symlink.c                  |  2 +-
 fs/nilfs2/inode.c                 |  1 +
 fs/nilfs2/namei.c                 |  1 +
 fs/ocfs2/inode.c                  |  1 +
 fs/ocfs2/namei.c                  |  1 +
 fs/qnx4/inode.c                   |  1 +
 fs/qnx6/inode.c                   |  1 +
 fs/ramfs/inode.c                  |  1 +
 fs/reiserfs/inode.c               |  1 +
 fs/reiserfs/namei.c               |  1 +
 fs/romfs/super.c                  |  1 +
 fs/squashfs/inode.c               |  2 ++
 fs/sysv/inode.c                   |  1 +
 fs/udf/inode.c                    |  1 +
 fs/udf/namei.c                    |  1 +
 fs/udf/symlink.c                  |  4 +---
 fs/ufs/inode.c                    |  1 +
 fs/ufs/namei.c                    |  1 +
 include/linux/fs.h                |  1 +
 mm/shmem.c                        |  9 +++------
 57 files changed, 81 insertions(+), 46 deletions(-)

diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting
index f24d1b8..3eb7c35 100644
--- a/Documentation/filesystems/porting
+++ b/Documentation/filesystems/porting
@@ -504,3 +504,8 @@ in your dentry operations instead.
 [mandatory]
 	__fd_install() & fd_install() can now sleep. Callers should not
 	hold a spinlock	or other resources that do not allow a schedule.
+--
+[mandatory]
+	any symlink that might use page_follow_link_light/page_put_link() must
+	have inode_nohighmem(inode) called before anything might start playing with
+	its pagecache.
diff --git a/fs/affs/inode.c b/fs/affs/inode.c
index 1734950..0fdb0f5 100644
--- a/fs/affs/inode.c
+++ b/fs/affs/inode.c
@@ -140,6 +140,7 @@ struct inode *affs_iget(struct super_block *sb, unsigned long ino)
 		break;
 	case ST_SOFTLINK:
 		inode->i_mode |= S_IFLNK;
+		inode_nohighmem(inode);
 		inode->i_op = &affs_symlink_inode_operations;
 		inode->i_data.a_ops = &affs_symlink_aops;
 		break;
diff --git a/fs/affs/namei.c b/fs/affs/namei.c
index 181e05b..00d3002 100644
--- a/fs/affs/namei.c
+++ b/fs/affs/namei.c
@@ -344,6 +344,7 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 		return -ENOSPC;
 
 	inode->i_op = &affs_symlink_inode_operations;
+	inode_nohighmem(inode);
 	inode->i_data.a_ops = &affs_symlink_aops;
 	inode->i_mode = S_IFLNK | 0777;
 	mode_to_prot(inode);
diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
index ea5b69a..e3f9dc3 100644
--- a/fs/affs/symlink.c
+++ b/fs/affs/symlink.c
@@ -14,7 +14,7 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
 {
 	struct buffer_head *bh;
 	struct inode *inode = page->mapping->host;
-	char *link = kmap(page);
+	char *link = page_address(page);
 	struct slink_front *lf;
 	int			 i, j;
 	char			 c;
@@ -57,12 +57,10 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
 	link[i] = '\0';
 	affs_brelse(bh);
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return -EIO;
 }
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index e06f5a2..86cc726 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -56,6 +56,7 @@ static int afs_inode_map_status(struct afs_vnode *vnode, struct key *key)
 	case AFS_FTYPE_SYMLINK:
 		inode->i_mode	= S_IFLNK | vnode->status.mode;
 		inode->i_op	= &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		break;
 	default:
 		printk("kAFS: AFS vnode with undefined type\n");
diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c
index 1c8b0dc..25250fa 100644
--- a/fs/befs/linuxvfs.c
+++ b/fs/befs/linuxvfs.c
@@ -397,6 +397,7 @@ static struct inode *befs_iget(struct super_block *sb, unsigned long ino)
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 			inode->i_mapping->a_ops = &befs_symlink_aops;
 		} else {
 			inode->i_link = befs_ino->i_data.symlink;
@@ -469,7 +470,7 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
 	struct befs_inode_info *befs_ino = BEFS_I(inode);
 	befs_data_stream *data = &befs_ino->i_data.ds;
 	befs_off_t len = data->size;
-	char *link = kmap(page);
+	char *link = page_address(page);
 
 	if (len == 0 || len > PAGE_SIZE) {
 		befs_error(sb, "Long symlink with illegal length");
@@ -483,12 +484,10 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
 	}
 	link[len - 1] = '\0';
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return -EIO;
 }
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index a70c579..70f98bf 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3774,6 +3774,7 @@ cache_acl:
 		break;
 	case S_IFLNK:
 		inode->i_op = &btrfs_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &btrfs_symlink_aops;
 		break;
 	default:
@@ -9705,6 +9706,7 @@ static int btrfs_symlink(struct inode *dir, struct dentry *dentry,
 	btrfs_free_path(path);
 
 	inode->i_op = &btrfs_symlink_inode_operations;
+	inode_nohighmem(inode);
 	inode->i_mapping->a_ops = &btrfs_symlink_aops;
 	inode_set_bytes(inode, name_len);
 	btrfs_i_size_write(inode, name_len);
diff --git a/fs/coda/cnode.c b/fs/coda/cnode.c
index 7740b1c..dd6a79e 100644
--- a/fs/coda/cnode.c
+++ b/fs/coda/cnode.c
@@ -8,6 +8,7 @@
 
 #include <linux/coda.h>
 #include <linux/coda_psdev.h>
+#include <linux/pagemap.h>
 #include "coda_linux.h"
 
 static inline int coda_fideq(struct CodaFid *fid1, struct CodaFid *fid2)
@@ -35,6 +36,7 @@ static void coda_fill_inode(struct inode *inode, struct coda_vattr *attr)
                 inode->i_fop = &coda_dir_operations;
         } else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &coda_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_data.a_ops = &coda_symlink_aops;
 		inode->i_mapping = &inode->i_data;
 	} else
diff --git a/fs/coda/symlink.c b/fs/coda/symlink.c
index ab94ef6..03736e2 100644
--- a/fs/coda/symlink.c
+++ b/fs/coda/symlink.c
@@ -26,7 +26,7 @@ static int coda_symlink_filler(struct file *file, struct page *page)
 	int error;
 	struct coda_inode_info *cii;
 	unsigned int len = PAGE_SIZE;
-	char *p = kmap(page);
+	char *p = page_address(page);
 
 	cii = ITOC(inode);
 
@@ -34,13 +34,11 @@ static int coda_symlink_filler(struct file *file, struct page *page)
 	if (error)
 		goto fail;
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return error;
 }
diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c
index 355c522..b862bc2 100644
--- a/fs/cramfs/inode.c
+++ b/fs/cramfs/inode.c
@@ -100,6 +100,7 @@ static struct inode *get_cramfs_inode(struct super_block *sb,
 		break;
 	case S_IFLNK:
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_data.a_ops = &cramfs_aops;
 		break;
 	default:
diff --git a/fs/efs/inode.c b/fs/efs/inode.c
index 079d203..cdf0872 100644
--- a/fs/efs/inode.c
+++ b/fs/efs/inode.c
@@ -151,6 +151,7 @@ struct inode *efs_iget(struct super_block *super, unsigned long ino)
 			break;
 		case S_IFLNK:
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 			inode->i_data.a_ops = &efs_symlink_aops;
 			break;
 		case S_IFCHR:
diff --git a/fs/efs/symlink.c b/fs/efs/symlink.c
index 75117d0..4870cc8 100644
--- a/fs/efs/symlink.c
+++ b/fs/efs/symlink.c
@@ -13,7 +13,7 @@
 
 static int efs_symlink_readpage(struct file *file, struct page *page)
 {
-	char *link = kmap(page);
+	char *link = page_address(page);
 	struct buffer_head * bh;
 	struct inode * inode = page->mapping->host;
 	efs_block_t size = inode->i_size;
@@ -39,12 +39,10 @@ static int efs_symlink_readpage(struct file *file, struct page *page)
 	}
 	link[size] = '\0';
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 fail:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return err;
 }
diff --git a/fs/exofs/inode.c b/fs/exofs/inode.c
index 73c64da..d8e9c181 100644
--- a/fs/exofs/inode.c
+++ b/fs/exofs/inode.c
@@ -1227,6 +1227,7 @@ struct inode *exofs_iget(struct super_block *sb, unsigned long ino)
 			inode->i_link = (char *)oi->i_data;
 		} else {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 			inode->i_mapping->a_ops = &exofs_aops;
 		}
 	} else {
diff --git a/fs/exofs/namei.c b/fs/exofs/namei.c
index 994e078..c20d77d 100644
--- a/fs/exofs/namei.c
+++ b/fs/exofs/namei.c
@@ -111,6 +111,7 @@ static int exofs_symlink(struct inode *dir, struct dentry *dentry,
 	if (l > sizeof(oi->i_data)) {
 		/* slow symlink */
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &exofs_aops;
 		memset(oi->i_data, 0, sizeof(oi->i_data));
 
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 0aa9bf6..338eefd 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -1420,6 +1420,7 @@ struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
 				sizeof(ei->i_data) - 1);
 		} else {
 			inode->i_op = &ext2_symlink_inode_operations;
+			inode_nohighmem(inode);
 			if (test_opt(inode->i_sb, NOBH))
 				inode->i_mapping->a_ops = &ext2_nobh_aops;
 			else
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index 3267a80d..7a2be8f 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -183,6 +183,7 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry,
 	if (l > sizeof (EXT2_I(inode)->i_data)) {
 		/* slow symlink */
 		inode->i_op = &ext2_symlink_inode_operations;
+		inode_nohighmem(inode);
 		if (test_opt(inode->i_sb, NOBH))
 			inode->i_mapping->a_ops = &ext2_nobh_aops;
 		else
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index ea433a7..b3bd912 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4283,6 +4283,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 			inode->i_op = &ext4_symlink_inode_operations;
 			ext4_set_aops(inode);
 		}
+		inode_nohighmem(inode);
 	} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
 	      S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
 		inode->i_op = &ext4_special_inode_operations;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index a969ab3..f27e0c2 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3132,6 +3132,7 @@ static int ext4_symlink(struct inode *dir,
 	if ((disk_link.len > EXT4_N_BLOCKS * 4)) {
 		if (!encryption_required)
 			inode->i_op = &ext4_symlink_inode_operations;
+		inode_nohighmem(inode);
 		ext4_set_aops(inode);
 		/*
 		 * We cannot call page_symlink() with transaction started
diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
index abe2401..0e6dc44 100644
--- a/fs/ext4/symlink.c
+++ b/fs/ext4/symlink.c
@@ -45,7 +45,7 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
 		cpage = read_mapping_page(inode->i_mapping, 0, NULL);
 		if (IS_ERR(cpage))
 			return ERR_CAST(cpage);
-		caddr = kmap(cpage);
+		caddr = page_address(cpage);
 		caddr[size] = 0;
 	}
 
@@ -75,16 +75,12 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
 	/* Null-terminate the name */
 	if (res <= plen)
 		paddr[res] = '\0';
-	if (cpage) {
-		kunmap(cpage);
+	if (cpage)
 		page_cache_release(cpage);
-	}
 	return *cookie = paddr;
 errout:
-	if (cpage) {
-		kunmap(cpage);
+	if (cpage)
 		page_cache_release(cpage);
-	}
 	kfree(paddr);
 	return ERR_PTR(res);
 }
diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
index 97e20de..5528801 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -202,6 +202,7 @@ make_now:
 			inode->i_op = &f2fs_encrypted_symlink_inode_operations;
 		else
 			inode->i_op = &f2fs_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &f2fs_dblock_aops;
 	} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
 			S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index 2c32110..484df68 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -351,6 +351,7 @@ static int f2fs_symlink(struct inode *dir, struct dentry *dentry,
 		inode->i_op = &f2fs_encrypted_symlink_inode_operations;
 	else
 		inode->i_op = &f2fs_symlink_inode_operations;
+	inode_nohighmem(inode);
 	inode->i_mapping->a_ops = &f2fs_dblock_aops;
 
 	f2fs_lock_op(sbi);
@@ -942,7 +943,7 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
 	cpage = read_mapping_page(inode->i_mapping, 0, NULL);
 	if (IS_ERR(cpage))
 		return ERR_CAST(cpage);
-	caddr = kmap(cpage);
+	caddr = page_address(cpage);
 	caddr[size] = 0;
 
 	/* Symlink is encrypted */
@@ -982,13 +983,11 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
 	/* Null-terminate the name */
 	paddr[res] = '\0';
 
-	kunmap(cpage);
 	page_cache_release(cpage);
 	return *cookie = paddr;
 errout:
 	kfree(cstr.name);
 	f2fs_fname_crypto_free_buffer(&pstr);
-	kunmap(cpage);
 	page_cache_release(cpage);
 	return ERR_PTR(res);
 }
diff --git a/fs/freevxfs/vxfs_inode.c b/fs/freevxfs/vxfs_inode.c
index ef73ed6..3e2ccad 100644
--- a/fs/freevxfs/vxfs_inode.c
+++ b/fs/freevxfs/vxfs_inode.c
@@ -326,6 +326,7 @@ vxfs_iget(struct super_block *sbp, ino_t ino)
 	} else if (S_ISLNK(ip->i_mode)) {
 		if (!VXFS_ISIMMED(vip)) {
 			ip->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(ip);
 			ip->i_mapping->a_ops = &vxfs_aops;
 		} else {
 			ip->i_op = &simple_symlink_inode_operations;
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index 6dd107d..19b33f8 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -403,6 +403,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, umode_t mode)
 	} else if (S_ISLNK(inode->i_mode)) {
 		sbi->file_count++;
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &hfsplus_aops;
 		hip->clump_blocks = 1;
 	} else
@@ -526,6 +527,7 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)
 			inode->i_mapping->a_ops = &hfsplus_aops;
 		} else if (S_ISLNK(inode->i_mode)) {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 			inode->i_mapping->a_ops = &hfsplus_aops;
 		} else {
 			init_special_inode(inode, inode->i_mode,
diff --git a/fs/hpfs/inode.c b/fs/hpfs/inode.c
index 933c737..1f3c6d7 100644
--- a/fs/hpfs/inode.c
+++ b/fs/hpfs/inode.c
@@ -77,6 +77,7 @@ void hpfs_read_inode(struct inode *i)
 			kfree(ea);
 			i->i_mode = S_IFLNK | 0777;
 			i->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(i);
 			i->i_data.a_ops = &hpfs_symlink_aops;
 			set_nlink(i, 1);
 			i->i_size = ea_size;
diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c
index ae4d5a1..506765a 100644
--- a/fs/hpfs/namei.c
+++ b/fs/hpfs/namei.c
@@ -332,6 +332,7 @@ static int hpfs_symlink(struct inode *dir, struct dentry *dentry, const char *sy
 	result->i_blocks = 1;
 	set_nlink(result, 1);
 	result->i_size = strlen(symlink);
+	inode_nohighmem(result);
 	result->i_op = &page_symlink_inode_operations;
 	result->i_data.a_ops = &hpfs_symlink_aops;
 
@@ -500,7 +501,7 @@ out:
 
 static int hpfs_symlink_readpage(struct file *file, struct page *page)
 {
-	char *link = kmap(page);
+	char *link = page_address(page);
 	struct inode *i = page->mapping->host;
 	struct fnode *fnode;
 	struct buffer_head *bh;
@@ -516,14 +517,12 @@ static int hpfs_symlink_readpage(struct file *file, struct page *page)
 		goto fail;
 	hpfs_unlock(i->i_sb);
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
 fail:
 	hpfs_unlock(i->i_sb);
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return err;
 }
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index de4bdfa..d8f51ee 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -760,6 +760,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb,
 			break;
 		case S_IFLNK:
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 			break;
 		}
 		lockdep_annotate_inode_mutex_key(inode);
diff --git a/fs/inode.c b/fs/inode.c
index 1be5f90..5bb85a0 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2028,3 +2028,9 @@ void inode_set_flags(struct inode *inode, unsigned int flags,
 				  new_flags) != old_flags));
 }
 EXPORT_SYMBOL(inode_set_flags);
+
+void inode_nohighmem(struct inode *inode)
+{
+	mapping_set_gfp_mask(inode->i_mapping, GFP_USER);
+}
+EXPORT_SYMBOL(inode_nohighmem);
diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
index d67a16f..61abdc4 100644
--- a/fs/isofs/inode.c
+++ b/fs/isofs/inode.c
@@ -1417,6 +1417,7 @@ static int isofs_read_inode(struct inode *inode, int relocated)
 		inode->i_fop = &isofs_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_data.a_ops = &isofs_symlink_aops;
 	} else
 		/* XXX - parse_rock_ridge_inode() had already set i_rdev. */
diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c
index 735d752..5384ceb 100644
--- a/fs/isofs/rock.c
+++ b/fs/isofs/rock.c
@@ -687,7 +687,7 @@ static int rock_ridge_symlink_readpage(struct file *file, struct page *page)
 	struct inode *inode = page->mapping->host;
 	struct iso_inode_info *ei = ISOFS_I(inode);
 	struct isofs_sb_info *sbi = ISOFS_SB(inode->i_sb);
-	char *link = kmap(page);
+	char *link = page_address(page);
 	unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
 	struct buffer_head *bh;
 	char *rpnt = link;
@@ -774,7 +774,6 @@ repeat:
 	brelse(bh);
 	*rpnt = '\0';
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
@@ -791,7 +790,6 @@ fail:
 	brelse(bh);
 error:
 	SetPageError(page);
-	kunmap(page);
 	unlock_page(page);
 	return -EIO;
 }
diff --git a/fs/jfs/inode.c b/fs/jfs/inode.c
index 41aa3ca..9d9bae6 100644
--- a/fs/jfs/inode.c
+++ b/fs/jfs/inode.c
@@ -60,6 +60,7 @@ struct inode *jfs_iget(struct super_block *sb, unsigned long ino)
 	} else if (S_ISLNK(inode->i_mode)) {
 		if (inode->i_size >= IDATASIZE) {
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 			inode->i_mapping->a_ops = &jfs_aops;
 		} else {
 			inode->i_op = &jfs_fast_symlink_inode_operations;
diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c
index 9d7551f..701f893 100644
--- a/fs/jfs/namei.c
+++ b/fs/jfs/namei.c
@@ -983,6 +983,7 @@ static int jfs_symlink(struct inode *dip, struct dentry *dentry,
 		jfs_info("jfs_symlink: allocate extent ip:0x%p", ip);
 
 		ip->i_op = &jfs_symlink_inode_operations;
+		inode_nohighmem(ip);
 		ip->i_mapping->a_ops = &jfs_aops;
 
 		/*
diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c
index 99944a4..542468e 100644
--- a/fs/logfs/dir.c
+++ b/fs/logfs/dir.c
@@ -529,6 +529,7 @@ static int logfs_symlink(struct inode *dir, struct dentry *dentry,
 		return PTR_ERR(inode);
 
 	inode->i_op = &page_symlink_inode_operations;
+	inode_nohighmem(inode);
 	inode->i_mapping->a_ops = &logfs_reg_aops;
 
 	return __logfs_create(dir, dentry, inode, target, destlen);
diff --git a/fs/logfs/inode.c b/fs/logfs/inode.c
index 06baa92..0fce46d 100644
--- a/fs/logfs/inode.c
+++ b/fs/logfs/inode.c
@@ -65,6 +65,7 @@ static void logfs_inode_setops(struct inode *inode)
 		break;
 	case S_IFLNK:
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &logfs_reg_aops;
 		break;
 	case S_IFSOCK:	/* fall through */
diff --git a/fs/minix/inode.c b/fs/minix/inode.c
index 086cd0a..67a23bf 100644
--- a/fs/minix/inode.c
+++ b/fs/minix/inode.c
@@ -452,6 +452,7 @@ void minix_set_inode(struct inode *inode, dev_t rdev)
 		inode->i_mapping->a_ops = &minix_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &minix_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &minix_aops;
 	} else
 		init_special_inode(inode, inode->i_mode, rdev);
diff --git a/fs/namei.c b/fs/namei.c
index 4bae5cb..2808958 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4527,7 +4527,8 @@ static const char *page_getlink(struct dentry * dentry, void **cookie)
 	if (IS_ERR(page))
 		return (char*)page;
 	*cookie = page;
-	kaddr = kmap(page);
+	BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
+	kaddr = page_address(page);
 	nd_terminate_link(kaddr, dentry->d_inode->i_size, PAGE_SIZE - 1);
 	return kaddr;
 }
@@ -4541,7 +4542,6 @@ EXPORT_SYMBOL(page_follow_link_light);
 void page_put_link(struct inode *unused, void *cookie)
 {
 	struct page *page = cookie;
-	kunmap(page);
 	page_cache_release(page);
 }
 EXPORT_SYMBOL(page_put_link);
@@ -4565,7 +4565,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;
@@ -4576,9 +4575,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);
diff --git a/fs/ncpfs/inode.c b/fs/ncpfs/inode.c
index 9605a2f..bb856f7 100644
--- a/fs/ncpfs/inode.c
+++ b/fs/ncpfs/inode.c
@@ -283,6 +283,7 @@ ncp_iget(struct super_block *sb, struct ncp_entry_info *info)
 #if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
 		} else if (S_ISLNK(inode->i_mode)) {
 			inode->i_op = &ncp_symlink_inode_operations;
+			inode_nohighmem(inode);
 			inode->i_data.a_ops = &ncp_symlink_aops;
 #endif
 		} else {
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 31b0a52..ae9aa0b 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -408,9 +408,10 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr, st
 				inode->i_fop = NULL;
 				inode->i_flags |= S_AUTOMOUNT;
 			}
-		} else if (S_ISLNK(inode->i_mode))
+		} else if (S_ISLNK(inode->i_mode)) {
 			inode->i_op = &nfs_symlink_inode_operations;
-		else
+			inode_nohighmem(inode);
+		} else
 			init_special_inode(inode, inode->i_mode, fattr->rdev);
 
 		memset(&inode->i_atime, 0, sizeof(inode->i_atime));
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index b6de433..abd93bf 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -56,7 +56,7 @@ static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
 	if (IS_ERR(page))
 		return ERR_CAST(page);
 	*cookie = page;
-	return kmap(page);
+	return page_address(page);
 }
 
 /*
diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c
index ac2f649..10b2252 100644
--- a/fs/nilfs2/inode.c
+++ b/fs/nilfs2/inode.c
@@ -510,6 +510,7 @@ static int __nilfs_read_inode(struct super_block *sb,
 		inode->i_mapping->a_ops = &nilfs_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &nilfs_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &nilfs_aops;
 	} else {
 		inode->i_op = &nilfs_special_inode_operations;
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index c9a1a49..90b3ba9 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -161,6 +161,7 @@ static int nilfs_symlink(struct inode *dir, struct dentry *dentry,
 
 	/* slow symlink */
 	inode->i_op = &nilfs_symlink_inode_operations;
+	inode_nohighmem(inode);
 	inode->i_mapping->a_ops = &nilfs_aops;
 	err = page_symlink(inode, symname, l);
 	if (err)
diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 8f87e05..97a563b 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -361,6 +361,7 @@ void ocfs2_populate_inode(struct inode *inode, struct ocfs2_dinode *fe,
 		    break;
 	    case S_IFLNK:
 		    inode->i_op = &ocfs2_symlink_inode_operations;
+		    inode_nohighmem(inode);
 		    i_size_write(inode, le64_to_cpu(fe->i_size));
 		    break;
 	    default:
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index a03f6f4..2efe8af 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -1960,6 +1960,7 @@ static int ocfs2_symlink(struct inode *dir,
 	inode->i_rdev = 0;
 	newsize = l - 1;
 	inode->i_op = &ocfs2_symlink_inode_operations;
+	inode_nohighmem(inode);
 	if (l > ocfs2_fast_symlink_chars(sb)) {
 		u32 offset = 0;
 
diff --git a/fs/qnx4/inode.c b/fs/qnx4/inode.c
index c4bcb77..f37b3de 100644
--- a/fs/qnx4/inode.c
+++ b/fs/qnx4/inode.c
@@ -316,6 +316,7 @@ struct inode *qnx4_iget(struct super_block *sb, unsigned long ino)
 		inode->i_fop = &qnx4_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &qnx4_aops;
 		qnx4_i(inode)->mmu_private = inode->i_size;
 	} else {
diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 32d2e1a..9728b54 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -582,6 +582,7 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
 		inode->i_mapping->a_ops = &qnx6_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &qnx6_aops;
 	} else
 		init_special_inode(inode, inode->i_mode, 0);
diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c
index 889d558..38981b0 100644
--- a/fs/ramfs/inode.c
+++ b/fs/ramfs/inode.c
@@ -79,6 +79,7 @@ struct inode *ramfs_get_inode(struct super_block *sb,
 			break;
 		case S_IFLNK:
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 			break;
 		}
 	}
diff --git a/fs/reiserfs/inode.c b/fs/reiserfs/inode.c
index 3d8e7e6..ae9e5b3 100644
--- a/fs/reiserfs/inode.c
+++ b/fs/reiserfs/inode.c
@@ -1361,6 +1361,7 @@ static void init_inode(struct inode *inode, struct treepath *path)
 		inode->i_fop = &reiserfs_dir_operations;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &reiserfs_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &reiserfs_address_space_operations;
 	} else {
 		inode->i_blocks = 0;
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index 47f9698..4fc2326 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -1170,6 +1170,7 @@ static int reiserfs_symlink(struct inode *parent_dir,
 	reiserfs_update_inode_transaction(parent_dir);
 
 	inode->i_op = &reiserfs_symlink_inode_operations;
+	inode_nohighmem(inode);
 	inode->i_mapping->a_ops = &reiserfs_address_space_operations;
 
 	retval = reiserfs_add_entry(&th, parent_dir, dentry->d_name.name,
diff --git a/fs/romfs/super.c b/fs/romfs/super.c
index 268733c..bb894e7 100644
--- a/fs/romfs/super.c
+++ b/fs/romfs/super.c
@@ -360,6 +360,7 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
 		break;
 	case ROMFH_SYM:
 		i->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(i);
 		i->i_data.a_ops = &romfs_aops;
 		mode |= S_IRWXUGO;
 		break;
diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c
index a1ce5ce..0927b1e 100644
--- a/fs/squashfs/inode.c
+++ b/fs/squashfs/inode.c
@@ -41,6 +41,7 @@
 #include <linux/fs.h>
 #include <linux/vfs.h>
 #include <linux/xattr.h>
+#include <linux/pagemap.h>
 
 #include "squashfs_fs.h"
 #include "squashfs_fs_sb.h"
@@ -291,6 +292,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
 		set_nlink(inode, le32_to_cpu(sqsh_ino->nlink));
 		inode->i_size = le32_to_cpu(sqsh_ino->symlink_size);
 		inode->i_op = &squashfs_symlink_inode_ops;
+		inode_nohighmem(inode);
 		inode->i_data.a_ops = &squashfs_symlink_aops;
 		inode->i_mode |= S_IFLNK;
 		squashfs_i(inode)->start = block;
diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c
index 02fa1dc..ef8bcdb 100644
--- a/fs/sysv/inode.c
+++ b/fs/sysv/inode.c
@@ -163,6 +163,7 @@ void sysv_set_inode(struct inode *inode, dev_t rdev)
 		inode->i_mapping->a_ops = &sysv_aops;
 	} else if (S_ISLNK(inode->i_mode)) {
 		inode->i_op = &sysv_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &sysv_aops;
 	} else
 		init_special_inode(inode, inode->i_mode, rdev);
diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index 8675c2b..0557463 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -1541,6 +1541,7 @@ reread:
 	case ICBTAG_FILE_TYPE_SYMLINK:
 		inode->i_data.a_ops = &udf_symlink_aops;
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mode = S_IFLNK | S_IRWXUGO;
 		break;
 	case ICBTAG_FILE_TYPE_MAIN:
diff --git a/fs/udf/namei.c b/fs/udf/namei.c
index d0e6de1..42eafb9 100644
--- a/fs/udf/namei.c
+++ b/fs/udf/namei.c
@@ -922,6 +922,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
 
 	inode->i_data.a_ops = &udf_symlink_aops;
 	inode->i_op = &page_symlink_inode_operations;
+	inode_nohighmem(inode);
 
 	if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
 		struct kernel_lb_addr eloc;
diff --git a/fs/udf/symlink.c b/fs/udf/symlink.c
index 862535b..8d61977 100644
--- a/fs/udf/symlink.c
+++ b/fs/udf/symlink.c
@@ -107,7 +107,7 @@ static int udf_symlink_filler(struct file *file, struct page *page)
 	struct buffer_head *bh = NULL;
 	unsigned char *symlink;
 	int err;
-	unsigned char *p = kmap(page);
+	unsigned char *p = page_address(page);
 	struct udf_inode_info *iinfo;
 	uint32_t pos;
 
@@ -141,7 +141,6 @@ static int udf_symlink_filler(struct file *file, struct page *page)
 
 	up_read(&iinfo->i_data_sem);
 	SetPageUptodate(page);
-	kunmap(page);
 	unlock_page(page);
 	return 0;
 
@@ -149,7 +148,6 @@ out_unlock_inode:
 	up_read(&iinfo->i_data_sem);
 	SetPageError(page);
 out_unmap:
-	kunmap(page);
 	unlock_page(page);
 	return err;
 }
diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c
index 737160a..d897e16 100644
--- a/fs/ufs/inode.c
+++ b/fs/ufs/inode.c
@@ -533,6 +533,7 @@ static void ufs_set_inode_ops(struct inode *inode)
 		} else {
 			inode->i_mapping->a_ops = &ufs_aops;
 			inode->i_op = &page_symlink_inode_operations;
+			inode_nohighmem(inode);
 		}
 	} else
 		init_special_inode(inode, inode->i_mode,
diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
index 24b0cbd..acf4a3b 100644
--- a/fs/ufs/namei.c
+++ b/fs/ufs/namei.c
@@ -124,6 +124,7 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry,
 	if (l > UFS_SB(sb)->s_uspi->s_maxsymlinklen) {
 		/* slow symlink */
 		inode->i_op = &page_symlink_inode_operations;
+		inode_nohighmem(inode);
 		inode->i_mapping->a_ops = &ufs_aops;
 		err = page_symlink(inode, symname, l);
 		if (err)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3aa5142..dfeda44 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3025,5 +3025,6 @@ static inline bool dir_relax(struct inode *inode)
 }
 
 extern bool path_noexec(const struct path *path);
+extern void inode_nohighmem(struct inode *inode);
 
 #endif /* _LINUX_FS_H */
diff --git a/mm/shmem.c b/mm/shmem.c
index 9187eee..64bf5ac 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2444,7 +2444,6 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	int len;
 	struct inode *inode;
 	struct page *page;
-	char *kaddr;
 	struct shmem_inode_info *info;
 
 	len = strlen(symname) + 1;
@@ -2483,9 +2482,8 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 		}
 		inode->i_mapping->a_ops = &shmem_aops;
 		inode->i_op = &shmem_symlink_inode_operations;
-		kaddr = kmap_atomic(page);
-		memcpy(kaddr, symname, len);
-		kunmap_atomic(kaddr);
+		inode_nohighmem(inode);
+		memcpy(page_address(page), symname, len);
 		SetPageUptodate(page);
 		set_page_dirty(page);
 		unlock_page(page);
@@ -2506,13 +2504,12 @@ static const char *shmem_follow_link(struct dentry *dentry, void **cookie)
 		return ERR_PTR(error);
 	unlock_page(page);
 	*cookie = page;
-	return kmap(page);
+	return page_address(page);
 }
 
 static void shmem_put_link(struct inode *unused, void *cookie)
 {
 	struct page *page = cookie;
-	kunmap(page);
 	mark_page_accessed(page);
 	page_cache_release(page);
 }
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 07/11] replace ->follow_link() with new method that could stay in RCU mode
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (5 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 08/11] teach page_get_link() to work " Al Viro
                     ` (5 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

new method: ->get_link(); replacement of ->follow_link().  The differences
are:
	* inode and dentry are passed separately
	* might be called both in RCU and non-RCU mode;
the former is indicated by passing it a NULL dentry.
	* when called that way it isn't allowed to block
and should return ERR_PTR(-ECHILD) if it needs to be called
in non-RCU mode.

It's a flagday change - the old method is gone, all in-tree instances
converted.  Conversion isn't hard; said that, so far very few instances
do not immediately bail out when called in RCU mode.  That'll change
in the next commits.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 Documentation/filesystems/Locking             |  4 +--
 Documentation/filesystems/porting             |  6 ++++
 drivers/staging/lustre/lustre/llite/symlink.c |  8 +++--
 fs/9p/vfs_inode.c                             | 17 +++++++---
 fs/9p/vfs_inode_dotl.c                        | 14 ++++++---
 fs/affs/symlink.c                             |  4 +--
 fs/autofs4/symlink.c                          | 13 +++++---
 fs/btrfs/inode.c                              |  2 +-
 fs/ceph/inode.c                               |  2 +-
 fs/cifs/cifsfs.c                              |  2 +-
 fs/cifs/cifsfs.h                              |  4 +--
 fs/cifs/link.c                                |  6 ++--
 fs/coda/cnode.c                               |  2 +-
 fs/configfs/symlink.c                         | 11 +++++--
 fs/dcache.c                                   |  2 +-
 fs/ecryptfs/inode.c                           | 12 +++++--
 fs/ext2/symlink.c                             |  4 +--
 fs/ext4/symlink.c                             | 13 +++++---
 fs/f2fs/namei.c                               | 16 ++++++----
 fs/fuse/dir.c                                 |  9 ++++--
 fs/gfs2/inode.c                               | 15 ++++++---
 fs/hostfs/hostfs_kern.c                       | 10 ++++--
 fs/jffs2/symlink.c                            |  2 +-
 fs/jfs/symlink.c                              |  4 +--
 fs/kernfs/symlink.c                           | 11 +++++--
 fs/libfs.c                                    |  9 +++---
 fs/minix/inode.c                              |  2 +-
 fs/namei.c                                    | 45 +++++++++++++++++----------
 fs/ncpfs/inode.c                              |  2 +-
 fs/nfs/symlink.c                              |  9 ++++--
 fs/nilfs2/namei.c                             |  2 +-
 fs/ocfs2/symlink.c                            |  2 +-
 fs/overlayfs/inode.c                          | 12 ++++---
 fs/proc/base.c                                | 22 +++++++------
 fs/proc/inode.c                               |  7 +++--
 fs/proc/namespaces.c                          |  9 ++++--
 fs/proc/self.c                                |  9 ++++--
 fs/proc/thread_self.c                         |  9 ++++--
 fs/reiserfs/namei.c                           |  2 +-
 fs/squashfs/symlink.c                         |  2 +-
 fs/sysv/inode.c                               |  2 +-
 fs/ubifs/file.c                               |  2 +-
 fs/xfs/xfs_iops.c                             |  8 +++--
 include/linux/fs.h                            |  6 ++--
 mm/shmem.c                                    | 12 ++++---
 45 files changed, 234 insertions(+), 132 deletions(-)

diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking
index 06d4434..4fba54b 100644
--- a/Documentation/filesystems/Locking
+++ b/Documentation/filesystems/Locking
@@ -50,7 +50,7 @@ prototypes:
 	int (*rename2) (struct inode *, struct dentry *,
 			struct inode *, struct dentry *, unsigned int);
 	int (*readlink) (struct dentry *, char __user *,int);
-	const char *(*follow_link) (struct dentry *, void **);
+	const char *(*get_link) (struct dentry *, struct inode *, void **);
 	void (*put_link) (struct inode *, void *);
 	void (*truncate) (struct inode *);
 	int (*permission) (struct inode *, int, unsigned int);
@@ -83,7 +83,7 @@ rmdir:		yes (both)	(see below)
 rename:		yes (all)	(see below)
 rename2:	yes (all)	(see below)
 readlink:	no
-follow_link:	no
+get_link:	no
 put_link:	no
 setattr:	yes
 permission:	no (may not block if called in rcu-walk mode)
diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting
index 3eb7c35..cf92a8c 100644
--- a/Documentation/filesystems/porting
+++ b/Documentation/filesystems/porting
@@ -509,3 +509,9 @@ in your dentry operations instead.
 	any symlink that might use page_follow_link_light/page_put_link() must
 	have inode_nohighmem(inode) called before anything might start playing with
 	its pagecache.
+--
+[mandatory]
+	->follow_link() is replaced with ->get_link(); same API, except that
+		* ->get_link() gets inode as a separate argument
+		* ->get_link() may be called in RCU mode - in that case NULL
+		  dentry is passed
diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c
index 69b2036..153fdf9 100644
--- a/drivers/staging/lustre/lustre/llite/symlink.c
+++ b/drivers/staging/lustre/lustre/llite/symlink.c
@@ -118,12 +118,14 @@ failed:
 	return rc;
 }
 
-static const char *ll_follow_link(struct dentry *dentry, void **cookie)
+static const char *ll_get_link(struct dentry *dentry,
+			       struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct ptlrpc_request *request = NULL;
 	int rc;
 	char *symname = NULL;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
 
 	CDEBUG(D_VFSTRACE, "VFS Op\n");
 	ll_inode_size_lock(inode);
@@ -149,7 +151,7 @@ static void ll_put_link(struct inode *unused, void *cookie)
 struct inode_operations ll_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.setattr	= ll_setattr,
-	.follow_link	= ll_follow_link,
+	.get_link	= ll_get_link,
 	.put_link	= ll_put_link,
 	.getattr	= ll_getattr,
 	.permission	= ll_inode_permission,
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 699941e..8ba5a89 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -1223,18 +1223,25 @@ ino_t v9fs_qid2ino(struct p9_qid *qid)
 }
 
 /**
- * v9fs_vfs_follow_link - follow a symlink path
+ * v9fs_vfs_get_link - follow a symlink path
  * @dentry: dentry for symlink
+ * @inode: inode for symlink
  * @cookie: place to pass the data to put_link()
  */
 
-static const char *v9fs_vfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *v9fs_vfs_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
-	struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry);
-	struct p9_fid *fid = v9fs_fid_lookup(dentry);
+	struct v9fs_session_info *v9ses;
+	struct p9_fid *fid;
 	struct p9_wstat *st;
 	char *res;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	v9ses = v9fs_dentry2v9ses(dentry);
+	fid = v9fs_fid_lookup(dentry);
 	p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
 
 	if (IS_ERR(fid))
@@ -1452,7 +1459,7 @@ static const struct inode_operations v9fs_file_inode_operations = {
 
 static const struct inode_operations v9fs_symlink_inode_operations = {
 	.readlink = generic_readlink,
-	.follow_link = v9fs_vfs_follow_link,
+	.get_link = v9fs_vfs_get_link,
 	.put_link = kfree_put_link,
 	.getattr = v9fs_vfs_getattr,
 	.setattr = v9fs_vfs_setattr,
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index cb899af..0cc105d 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -899,20 +899,26 @@ error:
 }
 
 /**
- * v9fs_vfs_follow_link_dotl - follow a symlink path
+ * v9fs_vfs_get_link_dotl - follow a symlink path
  * @dentry: dentry for symlink
+ * @inode: inode for symlink
  * @cookie: place to pass the data to put_link()
  */
 
 static const char *
-v9fs_vfs_follow_link_dotl(struct dentry *dentry, void **cookie)
+v9fs_vfs_get_link_dotl(struct dentry *dentry,
+		       struct inode *inode, void **cookie)
 {
-	struct p9_fid *fid = v9fs_fid_lookup(dentry);
+	struct p9_fid *fid;
 	char *target;
 	int retval;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
 
+	fid = v9fs_fid_lookup(dentry);
 	if (IS_ERR(fid))
 		return ERR_CAST(fid);
 	retval = p9_client_readlink(fid, &target);
@@ -984,7 +990,7 @@ const struct inode_operations v9fs_file_inode_operations_dotl = {
 
 const struct inode_operations v9fs_symlink_inode_operations_dotl = {
 	.readlink = generic_readlink,
-	.follow_link = v9fs_vfs_follow_link_dotl,
+	.get_link = v9fs_vfs_get_link_dotl,
 	.put_link = kfree_put_link,
 	.getattr = v9fs_vfs_getattr_dotl,
 	.setattr = v9fs_vfs_setattr_dotl,
diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
index e3f9dc3..39d1194 100644
--- a/fs/affs/symlink.c
+++ b/fs/affs/symlink.c
@@ -20,7 +20,7 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
 	char			 c;
 	char			 lc;
 
-	pr_debug("follow_link(ino=%lu)\n", inode->i_ino);
+	pr_debug("get_link(ino=%lu)\n", inode->i_ino);
 
 	bh = affs_bread(inode->i_sb, inode->i_ino);
 	if (!bh)
@@ -71,7 +71,7 @@ const struct address_space_operations affs_symlink_aops = {
 
 const struct inode_operations affs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= affs_notify_change,
 };
diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c
index da0c334..39e6f0b 100644
--- a/fs/autofs4/symlink.c
+++ b/fs/autofs4/symlink.c
@@ -12,10 +12,15 @@
 
 #include "autofs_i.h"
 
-static const char *autofs4_follow_link(struct dentry *dentry, void **cookie)
+static const char *autofs4_get_link(struct dentry *dentry,
+				    struct inode *inode, void **cookie)
 {
-	struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
-	struct autofs_info *ino = autofs4_dentry_ino(dentry);
+	struct autofs_sb_info *sbi;
+	struct autofs_info *ino;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	sbi = autofs4_sbi(dentry->d_sb);
+	ino = autofs4_dentry_ino(dentry);
 	if (ino && !autofs4_oz_mode(sbi))
 		ino->last_used = jiffies;
 	return d_inode(dentry)->i_private;
@@ -23,5 +28,5 @@ static const char *autofs4_follow_link(struct dentry *dentry, void **cookie)
 
 const struct inode_operations autofs4_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= autofs4_follow_link
+	.get_link	= autofs4_get_link
 };
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 70f98bf..3d4aa69 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -10096,7 +10096,7 @@ static const struct inode_operations btrfs_special_inode_operations = {
 };
 static const struct inode_operations btrfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= btrfs_getattr,
 	.setattr	= btrfs_setattr,
diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
index 498dcfa..da55eb8 100644
--- a/fs/ceph/inode.c
+++ b/fs/ceph/inode.c
@@ -1756,7 +1756,7 @@ retry:
  */
 static const struct inode_operations ceph_symlink_iops = {
 	.readlink = generic_readlink,
-	.follow_link = simple_follow_link,
+	.get_link = simple_get_link,
 	.setattr = ceph_setattr,
 	.getattr = ceph_getattr,
 	.setxattr = ceph_setxattr,
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index cbc0f4b..4593f416 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -900,7 +900,7 @@ const struct inode_operations cifs_file_inode_ops = {
 
 const struct inode_operations cifs_symlink_inode_ops = {
 	.readlink = generic_readlink,
-	.follow_link = cifs_follow_link,
+	.get_link = cifs_get_link,
 	.put_link = kfree_put_link,
 	.permission = cifs_permission,
 	/* BB add the following two eventually */
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index c3cc160..6886328 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -120,9 +120,7 @@ extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
 #endif
 
 /* Functions related to symlinks */
-extern const char *cifs_follow_link(struct dentry *direntry, void **cookie);
-extern int cifs_readlink(struct dentry *direntry, char __user *buffer,
-			 int buflen);
+extern const char *cifs_get_link(struct dentry *, struct inode *, void **);
 extern int cifs_symlink(struct inode *inode, struct dentry *direntry,
 			const char *symname);
 extern int	cifs_removexattr(struct dentry *, const char *);
diff --git a/fs/cifs/link.c b/fs/cifs/link.c
index e3548f7..6f2439b5 100644
--- a/fs/cifs/link.c
+++ b/fs/cifs/link.c
@@ -627,9 +627,8 @@ cifs_hl_exit:
 }
 
 const char *
-cifs_follow_link(struct dentry *direntry, void **cookie)
+cifs_get_link(struct dentry *direntry, struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(direntry);
 	int rc = -ENOMEM;
 	unsigned int xid;
 	char *full_path = NULL;
@@ -639,6 +638,9 @@ cifs_follow_link(struct dentry *direntry, void **cookie)
 	struct cifs_tcon *tcon;
 	struct TCP_Server_Info *server;
 
+	if (!direntry)
+		return ERR_PTR(-ECHILD);
+
 	xid = get_xid();
 
 	tlink = cifs_sb_tlink(cifs_sb);
diff --git a/fs/coda/cnode.c b/fs/coda/cnode.c
index dd6a79e..f18139c 100644
--- a/fs/coda/cnode.c
+++ b/fs/coda/cnode.c
@@ -18,7 +18,7 @@ static inline int coda_fideq(struct CodaFid *fid1, struct CodaFid *fid2)
 
 static const struct inode_operations coda_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= coda_setattr,
 };
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index ec5c832..b91c01e 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -279,11 +279,16 @@ static int configfs_getlink(struct dentry *dentry, char * path)
 
 }
 
-static const char *configfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *configfs_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
-	unsigned long page = get_zeroed_page(GFP_KERNEL);
+	unsigned long page;
 	int error;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	page = get_zeroed_page(GFP_KERNEL);
 	if (!page)
 		return ERR_PTR(-ENOMEM);
 
@@ -297,7 +302,7 @@ static const char *configfs_follow_link(struct dentry *dentry, void **cookie)
 }
 
 const struct inode_operations configfs_symlink_inode_operations = {
-	.follow_link = configfs_follow_link,
+	.get_link = configfs_get_link,
 	.readlink = generic_readlink,
 	.put_link = free_page_put_link,
 	.setattr = configfs_setattr,
diff --git a/fs/dcache.c b/fs/dcache.c
index 5c33aeb..d27f090 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1734,7 +1734,7 @@ static unsigned d_flags_for_inode(struct inode *inode)
 	}
 
 	if (unlikely(!(inode->i_opflags & IOP_NOFOLLOW))) {
-		if (unlikely(inode->i_op->follow_link)) {
+		if (unlikely(inode->i_op->get_link)) {
 			add_flags = DCACHE_SYMLINK_TYPE;
 			goto type_determined;
 		}
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index e2e47ba..5a05559 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -674,10 +674,16 @@ out:
 	return rc ? ERR_PTR(rc) : buf;
 }
 
-static const char *ecryptfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *ecryptfs_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
 	size_t len;
-	char *buf = ecryptfs_readlink_lower(dentry, &len);
+	char *buf;
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	buf = ecryptfs_readlink_lower(dentry, &len);
 	if (IS_ERR(buf))
 		return buf;
 	fsstack_copy_attr_atime(d_inode(dentry),
@@ -1095,7 +1101,7 @@ out:
 
 const struct inode_operations ecryptfs_symlink_iops = {
 	.readlink = generic_readlink,
-	.follow_link = ecryptfs_follow_link,
+	.get_link = ecryptfs_get_link,
 	.put_link = kfree_put_link,
 	.permission = ecryptfs_permission,
 	.setattr = ecryptfs_setattr,
diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c
index ae17179..4690511 100644
--- a/fs/ext2/symlink.c
+++ b/fs/ext2/symlink.c
@@ -22,7 +22,7 @@
 
 const struct inode_operations ext2_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= ext2_setattr,
 #ifdef CONFIG_EXT2_FS_XATTR
@@ -35,7 +35,7 @@ const struct inode_operations ext2_symlink_inode_operations = {
  
 const struct inode_operations ext2_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
+	.get_link	= simple_get_link,
 	.setattr	= ext2_setattr,
 #ifdef CONFIG_EXT2_FS_XATTR
 	.setxattr	= generic_setxattr,
diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
index 0e6dc44..3b4bfe2 100644
--- a/fs/ext4/symlink.c
+++ b/fs/ext4/symlink.c
@@ -23,17 +23,20 @@
 #include "xattr.h"
 
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
-static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cookie)
+static const char *ext4_encrypted_get_link(struct dentry *dentry,
+					   struct inode *inode, void **cookie)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
 	struct ext4_str cstr, pstr;
-	struct inode *inode = d_inode(dentry);
 	struct ext4_encrypted_symlink_data *sd;
 	loff_t size = min_t(loff_t, i_size_read(inode), PAGE_SIZE - 1);
 	int res;
 	u32 plen, max_size = inode->i_sb->s_blocksize;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	res = ext4_get_encryption_info(inode);
 	if (res)
 		return ERR_PTR(res);
@@ -87,7 +90,7 @@ errout:
 
 const struct inode_operations ext4_encrypted_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link    = ext4_encrypted_follow_link,
+	.get_link	= ext4_encrypted_get_link,
 	.put_link       = kfree_put_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
@@ -99,7 +102,7 @@ const struct inode_operations ext4_encrypted_symlink_inode_operations = {
 
 const struct inode_operations ext4_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
@@ -110,7 +113,7 @@ const struct inode_operations ext4_symlink_inode_operations = {
 
 const struct inode_operations ext4_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link    = simple_follow_link,
+	.get_link	= simple_get_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
 	.getxattr	= generic_getxattr,
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index 484df68..2a8d84b 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -315,9 +315,10 @@ fail:
 	return err;
 }
 
-static const char *f2fs_follow_link(struct dentry *dentry, void **cookie)
+static const char *f2fs_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	const char *link = page_follow_link_light(dentry, cookie);
+	const char *link = page_get_link(dentry, inode, cookie);
 	if (!IS_ERR(link) && !*link) {
 		/* this is broken symlink case */
 		page_put_link(NULL, *cookie);
@@ -924,18 +925,21 @@ static int f2fs_rename2(struct inode *old_dir, struct dentry *old_dentry,
 }
 
 #ifdef CONFIG_F2FS_FS_ENCRYPTION
-static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cookie)
+static const char *f2fs_encrypted_get_link(struct dentry *dentry,
+					   struct inode *inode, void **cookie)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
 	struct f2fs_str cstr;
 	struct f2fs_str pstr = FSTR_INIT(NULL, 0);
-	struct inode *inode = d_inode(dentry);
 	struct f2fs_encrypted_symlink_data *sd;
 	loff_t size = min_t(loff_t, i_size_read(inode), PAGE_SIZE - 1);
 	u32 max_size = inode->i_sb->s_blocksize;
 	int res;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	res = f2fs_get_encryption_info(inode);
 	if (res)
 		return ERR_PTR(res);
@@ -994,7 +998,7 @@ errout:
 
 const struct inode_operations f2fs_encrypted_symlink_inode_operations = {
 	.readlink       = generic_readlink,
-	.follow_link    = f2fs_encrypted_follow_link,
+	.get_link       = f2fs_encrypted_get_link,
 	.put_link       = kfree_put_link,
 	.getattr	= f2fs_getattr,
 	.setattr	= f2fs_setattr,
@@ -1030,7 +1034,7 @@ const struct inode_operations f2fs_dir_inode_operations = {
 
 const struct inode_operations f2fs_symlink_inode_operations = {
 	.readlink       = generic_readlink,
-	.follow_link    = f2fs_follow_link,
+	.get_link       = f2fs_get_link,
 	.put_link       = page_put_link,
 	.getattr	= f2fs_getattr,
 	.setattr	= f2fs_setattr,
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 5e2e087..148e8ef 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1365,14 +1365,17 @@ static int fuse_readdir(struct file *file, struct dir_context *ctx)
 	return err;
 }
 
-static const char *fuse_follow_link(struct dentry *dentry, void **cookie)
+static const char *fuse_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	FUSE_ARGS(args);
 	char *link;
 	ssize_t ret;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	link = (char *) __get_free_page(GFP_KERNEL);
 	if (!link)
 		return ERR_PTR(-ENOMEM);
@@ -1909,7 +1912,7 @@ static const struct inode_operations fuse_common_inode_operations = {
 
 static const struct inode_operations fuse_symlink_inode_operations = {
 	.setattr	= fuse_setattr,
-	.follow_link	= fuse_follow_link,
+	.get_link	= fuse_get_link,
 	.put_link	= free_page_put_link,
 	.readlink	= generic_readlink,
 	.getattr	= fuse_getattr,
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 063fdfc..10950560 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1712,24 +1712,29 @@ static int gfs2_rename2(struct inode *odir, struct dentry *odentry,
 }
 
 /**
- * gfs2_follow_link - Follow a symbolic link
+ * gfs2_get_link - Follow a symbolic link
  * @dentry: The dentry of the link
- * @nd: Data that we pass to vfs_follow_link()
+ * @inode: The inode of the link
+ * @cookie: place to store the information for ->put_link()
  *
  * This can handle symlinks of any size.
  *
  * Returns: 0 on success or error code
  */
 
-static const char *gfs2_follow_link(struct dentry *dentry, void **cookie)
+static const char *gfs2_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	struct gfs2_inode *ip = GFS2_I(d_inode(dentry));
+	struct gfs2_inode *ip = GFS2_I(inode);
 	struct gfs2_holder i_gh;
 	struct buffer_head *dibh;
 	unsigned int size;
 	char *buf;
 	int error;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &i_gh);
 	error = gfs2_glock_nq(&i_gh);
 	if (error) {
@@ -2132,7 +2137,7 @@ const struct inode_operations gfs2_dir_iops = {
 
 const struct inode_operations gfs2_symlink_iops = {
 	.readlink = generic_readlink,
-	.follow_link = gfs2_follow_link,
+	.get_link = gfs2_get_link,
 	.put_link = kfree_put_link,
 	.permission = gfs2_permission,
 	.setattr = gfs2_setattr,
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index 2ac99db..6ce5309 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -892,9 +892,13 @@ static const struct inode_operations hostfs_dir_iops = {
 	.setattr	= hostfs_setattr,
 };
 
-static const char *hostfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *hostfs_get_link(struct dentry *dentry,
+				   struct inode *inode, void **cookie)
 {
-	char *link = __getname();
+	char *link;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	link = __getname();
 	if (link) {
 		char *path = dentry_name(dentry);
 		int err = -ENOMEM;
@@ -922,7 +926,7 @@ static void hostfs_put_link(struct inode *unused, void *cookie)
 
 static const struct inode_operations hostfs_link_iops = {
 	.readlink	= generic_readlink,
-	.follow_link	= hostfs_follow_link,
+	.get_link	= hostfs_get_link,
 	.put_link	= hostfs_put_link,
 };
 
diff --git a/fs/jffs2/symlink.c b/fs/jffs2/symlink.c
index 8ce2f24..2cabd64 100644
--- a/fs/jffs2/symlink.c
+++ b/fs/jffs2/symlink.c
@@ -14,7 +14,7 @@
 const struct inode_operations jffs2_symlink_inode_operations =
 {
 	.readlink =	generic_readlink,
-	.follow_link =	simple_follow_link,
+	.get_link =	simple_get_link,
 	.setattr =	jffs2_setattr,
 	.setxattr =	jffs2_setxattr,
 	.getxattr =	jffs2_getxattr,
diff --git a/fs/jfs/symlink.c b/fs/jfs/symlink.c
index 5929e23..0211328 100644
--- a/fs/jfs/symlink.c
+++ b/fs/jfs/symlink.c
@@ -23,7 +23,7 @@
 
 const struct inode_operations jfs_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
+	.get_link	= simple_get_link,
 	.setattr	= jfs_setattr,
 	.setxattr	= jfs_setxattr,
 	.getxattr	= jfs_getxattr,
@@ -33,7 +33,7 @@ const struct inode_operations jfs_fast_symlink_inode_operations = {
 
 const struct inode_operations jfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= jfs_setattr,
 	.setxattr	= jfs_setxattr,
diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c
index db27252..ffae857 100644
--- a/fs/kernfs/symlink.c
+++ b/fs/kernfs/symlink.c
@@ -112,10 +112,15 @@ static int kernfs_getlink(struct dentry *dentry, char *path)
 	return error;
 }
 
-static const char *kernfs_iop_follow_link(struct dentry *dentry, void **cookie)
+static const char *kernfs_iop_get_link(struct dentry *dentry,
+				       struct inode *inode, void **cookie)
 {
 	int error = -ENOMEM;
-	unsigned long page = get_zeroed_page(GFP_KERNEL);
+	unsigned long page;
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	page = get_zeroed_page(GFP_KERNEL);
 	if (!page)
 		return ERR_PTR(-ENOMEM);
 	error = kernfs_getlink(dentry, (char *)page);
@@ -132,7 +137,7 @@ const struct inode_operations kernfs_symlink_iops = {
 	.getxattr	= kernfs_iop_getxattr,
 	.listxattr	= kernfs_iop_listxattr,
 	.readlink	= generic_readlink,
-	.follow_link	= kernfs_iop_follow_link,
+	.get_link	= kernfs_iop_get_link,
 	.put_link	= free_page_put_link,
 	.setattr	= kernfs_iop_setattr,
 	.getattr	= kernfs_iop_getattr,
diff --git a/fs/libfs.c b/fs/libfs.c
index c7cbfb0..8dc37fc 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -1092,14 +1092,15 @@ simple_nosetlease(struct file *filp, long arg, struct file_lock **flp,
 }
 EXPORT_SYMBOL(simple_nosetlease);
 
-const char *simple_follow_link(struct dentry *dentry, void **cookie)
+const char *simple_get_link(struct dentry *dentry, struct inode *inode,
+			    void **cookie)
 {
-	return d_inode(dentry)->i_link;
+	return inode->i_link;
 }
-EXPORT_SYMBOL(simple_follow_link);
+EXPORT_SYMBOL(simple_get_link);
 
 const struct inode_operations simple_symlink_inode_operations = {
-	.follow_link = simple_follow_link,
+	.get_link = simple_get_link,
 	.readlink = generic_readlink
 };
 EXPORT_SYMBOL(simple_symlink_inode_operations);
diff --git a/fs/minix/inode.c b/fs/minix/inode.c
index 67a23bf..3cce709 100644
--- a/fs/minix/inode.c
+++ b/fs/minix/inode.c
@@ -435,7 +435,7 @@ static const struct address_space_operations minix_aops = {
 
 static const struct inode_operations minix_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= minix_getattr,
 };
diff --git a/fs/namei.c b/fs/namei.c
index 2808958..1da3064 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -842,7 +842,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)
@@ -1005,10 +1005,18 @@ const char *get_link(struct nameidata *nd)
 	res = inode->i_link;
 	if (!res) {
 		if (nd->flags & LOOKUP_RCU) {
-			if (unlikely(unlazy_walk(nd, NULL, 0)))
-				return ERR_PTR(-ECHILD);
+			res = inode->i_op->get_link(NULL, inode,
+						    &last->cookie);
+			if (res == ERR_PTR(-ECHILD)) {
+				if (unlikely(unlazy_walk(nd, NULL, 0)))
+					return ERR_PTR(-ECHILD);
+				res = inode->i_op->get_link(dentry, inode,
+						            &last->cookie);
+			}
+		} else {
+			res = inode->i_op->get_link(dentry, inode,
+						    &last->cookie);
 		}
-		res = inode->i_op->follow_link(dentry, &last->cookie);
 		if (IS_ERR_OR_NULL(res)) {
 			last->cookie = NULL;
 			return res;
@@ -4495,8 +4503,8 @@ 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)
 {
@@ -4506,7 +4514,7 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 	int res;
 
 	if (!link) {
-		link = inode->i_op->follow_link(dentry, &cookie);
+		link = inode->i_op->get_link(dentry, inode, &cookie);
 		if (IS_ERR(link))
 			return PTR_ERR(link);
 	}
@@ -4518,26 +4526,27 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 EXPORT_SYMBOL(generic_readlink);
 
 /* get the link contents into pagecache */
-static const char *page_getlink(struct dentry * dentry, void **cookie)
+const char *page_get_link(struct dentry *dentry, struct inode *inode,
+				 void **cookie)
 {
 	char *kaddr;
 	struct page *page;
-	struct address_space *mapping = dentry->d_inode->i_mapping;
+	struct address_space *mapping = inode->i_mapping;
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	page = read_mapping_page(mapping, 0, NULL);
 	if (IS_ERR(page))
 		return (char*)page;
 	*cookie = page;
 	BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
 	kaddr = page_address(page);
-	nd_terminate_link(kaddr, dentry->d_inode->i_size, PAGE_SIZE - 1);
+	nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
 	return kaddr;
 }
 
-const char *page_follow_link_light(struct dentry *dentry, void **cookie)
-{
-	return page_getlink(dentry, cookie);
-}
-EXPORT_SYMBOL(page_follow_link_light);
+EXPORT_SYMBOL(page_get_link);
 
 void page_put_link(struct inode *unused, void *cookie)
 {
@@ -4549,7 +4558,9 @@ EXPORT_SYMBOL(page_put_link);
 int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 {
 	void *cookie = NULL;
-	int res = readlink_copy(buffer, buflen, page_getlink(dentry, &cookie));
+	int res = readlink_copy(buffer, buflen,
+				page_get_link(dentry, d_inode(dentry),
+					      &cookie));
 	if (cookie)
 		page_put_link(NULL, cookie);
 	return res;
@@ -4600,7 +4611,7 @@ EXPORT_SYMBOL(page_symlink);
 
 const struct inode_operations page_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 };
 EXPORT_SYMBOL(page_symlink_inode_operations);
diff --git a/fs/ncpfs/inode.c b/fs/ncpfs/inode.c
index bb856f7..3ab6cdb 100644
--- a/fs/ncpfs/inode.c
+++ b/fs/ncpfs/inode.c
@@ -244,7 +244,7 @@ static void ncp_set_attr(struct inode *inode, struct ncp_entry_info *nwinfo)
 #if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
 static const struct inode_operations ncp_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.setattr	= ncp_notify_change,
 };
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index abd93bf..8ade8a8 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -42,12 +42,15 @@ error:
 	return -EIO;
 }
 
-static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
+static const char *nfs_get_link(struct dentry *dentry,
+				struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct page *page;
 	void *err;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	err = ERR_PTR(nfs_revalidate_mapping(inode, inode->i_mapping));
 	if (err)
 		return err;
@@ -64,7 +67,7 @@ static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
  */
 const struct inode_operations nfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= nfs_follow_link,
+	.get_link	= nfs_get_link,
 	.put_link	= page_put_link,
 	.getattr	= nfs_getattr,
 	.setattr	= nfs_setattr,
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index 90b3ba9..63dddb7 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -569,7 +569,7 @@ const struct inode_operations nilfs_special_inode_operations = {
 
 const struct inode_operations nilfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.permission     = nilfs_permission,
 };
diff --git a/fs/ocfs2/symlink.c b/fs/ocfs2/symlink.c
index 66edce7..b4e79bc 100644
--- a/fs/ocfs2/symlink.c
+++ b/fs/ocfs2/symlink.c
@@ -88,7 +88,7 @@ const struct address_space_operations ocfs2_fast_symlink_aops = {
 
 const struct inode_operations ocfs2_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= ocfs2_getattr,
 	.setattr	= ocfs2_setattr,
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 4060ffd..38a0b8b 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -137,17 +137,21 @@ struct ovl_link_data {
 	void *cookie;
 };
 
-static const char *ovl_follow_link(struct dentry *dentry, void **cookie)
+static const char *ovl_get_link(struct dentry *dentry,
+				struct inode *inode, void **cookie)
 {
 	struct dentry *realdentry;
 	struct inode *realinode;
 	struct ovl_link_data *data = NULL;
 	const char *ret;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	realdentry = ovl_dentry_real(dentry);
 	realinode = realdentry->d_inode;
 
-	if (WARN_ON(!realinode->i_op->follow_link))
+	if (WARN_ON(!realinode->i_op->get_link))
 		return ERR_PTR(-EPERM);
 
 	if (realinode->i_op->put_link) {
@@ -157,7 +161,7 @@ static const char *ovl_follow_link(struct dentry *dentry, void **cookie)
 		data->realdentry = realdentry;
 	}
 
-	ret = realinode->i_op->follow_link(realdentry, cookie);
+	ret = realinode->i_op->get_link(realdentry, realinode, cookie);
 	if (IS_ERR_OR_NULL(ret)) {
 		kfree(data);
 		return ret;
@@ -378,7 +382,7 @@ static const struct inode_operations ovl_file_inode_operations = {
 
 static const struct inode_operations ovl_symlink_inode_operations = {
 	.setattr	= ovl_setattr,
-	.follow_link	= ovl_follow_link,
+	.get_link	= ovl_get_link,
 	.put_link	= ovl_put_link,
 	.readlink	= ovl_readlink,
 	.getattr	= ovl_getattr,
diff --git a/fs/proc/base.c b/fs/proc/base.c
index bd3e9e6..1a489e2 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -1564,12 +1564,15 @@ static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
 		return -ENOENT;
 }
 
-static const char *proc_pid_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_pid_get_link(struct dentry *dentry,
+				     struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	struct path path;
 	int error = -EACCES;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	/* Are we allowed to snoop on the tasks file descriptors? */
 	if (!proc_fd_access_allowed(inode))
 		goto out;
@@ -1630,7 +1633,7 @@ out:
 
 const struct inode_operations proc_pid_link_inode_operations = {
 	.readlink	= proc_pid_readlink,
-	.follow_link	= proc_pid_follow_link,
+	.get_link	= proc_pid_get_link,
 	.setattr	= proc_setattr,
 };
 
@@ -1895,7 +1898,7 @@ static const struct dentry_operations tid_map_files_dentry_operations = {
 	.d_delete	= pid_delete_dentry,
 };
 
-static int proc_map_files_get_link(struct dentry *dentry, struct path *path)
+static int map_files_get_link(struct dentry *dentry, struct path *path)
 {
 	unsigned long vm_start, vm_end;
 	struct vm_area_struct *vma;
@@ -1945,20 +1948,21 @@ struct map_files_info {
  * path to the file in question.
  */
 static const char *
-proc_map_files_follow_link(struct dentry *dentry, void **cookie)
+proc_map_files_get_link(struct dentry *dentry,
+			struct inode *inode, void **cookie)
 {
 	if (!capable(CAP_SYS_ADMIN))
 		return ERR_PTR(-EPERM);
 
-	return proc_pid_follow_link(dentry, NULL);
+	return proc_pid_get_link(dentry, inode, NULL);
 }
 
 /*
- * Identical to proc_pid_link_inode_operations except for follow_link()
+ * Identical to proc_pid_link_inode_operations except for get_link()
  */
 static const struct inode_operations proc_map_files_link_inode_operations = {
 	.readlink	= proc_pid_readlink,
-	.follow_link	= proc_map_files_follow_link,
+	.get_link	= proc_map_files_get_link,
 	.setattr	= proc_setattr,
 };
 
@@ -1975,7 +1979,7 @@ proc_map_files_instantiate(struct inode *dir, struct dentry *dentry,
 		return -ENOENT;
 
 	ei = PROC_I(inode);
-	ei->op.proc_get_link = proc_map_files_get_link;
+	ei->op.proc_get_link = map_files_get_link;
 
 	inode->i_op = &proc_map_files_link_inode_operations;
 	inode->i_size = 64;
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index bd95b9f..10360b2 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -393,9 +393,10 @@ static const struct file_operations proc_reg_file_ops_no_compat = {
 };
 #endif
 
-static const char *proc_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_get_link(struct dentry *dentry,
+				 struct inode *inode, void **cookie)
 {
-	struct proc_dir_entry *pde = PDE(d_inode(dentry));
+	struct proc_dir_entry *pde = PDE(inode);
 	if (unlikely(!use_pde(pde)))
 		return ERR_PTR(-EINVAL);
 	*cookie = pde;
@@ -409,7 +410,7 @@ static void proc_put_link(struct inode *unused, void *p)
 
 const struct inode_operations proc_link_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= proc_follow_link,
+	.get_link	= proc_get_link,
 	.put_link	= proc_put_link,
 };
 
diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c
index f6e8354..63861c1 100644
--- a/fs/proc/namespaces.c
+++ b/fs/proc/namespaces.c
@@ -30,14 +30,17 @@ static const struct proc_ns_operations *ns_entries[] = {
 	&mntns_operations,
 };
 
-static const char *proc_ns_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_ns_get_link(struct dentry *dentry,
+				    struct inode *inode, void **cookie)
 {
-	struct inode *inode = d_inode(dentry);
 	const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops;
 	struct task_struct *task;
 	struct path ns_path;
 	void *error = ERR_PTR(-EACCES);
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	task = get_proc_task(inode);
 	if (!task)
 		return error;
@@ -74,7 +77,7 @@ static int proc_ns_readlink(struct dentry *dentry, char __user *buffer, int bufl
 
 static const struct inode_operations proc_ns_link_inode_operations = {
 	.readlink	= proc_ns_readlink,
-	.follow_link	= proc_ns_follow_link,
+	.get_link	= proc_ns_get_link,
 	.setattr	= proc_setattr,
 };
 
diff --git a/fs/proc/self.c b/fs/proc/self.c
index 113b8d0..9dd0ae6 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -18,12 +18,15 @@ static int proc_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static const char *proc_self_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_self_get_link(struct dentry *dentry,
+				      struct inode *inode, void **cookie)
 {
-	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
+	struct pid_namespace *ns = inode->i_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	char *name;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
 	if (!tgid)
 		return ERR_PTR(-ENOENT);
 	/* 11 for max length of signed int in decimal + NULL term */
@@ -36,7 +39,7 @@ static const char *proc_self_follow_link(struct dentry *dentry, void **cookie)
 
 static const struct inode_operations proc_self_inode_operations = {
 	.readlink	= proc_self_readlink,
-	.follow_link	= proc_self_follow_link,
+	.get_link	= proc_self_get_link,
 	.put_link	= kfree_put_link,
 };
 
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 947b0f4..50eef6f 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -19,13 +19,16 @@ static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static const char *proc_thread_self_follow_link(struct dentry *dentry, void **cookie)
+static const char *proc_thread_self_get_link(struct dentry *dentry,
+					     struct inode *inode, void **cookie)
 {
-	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
+	struct pid_namespace *ns = inode->i_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	pid_t pid = task_pid_nr_ns(current, ns);
 	char *name;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
 	if (!pid)
 		return ERR_PTR(-ENOENT);
 	name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL);
@@ -37,7 +40,7 @@ static const char *proc_thread_self_follow_link(struct dentry *dentry, void **co
 
 static const struct inode_operations proc_thread_self_inode_operations = {
 	.readlink	= proc_thread_self_readlink,
-	.follow_link	= proc_thread_self_follow_link,
+	.get_link	= proc_thread_self_get_link,
 	.put_link	= kfree_put_link,
 };
 
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index 4fc2326..ecbf11e 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -1665,7 +1665,7 @@ const struct inode_operations reiserfs_dir_inode_operations = {
  */
 const struct inode_operations reiserfs_symlink_inode_operations = {
 	.readlink = generic_readlink,
-	.follow_link = page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link = page_put_link,
 	.setattr = reiserfs_setattr,
 	.setxattr = reiserfs_setxattr,
diff --git a/fs/squashfs/symlink.c b/fs/squashfs/symlink.c
index 12806df..7c635a5 100644
--- a/fs/squashfs/symlink.c
+++ b/fs/squashfs/symlink.c
@@ -119,7 +119,7 @@ const struct address_space_operations squashfs_symlink_aops = {
 
 const struct inode_operations squashfs_symlink_inode_ops = {
 	.readlink = generic_readlink,
-	.follow_link = page_follow_link_light,
+	.get_link = page_get_link,
 	.put_link = page_put_link,
 	.getxattr = generic_getxattr,
 	.listxattr = squashfs_listxattr
diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c
index ef8bcdb..80a40bc 100644
--- a/fs/sysv/inode.c
+++ b/fs/sysv/inode.c
@@ -146,7 +146,7 @@ static inline void write3byte(struct sysv_sb_info *sbi,
 
 static const struct inode_operations sysv_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= page_follow_link_light,
+	.get_link	= page_get_link,
 	.put_link	= page_put_link,
 	.getattr	= sysv_getattr,
 };
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index 0edc128..eff6280 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1608,7 +1608,7 @@ const struct inode_operations ubifs_file_inode_operations = {
 
 const struct inode_operations ubifs_symlink_inode_operations = {
 	.readlink    = generic_readlink,
-	.follow_link = simple_follow_link,
+	.get_link    = simple_get_link,
 	.setattr     = ubifs_setattr,
 	.getattr     = ubifs_getattr,
 	.setxattr    = ubifs_setxattr,
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 245268a..f638fd5 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -414,13 +414,17 @@ xfs_vn_rename(
  * uio is kmalloced for this reason...
  */
 STATIC const char *
-xfs_vn_follow_link(
+xfs_vn_get_link(
 	struct dentry		*dentry,
+	struct inode		*inode,
 	void			**cookie)
 {
 	char			*link;
 	int			error = -ENOMEM;
 
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
 	link = kmalloc(MAXPATHLEN+1, GFP_KERNEL);
 	if (!link)
 		goto out_err;
@@ -1172,7 +1176,7 @@ static const struct inode_operations xfs_dir_ci_inode_operations = {
 
 static const struct inode_operations xfs_symlink_inode_operations = {
 	.readlink		= generic_readlink,
-	.follow_link		= xfs_vn_follow_link,
+	.get_link		= xfs_vn_get_link,
 	.put_link		= kfree_put_link,
 	.getattr		= xfs_vn_getattr,
 	.setattr		= xfs_vn_setattr,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index dfeda44..d2fdf09 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1633,7 +1633,7 @@ struct file_operations {
 
 struct inode_operations {
 	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
-	const char * (*follow_link) (struct dentry *, void **);
+	const char * (*get_link) (struct dentry *, struct inode *, void **);
 	int (*permission) (struct inode *, int);
 	struct posix_acl * (*get_acl)(struct inode *, int);
 
@@ -2736,7 +2736,7 @@ extern const struct file_operations generic_ro_fops;
 
 extern int readlink_copy(char __user *, int, const char *);
 extern int page_readlink(struct dentry *, char __user *, int);
-extern const char *page_follow_link_light(struct dentry *, void **);
+extern const char *page_get_link(struct dentry *, struct inode *, void **);
 extern void page_put_link(struct inode *, void *);
 extern int __page_symlink(struct inode *inode, const char *symname, int len,
 		int nofs);
@@ -2754,7 +2754,7 @@ void __inode_sub_bytes(struct inode *inode, loff_t bytes);
 void inode_sub_bytes(struct inode *inode, loff_t bytes);
 loff_t inode_get_bytes(struct inode *inode);
 void inode_set_bytes(struct inode *inode, loff_t bytes);
-const char *simple_follow_link(struct dentry *, void **);
+const char *simple_get_link(struct dentry *, struct inode *, void **);
 extern const struct inode_operations simple_symlink_inode_operations;
 
 extern int iterate_dir(struct file *, struct dir_context *);
diff --git a/mm/shmem.c b/mm/shmem.c
index 64bf5ac..684dbc3 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2496,10 +2496,14 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	return 0;
 }
 
-static const char *shmem_follow_link(struct dentry *dentry, void **cookie)
+static const char *shmem_get_link(struct dentry *dentry,
+				  struct inode *inode, void **cookie)
 {
 	struct page *page = NULL;
-	int error = shmem_getpage(d_inode(dentry), 0, &page, SGP_READ, NULL);
+	int error;
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+	error = shmem_getpage(inode, 0, &page, SGP_READ, NULL);
 	if (error)
 		return ERR_PTR(error);
 	unlock_page(page);
@@ -2656,7 +2660,7 @@ static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
 
 static const struct inode_operations shmem_short_symlink_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= simple_follow_link,
+	.get_link	= simple_get_link,
 #ifdef CONFIG_TMPFS_XATTR
 	.setxattr	= shmem_setxattr,
 	.getxattr	= shmem_getxattr,
@@ -2667,7 +2671,7 @@ static const struct inode_operations shmem_short_symlink_operations = {
 
 static const struct inode_operations shmem_symlink_inode_operations = {
 	.readlink	= generic_readlink,
-	.follow_link	= shmem_follow_link,
+	.get_link	= shmem_get_link,
 	.put_link	= shmem_put_link,
 #ifdef CONFIG_TMPFS_XATTR
 	.setxattr	= shmem_setxattr,
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 08/11] teach page_get_link() to work in RCU mode
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (6 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 07/11] replace ->follow_link() with new method that could stay in RCU mode Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 09/11] teach shmem_get_link() " Al Viro
                     ` (4 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

more or less along the lines of Neil's patchset, sans the insanity
around kmap().

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/namei.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 1da3064..8f51788 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4533,12 +4533,19 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode,
 	struct page *page;
 	struct address_space *mapping = inode->i_mapping;
 
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
-
-	page = read_mapping_page(mapping, 0, NULL);
-	if (IS_ERR(page))
-		return (char*)page;
+	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;
+	}
 	*cookie = page;
 	BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
 	kaddr = page_address(page);
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 09/11] teach shmem_get_link() to work in RCU mode
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (7 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 08/11] teach page_get_link() to work " Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 10/11] teach proc_self_get_link()/proc_thread_self_get_link() " Al Viro
                     ` (3 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 mm/shmem.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/mm/shmem.c b/mm/shmem.c
index 684dbc3..0605716 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2501,12 +2501,20 @@ static const char *shmem_get_link(struct dentry *dentry,
 {
 	struct page *page = NULL;
 	int error;
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
-	error = shmem_getpage(inode, 0, &page, SGP_READ, NULL);
-	if (error)
-		return ERR_PTR(error);
-	unlock_page(page);
+	if (!dentry) {
+		page = find_get_page(inode->i_mapping, 0);
+		if (!page)
+			return ERR_PTR(-ECHILD);
+		if (!PageUptodate(page)) {
+			put_page(page);
+			return ERR_PTR(-ECHILD);
+		}
+	} else {
+		error = shmem_getpage(inode, 0, &page, SGP_READ, NULL);
+		if (error)
+			return ERR_PTR(error);
+		unlock_page(page);
+	}
 	*cookie = page;
 	return page_address(page);
 }
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 10/11] teach proc_self_get_link()/proc_thread_self_get_link() to work in RCU mode
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (8 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 09/11] teach shmem_get_link() " Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09  5:34   ` [PATCH v2 11/11] teach nfs_get_link() " Al Viro
                     ` (2 subsequent siblings)
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/proc/self.c        | 8 +++-----
 fs/proc/thread_self.c | 9 ++++-----
 2 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/fs/proc/self.c b/fs/proc/self.c
index 9dd0ae6..7a8b19e 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -25,14 +25,12 @@ static const char *proc_self_get_link(struct dentry *dentry,
 	pid_t tgid = task_tgid_nr_ns(current, ns);
 	char *name;
 
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
 	if (!tgid)
 		return ERR_PTR(-ENOENT);
 	/* 11 for max length of signed int in decimal + NULL term */
-	name = kmalloc(12, GFP_KERNEL);
-	if (!name)
-		return ERR_PTR(-ENOMEM);
+	name = kmalloc(12, dentry ? GFP_KERNEL : GFP_ATOMIC);
+	if (unlikely(!name))
+		return dentry ? ERR_PTR(-ENOMEM) : ERR_PTR(-ECHILD);
 	sprintf(name, "%d", tgid);
 	return *cookie = name;
 }
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 50eef6f..03eaa84 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -27,13 +27,12 @@ static const char *proc_thread_self_get_link(struct dentry *dentry,
 	pid_t pid = task_pid_nr_ns(current, ns);
 	char *name;
 
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
 	if (!pid)
 		return ERR_PTR(-ENOENT);
-	name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL);
-	if (!name)
-		return ERR_PTR(-ENOMEM);
+	name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF,
+				dentry ? GFP_KERNEL : GFP_ATOMIC);
+	if (unlikely(!name))
+		return dentry ? ERR_PTR(-ENOMEM) : ERR_PTR(-ECHILD);
 	sprintf(name, "%d/task/%d", tgid, pid);
 	return *cookie = name;
 }
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 11/11] teach nfs_get_link() to work in RCU mode
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (9 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 10/11] teach proc_self_get_link()/proc_thread_self_get_link() " Al Viro
@ 2015-12-09  5:34   ` Al Viro
  2015-12-09 17:24   ` [PATCHSET v2] ->follow_link() without dropping from " Linus Torvalds
  2015-12-09 21:57   ` NeilBrown
  12 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-09  5:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Linus Torvalds, Neil Brown, linux-fsdevel

From: Al Viro <viro@zeniv.linux.org.uk>

based upon the corresponding patch from Neil's March patchset,
again with kmap-related horrors removed.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/nfs/inode.c         | 21 +++++++++++++++++++++
 fs/nfs/symlink.c       | 30 ++++++++++++++++++++----------
 include/linux/nfs_fs.h |  1 +
 3 files changed, 42 insertions(+), 10 deletions(-)

diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index ae9aa0b..aa828e8 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -1087,6 +1087,27 @@ static bool nfs_mapping_need_revalidate_inode(struct inode *inode)
 		|| NFS_STALE(inode);
 }
 
+int nfs_revalidate_mapping_rcu(struct inode *inode)
+{
+	struct nfs_inode *nfsi = NFS_I(inode);
+	unsigned long *bitlock = &nfsi->flags;
+	int ret = 0;
+
+	if (IS_SWAPFILE(inode))
+		goto out;
+	if (nfs_mapping_need_revalidate_inode(inode)) {
+		ret = -ECHILD;
+		goto out;
+	}
+	spin_lock(&inode->i_lock);
+	if (test_bit(NFS_INO_INVALIDATING, bitlock) ||
+	    (nfsi->cache_validity & NFS_INO_INVALID_DATA))
+		ret = -ECHILD;
+	spin_unlock(&inode->i_lock);
+out:
+	return ret;
+}
+
 /**
  * __nfs_revalidate_mapping - Revalidate the pagecache
  * @inode - pointer to host inode
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index 8ade8a8..95c69af 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -48,16 +48,26 @@ static const char *nfs_get_link(struct dentry *dentry,
 	struct page *page;
 	void *err;
 
-	if (!dentry)
-		return ERR_PTR(-ECHILD);
-
-	err = ERR_PTR(nfs_revalidate_mapping(inode, inode->i_mapping));
-	if (err)
-		return err;
-	page = read_cache_page(&inode->i_data, 0,
-				(filler_t *)nfs_symlink_filler, inode);
-	if (IS_ERR(page))
-		return ERR_CAST(page);
+	if (!dentry) {
+		err = ERR_PTR(nfs_revalidate_mapping_rcu(inode));
+		if (err)
+			return err;
+		page = find_get_page(inode->i_mapping, 0);
+		if (!page)
+			return ERR_PTR(-ECHILD);
+		if (!PageUptodate(page)) {
+			put_page(page);
+			return ERR_PTR(-ECHILD);
+		}
+	} else {
+		err = ERR_PTR(nfs_revalidate_mapping(inode, inode->i_mapping));
+		if (err)
+			return err;
+		page = read_cache_page(&inode->i_data, 0,
+					(filler_t *)nfs_symlink_filler, inode);
+		if (IS_ERR(page))
+			return ERR_CAST(page);
+	}
 	*cookie = page;
 	return page_address(page);
 }
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index c0e9614..37a3d29 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -359,6 +359,7 @@ extern int nfs_revalidate_inode(struct nfs_server *server, struct inode *inode);
 extern int nfs_revalidate_inode_rcu(struct nfs_server *server, struct inode *inode);
 extern int __nfs_revalidate_inode(struct nfs_server *, struct inode *);
 extern int nfs_revalidate_mapping(struct inode *inode, struct address_space *mapping);
+extern int nfs_revalidate_mapping_rcu(struct inode *inode);
 extern int nfs_revalidate_mapping_protected(struct inode *inode, struct address_space *mapping);
 extern int nfs_setattr(struct dentry *, struct iattr *);
 extern void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr, struct nfs_fattr *);
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (10 preceding siblings ...)
  2015-12-09  5:34   ` [PATCH v2 11/11] teach nfs_get_link() " Al Viro
@ 2015-12-09 17:24   ` Linus Torvalds
  2015-12-09 18:23     ` Al Viro
  2015-12-09 21:57   ` NeilBrown
  12 siblings, 1 reply; 48+ messages in thread
From: Linus Torvalds @ 2015-12-09 17:24 UTC (permalink / raw)
  To: Al Viro; +Cc: Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Tue, Dec 8, 2015 at 9:32 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> Any help with review and testing would be welcome.  FWIW, it seems to survive
> the beating here.

It looks ok to me, but I *hate* that "cookie" thing we have. And I
don't actually see any reason for it.

Every single user except for a couple seem to just put the resulting
symlink address into the cookie (and then put_link() will kfree it or
put_page() it). And the calling convention would be *so* much clearer
if we just made things be

   char *get_link(dentry, inode);
   put_link(dentry, inode, char *);

and get rid of that cookie entirely.

The only users of the cookie seem to be

 - we currently use it for the page pointer vs the page data address.

   That's because of the HIGHMEM crap that you removed. Without
highmem, the page data address specifies the page fine without any
cookie.

 - overlayfs, which uses it to create a temporary data storage.

   Why? Mainly because it wants to hide the cookie in there. D'oh!

 - proc, which uses it to save the proc_dir_entry

   Why? The reason seems to be that we don't call put_link() with the
dentry that get_link got, so it can't look it up the sane way.

In other words, as far as I can tell, the cookie is just garbage now.

But maybe I missed something.

Other than that, I like the series. And I realize that technically the
"cookie removal" would be a separate thing, but since you're changing
the calling convention from "follow_link()" to "get_link()" in this
series, I _really_ think that as part of that calling convention
change we should just get rid of the cookie, rather than have
*another* calling convention change later.

Hmm?

                       Linus

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-09 17:24   ` [PATCHSET v2] ->follow_link() without dropping from " Linus Torvalds
@ 2015-12-09 18:23     ` Al Viro
  2015-12-10  0:10       ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2015-12-09 18:23 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Wed, Dec 09, 2015 at 09:24:32AM -0800, Linus Torvalds wrote:

>    That's because of the HIGHMEM crap that you removed. Without
> highmem, the page data address specifies the page fine without any
> cookie.
> 
>  - overlayfs, which uses it to create a temporary data storage.
> 
>    Why? Mainly because it wants to hide the cookie in there. D'oh!

Not really - it also wants to stash a way to underlying inode in there.
See below...

>  - proc, which uses it to save the proc_dir_entry
> 
>    Why? The reason seems to be that we don't call put_link() with the
> dentry that get_link got, so it can't look it up the sane way.

What's more, that dentry might very well have gone negative by that
point.  Think what happens if, during the symlink traversal, we run
into the hard "restart from scratch in non-RCU mode".  We'll need to
do ->put_link() on everything we have in stack.  Regardless of what
might've happened to dentries/inodes of symlinks involved - all we
are really promised is that RCU-delayed parts of their destruction hadn't
been entered yet.

Note, BTW, that *all* ->put_link() instances ignore the inode argument
and I seriously considered dropping it completely.

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
                     ` (11 preceding siblings ...)
  2015-12-09 17:24   ` [PATCHSET v2] ->follow_link() without dropping from " Linus Torvalds
@ 2015-12-09 21:57   ` NeilBrown
  12 siblings, 0 replies; 48+ messages in thread
From: NeilBrown @ 2015-12-09 21:57 UTC (permalink / raw)
  To: Al Viro, linux-kernel; +Cc: linux-fsdevel, Linus Torvalds

[-- Attachment #1: Type: text/plain, Size: 278 bytes --]


Hi Al,
 thanks a lot for this.  I particularly like that you renamed
 "follow_link" which didn't really describe what the function did any
 more.

 Apart from the changelog comment in 4/11 which starts
   "It was to needed for ..."

 I can't fault anything.

Thanks,
NeilBrown

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 818 bytes --]

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-09 18:23     ` Al Viro
@ 2015-12-10  0:10       ` Al Viro
  2015-12-10  2:40         ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2015-12-10  0:10 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Wed, Dec 09, 2015 at 06:23:09PM +0000, Al Viro wrote:

> What's more, that dentry might very well have gone negative by that
> point.  Think what happens if, during the symlink traversal, we run
> into the hard "restart from scratch in non-RCU mode".  We'll need to
> do ->put_link() on everything we have in stack.  Regardless of what
> might've happened to dentries/inodes of symlinks involved - all we
> are really promised is that RCU-delayed parts of their destruction hadn't
> been entered yet.
> 
> Note, BTW, that *all* ->put_link() instances ignore the inode argument
> and I seriously considered dropping it completely.

PS: I toyed with the idea of replacing cookie a struct callback_head, to make
it really obvious what's going on - we are leaving a closure to be evaluated
once the caller is done with our return value.  That way ->put_link() as a
method would've been gone and we would've had something like
	init_callback(destructor, page_put_link, page);
instead of
	*cookie = page;
plus
	.put_link = page_put_link,
we have right now, etc.

The reason not to go that way (and not all that strong, at that) is that
we get an extra pointer in struct saved, i.e. slightly heavier nameidata
(there are two struct saved embedded into it).  That, or play silly buggers
with
	union {
		struct callback_head callback;
		struct inode *inode;
	};
in struct saved - we need to stash a pointer to inode between pick_link()
and get_link().

Hell knows, still might make sense to go there...  What we want is to set
a closure to be evaluated once the caller of ->get_link() is done with
the string ->get_link() returns.  We end up picking the function to be
called at saved->inode->i_op->put_link and store its argument in
saved->cookie.  Might as well just use the representation we use for such
stuff (rcu_head, etc.) and be done with that...

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-10  0:10       ` Al Viro
@ 2015-12-10  2:40         ` Al Viro
  2015-12-11  1:54           ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2015-12-10  2:40 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Thu, Dec 10, 2015 at 12:10:12AM +0000, Al Viro wrote:

> PS: I toyed with the idea of replacing cookie a struct callback_head, to make

... except that it would have to be something like work_struct, since
callback_head gets embedded into the argument and here we don't want to
do that.  _And_ we've no use for ->entry in work_struct, which makes it
a bad fit as well.  IOW, please ignore that idiocy...

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-10  2:40         ` Al Viro
@ 2015-12-11  1:54           ` Al Viro
  2015-12-11  7:49             ` Rasmus Villemoes
  2015-12-13  3:47             ` Al Viro
  0 siblings, 2 replies; 48+ messages in thread
From: Al Viro @ 2015-12-11  1:54 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Thu, Dec 10, 2015 at 02:40:50AM +0000, Al Viro wrote:
> ... except that it would have to be something like work_struct, since
> callback_head gets embedded into the argument and here we don't want to
> do that.  _And_ we've no use for ->entry in work_struct, which makes it
> a bad fit as well.  IOW, please ignore that idiocy...

OK, the following (incremental to that series) seems to be better; in
particular, overlayfs ->get_link() doesn't allocate anything whatsoever,
->put_link() as a method is gone and instead of *cookie = <something that
will make sense for our put_link> we do
	set_delayed_call(done, callback, argument)
which is a whole lot more straightforward.  Memory footprint cost is
trivial - one extra pointer in struct nameidata.

Basically, we add a new structure (struct delayed_call; poor man's closure)
and pass a reference to that to ->get_link(); if anything needs to be done
once we are done with the string returned by ->get_link(), just fill that
structure (as above) and that's it.  It will be evaluated by caller once
it's through with the symlink body.

I'd put it in linux/fs.h for now, but it certainly belongs in something like
linux/types.h; the current location is temporary.  And I'm quite sure that
we have other places where something of that sort is open-coded - timers,
for one thing and probably quite a few other things.

I would really love to be able to say
	set_delayed_call(done, kfree, p);
but as it is I had to keep a wrapper - void kfree_link(void *).  The problem
is, you can't assign void f(const void *) to void (*p)(void *) - mismatch of
qualifiers in the arguments makes the latter not assignment-compatible with
the former.  If there's a clever trick allowing to sidestep that, I'd be
very happy; I don't know one.  Any ideas not starting with "use C11" (or,
worse yet, "use such and such C++ misfeature with arseloads of RTL required
in order to implement it") would be welcome...

	BTW, why are we passing unsigned long to free_page()?  We have
a bit under 700 callers; excluding the ones that have an explicit cast
to unsigned long right in the argument of call leaves ~150, and the rest
tend to contain a lot of pointer casts of unsigned long thing they are feeding
to free_page() (IOW, they would be just as happy if they kept it as a pointer
all along).  Sure, that would mean __get_free_page() et.al. returning void *,
but I don't see any problems with that either...  Is that just for historical
reasons, or is there anything more subtle I'm missing here?

	Anyway, for now the patch below is strictly for review and (if one
is brave enough) testing.  Comments would be very welcome.

diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c
index e5c4137..2610348 100644
--- a/drivers/staging/lustre/lustre/llite/symlink.c
+++ b/drivers/staging/lustre/lustre/llite/symlink.c
@@ -118,8 +118,14 @@ failed:
 	return rc;
 }
 
+static void ll_put_link(void *p)
+{
+	ptlrpc_req_finished(p);
+}
+
 static const char *ll_get_link(struct dentry *dentry,
-			       struct inode *inode, void **cookie)
+			       struct inode *inode,
+			       struct delayed_call *done)
 {
 	struct ptlrpc_request *request = NULL;
 	int rc;
@@ -137,22 +143,16 @@ static const char *ll_get_link(struct dentry *dentry,
 	}
 
 	/* symname may contain a pointer to the request message buffer,
-	 * we delay request releasing until ll_put_link then.
+	 * we delay request releasing then.
 	 */
-	*cookie = request;
+	set_delayed_call(done, ll_put_link, request);
 	return symname;
 }
 
-static void ll_put_link(struct inode *unused, void *cookie)
-{
-	ptlrpc_req_finished(cookie);
-}
-
 const struct inode_operations ll_fast_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.setattr	= ll_setattr,
 	.get_link	= ll_get_link,
-	.put_link	= ll_put_link,
 	.getattr	= ll_getattr,
 	.permission	= ll_inode_permission,
 	.setxattr	= ll_setxattr,
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index a237f47..c7cc7c3 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -1226,11 +1226,12 @@ ino_t v9fs_qid2ino(struct p9_qid *qid)
  * v9fs_vfs_get_link - follow a symlink path
  * @dentry: dentry for symlink
  * @inode: inode for symlink
- * @cookie: place to pass the data to put_link()
+ * @done: delayed call for when we are done with the return value
  */
 
 static const char *v9fs_vfs_get_link(struct dentry *dentry,
-				     struct inode *inode, void **cookie)
+				     struct inode *inode,
+				     struct delayed_call *done)
 {
 	struct v9fs_session_info *v9ses;
 	struct p9_fid *fid;
@@ -1266,7 +1267,8 @@ static const char *v9fs_vfs_get_link(struct dentry *dentry,
 
 	p9stat_free(st);
 	kfree(st);
-	return *cookie = res;
+	set_delayed_call(done, kfree_link, res);
+	return res;
 }
 
 /**
@@ -1460,7 +1462,6 @@ static const struct inode_operations v9fs_file_inode_operations = {
 static const struct inode_operations v9fs_symlink_inode_operations = {
 	.readlink = generic_readlink,
 	.get_link = v9fs_vfs_get_link,
-	.put_link = kfree_put_link,
 	.getattr = v9fs_vfs_getattr,
 	.setattr = v9fs_vfs_setattr,
 };
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index 0cc105d..a34702c 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -902,12 +902,13 @@ error:
  * v9fs_vfs_get_link_dotl - follow a symlink path
  * @dentry: dentry for symlink
  * @inode: inode for symlink
- * @cookie: place to pass the data to put_link()
+ * @done: destructor for return value
  */
 
 static const char *
 v9fs_vfs_get_link_dotl(struct dentry *dentry,
-		       struct inode *inode, void **cookie)
+		       struct inode *inode,
+		       struct delayed_call *done)
 {
 	struct p9_fid *fid;
 	char *target;
@@ -924,7 +925,8 @@ v9fs_vfs_get_link_dotl(struct dentry *dentry,
 	retval = p9_client_readlink(fid, &target);
 	if (retval)
 		return ERR_PTR(retval);
-	return *cookie = target;
+	set_delayed_call(done, kfree_link, target);
+	return target;
 }
 
 int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode)
@@ -991,7 +993,6 @@ const struct inode_operations v9fs_file_inode_operations_dotl = {
 const struct inode_operations v9fs_symlink_inode_operations_dotl = {
 	.readlink = generic_readlink,
 	.get_link = v9fs_vfs_get_link_dotl,
-	.put_link = kfree_put_link,
 	.getattr = v9fs_vfs_getattr_dotl,
 	.setattr = v9fs_vfs_setattr_dotl,
 	.setxattr = generic_setxattr,
diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
index 39d1194..69b03db 100644
--- a/fs/affs/symlink.c
+++ b/fs/affs/symlink.c
@@ -72,6 +72,5 @@ const struct address_space_operations affs_symlink_aops = {
 const struct inode_operations affs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.setattr	= affs_notify_change,
 };
diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c
index 39e6f0b..84e037d 100644
--- a/fs/autofs4/symlink.c
+++ b/fs/autofs4/symlink.c
@@ -13,7 +13,8 @@
 #include "autofs_i.h"
 
 static const char *autofs4_get_link(struct dentry *dentry,
-				    struct inode *inode, void **cookie)
+				    struct inode *inode,
+				    struct delayed_call *done)
 {
 	struct autofs_sb_info *sbi;
 	struct autofs_info *ino;
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 5234474..3b8856e 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -10097,7 +10097,6 @@ static const struct inode_operations btrfs_special_inode_operations = {
 static const struct inode_operations btrfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.getattr	= btrfs_getattr,
 	.setattr	= btrfs_setattr,
 	.permission	= btrfs_permission,
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index edcf0fa..b7fcb31 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -901,7 +901,6 @@ const struct inode_operations cifs_file_inode_ops = {
 const struct inode_operations cifs_symlink_inode_ops = {
 	.readlink = generic_readlink,
 	.get_link = cifs_get_link,
-	.put_link = kfree_put_link,
 	.permission = cifs_permission,
 	/* BB add the following two eventually */
 	/* revalidate: cifs_revalidate,
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index 8e79c25..68c4547 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -120,7 +120,8 @@ extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
 #endif
 
 /* Functions related to symlinks */
-extern const char *cifs_get_link(struct dentry *, struct inode *, void **);
+extern const char *cifs_get_link(struct dentry *, struct inode *,
+			struct delayed_call *);
 extern int cifs_symlink(struct inode *inode, struct dentry *direntry,
 			const char *symname);
 extern int	cifs_removexattr(struct dentry *, const char *);
diff --git a/fs/cifs/link.c b/fs/cifs/link.c
index 6f2439b5..062c237 100644
--- a/fs/cifs/link.c
+++ b/fs/cifs/link.c
@@ -627,7 +627,8 @@ cifs_hl_exit:
 }
 
 const char *
-cifs_get_link(struct dentry *direntry, struct inode *inode, void **cookie)
+cifs_get_link(struct dentry *direntry, struct inode *inode,
+	      struct delayed_call *done)
 {
 	int rc = -ENOMEM;
 	unsigned int xid;
@@ -680,7 +681,8 @@ cifs_get_link(struct dentry *direntry, struct inode *inode, void **cookie)
 		kfree(target_path);
 		return ERR_PTR(rc);
 	}
-	return *cookie = target_path;
+	set_delayed_call(done, kfree_link, target_path);
+	return target_path;
 }
 
 int
diff --git a/fs/coda/cnode.c b/fs/coda/cnode.c
index f18139c..1bfb7ba 100644
--- a/fs/coda/cnode.c
+++ b/fs/coda/cnode.c
@@ -19,7 +19,6 @@ static inline int coda_fideq(struct CodaFid *fid1, struct CodaFid *fid2)
 static const struct inode_operations coda_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.setattr	= coda_setattr,
 };
 
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index b91c01e..83d80a7 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -280,9 +280,11 @@ static int configfs_getlink(struct dentry *dentry, char * path)
 }
 
 static const char *configfs_get_link(struct dentry *dentry,
-				     struct inode *inode, void **cookie)
+				     struct inode *inode,
+				     struct delayed_call *done)
 {
 	unsigned long page;
+	char *body;
 	int error;
 
 	if (!dentry)
@@ -292,9 +294,11 @@ static const char *configfs_get_link(struct dentry *dentry,
 	if (!page)
 		return ERR_PTR(-ENOMEM);
 
-	error = configfs_getlink(dentry, (char *)page);
+	body = (char *)page;
+	error = configfs_getlink(dentry, body);
 	if (!error) {
-		return *cookie = (void *)page;
+		set_delayed_call(done, free_page_link, body);
+		return body;
 	}
 
 	free_page(page);
@@ -304,7 +308,6 @@ static const char *configfs_get_link(struct dentry *dentry,
 const struct inode_operations configfs_symlink_inode_operations = {
 	.get_link = configfs_get_link,
 	.readlink = generic_readlink,
-	.put_link = free_page_put_link,
 	.setattr = configfs_setattr,
 };
 
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 55241bf..040aa87 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -673,7 +673,8 @@ out:
 }
 
 static const char *ecryptfs_get_link(struct dentry *dentry,
-				     struct inode *inode, void **cookie)
+				     struct inode *inode,
+				     struct delayed_call *done)
 {
 	size_t len;
 	char *buf;
@@ -687,7 +688,8 @@ static const char *ecryptfs_get_link(struct dentry *dentry,
 	fsstack_copy_attr_atime(d_inode(dentry),
 				d_inode(ecryptfs_dentry_to_lower(dentry)));
 	buf[len] = '\0';
-	return *cookie = buf;
+	set_delayed_call(done, kfree_link, buf);
+	return buf;
 }
 
 /**
@@ -1100,7 +1102,6 @@ out:
 const struct inode_operations ecryptfs_symlink_iops = {
 	.readlink = generic_readlink,
 	.get_link = ecryptfs_get_link,
-	.put_link = kfree_put_link,
 	.permission = ecryptfs_permission,
 	.setattr = ecryptfs_setattr,
 	.getattr = ecryptfs_getattr_link,
diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c
index 4690511..3495d8a 100644
--- a/fs/ext2/symlink.c
+++ b/fs/ext2/symlink.c
@@ -23,7 +23,6 @@
 const struct inode_operations ext2_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.setattr	= ext2_setattr,
 #ifdef CONFIG_EXT2_FS_XATTR
 	.setxattr	= generic_setxattr,
diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
index 3b4bfe2..2281ac2 100644
--- a/fs/ext4/symlink.c
+++ b/fs/ext4/symlink.c
@@ -24,7 +24,8 @@
 
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
 static const char *ext4_encrypted_get_link(struct dentry *dentry,
-					   struct inode *inode, void **cookie)
+					   struct inode *inode,
+					   struct delayed_call *done)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
@@ -80,7 +81,8 @@ static const char *ext4_encrypted_get_link(struct dentry *dentry,
 		paddr[res] = '\0';
 	if (cpage)
 		page_cache_release(cpage);
-	return *cookie = paddr;
+	set_delayed_call(done, kfree_link, paddr);
+	return paddr;
 errout:
 	if (cpage)
 		page_cache_release(cpage);
@@ -91,7 +93,6 @@ errout:
 const struct inode_operations ext4_encrypted_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= ext4_encrypted_get_link,
-	.put_link       = kfree_put_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
 	.getxattr	= generic_getxattr,
@@ -103,7 +104,6 @@ const struct inode_operations ext4_encrypted_symlink_inode_operations = {
 const struct inode_operations ext4_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.setattr	= ext4_setattr,
 	.setxattr	= generic_setxattr,
 	.getxattr	= generic_getxattr,
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index 2a8d84b..1165021 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -316,12 +316,14 @@ fail:
 }
 
 static const char *f2fs_get_link(struct dentry *dentry,
-				 struct inode *inode, void **cookie)
+				 struct inode *inode,
+				 struct delayed_call *done)
 {
-	const char *link = page_get_link(dentry, inode, cookie);
+	const char *link = page_get_link(dentry, inode, done);
 	if (!IS_ERR(link) && !*link) {
 		/* this is broken symlink case */
-		page_put_link(NULL, *cookie);
+		do_delayed_call(done);
+		done->fn = NULL;
 		link = ERR_PTR(-ENOENT);
 	}
 	return link;
@@ -926,7 +928,8 @@ static int f2fs_rename2(struct inode *old_dir, struct dentry *old_dentry,
 
 #ifdef CONFIG_F2FS_FS_ENCRYPTION
 static const char *f2fs_encrypted_get_link(struct dentry *dentry,
-					   struct inode *inode, void **cookie)
+					   struct inode *inode,
+					   struct delayed_call *done)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
@@ -988,7 +991,8 @@ static const char *f2fs_encrypted_get_link(struct dentry *dentry,
 	paddr[res] = '\0';
 
 	page_cache_release(cpage);
-	return *cookie = paddr;
+	set_delayed_call(done, kfree_link, paddr);
+	return paddr;
 errout:
 	kfree(cstr.name);
 	f2fs_fname_crypto_free_buffer(&pstr);
@@ -999,7 +1003,6 @@ errout:
 const struct inode_operations f2fs_encrypted_symlink_inode_operations = {
 	.readlink       = generic_readlink,
 	.get_link       = f2fs_encrypted_get_link,
-	.put_link       = kfree_put_link,
 	.getattr	= f2fs_getattr,
 	.setattr	= f2fs_setattr,
 	.setxattr	= generic_setxattr,
@@ -1035,7 +1038,6 @@ const struct inode_operations f2fs_dir_inode_operations = {
 const struct inode_operations f2fs_symlink_inode_operations = {
 	.readlink       = generic_readlink,
 	.get_link       = f2fs_get_link,
-	.put_link       = page_put_link,
 	.getattr	= f2fs_getattr,
 	.setattr	= f2fs_setattr,
 #ifdef CONFIG_F2FS_FS_XATTR
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 148e8ef..e397c1a 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1366,7 +1366,8 @@ static int fuse_readdir(struct file *file, struct dir_context *ctx)
 }
 
 static const char *fuse_get_link(struct dentry *dentry,
-				 struct inode *inode, void **cookie)
+				 struct inode *inode,
+				 struct delayed_call *done)
 {
 	struct fuse_conn *fc = get_fuse_conn(inode);
 	FUSE_ARGS(args);
@@ -1392,7 +1393,7 @@ static const char *fuse_get_link(struct dentry *dentry,
 		link = ERR_PTR(ret);
 	} else {
 		link[ret] = '\0';
-		*cookie = link;
+		set_delayed_call(done, free_page_link, link);
 	}
 	fuse_invalidate_atime(inode);
 	return link;
@@ -1913,7 +1914,6 @@ static const struct inode_operations fuse_common_inode_operations = {
 static const struct inode_operations fuse_symlink_inode_operations = {
 	.setattr	= fuse_setattr,
 	.get_link	= fuse_get_link,
-	.put_link	= free_page_put_link,
 	.readlink	= generic_readlink,
 	.getattr	= fuse_getattr,
 	.setxattr	= fuse_setxattr,
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 10950560..1bae189 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1715,7 +1715,7 @@ static int gfs2_rename2(struct inode *odir, struct dentry *odentry,
  * gfs2_get_link - Follow a symbolic link
  * @dentry: The dentry of the link
  * @inode: The inode of the link
- * @cookie: place to store the information for ->put_link()
+ * @done: destructor for return value
  *
  * This can handle symlinks of any size.
  *
@@ -1723,7 +1723,8 @@ static int gfs2_rename2(struct inode *odir, struct dentry *odentry,
  */
 
 static const char *gfs2_get_link(struct dentry *dentry,
-				 struct inode *inode, void **cookie)
+				 struct inode *inode,
+				 struct delayed_call *done)
 {
 	struct gfs2_inode *ip = GFS2_I(inode);
 	struct gfs2_holder i_gh;
@@ -1764,7 +1765,7 @@ static const char *gfs2_get_link(struct dentry *dentry,
 out:
 	gfs2_glock_dq_uninit(&i_gh);
 	if (!IS_ERR(buf))
-		*cookie = buf;
+		set_delayed_call(done, kfree_link, buf);
 	return buf;
 }
 
@@ -2138,7 +2139,6 @@ const struct inode_operations gfs2_dir_iops = {
 const struct inode_operations gfs2_symlink_iops = {
 	.readlink = generic_readlink,
 	.get_link = gfs2_get_link,
-	.put_link = kfree_put_link,
 	.permission = gfs2_permission,
 	.setattr = gfs2_setattr,
 	.getattr = gfs2_getattr,
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index 6ce5309..7db524c 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -893,12 +893,13 @@ static const struct inode_operations hostfs_dir_iops = {
 };
 
 static const char *hostfs_get_link(struct dentry *dentry,
-				   struct inode *inode, void **cookie)
+				   struct inode *inode,
+				   struct delayed_call *done)
 {
 	char *link;
 	if (!dentry)
 		return ERR_PTR(-ECHILD);
-	link = __getname();
+	link = kmalloc(PATH_MAX, GFP_KERNEL);
 	if (link) {
 		char *path = dentry_name(dentry);
 		int err = -ENOMEM;
@@ -909,25 +910,20 @@ static const char *hostfs_get_link(struct dentry *dentry,
 			__putname(path);
 		}
 		if (err < 0) {
-			__putname(link);
+			kfree(link);
 			return ERR_PTR(err);
 		}
 	} else {
 		return ERR_PTR(-ENOMEM);
 	}
 
-	return *cookie = link;
-}
-
-static void hostfs_put_link(struct inode *unused, void *cookie)
-{
-	__putname(cookie);
+	set_delayed_call(done, kfree_link, link);
+	return link;
 }
 
 static const struct inode_operations hostfs_link_iops = {
 	.readlink	= generic_readlink,
 	.get_link	= hostfs_get_link,
-	.put_link	= hostfs_put_link,
 };
 
 static int hostfs_fill_sb_common(struct super_block *sb, void *d, int silent)
diff --git a/fs/jfs/symlink.c b/fs/jfs/symlink.c
index 0211328..f8db4fd 100644
--- a/fs/jfs/symlink.c
+++ b/fs/jfs/symlink.c
@@ -34,7 +34,6 @@ const struct inode_operations jfs_fast_symlink_inode_operations = {
 const struct inode_operations jfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.setattr	= jfs_setattr,
 	.setxattr	= jfs_setxattr,
 	.getxattr	= jfs_getxattr,
diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c
index ffae857..1267e0c 100644
--- a/fs/kernfs/symlink.c
+++ b/fs/kernfs/symlink.c
@@ -113,22 +113,26 @@ static int kernfs_getlink(struct dentry *dentry, char *path)
 }
 
 static const char *kernfs_iop_get_link(struct dentry *dentry,
-				       struct inode *inode, void **cookie)
+				       struct inode *inode,
+				       struct delayed_call *done)
 {
-	int error = -ENOMEM;
 	unsigned long page;
+	char *body;
+	int error;
 
 	if (!dentry)
 		return ERR_PTR(-ECHILD);
 	page = get_zeroed_page(GFP_KERNEL);
 	if (!page)
 		return ERR_PTR(-ENOMEM);
-	error = kernfs_getlink(dentry, (char *)page);
+	body = (char *)page;
+	error = kernfs_getlink(dentry, body);
 	if (unlikely(error < 0)) {
-		free_page((unsigned long)page);
+		free_page(page);
 		return ERR_PTR(error);
 	}
-	return *cookie = (char *)page;
+	set_delayed_call(done, free_page_link, body);
+	return body;
 }
 
 const struct inode_operations kernfs_symlink_iops = {
@@ -138,7 +142,6 @@ const struct inode_operations kernfs_symlink_iops = {
 	.listxattr	= kernfs_iop_listxattr,
 	.readlink	= generic_readlink,
 	.get_link	= kernfs_iop_get_link,
-	.put_link	= free_page_put_link,
 	.setattr	= kernfs_iop_setattr,
 	.getattr	= kernfs_iop_getattr,
 	.permission	= kernfs_iop_permission,
diff --git a/fs/libfs.c b/fs/libfs.c
index 8dc37fc..447bba7 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -1019,17 +1019,18 @@ int noop_fsync(struct file *file, loff_t start, loff_t end, int datasync)
 }
 EXPORT_SYMBOL(noop_fsync);
 
-void kfree_put_link(struct inode *unused, void *cookie)
+/* Because kfree isn't assignment-compatible with void(void*) ;-/ */
+void kfree_link(void *p)
 {
-	kfree(cookie);
+	kfree(p);
 }
-EXPORT_SYMBOL(kfree_put_link);
+EXPORT_SYMBOL(kfree_link);
 
-void free_page_put_link(struct inode *unused, void *cookie)
+void free_page_link(void *p)
 {
-	free_page((unsigned long) cookie);
+	free_page((unsigned long)p);
 }
-EXPORT_SYMBOL(free_page_put_link);
+EXPORT_SYMBOL(free_page_link);
 
 /*
  * nop .set_page_dirty method so that people can use .page_mkwrite on
@@ -1093,7 +1094,7 @@ simple_nosetlease(struct file *filp, long arg, struct file_lock **flp,
 EXPORT_SYMBOL(simple_nosetlease);
 
 const char *simple_get_link(struct dentry *dentry, struct inode *inode,
-			    void **cookie)
+			    struct delayed_call *done)
 {
 	return inode->i_link;
 }
diff --git a/fs/minix/inode.c b/fs/minix/inode.c
index 3cce709..cb1789c 100644
--- a/fs/minix/inode.c
+++ b/fs/minix/inode.c
@@ -436,7 +436,6 @@ static const struct address_space_operations minix_aops = {
 static const struct inode_operations minix_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.getattr	= minix_getattr,
 };
 
diff --git a/fs/namei.c b/fs/namei.c
index ea20ff4..8914593 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -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;
 };
@@ -590,11 +590,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);
+		last->done.fn = NULL;
 	}
 }
 
@@ -876,9 +873,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);
 }
@@ -910,7 +905,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;
 
@@ -1001,7 +996,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;
 
@@ -1022,23 +1017,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) {
-			res = inode->i_op->get_link(NULL, inode,
-						    &last->cookie);
+			res = get(NULL, inode, &last->done);
 			if (res == ERR_PTR(-ECHILD)) {
 				if (unlikely(unlazy_walk(nd, NULL, 0)))
 					return ERR_PTR(-ECHILD);
-				res = inode->i_op->get_link(dentry, inode,
-						            &last->cookie);
+				res = get(dentry, inode, &last->done);
 			}
 		} else {
-			res = inode->i_op->get_link(dentry, inode,
-						    &last->cookie);
+			res = get(dentry, inode, &last->done);
 		}
-		if (IS_ERR_OR_NULL(res)) {
-			last->cookie = NULL;
+		if (IS_ERR_OR_NULL(res))
 			return res;
-		}
 	}
 	if (*res == '/') {
 		if (!nd->root.mnt)
@@ -1699,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;
+	last->done.fn = NULL;
+	nd->link_inode = inode;
 	last->seq = seq;
 	return 1;
 }
@@ -4502,26 +4495,25 @@ EXPORT_SYMBOL(readlink_copy);
  */
 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->get_link(dentry, inode, &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 */
 const char *page_get_link(struct dentry *dentry, struct inode *inode,
-				 void **cookie)
+			  struct delayed_call *callback)
 {
 	char *kaddr;
 	struct page *page;
@@ -4540,7 +4532,7 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode,
 		if (IS_ERR(page))
 			return (char*)page;
 	}
-	*cookie = 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);
@@ -4549,21 +4541,19 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode,
 
 EXPORT_SYMBOL(page_get_link);
 
-void page_put_link(struct inode *unused, void *cookie)
+void page_put_link(void *arg)
 {
-	struct page *page = cookie;
-	page_cache_release(page);
+	put_page(arg);
 }
 EXPORT_SYMBOL(page_put_link);
 
 int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 {
-	void *cookie = NULL;
+	DEFINE_DELAYED_CALL(done);
 	int res = readlink_copy(buffer, buflen,
 				page_get_link(dentry, d_inode(dentry),
-					      &cookie));
-	if (cookie)
-		page_put_link(NULL, cookie);
+					      &done));
+	do_delayed_call(&done);
 	return res;
 }
 EXPORT_SYMBOL(page_readlink);
@@ -4613,6 +4603,5 @@ EXPORT_SYMBOL(page_symlink);
 const struct inode_operations page_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 };
 EXPORT_SYMBOL(page_symlink_inode_operations);
diff --git a/fs/ncpfs/inode.c b/fs/ncpfs/inode.c
index 3ab6cdb..ce1eb3f 100644
--- a/fs/ncpfs/inode.c
+++ b/fs/ncpfs/inode.c
@@ -245,7 +245,6 @@ static void ncp_set_attr(struct inode *inode, struct ncp_entry_info *nwinfo)
 static const struct inode_operations ncp_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.setattr	= ncp_notify_change,
 };
 #endif
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index 95c69af..4fe3eea 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -43,7 +43,8 @@ error:
 }
 
 static const char *nfs_get_link(struct dentry *dentry,
-				struct inode *inode, void **cookie)
+				struct inode *inode,
+				struct delayed_call *done)
 {
 	struct page *page;
 	void *err;
@@ -68,7 +69,7 @@ static const char *nfs_get_link(struct dentry *dentry,
 		if (IS_ERR(page))
 			return ERR_CAST(page);
 	}
-	*cookie = page;
+	set_delayed_call(done, page_put_link, page);
 	return page_address(page);
 }
 
@@ -78,7 +79,6 @@ static const char *nfs_get_link(struct dentry *dentry,
 const struct inode_operations nfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= nfs_get_link,
-	.put_link	= page_put_link,
 	.getattr	= nfs_getattr,
 	.setattr	= nfs_setattr,
 };
diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
index 63dddb7..7ccdb96 100644
--- a/fs/nilfs2/namei.c
+++ b/fs/nilfs2/namei.c
@@ -570,7 +570,6 @@ const struct inode_operations nilfs_special_inode_operations = {
 const struct inode_operations nilfs_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.permission     = nilfs_permission,
 };
 
diff --git a/fs/ocfs2/symlink.c b/fs/ocfs2/symlink.c
index b4e79bc..6c2a3e3 100644
--- a/fs/ocfs2/symlink.c
+++ b/fs/ocfs2/symlink.c
@@ -89,7 +89,6 @@ const struct address_space_operations ocfs2_fast_symlink_aops = {
 const struct inode_operations ocfs2_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.getattr	= ocfs2_getattr,
 	.setattr	= ocfs2_setattr,
 	.setxattr	= generic_setxattr,
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 38a0b8b..964a60f 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -131,19 +131,12 @@ out_dput:
 	return err;
 }
 
-
-struct ovl_link_data {
-	struct dentry *realdentry;
-	void *cookie;
-};
-
 static const char *ovl_get_link(struct dentry *dentry,
-				struct inode *inode, void **cookie)
+				struct inode *inode,
+				struct delayed_call *done)
 {
 	struct dentry *realdentry;
 	struct inode *realinode;
-	struct ovl_link_data *data = NULL;
-	const char *ret;
 
 	if (!dentry)
 		return ERR_PTR(-ECHILD);
@@ -154,38 +147,7 @@ static const char *ovl_get_link(struct dentry *dentry,
 	if (WARN_ON(!realinode->i_op->get_link))
 		return ERR_PTR(-EPERM);
 
-	if (realinode->i_op->put_link) {
-		data = kmalloc(sizeof(struct ovl_link_data), GFP_KERNEL);
-		if (!data)
-			return ERR_PTR(-ENOMEM);
-		data->realdentry = realdentry;
-	}
-
-	ret = realinode->i_op->get_link(realdentry, realinode, cookie);
-	if (IS_ERR_OR_NULL(ret)) {
-		kfree(data);
-		return ret;
-	}
-
-	if (data)
-		data->cookie = *cookie;
-
-	*cookie = data;
-
-	return ret;
-}
-
-static void ovl_put_link(struct inode *unused, void *c)
-{
-	struct inode *realinode;
-	struct ovl_link_data *data = c;
-
-	if (!data)
-		return;
-
-	realinode = data->realdentry->d_inode;
-	realinode->i_op->put_link(realinode, data->cookie);
-	kfree(data);
+	return realinode->i_op->get_link(realdentry, realinode, done);
 }
 
 static int ovl_readlink(struct dentry *dentry, char __user *buf, int bufsiz)
@@ -383,7 +345,6 @@ static const struct inode_operations ovl_file_inode_operations = {
 static const struct inode_operations ovl_symlink_inode_operations = {
 	.setattr	= ovl_setattr,
 	.get_link	= ovl_get_link,
-	.put_link	= ovl_put_link,
 	.readlink	= ovl_readlink,
 	.getattr	= ovl_getattr,
 	.setxattr	= ovl_setxattr,
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 1a489e2..71660bb 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -1565,7 +1565,8 @@ static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
 }
 
 static const char *proc_pid_get_link(struct dentry *dentry,
-				     struct inode *inode, void **cookie)
+				     struct inode *inode,
+				     struct delayed_call *done)
 {
 	struct path path;
 	int error = -EACCES;
@@ -1949,12 +1950,13 @@ struct map_files_info {
  */
 static const char *
 proc_map_files_get_link(struct dentry *dentry,
-			struct inode *inode, void **cookie)
+			struct inode *inode,
+		        struct delayed_call *done)
 {
 	if (!capable(CAP_SYS_ADMIN))
 		return ERR_PTR(-EPERM);
 
-	return proc_pid_get_link(dentry, inode, NULL);
+	return proc_pid_get_link(dentry, inode, done);
 }
 
 /*
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 10360b2..d0e9b9b 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -393,25 +393,25 @@ static const struct file_operations proc_reg_file_ops_no_compat = {
 };
 #endif
 
+static void proc_put_link(void *p)
+{
+	unuse_pde(p);
+}
+
 static const char *proc_get_link(struct dentry *dentry,
-				 struct inode *inode, void **cookie)
+				 struct inode *inode,
+				 struct delayed_call *done)
 {
 	struct proc_dir_entry *pde = PDE(inode);
 	if (unlikely(!use_pde(pde)))
 		return ERR_PTR(-EINVAL);
-	*cookie = pde;
+	set_delayed_call(done, proc_put_link, pde);
 	return pde->data;
 }
 
-static void proc_put_link(struct inode *unused, void *p)
-{
-	unuse_pde(p);
-}
-
 const struct inode_operations proc_link_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= proc_get_link,
-	.put_link	= proc_put_link,
 };
 
 struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c
index 63861c1..1dece87 100644
--- a/fs/proc/namespaces.c
+++ b/fs/proc/namespaces.c
@@ -31,7 +31,8 @@ static const struct proc_ns_operations *ns_entries[] = {
 };
 
 static const char *proc_ns_get_link(struct dentry *dentry,
-				    struct inode *inode, void **cookie)
+				    struct inode *inode,
+				    struct delayed_call *done)
 {
 	const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops;
 	struct task_struct *task;
diff --git a/fs/proc/self.c b/fs/proc/self.c
index 7a8b19e..67e8db4 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -19,7 +19,8 @@ static int proc_self_readlink(struct dentry *dentry, char __user *buffer,
 }
 
 static const char *proc_self_get_link(struct dentry *dentry,
-				      struct inode *inode, void **cookie)
+				      struct inode *inode,
+				      struct delayed_call *done)
 {
 	struct pid_namespace *ns = inode->i_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
@@ -32,13 +33,13 @@ static const char *proc_self_get_link(struct dentry *dentry,
 	if (unlikely(!name))
 		return dentry ? ERR_PTR(-ENOMEM) : ERR_PTR(-ECHILD);
 	sprintf(name, "%d", tgid);
-	return *cookie = name;
+	set_delayed_call(done, kfree_link, name);
+	return name;
 }
 
 static const struct inode_operations proc_self_inode_operations = {
 	.readlink	= proc_self_readlink,
 	.get_link	= proc_self_get_link,
-	.put_link	= kfree_put_link,
 };
 
 static unsigned self_inum;
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 03eaa84..9eacd59 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -20,7 +20,8 @@ static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer,
 }
 
 static const char *proc_thread_self_get_link(struct dentry *dentry,
-					     struct inode *inode, void **cookie)
+					     struct inode *inode,
+					     struct delayed_call *done)
 {
 	struct pid_namespace *ns = inode->i_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
@@ -34,13 +35,13 @@ static const char *proc_thread_self_get_link(struct dentry *dentry,
 	if (unlikely(!name))
 		return dentry ? ERR_PTR(-ENOMEM) : ERR_PTR(-ECHILD);
 	sprintf(name, "%d/task/%d", tgid, pid);
-	return *cookie = name;
+	set_delayed_call(done, kfree_link, name);
+	return name;
 }
 
 static const struct inode_operations proc_thread_self_inode_operations = {
 	.readlink	= proc_thread_self_readlink,
 	.get_link	= proc_thread_self_get_link,
-	.put_link	= kfree_put_link,
 };
 
 static unsigned thread_self_inum;
diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
index ecbf11e..2a12d46 100644
--- a/fs/reiserfs/namei.c
+++ b/fs/reiserfs/namei.c
@@ -1666,7 +1666,6 @@ const struct inode_operations reiserfs_dir_inode_operations = {
 const struct inode_operations reiserfs_symlink_inode_operations = {
 	.readlink = generic_readlink,
 	.get_link	= page_get_link,
-	.put_link = page_put_link,
 	.setattr = reiserfs_setattr,
 	.setxattr = reiserfs_setxattr,
 	.getxattr = reiserfs_getxattr,
diff --git a/fs/squashfs/symlink.c b/fs/squashfs/symlink.c
index 7c635a5..dbcc2f5 100644
--- a/fs/squashfs/symlink.c
+++ b/fs/squashfs/symlink.c
@@ -120,7 +120,6 @@ const struct address_space_operations squashfs_symlink_aops = {
 const struct inode_operations squashfs_symlink_inode_ops = {
 	.readlink = generic_readlink,
 	.get_link = page_get_link,
-	.put_link = page_put_link,
 	.getxattr = generic_getxattr,
 	.listxattr = squashfs_listxattr
 };
diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c
index 80a40bc..07ac18c 100644
--- a/fs/sysv/inode.c
+++ b/fs/sysv/inode.c
@@ -147,7 +147,6 @@ static inline void write3byte(struct sysv_sb_info *sbi,
 static const struct inode_operations sysv_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= page_get_link,
-	.put_link	= page_put_link,
 	.getattr	= sysv_getattr,
 };
 
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index f638fd5..06eafaf 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -417,7 +417,7 @@ STATIC const char *
 xfs_vn_get_link(
 	struct dentry		*dentry,
 	struct inode		*inode,
-	void			**cookie)
+	struct delayed_call	*done)
 {
 	char			*link;
 	int			error = -ENOMEM;
@@ -433,7 +433,8 @@ xfs_vn_get_link(
 	if (unlikely(error))
 		goto out_kfree;
 
-	return *cookie = link;
+	set_delayed_call(done, kfree_link, link);
+	return link;
 
  out_kfree:
 	kfree(link);
@@ -1177,7 +1178,6 @@ static const struct inode_operations xfs_dir_ci_inode_operations = {
 static const struct inode_operations xfs_symlink_inode_operations = {
 	.readlink		= generic_readlink,
 	.get_link		= xfs_vn_get_link,
-	.put_link		= kfree_put_link,
 	.getattr		= xfs_vn_getattr,
 	.setattr		= xfs_vn_setattr,
 	.setxattr		= generic_setxattr,
diff --git a/include/linux/fs.h b/include/linux/fs.h
index b18c020..351cfb2 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1635,14 +1635,34 @@ struct file_operations {
 			u64);
 };
 
+struct delayed_call {
+	void (*fn)(void *);
+	void *arg;
+};
+
+#define DEFINE_DELAYED_CALL(name) struct delayed_call name = {NULL, NULL}
+
+/* I really wish we had closures with sane typechecking... */
+static inline void set_delayed_call(struct delayed_call *call,
+		void (*fn)(void *), void *arg)
+{
+	call->fn = fn;
+	call->arg = arg;
+}
+
+static inline void do_delayed_call(struct delayed_call *call)
+{
+	if (call->fn)
+		call->fn(call->arg);
+}
+
 struct inode_operations {
 	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
-	const char * (*get_link) (struct dentry *, struct inode *, void **);
+	const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
 	int (*permission) (struct inode *, int);
 	struct posix_acl * (*get_acl)(struct inode *, int);
 
 	int (*readlink) (struct dentry *, char __user *,int);
-	void (*put_link) (struct inode *, void *);
 
 	int (*create) (struct inode *,struct dentry *, umode_t, bool);
 	int (*link) (struct dentry *,struct inode *,struct dentry *);
@@ -2744,14 +2764,15 @@ extern const struct file_operations generic_ro_fops;
 
 extern int readlink_copy(char __user *, int, const char *);
 extern int page_readlink(struct dentry *, char __user *, int);
-extern const char *page_get_link(struct dentry *, struct inode *, void **);
-extern void page_put_link(struct inode *, void *);
+extern const char *page_get_link(struct dentry *, struct inode *,
+				 struct delayed_call *);
+extern void page_put_link(void *);
 extern int __page_symlink(struct inode *inode, const char *symname, int len,
 		int nofs);
 extern int page_symlink(struct inode *inode, const char *symname, int len);
 extern const struct inode_operations page_symlink_inode_operations;
-extern void kfree_put_link(struct inode *, void *);
-extern void free_page_put_link(struct inode *, void *);
+extern void kfree_link(void *);
+extern void free_page_link(void *);
 extern int generic_readlink(struct dentry *, char __user *, int);
 extern void generic_fillattr(struct inode *, struct kstat *);
 int vfs_getattr_nosec(struct path *path, struct kstat *stat);
@@ -2762,7 +2783,8 @@ void __inode_sub_bytes(struct inode *inode, loff_t bytes);
 void inode_sub_bytes(struct inode *inode, loff_t bytes);
 loff_t inode_get_bytes(struct inode *inode);
 void inode_set_bytes(struct inode *inode, loff_t bytes);
-const char *simple_get_link(struct dentry *, struct inode *, void **);
+const char *simple_get_link(struct dentry *, struct inode *,
+			    struct delayed_call *);
 extern const struct inode_operations simple_symlink_inode_operations;
 
 extern int iterate_dir(struct file *, struct dir_context *);
diff --git a/mm/shmem.c b/mm/shmem.c
index 5e43f1f..728f67a 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2496,8 +2496,15 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	return 0;
 }
 
+static void shmem_put_link(void *arg)
+{
+	mark_page_accessed(arg);
+	put_page(arg);
+}
+
 static const char *shmem_get_link(struct dentry *dentry,
-				  struct inode *inode, void **cookie)
+				  struct inode *inode,
+				  struct delayed_call *done)
 {
 	struct page *page = NULL;
 	int error;
@@ -2515,17 +2522,10 @@ static const char *shmem_get_link(struct dentry *dentry,
 			return ERR_PTR(error);
 		unlock_page(page);
 	}
-	*cookie = page;
+	set_delayed_call(done, shmem_put_link, page);
 	return page_address(page);
 }
 
-static void shmem_put_link(struct inode *unused, void *cookie)
-{
-	struct page *page = cookie;
-	mark_page_accessed(page);
-	page_cache_release(page);
-}
-
 #ifdef CONFIG_TMPFS_XATTR
 /*
  * Superblocks without xattr inode operations may get some security.* xattr
@@ -2633,7 +2633,6 @@ static const struct inode_operations shmem_short_symlink_operations = {
 static const struct inode_operations shmem_symlink_inode_operations = {
 	.readlink	= generic_readlink,
 	.get_link	= shmem_get_link,
-	.put_link	= shmem_put_link,
 #ifdef CONFIG_TMPFS_XATTR
 	.setxattr	= generic_setxattr,
 	.getxattr	= generic_getxattr,

^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-11  1:54           ` Al Viro
@ 2015-12-11  7:49             ` Rasmus Villemoes
  2015-12-11 23:16               ` Al Viro
  2015-12-13  3:47             ` Al Viro
  1 sibling, 1 reply; 48+ messages in thread
From: Rasmus Villemoes @ 2015-12-11  7:49 UTC (permalink / raw)
  To: Al Viro
  Cc: Linus Torvalds, Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Fri, Dec 11 2015, Al Viro <viro@ZenIV.linux.org.uk> wrote:

> I would really love to be able to say
> 	set_delayed_call(done, kfree, p);
> but as it is I had to keep a wrapper - void kfree_link(void *).  The problem
> is, you can't assign void f(const void *) to void (*p)(void *) - mismatch of
> qualifiers in the arguments makes the latter not assignment-compatible with
> the former.  If there's a clever trick allowing to sidestep that, I'd be
> very happy; I don't know one.  Any ideas not starting with "use C11" (or,
> worse yet, "use such and such C++ misfeature with arseloads of RTL required
> in order to implement it") would be welcome...

I _think_ this satisfies these very reasonable criteria. What you're
looking for is presumably __attribute__((__transparent_union__)). At
least this compiles without warnings at -Wall -Wextra and gives the
expected disassembly, and the gcc docs mention transparent_union at
least back to 4.0.4.

#include <stddef.h>

struct delayed_call {
	void (*fn)(void *);
	void *arg;
};
union delayed_call_fn {
	void (*fn)(void *);
	void (*kfree_like)(const void *);
} __attribute__((__transparent_union__));

void
set_delayed_call(struct delayed_call *call, union delayed_call_fn u, void *arg)
{
	call->fn = u.fn;
	call->arg = arg;
}

void some_cb(void *);
void kfree(const void *);
extern struct delayed_call done;

void test(void)
{
	set_delayed_call(&done, some_cb, NULL);
	set_delayed_call(&done, kfree, NULL);
}

Rasmus

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-11  7:49             ` Rasmus Villemoes
@ 2015-12-11 23:16               ` Al Viro
  2015-12-12  2:00                 ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2015-12-11 23:16 UTC (permalink / raw)
  To: Rasmus Villemoes
  Cc: Linus Torvalds, Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Fri, Dec 11, 2015 at 08:49:24AM +0100, Rasmus Villemoes wrote:

> union delayed_call_fn {
> 	void (*fn)(void *);
> 	void (*kfree_like)(const void *);
> } __attribute__((__transparent_union__));
>
> void
> set_delayed_call(struct delayed_call *call, union delayed_call_fn u, void *arg)
> {
> 	call->fn = u.fn;
> 	call->arg = arg;
> }

	Yecchhhh...  If we are into that kind of gccisms, I'd rather use
__builtin_choose_expr/__builtin_types_compatible_p.  At least that is
used elsewhere in the kernel; __transparent_union__ kludge isn't.  Sure, it
means having set_delayed_call() a macro, but IMO it's less nasty that way...

	FWIW, another possibility is to have
#define CLOSURE_CALLBACK(f,type) \
	static inline void __closure_##f(void *p) {return f((type)p);}

#define set_delayed_type(call, f, arg) \
	sizeof(f(arg),0), \
	__set_delayed_type(call, __closure_##f, (void *)arg)

That could be reused for timers with typechecking - we have a lot of timer
callbacks that start with casting the argument (unsigned long, not void *,
but that's not a big deal) to whatever it is that callback really wants,
with setup_timer() callers explicitly casting that whatever the callback
really wants to unsigned long.  Which, of course, defeats the typechecking
by both cc(1) and sparse(1)...

I still hope for better solution, though...  Comments?

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-11 23:16               ` Al Viro
@ 2015-12-12  2:00                 ` Al Viro
  2015-12-13 18:43                   ` Rasmus Villemoes
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2015-12-12  2:00 UTC (permalink / raw)
  To: Rasmus Villemoes
  Cc: Linus Torvalds, Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Fri, Dec 11, 2015 at 11:16:24PM +0000, Al Viro wrote:

> #define set_delayed_type(call, f, arg) \
> 	sizeof(f(arg),0), \
> 	__set_delayed_type(call, __closure_##f, (void *)arg)
> 
> That could be reused for timers with typechecking - we have a lot of timer
> callbacks that start with casting the argument (unsigned long, not void *,
> but that's not a big deal) to whatever it is that callback really wants,
> with setup_timer() callers explicitly casting that whatever the callback
> really wants to unsigned long.  Which, of course, defeats the typechecking
> by both cc(1) and sparse(1)...
> 
> I still hope for better solution, though...  Comments?

Hmm...

#define set_delayed_type(call, f, arg)					\
 	(sizeof(f(arg),0),						\
	__set_delayed_type(call, ({					\
		void _(void *__) {f((typeof(arg))(unsigned long)__);}	\
		_;}), (void *)arg))

woult do nicely, but it's a gccism, and the one clang doesn't support ;-/
gcc support goes back at least to 4.1, not sure about the earlier branches
(and not at all sure how flaky it was/is, obviously).

But this thing works for e.g. f being void(char) and arg being char,
and AFAICS generates correct code - we end up with a stub like
_.1765:
        movsbl  %dil, %edi
        jmp     f
and its address gets stored in call->fn, which should DTRT on call...

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-11  1:54           ` Al Viro
  2015-12-11  7:49             ` Rasmus Villemoes
@ 2015-12-13  3:47             ` Al Viro
  1 sibling, 0 replies; 48+ messages in thread
From: Al Viro @ 2015-12-13  3:47 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Fri, Dec 11, 2015 at 01:54:25AM +0000, Al Viro wrote:

> 	BTW, why are we passing unsigned long to free_page()?  We have
> a bit under 700 callers; excluding the ones that have an explicit cast
> to unsigned long right in the argument of call leaves ~150, and the rest
> tend to contain a lot of pointer casts of unsigned long thing they are feeding
> to free_page() (IOW, they would be just as happy if they kept it as a pointer
> all along).  Sure, that would mean __get_free_page() et.al. returning void *,
> but I don't see any problems with that either...  Is that just for historical
> reasons, or is there anything more subtle I'm missing here?

The situation with free_pages() is even funnier - we have 313 call sites,
and after converting it to void(void *, unsigned) 31 of them need casts
to void *.  Right now the mainline has 249 of those call sites with
cast to unsigned long (or ulong, in several places).  Then there's a bunch
of places where we do __get_free_pages(), then use it a lot (all with
casts to pointers), then pass to free_pages() - those would be just fine
with storing it as void *, but even leaving those aside...

The current signature is contrary to actual use - nearly 80% of call
sites are forced to cast a pointer to unsigned long, only to have it
cast back to void * in free_pages() itself, we would obviously be better
off if we'd switched to just passing the damn thing as a pointer.  Especially
since that 80% turn into 90% once you add the callers that could easily
switch to storing the value eventually passed to free_pages() as a pointer.

And free_page() is basically the same story, only with twice as many
call sites...

While we are at it: __get_free_pages() has 238 call sites.  193 of them
immediately cast to pointer.  And there's a bunch of places like this:
static inline void __tlb_alloc_page(struct mmu_gather *tlb)
{
        unsigned long addr = __get_free_pages(GFP_NOWAIT | __GFP_NOWARN, 0);

        if (addr) {
                tlb->pages = (void *)addr;
                tlb->max = PAGE_SIZE / sizeof(struct page *);
        }
}
where the cast is not immediate, but might as well had been.  And conversion
of free_pages() whittles it down even more.  For __get_free_page() the
situation is about the same, ditto for get_zeroed_page().

I realize that get_free_page() has been returning unsigned long since 0.01,
but looking at 0.01... it might as well had been returning void * - wouldn't
be more inconvenient.  The kludge you had in get_pipe_inode() would've been
a bit more obviously wrong, but that's about it ;-)

Seriously, though - what do you think about a flagday commit right after
4.5-rc1 switching all those guys to void *?  For __get_user_pages(),
__get_user_page(), get_zeroed_page() - return values, for free_pages(),
free_page() - argument.

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCHSET v2] ->follow_link() without dropping from RCU mode
  2015-12-12  2:00                 ` Al Viro
@ 2015-12-13 18:43                   ` Rasmus Villemoes
  0 siblings, 0 replies; 48+ messages in thread
From: Rasmus Villemoes @ 2015-12-13 18:43 UTC (permalink / raw)
  To: Al Viro
  Cc: Linus Torvalds, Linux Kernel Mailing List, linux-fsdevel, Neil Brown

On Sat, Dec 12 2015, Al Viro <viro@ZenIV.linux.org.uk> wrote:

> On Fri, Dec 11, 2015 at 11:16:24PM +0000, Al Viro wrote:
>
>> #define set_delayed_type(call, f, arg) \
>> 	sizeof(f(arg),0), \
>> 	__set_delayed_type(call, __closure_##f, (void *)arg)
>> 
>> That could be reused for timers with typechecking - we have a lot of timer
>> callbacks that start with casting the argument (unsigned long, not void *,
>> but that's not a big deal) to whatever it is that callback really wants,
>> with setup_timer() callers explicitly casting that whatever the callback
>> really wants to unsigned long.  Which, of course, defeats the typechecking
>> by both cc(1) and sparse(1)...
>> 
>> I still hope for better solution, though...  Comments?
>
> Hmm...
>
> #define set_delayed_type(call, f, arg)					\
>  	(sizeof(f(arg),0),						\
> 	__set_delayed_type(call, ({					\
> 		void _(void *__) {f((typeof(arg))(unsigned long)__);}	\
> 		_;}), (void *)arg))
>
> woult do nicely, but it's a gccism, and the one clang doesn't support
> ;-/

Careful. While this may appear to work, I'm pretty sure it would break
horribly when f is not the actual name of a function but a function
pointer. Not that it won't compile, but it would presumably make gcc
emit what they call a 'trampoline', a small piece of executable code
located on the stack, allowing the stub to access the containing
function's local variables (namely, f). I don't know if kernel stacks
are even executable, but even then it's a bad idea, once the stack frame
containing the trampoline is gone. All of this could easily happen down
the line when someone sees a bunch of similar code and decides to factor
that into a helper(), and then it works whenever gcc decides to inline
the helper to both call sites and otherwise, as the gcc docs put it,
"all hell breaks loose". IOWs, an accident waiting to happen.

One could probably ensure that the macro is always used with a
compile/link-time constant by putting in some trick like

static void *link_time_constant_please = f;

but even then I wouldn't be completely sure that gcc would always emit a
stub as simple as the one you showed.

Rasmus

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2015-12-09  5:34   ` [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem Al Viro
@ 2016-01-14 13:22     ` Tomeu Vizoso
  2016-01-14 15:25       ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Tomeu Vizoso @ 2016-01-14 13:22 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On 9 December 2015 at 06:34, Al Viro <viro@zeniv.linux.org.uk> wrote:
> From: Al Viro <viro@zeniv.linux.org.uk>
>
> kmap() in page_follow_link_light() needed to go - allowing to hold
> an arbitrary number of kmaps for long is a great way to deadlocking
> the system.
>
> new helper (inode_nohighmem(inode)) needs to be used for pagecache
> symlinks inodes; done for all in-tree cases.  page_follow_link_light()
> instrumented to yell about anything missed.

Hi, starting with with this change, I get this oops when installing
packages into a rootfs in NFS:

[  144.455632] Unable to handle kernel NULL pointer dereference at
virtual address 00000000
[  144.463726] pgd = ee760000
[  144.466441] [00000000] *pgd=7b83f831
[  144.470036] Internal error: Oops: 17 [#1] SMP ARM
[  144.474732] Modules linked in:
[  144.477798] CPU: 2 PID: 1 Comm: systemd Not tainted
4.4.0-rc4-00006-g21fc61c73c39-dirty #3521
[  144.486307] Hardware name: Rockchip (Device Tree)
[  144.491004] task: ee078000 ti: ee062000 task.ti: ee062000
[  144.496399] PC is at strlen+0x0/0x2c
[  144.499972] LR is at readlink_copy+0x24/0x94
[  144.504236] pc : [<c049019c>]    lr : [<c031ae40>]    psr: 00000013
[  144.504236] sp : ee063f38  ip : 00000000  fp : ec8a66b0
[  144.515694] r10: 001acf88  r9 : 00000063  r8 : ee063f74
[  144.520909] r7 : 001acef8  r6 : 001acf88  r5 : 00000000  r4 : 00000063
[  144.527424] r3 : c0ee8e94  r2 : 00000000  r1 : 00000063  r0 : 00000000
[  144.533939] Flags: nzcv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment none
[  144.541060] Control: 10c5387d  Table: 2e76006a  DAC: 00000051
[  144.546794] Process systemd (pid: 1, stack limit = 0xee062220)
[  144.552614] Stack: (0xee063f38 to 0xee064000)
[  144.556962] 3f20:
    001acf88 ec8a66b0
[  144.565126] 3f40: 00000063 001acef8 ee063f74 c031aedc 001acf88
eff69f20 ffffffea 00004000
[  144.573290] 3f60: ffffff9c c0315e94 00000000 00000000 00000025
00000000 ee5f89d0 ec8367f8
[  144.581453] 3f80: 56978623 001acf88 00000064 00000063 0000014c
c0210d44 ee062000 00000000
[  144.589616] 3fa0: 001acef8 c0210b80 001acf88 00000064 ffffff9c
001acef8 001acf88 00000063
[  144.597780] 3fc0: 001acf88 00000064 00000063 0000014c be8930b8
001bcc94 0018695b 001acef8
[  144.605943] 3fe0: 0000014c be893094 b6f3bf7b b6ec98e6 20000030
ffffff9c ffffffff ffeffffe
[  144.614109] [<c049019c>] (strlen) from [<c031ae40>] (readlink_copy+0x24/0x94)
[  144.621234] [<c031ae40>] (readlink_copy) from [<c031aedc>]
(generic_readlink+0x2c/0x78)
[  144.629224] [<c031aedc>] (generic_readlink) from [<c0315e94>]
(SyS_readlinkat+0x98/0xe0)
[  144.637301] [<c0315e94>] (SyS_readlinkat) from [<c0210b80>]
(ret_fast_syscall+0x0/0x3c)
[  144.645290] Code: e7d23003 e3130020 1afffffb e12fff1e (e5d02000)
[  144.651388] ---[ end trace 299126b4c29ad1e5 ]---
[  144.658936] Kernel panic - not syncing: Attempted to kill init!
exitcode=0x0000000b
[  144.658936]
[  144.668056] CPU1: stopping
[  144.670759] CPU: 1 PID: 0 Comm: swapper/1 Tainted: G      D
4.4.0-rc4-00006-g21fc61c73c39-dirty #3521
[  144.680651] Hardware name: Rockchip (Device Tree)
[  144.685355] [<c02191a0>] (unwind_backtrace) from [<c0214898>]
(show_stack+0x10/0x14)
[  144.693085] [<c0214898>] (show_stack) from [<c0489ac4>]
(dump_stack+0x84/0x94)
[  144.700295] [<c0489ac4>] (dump_stack) from [<c0217950>]
(handle_IPI+0x188/0x1a8)
[  144.707678] [<c0217950>] (handle_IPI) from [<c020a740>]
(gic_handle_irq+0x90/0x94)
[  144.715233] [<c020a740>] (gic_handle_irq) from [<c02153d4>]
(__irq_svc+0x54/0x70)
[  144.722698] Exception stack(0xee0a7f80 to 0xee0a7fc8)
[  144.727739] 7f80: 00000001 00000000 00000000 c0223ae0 ee0a6000
c0ef04a4 00000000 00000000
[  144.735900] 7fa0: ee0a7fd8 c0ef0504 c09f7624 c0ef050c 2e0ca000
ee0a7fd0 c0211668 c021166c
[  144.744060] 7fc0: 60070013 ffffffff
[  144.747543] [<c02153d4>] (__irq_svc) from [<c021166c>]
(arch_cpu_idle+0x38/0x3c)
[  144.754926] [<c021166c>] (arch_cpu_idle) from [<c028158c>]
(cpu_startup_entry+0x1ec/0x248)
[  144.763175] [<c028158c>] (cpu_startup_entry) from [<0020ab4c>] (0x20ab4c)
[  144.769948] CPU0: stopping
[  144.772650] CPU: 0 PID: 0 Comm: swapper/0 Tainted: G      D
4.4.0-rc4-00006-g21fc61c73c39-dirty #3521
[  144.782543] Hardware name: Rockchip (Device Tree)
[  144.787241] [<c02191a0>] (unwind_backtrace) from [<c0214898>]
(show_stack+0x10/0x14)
[  144.794970] [<c0214898>] (show_stack) from [<c0489ac4>]
(dump_stack+0x84/0x94)
[  144.802179] [<c0489ac4>] (dump_stack) from [<c0217950>]
(handle_IPI+0x188/0x1a8)
[  144.809560] [<c0217950>] (handle_IPI) from [<c020a740>]
(gic_handle_irq+0x90/0x94)
[  144.817114] [<c020a740>] (gic_handle_irq) from [<c02153d4>]
(__irq_svc+0x54/0x70)
[  144.824579] Exception stack(0xc0eeff48 to 0xc0eeff90)
[  144.829620] ff40:                   00000001 00000000 00000000
c0223ae0 c0eee000 c0ef04a4
[  144.837782] ff60: 00000000 00000000 c0eeffa0 c0ef0504 c09f7624
c0ef050c 60000013 c0eeff98
[  144.845942] ff80: c0211668 c021166c 60000013 ffffffff
[  144.850983] [<c02153d4>] (__irq_svc) from [<c021166c>]
(arch_cpu_idle+0x38/0x3c)
[  144.858365] [<c021166c>] (arch_cpu_idle) from [<c028158c>]
(cpu_startup_entry+0x1ec/0x248)
[  144.866614] [<c028158c>] (cpu_startup_entry) from [<c0de6c88>]
(start_kernel+0x3a8/0x3b4)
[  144.874775] CPU3: stopping
[  144.877477] CPU: 3 PID: 8117 Comm: apt-get Tainted: G      D
 4.4.0-rc4-00006-g21fc61c73c39-dirty #3521
[  144.887455] Hardware name: Rockchip (Device Tree)
[  144.892154] [<c02191a0>] (unwind_backtrace) from [<c0214898>]
(show_stack+0x10/0x14)
[  144.899885] [<c0214898>] (show_stack) from [<c0489ac4>]
(dump_stack+0x84/0x94)
[  144.907094] [<c0489ac4>] (dump_stack) from [<c0217950>]
(handle_IPI+0x188/0x1a8)
[  144.914475] [<c0217950>] (handle_IPI) from [<c020a740>]
(gic_handle_irq+0x90/0x94)
[  144.922028] [<c020a740>] (gic_handle_irq) from [<c02153d4>]
(__irq_svc+0x54/0x70)
[  144.929494] Exception stack(0xed57da88 to 0xed57dad0)
[  144.934535] da80:                   c1020dc4 a0000013 c1020dc4
000079a8 00000000 ee273dd0
[  144.942696] daa0: f0bbd000 ee5d2300 00000000 00000000 0000000c
00000000 00000000 ed57dad8
[  144.950856] dac0: c059f2ec c09ef788 60000013 ffffffff
[  144.955899] [<c02153d4>] (__irq_svc) from [<c09ef788>]
(_raw_spin_unlock_irqrestore+0x1c/0x20)
[  144.964496] [<c09ef788>] (_raw_spin_unlock_irqrestore) from
[<c059f2ec>] (uart_chars_in_buffer+0x2c/0x34)
[  144.974046] [<c059f2ec>] (uart_chars_in_buffer) from [<c0588520>]
(n_tty_poll+0x180/0x19c)
[  144.982294] [<c0588520>] (n_tty_poll) from [<c0585418>] (tty_poll+0x6c/0x80)
[  144.989329] [<c0585418>] (tty_poll) from [<c0322f08>] (do_select+0x2dc/0x618)
[  144.996451] [<c0322f08>] (do_select) from [<c0323378>]
(core_sys_select+0x134/0x3f4)
[  145.004179] [<c0323378>] (core_sys_select) from [<c0323940>]
(SyS_pselect6+0x1d8/0x31c)
[  145.012168] [<c0323940>] (SyS_pselect6) from [<c0210b80>]
(ret_fast_syscall+0x0/0x3c)
[  145.019985] ---[ end Kernel panic - not syncing: Attempted to kill
init! exitcode=0x0000000b

Without it, the symlink is read correctly and there's no oops.

Regards,

Tomeu

> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
> ---
>  Documentation/filesystems/porting |  5 +++++
>  fs/affs/inode.c                   |  1 +
>  fs/affs/namei.c                   |  1 +
>  fs/affs/symlink.c                 |  4 +---
>  fs/afs/inode.c                    |  1 +
>  fs/befs/linuxvfs.c                |  5 ++---
>  fs/btrfs/inode.c                  |  2 ++
>  fs/coda/cnode.c                   |  2 ++
>  fs/coda/symlink.c                 |  4 +---
>  fs/cramfs/inode.c                 |  1 +
>  fs/efs/inode.c                    |  1 +
>  fs/efs/symlink.c                  |  4 +---
>  fs/exofs/inode.c                  |  1 +
>  fs/exofs/namei.c                  |  1 +
>  fs/ext2/inode.c                   |  1 +
>  fs/ext2/namei.c                   |  1 +
>  fs/ext4/inode.c                   |  1 +
>  fs/ext4/namei.c                   |  1 +
>  fs/ext4/symlink.c                 | 10 +++-------
>  fs/f2fs/inode.c                   |  1 +
>  fs/f2fs/namei.c                   |  5 ++---
>  fs/freevxfs/vxfs_inode.c          |  1 +
>  fs/hfsplus/inode.c                |  2 ++
>  fs/hpfs/inode.c                   |  1 +
>  fs/hpfs/namei.c                   |  5 ++---
>  fs/hugetlbfs/inode.c              |  1 +
>  fs/inode.c                        |  6 ++++++
>  fs/isofs/inode.c                  |  1 +
>  fs/isofs/rock.c                   |  4 +---
>  fs/jfs/inode.c                    |  1 +
>  fs/jfs/namei.c                    |  1 +
>  fs/logfs/dir.c                    |  1 +
>  fs/logfs/inode.c                  |  1 +
>  fs/minix/inode.c                  |  1 +
>  fs/namei.c                        |  9 +++------
>  fs/ncpfs/inode.c                  |  1 +
>  fs/nfs/inode.c                    |  5 +++--
>  fs/nfs/symlink.c                  |  2 +-
>  fs/nilfs2/inode.c                 |  1 +
>  fs/nilfs2/namei.c                 |  1 +
>  fs/ocfs2/inode.c                  |  1 +
>  fs/ocfs2/namei.c                  |  1 +
>  fs/qnx4/inode.c                   |  1 +
>  fs/qnx6/inode.c                   |  1 +
>  fs/ramfs/inode.c                  |  1 +
>  fs/reiserfs/inode.c               |  1 +
>  fs/reiserfs/namei.c               |  1 +
>  fs/romfs/super.c                  |  1 +
>  fs/squashfs/inode.c               |  2 ++
>  fs/sysv/inode.c                   |  1 +
>  fs/udf/inode.c                    |  1 +
>  fs/udf/namei.c                    |  1 +
>  fs/udf/symlink.c                  |  4 +---
>  fs/ufs/inode.c                    |  1 +
>  fs/ufs/namei.c                    |  1 +
>  include/linux/fs.h                |  1 +
>  mm/shmem.c                        |  9 +++------
>  57 files changed, 81 insertions(+), 46 deletions(-)
>
> diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting
> index f24d1b8..3eb7c35 100644
> --- a/Documentation/filesystems/porting
> +++ b/Documentation/filesystems/porting
> @@ -504,3 +504,8 @@ in your dentry operations instead.
>  [mandatory]
>         __fd_install() & fd_install() can now sleep. Callers should not
>         hold a spinlock or other resources that do not allow a schedule.
> +--
> +[mandatory]
> +       any symlink that might use page_follow_link_light/page_put_link() must
> +       have inode_nohighmem(inode) called before anything might start playing with
> +       its pagecache.
> diff --git a/fs/affs/inode.c b/fs/affs/inode.c
> index 1734950..0fdb0f5 100644
> --- a/fs/affs/inode.c
> +++ b/fs/affs/inode.c
> @@ -140,6 +140,7 @@ struct inode *affs_iget(struct super_block *sb, unsigned long ino)
>                 break;
>         case ST_SOFTLINK:
>                 inode->i_mode |= S_IFLNK;
> +               inode_nohighmem(inode);
>                 inode->i_op = &affs_symlink_inode_operations;
>                 inode->i_data.a_ops = &affs_symlink_aops;
>                 break;
> diff --git a/fs/affs/namei.c b/fs/affs/namei.c
> index 181e05b..00d3002 100644
> --- a/fs/affs/namei.c
> +++ b/fs/affs/namei.c
> @@ -344,6 +344,7 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
>                 return -ENOSPC;
>
>         inode->i_op = &affs_symlink_inode_operations;
> +       inode_nohighmem(inode);
>         inode->i_data.a_ops = &affs_symlink_aops;
>         inode->i_mode = S_IFLNK | 0777;
>         mode_to_prot(inode);
> diff --git a/fs/affs/symlink.c b/fs/affs/symlink.c
> index ea5b69a..e3f9dc3 100644
> --- a/fs/affs/symlink.c
> +++ b/fs/affs/symlink.c
> @@ -14,7 +14,7 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
>  {
>         struct buffer_head *bh;
>         struct inode *inode = page->mapping->host;
> -       char *link = kmap(page);
> +       char *link = page_address(page);
>         struct slink_front *lf;
>         int                      i, j;
>         char                     c;
> @@ -57,12 +57,10 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
>         link[i] = '\0';
>         affs_brelse(bh);
>         SetPageUptodate(page);
> -       kunmap(page);
>         unlock_page(page);
>         return 0;
>  fail:
>         SetPageError(page);
> -       kunmap(page);
>         unlock_page(page);
>         return -EIO;
>  }
> diff --git a/fs/afs/inode.c b/fs/afs/inode.c
> index e06f5a2..86cc726 100644
> --- a/fs/afs/inode.c
> +++ b/fs/afs/inode.c
> @@ -56,6 +56,7 @@ static int afs_inode_map_status(struct afs_vnode *vnode, struct key *key)
>         case AFS_FTYPE_SYMLINK:
>                 inode->i_mode   = S_IFLNK | vnode->status.mode;
>                 inode->i_op     = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 break;
>         default:
>                 printk("kAFS: AFS vnode with undefined type\n");
> diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c
> index 1c8b0dc..25250fa 100644
> --- a/fs/befs/linuxvfs.c
> +++ b/fs/befs/linuxvfs.c
> @@ -397,6 +397,7 @@ static struct inode *befs_iget(struct super_block *sb, unsigned long ino)
>         } else if (S_ISLNK(inode->i_mode)) {
>                 if (befs_ino->i_flags & BEFS_LONG_SYMLINK) {
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         inode->i_mapping->a_ops = &befs_symlink_aops;
>                 } else {
>                         inode->i_link = befs_ino->i_data.symlink;
> @@ -469,7 +470,7 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
>         struct befs_inode_info *befs_ino = BEFS_I(inode);
>         befs_data_stream *data = &befs_ino->i_data.ds;
>         befs_off_t len = data->size;
> -       char *link = kmap(page);
> +       char *link = page_address(page);
>
>         if (len == 0 || len > PAGE_SIZE) {
>                 befs_error(sb, "Long symlink with illegal length");
> @@ -483,12 +484,10 @@ static int befs_symlink_readpage(struct file *unused, struct page *page)
>         }
>         link[len - 1] = '\0';
>         SetPageUptodate(page);
> -       kunmap(page);
>         unlock_page(page);
>         return 0;
>  fail:
>         SetPageError(page);
> -       kunmap(page);
>         unlock_page(page);
>         return -EIO;
>  }
> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index a70c579..70f98bf 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c
> @@ -3774,6 +3774,7 @@ cache_acl:
>                 break;
>         case S_IFLNK:
>                 inode->i_op = &btrfs_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &btrfs_symlink_aops;
>                 break;
>         default:
> @@ -9705,6 +9706,7 @@ static int btrfs_symlink(struct inode *dir, struct dentry *dentry,
>         btrfs_free_path(path);
>
>         inode->i_op = &btrfs_symlink_inode_operations;
> +       inode_nohighmem(inode);
>         inode->i_mapping->a_ops = &btrfs_symlink_aops;
>         inode_set_bytes(inode, name_len);
>         btrfs_i_size_write(inode, name_len);
> diff --git a/fs/coda/cnode.c b/fs/coda/cnode.c
> index 7740b1c..dd6a79e 100644
> --- a/fs/coda/cnode.c
> +++ b/fs/coda/cnode.c
> @@ -8,6 +8,7 @@
>
>  #include <linux/coda.h>
>  #include <linux/coda_psdev.h>
> +#include <linux/pagemap.h>
>  #include "coda_linux.h"
>
>  static inline int coda_fideq(struct CodaFid *fid1, struct CodaFid *fid2)
> @@ -35,6 +36,7 @@ static void coda_fill_inode(struct inode *inode, struct coda_vattr *attr)
>                  inode->i_fop = &coda_dir_operations;
>          } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &coda_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_data.a_ops = &coda_symlink_aops;
>                 inode->i_mapping = &inode->i_data;
>         } else
> diff --git a/fs/coda/symlink.c b/fs/coda/symlink.c
> index ab94ef6..03736e2 100644
> --- a/fs/coda/symlink.c
> +++ b/fs/coda/symlink.c
> @@ -26,7 +26,7 @@ static int coda_symlink_filler(struct file *file, struct page *page)
>         int error;
>         struct coda_inode_info *cii;
>         unsigned int len = PAGE_SIZE;
> -       char *p = kmap(page);
> +       char *p = page_address(page);
>
>         cii = ITOC(inode);
>
> @@ -34,13 +34,11 @@ static int coda_symlink_filler(struct file *file, struct page *page)
>         if (error)
>                 goto fail;
>         SetPageUptodate(page);
> -       kunmap(page);
>         unlock_page(page);
>         return 0;
>
>  fail:
>         SetPageError(page);
> -       kunmap(page);
>         unlock_page(page);
>         return error;
>  }
> diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c
> index 355c522..b862bc2 100644
> --- a/fs/cramfs/inode.c
> +++ b/fs/cramfs/inode.c
> @@ -100,6 +100,7 @@ static struct inode *get_cramfs_inode(struct super_block *sb,
>                 break;
>         case S_IFLNK:
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_data.a_ops = &cramfs_aops;
>                 break;
>         default:
> diff --git a/fs/efs/inode.c b/fs/efs/inode.c
> index 079d203..cdf0872 100644
> --- a/fs/efs/inode.c
> +++ b/fs/efs/inode.c
> @@ -151,6 +151,7 @@ struct inode *efs_iget(struct super_block *super, unsigned long ino)
>                         break;
>                 case S_IFLNK:
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         inode->i_data.a_ops = &efs_symlink_aops;
>                         break;
>                 case S_IFCHR:
> diff --git a/fs/efs/symlink.c b/fs/efs/symlink.c
> index 75117d0..4870cc8 100644
> --- a/fs/efs/symlink.c
> +++ b/fs/efs/symlink.c
> @@ -13,7 +13,7 @@
>
>  static int efs_symlink_readpage(struct file *file, struct page *page)
>  {
> -       char *link = kmap(page);
> +       char *link = page_address(page);
>         struct buffer_head * bh;
>         struct inode * inode = page->mapping->host;
>         efs_block_t size = inode->i_size;
> @@ -39,12 +39,10 @@ static int efs_symlink_readpage(struct file *file, struct page *page)
>         }
>         link[size] = '\0';
>         SetPageUptodate(page);
> -       kunmap(page);
>         unlock_page(page);
>         return 0;
>  fail:
>         SetPageError(page);
> -       kunmap(page);
>         unlock_page(page);
>         return err;
>  }
> diff --git a/fs/exofs/inode.c b/fs/exofs/inode.c
> index 73c64da..d8e9c181 100644
> --- a/fs/exofs/inode.c
> +++ b/fs/exofs/inode.c
> @@ -1227,6 +1227,7 @@ struct inode *exofs_iget(struct super_block *sb, unsigned long ino)
>                         inode->i_link = (char *)oi->i_data;
>                 } else {
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         inode->i_mapping->a_ops = &exofs_aops;
>                 }
>         } else {
> diff --git a/fs/exofs/namei.c b/fs/exofs/namei.c
> index 994e078..c20d77d 100644
> --- a/fs/exofs/namei.c
> +++ b/fs/exofs/namei.c
> @@ -111,6 +111,7 @@ static int exofs_symlink(struct inode *dir, struct dentry *dentry,
>         if (l > sizeof(oi->i_data)) {
>                 /* slow symlink */
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &exofs_aops;
>                 memset(oi->i_data, 0, sizeof(oi->i_data));
>
> diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
> index 0aa9bf6..338eefd 100644
> --- a/fs/ext2/inode.c
> +++ b/fs/ext2/inode.c
> @@ -1420,6 +1420,7 @@ struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
>                                 sizeof(ei->i_data) - 1);
>                 } else {
>                         inode->i_op = &ext2_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         if (test_opt(inode->i_sb, NOBH))
>                                 inode->i_mapping->a_ops = &ext2_nobh_aops;
>                         else
> diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
> index 3267a80d..7a2be8f 100644
> --- a/fs/ext2/namei.c
> +++ b/fs/ext2/namei.c
> @@ -183,6 +183,7 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry,
>         if (l > sizeof (EXT2_I(inode)->i_data)) {
>                 /* slow symlink */
>                 inode->i_op = &ext2_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 if (test_opt(inode->i_sb, NOBH))
>                         inode->i_mapping->a_ops = &ext2_nobh_aops;
>                 else
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index ea433a7..b3bd912 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -4283,6 +4283,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
>                         inode->i_op = &ext4_symlink_inode_operations;
>                         ext4_set_aops(inode);
>                 }
> +               inode_nohighmem(inode);
>         } else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
>               S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
>                 inode->i_op = &ext4_special_inode_operations;
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index a969ab3..f27e0c2 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -3132,6 +3132,7 @@ static int ext4_symlink(struct inode *dir,
>         if ((disk_link.len > EXT4_N_BLOCKS * 4)) {
>                 if (!encryption_required)
>                         inode->i_op = &ext4_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 ext4_set_aops(inode);
>                 /*
>                  * We cannot call page_symlink() with transaction started
> diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
> index abe2401..0e6dc44 100644
> --- a/fs/ext4/symlink.c
> +++ b/fs/ext4/symlink.c
> @@ -45,7 +45,7 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
>                 cpage = read_mapping_page(inode->i_mapping, 0, NULL);
>                 if (IS_ERR(cpage))
>                         return ERR_CAST(cpage);
> -               caddr = kmap(cpage);
> +               caddr = page_address(cpage);
>                 caddr[size] = 0;
>         }
>
> @@ -75,16 +75,12 @@ static const char *ext4_encrypted_follow_link(struct dentry *dentry, void **cook
>         /* Null-terminate the name */
>         if (res <= plen)
>                 paddr[res] = '\0';
> -       if (cpage) {
> -               kunmap(cpage);
> +       if (cpage)
>                 page_cache_release(cpage);
> -       }
>         return *cookie = paddr;
>  errout:
> -       if (cpage) {
> -               kunmap(cpage);
> +       if (cpage)
>                 page_cache_release(cpage);
> -       }
>         kfree(paddr);
>         return ERR_PTR(res);
>  }
> diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
> index 97e20de..5528801 100644
> --- a/fs/f2fs/inode.c
> +++ b/fs/f2fs/inode.c
> @@ -202,6 +202,7 @@ make_now:
>                         inode->i_op = &f2fs_encrypted_symlink_inode_operations;
>                 else
>                         inode->i_op = &f2fs_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &f2fs_dblock_aops;
>         } else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
>                         S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
> diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
> index 2c32110..484df68 100644
> --- a/fs/f2fs/namei.c
> +++ b/fs/f2fs/namei.c
> @@ -351,6 +351,7 @@ static int f2fs_symlink(struct inode *dir, struct dentry *dentry,
>                 inode->i_op = &f2fs_encrypted_symlink_inode_operations;
>         else
>                 inode->i_op = &f2fs_symlink_inode_operations;
> +       inode_nohighmem(inode);
>         inode->i_mapping->a_ops = &f2fs_dblock_aops;
>
>         f2fs_lock_op(sbi);
> @@ -942,7 +943,7 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
>         cpage = read_mapping_page(inode->i_mapping, 0, NULL);
>         if (IS_ERR(cpage))
>                 return ERR_CAST(cpage);
> -       caddr = kmap(cpage);
> +       caddr = page_address(cpage);
>         caddr[size] = 0;
>
>         /* Symlink is encrypted */
> @@ -982,13 +983,11 @@ static const char *f2fs_encrypted_follow_link(struct dentry *dentry, void **cook
>         /* Null-terminate the name */
>         paddr[res] = '\0';
>
> -       kunmap(cpage);
>         page_cache_release(cpage);
>         return *cookie = paddr;
>  errout:
>         kfree(cstr.name);
>         f2fs_fname_crypto_free_buffer(&pstr);
> -       kunmap(cpage);
>         page_cache_release(cpage);
>         return ERR_PTR(res);
>  }
> diff --git a/fs/freevxfs/vxfs_inode.c b/fs/freevxfs/vxfs_inode.c
> index ef73ed6..3e2ccad 100644
> --- a/fs/freevxfs/vxfs_inode.c
> +++ b/fs/freevxfs/vxfs_inode.c
> @@ -326,6 +326,7 @@ vxfs_iget(struct super_block *sbp, ino_t ino)
>         } else if (S_ISLNK(ip->i_mode)) {
>                 if (!VXFS_ISIMMED(vip)) {
>                         ip->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(ip);
>                         ip->i_mapping->a_ops = &vxfs_aops;
>                 } else {
>                         ip->i_op = &simple_symlink_inode_operations;
> diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
> index 6dd107d..19b33f8 100644
> --- a/fs/hfsplus/inode.c
> +++ b/fs/hfsplus/inode.c
> @@ -403,6 +403,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, umode_t mode)
>         } else if (S_ISLNK(inode->i_mode)) {
>                 sbi->file_count++;
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &hfsplus_aops;
>                 hip->clump_blocks = 1;
>         } else
> @@ -526,6 +527,7 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)
>                         inode->i_mapping->a_ops = &hfsplus_aops;
>                 } else if (S_ISLNK(inode->i_mode)) {
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         inode->i_mapping->a_ops = &hfsplus_aops;
>                 } else {
>                         init_special_inode(inode, inode->i_mode,
> diff --git a/fs/hpfs/inode.c b/fs/hpfs/inode.c
> index 933c737..1f3c6d7 100644
> --- a/fs/hpfs/inode.c
> +++ b/fs/hpfs/inode.c
> @@ -77,6 +77,7 @@ void hpfs_read_inode(struct inode *i)
>                         kfree(ea);
>                         i->i_mode = S_IFLNK | 0777;
>                         i->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(i);
>                         i->i_data.a_ops = &hpfs_symlink_aops;
>                         set_nlink(i, 1);
>                         i->i_size = ea_size;
> diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c
> index ae4d5a1..506765a 100644
> --- a/fs/hpfs/namei.c
> +++ b/fs/hpfs/namei.c
> @@ -332,6 +332,7 @@ static int hpfs_symlink(struct inode *dir, struct dentry *dentry, const char *sy
>         result->i_blocks = 1;
>         set_nlink(result, 1);
>         result->i_size = strlen(symlink);
> +       inode_nohighmem(result);
>         result->i_op = &page_symlink_inode_operations;
>         result->i_data.a_ops = &hpfs_symlink_aops;
>
> @@ -500,7 +501,7 @@ out:
>
>  static int hpfs_symlink_readpage(struct file *file, struct page *page)
>  {
> -       char *link = kmap(page);
> +       char *link = page_address(page);
>         struct inode *i = page->mapping->host;
>         struct fnode *fnode;
>         struct buffer_head *bh;
> @@ -516,14 +517,12 @@ static int hpfs_symlink_readpage(struct file *file, struct page *page)
>                 goto fail;
>         hpfs_unlock(i->i_sb);
>         SetPageUptodate(page);
> -       kunmap(page);
>         unlock_page(page);
>         return 0;
>
>  fail:
>         hpfs_unlock(i->i_sb);
>         SetPageError(page);
> -       kunmap(page);
>         unlock_page(page);
>         return err;
>  }
> diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
> index de4bdfa..d8f51ee 100644
> --- a/fs/hugetlbfs/inode.c
> +++ b/fs/hugetlbfs/inode.c
> @@ -760,6 +760,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb,
>                         break;
>                 case S_IFLNK:
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         break;
>                 }
>                 lockdep_annotate_inode_mutex_key(inode);
> diff --git a/fs/inode.c b/fs/inode.c
> index 1be5f90..5bb85a0 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -2028,3 +2028,9 @@ void inode_set_flags(struct inode *inode, unsigned int flags,
>                                   new_flags) != old_flags));
>  }
>  EXPORT_SYMBOL(inode_set_flags);
> +
> +void inode_nohighmem(struct inode *inode)
> +{
> +       mapping_set_gfp_mask(inode->i_mapping, GFP_USER);
> +}
> +EXPORT_SYMBOL(inode_nohighmem);
> diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
> index d67a16f..61abdc4 100644
> --- a/fs/isofs/inode.c
> +++ b/fs/isofs/inode.c
> @@ -1417,6 +1417,7 @@ static int isofs_read_inode(struct inode *inode, int relocated)
>                 inode->i_fop = &isofs_dir_operations;
>         } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_data.a_ops = &isofs_symlink_aops;
>         } else
>                 /* XXX - parse_rock_ridge_inode() had already set i_rdev. */
> diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c
> index 735d752..5384ceb 100644
> --- a/fs/isofs/rock.c
> +++ b/fs/isofs/rock.c
> @@ -687,7 +687,7 @@ static int rock_ridge_symlink_readpage(struct file *file, struct page *page)
>         struct inode *inode = page->mapping->host;
>         struct iso_inode_info *ei = ISOFS_I(inode);
>         struct isofs_sb_info *sbi = ISOFS_SB(inode->i_sb);
> -       char *link = kmap(page);
> +       char *link = page_address(page);
>         unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
>         struct buffer_head *bh;
>         char *rpnt = link;
> @@ -774,7 +774,6 @@ repeat:
>         brelse(bh);
>         *rpnt = '\0';
>         SetPageUptodate(page);
> -       kunmap(page);
>         unlock_page(page);
>         return 0;
>
> @@ -791,7 +790,6 @@ fail:
>         brelse(bh);
>  error:
>         SetPageError(page);
> -       kunmap(page);
>         unlock_page(page);
>         return -EIO;
>  }
> diff --git a/fs/jfs/inode.c b/fs/jfs/inode.c
> index 41aa3ca..9d9bae6 100644
> --- a/fs/jfs/inode.c
> +++ b/fs/jfs/inode.c
> @@ -60,6 +60,7 @@ struct inode *jfs_iget(struct super_block *sb, unsigned long ino)
>         } else if (S_ISLNK(inode->i_mode)) {
>                 if (inode->i_size >= IDATASIZE) {
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         inode->i_mapping->a_ops = &jfs_aops;
>                 } else {
>                         inode->i_op = &jfs_fast_symlink_inode_operations;
> diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c
> index 9d7551f..701f893 100644
> --- a/fs/jfs/namei.c
> +++ b/fs/jfs/namei.c
> @@ -983,6 +983,7 @@ static int jfs_symlink(struct inode *dip, struct dentry *dentry,
>                 jfs_info("jfs_symlink: allocate extent ip:0x%p", ip);
>
>                 ip->i_op = &jfs_symlink_inode_operations;
> +               inode_nohighmem(ip);
>                 ip->i_mapping->a_ops = &jfs_aops;
>
>                 /*
> diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c
> index 99944a4..542468e 100644
> --- a/fs/logfs/dir.c
> +++ b/fs/logfs/dir.c
> @@ -529,6 +529,7 @@ static int logfs_symlink(struct inode *dir, struct dentry *dentry,
>                 return PTR_ERR(inode);
>
>         inode->i_op = &page_symlink_inode_operations;
> +       inode_nohighmem(inode);
>         inode->i_mapping->a_ops = &logfs_reg_aops;
>
>         return __logfs_create(dir, dentry, inode, target, destlen);
> diff --git a/fs/logfs/inode.c b/fs/logfs/inode.c
> index 06baa92..0fce46d 100644
> --- a/fs/logfs/inode.c
> +++ b/fs/logfs/inode.c
> @@ -65,6 +65,7 @@ static void logfs_inode_setops(struct inode *inode)
>                 break;
>         case S_IFLNK:
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &logfs_reg_aops;
>                 break;
>         case S_IFSOCK:  /* fall through */
> diff --git a/fs/minix/inode.c b/fs/minix/inode.c
> index 086cd0a..67a23bf 100644
> --- a/fs/minix/inode.c
> +++ b/fs/minix/inode.c
> @@ -452,6 +452,7 @@ void minix_set_inode(struct inode *inode, dev_t rdev)
>                 inode->i_mapping->a_ops = &minix_aops;
>         } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &minix_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &minix_aops;
>         } else
>                 init_special_inode(inode, inode->i_mode, rdev);
> diff --git a/fs/namei.c b/fs/namei.c
> index 4bae5cb..2808958 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -4527,7 +4527,8 @@ static const char *page_getlink(struct dentry * dentry, void **cookie)
>         if (IS_ERR(page))
>                 return (char*)page;
>         *cookie = page;
> -       kaddr = kmap(page);
> +       BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
> +       kaddr = page_address(page);
>         nd_terminate_link(kaddr, dentry->d_inode->i_size, PAGE_SIZE - 1);
>         return kaddr;
>  }
> @@ -4541,7 +4542,6 @@ EXPORT_SYMBOL(page_follow_link_light);
>  void page_put_link(struct inode *unused, void *cookie)
>  {
>         struct page *page = cookie;
> -       kunmap(page);
>         page_cache_release(page);
>  }
>  EXPORT_SYMBOL(page_put_link);
> @@ -4565,7 +4565,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;
> @@ -4576,9 +4575,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);
> diff --git a/fs/ncpfs/inode.c b/fs/ncpfs/inode.c
> index 9605a2f..bb856f7 100644
> --- a/fs/ncpfs/inode.c
> +++ b/fs/ncpfs/inode.c
> @@ -283,6 +283,7 @@ ncp_iget(struct super_block *sb, struct ncp_entry_info *info)
>  #if defined(CONFIG_NCPFS_EXTRAS) || defined(CONFIG_NCPFS_NFS_NS)
>                 } else if (S_ISLNK(inode->i_mode)) {
>                         inode->i_op = &ncp_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         inode->i_data.a_ops = &ncp_symlink_aops;
>  #endif
>                 } else {
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 31b0a52..ae9aa0b 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -408,9 +408,10 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr, st
>                                 inode->i_fop = NULL;
>                                 inode->i_flags |= S_AUTOMOUNT;
>                         }
> -               } else if (S_ISLNK(inode->i_mode))
> +               } else if (S_ISLNK(inode->i_mode)) {
>                         inode->i_op = &nfs_symlink_inode_operations;
> -               else
> +                       inode_nohighmem(inode);
> +               } else
>                         init_special_inode(inode, inode->i_mode, fattr->rdev);
>
>                 memset(&inode->i_atime, 0, sizeof(inode->i_atime));
> diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
> index b6de433..abd93bf 100644
> --- a/fs/nfs/symlink.c
> +++ b/fs/nfs/symlink.c
> @@ -56,7 +56,7 @@ static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
>         if (IS_ERR(page))
>                 return ERR_CAST(page);
>         *cookie = page;
> -       return kmap(page);
> +       return page_address(page);
>  }
>
>  /*
> diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c
> index ac2f649..10b2252 100644
> --- a/fs/nilfs2/inode.c
> +++ b/fs/nilfs2/inode.c
> @@ -510,6 +510,7 @@ static int __nilfs_read_inode(struct super_block *sb,
>                 inode->i_mapping->a_ops = &nilfs_aops;
>         } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &nilfs_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &nilfs_aops;
>         } else {
>                 inode->i_op = &nilfs_special_inode_operations;
> diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c
> index c9a1a49..90b3ba9 100644
> --- a/fs/nilfs2/namei.c
> +++ b/fs/nilfs2/namei.c
> @@ -161,6 +161,7 @@ static int nilfs_symlink(struct inode *dir, struct dentry *dentry,
>
>         /* slow symlink */
>         inode->i_op = &nilfs_symlink_inode_operations;
> +       inode_nohighmem(inode);
>         inode->i_mapping->a_ops = &nilfs_aops;
>         err = page_symlink(inode, symname, l);
>         if (err)
> diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
> index 8f87e05..97a563b 100644
> --- a/fs/ocfs2/inode.c
> +++ b/fs/ocfs2/inode.c
> @@ -361,6 +361,7 @@ void ocfs2_populate_inode(struct inode *inode, struct ocfs2_dinode *fe,
>                     break;
>             case S_IFLNK:
>                     inode->i_op = &ocfs2_symlink_inode_operations;
> +                   inode_nohighmem(inode);
>                     i_size_write(inode, le64_to_cpu(fe->i_size));
>                     break;
>             default:
> diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
> index a03f6f4..2efe8af 100644
> --- a/fs/ocfs2/namei.c
> +++ b/fs/ocfs2/namei.c
> @@ -1960,6 +1960,7 @@ static int ocfs2_symlink(struct inode *dir,
>         inode->i_rdev = 0;
>         newsize = l - 1;
>         inode->i_op = &ocfs2_symlink_inode_operations;
> +       inode_nohighmem(inode);
>         if (l > ocfs2_fast_symlink_chars(sb)) {
>                 u32 offset = 0;
>
> diff --git a/fs/qnx4/inode.c b/fs/qnx4/inode.c
> index c4bcb77..f37b3de 100644
> --- a/fs/qnx4/inode.c
> +++ b/fs/qnx4/inode.c
> @@ -316,6 +316,7 @@ struct inode *qnx4_iget(struct super_block *sb, unsigned long ino)
>                 inode->i_fop = &qnx4_dir_operations;
>         } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &qnx4_aops;
>                 qnx4_i(inode)->mmu_private = inode->i_size;
>         } else {
> diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
> index 32d2e1a..9728b54 100644
> --- a/fs/qnx6/inode.c
> +++ b/fs/qnx6/inode.c
> @@ -582,6 +582,7 @@ struct inode *qnx6_iget(struct super_block *sb, unsigned ino)
>                 inode->i_mapping->a_ops = &qnx6_aops;
>         } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &qnx6_aops;
>         } else
>                 init_special_inode(inode, inode->i_mode, 0);
> diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c
> index 889d558..38981b0 100644
> --- a/fs/ramfs/inode.c
> +++ b/fs/ramfs/inode.c
> @@ -79,6 +79,7 @@ struct inode *ramfs_get_inode(struct super_block *sb,
>                         break;
>                 case S_IFLNK:
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                         break;
>                 }
>         }
> diff --git a/fs/reiserfs/inode.c b/fs/reiserfs/inode.c
> index 3d8e7e6..ae9e5b3 100644
> --- a/fs/reiserfs/inode.c
> +++ b/fs/reiserfs/inode.c
> @@ -1361,6 +1361,7 @@ static void init_inode(struct inode *inode, struct treepath *path)
>                 inode->i_fop = &reiserfs_dir_operations;
>         } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &reiserfs_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &reiserfs_address_space_operations;
>         } else {
>                 inode->i_blocks = 0;
> diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c
> index 47f9698..4fc2326 100644
> --- a/fs/reiserfs/namei.c
> +++ b/fs/reiserfs/namei.c
> @@ -1170,6 +1170,7 @@ static int reiserfs_symlink(struct inode *parent_dir,
>         reiserfs_update_inode_transaction(parent_dir);
>
>         inode->i_op = &reiserfs_symlink_inode_operations;
> +       inode_nohighmem(inode);
>         inode->i_mapping->a_ops = &reiserfs_address_space_operations;
>
>         retval = reiserfs_add_entry(&th, parent_dir, dentry->d_name.name,
> diff --git a/fs/romfs/super.c b/fs/romfs/super.c
> index 268733c..bb894e7 100644
> --- a/fs/romfs/super.c
> +++ b/fs/romfs/super.c
> @@ -360,6 +360,7 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
>                 break;
>         case ROMFH_SYM:
>                 i->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(i);
>                 i->i_data.a_ops = &romfs_aops;
>                 mode |= S_IRWXUGO;
>                 break;
> diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c
> index a1ce5ce..0927b1e 100644
> --- a/fs/squashfs/inode.c
> +++ b/fs/squashfs/inode.c
> @@ -41,6 +41,7 @@
>  #include <linux/fs.h>
>  #include <linux/vfs.h>
>  #include <linux/xattr.h>
> +#include <linux/pagemap.h>
>
>  #include "squashfs_fs.h"
>  #include "squashfs_fs_sb.h"
> @@ -291,6 +292,7 @@ int squashfs_read_inode(struct inode *inode, long long ino)
>                 set_nlink(inode, le32_to_cpu(sqsh_ino->nlink));
>                 inode->i_size = le32_to_cpu(sqsh_ino->symlink_size);
>                 inode->i_op = &squashfs_symlink_inode_ops;
> +               inode_nohighmem(inode);
>                 inode->i_data.a_ops = &squashfs_symlink_aops;
>                 inode->i_mode |= S_IFLNK;
>                 squashfs_i(inode)->start = block;
> diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c
> index 02fa1dc..ef8bcdb 100644
> --- a/fs/sysv/inode.c
> +++ b/fs/sysv/inode.c
> @@ -163,6 +163,7 @@ void sysv_set_inode(struct inode *inode, dev_t rdev)
>                 inode->i_mapping->a_ops = &sysv_aops;
>         } else if (S_ISLNK(inode->i_mode)) {
>                 inode->i_op = &sysv_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &sysv_aops;
>         } else
>                 init_special_inode(inode, inode->i_mode, rdev);
> diff --git a/fs/udf/inode.c b/fs/udf/inode.c
> index 8675c2b..0557463 100644
> --- a/fs/udf/inode.c
> +++ b/fs/udf/inode.c
> @@ -1541,6 +1541,7 @@ reread:
>         case ICBTAG_FILE_TYPE_SYMLINK:
>                 inode->i_data.a_ops = &udf_symlink_aops;
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mode = S_IFLNK | S_IRWXUGO;
>                 break;
>         case ICBTAG_FILE_TYPE_MAIN:
> diff --git a/fs/udf/namei.c b/fs/udf/namei.c
> index d0e6de1..42eafb9 100644
> --- a/fs/udf/namei.c
> +++ b/fs/udf/namei.c
> @@ -922,6 +922,7 @@ static int udf_symlink(struct inode *dir, struct dentry *dentry,
>
>         inode->i_data.a_ops = &udf_symlink_aops;
>         inode->i_op = &page_symlink_inode_operations;
> +       inode_nohighmem(inode);
>
>         if (iinfo->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
>                 struct kernel_lb_addr eloc;
> diff --git a/fs/udf/symlink.c b/fs/udf/symlink.c
> index 862535b..8d61977 100644
> --- a/fs/udf/symlink.c
> +++ b/fs/udf/symlink.c
> @@ -107,7 +107,7 @@ static int udf_symlink_filler(struct file *file, struct page *page)
>         struct buffer_head *bh = NULL;
>         unsigned char *symlink;
>         int err;
> -       unsigned char *p = kmap(page);
> +       unsigned char *p = page_address(page);
>         struct udf_inode_info *iinfo;
>         uint32_t pos;
>
> @@ -141,7 +141,6 @@ static int udf_symlink_filler(struct file *file, struct page *page)
>
>         up_read(&iinfo->i_data_sem);
>         SetPageUptodate(page);
> -       kunmap(page);
>         unlock_page(page);
>         return 0;
>
> @@ -149,7 +148,6 @@ out_unlock_inode:
>         up_read(&iinfo->i_data_sem);
>         SetPageError(page);
>  out_unmap:
> -       kunmap(page);
>         unlock_page(page);
>         return err;
>  }
> diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c
> index 737160a..d897e16 100644
> --- a/fs/ufs/inode.c
> +++ b/fs/ufs/inode.c
> @@ -533,6 +533,7 @@ static void ufs_set_inode_ops(struct inode *inode)
>                 } else {
>                         inode->i_mapping->a_ops = &ufs_aops;
>                         inode->i_op = &page_symlink_inode_operations;
> +                       inode_nohighmem(inode);
>                 }
>         } else
>                 init_special_inode(inode, inode->i_mode,
> diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c
> index 24b0cbd..acf4a3b 100644
> --- a/fs/ufs/namei.c
> +++ b/fs/ufs/namei.c
> @@ -124,6 +124,7 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry,
>         if (l > UFS_SB(sb)->s_uspi->s_maxsymlinklen) {
>                 /* slow symlink */
>                 inode->i_op = &page_symlink_inode_operations;
> +               inode_nohighmem(inode);
>                 inode->i_mapping->a_ops = &ufs_aops;
>                 err = page_symlink(inode, symname, l);
>                 if (err)
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 3aa5142..dfeda44 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -3025,5 +3025,6 @@ static inline bool dir_relax(struct inode *inode)
>  }
>
>  extern bool path_noexec(const struct path *path);
> +extern void inode_nohighmem(struct inode *inode);
>
>  #endif /* _LINUX_FS_H */
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 9187eee..64bf5ac 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -2444,7 +2444,6 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
>         int len;
>         struct inode *inode;
>         struct page *page;
> -       char *kaddr;
>         struct shmem_inode_info *info;
>
>         len = strlen(symname) + 1;
> @@ -2483,9 +2482,8 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
>                 }
>                 inode->i_mapping->a_ops = &shmem_aops;
>                 inode->i_op = &shmem_symlink_inode_operations;
> -               kaddr = kmap_atomic(page);
> -               memcpy(kaddr, symname, len);
> -               kunmap_atomic(kaddr);
> +               inode_nohighmem(inode);
> +               memcpy(page_address(page), symname, len);
>                 SetPageUptodate(page);
>                 set_page_dirty(page);
>                 unlock_page(page);
> @@ -2506,13 +2504,12 @@ static const char *shmem_follow_link(struct dentry *dentry, void **cookie)
>                 return ERR_PTR(error);
>         unlock_page(page);
>         *cookie = page;
> -       return kmap(page);
> +       return page_address(page);
>  }
>
>  static void shmem_put_link(struct inode *unused, void *cookie)
>  {
>         struct page *page = cookie;
> -       kunmap(page);
>         mark_page_accessed(page);
>         page_cache_release(page);
>  }
> --
> 2.1.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 13:22     ` Tomeu Vizoso
@ 2016-01-14 15:25       ` Al Viro
  2016-01-14 15:58         ` Tomeu Vizoso
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2016-01-14 15:25 UTC (permalink / raw)
  To: Tomeu Vizoso; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 02:22:51PM +0100, Tomeu Vizoso wrote:
> On 9 December 2015 at 06:34, Al Viro <viro@zeniv.linux.org.uk> wrote:
> > From: Al Viro <viro@zeniv.linux.org.uk>
> >
> > kmap() in page_follow_link_light() needed to go - allowing to hold
> > an arbitrary number of kmaps for long is a great way to deadlocking
> > the system.
> >
> > new helper (inode_nohighmem(inode)) needs to be used for pagecache
> > symlinks inodes; done for all in-tree cases.  page_follow_link_light()
> > instrumented to yell about anything missed.
> 
> Hi, starting with with this change, I get this oops when installing
> packages into a rootfs in NFS:

Lovely...  So you somehow getting a highmem page out
                page = read_cache_page(&inode->i_data, 0,
                                        (filler_t *)nfs_symlink_filler, inode);
                if (IS_ERR(page))
                        return ERR_CAST(page);
and that - after
                        inode_nohighmem(inode);
(otherwise you wouldn't get nfs_symlink_inode_operations on that inode).

Could you add
	printk(KERN_ERR "i_data = %p, i_mapping = %p, flags: %lx\n",
		&inode->i_data,
		inode->i_mapping,
		(unsigned long)inode->i_data.flags);
right before the return from nfs_get_link() and see what it prints?

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 15:25       ` Al Viro
@ 2016-01-14 15:58         ` Tomeu Vizoso
  2016-01-14 16:23           ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Tomeu Vizoso @ 2016-01-14 15:58 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On 14 January 2016 at 16:25, Al Viro <viro@zeniv.linux.org.uk> wrote:
> On Thu, Jan 14, 2016 at 02:22:51PM +0100, Tomeu Vizoso wrote:
>> On 9 December 2015 at 06:34, Al Viro <viro@zeniv.linux.org.uk> wrote:
>> > From: Al Viro <viro@zeniv.linux.org.uk>
>> >
>> > kmap() in page_follow_link_light() needed to go - allowing to hold
>> > an arbitrary number of kmaps for long is a great way to deadlocking
>> > the system.
>> >
>> > new helper (inode_nohighmem(inode)) needs to be used for pagecache
>> > symlinks inodes; done for all in-tree cases.  page_follow_link_light()
>> > instrumented to yell about anything missed.
>>
>> Hi, starting with with this change, I get this oops when installing
>> packages into a rootfs in NFS:
>
> Lovely...  So you somehow getting a highmem page out
>                 page = read_cache_page(&inode->i_data, 0,
>                                         (filler_t *)nfs_symlink_filler, inode);
>                 if (IS_ERR(page))
>                         return ERR_CAST(page);
> and that - after
>                         inode_nohighmem(inode);
> (otherwise you wouldn't get nfs_symlink_inode_operations on that inode).
>
> Could you add
>         printk(KERN_ERR "i_data = %p, i_mapping = %p, flags: %lx\n",
>                 &inode->i_data,
>                 inode->i_mapping,
>                 (unsigned long)inode->i_data.flags);
> right before the return from nfs_get_link() and see what it prints?

Here it is:

[  170.136956] i_data = ed9c1b04, i_mapping = ed9c1b04, flags: 24200c0
[  170.144567] i_data = ed9de784, i_mapping = ed9de784, flags: 24200c0
[  170.151457] i_data = ed9dec84, i_mapping = ed9dec84, flags: 24200c0
[  170.158358] i_data = ed9c3b84, i_mapping = ed9c3b84, flags: 24200c0
[  170.165253] i_data = ed9d4204, i_mapping = ed9d4204, flags: 24200c0
[  170.172131] i_data = ed9df184, i_mapping = ed9df184, flags: 24200c0
[  170.188804] i_data = eddbce84, i_mapping = eddbce84, flags: 24200c0
[  170.196158] i_data = ec904984, i_mapping = ec904984, flags: 24200c0
[  170.205133] i_data = ec906784, i_mapping = ec906784, flags: 24200c0
[  170.211406] Unable to handle kernel NULL pointer dereference at
virtual address 00000000
[  170.219490] pgd = ee7f0000
[  170.222197] [00000000] *pgd=7b85b835
[  170.225784] Internal error: Oops: 17 [#1] SMP ARM
[  170.230477] Modules linked in:
[  170.233537] CPU: 2 PID: 1 Comm: systemd Not tainted
4.4.0-next-20160114-00005-g6f86169c2250-dirty #3525
[  170.242910] Hardware name: Rockchip (Device Tree)
[  170.247604] task: ee078000 ti: ee062000 task.ti: ee062000
[  170.252996] PC is at strlen+0x0/0x2c
[  170.256565] LR is at readlink_copy+0x24/0x94
[  170.260826] pc : [<c049b138>]    lr : [<c0321160>]    psr: 00000013
[  170.260826] sp : ee063f38  ip : 00000000  fp : ec9066b0
[  170.272281] r10: 001a49f8  r9 : 00000063  r8 : ee063f74
[  170.277494] r7 : 001a4968  r6 : 001a49f8  r5 : 00000000  r4 : 00000063
[  170.284008] r3 : 0000012c  r2 : 00000000  r1 : 00000063  r0 : 00000000
[  170.290522] Flags: nzcv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment none
[  170.297642] Control: 10c5387d  Table: 2e7f006a  DAC: 00000051
[  170.303375] Process systemd (pid: 1, stack limit = 0xee062220)
[  170.309196] Stack: (0xee063f38 to 0xee064000)
[  170.313542] 3f20:
    001a49f8 00000063
[  170.321705] 3f40: ffffff9c 001a4968 ee063f74 c0321208 c0321250
eff6b920 ffffffea ffffffea
[  170.329868] 3f60: 00004000 c031c234 00000000 00000000 00000025
00000000 ee102b50 ec874990
[  170.338030] 3f80: 5697c41b 001a49f8 00000064 00000063 0000014c
c0210e84 ee062000 00000000
[  170.346193] 3fa0: 001a4968 c0210cc0 001a49f8 00000064 ffffff9c
001a4968 001a49f8 00000063
[  170.354355] 3fc0: 001a49f8 00000064 00000063 0000014c bec450b8
001c1094 001d0933 001a4968
[  170.362518] 3fe0: 0000014c bec45094 b6f2af7b b6eb88e6 20000030
ffffff9c ffffffff ffeffffe
[  170.370683] [<c049b138>] (strlen) from [<c0321160>] (readlink_copy+0x24/0x94)
[  170.377806] [<c0321160>] (readlink_copy) from [<c0321208>]
(generic_readlink+0x38/0x80)
[  170.385796] [<c0321208>] (generic_readlink) from [<c031c234>]
(SyS_readlinkat+0x98/0xe0)
[  170.393874] [<c031c234>] (SyS_readlinkat) from [<c0210cc0>]
(ret_fast_syscall+0x0/0x3c)
[  170.401863] Code: e7d23003 e3130020 1afffffb e12fff1e (e5d02000)
[  170.407965] ---[ end trace 87f95166dedbabb0 ]---

Full log at https://lava.collabora.co.uk/scheduler/job/127626/log_file

Regards,

Tomeu

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 15:58         ` Tomeu Vizoso
@ 2016-01-14 16:23           ` Al Viro
  2016-01-14 16:57             ` Tomeu Vizoso
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2016-01-14 16:23 UTC (permalink / raw)
  To: Tomeu Vizoso; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 04:58:48PM +0100, Tomeu Vizoso wrote:
> > Could you add
> >         printk(KERN_ERR "i_data = %p, i_mapping = %p, flags: %lx\n",
> >                 &inode->i_data,
> >                 inode->i_mapping,
> >                 (unsigned long)inode->i_data.flags);
> > right before the return from nfs_get_link() and see what it prints?
> 
> Here it is:
> 
> [  170.136956] i_data = ed9c1b04, i_mapping = ed9c1b04, flags: 24200c0
> [  170.144567] i_data = ed9de784, i_mapping = ed9de784, flags: 24200c0
> [  170.151457] i_data = ed9dec84, i_mapping = ed9dec84, flags: 24200c0
> [  170.158358] i_data = ed9c3b84, i_mapping = ed9c3b84, flags: 24200c0
> [  170.165253] i_data = ed9d4204, i_mapping = ed9d4204, flags: 24200c0
> [  170.172131] i_data = ed9df184, i_mapping = ed9df184, flags: 24200c0
> [  170.188804] i_data = eddbce84, i_mapping = eddbce84, flags: 24200c0
> [  170.196158] i_data = ec904984, i_mapping = ec904984, flags: 24200c0
> [  170.205133] i_data = ec906784, i_mapping = ec906784, flags: 24200c0

Aha.  So ->i_data vs. ->i_mapping is irrelevant (as it ought to be here)
and inode_nohighmem() should've acted on the address_space we are hitting
here.  What do we have in flags...  ___GFP_IO | ___GFP_FS | ___GFP_HARDWALL |
___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM.  IOW, normal GFP_USER, no
__GFP_HIGHMEM in sight.

So either we have a highmem page somehow ending up in i_data before we
set the flags, or __page_cache_alloc() done by read_cache_page() returns
us a highmem page on GFP_USER | __GFP_COLD (or I'm misreading the things
completely)...

Could you slap
	printk(KERN_ERR "inode: %p, pages: %ld\n",
		inode, inode->i_data.nrpages);
before that read_cache_page() in nfs_get_link() and
	printk(KERN_ERR "page_address: %p\n", page_address(page));
right before the return?

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 16:23           ` Al Viro
@ 2016-01-14 16:57             ` Tomeu Vizoso
  2016-01-14 17:13               ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Tomeu Vizoso @ 2016-01-14 16:57 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On 14 January 2016 at 17:23, Al Viro <viro@zeniv.linux.org.uk> wrote:
> On Thu, Jan 14, 2016 at 04:58:48PM +0100, Tomeu Vizoso wrote:
>> > Could you add
>> >         printk(KERN_ERR "i_data = %p, i_mapping = %p, flags: %lx\n",
>> >                 &inode->i_data,
>> >                 inode->i_mapping,
>> >                 (unsigned long)inode->i_data.flags);
>> > right before the return from nfs_get_link() and see what it prints?
>>
>> Here it is:
>>
>> [  170.136956] i_data = ed9c1b04, i_mapping = ed9c1b04, flags: 24200c0
>> [  170.144567] i_data = ed9de784, i_mapping = ed9de784, flags: 24200c0
>> [  170.151457] i_data = ed9dec84, i_mapping = ed9dec84, flags: 24200c0
>> [  170.158358] i_data = ed9c3b84, i_mapping = ed9c3b84, flags: 24200c0
>> [  170.165253] i_data = ed9d4204, i_mapping = ed9d4204, flags: 24200c0
>> [  170.172131] i_data = ed9df184, i_mapping = ed9df184, flags: 24200c0
>> [  170.188804] i_data = eddbce84, i_mapping = eddbce84, flags: 24200c0
>> [  170.196158] i_data = ec904984, i_mapping = ec904984, flags: 24200c0
>> [  170.205133] i_data = ec906784, i_mapping = ec906784, flags: 24200c0
>
> Aha.  So ->i_data vs. ->i_mapping is irrelevant (as it ought to be here)
> and inode_nohighmem() should've acted on the address_space we are hitting
> here.  What do we have in flags...  ___GFP_IO | ___GFP_FS | ___GFP_HARDWALL |
> ___GFP_DIRECT_RECLAIM | ___GFP_KSWAPD_RECLAIM.  IOW, normal GFP_USER, no
> __GFP_HIGHMEM in sight.
>
> So either we have a highmem page somehow ending up in i_data before we
> set the flags, or __page_cache_alloc() done by read_cache_page() returns
> us a highmem page on GFP_USER | __GFP_COLD (or I'm misreading the things
> completely)...
>
> Could you slap
>         printk(KERN_ERR "inode: %p, pages: %ld\n",
>                 inode, inode->i_data.nrpages);
> before that read_cache_page() in nfs_get_link() and
>         printk(KERN_ERR "page_address: %p\n", page_address(page));
> right before the return?

Here it is:

[  170.715356] inode: ec8c30b0, pages: 1
[  170.719014] page_address:   (null)

https://lava.collabora.co.uk/scheduler/job/127698/log_file

Regards,

Tomeu

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 16:57             ` Tomeu Vizoso
@ 2016-01-14 17:13               ` Al Viro
  2016-01-14 19:15                 ` Tomeu Vizoso
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2016-01-14 17:13 UTC (permalink / raw)
  To: Tomeu Vizoso; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 05:57:42PM +0100, Tomeu Vizoso wrote:
> Here it is:
> 
> [  170.715356] inode: ec8c30b0, pages: 1
> [  170.719014] page_address:   (null)
> 
> https://lava.collabora.co.uk/scheduler/job/127698/log_file

	Lovely...  And that looks like the first time that inode hits
nfs_get_link().  Ho-hum...

	Could you add WARN_ON(inode->i_mapping.nrpages) in inode_nohighmem()
and see if that triggers?  It really shouldn't (we hit it after iget5_locked()
in nfs_fhget() has returned us a new inode, and there shouldn't be a chance
for any pages to get in there between struct inode allocation and that
point), but then neither should highmem pages be added to address_space
without __GFP_HIGHMEM in ->flags...

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 17:13               ` Al Viro
@ 2016-01-14 19:15                 ` Tomeu Vizoso
  2016-01-14 21:02                   ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Tomeu Vizoso @ 2016-01-14 19:15 UTC (permalink / raw)
  To: Al Viro; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On 14 January 2016 at 18:13, Al Viro <viro@zeniv.linux.org.uk> wrote:
> On Thu, Jan 14, 2016 at 05:57:42PM +0100, Tomeu Vizoso wrote:
>> Here it is:
>>
>> [  170.715356] inode: ec8c30b0, pages: 1
>> [  170.719014] page_address:   (null)
>>
>> https://lava.collabora.co.uk/scheduler/job/127698/log_file
>
>         Lovely...  And that looks like the first time that inode hits
> nfs_get_link().  Ho-hum...
>
>         Could you add WARN_ON(inode->i_mapping.nrpages) in inode_nohighmem()
> and see if that triggers?  It really shouldn't (we hit it after iget5_locked()

Indeed :(

https://lava.collabora.co.uk/scheduler/job/127782/log_file

diff --git a/fs/inode.c b/fs/inode.c
index 1e6dd388ba7f..b957744376e8 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2031,6 +2031,7 @@ EXPORT_SYMBOL(inode_set_flags);

 void inode_nohighmem(struct inode *inode)
 {
+       WARN_ON(inode->i_mapping->nrpages);
        mapping_set_gfp_mask(inode->i_mapping, GFP_USER);
 }
 EXPORT_SYMBOL(inode_nohighmem);

I have ran out of time for today, but tomorrow can do more tests if needed.

Regards,

Tomeu

> in nfs_fhget() has returned us a new inode, and there shouldn't be a chance
> for any pages to get in there between struct inode allocation and that
> point), but then neither should highmem pages be added to address_space
> without __GFP_HIGHMEM in ->flags...

^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 19:15                 ` Tomeu Vizoso
@ 2016-01-14 21:02                   ` Al Viro
  2016-01-14 21:40                     ` Linus Torvalds
  0 siblings, 1 reply; 48+ messages in thread
From: Al Viro @ 2016-01-14 21:02 UTC (permalink / raw)
  To: Tomeu Vizoso; +Cc: linux-kernel, Linus Torvalds, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 08:15:57PM +0100, Tomeu Vizoso wrote:
> On 14 January 2016 at 18:13, Al Viro <viro@zeniv.linux.org.uk> wrote:
> > On Thu, Jan 14, 2016 at 05:57:42PM +0100, Tomeu Vizoso wrote:
> >> Here it is:
> >>
> >> [  170.715356] inode: ec8c30b0, pages: 1
> >> [  170.719014] page_address:   (null)
> >>
> >> https://lava.collabora.co.uk/scheduler/job/127698/log_file
> >
> >         Lovely...  And that looks like the first time that inode hits
> > nfs_get_link().  Ho-hum...
> >
> >         Could you add WARN_ON(inode->i_mapping.nrpages) in inode_nohighmem()
> > and see if that triggers?  It really shouldn't (we hit it after iget5_locked()
> 
> Indeed :(
> 
> https://lava.collabora.co.uk/scheduler/job/127782/log_file

OK...  Unless I'm misreading that, we have
	* inode->i_data.flags set to GFP_USER, with no pages present in
there.
	* at some later point nfs_get_link() is called on that inode
(for the first time) and sees a page with logical offset 0 already present
in there, that page being a highmem one.

That would certainly suffice for the things to blow up...

Let's try this: in the beginning of __add_to_page_cache_locked() add
	VM_BUG_ON_PAGE(PageHighMem(page) & !(mapping->flags & __GFP_HIGHMEM),
			page);
and see if that triggers.

<pokes around>

Arrrgh.  Try this:

diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index ce5a218..8a05309 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1894,15 +1894,14 @@ int nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
 	attr.ia_mode = S_IFLNK | S_IRWXUGO;
 	attr.ia_valid = ATTR_MODE;
 
-	page = alloc_page(GFP_HIGHUSER);
+	page = alloc_page(GFP_USER);
 	if (!page)
 		return -ENOMEM;
 
-	kaddr = kmap_atomic(page);
+	kaddr = page_address(page);
 	memcpy(kaddr, symname, pathlen);
 	if (pathlen < PAGE_SIZE)
 		memset(kaddr + pathlen, 0, PAGE_SIZE - pathlen);
-	kunmap_atomic(kaddr);
 
 	trace_nfs_symlink_enter(dir, dentry);
 	error = NFS_PROTO(dir)->symlink(dir, dentry, page, pathlen, &attr);

^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 21:02                   ` Al Viro
@ 2016-01-14 21:40                     ` Linus Torvalds
  2016-01-14 22:25                       ` Al Viro
  0 siblings, 1 reply; 48+ messages in thread
From: Linus Torvalds @ 2016-01-14 21:40 UTC (permalink / raw)
  To: Al Viro; +Cc: Tomeu Vizoso, linux-kernel, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 1:02 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> Arrrgh.  Try this:

Yeah, that would do it.

Al, did you check any other filesystems do this?

Also, I'm wondering if we should perhaps revert the "don't use
highmem". Do we actually have examples of running out of kmaps? Do we
care?

                Linus

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 21:40                     ` Linus Torvalds
@ 2016-01-14 22:25                       ` Al Viro
  2016-01-14 23:33                         ` Al Viro
  2016-01-14 23:58                         ` Linus Torvalds
  0 siblings, 2 replies; 48+ messages in thread
From: Al Viro @ 2016-01-14 22:25 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Tomeu Vizoso, linux-kernel, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 01:40:32PM -0800, Linus Torvalds wrote:
> On Thu, Jan 14, 2016 at 1:02 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
> >
> > Arrrgh.  Try this:
> 
> Yeah, that would do it.
> 
> Al, did you check any other filesystems do this?

There's one more turd like that - shmem should've done inode_nohighmem()
a bit earlier in shmem_symlink().  The rest is OK.

> Also, I'm wondering if we should perhaps revert the "don't use
> highmem". Do we actually have examples of running out of kmaps? Do we
> care?

For one thing, we'll lose RCU ->get_link() for those.  For another... yes,
it was a nasty bug (I missed the possibility that filesystem might seed
the page cache on ->symlink() directly and use a highmem page - mea culpa),
but we can easily catch it at runtime.  We really shouldn't put highmem
pages into address_space without __GFP_HIGHMEM, and catching those in
__add_to_page_cache_locked() isn't costly.

Anyway, mm/shmem.c bit follows.  With that + NFS one we ought to be OK
wrt that class of bogosities.  I'll write the bits for
Documentation/filesystems/porting (basically, "if you preseed the pagecache
at ->symlink() time, don't put highmem pages there; page_symlink() will
take care of that, provided that inode_nohighmem() is called first") and
push the combined patch to #for-linus.

diff --git a/mm/shmem.c b/mm/shmem.c
index 5813b7f..642471b 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2469,6 +2469,7 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 		inode->i_op = &shmem_short_symlink_operations;
 		inode->i_link = info->symlink;
 	} else {
+		inode_nohighmem(inode);
 		error = shmem_getpage(inode, 0, &page, SGP_WRITE, NULL);
 		if (error) {
 			iput(inode);
@@ -2476,7 +2477,6 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 		}
 		inode->i_mapping->a_ops = &shmem_aops;
 		inode->i_op = &shmem_symlink_inode_operations;
-		inode_nohighmem(inode);
 		memcpy(page_address(page), symname, len);
 		SetPageUptodate(page);
 		set_page_dirty(page);

^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 22:25                       ` Al Viro
@ 2016-01-14 23:33                         ` Al Viro
  2016-01-14 23:58                         ` Linus Torvalds
  1 sibling, 0 replies; 48+ messages in thread
From: Al Viro @ 2016-01-14 23:33 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Tomeu Vizoso, linux-kernel, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 10:25:44PM +0000, Al Viro wrote:

> Anyway, mm/shmem.c bit follows.  With that + NFS one we ought to be OK
> wrt that class of bogosities.  I'll write the bits for
> Documentation/filesystems/porting (basically, "if you preseed the pagecache
> at ->symlink() time, don't put highmem pages there; page_symlink() will
> take care of that, provided that inode_nohighmem() is called first") and
> push the combined patch to #for-linus.

Done and pushed.  Please, pull from
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git

Shortlog:
Al Viro (1):
      Make sure that highmem pages are not added to symlink page cache

Diffstat:
 Documentation/filesystems/porting | 6 +++++-
 fs/nfs/dir.c                      | 5 ++---
 mm/shmem.c                        | 2 +-
 3 files changed, 8 insertions(+), 5 deletions(-)

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 22:25                       ` Al Viro
  2016-01-14 23:33                         ` Al Viro
@ 2016-01-14 23:58                         ` Linus Torvalds
  2016-01-15  0:05                           ` Al Viro
  1 sibling, 1 reply; 48+ messages in thread
From: Linus Torvalds @ 2016-01-14 23:58 UTC (permalink / raw)
  To: Al Viro; +Cc: Tomeu Vizoso, linux-kernel, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 2:25 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> For one thing, we'll lose RCU ->get_link() for those.

Why couldn't we just do that in the RCU walker? kmap should be fine..

That said, as long as you think it's ok now, I guess I don't care.
Having some sanity testing in __add_to_page_cache_locked might be a
good safety net.

             Linus

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem
  2016-01-14 23:58                         ` Linus Torvalds
@ 2016-01-15  0:05                           ` Al Viro
  0 siblings, 0 replies; 48+ messages in thread
From: Al Viro @ 2016-01-15  0:05 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Tomeu Vizoso, linux-kernel, Neil Brown, linux-fsdevel

On Thu, Jan 14, 2016 at 03:58:22PM -0800, Linus Torvalds wrote:
> On Thu, Jan 14, 2016 at 2:25 PM, Al Viro <viro@zeniv.linux.org.uk> wrote:
> >
> > For one thing, we'll lose RCU ->get_link() for those.
> 
> Why couldn't we just do that in the RCU walker? kmap should be fine..

In map_new_virtual():
                        __set_current_state(TASK_UNINTERRUPTIBLE);
                        add_wait_queue(pkmap_map_wait, &wait);
                        unlock_kmap();
                        schedule();
                        remove_wait_queue(pkmap_map_wait, &wait);
                        lock_kmap();
IOW, not in RCU mode ;-/

> That said, as long as you think it's ok now, I guess I don't care.
> Having some sanity testing in __add_to_page_cache_locked might be a
> good safety net.

That's better as a separate commit, IMO.  The thing I'm not sure about is
whether we want a BUG() in there - VM_WARN_ON(), perhaps?  OTOH, we do
have VM_BUG_ON_PAGE() in the same place already...

^ permalink raw reply	[flat|nested] 48+ messages in thread

end of thread, other threads:[~2016-01-15  0:05 UTC | newest]

Thread overview: 48+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-17 22:57 [PATCHSET] ->follow_link() without dropping from RCU mode Al Viro
2015-11-17 23:00 ` [PATCH 01/10] switch befs long symlinks to page_symlink_operations Al Viro
2015-11-17 23:00 ` [PATCH 02/10] logfs: don't duplicate page_symlink_inode_operations Al Viro
2015-11-17 23:00 ` [PATCH 03/10] udf: " Al Viro
2015-11-17 23:00 ` [PATCH 04/10] ufs: get rid of ->setattr() for symlinks Al Viro
2015-11-17 23:00 ` [PATCH 05/10] namei: page_getlink() and page_follow_link_light() are the same thing Al Viro
2015-11-17 23:00 ` [PATCH 06/10] [vfs] don't put symlink bodies in pagecache into highmem Al Viro
2015-11-19 23:02   ` Dave Chinner
2015-11-17 23:00 ` [PATCH 07/10] [vfs] replace ->follow_link() with new method that could stay in RCU mode Al Viro
2015-11-17 23:00 ` [PATCH 08/10] teach page_get_link() to work " Al Viro
2015-11-17 23:00 ` [PATCH 09/10] teach shmem_get_link() " Al Viro
2015-11-17 23:00 ` [PATCH 10/10] teach proc_self_get_link()/proc_thread_self_get_link() " Al Viro
2015-12-09  5:32 ` [PATCHSET v2] ->follow_link() without dropping from " Al Viro
2015-12-09  5:34   ` [PATCH v2 01/11] switch befs long symlinks to page_symlink_operations Al Viro
2015-12-09  5:34   ` [PATCH v2 02/11] logfs: don't duplicate page_symlink_inode_operations Al Viro
2015-12-09  5:34   ` [PATCH v2 03/11] udf: " Al Viro
2015-12-09  5:34   ` [PATCH v2 04/11] ufs: get rid of ->setattr() for symlinks Al Viro
2015-12-09  5:34   ` [PATCH v2 05/11] namei: page_getlink() and page_follow_link_light() are the same thing Al Viro
2015-12-09  5:34   ` [PATCH v2 06/11] don't put symlink bodies in pagecache into highmem Al Viro
2016-01-14 13:22     ` Tomeu Vizoso
2016-01-14 15:25       ` Al Viro
2016-01-14 15:58         ` Tomeu Vizoso
2016-01-14 16:23           ` Al Viro
2016-01-14 16:57             ` Tomeu Vizoso
2016-01-14 17:13               ` Al Viro
2016-01-14 19:15                 ` Tomeu Vizoso
2016-01-14 21:02                   ` Al Viro
2016-01-14 21:40                     ` Linus Torvalds
2016-01-14 22:25                       ` Al Viro
2016-01-14 23:33                         ` Al Viro
2016-01-14 23:58                         ` Linus Torvalds
2016-01-15  0:05                           ` Al Viro
2015-12-09  5:34   ` [PATCH v2 07/11] replace ->follow_link() with new method that could stay in RCU mode Al Viro
2015-12-09  5:34   ` [PATCH v2 08/11] teach page_get_link() to work " Al Viro
2015-12-09  5:34   ` [PATCH v2 09/11] teach shmem_get_link() " Al Viro
2015-12-09  5:34   ` [PATCH v2 10/11] teach proc_self_get_link()/proc_thread_self_get_link() " Al Viro
2015-12-09  5:34   ` [PATCH v2 11/11] teach nfs_get_link() " Al Viro
2015-12-09 17:24   ` [PATCHSET v2] ->follow_link() without dropping from " Linus Torvalds
2015-12-09 18:23     ` Al Viro
2015-12-10  0:10       ` Al Viro
2015-12-10  2:40         ` Al Viro
2015-12-11  1:54           ` Al Viro
2015-12-11  7:49             ` Rasmus Villemoes
2015-12-11 23:16               ` Al Viro
2015-12-12  2:00                 ` Al Viro
2015-12-13 18:43                   ` Rasmus Villemoes
2015-12-13  3:47             ` Al Viro
2015-12-09 21:57   ` NeilBrown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.