make ext2_get_page() and friends work without external serialization
Right now ext2_get_page() (and its analogues in a bunch of other filesystems)
relies upon the directory being locked - the way it sets and tests Checked and
Error bits would be racy without that. Switch to a slightly different scheme,
_not_ setting Checked in case of failure. That way the logics becomes
if Checked => OK
else if Error => fail
else if !validate => fail
else => OK
with validation setting Checked or Error on success and failure resp. and
returning which one had happened. Equivalent to the current logics, but unlike
the current logics not sensitive to the order of set_bit, test_bit getting
reordered by CPU, etc.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 5fda2bc..cdf8fbb 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -128,7 +128,7 @@
/*
* check that a directory page is valid
*/
-static inline void afs_dir_check_page(struct inode *dir, struct page *page)
+static inline bool afs_dir_check_page(struct inode *dir, struct page *page)
{
struct afs_dir_page *dbuf;
loff_t latter;
@@ -168,11 +168,11 @@
}
SetPageChecked(page);
- return;
+ return true;
error:
- SetPageChecked(page);
SetPageError(page);
+ return false;
}
/*
@@ -196,10 +196,10 @@
page = read_cache_page(dir->i_mapping, index, afs_page_filler, key);
if (!IS_ERR(page)) {
kmap(page);
- if (!PageChecked(page))
- afs_dir_check_page(dir, page);
- if (PageError(page))
- goto fail;
+ if (unlikely(!PageChecked(page))) {
+ if (PageError(page) || !afs_dir_check_page(dir, page))
+ goto fail;
+ }
}
return page;
diff --git a/fs/exofs/dir.c b/fs/exofs/dir.c
index 547b93c..a7661aa 100644
--- a/fs/exofs/dir.c
+++ b/fs/exofs/dir.c
@@ -79,7 +79,7 @@
return err;
}
-static void exofs_check_page(struct page *page)
+static bool exofs_check_page(struct page *page)
{
struct inode *dir = page->mapping->host;
unsigned chunk_size = exofs_chunk_size(dir);
@@ -114,7 +114,7 @@
goto Eend;
out:
SetPageChecked(page);
- return;
+ return true;
Ebadsize:
EXOFS_ERR("ERROR [exofs_check_page]: "
@@ -150,8 +150,8 @@
dir->i_ino, (page->index<<PAGE_SHIFT)+offs,
_LLU(le64_to_cpu(p->inode_no)));
fail:
- SetPageChecked(page);
SetPageError(page);
+ return false;
}
static struct page *exofs_get_page(struct inode *dir, unsigned long n)
@@ -161,10 +161,10 @@
if (!IS_ERR(page)) {
kmap(page);
- if (!PageChecked(page))
- exofs_check_page(page);
- if (PageError(page))
- goto fail;
+ if (unlikely(!PageChecked(page))) {
+ if (PageError(page) || !exofs_check_page(page))
+ goto fail;
+ }
}
return page;
diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c
index 7ff6fcf..f0f4363 100644
--- a/fs/ext2/dir.c
+++ b/fs/ext2/dir.c
@@ -110,7 +110,7 @@
return err;
}
-static void ext2_check_page(struct page *page, int quiet)
+static bool ext2_check_page(struct page *page, int quiet)
{
struct inode *dir = page->mapping->host;
struct super_block *sb = dir->i_sb;
@@ -148,7 +148,7 @@
goto Eend;
out:
SetPageChecked(page);
- return;
+ return true;
/* Too bad, we had an error */
@@ -190,8 +190,8 @@
(unsigned long) le32_to_cpu(p->inode));
}
fail:
- SetPageChecked(page);
SetPageError(page);
+ return false;
}
static struct page * ext2_get_page(struct inode *dir, unsigned long n,
@@ -201,10 +201,10 @@
struct page *page = read_mapping_page(mapping, n, NULL);
if (!IS_ERR(page)) {
kmap(page);
- if (!PageChecked(page))
- ext2_check_page(page, quiet);
- if (PageError(page))
- goto fail;
+ if (unlikely(!PageChecked(page))) {
+ if (PageError(page) || !ext2_check_page(page, quiet))
+ goto fail;
+ }
}
return page;
diff --git a/fs/nilfs2/dir.c b/fs/nilfs2/dir.c
index e08f064..2eaed925 100644
--- a/fs/nilfs2/dir.c
+++ b/fs/nilfs2/dir.c
@@ -102,7 +102,7 @@
unlock_page(page);
}
-static void nilfs_check_page(struct page *page)
+static bool nilfs_check_page(struct page *page)
{
struct inode *dir = page->mapping->host;
struct super_block *sb = dir->i_sb;
@@ -137,7 +137,7 @@
goto Eend;
out:
SetPageChecked(page);
- return;
+ return true;
/* Too bad, we had an error */
@@ -173,8 +173,8 @@
dir->i_ino, (page->index<<PAGE_SHIFT)+offs,
(unsigned long) le64_to_cpu(p->inode));
fail:
- SetPageChecked(page);
SetPageError(page);
+ return false;
}
static struct page *nilfs_get_page(struct inode *dir, unsigned long n)
@@ -184,10 +184,10 @@
if (!IS_ERR(page)) {
kmap(page);
- if (!PageChecked(page))
- nilfs_check_page(page);
- if (PageError(page))
- goto fail;
+ if (unlikely(!PageChecked(page))) {
+ if (PageError(page) || !nilfs_check_page(page))
+ goto fail;
+ }
}
return page;
diff --git a/fs/ufs/dir.c b/fs/ufs/dir.c
index 0b14572..4c07421 100644
--- a/fs/ufs/dir.c
+++ b/fs/ufs/dir.c
@@ -105,7 +105,7 @@
}
-static void ufs_check_page(struct page *page)
+static bool ufs_check_page(struct page *page)
{
struct inode *dir = page->mapping->host;
struct super_block *sb = dir->i_sb;
@@ -143,7 +143,7 @@
goto Eend;
out:
SetPageChecked(page);
- return;
+ return true;
/* Too bad, we had an error */
@@ -180,8 +180,8 @@
"offset=%lu",
dir->i_ino, (page->index<<PAGE_SHIFT)+offs);
fail:
- SetPageChecked(page);
SetPageError(page);
+ return false;
}
static struct page *ufs_get_page(struct inode *dir, unsigned long n)
@@ -190,10 +190,10 @@
struct page *page = read_mapping_page(mapping, n, NULL);
if (!IS_ERR(page)) {
kmap(page);
- if (!PageChecked(page))
- ufs_check_page(page);
- if (PageError(page))
- goto fail;
+ if (unlikely(!PageChecked(page))) {
+ if (PageError(page) || !ufs_check_page(page))
+ goto fail;
+ }
}
return page;