Hi.
I'm pleased to announce POHMEL high performance network filesystem.
POHMELFS stands for Parallel Optimized Host Message Exchange Layered File System.
Development status can be tracked in filesystem section [1].
This is a high performance network filesystem with local coherent cache of data
and metadata. Its main goal is distributed parallel processing of data.
This release brings following features:
* Full transaction support for all operations (object creation/removal,
data reading and writing). Data reading transactions are not optimal yet
and will be improved in the next release (although fast).
* Data and metadata cache coherency support. More details on how this is
implemented one can find in appropriate section [5].
* Transaction timeout based resending. If given transaction did not
receive reply after specified timeout, transaction will be resent
(possibly to different server).
* Switched writepage path to ->sendpage() which improved performance and
robustness of the writing.
* Preliminary support for parallel data processing. Code to write data
to multiple servers in parallel and balance reading between them was
imported, but is not used right now.
* Fair number of bugfixes.
Basic POHMELFS features:
* Local coherent (notes [2]) cache for data and metadata.
* Completely async processing of all events (hard and symlinks are the only
exceptions) including object creation and data reading/writing.
* Flexible object architecture optimized for network processing. Ability to
create long pathes to object and remove arbitrary huge directoris in
single network command.
* High performance is one of the main design goals.
* Very fast and scalable multithreaded userspace server. Being in userspace
it works with any underlying filesystem and still is much faster than
async ni-kernel NFS one.
* Client is able to switch between different servers (if one goes down,
client automatically reconnects to second and so on).
* Transactions support. Full failover for all operations. Resending
transactions to different servers on timeout or error.
Roadmap includes:
* Server redundancy extensions (ability to store data in multiple locations
according to regexp rules, like '*.txt' in /root1 and '*.jpg' in /root1
and /root2.
* Strong authentification and possible data encryption in network
channel.
* Async writing of the data from receiving kernel thread into userspace
pages via copy_to_user() (check development tracking blog for results).
* Client parallel extensions: ability to write to multiple servers and
balance reading between them. Code was imported to the current version,
but not enabled yet.
* Client dynamical server reconfiguration: ability to add/remove servers
from working set by server command and from userspace.
* Start generic server distribution development.
One can grab sources from archive or git [2] or check homepage [3].
The nearest roadmap (next release is scheduled for the start of the month) includes:
* Improved reading transactions.
* Server redundancy extensions (ability to store data in multiple
locations according to regext rules, like '*.txt' in /root1 and '*.jpg'
in /root1 and /root2.
* Client parallel extensions: ability to write to multiple servers and
balance reading between them. Code was imported to the current
version, but not enabled yet.
* Client dynamical server reconfiguration: ability to add/remove servers
from working set by server command and from userspace.
Thank you.
1. POHMELFS development status.
http://tservice.net.ru/~s0mbre/blog/devel/fs/index.html
2. Source archive.
http://tservice.net.ru/~s0mbre/archive/pohmelfs/
Git tree.
http://tservice.net.ru/~s0mbre/archive/pohmelfs/pohmelfs.git/
3. POHMELFS homepage.
http://tservice.net.ru/~s0mbre/old/?section=projects&item=pohmelfs
4. POHMELFS vs NFS benchmark [iozone results are coming].
http://tservice.net.ru/~s0mbre/blog/devel/fs/2008_04_18.html
http://tservice.net.ru/~s0mbre/blog/devel/fs/2008_04_14.html
http://tservice.net.ru/~s0mbre/blog/devel/fs/2008_05_12.html
5. Cache-coherency notes.
http://tservice.net.ru/~s0mbre/blog/devel/fs/2008_05_17.html
Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
fs/Kconfig | 2 +
fs/Makefile | 1 +
fs/pohmelfs/Kconfig | 25 +
fs/pohmelfs/Makefile | 3 +
fs/pohmelfs/config.c | 148 ++++
fs/pohmelfs/dir.c | 961 ++++++++++++++++++++++++
fs/pohmelfs/inode.c | 1819 ++++++++++++++++++++++++++++++++++++++++++++++
fs/pohmelfs/net.c | 978 +++++++++++++++++++++++++
fs/pohmelfs/netfs.h | 496 +++++++++++++
fs/pohmelfs/path_entry.c | 296 ++++++++
fs/pohmelfs/trans.c | 609 ++++++++++++++++
11 files changed, 5338 insertions(+), 0 deletions(-)
diff --git a/fs/Kconfig b/fs/Kconfig
index c509123..59935cd 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -1566,6 +1566,8 @@ menuconfig NETWORK_FILESYSTEMS
if NETWORK_FILESYSTEMS
+source "fs/pohmelfs/Kconfig"
+
config NFS_FS
tristate "NFS file system support"
depends on INET
diff --git a/fs/Makefile b/fs/Makefile
index 1e7a11b..6ce6a35 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -119,3 +119,4 @@ obj-$(CONFIG_HPPFS) += hppfs/
obj-$(CONFIG_DEBUG_FS) += debugfs/
obj-$(CONFIG_OCFS2_FS) += ocfs2/
obj-$(CONFIG_GFS2_FS) += gfs2/
+obj-$(CONFIG_POHMELFS) += pohmelfs/
diff --git a/fs/pohmelfs/Kconfig b/fs/pohmelfs/Kconfig
new file mode 100644
index 0000000..38826a6
--- /dev/null
+++ b/fs/pohmelfs/Kconfig
@@ -0,0 +1,25 @@
+config POHMELFS
+ tristate "POHMELFS filesystem support"
+ help
+ POHMELFS stands for Parallel Optimized Host Message Exchange Layered File System.
+ This is a network filesystem which supports coherent caching of data and metadata
+ on clients.
+
+config POHMELFS_DEBUG
+ bool "POHMELFS debugging"
+ depends on POHMELFS
+ default n
+ help
+ Turns on excessive POHMELFS debugging facilities.
+ You usually do not want to slow things down noticebly and get really lots of kernel
+ messages in syslog.
+
+config POHMELFS_CC_GROUP
+ bool "POHMELFS cache coherency protocol"
+ depends on POHMELFS
+ default y
+ help
+ This allows to broadcast data and metadata cache coherency messages between clients.
+ Usually you want this facility, although without locking you can get different from
+ POSIX expectation behaviour. For more details check POHMELFS homepage and development
+ section.
diff --git a/fs/pohmelfs/Makefile b/fs/pohmelfs/Makefile
new file mode 100644
index 0000000..aa415a3
--- /dev/null
+++ b/fs/pohmelfs/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_POHMELFS) += pohmelfs.o
+
+pohmelfs-y := inode.o config.o dir.o net.o path_entry.o trans.o
diff --git a/fs/pohmelfs/config.c b/fs/pohmelfs/config.c
new file mode 100644
index 0000000..0f3503b
--- /dev/null
+++ b/fs/pohmelfs/config.c
@@ -0,0 +1,148 @@
+/*
+ * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/connector.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+
+#include "netfs.h"
+
+/*
+ * Global configuration list.
+ * Each client can be asked to get one of them.
+ *
+ * Allows to provide remote server address (ipv4/v6/whatever), port
+ * and so on via kernel connector.
+ */
+
+static struct cb_id pohmelfs_cn_id = {.idx = POHMELFS_CN_IDX, .val = POHMELFS_CN_VAL};
+static LIST_HEAD(pohmelfs_config_list);
+static DEFINE_MUTEX(pohmelfs_config_lock);
+
+int pohmelfs_copy_config(struct pohmelfs_sb *psb)
+{
+ struct pohmelfs_config *c, *dst;
+ int err = -ENODEV;
+
+ mutex_lock(&pohmelfs_config_lock);
+ list_for_each_entry(c, &pohmelfs_config_list, config_entry) {
+ if (c->state.ctl.idx != psb->idx)
+ continue;
+
+ dst = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL);
+ if (!dst) {
+ err = -ENOMEM;
+ goto err_out_unlock;
+ }
+
+ memcpy(&dst->state.ctl, &c->state.ctl, sizeof(struct pohmelfs_ctl));
+
+ mutex_lock(&psb->state_lock);
+ list_add_tail(&dst->config_entry, &psb->state_list);
+ mutex_unlock(&psb->state_lock);
+ err = 0;
+ }
+ mutex_unlock(&pohmelfs_config_lock);
+
+ return err;
+
+err_out_unlock:
+ mutex_unlock(&pohmelfs_config_lock);
+
+ mutex_lock(&psb->state_lock);
+ list_for_each_entry_safe(dst, c, &psb->state_list, config_entry) {
+ list_del(&dst->config_entry);
+ kfree(dst);
+ }
+ mutex_unlock(&psb->state_lock);
+
+ return err;
+}
+
+static void pohmelfs_cn_callback(void *data)
+{
+ struct cn_msg *msg = data;
+ struct pohmelfs_ctl *ctl;
+ struct pohmelfs_cn_ack *ack;
+ struct pohmelfs_config *c;
+ int err;
+
+ if (msg->len < sizeof(struct pohmelfs_ctl)) {
+ err = -EBADMSG;
+ goto out;
+ }
+
+ ctl = (struct pohmelfs_ctl *)msg->data;
+
+ err = 0;
+ mutex_lock(&pohmelfs_config_lock);
+ list_for_each_entry(c, &pohmelfs_config_list, config_entry) {
+ struct pohmelfs_ctl *sc = &c->state.ctl;
+
+ if (sc->idx == ctl->idx && sc->type == ctl->type &&
+ sc->proto == ctl->proto &&
+ sc->addrlen == ctl->addrlen &&
+ !memcmp(&sc->addr, &ctl->addr, ctl->addrlen)) {
+ err = -EEXIST;
+ break;
+ }
+ }
+ if (!err) {
+ c = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL);
+ if (!c) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(&c->state.ctl, ctl, sizeof(struct pohmelfs_ctl));
+ list_add_tail(&c->config_entry, &pohmelfs_config_list);
+ }
+ mutex_unlock(&pohmelfs_config_lock);
+
+out:
+ ack = kmalloc(sizeof(struct pohmelfs_cn_ack), GFP_KERNEL);
+ if (!ack)
+ return;
+
+ memcpy(&ack->msg, msg, sizeof(struct cn_msg));
+
+ ack->msg.ack = msg->ack + 1;
+ ack->msg.len = sizeof(struct pohmelfs_cn_ack) - sizeof(struct cn_msg);
+
+ ack->error = err;
+
+ cn_netlink_send(&ack->msg, 0, GFP_KERNEL);
+ kfree(ack);
+}
+
+int __init pohmelfs_config_init(void)
+{
+ return cn_add_callback(&pohmelfs_cn_id, "pohmelfs", pohmelfs_cn_callback);
+}
+
+void __exit pohmelfs_config_exit(void)
+{
+ struct pohmelfs_config *c, *tmp;
+
+ cn_del_callback(&pohmelfs_cn_id);
+
+ mutex_lock(&pohmelfs_config_lock);
+ list_for_each_entry_safe(c, tmp, &pohmelfs_config_list, config_entry) {
+ list_del(&c->config_entry);
+ kfree(c);
+ }
+ mutex_unlock(&pohmelfs_config_lock);
+}
diff --git a/fs/pohmelfs/dir.c b/fs/pohmelfs/dir.c
new file mode 100644
index 0000000..c5736d3
--- /dev/null
+++ b/fs/pohmelfs/dir.c
@@ -0,0 +1,961 @@
+/*
+ * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/jhash.h>
+#include <linux/pagemap.h>
+
+#include "netfs.h"
+
+/*
+ * Each pohmelfs directory inode contains a tree of childrens indexed
+ * by offset (in the dir reading stream) and name hash and len. Entries
+ * of that hashes are called pohmelfs_name.
+ *
+ * This routings deal with it.
+ */
+static int pohmelfs_cmp_offset(struct pohmelfs_name *n, u64 offset)
+{
+ if (n->offset > offset)
+ return -1;
+ if (n->offset < offset)
+ return 1;
+ return 0;
+}
+
+static struct pohmelfs_name *pohmelfs_search_offset(struct pohmelfs_inode *pi, u64 offset)
+{
+ struct rb_node *n = pi->offset_root.rb_node;
+ struct pohmelfs_name *tmp;
+ int cmp;
+
+ while (n) {
+ tmp = rb_entry(n, struct pohmelfs_name, offset_node);
+
+ cmp = pohmelfs_cmp_offset(tmp, offset);
+ if (cmp < 0)
+ n = n->rb_left;
+ else if (cmp > 0)
+ n = n->rb_right;
+ else
+ return tmp;
+ }
+
+ return NULL;
+}
+
+static struct pohmelfs_name *pohmelfs_insert_offset(struct pohmelfs_inode *pi,
+ struct pohmelfs_name *new)
+{
+ struct rb_node **n = &pi->offset_root.rb_node, *parent = NULL;
+ struct pohmelfs_name *ret = NULL, *tmp;
+ int cmp;
+
+ while (*n) {
+ parent = *n;
+
+ tmp = rb_entry(parent, struct pohmelfs_name, offset_node);
+
+ cmp = pohmelfs_cmp_offset(tmp, new->offset);
+ if (cmp < 0)
+ n = &parent->rb_left;
+ else if (cmp > 0)
+ n = &parent->rb_right;
+ else {
+ ret = tmp;
+ break;
+ }
+ }
+
+ if (ret)
+ return ret;
+
+ rb_link_node(&new->offset_node, parent, n);
+ rb_insert_color(&new->offset_node, &pi->offset_root);
+
+ pi->total_len += new->len;
+
+ return NULL;
+}
+
+static int pohmelfs_cmp_hash(struct pohmelfs_name *n, u32 hash, u32 len)
+{
+ if (n->hash > hash)
+ return -1;
+ if (n->hash < hash)
+ return 1;
+
+ if (n->len > len)
+ return -1;
+ if (n->len < len)
+ return 1;
+
+ return 0;
+}
+
+static struct pohmelfs_name *pohmelfs_search_hash(struct pohmelfs_inode *pi, u32 hash, u32 len)
+{
+ struct rb_node *n = pi->hash_root.rb_node;
+ struct pohmelfs_name *tmp;
+ int cmp;
+
+ while (n) {
+ tmp = rb_entry(n, struct pohmelfs_name, hash_node);
+
+ cmp = pohmelfs_cmp_hash(tmp, hash, len);
+ if (cmp < 0)
+ n = n->rb_left;
+ else if (cmp > 0)
+ n = n->rb_right;
+ else
+ return tmp;
+ }
+
+ return NULL;
+}
+
+static void __pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
+{
+ rb_erase(&node->offset_node, &parent->offset_root);
+ rb_erase(&node->hash_node, &parent->hash_root);
+}
+
+/*
+ * Remove name cache entry from its caches and free it.
+ */
+static void pohmelfs_name_free(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
+{
+ __pohmelfs_name_del(parent, node);
+ list_del(&node->sync_del_entry);
+ list_del(&node->sync_create_entry);
+ kfree(node);
+}
+
+static struct pohmelfs_name *pohmelfs_insert_hash(struct pohmelfs_inode *pi,
+ struct pohmelfs_name *new)
+{
+ struct rb_node **n = &pi->hash_root.rb_node, *parent = NULL;
+ struct pohmelfs_name *ret = NULL, *tmp;
+ int cmp;
+
+ while (*n) {
+ parent = *n;
+
+ tmp = rb_entry(parent, struct pohmelfs_name, hash_node);
+
+ cmp = pohmelfs_cmp_hash(tmp, new->hash, new->len);
+ if (cmp < 0)
+ n = &parent->rb_left;
+ else if (cmp > 0)
+ n = &parent->rb_right;
+ else {
+ ret = tmp;
+ break;
+ }
+ }
+
+ if (ret) {
+ printk("%s: exist: ino: %llu, hash: %x, len: %u, data: '%s', new: ino: %llu, hash: %x, len: %u, data: '%s'.\n",
+ __func__, ret->ino, ret->hash, ret->len, ret->data,
+ new->ino, new->hash, new->len, new->data);
+ ret->ino = new->ino;
+ return ret;
+ }
+
+ rb_link_node(&new->hash_node, parent, n);
+ rb_insert_color(&new->hash_node, &pi->hash_root);
+
+ return NULL;
+}
+
+/*
+ * Free name cache for given inode.
+ */
+void pohmelfs_free_names(struct pohmelfs_inode *parent)
+{
+ struct rb_node *rb_node;
+ struct pohmelfs_name *n;
+
+ for (rb_node = rb_first(&parent->offset_root); rb_node;) {
+ n = rb_entry(rb_node, struct pohmelfs_name, offset_node);
+ rb_node = rb_next(rb_node);
+
+ pohmelfs_name_free(parent, n);
+ }
+}
+
+/*
+ * When name cache entry is removed (for example when object is removed),
+ * offset for all subsequent childrens has to be fixed to match new reality.
+ */
+static int pohmelfs_fix_offset(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
+{
+ struct rb_node *rb_node;
+ int decr = 0;
+
+ for (rb_node = rb_next(&node->offset_node); rb_node; rb_node = rb_next(rb_node)) {
+ struct pohmelfs_name *n = container_of(rb_node, struct pohmelfs_name, offset_node);
+
+ n->offset -= node->len;
+ decr++;
+ }
+
+ parent->total_len -= node->len;
+
+ return decr;
+}
+
+/*
+ * Fix offset and free name cache entry helper.
+ */
+void pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
+{
+ int decr;
+
+ decr = pohmelfs_fix_offset(parent, node);
+
+ dprintk("%s: parent: %llu, ino: %llu, decr: %d.\n",
+ __func__, parent->ino, node->ino, decr);
+
+ pohmelfs_name_free(parent, node);
+}
+
+/*
+ * Insert new name cache entry into all caches (offset and name hash).
+ */
+static int pohmelfs_insert_name(struct pohmelfs_inode *parent, struct pohmelfs_name *n)
+{
+ struct pohmelfs_name *name;
+
+ name = pohmelfs_insert_offset(parent, n);
+ if (name)
+ return -EEXIST;
+
+ name = pohmelfs_insert_hash(parent, n);
+ if (name) {
+ parent->total_len -= n->len;
+ rb_erase(&n->offset_node, &parent->offset_root);
+ return -EEXIST;
+ }
+
+ list_add_tail(&n->sync_create_entry, &parent->sync_create_list);
+
+ return 0;
+}
+
+/*
+ * Allocate new name cache entry.
+ */
+static struct pohmelfs_name *pohmelfs_name_clone(unsigned int len)
+{
+ struct pohmelfs_name *n;
+
+ n = kzalloc(sizeof(struct pohmelfs_name) + len, GFP_KERNEL);
+ if (!n)
+ return NULL;
+
+ INIT_LIST_HEAD(&n->sync_create_entry);
+ INIT_LIST_HEAD(&n->sync_del_entry);
+
+ n->data = (char *)(n+1);
+
+ return n;
+}
+
+/*
+ * Add new name entry into directory's cache.
+ */
+static int pohmelfs_add_dir(struct pohmelfs_sb *psb, struct pohmelfs_inode *parent,
+ struct pohmelfs_inode *npi, struct qstr *str, unsigned int mode, int link)
+{
+ int err = -ENOMEM;
+ struct pohmelfs_name *n;
+ struct pohmelfs_path_entry *e = NULL;
+
+ n = pohmelfs_name_clone(str->len + 1);
+ if (!n)
+ goto err_out_exit;
+
+ n->ino = npi->ino;
+ n->offset = parent->total_len;
+ n->mode = mode;
+ n->len = str->len;
+ n->hash = str->hash;
+ sprintf(n->data, str->name);
+
+ if (!(str->len == 1 && str->name[0] == '.') &&
+ !(str->len == 2 && str->name[0] == '.' && str->name[1] == '.')) {
+ mutex_lock(&psb->path_lock);
+ e = pohmelfs_add_path_entry(psb, parent->ino, npi->ino, str, link, mode);
+ mutex_unlock(&psb->path_lock);
+ if (IS_ERR(e)) {
+ err = PTR_ERR(e);
+ goto err_out_free;
+ }
+ }
+
+ mutex_lock(&parent->offset_lock);
+ err = pohmelfs_insert_name(parent, n);
+ mutex_unlock(&parent->offset_lock);
+
+ if (err) {
+ if (err != -EEXIST)
+ goto err_out_remove;
+ kfree(n);
+ }
+
+ return 0;
+
+err_out_remove:
+ if (e) {
+ mutex_lock(&psb->path_lock);
+ pohmelfs_remove_path_entry(psb, e);
+ mutex_unlock(&psb->path_lock);
+ }
+err_out_free:
+ kfree(n);
+err_out_exit:
+ return err;
+}
+
+/*
+ * Create new inode for given parameters (name, inode info, parent).
+ * This does not create object on the server, it will be synced there during writeback.
+ */
+struct pohmelfs_inode *pohmelfs_new_inode(struct pohmelfs_sb *psb,
+ struct pohmelfs_inode *parent, struct qstr *str,
+ struct netfs_inode_info *info, int link)
+{
+ struct inode *new = NULL;
+ struct pohmelfs_inode *npi;
+ int err = -EEXIST;
+
+ dprintk("%s: creating inode: parent: %llu, ino: %llu, str: %p.\n",
+ __func__, (parent)?parent->ino:0, info->ino, str);
+
+ err = -ENOMEM;
+ new = iget_locked(psb->sb, info->ino);
+ if (!new)
+ goto err_out_exit;
+
+ npi = POHMELFS_I(new);
+ npi->ino = info->ino;
+ err = 0;
+
+ if (new->i_state & I_NEW) {
+ dprintk("%s: filling VFS inode: %lu/%llu.\n",
+ __func__, new->i_ino, info->ino);
+ pohmelfs_fill_inode(new, info);
+
+ if (S_ISDIR(info->mode)) {
+ struct qstr s;
+
+ s.name = ".";
+ s.len = 1;
+ s.hash = jhash(s.name, s.len, 0);
+
+ err = pohmelfs_add_dir(psb, npi, npi, &s, info->mode, 0);
+ if (err)
+ goto err_out_put;
+
+ s.name = "..";
+ s.len = 2;
+ s.hash = jhash(s.name, s.len, 0);
+
+ err = pohmelfs_add_dir(psb, npi, (parent)?parent:npi, &s,
+ (parent)?parent->vfs_inode.i_mode:npi->vfs_inode.i_mode, 0);
+ if (err)
+ goto err_out_put;
+ }
+ }
+
+ if (str) {
+ if (parent) {
+ err = pohmelfs_add_dir(psb, parent, npi, str, info->mode, link);
+
+ dprintk("%s: %s inserted name: '%s', new_offset: %llu, ino: %llu, parent: %llu.\n",
+ __func__, (err)?"unsuccessfully":"successfully",
+ str->name, parent->total_len, info->ino, parent->ino);
+
+ if (err && err != -EEXIST)
+ goto err_out_put;
+ } else {
+ mutex_lock(&psb->path_lock);
+ pohmelfs_add_path_entry(psb, npi->ino, npi->ino, str, link, info->mode);
+ mutex_unlock(&psb->path_lock);
+ }
+ }
+
+ if (new->i_state & I_NEW) {
+ if (parent)
+ mark_inode_dirty(&parent->vfs_inode);
+ mark_inode_dirty(new);
+
+#ifdef POHMELFS_CC_GROUP
+ pohmelfs_meta_command(npi, NETFS_JOIN_GROUP, 0, NULL, NULL, 0);
+#endif
+ }
+ unlock_new_inode(new);
+
+ return npi;
+
+err_out_put:
+ printk("%s: putting inode: %p, npi: %p, error: %d.\n", __func__, new, npi, err);
+ iput(new);
+err_out_exit:
+ return ERR_PTR(err);
+}
+
+/*
+ * Receive directory content from the server.
+ * This should be only done for objects, which were not created locally,
+ * and which were not synced previously.
+ */
+static int pohmelfs_sync_remote_dir(struct pohmelfs_inode *pi)
+{
+ struct inode *inode = &pi->vfs_inode;
+ struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
+ long ret = msecs_to_jiffies(25000);
+ int err;
+
+ dprintk("%s: dir: %llu, state: %lx: created: %d, remote_synced: %d.\n",
+ __func__, pi->ino, pi->state, test_bit(NETFS_INODE_CREATED, &pi->state),
+ test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state));
+
+ if (!test_bit(NETFS_INODE_CREATED, &pi->state))
+ return 0;
+
+ if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state))
+ return 0;
+
+ err = pohmelfs_meta_command(pi, NETFS_READDIR, 0, NULL, NULL, 0);
+ if (err)
+ return err;
+
+ ret = wait_event_interruptible_timeout(psb->wait,
+ test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state), ret);
+ dprintk("%s: awake dir: %llu, ret: %ld.\n", __func__, pi->ino, ret);
+ if (ret <= 0) {
+ err = -ETIMEDOUT;
+ goto err_out_exit;
+ }
+
+ return 0;
+
+err_out_exit:
+ clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state);
+
+ return err;
+}
+
+/*
+ * VFS readdir callback. Syncs directory content from server if needed,
+ * and provide info to userspace.
+ */
+static int pohmelfs_readdir(struct file *file, void *dirent, filldir_t filldir)
+{
+ struct inode *inode = file->f_path.dentry->d_inode;
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+ struct pohmelfs_name *n;
+ int err = 0, mode;
+ u64 len;
+
+ dprintk("%s: parent: %llu.\n", __func__, pi->ino);
+
+ err = pohmelfs_sync_remote_dir(pi);
+ if (err)
+ return err;
+
+ while (1) {
+ mutex_lock(&pi->offset_lock);
+ n = pohmelfs_search_offset(pi, file->f_pos);
+ if (!n) {
+ mutex_unlock(&pi->offset_lock);
+ err = 0;
+ break;
+ }
+
+ mode = (n->mode >> 12) & 15;
+
+ dprintk("%s: offset: %llu, parent ino: %llu, name: '%s', len: %u, ino: %llu, mode: %o/%o.\n",
+ __func__, file->f_pos, pi->ino, n->data, n->len,
+ n->ino, n->mode, mode);
+
+ len = n->len;
+ err = filldir(dirent, n->data, n->len, file->f_pos, n->ino, mode);
+ mutex_unlock(&pi->offset_lock);
+
+ if (err < 0) {
+ dprintk("%s: err: %d.\n", __func__, err);
+ err = 0;
+ break;
+ }
+
+ file->f_pos += len;
+ }
+
+ return err;
+}
+
+const struct file_operations pohmelfs_dir_fops = {
+ .read = generic_read_dir,
+ .readdir = pohmelfs_readdir,
+};
+
+/*
+ * Lookup single object on server.
+ */
+static int pohmelfs_lookup_single(struct pohmelfs_inode *parent,
+ struct qstr *str, u64 ino)
+{
+ struct pohmelfs_sb *psb = POHMELFS_SB(parent->vfs_inode.i_sb);
+ long ret = msecs_to_jiffies(5000);
+ int err;
+
+ set_bit(NETFS_COMMAND_PENDING, &parent->state);
+ err = pohmelfs_meta_command_data(parent, NETFS_LOOKUP,
+ (char *)str->name, 0, NULL, NULL, ino);
+ if (err)
+ goto err_out_exit;
+
+ err = 0;
+ ret = wait_event_interruptible_timeout(psb->wait,
+ !test_bit(NETFS_COMMAND_PENDING, &parent->state), ret);
+ if (ret == 0)
+ err = -ETIMEDOUT;
+ else if (signal_pending(current))
+ err = -EINTR;
+
+ if (err)
+ goto err_out_exit;
+
+ return 0;
+
+err_out_exit:
+ clear_bit(NETFS_COMMAND_PENDING, &parent->state);
+
+ printk("%s: failed: parent: %llu, ino: %llu, name: '%s', err: %d.\n",
+ __func__, parent->ino, ino, str->name, err);
+
+ return err;
+}
+
+/*
+ * VFS lookup callback.
+ * We first try to get inode number from local name cache, if we have one,
+ * then inode can be found in inode cache. If there is no inode or no object in
+ * local cache, try to lookup it on server. This only should be done for directories,
+ * which were not created locally, otherwise remote server does not know about dir at all,
+ * so no need to try to know that.
+ */
+struct dentry *pohmelfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
+{
+ struct pohmelfs_inode *parent = POHMELFS_I(dir);
+ struct pohmelfs_name *n;
+ struct inode *inode = NULL;
+ unsigned long ino = 0;
+ int err;
+ struct qstr str = dentry->d_name;
+
+ str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
+
+ mutex_lock(&parent->offset_lock);
+ n = pohmelfs_search_hash(parent, str.hash, str.len);
+ if (n)
+ ino = n->ino;
+ mutex_unlock(&parent->offset_lock);
+
+ dprintk("%s: 1 ino: %lu, inode: %p, name: '%s', hash: %x, parent_state: %lx.\n",
+ __func__, ino, inode, str.name, str.hash, parent->state);
+
+ if (ino) {
+ inode = ilookup(dir->i_sb, ino);
+ if (inode)
+ goto out;
+ }
+
+ dprintk("%s: dir: %p, dir_ino: %llu, name: '%s', len: %u, dir_state: %lx, ino: %lu.\n",
+ __func__, dir, parent->ino,
+ str.name, str.len, parent->state, ino);
+
+ if (!ino) {
+ if (!test_bit(NETFS_INODE_CREATED, &parent->state))
+ goto out;
+
+ if (test_bit(NETFS_INODE_REMOTE_SYNCED, &parent->state))
+ goto out;
+ }
+
+ err = pohmelfs_lookup_single(parent, &str, ino);
+ if (err)
+ goto out;
+
+ if (!ino) {
+ mutex_lock(&parent->offset_lock);
+ n = pohmelfs_search_hash(parent, str.hash, str.len);
+ if (n)
+ ino = n->ino;
+ mutex_unlock(&parent->offset_lock);
+ }
+
+ if (ino) {
+ inode = ilookup(dir->i_sb, ino);
+ printk("%s: second lookup ino: %lu, inode: %p, name: '%s', hash: %x.\n",
+ __func__, ino, inode, str.name, str.hash);
+ if (!inode) {
+ printk("%s: No inode for ino: %lu, name: '%s', hash: %x.\n",
+ __func__, ino, str.name, str.hash);
+ //return NULL;
+ return ERR_PTR(-EACCES);
+ }
+ } else {
+ printk("%s: No inode number : name: '%s', hash: %x.\n",
+ __func__, str.name, str.hash);
+ }
+out:
+ return d_splice_alias(inode, dentry);
+}
+
+/*
+ * Create new object in local cache. Object will be synced to server
+ * during writeback for given inode.
+ */
+struct pohmelfs_inode *pohmelfs_create_entry_local(struct pohmelfs_sb *psb,
+ struct pohmelfs_inode *parent, struct qstr *str, u64 start, int mode)
+{
+ struct pohmelfs_inode *npi;
+ int err = -ENOMEM;
+ struct netfs_inode_info info;
+
+ dprintk("%s: name: '%s', mode: %o, start: %llu.\n",
+ __func__, str->name, mode, start);
+
+ info.mode = mode;
+ info.ino = start;
+
+ if (!start)
+ info.ino = pohmelfs_new_ino(psb);
+
+ info.nlink = S_ISDIR(mode)?2:1;
+ info.uid = current->uid;
+ info.gid = current->gid;
+ info.size = 0;
+ info.blocksize = 512;
+ info.blocks = 0;
+ info.rdev = 0;
+ info.version = 0;
+
+ npi = pohmelfs_new_inode(psb, parent, str, &info, !!start);
+ if (IS_ERR(npi)) {
+ err = PTR_ERR(npi);
+ goto err_out_unlock;
+ }
+
+ set_bit(NETFS_INODE_REMOTE_SYNCED, &npi->state);
+
+ return npi;
+
+err_out_unlock:
+ dprintk("%s: err: %d.\n", __func__, err);
+ return ERR_PTR(err);
+}
+
+/*
+ * Create local object and bind it to dentry.
+ */
+static int pohmelfs_create_entry(struct inode *dir, struct dentry *dentry, u64 start, int mode)
+{
+ struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb);
+ struct pohmelfs_inode *npi;
+ struct qstr str = dentry->d_name;
+
+ str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
+
+ npi = pohmelfs_create_entry_local(psb, POHMELFS_I(dir), &str, start, mode);
+ if (IS_ERR(npi))
+ return PTR_ERR(npi);
+
+ d_instantiate(dentry, &npi->vfs_inode);
+
+ dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n",
+ __func__, POHMELFS_I(dir)->ino, npi->ino, dentry->d_name.name,
+ (signed)dir->i_nlink, (signed)npi->vfs_inode.i_nlink);
+
+ return 0;
+}
+
+/*
+ * VFS create and mkdir callbacks.
+ */
+static int pohmelfs_create(struct inode *dir, struct dentry *dentry, int mode,
+ struct nameidata *nd)
+{
+ return pohmelfs_create_entry(dir, dentry, 0, mode);
+}
+
+static int pohmelfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+ int err;
+
+ inode_inc_link_count(dir);
+ err = pohmelfs_create_entry(dir, dentry, 0, mode | S_IFDIR);
+ if (err)
+ inode_dec_link_count(dir);
+
+ return err;
+}
+
+/*
+ * Remove entry from local cache.
+ * Object will not be removed from server, instead it will be queued into parent
+ * to-be-removed queue, which will be processed during parent writeback (parent
+ * also marked as dirty). Writeback will send remove request to server.
+ * Such approach allows to remove vey huge directories (like 2.6.24 kernel tree)
+ * with only single network command.
+ */
+static int pohmelfs_remove_entry(struct inode *dir, struct dentry *dentry)
+{
+ struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb);
+ struct inode *inode = dentry->d_inode;
+ struct pohmelfs_inode *parent = POHMELFS_I(dir), *pi = POHMELFS_I(inode);
+ struct pohmelfs_name *n;
+ int err = -ENOENT;
+ struct qstr str = dentry->d_name;
+
+ str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
+
+ dprintk("%s: dir_ino: %llu, inode: %llu, name: '%s', nlink: %d.\n",
+ __func__, parent->ino, pi->ino,
+ str.name, (signed)inode->i_nlink);
+
+ mutex_lock(&parent->offset_lock);
+ n = pohmelfs_search_hash(parent, str.hash, str.len);
+ if (n) {
+ pohmelfs_fix_offset(parent, n);
+ if (test_bit(NETFS_INODE_CREATED, &pi->state)) {
+ __pohmelfs_name_del(parent, n);
+ list_add_tail(&n->sync_del_entry, &parent->sync_del_list);
+ } else
+ pohmelfs_name_free(parent, n);
+ err = 0;
+ }
+ mutex_unlock(&parent->offset_lock);
+
+ if (!err) {
+ mutex_lock(&psb->path_lock);
+ pohmelfs_remove_path_entry_by_ino(psb, pi->ino);
+ mutex_unlock(&psb->path_lock);
+
+ pohmelfs_inode_del_inode(psb, pi);
+
+ mark_inode_dirty(dir);
+
+ inode->i_ctime = dir->i_ctime;
+ if (inode->i_nlink)
+ inode_dec_link_count(inode);
+ }
+ dprintk("%s: inode: %p, lock: %ld, unhashed: %d.\n",
+ __func__, pi, inode->i_state & I_LOCK, hlist_unhashed(&inode->i_hash));
+
+ return err;
+}
+
+/*
+ * Unlink and rmdir VFS callbacks.
+ */
+static int pohmelfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+ return pohmelfs_remove_entry(dir, dentry);
+}
+
+static int pohmelfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ int err;
+ struct inode *inode = dentry->d_inode;
+
+ dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n",
+ __func__, POHMELFS_I(dir)->ino, POHMELFS_I(inode)->ino,
+ dentry->d_name.name, (signed)dir->i_nlink, (signed)inode->i_nlink);
+
+ err = pohmelfs_remove_entry(dir, dentry);
+ if (!err) {
+ inode_dec_link_count(dir);
+ inode_dec_link_count(inode);
+ }
+
+ return err;
+}
+
+/*
+ * Link creation is synchronous.
+ * I'm lazy.
+ * Earth is somewhat round.
+ */
+static int pohmelfs_create_link(struct pohmelfs_inode *parent, struct qstr *obj,
+ struct pohmelfs_inode *target, struct qstr *tstr)
+{
+ struct super_block *sb = parent->vfs_inode.i_sb;
+ struct pohmelfs_sb *psb = POHMELFS_SB(sb);
+ struct netfs_cmd *cmd;
+ unsigned int path_size = 0, cur_len;
+ struct netfs_trans *t;
+ void *data;
+ int err;
+
+ err = sb->s_op->write_inode(&parent->vfs_inode, 0);
+ if (err)
+ return err;
+
+ t = netfs_trans_alloc_page(NETFS_TRANS_SYNC);
+ if (!t)
+ return -ENOMEM;
+ cur_len = t->data_size - netfs_trans_cur_len(t);
+
+ cmd = netfs_trans_add(t, cur_len);
+ if (IS_ERR(cmd)) {
+ err = PTR_ERR(cmd);
+ goto err_out_free;
+ }
+
+ data = (void *)(cmd + 1);
+ cur_len -= sizeof(struct netfs_cmd);
+
+ mutex_lock(&psb->path_lock);
+ err = pohmelfs_construct_path_string(parent, data, cur_len - obj->len - 1);
+ if (err > 0) {
+ path_size = err;
+
+ path_size += sprintf(data + path_size, "/%s|", obj->name);
+
+ cmd->ext = path_size - 1; /* No | symbol */
+
+ if (target) {
+ err = pohmelfs_construct_path_string(target, data + path_size, cur_len - path_size - 1);
+ if (err > 0)
+ path_size += err + 1;
+ }
+ }
+ mutex_unlock(&psb->path_lock);
+
+ if (err < 0)
+ goto err_out_free;
+
+ cmd->start = 0;
+
+ if (!target) {
+ if (tstr->len > cur_len - path_size - 1) {
+ err = -ENAMETOOLONG;
+ goto err_out_free;
+ }
+
+ path_size += sprintf(data + path_size, "%s", tstr->name) + 1 /* 0-byte */;
+ cmd->start = 1;
+ }
+
+ netfs_trans_fixup_last(t, path_size - cur_len);
+
+ dprintk("%s: parent: %llu, obj: '%s', target_inode: %llu, target_str: '%s', full: '%s'.\n",
+ __func__, parent->ino, obj->name, (target)?target->ino:0, (tstr)?tstr->name:NULL,
+ (char *)data);
+
+ cmd->cmd = NETFS_LINK;
+ cmd->size = path_size;
+ cmd->id = parent->ino;
+ netfs_convert_cmd(cmd);
+
+ err = netfs_trans_finish(t, psb);
+ if (err)
+ goto err_out_free;
+
+ return 0;
+
+err_out_free:
+ netfs_trans_exit(t, err);
+
+ return err;
+}
+
+/*
+ * VFS hard and soft link callbacks.
+ */
+static int pohmelfs_link(struct dentry *old_dentry, struct inode *dir,
+ struct dentry *dentry)
+{
+ struct inode *inode = old_dentry->d_inode;
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+ int err;
+ struct qstr str = dentry->d_name;
+
+ str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
+
+ err = inode->i_sb->s_op->write_inode(inode, 0);
+ if (err)
+ return err;
+
+ err = pohmelfs_create_link(POHMELFS_I(dir), &str, pi, NULL);
+ if (err)
+ return err;
+
+ return pohmelfs_create_entry(dir, dentry, pi->ino, inode->i_mode);
+}
+
+static int pohmelfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
+{
+ struct qstr sym_str;
+ struct qstr str = dentry->d_name;
+ struct inode *inode;
+ int err;
+
+ str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
+
+ sym_str.name = symname;
+ sym_str.len = strlen(symname);
+
+ err = pohmelfs_create_link(POHMELFS_I(dir), &str, NULL, &sym_str);
+ if (err)
+ goto err_out_exit;
+
+ err = pohmelfs_create_entry(dir, dentry, 0, S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO);
+ if (err)
+ goto err_out_exit;
+
+ inode = dentry->d_inode;
+
+ err = page_symlink(inode, symname, sym_str.len + 1);
+ if (err)
+ goto err_out_put;
+
+ return 0;
+
+err_out_put:
+ iput(inode);
+err_out_exit:
+ return err;
+}
+
+/*
+ * POHMELFS directory inode operations.
+ */
+const struct inode_operations pohmelfs_dir_inode_ops = {
+ .link = pohmelfs_link,
+ .symlink = pohmelfs_symlink,
+ .unlink = pohmelfs_unlink,
+ .mkdir = pohmelfs_mkdir,
+ .rmdir = pohmelfs_rmdir,
+ .create = pohmelfs_create,
+ .lookup = pohmelfs_lookup,
+ .setattr = pohmelfs_setattr,
+};
diff --git a/fs/pohmelfs/inode.c b/fs/pohmelfs/inode.c
new file mode 100644
index 0000000..32d3b7b
--- /dev/null
+++ b/fs/pohmelfs/inode.c
@@ -0,0 +1,1819 @@
+/*
+ * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/backing-dev.h>
+#include <linux/fs.h>
+#include <linux/jhash.h>
+#include <linux/hash.h>
+#include <linux/ktime.h>
+#include <linux/mm.h>
+#include <linux/mount.h>
+#include <linux/pagemap.h>
+#include <linux/pagevec.h>
+#include <linux/parser.h>
+#include <linux/swap.h>
+#include <linux/slab.h>
+#include <linux/statfs.h>
+#include <linux/writeback.h>
+#include <linux/quotaops.h>
+
+#include "netfs.h"
+
+static struct kmem_cache *pohmelfs_inode_cache;
+
+/*
+ * Removes inode from all trees, drops local name cache and removes all queued
+ * requests for object removal.
+ */
+void pohmelfs_inode_del_inode(struct pohmelfs_sb *psb, struct pohmelfs_inode *pi)
+{
+ struct pohmelfs_name *n, *tmp;
+
+ mutex_lock(&pi->offset_lock);
+ pohmelfs_free_names(pi);
+
+ list_for_each_entry_safe(n, tmp, &pi->sync_create_list, sync_create_entry) {
+ list_del_init(&n->sync_create_entry);
+ list_del_init(&n->sync_del_entry);
+ kfree(n);
+ }
+
+ list_for_each_entry_safe(n, tmp, &pi->sync_del_list, sync_del_entry) {
+ list_del_init(&n->sync_create_entry);
+ list_del_init(&n->sync_del_entry);
+ kfree(n);
+ }
+ mutex_unlock(&pi->offset_lock);
+
+ dprintk("%s: deleted stuff in ino: %llu.\n", __func__, pi->ino);
+}
+
+/*
+ * Sync inode to server.
+ * Returns zero in success and negative error value otherwise.
+ * It will gather path to root directory into structures containing
+ * creation mode, permissions and names, so that the whole path
+ * to given inode could be created using only single network command.
+ */
+static int pohmelfs_write_inode_create(struct inode *inode, struct netfs_trans *trans)
+{
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+ struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
+ int err = -ENOMEM, size;
+ struct netfs_cmd *cmd;
+ void *data;
+ unsigned int cur_len = trans->data_size - netfs_trans_cur_len(trans);
+
+ dprintk("%s: started ino: %llu.\n", __func__, pi->ino);
+
+ cmd = netfs_trans_add(trans, cur_len);
+ if (IS_ERR(cmd)) {
+ err = PTR_ERR(cmd);
+ goto err_out_exit;
+ }
+
+ data = (void *)(cmd + 1);
+
+ mutex_lock(&psb->path_lock);
+ err = pohmelfs_construct_path(pi, data, cur_len - sizeof(struct netfs_cmd));
+ mutex_unlock(&psb->path_lock);
+ if (err < 0)
+ goto err_out_unroll;
+
+ size = err;
+
+ netfs_trans_fixup_last(trans, size + sizeof(struct netfs_cmd) - cur_len);
+
+ if (size) {
+ cmd->start = 0;
+ cmd->cmd = NETFS_CREATE;
+ cmd->size = size;
+ cmd->id = pi->ino;
+ cmd->ext = 0;
+
+ netfs_convert_cmd(cmd);
+ }
+
+ dprintk("%s: completed ino: %llu, size: %d.\n", __func__, pi->ino, size);
+ return 0;
+
+err_out_unroll:
+ netfs_trans_fixup_last(trans, cur_len);
+err_out_exit:
+ clear_bit(NETFS_INODE_CREATED, &pi->state);
+ printk("%s: completed ino: %llu, err: %d.\n", __func__, pi->ino, err);
+ return err;
+}
+
+static int pohmelfs_write_trans_complete(struct netfs_trans *t, int err)
+{
+ unsigned i;
+
+ dprintk("%s: t: %p, trans_gen: %u, trans_size: %u, data_size: %u, trans_idx: %u, iovec_num: %u, err: %d.\n",
+ __func__, t, t->trans_gen, t->trans_size, t->data_size, t->trans_idx, t->iovec_num, err);
+
+ for (i = 0; i < t->iovec_num-1; i++) {
+ struct page *page = t->data[i+1];
+
+ if (!page)
+ continue;
+
+ if (err)
+ printk("%s: mapping: %p, inode: %p, completed page: %p, size: %lu.\n",
+ __func__, page->mapping, page->mapping->host, page, page_private(page));
+#if 0
+ if (err)
+ __set_page_dirty_nobuffers(page);
+#endif
+ end_page_writeback(page);
+
+ BUG_ON(PageWriteback(page));
+
+ unlock_page(page);
+ page_cache_release(page);
+ }
+ return err;
+}
+
+static int pohmelfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
+{
+ struct inode *inode = mapping->host;
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+ struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
+ struct backing_dev_info *bdi = mapping->backing_dev_info;
+ int ret = 0;
+ int done = 0;
+ int nr_pages;
+ int created = 0;
+ pgoff_t index;
+ pgoff_t end; /* Inclusive */
+ int scanned = 0;
+ int range_whole = 0;
+
+ if (wbc->nonblocking && bdi_write_congested(bdi)) {
+ wbc->encountered_congestion = 1;
+ return 0;
+ }
+
+ if (wbc->range_cyclic) {
+ index = mapping->writeback_index; /* Start from prev offset */
+ end = -1;
+ } else {
+ index = wbc->range_start >> PAGE_CACHE_SHIFT;
+ end = wbc->range_end >> PAGE_CACHE_SHIFT;
+ if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
+ range_whole = 1;
+ scanned = 1;
+ }
+retry:
+ while (!done && (index <= end)) {
+ unsigned int i = min(end - index, (pgoff_t)1024);
+ unsigned int cur_len, added = 0;
+ void *data;
+ struct netfs_cmd *cmd, *cmds;
+ struct netfs_trans *trans;
+
+ trans = netfs_trans_alloc_for_pages(i);
+ if (!trans) {
+ ret = -ENOMEM;
+ goto err_out_break;
+ }
+
+ cmds = (struct netfs_cmd *)(trans->data + trans->iovec_num);
+
+ nr_pages = find_get_pages_tag(mapping, &index,
+ PAGECACHE_TAG_DIRTY, trans->iovec_num-1,
+ (struct page **)&trans->data[1]);
+
+ dprintk("%s: t: %p, nr_pages: %u, end: %lu, index: %lu, max: %u.\n",
+ __func__, trans, nr_pages, end, index, trans->iovec_num);
+
+ if (!nr_pages)
+ goto err_out_break;
+
+ ret = netfs_trans_start_empty(trans, NETFS_TRANS_SYNC);
+ if (ret)
+ goto err_out_break;
+ trans->complete = &pohmelfs_write_trans_complete;
+
+ cmd = netfs_trans_add(trans, sizeof(struct netfs_cmd));
+ if (IS_ERR(cmd)) {
+ ret = PTR_ERR(cmd);
+ goto err_out_reset;
+ }
+
+ trans->trans_size -= sizeof(struct netfs_cmd);
+
+ if (!test_bit(NETFS_INODE_CREATED, &pi->state)) {
+ ret = pohmelfs_write_inode_create(inode, trans);
+ if (ret)
+ goto err_out_reset;
+ created = 1;
+ }
+
+ cur_len = trans->data_size - netfs_trans_cur_len(trans);
+
+ cmd = netfs_trans_add(trans, cur_len);
+ if (IS_ERR(cmd)) {
+ ret = PTR_ERR(cmd);
+ goto err_out_reset;
+ }
+
+ data = (void *)(cmd + 1);
+
+ mutex_lock(&psb->path_lock);
+ ret = pohmelfs_construct_path_string(pi, data, cur_len - sizeof(struct netfs_cmd));
+ mutex_unlock(&psb->path_lock);
+ if (ret < 0)
+ goto err_out_reset;
+
+ netfs_trans_fixup_last(trans, ret + 1 + sizeof(struct netfs_cmd) - cur_len);
+
+ cmd->id = pi->ino;
+ cmd->start = 0;
+ cmd->size = ret + 1;
+ cmd->cmd = NETFS_OPEN;
+ cmd->ext = O_RDWR;
+
+ netfs_convert_cmd(cmd);
+
+ ret = 0;
+
+ scanned = 1;
+ for (i = 0; i < nr_pages; i++) {
+ struct page *page = trans->data[i+1];
+ struct iovec *io;
+
+ trans->data[i+1] = NULL;
+
+ lock_page(page);
+
+ if (unlikely(page->mapping != mapping)) {
+ unlock_page(page);
+ continue;
+ }
+
+ if (!wbc->range_cyclic && page->index > end) {
+ done = 1;
+ unlock_page(page);
+ continue;
+ }
+
+ if (wbc->sync_mode != WB_SYNC_NONE)
+ wait_on_page_writeback(page);
+
+ if (PageWriteback(page) ||
+ !clear_page_dirty_for_io(page)) {
+ dprintk("%s: not clear for io page: %p, writeback: %d.\n",
+ __func__, page, PageWriteback(page));
+ unlock_page(page);
+ continue;
+ }
+
+ set_page_writeback(page);
+
+ cmd = &cmds[i];
+
+ cmd->id = pi->ino;
+ cmd->start = page->index << PAGE_CACHE_SHIFT;
+ cmd->size = page_private(page);
+ cmd->cmd = NETFS_WRITE_PAGE;
+ cmd->ext = 0;
+
+ trans->trans_size += cmd->size + sizeof(struct netfs_cmd);
+
+ netfs_convert_cmd(cmd);
+
+ io = &trans->iovec[++trans->trans_idx];
+ io->iov_len = sizeof(struct netfs_cmd);
+ io->iov_base = cmd;
+
+ io = &trans->iovec[++trans->trans_idx];
+ io->iov_len = page_private(page);
+ io->iov_base = page;
+
+ trans->data[i+1] = page;
+
+ added++;
+
+ dprintk("%s: added trans: %p, idx: %u, page: %p, addr: %p [High: %d], size: %lu.\n",
+ __func__, trans, trans->trans_idx, page, io->iov_base,
+ !!PageHighMem(page), page_private(page));
+
+ if (ret || (--(wbc->nr_to_write) <= 0))
+ done = 1;
+ if (wbc->nonblocking && bdi_write_congested(bdi)) {
+ wbc->encountered_congestion = 1;
+ done = 1;
+ }
+ }
+
+ if (added || created) {
+ ret = netfs_trans_finish(trans, psb);
+ if (ret)
+ netfs_trans_exit(trans, ret);
+ } else {
+ netfs_trans_reset(trans);
+ netfs_trans_exit(trans, 0);
+ }
+
+ if (ret)
+ break;
+
+ continue;
+
+err_out_reset:
+ netfs_trans_reset(trans);
+err_out_break:
+ netfs_trans_exit(trans, ret);
+ break;
+ }
+
+ if (!scanned && !done) {
+ /*
+ * We hit the last page and there is more work to be done: wrap
+ * back to the start of the file
+ */
+ scanned = 1;
+ index = 0;
+ goto retry;
+ }
+
+ dprintk("%s: range_cyclic: %d, range_whole: %d, nr_to_write: %lu, index: %lu, ret: %d, created: %d.\n",
+ __func__, wbc->range_cyclic, range_whole, wbc->nr_to_write, index, ret, created);
+
+ if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))
+ mapping->writeback_index = index;
+
+ return ret;
+}
+
+/*
+ * Removes given child from given inode on server.
+ */
+static int pohmelfs_remove_child(struct pohmelfs_inode *parent, struct pohmelfs_name *n)
+{
+ dprintk("%s: parent: %llu, ino: %llu, name: '%s'.\n",
+ __func__, parent->ino, n->ino, n->data);
+
+ return pohmelfs_meta_command_data(parent, NETFS_REMOVE, n->data, 0, NULL, NULL, 0);
+}
+
+/*
+ * Removes all childs, marked for deletion, on server.
+ */
+static int pohmelfs_write_inode_remove_children(struct inode *inode)
+{
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+ int err, error = 0;
+ struct pohmelfs_name *n, *tmp;
+
+ dprintk("%s: parent: %llu, del_list_empty: %d.\n",
+ __func__, pi->ino, list_empty(&pi->sync_del_list));
+
+ if (!list_empty(&pi->sync_del_list)) {
+ mutex_lock(&pi->offset_lock);
+ list_for_each_entry_safe(n, tmp, &pi->sync_del_list, sync_del_entry) {
+ list_del_init(&n->sync_del_entry);
+ list_del_init(&n->sync_create_entry);
+
+ err = pohmelfs_remove_child(pi, n);
+ if (err)
+ error = err;
+
+ kfree(n);
+ }
+ mutex_unlock(&pi->offset_lock);
+ }
+
+ return error;
+}
+
+/*
+ * Inode writeback creation completion callback.
+ * Only invoked for just created inodes, which do not have pages attached,
+ * like dirs and empty files.
+ */
+static int pohmelfs_write_inode_complete(struct netfs_trans *t, int err)
+{
+ struct inode *inode = t->private;
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+
+ if (inode) {
+ if (err) {
+ mark_inode_dirty(inode);
+ clear_bit(NETFS_INODE_CREATED, &pi->state);
+ } else
+ set_bit(NETFS_INODE_CREATED, &pi->state);
+
+ pohmelfs_put_inode(pi);
+ }
+
+ return err;
+}
+
+/*
+ * Writeback for given inode.
+ */
+static int pohmelfs_write_inode(struct inode *inode, int sync)
+{
+ int err = 0;
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+
+ dprintk("%s: started ino: %llu.\n", __func__, pi->ino);
+
+ if (!test_bit(NETFS_INODE_CREATED, &pi->state)) {
+ struct netfs_trans *t;
+
+ t = netfs_trans_alloc_page(NETFS_TRANS_SYNC);
+ if (!t) {
+ err = -ENOMEM;
+ goto out;
+ }
+ t->complete = pohmelfs_write_inode_complete;
+ t->private = igrab(inode);
+ if (!t->private) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ err = pohmelfs_write_inode_create(inode, t);
+ if (err)
+ goto out;
+
+ err = netfs_trans_finish(t, POHMELFS_SB(inode->i_sb));
+ if (err)
+ goto out;
+
+out:
+ if (err)
+ netfs_trans_exit(t, err);
+ }
+
+ pohmelfs_write_inode_remove_children(inode);
+
+ return err;
+}
+
+/*
+ * It is not exported, sorry...
+ */
+static inline wait_queue_head_t *page_waitqueue(struct page *page)
+{
+ const struct zone *zone = page_zone(page);
+
+ return &zone->wait_table[hash_ptr(page, zone->wait_table_bits)];
+}
+
+static int pohmelfs_readpage_complete(struct netfs_trans *t, int err)
+{
+ struct page *page = t->private;
+
+ if (err)
+ SetPageError(page);
+
+ page_cache_release(page);
+ unlock_page(page);
+
+ return err;
+}
+
+/*
+ * Read/write page request to remote server.
+ * If @wait is set and page is locked, it will wait until page is unlocked.
+ */
+static int netfs_process_page(struct page *page, __u32 cmd_op, __u32 size, int wait)
+{
+ struct inode *inode = page->mapping->host;
+ struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+ struct netfs_trans *t;
+ struct netfs_cmd *cmd;
+ int err, path_size;
+ unsigned int cur_len;
+ void *data;
+
+ if (unlikely(!size)) {
+ SetPageUptodate(page);
+ unlock_page(page);
+ return 0;
+ }
+
+#if 0
+ {
+ SetPageUptodate(page);
+ unlock_page(page);
+ return 0;
+ }
+#endif
+
+ t = netfs_trans_alloc_page(NETFS_TRANS_SYNC);
+ if (!t) {
+ err = -ENOMEM;
+ goto err_out_exit;
+ }
+ cur_len = t->data_size - netfs_trans_cur_len(t);
+ t->complete = pohmelfs_readpage_complete;
+ t->private = page;
+ page_cache_get(page);
+
+ cmd = netfs_trans_add(t, cur_len);
+ if (IS_ERR(cmd)) {
+ err = PTR_ERR(cmd);
+ goto err_out_free;
+ }
+ data = (void *)(cmd + 1);
+ cur_len -= sizeof(struct netfs_cmd);
+
+ mutex_lock(&psb->path_lock);
+ err = pohmelfs_construct_path_string(pi, data, cur_len);
+ mutex_unlock(&psb->path_lock);
+ if (err < 0)
+ goto err_out_free;
+
+ path_size = err + 1;
+
+ cmd->id = pi->ino;
+ cmd->start = page->index << PAGE_CACHE_SHIFT;
+ cmd->size = size + path_size;
+ cmd->cmd = cmd_op;
+ cmd->ext = path_size;
+
+ dprintk("%s: path: '%s', page: %p, ino: %llu, start: %llu, idx: %lu, cmd: %u, size: %u.\n",
+ __func__, (char *)data, page, pi->ino, cmd->start, page->index, cmd_op, size);
+
+ netfs_convert_cmd(cmd);
+
+ netfs_trans_fixup_last(t, path_size - cur_len);
+
+ err = netfs_trans_finish(t, psb);
+ if (err)
+ goto err_out_free;
+
+ err = 0;
+ if (wait && TestSetPageLocked(page)) {
+ long ret = msecs_to_jiffies(5000);
+ DEFINE_WAIT_BIT(wait, &page->flags, PG_locked);
+
+ for (;;) {
+ prepare_to_wait(page_waitqueue(page), &wait.wait, TASK_INTERRUPTIBLE);
+
+ dprintk("%s: page: %p, locked: %d, uptodate: %d, error: %d.\n",
+ __func__, page, PageLocked(page), PageUptodate(page),
+ PageError(page));
+
+ if (!PageLocked(page))
+ break;
+
+ if (!signal_pending(current)) {
+ ret = schedule_timeout(ret);
+ if (!ret)
+ break;
+ continue;
+ }
+ ret = -ERESTARTSYS;
+ break;
+ }
+ finish_wait(page_waitqueue(page), &wait.wait);
+
+ if (!ret)
+ err = -ETIMEDOUT;
+
+ dprintk("%s: page: %p, uptodate: %d, locked: %d, err: %d.\n",
+ __func__, page, PageUptodate(page), PageLocked(page), err);
+
+ if (!PageUptodate(page))
+ err = -EIO;
+
+ if (PageLocked(page))
+ unlock_page(page);
+ }
+
+ return err;
+
+err_out_free:
+ netfs_trans_exit(t, err);
+err_out_exit:
+ SetPageError(page);
+ if (PageLocked(page))
+ unlock_page(page);
+
+ printk("%s: page: %p, start: %lu, size: %u, err: %d.\n",
+ __func__, page, page->index << PAGE_CACHE_SHIFT, size, err);
+
+ return err;
+}
+
+static int pohmelfs_readpage(struct file *file, struct page *page)
+{
+ ClearPageChecked(page);
+ return netfs_process_page(page, NETFS_READ_PAGE, PAGE_CACHE_SIZE, 1);
+}
+
+/*
+ * Write begin/end magic.
+ * Allocates a page and writes inode if it was not synced to server before.
+ */
+static int pohmelfs_write_begin(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned flags,
+ struct page **pagep, void **fsdata)
+{
+ struct inode *inode = mapping->host;
+ struct page *page;
+ pgoff_t index;
+ unsigned start, end;
+ int err;
+
+ *pagep = NULL;
+
+ index = pos >> PAGE_CACHE_SHIFT;
+ start = pos & (PAGE_CACHE_SIZE - 1);
+ end = start + len;
+
+ page = __grab_cache_page(mapping, index);
+
+ dprintk("%s: page: %p pos: %llu, len: %u, index: %lu, start: %u, end: %u, uptodate: %d.\n",
+ __func__, page, pos, len, index, start, end, PageUptodate(page));
+
+ if (!page) {
+ err = -ENOMEM;
+ goto err_out_exit;
+ }
+
+ while (!PageUptodate(page)) {
+ if (start && test_bit(NETFS_INODE_CREATED, &POHMELFS_I(inode)->state)) {
+ err = pohmelfs_readpage(file, page);
+ if (err)
+ goto err_out_exit;
+
+ lock_page(page);
+ continue;
+ }
+
+ if (len != PAGE_CACHE_SIZE) {
+ void *kaddr = kmap_atomic(page, KM_USER0);
+
+ memset(kaddr + start, 0, PAGE_CACHE_SIZE - start);
+ flush_dcache_page(page);
+ kunmap_atomic(kaddr, KM_USER0);
+ }
+ SetPageUptodate(page);
+ }
+
+ set_page_private(page, end);
+
+ *pagep = page;
+
+ return 0;
+
+err_out_exit:
+ page_cache_release(page);
+ *pagep = NULL;
+
+ return err;
+}
+
+static int pohmelfs_write_end(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned copied,
+ struct page *page, void *fsdata)
+{
+ struct inode *inode = mapping->host;
+
+ if (copied != len) {
+ unsigned from = pos & (PAGE_CACHE_SIZE - 1);
+ void *kaddr = kmap_atomic(page, KM_USER0);
+
+ memset(kaddr + from + copied, 0, len - copied);
+ flush_dcache_page(page);
+ kunmap_atomic(kaddr, KM_USER0);
+ }
+
+ SetPageUptodate(page);
+ set_page_dirty(page);
+
+ dprintk("%s: page: %p [U: %d, D: %dd, L: %d], pos: %llu, len: %u, copied: %u.\n",
+ __func__, page,
+ PageUptodate(page), PageDirty(page), PageLocked(page),
+ pos, len, copied);
+
+ flush_dcache_page(page);
+
+ unlock_page(page);
+ page_cache_release(page);
+
+ if (pos + copied > inode->i_size) {
+ i_size_write(inode, pos + copied);
+
+ if (test_bit(NETFS_INODE_CREATED, &POHMELFS_I(inode)->state)) {
+ int err = pohmelfs_meta_command(POHMELFS_I(inode), NETFS_INODE_INFO,
+ 0, NULL, NULL, 0);
+ if (err)
+ return err;
+ }
+ }
+
+ return copied;
+}
+
+/*
+ * Small addres space operations for POHMELFS.
+ */
+const struct address_space_operations pohmelfs_aops = {
+ .readpage = pohmelfs_readpage,
+ .writepages = pohmelfs_writepages,
+ .write_begin = pohmelfs_write_begin,
+ .write_end = pohmelfs_write_end,
+ .set_page_dirty = __set_page_dirty_nobuffers,
+};
+
+static atomic_t inodes_allocated = ATOMIC_INIT(0);
+static atomic_t inodes_destroyed = ATOMIC_INIT(0);
+
+/*
+ * ->detroy_inode() callback. Deletes inode from the caches
+ * and frees private data.
+ */
+static void pohmelfs_destroy_inode(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct pohmelfs_sb *psb = POHMELFS_SB(sb);
+ struct pohmelfs_inode *pi = POHMELFS_I(inode);
+
+ pohmelfs_inode_del_inode(psb, pi);
+#ifdef POHMELFS_CC_GROUP
+ pohmelfs_meta_command(pi, NETFS_LEAVE_GROUP, 0, NULL, NULL, 0);
+#endif
+
+ dprintk("%s: pi: %p, inode: %p, ino: %llu.\n",
+ __func__, pi, &pi->vfs_inode, pi->ino);
+ kmem_cache_free(pohmelfs_inode_cache, pi);
+ atomic_inc(&inodes_destroyed);
+}
+
+/*
+ * ->alloc_inode() callback. Allocates inode and initilizes private data.
+ */
+static struct inode *pohmelfs_alloc_inode(struct super_block *sb)
+{
+ struct pohmelfs_inode *pi;
+
+ pi = kmem_cache_alloc(pohmelfs_inode_cache, GFP_NOIO);
+ if (!pi)
+ return NULL;
+
+ pi->offset_root = RB_ROOT;
+ pi->hash_root = RB_ROOT;
+ mutex_init(&pi->offset_lock);
+
+ INIT_LIST_HEAD(&pi->sync_del_list);
+ INIT_LIST_HEAD(&pi->sync_create_list);
+
+ INIT_LIST_HEAD(&pi->inode_entry);
+
+ pi->state = 0;
+ pi->total_len = 0;
+ pi->drop_count = 0;
+
+ dprintk("%s: pi: %p, inode: %p.\n", __func__, pi, &pi->vfs_inode);
+
+ atomic_inc(&inodes_allocated);
+
+ return &pi->vfs_inode;
+}
+
+/*
+ * Here starts async POHMELFS reading magic.
+ * It is pretty trivial though.
+ * This actor just copies data to userspace.
+ */
+static int pohmelfs_file_read_actor(char __user *buf, struct page *page,
+ unsigned long offset, unsigned long size)
+{
+ char *kaddr;
+ unsigned long left;
+ int error, num = 10;
+
+ do {
+ error = 0;
+ /*
+ * Faults on the destination of a read are common, so do it before
+ * taking the kmap.
+ */
+ if (!fault_in_pages_writeable(buf, size)) {
+ kaddr = kmap_atomic(page, KM_USER0);
+ left = __copy_to_user_inatomic(buf, kaddr + offset, size);
+ kunmap_atomic(kaddr, KM_USER0);
+ if (left == 0)
+ break;
+ }
+
+ /* Do it the slow way */
+ kaddr = kmap(page);
+ left = __copy_to_user(buf, kaddr + offset, size);
+ kunmap(page);
+
+ if (left)
+ error = -EFAULT;
+
+ dprintk("%s: page: %p, buf: %p, size: %lu, left: %lu, num: %d, err: %d.\n",
+ __func__, page, buf, size, left, num, error);
+
+ offset += size - left;
+ buf += size - left;
+ size = left;
+ } while (size && --num);
+
+ dprintk("%s: completed: page: %p, size: %lu, left: %lu, err: %d.\n",
+ __func__, page, size, left, error);
+
+ return error;
+}
+
+/*
+ * When page is not uptodate, it is queued to be completed when data is received from
+ * remote server. This shared info sructure holds that pages. When all pages are
+ * processed it has to be freed, which is done here.
+ */
+void pohmelfs_put_shared_info(struct pohmelfs_shared_info *sh)
+{
+ dprintk("%s: completed: %d, scheduled: %d.\n",
+ __func__, atomic_read(&sh->pages_completed), sh->pages_scheduled);
+
+ if (atomic_inc_return(&sh->pages_completed) == sh->pages_scheduled) {
+ dprintk("%s: freeing shared info.\n", __func__);
+
+ BUG_ON(!list_empty(&sh->page_list));
+ kfree(sh);
+ }
+}
+
+/*
+ * Simple async reading magic.
+ * If page is uptodate, it is copied to userspace, otherwise request is being sent
+ * to the server. This is done for all pages.
+ *
+ * When requests are received by async thread, this (now sync) thread awakes (at the very
+ * end) and copies data to userspace. There is a work in progress for async copy from
+ * receiving thread to 'our' userspace via copy_to_user(), so far it does not work
+ * reliably.
+ */
+static void pohmelfs_file_read(struct file *file, loff_t *ppos,
+ read_descriptor_t *desc)
+{
+ struct address_space *mapping = file->f_mapping;
+ struct inode *inode = mapping->host;
+ struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
+ pgoff_t index;
+ unsigned long offset; /* offset into pagecache page */
+ int err;
+ struct pohmelfs_shared_info *sh = NULL;
+ unsigned long nr = PAGE_CACHE_SIZE;
+
+ index = *ppos >> PAGE_CACHE_SHIFT;
+ offset = *ppos & ~PAGE_CACHE_MASK;
+
+ while (desc->count && nr == PAGE_CACHE_SIZE) {
+ struct page *page;
+ pgoff_t end_index;
+ loff_t isize;
+
+ nr = PAGE_CACHE_SIZE;
+
+ dprintk("%s: index: %lu, count: %zu, written: %zu.\n", __func__, index, desc->count, desc->written);
+
+ isize = i_size_read(inode);
+ end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
+ if (unlikely(!isize || index > end_index))
+ break;
+
+ /* nr is the maximum number of bytes to copy from this page */
+ if (index == end_index) {
+ nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
+ if (nr <= offset)
+ break;
+ }
+ nr = nr - offset;
+
+repeat:
+ page = find_get_page(mapping, index);
+ if (!page) {
+ page = page_cache_alloc_cold(mapping);
+ if (!page) {
+ desc->error = -ENOMEM;
+ break;
+ }
+
+ err = add_to_page_cache(page, mapping, index, GFP_NOIO);
+ if (unlikely(err)) {
+ page_cache_release(page);
+ if (err == -EEXIST)
+ goto repeat;
+ desc->error = err;
+ break;
+ }
+ //lru_cache_add(page);
+
+ goto readpage;
+ }
+
+ dprintk("%s: file: %p, page: %p [U: %d, L: %d], buf: %p, offset: %lu, index: %lu, nr: %lu, count: %zu, written: %zu.\n",
+ __func__, file, page, PageUptodate(page), PageLocked(page), desc->arg.buf,
+ offset, index, nr, desc->count, desc->written);
+
+ if (PageUptodate(page)) {
+page_ok:
+ /* If users can be writing to this page using arbitrary
+ * virtual addresses, take care about potential aliasing
+ * before reading the page on the kernel side.
+ */
+ if (mapping_writably_mapped(mapping))
+ flush_dcache_page(page);
+
+ mark_page_accessed(page);
+
+ /*
+ * Ok, we have the page, and it's up-to-date, so
+ * now we can copy it to user space...
+ */
+ err = pohmelfs_file_read_actor(desc->arg.buf, page, offset, nr);
+ page_cache_release(page);
+ if (err) {
+ desc->error = err;
+ break;
+ }
+ } else {
+ struct pohmelfs_page_private *priv;
+
+#if 0
+ /*
+ * Waiting for __lock_page_killable to be exported.
+ */
+ if (lock_page_killable(page)) {
+ err = -EIO;
+ goto readpage_error;
+ }
+#else
+ lock_page(page);
+#endif
+ if (PageUptodate(page)) {
+ unlock_page(page);
+ goto page_ok;
+ }
+
+ if (!page->mapping) {
+ unlock_page(page);
+ page_cache_release(page);
+ break;
+ }
+
+readpage:
+ if (unlikely(!sh)) {
+ sh = kzalloc(sizeof(struct pohmelfs_shared_info), GFP_NOFS);
+ if (!sh) {
+ desc->error = -ENOMEM;
+ page_cache_release(page);
+ break;
+ }
+ sh->pages_scheduled = 1;
+ atomic_set(&sh->pages_completed, 0);
+ INIT_LIST_HEAD(&sh->page_list);
+ mutex_init(&sh->page_lock);
+ }
+
+ priv = kmalloc(sizeof(struct pohmelfs_page_private), GFP_NOFS);
+ if (!priv) {
+ desc->error = -ENOMEM;
+ page_cache_release(page);
+ break;
+ }
+
+ priv->buf = desc->arg.buf;
+ priv->offset = offset;
+ priv->nr = nr;
+ priv->shared = sh;
+ priv->private = page_private(page);
+ priv->page = page;
+
+ set_page_private(page, (unsigned long)priv);
+ SetPageChecked(page);
+
+ sh->pages_scheduled++;
+ err = netfs_process_page(page, NETFS_READ_PAGE, nr, 0);
+ if (unlikely(err)) {
+ desc->error = err;
+ sh->pages_scheduled--;
+ page_cache_release(page);
+ break;
+ }
+
+ dprintk("%s: page: %p, completed: %d, scheduled: %d.\n",
+ __func__, page, atomic_read(&sh->pages_completed), sh->pages_scheduled);
+ }
+
+ desc->count -= nr;
+ desc->written += nr;
+ desc->arg.buf += nr;
+
+ offset += nr;
+ index += offset >> PAGE_CACHE_SHIFT;
+ offset &= ~PAGE_CACHE_MASK;
+
+ dprintk("%s: count: %zu, written: %zu, nr: %lu.\n", __func__, desc->count, desc->written, nr);
+ }
+
+ *ppos = ((loff_t)index << PAGE_CACHE_SHIFT) + offset;
+ if (file)
+ file_accessed(file);
+
+ if (sh) {
+ struct pohmelfs_page_private *p;
+
+ dprintk("%s: completed: %d, scheduled: %d.\n",
+ __func__, atomic_read(&sh->pages_completed), sh->pages_scheduled);
+
+ while (!sh->freeing) {
+ wait_event_interruptible(psb->wait,
+ (atomic_read(&sh->pages_completed) == sh->pages_scheduled - 1) ||
+ !list_empty(&sh->page_list));
+
+ dprintk("%s: completed: %d, scheduled: %d, signal: %d.\n",
+ __func__, atomic_read(&sh->pages_completed), sh->pages_scheduled, signal_pending(current));
+
+ if (signal_pending(current)) {
+ mutex_lock(&sh->page_lock);
+ sh->freeing = 1;
+ mutex_unlock(&sh->page_lock);
+ }
+
+ while (!list_empty(&sh->page_list)) {
+ mutex_lock(&sh->page_lock);
+ p = list_entry(sh->page_list.next, struct pohmelfs_page_private,
+ page_entry);
+ list_del(&p->page_entry);
+ mutex_unlock(&sh->page_lock);
+
+ err = pohmelfs_file_read_actor(p->buf, p->page, p->offset, p->nr);
+
+ if (err)
+ SetPageError(p->page);
+ else
+ SetPageUptodate(p->page);
+
+ kfree(p);
+ }
+
+ if (atomic_read(&sh->pages_completed) == sh->pages_scheduled - 1)
+ sh->freeing = 1;
+ }
+
+ pohmelfs_put_shared_info(sh);
+ }
+}
+
+/*
+ * ->aio_read() callback. Just runs over segments and tries to read data.
+ */
+static ssize_t pohmelfs_aio_read(struct kiocb *iocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t pos)
+{
+ struct file *file = iocb->ki_filp;
+ ssize_t retval;
+ unsigned long seg;
+ size_t count;
+ loff_t *ppos = &iocb->ki_pos;
+
+ count = 0;
+ retval = generic_segment_checks(iov, &nr_segs, &count, VERIFY_WRITE);
+ if (retval)
+ return retval;
+
+ dprintk("%s: nr_segs: %lu, count: %zu.\n", __func__, nr_segs, count);
+ retval = 0;
+ if (count) {
+ for (seg = 0; seg < nr_segs; seg++) {
+ read_descriptor_t desc;
+
+ desc.written =