linux-stable/drivers/staging/pohmelfs/net.c

1210 lines
29 KiB
C
Raw Normal View History

/*
* 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 cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
#include <linux/slab.h>
#include <linux/swap.h>
#include <linux/syscalls.h>
#include <linux/vmalloc.h>
#include "netfs.h"
/*
* 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 {
struct dentry *dentry, *alias, *pd;
set_bit(NETFS_INODE_REMOTE_SYNCED, &npi->state);
clear_bit(NETFS_INODE_OWNED, &npi->state);
pd = d_find_alias(&parent->vfs_inode);
if (pd) {
str.hash = full_name_hash(str.name, str.len);
dentry = d_alloc(pd, &str);
if (dentry) {
alias = d_materialise_unique(dentry, &npi->vfs_inode);
if (alias)
dput(alias);
}
dput(dentry);
dput(pd);
}
}
}
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;
}
mutex_unlock(&st->trans_lock);
if (!t) {
printk("%s: failed to find transaction: start: %llu: id: %llu, size: %u, ext: %u.\n",
__func__, cmd->start, cmd->id, cmd->size, cmd->ext);
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_long_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 %pi4:%d.\n", __func__,
&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(KERN_INFO "%s: disconnected from peer %pi4:%d.\n", __func__,
&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(KERN_INFO "%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);
}
}