Staging: pohmelfs: network operations.
authorEvgeniy Polyakov <zbr@ioremap.net>
Mon, 9 Feb 2009 14:02:40 +0000 (17:02 +0300)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 3 Apr 2009 21:53:35 +0000 (14:53 -0700)
This is a main network processing patch. It includes
both low-level socket machinery, zero-copy sending helpers,
receiving and parsing callbacks and mainly logical
commands handlers.

POHMELFS uses async network approach, when every command
can be separated from its answer and received after some
time after the request during which another lots of commands
can be injected into the network and replies to them received.

With read operation balancing between multiple hosts it is possible
that operations will arrive out of order and this is handled
by the transaction mechanism described partially here.

Having a transaction to guard the set of logically compound operations
allows to send data without thinking about its status and using
zero-copy sending mechanism, since transaction will receive explicit
acks from the servers when they are completed.

This patch also contains header with network srtuctures, commands
and short comments on how they are used.

Signed-off-by: Evgeniy Polyakov <zbr@ioremap.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/staging/pohmelfs/net.c [new file with mode: 0644]
drivers/staging/pohmelfs/netfs.h [new file with mode: 0644]

diff --git a/drivers/staging/pohmelfs/net.c b/drivers/staging/pohmelfs/net.c
new file mode 100644 (file)
index 0000000..d161237
--- /dev/null
@@ -0,0 +1,1246 @@
+/*
+ * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
+ * 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/fsnotify.h>
+#include <linux/jhash.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/kthread.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/swap.h>
+#include <linux/syscalls.h>
+
+#include "netfs.h"
+
+static int pohmelfs_ftrans_size = 10240;
+static u32 *pohmelfs_ftrans;
+
+int pohmelfs_ftrans_init(void)
+{
+       pohmelfs_ftrans = vmalloc(pohmelfs_ftrans_size * 4);
+       if (!pohmelfs_ftrans)
+               return -ENOMEM;
+
+       return 0;
+}
+
+void pohmelfs_ftrans_exit(void)
+{
+       vfree(pohmelfs_ftrans);
+}
+
+void pohmelfs_ftrans_clean(u64 id)
+{
+       if (pohmelfs_ftrans) {
+               u32 i = id & 0xffffffff;
+               int idx = i % pohmelfs_ftrans_size;
+
+               pohmelfs_ftrans[idx] = 0;
+       }
+}
+
+void pohmelfs_ftrans_update(u64 id)
+{
+       if (pohmelfs_ftrans) {
+               u32 i = id & 0xffffffff;
+               int idx = i % pohmelfs_ftrans_size;
+
+               pohmelfs_ftrans[idx] = i;
+       }
+}
+
+int pohmelfs_ftrans_check(u64 id)
+{
+       if (pohmelfs_ftrans) {
+               u32 i = id & 0xffffffff;
+               int idx = i % pohmelfs_ftrans_size;
+
+               return (pohmelfs_ftrans[idx] == i);
+       }
+
+       return -1;
+}
+
+/*
+ * Async machinery lives here.
+ * All commands being sent to server do _not_ require sync reply,
+ * instead, if it is really needed, like readdir or readpage, caller
+ * sleeps waiting for data, which will be placed into provided buffer
+ * and caller will be awakened.
+ *
+ * Every command response can come without some listener. For example
+ * readdir response will add new objects into cache without appropriate
+ * request from userspace. This is used in cache coherency.
+ *
+ * If object is not found for given data, it is discarded.
+ *
+ * All requests are received by dedicated kernel thread.
+ */
+
+/*
+ * Basic network sending/receiving functions.
+ * Blocked mode is used.
+ */
+static int netfs_data_recv(struct netfs_state *st, void *buf, u64 size)
+{
+       struct msghdr msg;
+       struct kvec iov;
+       int err;
+
+       BUG_ON(!size);
+
+       iov.iov_base = buf;
+       iov.iov_len = size;
+
+       msg.msg_iov = (struct iovec *)&iov;
+       msg.msg_iovlen = 1;
+       msg.msg_name = NULL;
+       msg.msg_namelen = 0;
+       msg.msg_control = NULL;
+       msg.msg_controllen = 0;
+       msg.msg_flags = MSG_DONTWAIT;
+
+       err = kernel_recvmsg(st->socket, &msg, &iov, 1, iov.iov_len,
+                       msg.msg_flags);
+       if (err <= 0) {
+               printk("%s: failed to recv data: size: %llu, err: %d.\n", __func__, size, err);
+               if (err == 0)
+                       err = -ECONNRESET;
+       }
+
+       return err;
+}
+
+static int pohmelfs_data_recv(struct netfs_state *st, void *data, unsigned int size)
+{
+       unsigned int revents = 0;
+       unsigned int err_mask = POLLERR | POLLHUP | POLLRDHUP;
+       unsigned int mask = err_mask | POLLIN;
+       int err = 0;
+
+       while (size && !err) {
+               revents = netfs_state_poll(st);
+
+               if (!(revents & mask)) {
+                       DEFINE_WAIT(wait);
+
+                       for (;;) {
+                               prepare_to_wait(&st->thread_wait, &wait, TASK_INTERRUPTIBLE);
+                               if (kthread_should_stop())
+                                       break;
+
+                               revents = netfs_state_poll(st);
+
+                               if (revents & mask)
+                                       break;
+
+                               if (signal_pending(current))
+                                       break;
+
+                               schedule();
+                               continue;
+                       }
+                       finish_wait(&st->thread_wait, &wait);
+               }
+
+               err = 0;
+               netfs_state_lock(st);
+               if (st->socket && (st->read_socket == st->socket) && (revents & POLLIN)) {
+                       err = netfs_data_recv(st, data, size);
+                       if (err > 0) {
+                               data += err;
+                               size -= err;
+                               err = 0;
+                       } else if (err == 0)
+                               err = -ECONNRESET;
+               }
+
+               if (revents & err_mask) {
+                       printk("%s: revents: %x, socket: %p, size: %u, err: %d.\n",
+                                       __func__, revents, st->socket, size, err);
+                       err = -ECONNRESET;
+               }
+               netfs_state_unlock(st);
+
+               if (err < 0) {
+                       if (netfs_state_trylock_send(st)) {
+                               netfs_state_exit(st);
+                               err = netfs_state_init(st);
+                               if (!err)
+                                       err = -EAGAIN;
+                               netfs_state_unlock_send(st);
+                       } else {
+                               st->need_reset = 1;
+                       }
+               }
+
+               if (kthread_should_stop())
+                       err = -ENODEV;
+
+               if (err)
+                       printk("%s: socket: %p, read_socket: %p, revents: %x, rev_error: %d, "
+                                       "should_stop: %d, size: %u, err: %d.\n",
+                               __func__, st->socket, st->read_socket,
+                               revents, revents & err_mask, kthread_should_stop(), size, err);
+       }
+
+       return err;
+}
+
+int pohmelfs_data_recv_and_check(struct netfs_state *st, void *data, unsigned int size)
+{
+       struct netfs_cmd *cmd = &st->cmd;
+       int err;
+
+       err = pohmelfs_data_recv(st, data, size);
+       if (err)
+               return err;
+
+       return pohmelfs_crypto_process_input_data(&st->eng, cmd->iv, data, NULL, size);
+}
+
+/*
+ * Polling machinery.
+ */
+
+struct netfs_poll_helper
+{
+       poll_table              pt;
+       struct netfs_state      *st;
+};
+
+static int netfs_queue_wake(wait_queue_t *wait, unsigned mode, int sync, void *key)
+{
+       struct netfs_state *st = container_of(wait, struct netfs_state, wait);
+
+       wake_up(&st->thread_wait);
+       return 1;
+}
+
+static void netfs_queue_func(struct file *file, wait_queue_head_t *whead,
+                                poll_table *pt)
+{
+       struct netfs_state *st = container_of(pt, struct netfs_poll_helper, pt)->st;
+
+       st->whead = whead;
+       init_waitqueue_func_entry(&st->wait, netfs_queue_wake);
+       add_wait_queue(whead, &st->wait);
+}
+
+static void netfs_poll_exit(struct netfs_state *st)
+{
+       if (st->whead) {
+               remove_wait_queue(st->whead, &st->wait);
+               st->whead = NULL;
+       }
+}
+
+static int netfs_poll_init(struct netfs_state *st)
+{
+       struct netfs_poll_helper ph;
+
+       ph.st = st;
+       init_poll_funcptr(&ph.pt, &netfs_queue_func);
+
+       st->socket->ops->poll(NULL, st->socket, &ph.pt);
+       return 0;
+}
+
+/*
+ * Get response for readpage command. We search inode and page in its mapping
+ * and copy data into. If it was async request, then we queue page into shared
+ * data and wakeup listener, who will copy it to userspace.
+ *
+ * There is a work in progress of allowing to call copy_to_user() directly from
+ * async receiving kernel thread.
+ */
+static int pohmelfs_read_page_response(struct netfs_state *st)
+{
+       struct pohmelfs_sb *psb = st->psb;
+       struct netfs_cmd *cmd = &st->cmd;
+       struct inode *inode;
+       struct page *page;
+       int err = 0;
+
+       if (cmd->size > PAGE_CACHE_SIZE) {
+               err = -EINVAL;
+               goto err_out_exit;
+       }
+
+       inode = ilookup(st->psb->sb, cmd->id);
+       if (!inode) {
+               printk("%s: failed to find inode: id: %llu.\n", __func__, cmd->id);
+               err = -ENOENT;
+               goto err_out_exit;
+       }
+
+       page = find_get_page(inode->i_mapping, cmd->start >> PAGE_CACHE_SHIFT);
+       if (!page || !PageLocked(page)) {
+               printk("%s: failed to find/lock page: page: %p, id: %llu, start: %llu, index: %llu.\n",
+                               __func__, page, cmd->id, cmd->start, cmd->start >> PAGE_CACHE_SHIFT);
+
+               while (cmd->size) {
+                       unsigned int sz = min(cmd->size, st->size);
+
+                       err = pohmelfs_data_recv(st, st->data, sz);
+                       if (err)
+                               break;
+
+                       cmd->size -= sz;
+               }
+
+               err = -ENODEV;
+               if (page)
+                       goto err_out_page_put;
+               goto err_out_put;
+       }
+
+       if (cmd->size) {
+               void *addr;
+
+               addr = kmap(page);
+               err = pohmelfs_data_recv(st, addr, cmd->size);
+               kunmap(page);
+
+               if (err)
+                       goto err_out_page_unlock;
+       }
+
+       dprintk("%s: page: %p, start: %llu, size: %u, locked: %d.\n",
+               __func__, page, cmd->start, cmd->size, PageLocked(page));
+
+       SetPageChecked(page);
+       if ((psb->hash_string || psb->cipher_string) && psb->perform_crypto && cmd->size) {
+               err = pohmelfs_crypto_process_input_page(&st->eng, page, cmd->size, cmd->iv);
+               if (err < 0)
+                       goto err_out_page_unlock;
+       } else {
+               SetPageUptodate(page);
+               unlock_page(page);
+               page_cache_release(page);
+       }
+
+       pohmelfs_put_inode(POHMELFS_I(inode));
+       wake_up(&st->psb->wait);
+
+       return 0;
+
+err_out_page_unlock:
+       SetPageError(page);
+       unlock_page(page);
+err_out_page_put:
+       page_cache_release(page);
+err_out_put:
+       pohmelfs_put_inode(POHMELFS_I(inode));
+err_out_exit:
+       wake_up(&st->psb->wait);
+       return err;
+}
+
+static int pohmelfs_check_name(struct pohmelfs_inode *parent, struct qstr *str,
+               struct netfs_inode_info *info)
+{
+       struct inode *inode;
+       struct pohmelfs_name *n;
+       int err = 0;
+       u64 ino = 0;
+
+       mutex_lock(&parent->offset_lock);
+       n = pohmelfs_search_hash(parent, str->hash);
+       if (n)
+               ino = n->ino;
+       mutex_unlock(&parent->offset_lock);
+
+       if (!ino)
+               goto out;
+
+       inode = ilookup(parent->vfs_inode.i_sb, ino);
+       if (!inode)
+               goto out;
+
+       dprintk("%s: parent: %llu, inode: %llu.\n", __func__, parent->ino, ino);
+
+       pohmelfs_fill_inode(inode, info);
+       pohmelfs_put_inode(POHMELFS_I(inode));
+       err = -EEXIST;
+out:
+       return err;
+}
+
+/*
+ * Readdir response from server. If special field is set, we wakeup
+ * listener (readdir() call), which will copy data to userspace.
+ */
+static int pohmelfs_readdir_response(struct netfs_state *st)
+{
+       struct inode *inode;
+       struct netfs_cmd *cmd = &st->cmd;
+       struct netfs_inode_info *info;
+       struct pohmelfs_inode *parent = NULL, *npi;
+       int err = 0, last = cmd->ext;
+       struct qstr str;
+
+       if (cmd->size > st->size)
+               return -EINVAL;
+
+       inode = ilookup(st->psb->sb, cmd->id);
+       if (!inode) {
+               printk("%s: failed to find inode: id: %llu.\n", __func__, cmd->id);
+               return -ENOENT;
+       }
+       parent = POHMELFS_I(inode);
+
+       if (!cmd->size && cmd->start) {
+               err = -cmd->start;
+               goto out;
+       }
+
+       if (cmd->size) {
+               char *name;
+
+               err = pohmelfs_data_recv_and_check(st, st->data, cmd->size);
+               if (err)
+                       goto err_out_put;
+
+               info = (struct netfs_inode_info *)(st->data);
+
+               name = (char *)(info + 1);
+               str.len = cmd->size - sizeof(struct netfs_inode_info) - 1 - cmd->cpad;
+               name[str.len] = 0;
+               str.name = name;
+               str.hash = jhash(str.name, str.len, 0);
+
+               netfs_convert_inode_info(info);
+
+               if (parent) {
+                       err = pohmelfs_check_name(parent, &str, info);
+                       if (err) {
+                               if (err == -EEXIST)
+                                       err = 0;
+                               goto out;
+                       }
+               }
+
+               info->ino = cmd->start;
+               if (!info->ino)
+                       info->ino = pohmelfs_new_ino(st->psb);
+
+               dprintk("%s: parent: %llu, ino: %llu, name: '%s', hash: %x, len: %u, mode: %o.\n",
+                               __func__, parent->ino, info->ino, str.name, str.hash, str.len,
+                               info->mode);
+
+               npi = pohmelfs_new_inode(st->psb, parent, &str, info, 0);
+               if (IS_ERR(npi)) {
+                       err = PTR_ERR(npi);
+
+                       if (err != -EEXIST)
+                               goto err_out_put;
+               } else {
+                       set_bit(NETFS_INODE_REMOTE_SYNCED, &npi->state);
+                       clear_bit(NETFS_INODE_OWNED, &npi->state);
+               }
+       }
+out:
+       if (last) {
+               set_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &parent->state);
+               set_bit(NETFS_INODE_REMOTE_SYNCED, &parent->state);
+               wake_up(&st->psb->wait);
+       }
+       pohmelfs_put_inode(parent);
+
+       return err;
+
+err_out_put:
+       clear_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &parent->state);
+       printk("%s: parent: %llu, ino: %llu, cmd_id: %llu.\n", __func__, parent->ino, cmd->start, cmd->id);
+       pohmelfs_put_inode(parent);
+       wake_up(&st->psb->wait);
+       return err;
+}
+
+/*
+ * Lookup command response.
+ * It searches for inode to be looked at (if it exists) and substitutes
+ * its inode information (size, permission, mode and so on), if inode does
+ * not exist, new one will be created and inserted into caches.
+ */
+static int pohmelfs_lookup_response(struct netfs_state *st)
+{
+       struct inode *inode = NULL;
+       struct netfs_cmd *cmd = &st->cmd;
+       struct netfs_inode_info *info;
+       struct pohmelfs_inode *parent = NULL, *npi;
+       int err = -EINVAL;
+       char *name;
+
+       inode = ilookup(st->psb->sb, cmd->id);
+       if (!inode) {
+               printk("%s: lookup response: id: %llu, start: %llu, size: %u.\n",
+                               __func__, cmd->id, cmd->start, cmd->size);
+               err = -ENOENT;
+               goto err_out_exit;
+       }
+       parent = POHMELFS_I(inode);
+
+       if (!cmd->size) {
+               err = -cmd->start;
+               goto err_out_put;
+       }
+
+       if (cmd->size < sizeof(struct netfs_inode_info)) {
+               printk("%s: broken lookup response: id: %llu, start: %llu, size: %u.\n",
+                               __func__, cmd->id, cmd->start, cmd->size);
+               err = -EINVAL;
+               goto err_out_put;
+       }
+
+       err = pohmelfs_data_recv_and_check(st, st->data, cmd->size);
+       if (err)
+               goto err_out_put;
+
+       info = (struct netfs_inode_info *)(st->data);
+       name = (char *)(info + 1);
+
+       netfs_convert_inode_info(info);
+
+       info->ino = cmd->start;
+       if (!info->ino)
+               info->ino = pohmelfs_new_ino(st->psb);
+
+       dprintk("%s: parent: %llu, ino: %llu, name: '%s', start: %llu.\n",
+                       __func__, parent->ino, info->ino, name, cmd->start);
+
+       if (cmd->start)
+               npi = pohmelfs_new_inode(st->psb, parent, NULL, info, 0);
+       else {
+               struct qstr str;
+
+               str.name = name;
+               str.len = cmd->size - sizeof(struct netfs_inode_info) - 1 - cmd->cpad;
+               str.hash = jhash(name, str.len, 0);
+
+               npi = pohmelfs_new_inode(st->psb, parent, &str, info, 0);
+       }
+       if (IS_ERR(npi)) {
+               err = PTR_ERR(npi);
+
+               if (err != -EEXIST)
+                       goto err_out_put;
+       } else {
+               set_bit(NETFS_INODE_REMOTE_SYNCED, &npi->state);
+               clear_bit(NETFS_INODE_OWNED, &npi->state);
+       }
+
+       clear_bit(NETFS_COMMAND_PENDING, &parent->state);
+       pohmelfs_put_inode(parent);
+
+       wake_up(&st->psb->wait);
+
+       return 0;
+
+err_out_put:
+       pohmelfs_put_inode(parent);
+err_out_exit:
+       clear_bit(NETFS_COMMAND_PENDING, &parent->state);
+       wake_up(&st->psb->wait);
+       printk("%s: inode: %p, id: %llu, start: %llu, size: %u, err: %d.\n",
+                       __func__, inode, cmd->id, cmd->start, cmd->size, err);
+       return err;
+}
+
+/*
+ * Create response, just marks local inode as 'created', so that writeback
+ * for any of its children (or own) would not try to sync it again.
+ */
+static int pohmelfs_create_response(struct netfs_state *st)
+{
+       struct inode *inode;
+       struct netfs_cmd *cmd = &st->cmd;
+       struct pohmelfs_inode *pi;
+
+       inode = ilookup(st->psb->sb, cmd->id);
+       if (!inode) {
+               printk("%s: failed to find inode: id: %llu, start: %llu.\n",
+                               __func__, cmd->id, cmd->start);
+               goto err_out_exit;
+       }
+
+       pi = POHMELFS_I(inode);
+
+       /*
+        * To lock or not to lock?
+        * We actually do not care if it races...
+        */
+       if (cmd->start)
+               make_bad_inode(inode);
+       set_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state);
+
+       pohmelfs_put_inode(pi);
+
+       wake_up(&st->psb->wait);
+       return 0;
+
+err_out_exit:
+       wake_up(&st->psb->wait);
+       return -ENOENT;
+}
+
+/*
+ * Object remove response. Just says that remove request has been received.
+ * Used in cache coherency protocol.
+ */
+static int pohmelfs_remove_response(struct netfs_state *st)
+{
+       struct netfs_cmd *cmd = &st->cmd;
+       int err;
+
+       err = pohmelfs_data_recv_and_check(st, st->data, cmd->size);
+       if (err)
+               return err;
+
+       dprintk("%s: parent: %llu, path: '%s'.\n", __func__, cmd->id, (char *)st->data);
+
+       return 0;
+}
+
+/*
+ * Transaction reply processing.
+ *
+ * Find transaction based on its generation number, bump its reference counter,
+ * so that none could free it under us, drop from the trees and lists and
+ * drop reference counter. When it hits zero (when all destinations replied
+ * and all timeout handled by async scanning code), completion will be called
+ * and transaction will be freed.
+ */
+static int pohmelfs_transaction_response(struct netfs_state *st)
+{
+       struct netfs_trans_dst *dst;
+       struct netfs_trans *t = NULL;
+       struct netfs_cmd *cmd = &st->cmd;
+       short err = (signed)cmd->ext;
+
+       mutex_lock(&st->trans_lock);
+       dst = netfs_trans_search(st, cmd->start);
+       if (dst) {
+               netfs_trans_remove_nolock(dst, st);
+               t = dst->trans;
+
+               pohmelfs_ftrans_update(cmd->start);
+       }
+       mutex_unlock(&st->trans_lock);
+
+       if (!t) {
+               int check = pohmelfs_ftrans_check(cmd->start);
+               printk("%s: failed to find transaction: start: %llu: id: %llu, size: %u, ext: %u, double: %d.\n",
+                               __func__, cmd->start, cmd->id, cmd->size, cmd->ext, check);
+               err = -EINVAL;
+               goto out;
+       }
+
+       t->result = err;
+       netfs_trans_drop_dst_nostate(dst);
+
+out:
+       wake_up(&st->psb->wait);
+       return err;
+}
+
+/*
+ * Inode metadata cache coherency message.
+ */
+static int pohmelfs_page_cache_response(struct netfs_state *st)
+{
+       struct netfs_cmd *cmd = &st->cmd;
+       struct inode *inode;
+
+       dprintk("%s: st: %p, id: %llu, start: %llu, size: %u.\n", __func__, st, cmd->id, cmd->start, cmd->size);
+
+       inode = ilookup(st->psb->sb, cmd->id);
+       if (!inode) {
+               printk("%s: failed to find inode: id: %llu.\n", __func__, cmd->id);
+               return -ENOENT;
+       }
+
+       set_bit(NETFS_INODE_NEED_FLUSH, &POHMELFS_I(inode)->state);
+       pohmelfs_put_inode(POHMELFS_I(inode));
+
+       return 0;
+}
+
+/*
+ * Root capabilities response: export statistics
+ * like used and available size, number of files and dirs,
+ * permissions.
+ */
+static int pohmelfs_root_cap_response(struct netfs_state *st)
+{
+       struct netfs_cmd *cmd = &st->cmd;
+       struct netfs_root_capabilities *cap;
+       struct pohmelfs_sb *psb = st->psb;
+
+       if (cmd->size != sizeof(struct netfs_root_capabilities)) {
+               psb->flags = EPROTO;
+               wake_up(&psb->wait);
+               return -EPROTO;
+       }
+
+       cap = st->data;
+
+       netfs_convert_root_capabilities(cap);
+
+       if (psb->total_size < cap->used + cap->avail)
+               psb->total_size = cap->used + cap->avail;
+       if (cap->avail)
+               psb->avail_size = cap->avail;
+       psb->state_flags = cap->flags;
+
+       if (psb->state_flags & POHMELFS_FLAGS_RO) {
+               psb->sb->s_flags |= MS_RDONLY;
+               printk(KERN_INFO "Mounting POHMELFS (%d) read-only.\n", psb->idx);
+       }
+
+       if (psb->state_flags & POHMELFS_FLAGS_XATTR)
+               printk(KERN_INFO "Mounting POHMELFS (%d) "
+                       "with extended attributes support.\n", psb->idx);
+
+       if (atomic_read(&psb->total_inodes) <= 1)
+               atomic_long_set(&psb->total_inodes, cap->nr_files);
+
+       dprintk("%s: total: %llu, avail: %llu, flags: %llx, inodes: %llu.\n",
+               __func__, psb->total_size, psb->avail_size, psb->state_flags, cap->nr_files);
+
+       psb->flags = 0;
+       wake_up(&psb->wait);
+       return 0;
+}
+
+/*
+ * Crypto capabilities of the server, where it says that
+ * it supports or does not requested hash/cipher algorithms.
+ */
+static int pohmelfs_crypto_cap_response(struct netfs_state *st)
+{
+       struct netfs_cmd *cmd = &st->cmd;
+       struct netfs_crypto_capabilities *cap;
+       struct pohmelfs_sb *psb = st->psb;
+       int err = 0;
+
+       if (cmd->size != sizeof(struct netfs_crypto_capabilities)) {
+               psb->flags = EPROTO;
+               wake_up(&psb->wait);
+               return -EPROTO;
+       }
+
+       cap = st->data;
+
+       dprintk("%s: cipher '%s': %s, hash: '%s': %s.\n",
+                       __func__,
+                       psb->cipher_string, (cap->cipher_strlen)?"SUPPORTED":"NOT SUPPORTED",
+                       psb->hash_string, (cap->hash_strlen)?"SUPPORTED":"NOT SUPPORTED");
+
+       if (!cap->hash_strlen) {
+               if (psb->hash_strlen && psb->crypto_fail_unsupported)
+                       err = -ENOTSUPP;
+               psb->hash_strlen = 0;
+               kfree(psb->hash_string);
+               psb->hash_string = NULL;
+       }
+
+       if (!cap->cipher_strlen) {
+               if (psb->cipher_strlen && psb->crypto_fail_unsupported)
+                       err = -ENOTSUPP;
+               psb->cipher_strlen = 0;
+               kfree(psb->cipher_string);
+               psb->cipher_string = NULL;
+       }
+
+       return err;
+}
+
+/*
+ * Capabilities handshake response.
+ */
+static int pohmelfs_capabilities_response(struct netfs_state *st)
+{
+       struct netfs_cmd *cmd = &st->cmd;
+       int err = 0;
+
+       err = pohmelfs_data_recv(st, st->data, cmd->size);
+       if (err)
+               return err;
+
+       switch (cmd->id) {
+               case POHMELFS_CRYPTO_CAPABILITIES:
+                       return pohmelfs_crypto_cap_response(st);
+               case POHMELFS_ROOT_CAPABILITIES:
+                       return pohmelfs_root_cap_response(st);
+               default:
+                       break;
+       }
+       return -EINVAL;
+}
+
+/*
+ * Receiving extended attribute.
+ * Does not work properly if received size is more than requested one,
+ * it should not happen with current request/reply model though.
+ */
+static int pohmelfs_getxattr_response(struct netfs_state *st)
+{
+       struct pohmelfs_sb *psb = st->psb;
+       struct netfs_cmd *cmd = &st->cmd;
+       struct pohmelfs_mcache *m;
+       short error = (signed short)cmd->ext, err;
+       unsigned int sz, total_size;
+
+       m = pohmelfs_mcache_search(psb, cmd->id);
+
+       dprintk("%s: id: %llu, gen: %llu, err: %d.\n",
+               __func__, cmd->id, (m)?m->gen:0, error);
+
+       if (!m) {
+               printk("%s: failed to find getxattr cache entry: id: %llu.\n", __func__, cmd->id);
+               return -ENOENT;
+       }
+
+       if (cmd->size) {
+               sz = min_t(unsigned int, cmd->size, m->size);
+               err = pohmelfs_data_recv_and_check(st, m->data, sz);
+               if (err) {
+                       error = err;
+                       goto out;
+               }
+
+               m->size = sz;
+               total_size = cmd->size - sz;
+
+               while (total_size) {
+                       sz = min(total_size, st->size);
+
+                       err = pohmelfs_data_recv_and_check(st, st->data, sz);
+                       if (err) {
+                               error = err;
+                               break;
+                       }
+
+                       total_size -= sz;
+               }
+       }
+
+out:
+       m->err = error;
+       complete(&m->complete);
+       pohmelfs_mcache_put(psb, m);
+
+       return error;
+}
+
+int pohmelfs_data_lock_response(struct netfs_state *st)
+{
+       struct pohmelfs_sb *psb = st->psb;
+       struct netfs_cmd *cmd = &st->cmd;
+       struct pohmelfs_mcache *m;
+       short err = (signed short)cmd->ext;
+       u64 id = cmd->id;
+
+       m = pohmelfs_mcache_search(psb, id);
+
+       dprintk("%s: id: %llu, gen: %llu, err: %d.\n",
+               __func__, cmd->id, (m)?m->gen:0, err);
+
+       if (!m) {
+               pohmelfs_data_recv(st, st->data, cmd->size);
+               printk("%s: failed to find data lock response: id: %llu.\n", __func__, cmd->id);
+               return -ENOENT;
+       }
+
+       if (cmd->size)
+               err = pohmelfs_data_recv_and_check(st, &m->info, cmd->size);
+
+       m->err = err;
+       complete(&m->complete);
+       pohmelfs_mcache_put(psb, m);
+
+       return err;
+}
+
+static void __inline__ netfs_state_reset(struct netfs_state *st)
+{
+       netfs_state_lock_send(st);
+       netfs_state_exit(st);
+       netfs_state_init(st);
+       netfs_state_unlock_send(st);
+}
+
+/*
+ * Main receiving function, called from dedicated kernel thread.
+ */
+static int pohmelfs_recv(void *data)
+{
+       int err = -EINTR;
+       struct netfs_state *st = data;
+       struct netfs_cmd *cmd = &st->cmd;
+
+       while (!kthread_should_stop()) {
+               /*
+                * If socket will be reset after this statement, then
+                * pohmelfs_data_recv() will just fail and loop will
+                * start again, so it can be done without any locks.
+                *
+                * st->read_socket is needed to prevents state machine
+                * breaking between this data reading and subsequent one
+                * in protocol specific functions during connection reset.
+                * In case of reset we have to read next command and do
+                * not expect data for old command to magically appear in
+                * new connection.
+                */
+               st->read_socket = st->socket;
+               err = pohmelfs_data_recv(st, cmd, sizeof(struct netfs_cmd));
+               if (err) {
+                       msleep(1000);
+                       continue;
+               }
+
+               netfs_convert_cmd(cmd);
+
+               dprintk("%s: cmd: %u, id: %llu, start: %llu, size: %u, "
+                               "ext: %u, csize: %u, cpad: %u.\n",
+                               __func__, cmd->cmd, cmd->id, cmd->start,
+                               cmd->size, cmd->ext, cmd->csize, cmd->cpad);
+
+               if (cmd->csize) {
+                       struct pohmelfs_crypto_engine *e = &st->eng;
+
+                       if (unlikely(cmd->csize > e->size/2)) {
+                               netfs_state_reset(st);
+                               continue;
+                       }
+
+                       if (e->hash && unlikely(cmd->csize != st->psb->crypto_attached_size)) {
+                               dprintk("%s: cmd: cmd: %u, id: %llu, start: %llu, size: %u, "
+                                               "csize: %u != digest size %u.\n",
+                                               __func__, cmd->cmd, cmd->id, cmd->start, cmd->size,
+                                               cmd->csize, st->psb->crypto_attached_size);
+                               netfs_state_reset(st);
+                               continue;
+                       }
+
+                       err = pohmelfs_data_recv(st, e->data, cmd->csize);
+                       if (err) {
+                               netfs_state_reset(st);
+                               continue;
+                       }
+
+#ifdef CONFIG_POHMELFS_DEBUG
+                       {
+                               unsigned int i;
+                               unsigned char *hash = e->data;
+
+                               dprintk("%s: received hash: ", __func__);
+                               for (i=0; i<cmd->csize; ++i) {
+                                       printk("%02x ", hash[i]);
+                               }
+                               printk("\n");
+                       }
+#endif
+                       cmd->size -= cmd->csize;
+               }
+
+               /*
+                * This should catch protocol breakage and random garbage instead of commands.
+                */
+               if (unlikely((cmd->size > st->size) && (cmd->cmd != NETFS_XATTR_GET))) {
+                       netfs_state_reset(st);
+                       continue;
+               }
+
+               switch (cmd->cmd) {
+                       case NETFS_READ_PAGE:
+                               err = pohmelfs_read_page_response(st);
+                               break;
+                       case NETFS_READDIR:
+                               err = pohmelfs_readdir_response(st);
+                               break;
+                       case NETFS_LOOKUP:
+                               err = pohmelfs_lookup_response(st);
+                               break;
+                       case NETFS_CREATE:
+                               err = pohmelfs_create_response(st);
+                               break;
+                       case NETFS_REMOVE:
+                               err = pohmelfs_remove_response(st);
+                               break;
+                       case NETFS_TRANS:
+                               err = pohmelfs_transaction_response(st);
+                               break;
+                       case NETFS_PAGE_CACHE:
+                               err = pohmelfs_page_cache_response(st);
+                               break;
+                       case NETFS_CAPABILITIES:
+                               err = pohmelfs_capabilities_response(st);
+                               break;
+                       case NETFS_LOCK:
+                               err = pohmelfs_data_lock_response(st);
+                               break;
+                       case NETFS_XATTR_GET:
+                               err = pohmelfs_getxattr_response(st);
+                               break;
+                       default:
+                               printk("%s: wrong cmd: %u, id: %llu, start: %llu, size: %u, ext: %u.\n",
+                                       __func__, cmd->cmd, cmd->id, cmd->start, cmd->size, cmd->ext);
+                               netfs_state_reset(st);
+                               break;
+               }
+       }
+
+       while (!kthread_should_stop())
+               schedule_timeout_uninterruptible(msecs_to_jiffies(10));
+
+       return err;
+}
+
+int netfs_state_init(struct netfs_state *st)
+{
+       int err;
+       struct pohmelfs_ctl *ctl = &st->ctl;
+
+       err = sock_create(ctl->addr.sa_family, ctl->type, ctl->proto, &st->socket);
+       if (err) {
+               printk("%s: failed to create a socket: family: %d, type: %d, proto: %d, err: %d.\n",
+                               __func__, ctl->addr.sa_family, ctl->type, ctl->proto, err);
+               goto err_out_exit;
+       }
+
+       st->socket->sk->sk_allocation = GFP_NOIO;
+       st->socket->sk->sk_sndtimeo = st->socket->sk->sk_rcvtimeo = msecs_to_jiffies(60000);
+
+       err = kernel_connect(st->socket, (struct sockaddr *)&ctl->addr, ctl->addrlen, 0);
+       if (err) {
+               printk("%s: failed to connect to server: idx: %u, err: %d.\n",
+                               __func__, st->psb->idx, err);
+               goto err_out_release;
+       }
+       st->socket->sk->sk_sndtimeo = st->socket->sk->sk_rcvtimeo = msecs_to_jiffies(60000);
+
+       err = netfs_poll_init(st);
+       if (err)
+               goto err_out_release;
+
+       if (st->socket->ops->family == AF_INET) {
+               struct sockaddr_in *sin = (struct sockaddr_in *)&ctl->addr;
+               printk(KERN_INFO "%s: (re)connected to peer %u.%u.%u.%u:%d.\n", __func__,
+                       NIPQUAD(sin->sin_addr.s_addr), ntohs(sin->sin_port));
+       } else if (st->socket->ops->family == AF_INET6) {
+               struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&ctl->addr;
+               printk(KERN_INFO "%s: (re)connected to peer "
+                       "%pi6:%d",
+                       __func__, &sin->sin6_addr, ntohs(sin->sin6_port));
+       }
+
+       return 0;
+
+err_out_release:
+       sock_release(st->socket);
+err_out_exit:
+       st->socket = NULL;
+       return err;
+}
+
+void netfs_state_exit(struct netfs_state *st)
+{
+       if (st->socket) {
+               netfs_poll_exit(st);
+               st->socket->ops->shutdown(st->socket, 2);
+
+               if (st->socket->ops->family == AF_INET) {
+                       struct sockaddr_in *sin = (struct sockaddr_in *)&st->ctl.addr;
+                       printk("%s: disconnected from peer %u.%u.%u.%u:%d.\n", __func__,
+                               NIPQUAD(sin->sin_addr.s_addr), ntohs(sin->sin_port));
+               } else if (st->socket->ops->family == AF_INET6) {
+                       struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&st->ctl.addr;
+                       printk("%s: disconnected from peer "
+                               "%pi6:%d",
+                               __func__, &sin->sin6_addr, ntohs(sin->sin6_port));
+               }
+
+               sock_release(st->socket);
+               st->socket = NULL;
+               st->read_socket = NULL;
+               st->need_reset = 0;
+       }
+}
+
+int pohmelfs_state_init_one(struct pohmelfs_sb *psb, struct pohmelfs_config *conf)
+{
+       struct netfs_state *st = &conf->state;
+       int err = -ENOMEM;
+
+       mutex_init(&st->__state_lock);
+       mutex_init(&st->__state_send_lock);
+       init_waitqueue_head(&st->thread_wait);
+
+       st->psb = psb;
+       st->trans_root = RB_ROOT;
+       mutex_init(&st->trans_lock);
+
+       st->size = psb->trans_data_size;
+       st->data = kmalloc(st->size, GFP_KERNEL);
+       if (!st->data)
+               goto err_out_exit;
+
+       if (psb->perform_crypto) {
+               err = pohmelfs_crypto_engine_init(&st->eng, psb);
+               if (err)
+                       goto err_out_free_data;
+       }
+
+       err = netfs_state_init(st);
+       if (err)
+               goto err_out_free_engine;
+
+       st->thread = kthread_run(pohmelfs_recv, st, "pohmelfs/%u", psb->idx);
+       if (IS_ERR(st->thread)) {
+               err = PTR_ERR(st->thread);
+               goto err_out_netfs_exit;
+       }
+
+       if (!psb->active_state)
+               psb->active_state = conf;
+
+       dprintk("%s: conf: %p, st: %p, socket: %p.\n",
+                       __func__, conf, st, st->socket);
+       return 0;
+
+err_out_netfs_exit:
+       netfs_state_exit(st);
+err_out_free_engine:
+       pohmelfs_crypto_engine_exit(&st->eng);
+err_out_free_data:
+       kfree(st->data);
+err_out_exit:
+       return err;
+
+}
+
+void pohmelfs_state_flush_transactions(struct netfs_state *st)
+{
+       struct rb_node *rb_node;
+       struct netfs_trans_dst *dst;
+
+       mutex_lock(&st->trans_lock);
+       for (rb_node = rb_first(&st->trans_root); rb_node; ) {
+               dst = rb_entry(rb_node, struct netfs_trans_dst, state_entry);
+               rb_node = rb_next(rb_node);
+
+               dst->trans->result = -EINVAL;
+               netfs_trans_remove_nolock(dst, st);
+               netfs_trans_drop_dst_nostate(dst);
+       }
+       mutex_unlock(&st->trans_lock);
+}
+
+static void pohmelfs_state_exit_one(struct pohmelfs_config *c)
+{
+       struct netfs_state *st = &c->state;
+
+       dprintk("%s: exiting, st: %p.\n", __func__, st);
+       if (st->thread) {
+               kthread_stop(st->thread);
+               st->thread = NULL;
+       }
+
+       netfs_state_lock_send(st);
+       netfs_state_exit(st);
+       netfs_state_unlock_send(st);
+
+       pohmelfs_state_flush_transactions(st);
+
+       pohmelfs_crypto_engine_exit(&st->eng);
+       kfree(st->data);
+
+       kfree(c);
+}
+
+/*
+ * Initialize network stack. It searches for given ID in global
+ * configuration table, this contains information of the remote server
+ * (address (any supported by socket interface) and port, protocol and so on).
+ */
+int pohmelfs_state_init(struct pohmelfs_sb *psb)
+{
+       int err = -ENOMEM;
+
+       err = pohmelfs_copy_config(psb);
+       if (err) {
+               pohmelfs_state_exit(psb);
+               return err;
+       }
+
+       return 0;
+}
+
+void pohmelfs_state_exit(struct pohmelfs_sb *psb)
+{
+       struct pohmelfs_config *c, *tmp;
+
+       list_for_each_entry_safe(c, tmp, &psb->state_list, config_entry) {
+               list_del(&c->config_entry);
+               pohmelfs_state_exit_one(c);
+       }
+}
+
+void pohmelfs_switch_active(struct pohmelfs_sb *psb)
+{
+       struct pohmelfs_config *c = psb->active_state;
+
+       if (!list_empty(&psb->state_list)) {
+               if (c->config_entry.next != &psb->state_list) {
+                       psb->active_state = list_entry(c->config_entry.next,
+                               struct pohmelfs_config, config_entry);
+               } else {
+                       psb->active_state = list_entry(psb->state_list.next,
+                               struct pohmelfs_config, config_entry);
+               }
+
+               dprintk("%s: empty: %d, active %p -> %p.\n",
+                       __func__, list_empty(&psb->state_list), c,
+                       psb->active_state);
+       } else
+               psb->active_state = NULL;
+}
+
+void pohmelfs_check_states(struct pohmelfs_sb *psb)
+{
+       struct pohmelfs_config *c, *tmp;
+       LIST_HEAD(delete_list);
+
+       mutex_lock(&psb->state_lock);
+       list_for_each_entry_safe(c, tmp, &psb->state_list, config_entry) {
+               if (pohmelfs_config_check(c, psb->idx)) {
+
+                       if (psb->active_state == c)
+                               pohmelfs_switch_active(psb);
+                       list_move(&c->config_entry, &delete_list);
+               }
+       }
+       pohmelfs_copy_config(psb);
+       mutex_unlock(&psb->state_lock);
+
+       list_for_each_entry_safe(c, tmp, &delete_list, config_entry) {
+               list_del(&c->config_entry);
+               pohmelfs_state_exit_one(c);
+       }
+}
diff --git a/drivers/staging/pohmelfs/netfs.h b/drivers/staging/pohmelfs/netfs.h
new file mode 100644 (file)
index 0000000..2ff21ae
--- /dev/null
@@ -0,0 +1,932 @@
+/*
+ * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
+ * 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.
+ */
+
+#ifndef __NETFS_H
+#define __NETFS_H
+
+#include <linux/types.h>
+#include <linux/connector.h>
+
+#define POHMELFS_CN_IDX                        5
+#define POHMELFS_CN_VAL                        0
+
+#define POHMELFS_CTLINFO_ACK           1
+#define POHMELFS_NOINFO_ACK            2
+
+
+/*
+ * Network command structure.
+ * Will be extended.
+ */
+struct netfs_cmd
+{
+       __u16                   cmd;    /* Command number */
+       __u16                   csize;  /* Attached crypto information size */
+       __u16                   cpad;   /* Attached padding size */
+       __u16                   ext;    /* External flags */
+       __u32                   size;   /* Size of the attached data */
+       __u32                   trans;  /* Transaction id */
+       __u64                   id;     /* Object ID to operate on. Used for feedback.*/
+       __u64                   start;  /* Start of the object. */
+       __u64                   iv;     /* IV sequence */
+       __u8                    data[0];
+};
+
+static inline void netfs_convert_cmd(struct netfs_cmd *cmd)
+{
+       cmd->id = __be64_to_cpu(cmd->id);
+       cmd->start = __be64_to_cpu(cmd->start);
+       cmd->iv = __be64_to_cpu(cmd->iv);
+       cmd->cmd = __be16_to_cpu(cmd->cmd);
+       cmd->ext = __be16_to_cpu(cmd->ext);
+       cmd->csize = __be16_to_cpu(cmd->csize);
+       cmd->cpad = __be16_to_cpu(cmd->cpad);
+       cmd->size = __be32_to_cpu(cmd->size);
+}
+
+#define NETFS_TRANS_SINGLE_DST         (1<<0)
+
+enum {
+       NETFS_READDIR   = 1,    /* Read directory for given inode number */
+       NETFS_READ_PAGE,        /* Read data page from the server */
+       NETFS_WRITE_PAGE,       /* Write data page to the server */
+       NETFS_CREATE,           /* Create directory entry */
+       NETFS_REMOVE,           /* Remove directory entry */
+
+       NETFS_LOOKUP,           /* Lookup single object */
+       NETFS_LINK,             /* Create a link */
+       NETFS_TRANS,            /* Transaction */
+       NETFS_OPEN,             /* Open intent */
+       NETFS_INODE_INFO,       /* Metadata cache coherency synchronization message */
+
+       NETFS_PAGE_CACHE,       /* Page cache invalidation message */
+       NETFS_READ_PAGES,       /* Read multiple contiguous pages in one go */
+       NETFS_RENAME,           /* Rename object */
+       NETFS_CAPABILITIES,     /* Capabilities of the client, for example supported crypto */
+       NETFS_LOCK,             /* Distributed lock message */
+
+       NETFS_XATTR_SET,        /* Set extended attribute */
+       NETFS_XATTR_GET,        /* Get extended attribute */
+       NETFS_CMD_MAX
+};
+
+enum {
+       POHMELFS_FLAGS_ADD = 0, /* Network state control message for ADD */
+       POHMELFS_FLAGS_DEL,     /* Network state control message for DEL */
+       POHMELFS_FLAGS_SHOW,    /* Network state control message for SHOW */
+       POHMELFS_FLAGS_CRYPTO,  /* Crypto data control message */
+};
+
+/*
+ * Always wanted to copy it from socket headers into public one,
+ * since they are __KERNEL__ protected there.
+ */
+#define _K_SS_MAXSIZE  128
+
+struct saddr
+{
+       unsigned short          sa_family;
+       char                    addr[_K_SS_MAXSIZE];
+};
+
+enum {
+       POHMELFS_CRYPTO_HASH = 0,
+       POHMELFS_CRYPTO_CIPHER,
+};
+
+struct pohmelfs_crypto
+{
+       unsigned int            idx;            /* Config index */
+       unsigned short          strlen;         /* Size of the attached crypto string including 0-byte
+                                                * "cbc(aes)" for example */
+       unsigned short          type;           /* HMAC, cipher, both */
+       unsigned int            keysize;        /* Key size */
+       unsigned char           data[0];        /* Algorithm string, key and IV */
+};
+
+/*
+ * Configuration command used to create table of different remote servers.
+ */
+struct pohmelfs_ctl
+{
+       unsigned int            idx;            /* Config index */
+       unsigned int            type;           /* Socket type */
+       unsigned int            proto;          /* Socket protocol */
+       unsigned int            addrlen;        /* Size of the address */
+       unsigned short          unused;         /* Align structure by 4 bytes */
+       struct saddr            addr;           /* Remote server address */
+};
+
+/*
+ * Ack for userspace about requested command.
+ */
+struct pohmelfs_cn_ack
+{
+       struct cn_msg           msg;
+       int                     error;
+       int                     msg_num;
+       int                     unused[3];
+       struct pohmelfs_ctl     ctl;
+};
+
+/*
+ * Inode info structure used to sync with server.
+ * Check what stat() returns.
+ */
+struct netfs_inode_info
+{
+       unsigned int            mode;
+       unsigned int            nlink;
+       unsigned int            uid;
+       unsigned int            gid;
+       unsigned int            blocksize;
+       unsigned int            padding;
+       __u64                   ino;
+       __u64                   blocks;
+       __u64                   rdev;
+       __u64                   size;
+       __u64                   version;
+};
+
+static inline void netfs_convert_inode_info(struct netfs_inode_info *info)
+{
+       info->mode = __cpu_to_be32(info->mode);
+       info->nlink = __cpu_to_be32(info->nlink);
+       info->uid = __cpu_to_be32(info->uid);
+       info->gid = __cpu_to_be32(info->gid);
+       info->blocksize = __cpu_to_be32(info->blocksize);
+       info->blocks = __cpu_to_be64(info->blocks);
+       info->rdev = __cpu_to_be64(info->rdev);
+       info->size = __cpu_to_be64(info->size);
+       info->version = __cpu_to_be64(info->version);
+       info->ino = __cpu_to_be64(info->ino);
+}
+
+/*
+ * Cache state machine.
+ */
+enum {
+       NETFS_COMMAND_PENDING = 0,      /* Command is being executed */
+       NETFS_INODE_REMOTE_SYNCED,      /* Inode was synced to server */
+       NETFS_INODE_REMOTE_DIR_SYNCED,  /* Inode (directory) was synced from the server */
+       NETFS_INODE_OWNED,              /* Inode is owned by given host */
+       NETFS_INODE_NEED_FLUSH,         /* Inode has to be flushed to the server */
+};
+
+/*
+ * POHMELFS capabilities: information about supported
+ * crypto operations (hash/cipher, modes, key sizes and so on),
+ * root informaion (used/available size, number of objects, permissions)
+ */
+enum pohmelfs_capabilities {
+       POHMELFS_CRYPTO_CAPABILITIES = 0,
+       POHMELFS_ROOT_CAPABILITIES,
+};
+
+/* Read-only mount */
+#define POHMELFS_FLAGS_RO              (1<<0)
+/* Extended attributes support on/off */
+#define POHMELFS_FLAGS_XATTR           (1<<1)
+
+struct netfs_root_capabilities
+{
+       __u64                   nr_files;
+       __u64                   used, avail;
+       __u64                   flags;
+};
+
+static inline void netfs_convert_root_capabilities(struct netfs_root_capabilities *cap)
+{
+       cap->nr_files = __cpu_to_be64(cap->nr_files);
+       cap->used = __cpu_to_be64(cap->used);
+       cap->avail = __cpu_to_be64(cap->avail);
+       cap->flags = __cpu_to_be64(cap->flags);
+}
+
+struct netfs_crypto_capabilities
+{
+       unsigned short          hash_strlen;    /* Hash string length, like "hmac(sha1) including 0 byte "*/
+       unsigned short          cipher_strlen;  /* Cipher string length with the same format */
+       unsigned int            cipher_keysize; /* Cipher key size */
+};
+
+static inline void netfs_convert_crypto_capabilities(struct netfs_crypto_capabilities *cap)
+{
+       cap->hash_strlen = __cpu_to_be16(cap->hash_strlen);
+       cap->cipher_strlen = __cpu_to_be16(cap->cipher_strlen);
+       cap->cipher_keysize = __cpu_to_be32(cap->cipher_keysize);
+}
+
+enum pohmelfs_lock_type {
+       POHMELFS_LOCK_GRAB      = (1<<15),
+
+       POHMELFS_READ_LOCK      = 0,
+       POHMELFS_WRITE_LOCK,
+};
+
+struct netfs_lock
+{
+       __u64                   start;
+       __u64                   ino;
+       __u32                   size;
+       __u32                   type;
+};
+
+static inline void netfs_convert_lock(struct netfs_lock *lock)
+{
+       lock->start = __cpu_to_be64(lock->start);
+       lock->ino = __cpu_to_be64(lock->ino);
+       lock->size = __cpu_to_be32(lock->size);
+       lock->type = __cpu_to_be32(lock->type);
+}
+
+#ifdef __KERNEL__
+
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/rbtree.h>
+#include <linux/net.h>
+#include <linux/poll.h>
+
+/*
+ * Private POHMELFS cache of objects in directory.
+ */
+struct pohmelfs_name
+{
+       struct rb_node          hash_node;
+
+       struct list_head        sync_create_entry;
+
+       u64                     ino;
+
+       u32                     hash;
+       u32                     mode;
+       u32                     len;
+
+       char                    *data;
+};
+
+/*
+ * POHMELFS inode. Main object.
+ */
+struct pohmelfs_inode
+{
+       struct list_head        inode_entry;            /* Entry in superblock list.
+                                                        * Objects which are not bound to dentry require to be dropped
+                                                        * in ->put_super()
+                                                        */
+       struct rb_root          hash_root;              /* The same, but indexed by name hash and len */
+       struct mutex            offset_lock;            /* Protect both above trees */
+
+       struct list_head        sync_create_list;       /* List of created but not yet synced to the server children */
+
+       unsigned int            drop_count;
+
+       int                     lock_type;              /* How this inode is locked: read or write */
+
+       int                     error;                  /* Transaction error for given inode */
+
+       long                    state;                  /* State machine above */
+
+       u64                     ino;                    /* Inode number */
+       u64                     total_len;              /* Total length of all children names, used to create offsets */
+
+       struct inode            vfs_inode;
+};
+
+struct netfs_trans;
+typedef int (* netfs_trans_complete_t)(struct page **pages, unsigned int page_num,
+               void *private, int err);
+
+struct netfs_state;
+struct pohmelfs_sb;
+
+struct netfs_trans
+{
+       /*
+        * Transaction header and attached contiguous data live here.
+        */
+       struct iovec                    iovec;
+
+       /*
+        * Pages attached to transaction.
+        */
+       struct page                     **pages;
+
+       /*
+        * List and protecting lock for transaction destination
+        * network states.
+        */
+       spinlock_t                      dst_lock;
+       struct list_head                dst_list;
+
+       /*
+        * Number of users for given transaction.
+        * For example each network state attached to transaction
+        * via dst_list increases it.
+        */
+       atomic_t                        refcnt;
+
+       /*
+        * Number of pages attached to given transaction.
+        * Some slots in above page array can be NULL, since
+        * for example page can be under writeback already,
+        * so we skip it in this transaction.
+        */
+       unsigned int                    page_num;
+
+       /*
+        * Transaction flags: single dst or broadcast and so on.
+        */
+       unsigned int                    flags;
+
+       /*
+        * Size of the data, which can be placed into
+        * iovec.iov_base area.
+        */
+       unsigned int                    total_size;
+
+       /*
+        * Number of pages to be sent to remote server.
+        * Usually equal to above page_num, but in case of partial
+        * writeback it can accumulate only pages already completed
+        * previous writeback.
+        */
+       unsigned int                    attached_pages;
+
+       /*
+        * Attached number of bytes in all above pages.
+        */
+       unsigned int                    attached_size;
+
+       /*
+        * Unique transacton generation number.
+        * Used as identity in the network state tree of transactions.
+        */
+       unsigned int                    gen;
+
+       /*
+        * Transaction completion status.
+        */
+       int                             result;
+
+       /*
+        * Superblock this transaction belongs to
+        */
+       struct pohmelfs_sb              *psb;
+
+       /*
+        * Crypto engine, which processed this transaction.
+        * Can be not NULL only if crypto engine holds encrypted pages.
+        */
+       struct pohmelfs_crypto_engine   *eng;
+
+       /* Private data */
+       void                            *private;
+
+       /* Completion callback, invoked just before transaction is destroyed */
+       netfs_trans_complete_t          complete;
+};
+
+static inline int netfs_trans_cur_len(struct netfs_trans *t)
+{
+       return (signed)(t->total_size - t->iovec.iov_len);
+}
+
+static inline void *netfs_trans_current(struct netfs_trans *t)
+{
+       return t->iovec.iov_base + t->iovec.iov_len;
+}
+
+struct netfs_trans *netfs_trans_alloc(struct pohmelfs_sb *psb, unsigned int size,
+               unsigned int flags, unsigned int nr);
+void netfs_trans_free(struct netfs_trans *t);
+int netfs_trans_finish(struct netfs_trans *t, struct pohmelfs_sb *psb);
+int netfs_trans_finish_send(struct netfs_trans *t, struct pohmelfs_sb *psb);
+
+static inline void netfs_trans_reset(struct netfs_trans *t)
+{
+       t->complete = NULL;
+}
+
+struct netfs_trans_dst
+{
+       struct list_head                trans_entry;
+       struct rb_node                  state_entry;
+
+       unsigned long                   send_time;
+
+       /*
+        * Times this transaction was resent to its old or new,
+        * depending on flags, destinations. When it reaches maximum
+        * allowed number, specified in superblock->trans_retries,
+        * transaction will be freed with ETIMEDOUT error.
+        */
+       unsigned int                    retries;
+
+       struct netfs_trans              *trans;
+       struct netfs_state              *state;
+};
+
+struct netfs_trans_dst *netfs_trans_search(struct netfs_state *st, unsigned int gen);
+void netfs_trans_drop_dst(struct netfs_trans_dst *dst);
+void netfs_trans_drop_dst_nostate(struct netfs_trans_dst *dst);
+void netfs_trans_drop_trans(struct netfs_trans *t, struct netfs_state *st);
+void netfs_trans_drop_last(struct netfs_trans *t, struct netfs_state *st);
+int netfs_trans_resend(struct netfs_trans *t, struct pohmelfs_sb *psb);
+int netfs_trans_remove_nolock(struct netfs_trans_dst *dst, struct netfs_state *st);
+
+int netfs_trans_init(void);
+void netfs_trans_exit(void);
+
+struct pohmelfs_crypto_engine
+{
+       u64                             iv;             /* Crypto IV for current operation */
+       unsigned long                   timeout;        /* Crypto waiting timeout */
+       unsigned int                    size;           /* Size of crypto scratchpad */
+       void                            *data;          /* Temporal crypto scratchpad */
+       /*
+        * Crypto operations performed on objects.
+        */
+       struct crypto_hash              *hash;
+       struct crypto_ablkcipher        *cipher;
+
+       struct pohmelfs_crypto_thread   *thread;        /* Crypto thread which hosts this engine */
+
+       struct page                     **pages;
+       unsigned int                    page_num;
+};
+
+struct pohmelfs_crypto_thread
+{
+       struct list_head                thread_entry;
+
+       struct task_struct              *thread;
+       struct pohmelfs_sb              *psb;
+
+       struct pohmelfs_crypto_engine   eng;
+
+       struct netfs_trans              *trans;
+
+       wait_queue_head_t               wait;
+       int                             error;
+
+       unsigned int                    size;
+       struct page                     *page;
+};
+
+void pohmelfs_crypto_thread_make_ready(struct pohmelfs_crypto_thread *th);
+
+/*
+ * Network state, attached to one server.
+ */
+struct netfs_state
+{
+       struct mutex            __state_lock;           /* Can not allow to use the same socket simultaneously */
+       struct mutex            __state_send_lock;
+       struct netfs_cmd        cmd;                    /* Cached command */
+       struct netfs_inode_info info;                   /* Cached inode info */
+
+       void                    *data;                  /* Cached some data */
+       unsigned int            size;                   /* Size of that data */
+
+       struct pohmelfs_sb      *psb;                   /* Superblock */
+
+       struct task_struct      *thread;                /* Async receiving thread */
+
+       /* Waiting/polling machinery */
+       wait_queue_t            wait;
+       wait_queue_head_t       *whead;
+       wait_queue_head_t       thread_wait;
+
+       struct mutex            trans_lock;
+       struct rb_root          trans_root;
+
+       struct pohmelfs_ctl     ctl;                    /* Remote peer */
+
+       struct socket           *socket;                /* Socket object */
+       struct socket           *read_socket;           /* Cached pointer to socket object.
+                                                        * Used to determine if between lock drops socket was changed.
+                                                        * Never used to read data or any kind of access.
+                                                        */
+       /*
+        * Crypto engines to process incoming data.
+        */
+       struct pohmelfs_crypto_engine   eng;
+
+       int                     need_reset;
+};
+
+int netfs_state_init(struct netfs_state *st);
+void netfs_state_exit(struct netfs_state *st);
+
+static inline void netfs_state_lock_send(struct netfs_state *st)
+{
+       mutex_lock(&st->__state_send_lock);
+}
+
+static inline int netfs_state_trylock_send(struct netfs_state *st)
+{
+       return mutex_trylock(&st->__state_send_lock);
+}
+
+static inline void netfs_state_unlock_send(struct netfs_state *st)
+{
+       BUG_ON(!mutex_is_locked(&st->__state_send_lock));
+
+       mutex_unlock(&st->__state_send_lock);
+}
+
+static inline void netfs_state_lock(struct netfs_state *st)
+{
+       mutex_lock(&st->__state_lock);
+}
+
+static inline void netfs_state_unlock(struct netfs_state *st)
+{
+       BUG_ON(!mutex_is_locked(&st->__state_lock));
+
+       mutex_unlock(&st->__state_lock);
+}
+
+static inline unsigned int netfs_state_poll(struct netfs_state *st)
+{
+       unsigned int revents = POLLHUP | POLLERR;
+
+       netfs_state_lock(st);
+       if (st->socket)
+               revents = st->socket->ops->poll(NULL, st->socket, NULL);
+       netfs_state_unlock(st);
+
+       return revents;
+}
+
+struct pohmelfs_config;
+
+struct pohmelfs_sb
+{
+       struct rb_root          mcache_root;
+       struct mutex            mcache_lock;
+       atomic_long_t           mcache_gen;
+       unsigned long           mcache_timeout;
+
+       unsigned int            idx;
+
+       unsigned int            trans_retries;
+
+       atomic_t                trans_gen;
+
+       unsigned int            crypto_attached_size;
+       unsigned int            crypto_align_size;
+
+       unsigned int            crypto_fail_unsupported;
+
+       unsigned int            crypto_thread_num;
+       struct list_head        crypto_active_list, crypto_ready_list;
+       struct mutex            crypto_thread_lock;
+
+       unsigned int            trans_max_pages;
+       unsigned long           trans_data_size;
+       unsigned long           trans_timeout;
+
+       unsigned long           drop_scan_timeout;
+       unsigned long           trans_scan_timeout;
+
+       unsigned long           wait_on_page_timeout;
+
+       struct list_head        flush_list;
+       struct list_head        drop_list;
+       spinlock_t              ino_lock;
+       u64                     ino;
+
+       /*
+        * Remote nodes POHMELFS connected to.
+        */
+       struct list_head        state_list;
+       struct mutex            state_lock;
+
+       /*
+        * Currently active state to request data from.
+        */
+       struct pohmelfs_config  *active_state;
+
+
+       wait_queue_head_t       wait;
+
+       /*
+        * Timed checks: stale transactions, inodes to be freed and so on.
+        */
+       struct delayed_work     dwork;
+       struct delayed_work     drop_dwork;
+
+       struct super_block      *sb;
+
+       /*
+        * Algorithm strings.
+        */
+       char                    *hash_string;
+       char                    *cipher_string;
+
+       u8                      *hash_key;
+       u8                      *cipher_key;
+
+       /*
+        * Algorithm string lengths.
+        */
+       unsigned int            hash_strlen;
+       unsigned int            cipher_strlen;
+       unsigned int            hash_keysize;
+       unsigned int            cipher_keysize;
+
+       /*
+        * Controls whether to perfrom crypto processing or not.
+        */
+       int                     perform_crypto;
+
+       /*
+        * POHMELFS statistics.
+        */
+       u64                     total_size;
+       u64                     avail_size;
+       atomic_long_t           total_inodes;
+
+       /*
+        * Xattr support, read-only and so on.
+        */
+       u64                     state_flags;
+
+       /*
+        * Temporary storage to detect changes in the wait queue.
+        */
+       long                    flags;
+};
+
+static inline void netfs_trans_update(struct netfs_cmd *cmd,
+               struct netfs_trans *t, unsigned int size)
+{
+       unsigned int sz = ALIGN(size, t->psb->crypto_align_size);
+
+       t->iovec.iov_len += sizeof(struct netfs_cmd) + sz;
+       cmd->cpad = __cpu_to_be16(sz - size);
+}
+
+static inline struct pohmelfs_sb *POHMELFS_SB(struct super_block *sb)
+{
+       return sb->s_fs_info;
+}
+
+static inline struct pohmelfs_inode *POHMELFS_I(struct inode *inode)
+{
+       return container_of(inode, struct pohmelfs_inode, vfs_inode);
+}
+
+static inline u64 pohmelfs_new_ino(struct pohmelfs_sb *psb)
+{
+       u64 ino;
+
+       spin_lock(&psb->ino_lock);
+       ino = psb->ino++;
+       spin_unlock(&psb->ino_lock);
+
+       return ino;
+}
+
+static inline void pohmelfs_put_inode(struct pohmelfs_inode *pi)
+{
+       struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb);
+
+       spin_lock(&psb->ino_lock);
+       list_move_tail(&pi->inode_entry, &psb->drop_list);
+       pi->drop_count++;
+       spin_unlock(&psb->ino_lock);
+}
+
+struct pohmelfs_config
+{
+       struct list_head        config_entry;
+
+       struct netfs_state      state;
+};
+
+struct pohmelfs_config_group
+{
+       /*
+        * Entry in the global config group list.
+        */
+       struct list_head        group_entry;
+
+       /*
+        * Index of the current group.
+        */
+       unsigned int            idx;
+       /*
+        * Number of config_list entries in this group entry.
+        */
+       unsigned int            num_entry;
+       /*
+        * Algorithm strings.
+        */
+       char                    *hash_string;
+       char                    *cipher_string;
+
+       /*
+        * Algorithm string lengths.
+        */
+       unsigned int            hash_strlen;
+       unsigned int            cipher_strlen;
+
+       /*
+        * Key and its size.
+        */
+       unsigned int            hash_keysize;
+       unsigned int            cipher_keysize;
+       u8                      *hash_key;
+       u8                      *cipher_key;
+
+       /*
+        * List of config entries (network state info) for given idx.
+        */
+       struct list_head        config_list;
+};
+
+int __init pohmelfs_config_init(void);
+void pohmelfs_config_exit(void);
+int pohmelfs_copy_config(struct pohmelfs_sb *psb);
+int pohmelfs_copy_crypto(struct pohmelfs_sb *psb);
+int pohmelfs_config_check(struct pohmelfs_config *config, int idx);
+int pohmelfs_state_init_one(struct pohmelfs_sb *psb, struct pohmelfs_config *conf);
+
+extern const struct file_operations pohmelfs_dir_fops;
+extern const struct inode_operations pohmelfs_dir_inode_ops;
+
+int pohmelfs_state_init(struct pohmelfs_sb *psb);
+void pohmelfs_state_exit(struct pohmelfs_sb *psb);
+void pohmelfs_state_flush_transactions(struct netfs_state *st);
+
+void pohmelfs_fill_inode(struct inode *inode, struct netfs_inode_info *info);
+
+void pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *n);
+void pohmelfs_free_names(struct pohmelfs_inode *parent);
+struct pohmelfs_name *pohmelfs_search_hash(struct pohmelfs_inode *pi, u32 hash);
+
+void pohmelfs_inode_del_inode(struct pohmelfs_sb *psb, struct pohmelfs_inode *pi);
+
+struct pohmelfs_inode *pohmelfs_create_entry_local(struct pohmelfs_sb *psb,
+       struct pohmelfs_inode *parent, struct qstr *str, u64 start, int mode);
+
+int pohmelfs_write_create_inode(struct pohmelfs_inode *pi);
+
+int pohmelfs_write_inode_create(struct inode *inode, struct netfs_trans *trans);
+int pohmelfs_remove_child(struct pohmelfs_inode *parent, struct pohmelfs_name *n);
+
+struct pohmelfs_inode *pohmelfs_new_inode(struct pohmelfs_sb *psb,
+               struct pohmelfs_inode *parent, struct qstr *str,
+               struct netfs_inode_info *info, int link);
+
+int pohmelfs_setattr(struct dentry *dentry, struct iattr *attr);
+int pohmelfs_setattr_raw(struct inode *inode, struct iattr *attr);
+
+int pohmelfs_meta_command(struct pohmelfs_inode *pi, unsigned int cmd_op, unsigned int flags,
+               netfs_trans_complete_t complete, void *priv, u64 start);
+int pohmelfs_meta_command_data(struct pohmelfs_inode *pi, u64 id, unsigned int cmd_op, char *addon,
+               unsigned int flags, netfs_trans_complete_t complete, void *priv, u64 start);
+
+void pohmelfs_check_states(struct pohmelfs_sb *psb);
+void pohmelfs_switch_active(struct pohmelfs_sb *psb);
+
+int pohmelfs_construct_path_string(struct pohmelfs_inode *pi, void *data, int len);
+int pohmelfs_path_length(struct pohmelfs_inode *pi);
+
+struct pohmelfs_crypto_completion
+{
+       struct completion       complete;
+       int                     error;
+};
+
+int pohmelfs_trans_crypt(struct netfs_trans *t, struct pohmelfs_sb *psb);
+void pohmelfs_crypto_exit(struct pohmelfs_sb *psb);
+int pohmelfs_crypto_init(struct pohmelfs_sb *psb);
+
+int pohmelfs_crypto_engine_init(struct pohmelfs_crypto_engine *e, struct pohmelfs_sb *psb);
+void pohmelfs_crypto_engine_exit(struct pohmelfs_crypto_engine *e);
+
+int pohmelfs_crypto_process_input_data(struct pohmelfs_crypto_engine *e, u64 iv,
+               void *data, struct page *page, unsigned int size);
+int pohmelfs_crypto_process_input_page(struct pohmelfs_crypto_engine *e,
+               struct page *page, unsigned int size, u64 iv);
+
+static inline u64 pohmelfs_gen_iv(struct netfs_trans *t)
+{
+       u64 iv = t->gen;
+
+       iv <<= 32;
+       iv |= ((unsigned long)t) & 0xffffffff;
+
+       return iv;
+}
+
+int pohmelfs_data_lock(struct pohmelfs_inode *pi, u64 start, u32 size, int type);
+int pohmelfs_data_unlock(struct pohmelfs_inode *pi, u64 start, u32 size, int type);
+int pohmelfs_data_lock_response(struct netfs_state *st);
+
+static inline int pohmelfs_need_lock(struct pohmelfs_inode *pi, int type)
+{
+       if (test_bit(NETFS_INODE_OWNED, &pi->state)) {
+               if (type == pi->lock_type)
+                       return 0;
+               if ((type == POHMELFS_READ_LOCK) && (pi->lock_type == POHMELFS_WRITE_LOCK))
+                       return 0;
+       }
+
+       if (!test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state))
+               return 0;
+
+       return 1;
+}
+
+int __init pohmelfs_mcache_init(void);
+void pohmelfs_mcache_exit(void);
+
+//#define CONFIG_POHMELFS_DEBUG
+
+#ifdef CONFIG_POHMELFS_DEBUG
+#define dprintka(f, a...) printk(f, ##a)
+#define dprintk(f, a...) printk("%d: " f, task_pid_vnr(current), ##a)
+#else
+#define dprintka(f, a...) do {} while (0)
+#define dprintk(f, a...) do {} while (0)
+#endif
+
+static inline void netfs_trans_get(struct netfs_trans *t)
+{
+       atomic_inc(&t->refcnt);
+}
+
+static inline void netfs_trans_put(struct netfs_trans *t)
+{
+       if (atomic_dec_and_test(&t->refcnt)) {
+               dprintk("%s: t: %p, gen: %u, err: %d.\n",
+                       __func__, t, t->gen, t->result);
+               if (t->complete)
+                       t->complete(t->pages, t->page_num,
+                               t->private, t->result);
+               netfs_trans_free(t);
+       }
+}
+
+struct pohmelfs_mcache
+{
+       struct rb_node                  mcache_entry;
+       struct completion               complete;
+
+       atomic_t                        refcnt;
+
+       u64                             gen;
+
+       void                            *data;
+       u64                             start;
+       u32                             size;
+       int                             err;
+
+       struct netfs_inode_info         info;
+};
+
+struct pohmelfs_mcache *pohmelfs_mcache_alloc(struct pohmelfs_sb *psb, u64 start,
+               unsigned int size, void *data);
+void pohmelfs_mcache_free(struct pohmelfs_sb *psb, struct pohmelfs_mcache *m);
+struct pohmelfs_mcache *pohmelfs_mcache_search(struct pohmelfs_sb *psb, u64 gen);
+void pohmelfs_mcache_remove_locked(struct pohmelfs_sb *psb, struct pohmelfs_mcache *m);
+
+static inline void pohmelfs_mcache_get(struct pohmelfs_mcache *m)
+{
+       atomic_inc(&m->refcnt);
+}
+
+static inline void pohmelfs_mcache_put(struct pohmelfs_sb *psb,
+               struct pohmelfs_mcache *m)
+{
+       if (atomic_dec_and_test(&m->refcnt))
+               pohmelfs_mcache_free(psb, m);
+}
+
+int pohmelfs_ftrans_init(void);
+void pohmelfs_ftrans_exit(void);
+void pohmelfs_ftrans_update(u64 id);
+int pohmelfs_ftrans_check(u64 id);
+void pohmelfs_ftrans_clean(u64 id);
+
+#endif /* __KERNEL__*/
+
+#endif /* __NETFS_H */