aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Howells <dhowells@redhat.com>2012-02-23 17:36:26 +0000
committerDavid Howells <dhowells@redhat.com>2012-02-23 17:36:26 +0000
commitcea78f4d2c26b497966593718d44582085857194 (patch)
treeaa3c6d3b5deddd03fd92afabec4c50a28e152bde
parent1b147244ad704bb8a4a14d2d2f2df2e72fdb8f88 (diff)
downloadlinux-fscache-devel.tar.gz
Noisefs: A predictable noise producing fs for testing thingsdevel
Add a configurable, predictable noise-producing filesystem for testing stuff, particularly cache coherency handling in NFS and FS-Cache. It's based somewhat on ramfs, so you mount an empty fs and populate it: mount -t noisefs none /mnt touch /mnt/{a,b} mkdir /mnt/{c,d} touch /mnt/c/{e,f} The size of files can be set, e.g.: echo hello >/mnt/a echo hello >>/mnt/a truncate -s 4096 /mnt/a However, no data is actually stored, and the filesystem is not persistent. Data can be read, but it's generated by a pattern generator, and is available up to the EOF point. The data pattern is generated in BE words of four bytes, consisting of the sum of the filesystem key, inode number, inode data version number and the word number within the file (file offset / 4). This should make it easy to check quickly whether the data is correct. ============= CONFIGURATION ============= (1) A file's i_version number can be given a timeout. This means that once an i_version is viewed (by getattr), it is only good for that many more seconds. After that it will be incremented and the timeout begun again: setfattr -n iversion_timo -v 4 /mnt/a The file's i_version number is also incremented any time there's a write or a truncation operation performed on the file. (2) A file can be made to inject an arbitrary error (EIO by default) when i_version reaches or exceeds a certain number: setfattr -n inject_error_at -v 4 /mnt/a setfattr -n error_to_inject -v 28 /mnt/a So the above would inject ENOSPC when i_version >= 4. (3) A 32-bit filesystem ID key can be set during mounting: mount -t noisefs none /mnt -o fs_key=1234 Note that when exporting this filesystem through NFS, an FSID should be set as the mount does not have real device numbers of its own. Signed-off-by: David Howells <dhowells@redhat.com>
-rw-r--r--fs/Kconfig1
-rw-r--r--fs/Makefile1
-rw-r--r--fs/noisefs/Kconfig20
-rw-r--r--fs/noisefs/Makefile7
-rw-r--r--fs/noisefs/file.c329
-rw-r--r--fs/noisefs/inode.c172
-rw-r--r--fs/noisefs/internal.h59
-rw-r--r--fs/noisefs/super.c302
-rw-r--r--mm/page-writeback.c1
9 files changed, 892 insertions, 0 deletions
diff --git a/fs/Kconfig b/fs/Kconfig
index d621f02a3f9e26..1ad9d40a61f8d4 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -173,6 +173,7 @@ config HUGETLB_PAGE
def_bool HUGETLBFS
source "fs/configfs/Kconfig"
+source "fs/noisefs/Kconfig"
endmenu
diff --git a/fs/Makefile b/fs/Makefile
index 93804d4d66e187..15106d552d7a56 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -124,3 +124,4 @@ obj-$(CONFIG_GFS2_FS) += gfs2/
obj-y += exofs/ # Multiple modules
obj-$(CONFIG_CEPH_FS) += ceph/
obj-$(CONFIG_PSTORE) += pstore/
+obj-$(CONFIG_NOISE_FS) += noisefs/
diff --git a/fs/noisefs/Kconfig b/fs/noisefs/Kconfig
new file mode 100644
index 00000000000000..bf68072bf49952
--- /dev/null
+++ b/fs/noisefs/Kconfig
@@ -0,0 +1,20 @@
+config NOISE_FS
+ tristate "Predictable noise-maker filesystem (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ help
+ A debugging filesystem that generates pages of noise in a predictable
+ pattern based on the fs key, inode number, page number and data
+ version number.
+
+ The filesystem borrows from ramfs to provide directory and special
+ file support; only regular files produce noise.
+
+ No backing storage is required as data writes merely update the data
+ version number. The files can be programmed to automatically update
+ the data version number every N reads to simulate the effect of
+ interference by another writer.
+
+ Export ops are available, so the thing can be NFS exported.
+
+ To compile this filesystem as a module, choose M here: the module
+ will be called noisefs. If unsure, say N.
diff --git a/fs/noisefs/Makefile b/fs/noisefs/Makefile
new file mode 100644
index 00000000000000..59f97465da65b3
--- /dev/null
+++ b/fs/noisefs/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the linux ramfs routines.
+#
+
+obj-$(CONFIG_NOISE_FS) += noisefs.o
+
+noisefs-objs += super.o inode.o file.o
diff --git a/fs/noisefs/file.c b/fs/noisefs/file.c
new file mode 100644
index 00000000000000..98af0b2ac7c8bf
--- /dev/null
+++ b/fs/noisefs/file.c
@@ -0,0 +1,329 @@
+/* Predictable noise generator file handler
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/aio.h>
+#include <linux/uaccess.h>
+#include "internal.h"
+
+/*
+ * Fabricate data for the reader to read
+ */
+static ssize_t noisefs_read(struct file *filp, char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ struct inode *i = filp->f_dentry->d_inode;
+ struct noisefs_inode *ni = NOISEFS_I(i);
+ ssize_t ret;
+ size_t piece;
+ loff_t pos = *ppos, eof, avail;
+ __be32 raw;
+ const void const *raw_p = &raw;
+ u32 data;
+
+ kenter("%lu,,%zu,%llu", i->i_ino, len, pos);
+
+ if (pos + len < pos)
+ len = (loff_t)-1ULL - pos;
+ if (len == 0) {
+ kleave(" = 0 [len 0]");
+ return 0;
+ }
+
+ if (ni->inject_error_at && i->i_version >= ni->inject_error_at) {
+ pr_warning("noisefs_read() injected error\n");
+ return ni->error_to_inject;
+ }
+
+ /* Determine the word covering the starting position and file size */
+ spin_lock(&i->i_lock);
+ eof = i_size_read(i);
+ data = NOISEFS_SB(i->i_sb)->fs_key;
+ data += i->i_ino + i->i_version;
+ data += pos >> 2;
+ spin_unlock(&i->i_lock);
+
+ kdebug("start with %08x eof %llx", data, eof);
+
+ if (pos >= eof) {
+ kleave(" = 0 [after eof]");
+ return 0;
+ }
+
+ /* Shrink the read to the part before the EOF */
+ avail = eof - pos;
+ if (avail < len)
+ len = avail;
+ *ppos = pos + len;
+ ret = len;
+
+ kdebug("readable %lx/%llx", len, avail);
+
+ /* handle a misaligned starting position */
+ if (pos & 3) {
+ unsigned off = pos & 3;
+
+ piece = sizeof(data) - off;
+ piece = (len > piece) ? piece : len;
+
+ kdebug("misaligned start %zu/%zu", piece, len);
+
+ raw = cpu_to_be32(data);
+ if (copy_to_user(buf, raw_p + off, piece))
+ goto fault;
+ buf += piece;
+ len -= piece;
+ data++;
+ }
+
+ /* Handle all the whole words */
+ if (len >= 4) {
+ kdebug("whole words %zu", len & ~3);
+ do {
+ raw = cpu_to_be32(data);
+ if (put_user(raw, (__be32 __user *)buf) < 0)
+ goto fault;
+ buf += 4;
+ len -= 4;
+ data++;
+ } while (len >= 4);
+ }
+
+ /* Handle trailing partial word */
+ if (len > 0) {
+ kdebug("partial trailer %zu", len);
+ raw = cpu_to_be32(data);
+ if (copy_to_user(buf, raw_p, len))
+ goto fault;
+ }
+
+ kleave(" = %zd [%llx]", ret, *ppos);
+ return ret;
+
+fault:
+ kleave(" = -EFAULT");
+ return -EFAULT;
+}
+
+/*
+ * Note a write, but don't actually store the data
+ */
+static ssize_t noisefs_write(struct file *filp, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ struct inode *i = filp->f_dentry->d_inode;
+ struct noisefs_inode *ni = NOISEFS_I(i);
+ loff_t pos = *ppos, eof;
+ time_t now;
+
+ kenter("%lu{%llx},,%zu,%llu", i->i_ino, i->i_version, len, pos);
+
+ if (pos + len < pos)
+ return -EFBIG;
+
+ if (ni->inject_error_at && i->i_version >= ni->inject_error_at) {
+ pr_warning("noisefs_write() injected error\n");
+ return ni->error_to_inject;
+ }
+
+ now = CURRENT_TIME.tv_sec;
+
+ spin_lock(&i->i_lock);
+
+ eof = i_size_read(i);
+
+ if (filp->f_flags & O_APPEND)
+ pos = eof;
+
+ if (pos + len > eof)
+ i_size_write(i, pos + len);
+ i->i_version++;
+ if (ni->iver_timeout)
+ ni->iver_expiry = now + ni->iver_timeout;
+
+ spin_unlock(&i->i_lock);
+ *ppos = pos + len;
+ return len;
+}
+
+static ssize_t noisefs_aio_write(struct kiocb *iocb,
+ const struct iovec *iov, unsigned long nr_segs,
+ loff_t pos)
+{
+ loff_t pos_copy = pos;
+ return noisefs_write(iocb->ki_filp, NULL, iocb->ki_nbytes, &pos_copy);
+}
+
+static ssize_t noisefs_splice_write(struct pipe_inode_info *pipe,
+ struct file *out,
+ loff_t *ppos, size_t len,
+ unsigned int flags)
+{
+ return noisefs_write(out, NULL, len, ppos);
+}
+
+const struct file_operations noisefs_file_operations = {
+ .read = noisefs_read,
+ .write = noisefs_write,
+ .aio_write = noisefs_aio_write,
+ .splice_write = noisefs_splice_write,
+ .llseek = generic_file_llseek,
+ .fsync = noop_fsync,
+};
+
+static int noisefs_extract_val(const void *value, size_t size,
+ unsigned *_val)
+{
+ if (size > 0 && value && *(char *)value) {
+ char buf[8];
+ if (size > sizeof(buf) - 1)
+ return -EINVAL;
+ memcpy(buf, value, size);
+ buf[size] = 0;
+ return kstrtouint(value, 10, _val);
+ }
+
+ return 0;
+}
+
+static int noisefs_setxattr(struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ struct inode *i = dentry->d_inode;
+ struct noisefs_inode *ni = NOISEFS_I(i);
+
+ kenter("%lu,%s,,,", i->i_ino, name);
+
+ if (!name || !*name)
+ return -EOPNOTSUPP;
+
+ if (strcmp(name, "iversion_timo") == 0) {
+ unsigned timeout = 0;
+
+ if (noisefs_extract_val(value, size, &timeout) < 0)
+ return -EINVAL;
+
+ /* increment i_version after first getattr on this version */
+ kdebug("set timeout %x", timeout);
+ spin_lock(&ni->lock);
+ ni->iver_timeout = timeout;
+ if (!timeout)
+ ni->iver_expiry = 0;
+ spin_unlock(&ni->lock);
+ return 0;
+ }
+
+ if (strcmp(name, "inject_error_at") == 0) {
+ unsigned when = 0;
+
+ if (noisefs_extract_val(value, size, &when) < 0)
+ return -EINVAL;
+ ni->inject_error_at = when;
+ return 0;
+ }
+
+ if (strcmp(name, "error_to_inject") == 0) {
+ unsigned err = 0;
+
+ if (noisefs_extract_val(value, size, &err) < 0 ||
+ err <= 0 || err >= 512)
+ return -EINVAL;
+ ni->error_to_inject = -err;
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int noisefs_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+ struct inode *i = dentry->d_inode;
+ struct noisefs_inode *ni = NOISEFS_I(i);
+ time_t now;
+ int error;
+
+ kenter("%lu{%llx},{%x}", i->i_ino, i->i_version, iattr->ia_valid);
+
+ error = inode_change_ok(i, iattr);
+ if (error)
+ return error;
+
+ if (ni->inject_error_at && i->i_version >= ni->inject_error_at) {
+ pr_warning("noisefs_setattr() preinjected error\n");
+ return ni->error_to_inject;
+ }
+
+ if (iattr->ia_valid & ATTR_SIZE) {
+ now = CURRENT_TIME.tv_sec;
+ spin_lock(&i->i_lock);
+ i_size_write(i, iattr->ia_size);
+ i->i_version++;
+ if (ni->iver_timeout)
+ ni->iver_expiry = now + ni->iver_timeout;
+ spin_unlock(&i->i_lock);
+ }
+
+ if (ni->inject_error_at && i->i_version >= ni->inject_error_at) {
+ pr_warning("noisefs_read() injected error\n");
+ return ni->error_to_inject;
+ }
+
+ setattr_copy(i, iattr);
+ mark_inode_dirty(i);
+ return 0;
+}
+
+static int noisefs_getattr(struct vfsmount *mnt, struct dentry *dentry,
+ struct kstat *stat)
+{
+ struct inode *i = dentry->d_inode;
+ struct noisefs_inode *ni = NOISEFS_I(i);
+ time_t now;
+
+ if (ni->iver_timeout) {
+ now = CURRENT_TIME.tv_sec;
+ spin_lock(&ni->lock);
+ if (ni->iver_timeout) {
+ if (!ni->iver_expiry || now > ni->iver_expiry) {
+ if (ni->iver_expiry) {
+ kdebug("i_version expired");
+ spin_lock(&i->i_lock);
+ i->i_version++;
+ spin_unlock(&i->i_lock);
+ }
+ ni->iver_expiry = now + ni->iver_timeout;
+ }
+ }
+ spin_unlock(&ni->lock);
+ }
+
+ if (ni->inject_error_at && i->i_version >= ni->inject_error_at) {
+ pr_warning("noisefs_getattr() injected error\n");
+ return ni->error_to_inject;
+ }
+
+ return simple_getattr(mnt, dentry, stat);
+}
+
+const struct inode_operations noisefs_file_inode_operations = {
+ .getattr = noisefs_getattr,
+ .setattr = noisefs_setattr,
+ .setxattr = noisefs_setxattr,
+};
+
+const struct address_space_operations noisefs_aops = {
+ .readpage = simple_readpage,
+ .write_begin = simple_write_begin,
+ .write_end = simple_write_end,
+ .set_page_dirty = __set_page_dirty_no_writeback,
+};
diff --git a/fs/noisefs/inode.c b/fs/noisefs/inode.c
new file mode 100644
index 00000000000000..62fc2aae796f56
--- /dev/null
+++ b/fs/noisefs/inode.c
@@ -0,0 +1,172 @@
+/* Predictable noise generator filesystem
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/fs.h>
+#include <linux/time.h>
+#include <linux/pagemap.h>
+#include <linux/backing-dev.h>
+#include <linux/slab.h>
+#include "internal.h"
+
+static const struct inode_operations noisefs_dir_inode_operations;
+
+static struct backing_dev_info noisefs_backing_dev_info = {
+ .name = "noisefs",
+ .ra_pages = 0, /* No readahead */
+ .capabilities = BDI_CAP_NO_ACCT_AND_WRITEBACK,
+};
+
+void noisefs_i_init_once(void *_inode)
+{
+ struct noisefs_inode *ni = _inode;
+
+ memset(ni, 0, sizeof(*ni));
+ inode_init_once(&ni->vfs_inode);
+ spin_lock_init(&ni->lock);
+}
+
+/*
+ * Get an inode and initialise it
+ */
+struct inode *noisefs_iget(struct super_block *sb, const struct inode *dir,
+ int mode, dev_t dev)
+{
+ struct noisefs_super *s = NOISEFS_SB(sb);
+ struct noisefs_inode *ni;
+ struct inode *inode;
+ ino_t ino;
+
+ kenter(",%lx,%o,%x", dir ? dir->i_ino : 0, mode, dev);
+
+ spin_lock(&s->ino_lock);
+ if (S_ISREG(mode)) {
+ if (s->regular_ino == INT_MAX)
+ goto out_of_inos;
+ ino = ++s->regular_ino;
+ } else {
+ if (s->special_ino == UINT_MAX)
+ goto out_of_inos;
+ ino = ++s->special_ino;
+ }
+ spin_unlock(&s->ino_lock);
+
+ inode = iget_locked(sb, ino);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+ if (!(inode->i_state & I_NEW)) {
+ kenter(" = {%lx} [extant]", inode->i_ino);
+ return inode;
+ }
+
+ inode_init_owner(inode, dir, mode);
+ inode->i_mapping->a_ops = &noisefs_aops;
+ inode->i_mapping->backing_dev_info = &noisefs_backing_dev_info;
+ mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
+ mapping_set_unevictable(inode->i_mapping);
+ inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+
+ switch (mode & S_IFMT) {
+ default:
+ init_special_inode(inode, mode, dev);
+ break;
+ case S_IFREG:
+ inode->i_op = &noisefs_file_inode_operations;
+ inode->i_fop = &noisefs_file_operations;
+ break;
+ case S_IFDIR:
+ inode->i_op = &noisefs_dir_inode_operations;
+ inode->i_fop = &simple_dir_operations;
+
+ /* directory inodes start off with i_nlink == 2 (for "." entry) */
+ inc_nlink(inode);
+ break;
+ case S_IFLNK:
+ inode->i_op = &page_symlink_inode_operations;
+ break;
+ }
+
+ ni = NOISEFS_I(inode);
+ ni->error_to_inject = -EIO;
+
+ unlock_new_inode(inode);
+ kenter(" = {%lx} [new]", inode->i_ino);
+ return inode;
+
+out_of_inos:
+ spin_unlock(&s->ino_lock);
+ pr_warning("noisefs: Ran out of inode numbers\n");
+ return NULL;
+}
+
+/*
+ * File creation. Allocate an inode, and we're done..
+ */
+static int noisefs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
+ dev_t dev)
+{
+ struct inode *inode = noisefs_iget(dir->i_sb, dir, mode, dev);
+ int error = -ENOSPC;
+
+ if (inode) {
+ d_instantiate(dentry, inode);
+ dget(dentry); /* Extra count - pin the dentry in core */
+ dir->i_mtime = dir->i_ctime = CURRENT_TIME;
+ error = 0;
+ }
+ return error;
+}
+
+static int noisefs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+ int retval = noisefs_mknod(dir, dentry, mode | S_IFDIR, 0);
+ if (!retval)
+ inc_nlink(dir);
+ return retval;
+}
+
+static int noisefs_create(struct inode *dir, struct dentry *dentry,
+ umode_t mode, struct nameidata *nd)
+{
+ return noisefs_mknod(dir, dentry, mode | S_IFREG, 0);
+}
+
+static int noisefs_symlink(struct inode *dir, struct dentry *dentry,
+ const char *symname)
+{
+ struct inode *inode;
+ int error = -ENOSPC;
+
+ inode = noisefs_iget(dir->i_sb, dir, S_IFLNK|S_IRWXUGO, 0);
+ if (inode) {
+ int l = strlen(symname) + 1;
+ error = page_symlink(inode, symname, l);
+ if (!error) {
+ d_instantiate(dentry, inode);
+ dget(dentry);
+ dir->i_mtime = dir->i_ctime = CURRENT_TIME;
+ } else {
+ iput(inode);
+ }
+ }
+ return error;
+}
+
+static const struct inode_operations noisefs_dir_inode_operations = {
+ .create = noisefs_create,
+ .lookup = simple_lookup,
+ .link = simple_link,
+ .unlink = simple_unlink,
+ .symlink = noisefs_symlink,
+ .mkdir = noisefs_mkdir,
+ .rmdir = simple_rmdir,
+ .mknod = noisefs_mknod,
+ .rename = simple_rename,
+};
diff --git a/fs/noisefs/internal.h b/fs/noisefs/internal.h
new file mode 100644
index 00000000000000..37a93ece5409e9
--- /dev/null
+++ b/fs/noisefs/internal.h
@@ -0,0 +1,59 @@
+/* Predictable noise generator filesystem internal defs
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+struct noisefs_super {
+ spinlock_t ino_lock; /* Lock for *_ino */
+ unsigned long regular_ino; /* Last regular inode number allocated */
+ unsigned long special_ino; /* Last special inode number allocated */
+ unsigned fs_key; /* Filesystem noise key */
+};
+
+static inline struct noisefs_super *NOISEFS_SB(struct super_block *sb)
+{
+ return (struct noisefs_super *)sb->s_fs_info;
+}
+
+struct noisefs_inode {
+ struct inode vfs_inode;
+ spinlock_t lock;
+ unsigned iver_timeout;
+ time_t iver_expiry;
+ unsigned inject_error_at;
+ int error_to_inject;
+};
+
+static inline struct noisefs_inode *NOISEFS_I(struct inode *inode)
+{
+ return container_of(inode, struct noisefs_inode, vfs_inode);
+}
+
+/*
+ * file.c
+ */
+extern const struct address_space_operations noisefs_aops;
+extern const struct file_operations noisefs_file_operations;
+extern const struct inode_operations noisefs_file_inode_operations;
+
+/*
+ * inode.c
+ */
+extern void noisefs_i_init_once(void *);
+extern struct inode *noisefs_iget(struct super_block *,
+ const struct inode *, int, dev_t);
+
+/*
+ * Debugging
+ */
+#define dbgprintk(FMT, ...) \
+ no_printk("[%-6.6s] "FMT"\n", current->comm, ##__VA_ARGS__)
+#define kenter(FMT, ...) dbgprintk("==> %s("FMT")", __func__ , ##__VA_ARGS__)
+#define kleave(FMT, ...) dbgprintk("<== %s()"FMT"", __func__ , ##__VA_ARGS__)
+#define kdebug(FMT, ...) dbgprintk(" "FMT, ##__VA_ARGS__)
diff --git a/fs/noisefs/super.c b/fs/noisefs/super.c
new file mode 100644
index 00000000000000..6118fad65819b2
--- /dev/null
+++ b/fs/noisefs/super.c
@@ -0,0 +1,302 @@
+/* Predictable noise generator filesystem
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/exportfs.h>
+#include <linux/pagemap.h>
+#include <linux/parser.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include "internal.h"
+
+MODULE_LICENSE("GPL");
+
+static struct kmem_cache *noisefs_inode_cache;
+
+static struct inode *noisefs_alloc_inode(struct super_block *sb)
+{
+ struct noisefs_inode *ni;
+
+ ni = kmem_cache_alloc(noisefs_inode_cache, GFP_KERNEL);
+ if (!ni)
+ return NULL;
+ return &ni->vfs_inode;
+}
+
+static void noisefs_i_callback(struct rcu_head *head)
+{
+ struct inode *inode = container_of(head, struct inode, i_rcu);
+ struct noisefs_inode *ni = NOISEFS_I(inode);
+
+ INIT_LIST_HEAD(&inode->i_dentry);
+ kmem_cache_free(noisefs_inode_cache, ni);
+}
+
+static void noisefs_destroy_inode(struct inode *inode)
+{
+ call_rcu(&inode->i_rcu, noisefs_i_callback);
+}
+
+static const struct super_operations noisefs_super_ops = {
+ .statfs = simple_statfs,
+ .alloc_inode = noisefs_alloc_inode,
+ .drop_inode = generic_delete_inode,
+ .destroy_inode = noisefs_destroy_inode,
+ .show_options = generic_show_options,
+};
+
+static int noisefs_encode_fh(struct dentry *dentry, __u32 *fh,
+ int *max_len, int connectable)
+{
+ struct inode *inode = dentry->d_inode;
+ int len = *max_len;
+ int type = FILEID_INO32_GEN;
+
+ kenter("%lx,,{%d},%d", inode->i_ino, len, connectable);
+
+ if (connectable && (len < 4)) {
+ *max_len = 4;
+ return 255;
+ } else if (len < 2) {
+ *max_len = 2;
+ return 255;
+ }
+
+ len = 2;
+ fh[0] = inode->i_ino;
+ fh[1] = inode->i_generation;
+ if (connectable && !S_ISDIR(inode->i_mode)) {
+ struct inode *parent;
+
+ spin_lock(&dentry->d_lock);
+ parent = dentry->d_parent->d_inode;
+ fh[2] = parent->i_ino;
+ fh[3] = parent->i_generation;
+ spin_unlock(&dentry->d_lock);
+ len = 4;
+ type = FILEID_INO32_GEN_PARENT;
+ }
+ *max_len = len;
+ kleave(" = %d [%d]", type, len);
+ return type;
+}
+
+static struct inode *noisefs_nfs_get_inode(struct super_block *sb,
+ u64 ino, u32 generation)
+{
+ struct inode *inode;
+
+ kenter(",%llx,%x", ino, generation);
+
+ /* If the inode exists, it will be in core already. */
+ inode = ilookup(sb, ino);
+ if (!inode) {
+ kleave(" = -ESTALE [no ino]");
+ return ERR_PTR(-ESTALE);
+ }
+ if (generation && inode->i_generation != generation) {
+ kleave(" = -ESTALE [%x!=%x]", inode->i_generation, generation);
+ iput(inode);
+ return ERR_PTR(-ESTALE);
+ }
+ return inode;
+}
+
+static struct dentry *noisefs_fh_to_dentry(struct super_block *sb,
+ struct fid *fid,
+ int fh_len, int fh_type)
+{
+ struct dentry *dentry;
+
+ kenter(",,%d,%d", fh_len, fh_type);
+ dentry = generic_fh_to_dentry(sb, fid, fh_len, fh_type,
+ noisefs_nfs_get_inode);
+ kleave(" = %p", dentry);
+ return dentry;
+}
+
+static struct dentry *noisefs_fh_to_parent(struct super_block *sb,
+ struct fid *fid,
+ int fh_len, int fh_type)
+{
+ struct dentry *dentry;
+
+ kenter(",,%d,%d", fh_len, fh_type);
+ dentry = generic_fh_to_parent(sb, fid, fh_len, fh_type,
+ noisefs_nfs_get_inode);
+ kleave(" = %p", dentry);
+ return dentry;
+}
+
+static struct dentry *noisefs_get_parent(struct dentry *child)
+{
+ struct dentry *dentry;
+
+ kenter("%lx", child->d_inode ? child->d_inode->i_ino : 0);
+
+ /* should we check !IS_ROOT(child)? */
+ dentry = dget_parent(child);
+ kleave(" = %lx", dentry->d_inode->i_ino);
+ return dentry;
+}
+
+static const struct export_operations noisefs_export_ops = {
+ .encode_fh = noisefs_encode_fh,
+ .fh_to_dentry = noisefs_fh_to_dentry,
+ .fh_to_parent = noisefs_fh_to_parent,
+ .get_parent = noisefs_get_parent,
+};
+
+enum {
+ Opt_fs_key,
+ Opt_err
+};
+
+static const match_table_t noisefs_mountopt_tokens = {
+ { Opt_fs_key, "fs_key=%d" },
+ { Opt_err, NULL }
+};
+
+static int noisefs_parse_options(char *data, struct noisefs_super *s)
+{
+ substring_t args[MAX_OPT_ARGS];
+ int option;
+ int token;
+ char *p;
+
+ while ((p = strsep(&data, ",")) != NULL) {
+ if (!*p)
+ continue;
+
+ token = match_token(p, noisefs_mountopt_tokens, args);
+ switch (token) {
+ case Opt_fs_key:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ break;
+ default:
+ pr_err("noisefs: Unknown mount option\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+int noisefs_fill_super(struct super_block *sb, void *data, int silent)
+{
+ struct noisefs_super *s;
+ struct inode *inode;
+ struct dentry *root;
+ int err;
+
+ save_mount_options(sb, data);
+
+ err = -ENOMEM;
+ s = kzalloc(sizeof(struct noisefs_super), GFP_KERNEL);
+ if (!s)
+ goto fail;
+
+ spin_lock_init(&s->ino_lock);
+ s->special_ino = 0x80000000U;
+
+ sb->s_fs_info = s;
+
+ err = noisefs_parse_options(data, s);
+ if (err)
+ goto fail_have_super;
+
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+ sb->s_blocksize = PAGE_CACHE_SIZE;
+ sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+ sb->s_magic = 0x4E6F6973U;
+ sb->s_op = &noisefs_super_ops;
+ sb->s_export_op = &noisefs_export_ops;
+ sb->s_time_gran = 1;
+ sb->s_flags |= MS_I_VERSION;
+
+ err = -ENOMEM;
+ inode = noisefs_iget(sb, NULL,
+ S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR, 0);
+ if (!inode)
+ goto fail_have_super;
+
+ root = d_alloc_root(inode);
+ if (!root)
+ goto fail_have_inode;
+ sb->s_root = root;
+ return 0;
+
+fail_have_inode:
+ iput(inode);
+fail_have_super:
+ kfree(sb->s_fs_info);
+ sb->s_fs_info = NULL;
+fail:
+ return err;
+}
+
+struct dentry *noisefs_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name, void *data)
+{
+ return mount_nodev(fs_type, flags, data, noisefs_fill_super);
+}
+
+static void noisefs_kill_sb(struct super_block *sb)
+{
+ kfree(sb->s_fs_info);
+ kill_litter_super(sb);
+}
+
+static struct file_system_type noisefs_fs_type = {
+ .name = "noisefs",
+ .mount = noisefs_mount,
+ .kill_sb = noisefs_kill_sb,
+};
+
+static int __init init_noisefs_fs(void)
+{
+ int ret;
+
+ noisefs_inode_cache = kmem_cache_create("noisefs_inode_cache",
+ sizeof(struct noisefs_inode),
+ 0,
+ SLAB_HWCACHE_ALIGN,
+ noisefs_i_init_once);
+ if (!noisefs_inode_cache) {
+ pr_notice("noisefs: Failed to allocate inode cache\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = register_filesystem(&noisefs_fs_type);
+ if (ret < 0) {
+ pr_notice("noisefs: Failed to register filesystem\n");
+ goto error_fsreg;
+ }
+ return 0;
+
+error_fsreg:
+ kmem_cache_destroy(noisefs_inode_cache);
+error:
+ return ret;
+}
+
+static void __exit exit_noisefs_fs(void)
+{
+ unregister_filesystem(&noisefs_fs_type);
+ kmem_cache_destroy(noisefs_inode_cache);
+}
+
+module_init(init_noisefs_fs)
+module_exit(exit_noisefs_fs)
diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 363ba7082ef59e..4dc4ec1c09e3b5 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -1925,6 +1925,7 @@ int __set_page_dirty_no_writeback(struct page *page)
return !TestSetPageDirty(page);
return 0;
}
+EXPORT_SYMBOL(__set_page_dirty_no_writeback);
/*
* Helper function for set_page_dirty family.