Commit 67435319 authored by Evgeniy Polyakov's avatar Evgeniy Polyakov Committed by Greg Kroah-Hartman

staging: pohmelfs: remove drivers/staging/pohmelfs

New pohmelfs is coming, and it is time to remove deadly old design
https://lkml.org/lkml/2012/2/8/293Signed-off-by: default avatarEvgeniy Polyakov <zbr@ioremap.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 203209ef
......@@ -60,8 +60,6 @@ source "drivers/staging/rts5139/Kconfig"
source "drivers/staging/frontier/Kconfig"
source "drivers/staging/pohmelfs/Kconfig"
source "drivers/staging/phison/Kconfig"
source "drivers/staging/line6/Kconfig"
......
......@@ -22,7 +22,6 @@ obj-$(CONFIG_R8712U) += rtl8712/
obj-$(CONFIG_RTS_PSTOR) += rts_pstor/
obj-$(CONFIG_RTS5139) += rts5139/
obj-$(CONFIG_TRANZPORT) += frontier/
obj-$(CONFIG_POHMELFS) += pohmelfs/
obj-$(CONFIG_IDE_PHISON) += phison/
obj-$(CONFIG_LINE6_USB) += line6/
obj-$(CONFIG_USB_SERIAL_QUATECH2) += serqt_usb2/
......
config POHMELFS
tristate "POHMELFS filesystem support"
depends on NET
select CONNECTOR
select CRYPTO
select CRYPTO_BLKCIPHER
select CRYPTO_HMAC
help
POHMELFS stands for Parallel Optimized Host Message Exchange Layered
File System. This is a network filesystem which supports coherent
caching of data and metadata on clients.
config POHMELFS_DEBUG
bool "POHMELFS debugging"
depends on POHMELFS
default n
help
Turns on excessive POHMELFS debugging facilities.
You usually do not want to slow things down noticeably and get really
lots of kernel messages in syslog.
obj-$(CONFIG_POHMELFS) += pohmelfs.o
pohmelfs-y := inode.o config.o dir.o net.o path_entry.o trans.o crypto.o lock.o mcache.o
/*
* 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/kernel.h>
#include <linux/connector.h>
#include <linux/crypto.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/in.h>
#include <linux/slab.h>
#include "netfs.h"
/*
* Global configuration list.
* Each client can be asked to get one of them.
*
* Allows to provide remote server address (ipv4/v6/whatever), port
* and so on via kernel connector.
*/
static struct cb_id pohmelfs_cn_id = {.idx = POHMELFS_CN_IDX, .val = POHMELFS_CN_VAL};
static LIST_HEAD(pohmelfs_config_list);
static DEFINE_MUTEX(pohmelfs_config_lock);
static inline int pohmelfs_config_eql(struct pohmelfs_ctl *sc, struct pohmelfs_ctl *ctl)
{
if (sc->idx == ctl->idx && sc->type == ctl->type &&
sc->proto == ctl->proto &&
sc->addrlen == ctl->addrlen &&
!memcmp(&sc->addr, &ctl->addr, ctl->addrlen))
return 1;
return 0;
}
static struct pohmelfs_config_group *pohmelfs_find_config_group(unsigned int idx)
{
struct pohmelfs_config_group *g, *group = NULL;
list_for_each_entry(g, &pohmelfs_config_list, group_entry) {
if (g->idx == idx) {
group = g;
break;
}
}
return group;
}
static struct pohmelfs_config_group *pohmelfs_find_create_config_group(unsigned int idx)
{
struct pohmelfs_config_group *g;
g = pohmelfs_find_config_group(idx);
if (g)
return g;
g = kzalloc(sizeof(struct pohmelfs_config_group), GFP_KERNEL);
if (!g)
return NULL;
INIT_LIST_HEAD(&g->config_list);
g->idx = idx;
g->num_entry = 0;
list_add_tail(&g->group_entry, &pohmelfs_config_list);
return g;
}
static inline void pohmelfs_insert_config_entry(struct pohmelfs_sb *psb, struct pohmelfs_config *dst)
{
struct pohmelfs_config *tmp;
INIT_LIST_HEAD(&dst->config_entry);
list_for_each_entry(tmp, &psb->state_list, config_entry) {
if (dst->state.ctl.prio > tmp->state.ctl.prio)
list_add_tail(&dst->config_entry, &tmp->config_entry);
}
if (list_empty(&dst->config_entry))
list_add_tail(&dst->config_entry, &psb->state_list);
}
static int pohmelfs_move_config_entry(struct pohmelfs_sb *psb,
struct pohmelfs_config *dst, struct pohmelfs_config *new)
{
if ((dst->state.ctl.prio == new->state.ctl.prio) &&
(dst->state.ctl.perm == new->state.ctl.perm))
return 0;
dprintk("%s: dst: prio: %d, perm: %x, new: prio: %d, perm: %d.\n",
__func__, dst->state.ctl.prio, dst->state.ctl.perm,
new->state.ctl.prio, new->state.ctl.perm);
dst->state.ctl.prio = new->state.ctl.prio;
dst->state.ctl.perm = new->state.ctl.perm;
list_del_init(&dst->config_entry);
pohmelfs_insert_config_entry(psb, dst);
return 0;
}
/*
* pohmelfs_copy_config() is used to copy new state configs from the
* config group (controlled by the netlink messages) into the superblock.
* This happens either at startup time where no transactions can access
* the list of the configs (and thus list of the network states), or at
* run-time, where it is protected by the psb->state_lock.
*/
int pohmelfs_copy_config(struct pohmelfs_sb *psb)
{
struct pohmelfs_config_group *g;
struct pohmelfs_config *c, *dst;
int err = -ENODEV;
mutex_lock(&pohmelfs_config_lock);
g = pohmelfs_find_config_group(psb->idx);
if (!g)
goto out_unlock;
/*
* Run over all entries in given config group and try to create and
* initialize those, which do not exist in superblock list.
* Skip all existing entries.
*/
list_for_each_entry(c, &g->config_list, config_entry) {
err = 0;
list_for_each_entry(dst, &psb->state_list, config_entry) {
if (pohmelfs_config_eql(&dst->state.ctl, &c->state.ctl)) {
err = pohmelfs_move_config_entry(psb, dst, c);
if (!err)
err = -EEXIST;
break;
}
}
if (err)
continue;
dst = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL);
if (!dst) {
err = -ENOMEM;
break;
}
memcpy(&dst->state.ctl, &c->state.ctl, sizeof(struct pohmelfs_ctl));
pohmelfs_insert_config_entry(psb, dst);
err = pohmelfs_state_init_one(psb, dst);
if (err) {
list_del(&dst->config_entry);
kfree(dst);
}
err = 0;
}
out_unlock:
mutex_unlock(&pohmelfs_config_lock);
return err;
}
int pohmelfs_copy_crypto(struct pohmelfs_sb *psb)
{
struct pohmelfs_config_group *g;
int err = -ENOENT;
mutex_lock(&pohmelfs_config_lock);
g = pohmelfs_find_config_group(psb->idx);
if (!g)
goto err_out_exit;
if (g->hash_string) {
err = -ENOMEM;
psb->hash_string = kstrdup(g->hash_string, GFP_KERNEL);
if (!psb->hash_string)
goto err_out_exit;
psb->hash_strlen = g->hash_strlen;
}
if (g->cipher_string) {
psb->cipher_string = kstrdup(g->cipher_string, GFP_KERNEL);
if (!psb->cipher_string)
goto err_out_free_hash_string;
psb->cipher_strlen = g->cipher_strlen;
}
if (g->hash_keysize) {
psb->hash_key = kmemdup(g->hash_key, g->hash_keysize,
GFP_KERNEL);
if (!psb->hash_key)
goto err_out_free_cipher_string;
psb->hash_keysize = g->hash_keysize;
}
if (g->cipher_keysize) {
psb->cipher_key = kmemdup(g->cipher_key, g->cipher_keysize,
GFP_KERNEL);
if (!psb->cipher_key)
goto err_out_free_hash;
psb->cipher_keysize = g->cipher_keysize;
}
mutex_unlock(&pohmelfs_config_lock);
return 0;
err_out_free_hash:
kfree(psb->hash_key);
err_out_free_cipher_string:
kfree(psb->cipher_string);
err_out_free_hash_string:
kfree(psb->hash_string);
err_out_exit:
mutex_unlock(&pohmelfs_config_lock);
return err;
}
static int pohmelfs_send_reply(int err, int msg_num, int action, struct cn_msg *msg, struct pohmelfs_ctl *ctl)
{
struct pohmelfs_cn_ack *ack;
ack = kzalloc(sizeof(struct pohmelfs_cn_ack), GFP_KERNEL);
if (!ack)
return -ENOMEM;
memcpy(&ack->msg, msg, sizeof(struct cn_msg));
if (action == POHMELFS_CTLINFO_ACK)
memcpy(&ack->ctl, ctl, sizeof(struct pohmelfs_ctl));
ack->msg.len = sizeof(struct pohmelfs_cn_ack) - sizeof(struct cn_msg);
ack->msg.ack = msg->ack + 1;
ack->error = err;
ack->msg_num = msg_num;
cn_netlink_send(&ack->msg, 0, GFP_KERNEL);
kfree(ack);
return 0;
}
static int pohmelfs_cn_disp(struct cn_msg *msg)
{
struct pohmelfs_config_group *g;
struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data;
struct pohmelfs_config *c, *tmp;
int err = 0, i = 1;
if (msg->len != sizeof(struct pohmelfs_ctl))
return -EBADMSG;
mutex_lock(&pohmelfs_config_lock);
g = pohmelfs_find_config_group(ctl->idx);
if (!g) {
pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL);
goto out_unlock;
}
list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) {
struct pohmelfs_ctl *sc = &c->state.ctl;
if (pohmelfs_send_reply(err, g->num_entry - i, POHMELFS_CTLINFO_ACK, msg, sc)) {
err = -ENOMEM;
goto out_unlock;
}
i += 1;
}
out_unlock:
mutex_unlock(&pohmelfs_config_lock);
return err;
}
static int pohmelfs_cn_dump(struct cn_msg *msg)
{
struct pohmelfs_config_group *g;
struct pohmelfs_config *c, *tmp;
int err = 0, i = 1;
int total_msg = 0;
if (msg->len != sizeof(struct pohmelfs_ctl))
return -EBADMSG;
mutex_lock(&pohmelfs_config_lock);
list_for_each_entry(g, &pohmelfs_config_list, group_entry)
total_msg += g->num_entry;
if (total_msg == 0) {
if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL))
err = -ENOMEM;
goto out_unlock;
}
list_for_each_entry(g, &pohmelfs_config_list, group_entry) {
list_for_each_entry_safe(c, tmp, &g->config_list,
config_entry) {
struct pohmelfs_ctl *sc = &c->state.ctl;
if (pohmelfs_send_reply(err, total_msg - i,
POHMELFS_CTLINFO_ACK, msg,
sc)) {
err = -ENOMEM;
goto out_unlock;
}
i += 1;
}
}
out_unlock:
mutex_unlock(&pohmelfs_config_lock);
return err;
}
static int pohmelfs_cn_flush(struct cn_msg *msg)
{
struct pohmelfs_config_group *g;
struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data;
struct pohmelfs_config *c, *tmp;
int err = 0;
if (msg->len != sizeof(struct pohmelfs_ctl))
return -EBADMSG;
mutex_lock(&pohmelfs_config_lock);
if (ctl->idx != POHMELFS_NULL_IDX) {
g = pohmelfs_find_config_group(ctl->idx);
if (!g)
goto out_unlock;
list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) {
list_del(&c->config_entry);
g->num_entry--;
kfree(c);
}
} else {
list_for_each_entry(g, &pohmelfs_config_list, group_entry) {
list_for_each_entry_safe(c, tmp, &g->config_list,
config_entry) {
list_del(&c->config_entry);
g->num_entry--;
kfree(c);
}
}
}
out_unlock:
mutex_unlock(&pohmelfs_config_lock);
pohmelfs_cn_dump(msg);
return err;
}
static int pohmelfs_modify_config(struct pohmelfs_ctl *old, struct pohmelfs_ctl *new)
{
old->perm = new->perm;
old->prio = new->prio;
return 0;
}
static int pohmelfs_cn_ctl(struct cn_msg *msg, int action)
{
struct pohmelfs_config_group *g;
struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data;
struct pohmelfs_config *c, *tmp;
int err = 0;
if (msg->len != sizeof(struct pohmelfs_ctl))
return -EBADMSG;
mutex_lock(&pohmelfs_config_lock);
g = pohmelfs_find_create_config_group(ctl->idx);
if (!g) {
err = -ENOMEM;
goto out_unlock;
}
list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) {
struct pohmelfs_ctl *sc = &c->state.ctl;
if (pohmelfs_config_eql(sc, ctl)) {
if (action == POHMELFS_FLAGS_ADD) {
err = -EEXIST;
goto out_unlock;
} else if (action == POHMELFS_FLAGS_DEL) {
list_del(&c->config_entry);
g->num_entry--;
kfree(c);
goto out_unlock;
} else if (action == POHMELFS_FLAGS_MODIFY) {
err = pohmelfs_modify_config(sc, ctl);
goto out_unlock;
} else {
err = -EEXIST;
goto out_unlock;
}
}
}
if (action == POHMELFS_FLAGS_DEL) {
err = -EBADMSG;
goto out_unlock;
}
c = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL);
if (!c) {
err = -ENOMEM;
goto out_unlock;
}
memcpy(&c->state.ctl, ctl, sizeof(struct pohmelfs_ctl));
g->num_entry++;
list_add_tail(&c->config_entry, &g->config_list);
out_unlock:
mutex_unlock(&pohmelfs_config_lock);
if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL))
err = -ENOMEM;
return err;
}
static int pohmelfs_crypto_hash_init(struct pohmelfs_config_group *g, struct pohmelfs_crypto *c)
{
char *algo = (char *)c->data;
u8 *key = (u8 *)(algo + c->strlen);
if (g->hash_string)
return -EEXIST;
g->hash_string = kstrdup(algo, GFP_KERNEL);
if (!g->hash_string)
return -ENOMEM;
g->hash_strlen = c->strlen;
g->hash_keysize = c->keysize;
g->hash_key = kmemdup(key, c->keysize, GFP_KERNEL);
if (!g->hash_key) {
kfree(g->hash_string);
return -ENOMEM;
}
return 0;
}
static int pohmelfs_crypto_cipher_init(struct pohmelfs_config_group *g, struct pohmelfs_crypto *c)
{
char *algo = (char *)c->data;
u8 *key = (u8 *)(algo + c->strlen);
if (g->cipher_string)
return -EEXIST;
g->cipher_string = kstrdup(algo, GFP_KERNEL);
if (!g->cipher_string)
return -ENOMEM;
g->cipher_strlen = c->strlen;
g->cipher_keysize = c->keysize;
g->cipher_key = kmemdup(key, c->keysize, GFP_KERNEL);
if (!g->cipher_key) {
kfree(g->cipher_string);
return -ENOMEM;
}
return 0;
}
static int pohmelfs_cn_crypto(struct cn_msg *msg)
{
struct pohmelfs_crypto *crypto = (struct pohmelfs_crypto *)msg->data;
struct pohmelfs_config_group *g;
int err = 0;
dprintk("%s: idx: %u, strlen: %u, type: %u, keysize: %u, algo: %s.\n",
__func__, crypto->idx, crypto->strlen, crypto->type,
crypto->keysize, (char *)crypto->data);
mutex_lock(&pohmelfs_config_lock);
g = pohmelfs_find_create_config_group(crypto->idx);
if (!g) {
err = -ENOMEM;
goto out_unlock;
}
switch (crypto->type) {
case POHMELFS_CRYPTO_HASH:
err = pohmelfs_crypto_hash_init(g, crypto);
break;
case POHMELFS_CRYPTO_CIPHER:
err = pohmelfs_crypto_cipher_init(g, crypto);
break;
default:
err = -ENOTSUPP;
break;
}
out_unlock:
mutex_unlock(&pohmelfs_config_lock);
if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL))
err = -ENOMEM;
return err;
}
static void pohmelfs_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
{
int err;
if (!cap_raised(current_cap(), CAP_SYS_ADMIN))
return;
switch (msg->flags) {
case POHMELFS_FLAGS_ADD:
case POHMELFS_FLAGS_DEL:
case POHMELFS_FLAGS_MODIFY:
err = pohmelfs_cn_ctl(msg, msg->flags);
break;
case POHMELFS_FLAGS_FLUSH:
err = pohmelfs_cn_flush(msg);
break;
case POHMELFS_FLAGS_SHOW:
err = pohmelfs_cn_disp(msg);
break;
case POHMELFS_FLAGS_DUMP:
err = pohmelfs_cn_dump(msg);
break;
case POHMELFS_FLAGS_CRYPTO:
err = pohmelfs_cn_crypto(msg);
break;
default:
err = -ENOSYS;
break;
}
}
int pohmelfs_config_check(struct pohmelfs_config *config, int idx)
{
struct pohmelfs_ctl *ctl = &config->state.ctl;
struct pohmelfs_config *tmp;
int err = -ENOENT;
struct pohmelfs_ctl *sc;
struct pohmelfs_config_group *g;
mutex_lock(&pohmelfs_config_lock);
g = pohmelfs_find_config_group(ctl->idx);
if (g) {
list_for_each_entry(tmp, &g->config_list, config_entry) {
sc = &tmp->state.ctl;
if (pohmelfs_config_eql(sc, ctl)) {
err = 0;
break;
}
}
}
mutex_unlock(&pohmelfs_config_lock);
return err;
}
int __init pohmelfs_config_init(void)
{
/* XXX remove (void *) cast when vanilla connector got synced */
return cn_add_callback(&pohmelfs_cn_id, "pohmelfs", (void *)pohmelfs_cn_callback);
}
void pohmelfs_config_exit(void)
{
struct pohmelfs_config *c, *tmp;
struct pohmelfs_config_group *g, *gtmp;
cn_del_callback(&pohmelfs_cn_id);
mutex_lock(&pohmelfs_config_lock);
list_for_each_entry_safe(g, gtmp, &pohmelfs_config_list, group_entry) {
list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) {
list_del(&c->config_entry);
kfree(c);
}
list_del(&g->group_entry);
kfree(g->hash_string);
kfree(g->cipher_string);
kfree(g);
}
mutex_unlock(&pohmelfs_config_lock);
}
/*
* 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/crypto.h>
#include <linux/highmem.h>
#include <linux/kthread.h>
#include <linux/pagemap.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include "netfs.h"
static struct crypto_hash *pohmelfs_init_hash(struct pohmelfs_sb *psb)
{
int err;
struct crypto_hash *hash;
hash = crypto_alloc_hash(psb->hash_string, 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(hash)) {
err = PTR_ERR(hash);
dprintk("%s: idx: %u: failed to allocate hash '%s', err: %d.\n",
__func__, psb->idx, psb->hash_string, err);
goto err_out_exit;
}
psb->crypto_attached_size = crypto_hash_digestsize(hash);
if (!psb->hash_keysize)
return hash;
err = crypto_hash_setkey(hash, psb->hash_key, psb->hash_keysize);
if (err) {
dprintk("%s: idx: %u: failed to set key for hash '%s', err: %d.\n",
__func__, psb->idx, psb->hash_string, err);
goto err_out_free;
}
return hash;
err_out_free:
crypto_free_hash(hash);
err_out_exit:
return ERR_PTR(err);
}
static struct crypto_ablkcipher *pohmelfs_init_cipher(struct pohmelfs_sb *psb)
{
int err = -EINVAL;
struct crypto_ablkcipher *cipher;
if (!psb->cipher_keysize)
goto err_out_exit;
cipher = crypto_alloc_ablkcipher(psb->cipher_string, 0, 0);
if (IS_ERR(cipher)) {
err = PTR_ERR(cipher);
dprintk("%s: idx: %u: failed to allocate cipher '%s', err: %d.\n",
__func__, psb->idx, psb->cipher_string, err);
goto err_out_exit;
}
crypto_ablkcipher_clear_flags(cipher, ~0);
err = crypto_ablkcipher_setkey(cipher, psb->cipher_key, psb->cipher_keysize);
if (err) {
dprintk("%s: idx: %u: failed to set key for cipher '%s', err: %d.\n",
__func__, psb->idx, psb->cipher_string, err);
goto err_out_free;
}
return cipher;
err_out_free:
crypto_free_ablkcipher(cipher);
err_out_exit:
return ERR_PTR(err);
}
int pohmelfs_crypto_engine_init(struct pohmelfs_crypto_engine *e, struct pohmelfs_sb *psb)
{
int err;
e->page_num = 0;
e->size = PAGE_SIZE;
e->data = kmalloc(e->size, GFP_KERNEL);
if (!e->data) {
err = -ENOMEM;
goto err_out_exit;
}
if (psb->hash_string) {
e->hash = pohmelfs_init_hash(psb);
if (IS_ERR(e->hash)) {
err = PTR_ERR(e->hash);
e->hash = NULL;
goto err_out_free;
}
}
if (psb->cipher_string) {
e->cipher = pohmelfs_init_cipher(psb);
if (IS_ERR(e->cipher)) {
err = PTR_ERR(e->cipher);
e->cipher = NULL;
goto err_out_free_hash;
}
}
return 0;
err_out_free_hash:
crypto_free_hash(e->hash);
err_out_free:
kfree(e->data);
err_out_exit:
return err;
}
void pohmelfs_crypto_engine_exit(struct pohmelfs_crypto_engine *e)
{
crypto_free_hash(e->hash);
crypto_free_ablkcipher(e->cipher);
kfree(e->data);
}
static void pohmelfs_crypto_complete(struct crypto_async_request *req, int err)
{
struct pohmelfs_crypto_completion *c = req->data;
if (err == -EINPROGRESS)
return;
dprintk("%s: req: %p, err: %d.\n", __func__, req, err);
c->error = err;
complete(&c->complete);
}
static int pohmelfs_crypto_process(struct ablkcipher_request *req,
struct scatterlist *sg_dst, struct scatterlist *sg_src,
void *iv, int enc, unsigned long timeout)
{
struct pohmelfs_crypto_completion complete;
int err;
init_completion(&complete.complete);
complete.error = -EINPROGRESS;
ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
pohmelfs_crypto_complete, &complete);
ablkcipher_request_set_crypt(req, sg_src, sg_dst, sg_src->length, iv);
if (enc)
err = crypto_ablkcipher_encrypt(req);
else
err = crypto_ablkcipher_decrypt(req);
switch (err) {
case -EINPROGRESS:
case -EBUSY:
err = wait_for_completion_interruptible_timeout(&complete.complete,
timeout);
if (!err)
err = -ETIMEDOUT;
else if (err > 0)
err = complete.error;
break;
default:
break;
}
return err;
}
int pohmelfs_crypto_process_input_data(struct pohmelfs_crypto_engine *e, u64 cmd_iv,
void *data, struct page *page, unsigned int size)
{
int err;
struct scatterlist sg;
if (!e->cipher && !e->hash)
return 0;
dprintk("%s: eng: %p, iv: %llx, data: %p, page: %p/%lu, size: %u.\n",
__func__, e, cmd_iv, data, page, (page) ? page->index : 0, size);
if (data) {
sg_init_one(&sg, data, size);
} else {
sg_init_table(&sg, 1);
sg_set_page(&sg, page, size, 0);
}
if (e->cipher) {
struct ablkcipher_request *req = e->data + crypto_hash_digestsize(e->hash);
u8 iv[32];
memset(iv, 0, sizeof(iv));
memcpy(iv, &cmd_iv, sizeof(cmd_iv));
ablkcipher_request_set_tfm(req, e->cipher);
err = pohmelfs_crypto_process(req, &sg, &sg, iv, 0, e->timeout);
if (err)
goto err_out_exit;
}
if (e->hash) {
struct hash_desc desc;
void *dst = e->data + e->size/2;
desc.tfm = e->hash;
desc.flags = 0;
err = crypto_hash_init(&desc);
if (err)
goto err_out_exit;
err = crypto_hash_update(&desc, &sg, size);
if (err)
goto err_out_exit;
err = crypto_hash_final(&desc, dst);
if (err)
goto err_out_exit;
err = !!memcmp(dst, e->data, crypto_hash_digestsize(e->hash));
if (err) {
#ifdef CONFIG_POHMELFS_DEBUG
unsigned int i;
unsigned char *recv = e->data, *calc = dst;
dprintk("%s: eng: %p, hash: %p, cipher: %p: iv : %llx, hash mismatch (recv/calc): ",
__func__, e, e->hash, e->cipher, cmd_iv);
for (i = 0; i < crypto_hash_digestsize(e->hash); ++i) {
#if 0
dprintka("%02x ", recv[i]);
if (recv[i] != calc[i]) {
dprintka("| calc byte: %02x.\n", calc[i]);
break;
}
#else
dprintka("%02x/%02x ", recv[i], calc[i]);
#endif
}
dprintk("\n");
#endif
goto err_out_exit;
} else {
dprintk("%s: eng: %p, hash: %p, cipher: %p: hashes matched.\n",
__func__, e, e->hash, e->cipher);
}
}
dprintk("%s: eng: %p, size: %u, hash: %p, cipher: %p: completed.\n",
__func__, e, e->size, e->hash, e->cipher);
return 0;
err_out_exit:
dprintk("%s: eng: %p, hash: %p, cipher: %p: err: %d.\n",
__func__, e, e->hash, e->cipher, err);
return err;
}
static int pohmelfs_trans_iter(struct netfs_trans *t, struct pohmelfs_crypto_engine *e,
int (*iterator) (struct pohmelfs_crypto_engine *e,
struct scatterlist *dst,
struct scatterlist *src))
{
void *data = t->iovec.iov_base + sizeof(struct netfs_cmd) + t->psb->crypto_attached_size;
unsigned int size = t->iovec.iov_len - sizeof(struct netfs_cmd) - t->psb->crypto_attached_size;
struct netfs_cmd *cmd = data;
unsigned int sz, pages = t->attached_pages, i, csize, cmd_cmd, dpage_idx;
struct scatterlist sg_src, sg_dst;
int err;
while (size) {
cmd = data;
cmd_cmd = __be16_to_cpu(cmd->cmd);
csize = __be32_to_cpu(cmd->size);
cmd->iv = __cpu_to_be64(e->iv);
if (cmd_cmd == NETFS_READ_PAGES || cmd_cmd == NETFS_READ_PAGE)
csize = __be16_to_cpu(cmd->ext);
sz = csize + __be16_to_cpu(cmd->cpad) + sizeof(struct netfs_cmd);
dprintk("%s: size: %u, sz: %u, cmd_size: %u, cmd_cpad: %u.\n",
__func__, size, sz, __be32_to_cpu(cmd->size), __be16_to_cpu(cmd->cpad));
data += sz;
size -= sz;
sg_init_one(&sg_src, cmd->data, sz - sizeof(struct netfs_cmd));
sg_init_one(&sg_dst, cmd->data, sz - sizeof(struct netfs_cmd));
err = iterator(e, &sg_dst, &sg_src);
if (err)
return err;
}
if (!pages)
return 0;
dpage_idx = 0;
for (i = 0; i < t->page_num; ++i) {
struct page *page = t->pages[i];
struct page *dpage = e->pages[dpage_idx];
if (!page)
continue;
sg_init_table(&sg_src, 1);
sg_init_table(&sg_dst, 1);
sg_set_page(&sg_src, page, page_private(page), 0);
sg_set_page(&sg_dst, dpage, page_private(page), 0);
err = iterator(e, &sg_dst, &sg_src);
if (err)
return err;
pages--;
if (!pages)
break;
dpage_idx++;
}
return 0;
}
static int pohmelfs_encrypt_iterator(struct pohmelfs_crypto_engine *e,
struct scatterlist *sg_dst, struct scatterlist *sg_src)
{
struct ablkcipher_request *req = e->data;
u8 iv[32];
memset(iv, 0, sizeof(iv));
memcpy(iv, &e->iv, sizeof(e->iv));
return pohmelfs_crypto_process(req, sg_dst, sg_src, iv, 1, e->timeout);
}
static int pohmelfs_encrypt(struct pohmelfs_crypto_thread *tc)
{
struct netfs_trans *t = tc->trans;
struct pohmelfs_crypto_engine *e = &tc->eng;
struct ablkcipher_request *req = e->data;
memset(req, 0, sizeof(struct ablkcipher_request));
ablkcipher_request_set_tfm(req, e->cipher);
e->iv = pohmelfs_gen_iv(t);
return pohmelfs_trans_iter(t, e, pohmelfs_encrypt_iterator);
}
static int pohmelfs_hash_iterator(struct pohmelfs_crypto_engine *e,
struct scatterlist *sg_dst, struct scatterlist *sg_src)
{
return crypto_hash_update(e->data, sg_src, sg_src->length);
}
static int pohmelfs_hash(struct pohmelfs_crypto_thread *tc)
{
struct pohmelfs_crypto_engine *e = &tc->eng;
struct hash_desc *desc = e->data;
unsigned char *dst = tc->trans->iovec.iov_base + sizeof(struct netfs_cmd);
int err;
desc->tfm = e->hash;
desc->flags = 0;
err = crypto_hash_init(desc);
if (err)
return err;
err = pohmelfs_trans_iter(tc->trans, e, pohmelfs_hash_iterator);
if (err)
return err;
err = crypto_hash_final(desc, dst);
if (err)
return err;
{
unsigned int i;
dprintk("%s: ", __func__);
for (i = 0; i < tc->psb->crypto_attached_size; ++i)
dprintka("%02x ", dst[i]);
dprintka("\n");
}
return 0;
}
static void pohmelfs_crypto_pages_free(struct pohmelfs_crypto_engine *e)
{
unsigned int i;
for (i = 0; i < e->page_num; ++i)
__free_page(e->pages[i]);
kfree(e->pages);
}
static int pohmelfs_crypto_pages_alloc(struct pohmelfs_crypto_engine *e, struct pohmelfs_sb *psb)
{
unsigned int i;
e->pages = kmalloc(psb->trans_max_pages * sizeof(struct page *), GFP_KERNEL);
if (!e->pages)
return -ENOMEM;
for (i = 0; i < psb->trans_max_pages; ++i) {
e->pages[i] = alloc_page(GFP_KERNEL);
if (!e->pages[i])
break;
}
e->page_num = i;
if (!e->page_num)
goto err_out_free;
return 0;
err_out_free:
kfree(e->pages);
return -ENOMEM;
}
static void pohmelfs_sys_crypto_exit_one(struct pohmelfs_crypto_thread *t)
{
struct pohmelfs_sb *psb = t->psb;
if (t->thread)
kthread_stop(t->thread);
mutex_lock(&psb->crypto_thread_lock);
list_del(&t->thread_entry);
psb->crypto_thread_num--;
mutex_unlock(&psb->crypto_thread_lock);
pohmelfs_crypto_engine_exit(&t->eng);
pohmelfs_crypto_pages_free(&t->eng);
kfree(t);
}
static int pohmelfs_crypto_finish(struct netfs_trans *t, struct pohmelfs_sb *psb, int err)
{
struct netfs_cmd *cmd = t->iovec.iov_base;
netfs_convert_cmd(cmd);
if (likely(!err))
err = netfs_trans_finish_send(t, psb);
t->result = err;
netfs_trans_put(t);
return err;
}
void pohmelfs_crypto_thread_make_ready(struct pohmelfs_crypto_thread *th)
{
struct pohmelfs_sb *psb = th->psb;
th->page = NULL;
th->trans = NULL;
mutex_lock(&psb->crypto_thread_lock);
list_move_tail(&th->thread_entry, &psb->crypto_ready_list);
mutex_unlock(&psb->crypto_thread_lock);
wake_up(&psb->wait);
}
static int pohmelfs_crypto_thread_trans(struct pohmelfs_crypto_thread *t)
{
struct netfs_trans *trans;
int err = 0;
trans = t->trans;
trans->eng = NULL;
if (t->eng.hash) {
err = pohmelfs_hash(t);
if (err)
goto out_complete;
}
if (t->eng.cipher) {
err = pohmelfs_encrypt(t);
if (err)
goto out_complete;
trans->eng = &t->eng;
}
out_complete:
t->page = NULL;
t->trans = NULL;
if (!trans->eng)
pohmelfs_crypto_thread_make_ready(t);
pohmelfs_crypto_finish(trans, t->psb, err);
return err;
}
static int pohmelfs_crypto_thread_page(struct pohmelfs_crypto_thread *t)
{
struct pohmelfs_crypto_engine *e = &t->eng;
struct page *page = t->page;
int err;
WARN_ON(!PageChecked(page));
err = pohmelfs_crypto_process_input_data(e, e->iv, NULL, page, t->size);
if (!err)
SetPageUptodate(page);
else
SetPageError(page);
unlock_page(page);
page_cache_release(page);
pohmelfs_crypto_thread_make_ready(t);
return err;
}
static int pohmelfs_crypto_thread_func(void *data)
{
struct pohmelfs_crypto_thread *t = data;
while (!kthread_should_stop()) {
wait_event_interruptible(t->wait, kthread_should_stop() ||
t->trans || t->page);
if (kthread_should_stop())
break;
if (!t->trans && !t->page)
continue;
dprintk("%s: thread: %p, trans: %p, page: %p.\n",
__func__, t, t->trans, t->page);
if (t->trans)
pohmelfs_crypto_thread_trans(t);
else if (t->page)
pohmelfs_crypto_thread_page(t);
}
return 0;
}
static void pohmelfs_crypto_flush(struct pohmelfs_sb *psb, struct list_head *head)
{
while (!list_empty(head)) {
struct pohmelfs_crypto_thread *t = NULL;
mutex_lock(&psb->crypto_thread_lock);
if (!list_empty(head)) {
t = list_first_entry(head, struct pohmelfs_crypto_thread, thread_entry);
list_del_init(&t->thread_entry);
}
mutex_unlock(&psb->crypto_thread_lock);
if (t)
pohmelfs_sys_crypto_exit_one(t);
}
}
static void pohmelfs_sys_crypto_exit(struct pohmelfs_sb *psb)
{
while (!list_empty(&psb->crypto_active_list) || !list_empty(&psb->crypto_ready_list)) {
dprintk("%s: crypto_thread_num: %u.\n", __func__, psb->crypto_thread_num);
pohmelfs_crypto_flush(psb, &psb->crypto_active_list);
pohmelfs_crypto_flush(psb, &psb->crypto_ready_list);
}
}
static int pohmelfs_sys_crypto_init(struct pohmelfs_sb *psb)
{
unsigned int i;
struct pohmelfs_crypto_thread *t;
struct pohmelfs_config *c;
struct netfs_state *st;
int err;
list_for_each_entry(c, &psb->state_list, config_entry) {
st = &c->state;
err = pohmelfs_crypto_engine_init(&st->eng, psb);
if (err)
goto err_out_exit;
dprintk("%s: st: %p, eng: %p, hash: %p, cipher: %p.\n",
__func__, st, &st->eng, &st->eng.hash, &st->eng.cipher);
}
for (i = 0; i < psb->crypto_thread_num; ++i) {
err = -ENOMEM;
t = kzalloc(sizeof(struct pohmelfs_crypto_thread), GFP_KERNEL);
if (!t)
goto err_out_free_state_engines;
init_waitqueue_head(&t->wait);
t->psb = psb;
t->trans = NULL;
t->eng.thread = t;
err = pohmelfs_crypto_engine_init(&t->eng, psb);
if (err)
goto err_out_free_state_engines;
err = pohmelfs_crypto_pages_alloc(&t->eng, psb);
if (err)
goto err_out_free;
t->thread = kthread_run(pohmelfs_crypto_thread_func, t,
"pohmelfs-crypto-%d-%d", psb->idx, i);
if (IS_ERR(t->thread)) {
err = PTR_ERR(t->thread);
t->thread = NULL;
goto err_out_free;
}
if (t->eng.cipher)
psb->crypto_align_size = crypto_ablkcipher_blocksize(t->eng.cipher);
mutex_lock(&psb->crypto_thread_lock);
list_add_tail(&t->thread_entry, &psb->crypto_ready_list);
mutex_unlock(&psb->crypto_thread_lock);
}
psb->crypto_thread_num = i;
return 0;
err_out_free:
pohmelfs_sys_crypto_exit_one(t);
err_out_free_state_engines:
list_for_each_entry(c, &psb->state_list, config_entry) {
st = &c->state;
pohmelfs_crypto_engine_exit(&st->eng);
}
err_out_exit:
pohmelfs_sys_crypto_exit(psb);
return err;
}
void pohmelfs_crypto_exit(struct pohmelfs_sb *psb)
{
pohmelfs_sys_crypto_exit(psb);
kfree(psb->hash_string);
kfree(psb->cipher_string);
}
static int pohmelfs_crypt_init_complete(struct page **pages, unsigned int page_num,
void *private, int err)
{
struct pohmelfs_sb *psb = private;
psb->flags = -err;
dprintk("%s: err: %d.\n", __func__, err);
wake_up(&psb->wait);
return err;
}
static int pohmelfs_crypto_init_handshake(struct pohmelfs_sb *psb)
{
struct netfs_trans *t;
struct netfs_crypto_capabilities *cap;
struct netfs_cmd *cmd;
char *str;
int err = -ENOMEM, size;
size = sizeof(struct netfs_crypto_capabilities) +
psb->cipher_strlen + psb->hash_strlen + 2; /* 0 bytes */
t = netfs_trans_alloc(psb, size, 0, 0);
if (!t)
goto err_out_exit;
t->complete = pohmelfs_crypt_init_complete;
t->private = psb;
cmd = netfs_trans_current(t);
cap = (struct netfs_crypto_capabilities *)(cmd + 1);
str = (char *)(cap + 1);
cmd->cmd = NETFS_CAPABILITIES;
cmd->id = POHMELFS_CRYPTO_CAPABILITIES;
cmd->size = size;
cmd->start = 0;
cmd->ext = 0;
cmd->csize = 0;
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, size);
cap->hash_strlen = psb->hash_strlen;
if (cap->hash_strlen) {
sprintf(str, "%s", psb->hash_string);
str += cap->hash_strlen;
}
cap->cipher_strlen = psb->cipher_strlen;
cap->cipher_keysize = psb->cipher_keysize;
if (cap->cipher_strlen)
sprintf(str, "%s", psb->cipher_string);
netfs_convert_crypto_capabilities(cap);
psb->flags = ~0;
err = netfs_trans_finish(t, psb);
if (err)
goto err_out_exit;
err = wait_event_interruptible_timeout(psb->wait, (psb->flags != ~0),
psb->wait_on_page_timeout);
if (!err)
err = -ETIMEDOUT;
else if (err > 0)
err = -psb->flags;
if (!err)
psb->perform_crypto = 1;
psb->flags = 0;
/*
* At this point NETFS_CAPABILITIES response command
* should setup superblock in a way, which is acceptable
* for both client and server, so if server refuses connection,
* it will send error in transaction response.
*/
if (err)
goto err_out_exit;
return 0;
err_out_exit:
return err;
}
int pohmelfs_crypto_init(struct pohmelfs_sb *psb)
{
int err;
if (!psb->cipher_string && !psb->hash_string)
return 0;
err = pohmelfs_crypto_init_handshake(psb);
if (err)
return err;
err = pohmelfs_sys_crypto_init(psb);
if (err)
return err;
return 0;
}
static int pohmelfs_crypto_thread_get(struct pohmelfs_sb *psb,
int (*action)(struct pohmelfs_crypto_thread *t, void *data), void *data)
{
struct pohmelfs_crypto_thread *t = NULL;
int err;
while (!t) {
err = wait_event_interruptible_timeout(psb->wait,
!list_empty(&psb->crypto_ready_list),
psb->wait_on_page_timeout);
t = NULL;
err = 0;
mutex_lock(&psb->crypto_thread_lock);
if (!list_empty(&psb->crypto_ready_list)) {
t = list_entry(psb->crypto_ready_list.prev,
struct pohmelfs_crypto_thread,
thread_entry);
list_move_tail(&t->thread_entry,
&psb->crypto_active_list);
action(t, data);
wake_up(&t->wait);
}
mutex_unlock(&psb->crypto_thread_lock);
}
return err;
}
static int pohmelfs_trans_crypt_action(struct pohmelfs_crypto_thread *t, void *data)
{
struct netfs_trans *trans = data;
netfs_trans_get(trans);
t->trans = trans;
dprintk("%s: t: %p, gen: %u, thread: %p.\n", __func__, trans, trans->gen, t);
return 0;
}
int pohmelfs_trans_crypt(struct netfs_trans *trans, struct pohmelfs_sb *psb)
{
if ((!psb->hash_string && !psb->cipher_string) || !psb->perform_crypto) {
netfs_trans_get(trans);
return pohmelfs_crypto_finish(trans, psb, 0);
}
return pohmelfs_crypto_thread_get(psb, pohmelfs_trans_crypt_action, trans);
}
struct pohmelfs_crypto_input_action_data {
struct page *page;
struct pohmelfs_crypto_engine *e;
u64 iv;
unsigned int size;
};
static int pohmelfs_crypt_input_page_action(struct pohmelfs_crypto_thread *t, void *data)
{
struct pohmelfs_crypto_input_action_data *act = data;
memcpy(t->eng.data, act->e->data, t->psb->crypto_attached_size);
t->size = act->size;
t->eng.iv = act->iv;
t->page = act->page;
return 0;
}
int pohmelfs_crypto_process_input_page(struct pohmelfs_crypto_engine *e,
struct page *page, unsigned int size, u64 iv)
{
struct inode *inode = page->mapping->host;
struct pohmelfs_crypto_input_action_data act;
int err = -ENOENT;
act.page = page;
act.e = e;
act.size = size;
act.iv = iv;
err = pohmelfs_crypto_thread_get(POHMELFS_SB(inode->i_sb),
pohmelfs_crypt_input_page_action, &act);
if (err)
goto err_out_exit;
return 0;
err_out_exit:
SetPageUptodate(page);
page_cache_release(page);
return err;
}
/*
* 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/kernel.h>
#include <linux/fs.h>
#include <linux/jhash.h>
#include <linux/namei.h>
#include <linux/slab.h>
#include <linux/pagemap.h>
#include "netfs.h"
static int pohmelfs_cmp_hash(struct pohmelfs_name *n, u32 hash)
{
if (n->hash > hash)
return -1;
if (n->hash < hash)
return 1;
return 0;
}
static struct pohmelfs_name *pohmelfs_search_hash_unprecise(struct pohmelfs_inode *pi, u32 hash)
{
struct rb_node *n = pi->hash_root.rb_node;
struct pohmelfs_name *tmp = NULL;
int cmp;
while (n) {
tmp = rb_entry(n, struct pohmelfs_name, hash_node);
cmp = pohmelfs_cmp_hash(tmp, hash);
if (cmp < 0)
n = n->rb_left;
else if (cmp > 0)
n = n->rb_right;
else
break;
}
return tmp;
}
struct pohmelfs_name *pohmelfs_search_hash(struct pohmelfs_inode *pi, u32 hash)
{
struct pohmelfs_name *tmp;
tmp = pohmelfs_search_hash_unprecise(pi, hash);
if (tmp && (tmp->hash == hash))
return tmp;
return NULL;
}
static void __pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
{
rb_erase(&node->hash_node, &parent->hash_root);
}
/*
* Remove name cache entry from its caches and free it.
*/
static void pohmelfs_name_free(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
{
__pohmelfs_name_del(parent, node);
list_del(&node->sync_create_entry);
kfree(node);
}
static struct pohmelfs_name *pohmelfs_insert_hash(struct pohmelfs_inode *pi,
struct pohmelfs_name *new)
{
struct rb_node **n = &pi->hash_root.rb_node, *parent = NULL;
struct pohmelfs_name *ret = NULL, *tmp;
int cmp;
while (*n) {
parent = *n;
tmp = rb_entry(parent, struct pohmelfs_name, hash_node);
cmp = pohmelfs_cmp_hash(tmp, new->hash);
if (cmp < 0)
n = &parent->rb_left;
else if (cmp > 0)
n = &parent->rb_right;
else {
ret = tmp;
break;
}
}
if (ret) {
printk("%s: exist: parent: %llu, ino: %llu, hash: %x, len: %u, data: '%s', "
"new: ino: %llu, hash: %x, len: %u, data: '%s'.\n",
__func__, pi->ino,
ret->ino, ret->hash, ret->len, ret->data,
new->ino, new->hash, new->len, new->data);
ret->ino = new->ino;
return ret;
}
rb_link_node(&new->hash_node, parent, n);
rb_insert_color(&new->hash_node, &pi->hash_root);
return NULL;
}
/*
* Free name cache for given inode.
*/
void pohmelfs_free_names(struct pohmelfs_inode *parent)
{
struct rb_node *rb_node;
struct pohmelfs_name *n;
for (rb_node = rb_first(&parent->hash_root); rb_node;) {
n = rb_entry(rb_node, struct pohmelfs_name, hash_node);
rb_node = rb_next(rb_node);
pohmelfs_name_free(parent, n);
}
}
static void pohmelfs_fix_offset(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
{
parent->total_len -= node->len;
}
/*
* Free name cache entry helper.
*/
void pohmelfs_name_del(struct pohmelfs_inode *parent, struct pohmelfs_name *node)
{
pohmelfs_fix_offset(parent, node);
pohmelfs_name_free(parent, node);
}
/*
* Insert new name cache entry into all hash cache.
*/
static int pohmelfs_insert_name(struct pohmelfs_inode *parent, struct pohmelfs_name *n)
{
struct pohmelfs_name *name;
name = pohmelfs_insert_hash(parent, n);
if (name)
return -EEXIST;
parent->total_len += n->len;
list_add_tail(&n->sync_create_entry, &parent->sync_create_list);
return 0;
}
/*
* Allocate new name cache entry.
*/
static struct pohmelfs_name *pohmelfs_name_alloc(unsigned int len)
{
struct pohmelfs_name *n;
n = kzalloc(sizeof(struct pohmelfs_name) + len, GFP_KERNEL);
if (!n)
return NULL;
INIT_LIST_HEAD(&n->sync_create_entry);
n->data = (char *)(n+1);
return n;
}
/*
* Add new name entry into directory's cache.
*/
static int pohmelfs_add_dir(struct pohmelfs_sb *psb, struct pohmelfs_inode *parent,
struct pohmelfs_inode *npi, struct qstr *str, unsigned int mode, int link)
{
int err = -ENOMEM;
struct pohmelfs_name *n;
n = pohmelfs_name_alloc(str->len + 1);
if (!n)
goto err_out_exit;
n->ino = npi->ino;
n->mode = mode;
n->len = str->len;
n->hash = str->hash;
sprintf(n->data, "%s", str->name);
mutex_lock(&parent->offset_lock);
err = pohmelfs_insert_name(parent, n);
mutex_unlock(&parent->offset_lock);
if (err) {
if (err != -EEXIST)
goto err_out_free;
kfree(n);
}
return 0;
err_out_free:
kfree(n);
err_out_exit:
return err;
}
/*
* Create new inode for given parameters (name, inode info, parent).
* This does not create object on the server, it will be synced there during writeback.
*/
struct pohmelfs_inode *pohmelfs_new_inode(struct pohmelfs_sb *psb,
struct pohmelfs_inode *parent, struct qstr *str,
struct netfs_inode_info *info, int link)
{
struct inode *new = NULL;
struct pohmelfs_inode *npi;
int err = -EEXIST;
dprintk("%s: creating inode: parent: %llu, ino: %llu, str: %p.\n",
__func__, (parent) ? parent->ino : 0, info->ino, str);
err = -ENOMEM;
new = iget_locked(psb->sb, info->ino);
if (!new)
goto err_out_exit;
npi = POHMELFS_I(new);
npi->ino = info->ino;
err = 0;
if (new->i_state & I_NEW) {
dprintk("%s: filling VFS inode: %lu/%llu.\n",
__func__, new->i_ino, info->ino);
pohmelfs_fill_inode(new, info);
if (S_ISDIR(info->mode)) {
struct qstr s;
s.name = ".";
s.len = 1;
s.hash = jhash(s.name, s.len, 0);
err = pohmelfs_add_dir(psb, npi, npi, &s, info->mode, 0);
if (err)
goto err_out_put;
s.name = "..";
s.len = 2;
s.hash = jhash(s.name, s.len, 0);
err = pohmelfs_add_dir(psb, npi, (parent) ? parent : npi, &s,
(parent) ? parent->vfs_inode.i_mode : npi->vfs_inode.i_mode, 0);
if (err)
goto err_out_put;
}
}
if (str) {
if (parent) {
err = pohmelfs_add_dir(psb, parent, npi, str, info->mode, link);
dprintk("%s: %s inserted name: '%s', new_offset: %llu, ino: %llu, parent: %llu.\n",
__func__, (err) ? "unsuccessfully" : "successfully",
str->name, parent->total_len, info->ino, parent->ino);
if (err && err != -EEXIST)
goto err_out_put;
}
}
if (new->i_state & I_NEW) {
if (parent)
mark_inode_dirty(&parent->vfs_inode);
mark_inode_dirty(new);
}
set_bit(NETFS_INODE_OWNED, &npi->state);
npi->lock_type = POHMELFS_WRITE_LOCK;
unlock_new_inode(new);
return npi;
err_out_put:
printk("%s: putting inode: %p, npi: %p, error: %d.\n", __func__, new, npi, err);
iput(new);
err_out_exit:
return ERR_PTR(err);
}
static int pohmelfs_remote_sync_complete(struct page **pages, unsigned int page_num,
void *private, int err)
{
struct pohmelfs_inode *pi = private;
struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb);
dprintk("%s: ino: %llu, err: %d.\n", __func__, pi->ino, err);
if (err)
pi->error = err;
wake_up(&psb->wait);
pohmelfs_put_inode(pi);
return err;
}
/*
* Receive directory content from the server.
* This should be only done for objects, which were not created locally,
* and which were not synced previously.
*/
static int pohmelfs_sync_remote_dir(struct pohmelfs_inode *pi)
{
struct inode *inode = &pi->vfs_inode;
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
long ret = psb->wait_on_page_timeout;
int err;
dprintk("%s: dir: %llu, state: %lx: remote_synced: %d.\n",
__func__, pi->ino, pi->state, test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state));
if (test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &pi->state))
return 0;
if (!igrab(inode)) {
err = -ENOENT;
goto err_out_exit;
}
err = pohmelfs_meta_command(pi, NETFS_READDIR, NETFS_TRANS_SINGLE_DST,
pohmelfs_remote_sync_complete, pi, 0);
if (err)
goto err_out_exit;
pi->error = 0;
ret = wait_event_interruptible_timeout(psb->wait,
test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &pi->state) || pi->error, ret);
dprintk("%s: awake dir: %llu, ret: %ld, err: %d.\n", __func__, pi->ino, ret, pi->error);
if (ret <= 0) {
err = ret;
if (!err)
err = -ETIMEDOUT;
goto err_out_exit;
}
if (pi->error)
return pi->error;
return 0;
err_out_exit:
clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state);
return err;
}
static int pohmelfs_dir_open(struct inode *inode, struct file *file)
{
file->private_data = NULL;
return 0;
}
/*
* VFS readdir callback. Syncs directory content from server if needed,
* and provides direntry info to the userspace.
*/
static int pohmelfs_readdir(struct file *file, void *dirent, filldir_t filldir)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
struct pohmelfs_name *n;
struct rb_node *rb_node;
int err = 0, mode;
u64 len;
dprintk("%s: parent: %llu, fpos: %llu, hash: %08lx.\n",
__func__, pi->ino, (u64)file->f_pos,
(unsigned long)file->private_data);
#if 0
err = pohmelfs_data_lock(pi, 0, ~0, POHMELFS_READ_LOCK);
if (err)
return err;
#endif
err = pohmelfs_sync_remote_dir(pi);
if (err)
return err;
if (file->private_data && (file->private_data == (void *)(unsigned long)file->f_pos))
return 0;
mutex_lock(&pi->offset_lock);
n = pohmelfs_search_hash_unprecise(pi, (unsigned long)file->private_data);
while (n) {
mode = (n->mode >> 12) & 15;
dprintk("%s: offset: %llu, parent ino: %llu, name: '%s', len: %u, ino: %llu, "
"mode: %o/%o, fpos: %llu, hash: %08x.\n",
__func__, file->f_pos, pi->ino, n->data, n->len,
n->ino, n->mode, mode, file->f_pos, n->hash);
file->private_data = (void *)(unsigned long)n->hash;
len = n->len;
err = filldir(dirent, n->data, n->len, file->f_pos, n->ino, mode);
if (err < 0) {
dprintk("%s: err: %d.\n", __func__, err);
err = 0;
break;
}
file->f_pos += len;
rb_node = rb_next(&n->hash_node);
if (!rb_node || (rb_node == &n->hash_node)) {
file->private_data = (void *)(unsigned long)file->f_pos;
break;
}
n = rb_entry(rb_node, struct pohmelfs_name, hash_node);
}
mutex_unlock(&pi->offset_lock);
return err;
}
static loff_t pohmelfs_dir_lseek(struct file *file, loff_t offset, int origin)
{
file->f_pos = offset;
file->private_data = NULL;
return offset;
}
const struct file_operations pohmelfs_dir_fops = {
.open = pohmelfs_dir_open,
.read = generic_read_dir,
.llseek = pohmelfs_dir_lseek,
.readdir = pohmelfs_readdir,
};
/*
* Lookup single object on server.
*/
static int pohmelfs_lookup_single(struct pohmelfs_inode *parent,
struct qstr *str, u64 ino)
{
struct pohmelfs_sb *psb = POHMELFS_SB(parent->vfs_inode.i_sb);
long ret = msecs_to_jiffies(5000);
int err;
set_bit(NETFS_COMMAND_PENDING, &parent->state);
err = pohmelfs_meta_command_data(parent, parent->ino, NETFS_LOOKUP,
(char *)str->name, NETFS_TRANS_SINGLE_DST, NULL, NULL, ino);
if (err)
goto err_out_exit;
err = 0;
ret = wait_event_interruptible_timeout(psb->wait,
!test_bit(NETFS_COMMAND_PENDING, &parent->state), ret);
if (ret <= 0) {
err = ret;
if (!err)
err = -ETIMEDOUT;
}
if (err)
goto err_out_exit;
return 0;
err_out_exit:
clear_bit(NETFS_COMMAND_PENDING, &parent->state);
printk("%s: failed: parent: %llu, ino: %llu, name: '%s', err: %d.\n",
__func__, parent->ino, ino, str->name, err);
return err;
}
/*
* VFS lookup callback.
* We first try to get inode number from local name cache, if we have one,
* then inode can be found in inode cache. If there is no inode or no object in
* local cache, try to lookup it on server. This only should be done for directories,
* which were not created locally, otherwise remote server does not know about dir at all,
* so no need to try to know that.
*/
struct dentry *pohmelfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
struct pohmelfs_inode *parent = POHMELFS_I(dir);
struct pohmelfs_name *n;
struct inode *inode = NULL;
unsigned long ino = 0;
int err, lock_type = POHMELFS_READ_LOCK, need_lock = 1;
struct qstr str = dentry->d_name;
if ((nd->intent.open.flags & O_ACCMODE) != O_RDONLY)
lock_type = POHMELFS_WRITE_LOCK;
if (test_bit(NETFS_INODE_OWNED, &parent->state)) {
if (lock_type == parent->lock_type)
need_lock = 0;
if ((lock_type == POHMELFS_READ_LOCK) && (parent->lock_type == POHMELFS_WRITE_LOCK))
need_lock = 0;
}
if ((lock_type == POHMELFS_READ_LOCK) && !test_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &parent->state))
need_lock = 1;
str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
mutex_lock(&parent->offset_lock);
n = pohmelfs_search_hash(parent, str.hash);
if (n)
ino = n->ino;
mutex_unlock(&parent->offset_lock);
dprintk("%s: start ino: %lu, inode: %p, name: '%s', hash: %x, parent_state: %lx, need_lock: %d.\n",
__func__, ino, inode, str.name, str.hash, parent->state, need_lock);
if (ino) {
inode = ilookup(dir->i_sb, ino);
if (inode)
goto out;
}
dprintk("%s: no inode dir: %p, dir_ino: %llu, name: '%s', len: %u, dir_state: %lx, ino: %lu.\n",
__func__, dir, parent->ino,
str.name, str.len, parent->state, ino);
if (!ino) {
if (!need_lock)
goto out;
}
err = pohmelfs_data_lock(parent, 0, ~0, lock_type);
if (err)
goto out;
err = pohmelfs_lookup_single(parent, &str, ino);
if (err)
goto out;
if (!ino) {
mutex_lock(&parent->offset_lock);
n = pohmelfs_search_hash(parent, str.hash);
if (n)
ino = n->ino;
mutex_unlock(&parent->offset_lock);
}
if (ino) {
inode = ilookup(dir->i_sb, ino);
dprintk("%s: second lookup ino: %lu, inode: %p, name: '%s', hash: %x.\n",
__func__, ino, inode, str.name, str.hash);
if (!inode) {
dprintk("%s: No inode for ino: %lu, name: '%s', hash: %x.\n",
__func__, ino, str.name, str.hash);
/* return NULL; */
return ERR_PTR(-EACCES);
}
} else {
printk("%s: No inode number : name: '%s', hash: %x.\n",
__func__, str.name, str.hash);
}
out:
return d_splice_alias(inode, dentry);
}
/*
* Create new object in local cache. Object will be synced to server
* during writeback for given inode.
*/
struct pohmelfs_inode *pohmelfs_create_entry_local(struct pohmelfs_sb *psb,
struct pohmelfs_inode *parent, struct qstr *str, u64 start, umode_t mode)
{
struct pohmelfs_inode *npi;
int err = -ENOMEM;
struct netfs_inode_info info;
dprintk("%s: name: '%s', mode: %ho, start: %llu.\n",
__func__, str->name, mode, start);
info.mode = mode;
info.ino = start;
if (!start)
info.ino = pohmelfs_new_ino(psb);
info.nlink = S_ISDIR(mode) ? 2 : 1;
info.uid = current_fsuid();
info.gid = current_fsgid();
info.size = 0;
info.blocksize = 512;
info.blocks = 0;
info.rdev = 0;
info.version = 0;
npi = pohmelfs_new_inode(psb, parent, str, &info, !!start);
if (IS_ERR(npi)) {
err = PTR_ERR(npi);
goto err_out_unlock;
}
return npi;
err_out_unlock:
dprintk("%s: err: %d.\n", __func__, err);
return ERR_PTR(err);
}
/*
* Create local object and bind it to dentry.
*/
static int pohmelfs_create_entry(struct inode *dir, struct dentry *dentry,
u64 start, umode_t mode)
{
struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb);
struct pohmelfs_inode *npi, *parent;
struct qstr str = dentry->d_name;
int err;
parent = POHMELFS_I(dir);
err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK);
if (err)
return err;
str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
npi = pohmelfs_create_entry_local(psb, parent, &str, start, mode);
if (IS_ERR(npi))
return PTR_ERR(npi);
d_instantiate(dentry, &npi->vfs_inode);
dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n",
__func__, parent->ino, npi->ino, dentry->d_name.name,
(signed)dir->i_nlink, (signed)npi->vfs_inode.i_nlink);
return 0;
}
/*
* VFS create and mkdir callbacks.
*/
static int pohmelfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
struct nameidata *nd)
{
return pohmelfs_create_entry(dir, dentry, 0, mode);
}
static int pohmelfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
int err;
inode_inc_link_count(dir);
err = pohmelfs_create_entry(dir, dentry, 0, mode | S_IFDIR);
if (err)
inode_dec_link_count(dir);
return err;
}
static int pohmelfs_remove_entry(struct inode *dir, struct dentry *dentry)
{
struct pohmelfs_sb *psb = POHMELFS_SB(dir->i_sb);
struct inode *inode = dentry->d_inode;
struct pohmelfs_inode *parent = POHMELFS_I(dir), *pi = POHMELFS_I(inode);
struct pohmelfs_name *n;
int err = -ENOENT;
struct qstr str = dentry->d_name;
err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK);
if (err)
return err;
str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
dprintk("%s: dir_ino: %llu, inode: %llu, name: '%s', nlink: %d.\n",
__func__, parent->ino, pi->ino,
str.name, (signed)inode->i_nlink);
BUG_ON(!inode);
mutex_lock(&parent->offset_lock);
n = pohmelfs_search_hash(parent, str.hash);
if (n) {
pohmelfs_fix_offset(parent, n);
if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state))
pohmelfs_remove_child(pi, n);
pohmelfs_name_free(parent, n);
err = 0;
}
mutex_unlock(&parent->offset_lock);
if (!err) {
psb->avail_size += inode->i_size;
pohmelfs_inode_del_inode(psb, pi);
mark_inode_dirty(dir);
inode->i_ctime = dir->i_ctime;
if (inode->i_nlink)
inode_dec_link_count(inode);
}
return err;
}
/*
* Unlink and rmdir VFS callbacks.
*/
static int pohmelfs_unlink(struct inode *dir, struct dentry *dentry)
{
return pohmelfs_remove_entry(dir, dentry);
}
static int pohmelfs_rmdir(struct inode *dir, struct dentry *dentry)
{
int err;
struct inode *inode = dentry->d_inode;
dprintk("%s: parent: %llu, inode: %llu, name: '%s', parent_nlink: %d, nlink: %d.\n",
__func__, POHMELFS_I(dir)->ino, POHMELFS_I(inode)->ino,
dentry->d_name.name, (signed)dir->i_nlink, (signed)inode->i_nlink);
err = pohmelfs_remove_entry(dir, dentry);
if (!err) {
inode_dec_link_count(dir);
inode_dec_link_count(inode);
}
return err;
}
/*
* Link creation is synchronous.
* I'm lazy.
* Earth is somewhat round.
*/
static int pohmelfs_create_link(struct pohmelfs_inode *parent, struct qstr *obj,
struct pohmelfs_inode *target, struct qstr *tstr)
{
struct super_block *sb = parent->vfs_inode.i_sb;
struct pohmelfs_sb *psb = POHMELFS_SB(sb);
struct netfs_cmd *cmd;
struct netfs_trans *t;
void *data;
int err, parent_len, target_len = 0, cur_len, path_size = 0;
err = pohmelfs_data_lock(parent, 0, ~0, POHMELFS_WRITE_LOCK);
if (err)
return err;
err = sb->s_op->write_inode(&parent->vfs_inode, 0);
if (err)
goto err_out_exit;
if (tstr)
target_len = tstr->len;
parent_len = pohmelfs_path_length(parent);
if (target)
target_len += pohmelfs_path_length(target);
if (parent_len < 0) {
err = parent_len;
goto err_out_exit;
}
if (target_len < 0) {
err = target_len;
goto err_out_exit;
}
t = netfs_trans_alloc(psb, parent_len + target_len + obj->len + 2, 0, 0);
if (!t) {
err = -ENOMEM;
goto err_out_exit;
}
cur_len = netfs_trans_cur_len(t);
cmd = netfs_trans_current(t);
if (IS_ERR(cmd)) {
err = PTR_ERR(cmd);
goto err_out_free;
}
data = (void *)(cmd + 1);
cur_len -= sizeof(struct netfs_cmd);
err = pohmelfs_construct_path_string(parent, data, parent_len);
if (err > 0) {
/* Do not place null-byte before the slash */
path_size = err - 1;
cur_len -= path_size;
err = snprintf(data + path_size, cur_len, "/%s|", obj->name);
path_size += err;
cur_len -= err;
cmd->ext = path_size - 1; /* No | symbol */
if (target) {
err = pohmelfs_construct_path_string(target, data + path_size, target_len);
if (err > 0) {
path_size += err;
cur_len -= err;
}
}
}
if (err < 0)
goto err_out_free;
cmd->start = 0;
if (!target && tstr) {
if (tstr->len > cur_len - 1) {
err = -ENAMETOOLONG;
goto err_out_free;
}
err = snprintf(data + path_size, cur_len, "%s", tstr->name) + 1; /* 0-byte */
path_size += err;
cur_len -= err;
cmd->start = 1;
}
dprintk("%s: parent: %llu, obj: '%s', target_inode: %llu, target_str: '%s', full: '%s'.\n",
__func__, parent->ino, obj->name, (target) ? target->ino : 0, (tstr) ? tstr->name : NULL,
(char *)data);
cmd->cmd = NETFS_LINK;
cmd->size = path_size;
cmd->id = parent->ino;
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, path_size);
err = netfs_trans_finish(t, psb);
if (err)
goto err_out_exit;
return 0;
err_out_free:
t->result = err;
netfs_trans_put(t);
err_out_exit:
return err;
}
/*
* VFS hard and soft link callbacks.
*/
static int pohmelfs_link(struct dentry *old_dentry, struct inode *dir,
struct dentry *dentry)
{
struct inode *inode = old_dentry->d_inode;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
int err;
struct qstr str = dentry->d_name;
str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
err = inode->i_sb->s_op->write_inode(inode, 0);
if (err)
return err;
err = pohmelfs_create_link(POHMELFS_I(dir), &str, pi, NULL);
if (err)
return err;
return pohmelfs_create_entry(dir, dentry, pi->ino, inode->i_mode);
}
static int pohmelfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
struct qstr sym_str;
struct qstr str = dentry->d_name;
struct inode *inode;
int err;
str.hash = jhash(dentry->d_name.name, dentry->d_name.len, 0);
sym_str.name = symname;
sym_str.len = strlen(symname);
err = pohmelfs_create_link(POHMELFS_I(dir), &str, NULL, &sym_str);
if (err)
goto err_out_exit;
err = pohmelfs_create_entry(dir, dentry, 0, S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO);
if (err)
goto err_out_exit;
inode = dentry->d_inode;
err = page_symlink(inode, symname, sym_str.len + 1);
if (err)
goto err_out_put;
return 0;
err_out_put:
iput(inode);
err_out_exit:
return err;
}
static int pohmelfs_send_rename(struct pohmelfs_inode *pi, struct pohmelfs_inode *parent,
struct qstr *str)
{
int path_len, err, total_len = 0, inode_len, parent_len;
char *path;
struct netfs_trans *t;
struct netfs_cmd *cmd;
struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb);
parent_len = pohmelfs_path_length(parent);
inode_len = pohmelfs_path_length(pi);
if (parent_len < 0 || inode_len < 0)
return -EINVAL;
path_len = parent_len + inode_len + str->len + 3;
t = netfs_trans_alloc(psb, path_len, 0, 0);
if (!t)
return -ENOMEM;
cmd = netfs_trans_current(t);
path = (char *)(cmd + 1);
err = pohmelfs_construct_path_string(pi, path, inode_len);
if (err < 0)
goto err_out_unlock;
cmd->ext = err;
path += err;
total_len += err;
path_len -= err;
*path = '|';
path++;
total_len++;
path_len--;
err = pohmelfs_construct_path_string(parent, path, parent_len);
if (err < 0)
goto err_out_unlock;
/*
* Do not place a null-byte before the final slash and the name.
*/
err--;
path += err;
total_len += err;
path_len -= err;
err = snprintf(path, path_len - 1, "/%s", str->name);
total_len += err + 1; /* 0 symbol */
path_len -= err + 1;
cmd->cmd = NETFS_RENAME;
cmd->id = pi->ino;
cmd->start = parent->ino;
cmd->size = total_len;
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, total_len);
return netfs_trans_finish(t, psb);
err_out_unlock:
netfs_trans_free(t);
return err;
}
static int pohmelfs_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
struct inode *inode = old_dentry->d_inode;
struct pohmelfs_inode *old_parent, *pi, *new_parent;
struct qstr str = new_dentry->d_name;
struct pohmelfs_name *n;
unsigned int old_hash;
int err = -ENOENT;
pi = POHMELFS_I(inode);
old_parent = POHMELFS_I(old_dir);
if (new_dir)
new_dir->i_sb->s_op->write_inode(new_dir, 0);
old_hash = jhash(old_dentry->d_name.name, old_dentry->d_name.len, 0);
str.hash = jhash(new_dentry->d_name.name, new_dentry->d_name.len, 0);
str.len = new_dentry->d_name.len;
str.name = new_dentry->d_name.name;
str.hash = jhash(new_dentry->d_name.name, new_dentry->d_name.len, 0);
if (new_dir) {
new_parent = POHMELFS_I(new_dir);
err = -ENOTEMPTY;
if (S_ISDIR(inode->i_mode) &&
new_parent->total_len <= 3)
goto err_out_exit;
} else {
new_parent = old_parent;
}
dprintk("%s: ino: %llu, parent: %llu, name: '%s' -> parent: %llu, name: '%s', i_size: %llu.\n",
__func__, pi->ino, old_parent->ino, old_dentry->d_name.name,
new_parent->ino, new_dentry->d_name.name, inode->i_size);
if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state) &&
test_bit(NETFS_INODE_OWNED, &pi->state)) {
err = pohmelfs_send_rename(pi, new_parent, &str);
if (err)
goto err_out_exit;
}
n = pohmelfs_name_alloc(str.len + 1);
if (!n)
goto err_out_exit;
mutex_lock(&new_parent->offset_lock);
n->ino = pi->ino;
n->mode = inode->i_mode;
n->len = str.len;
n->hash = str.hash;
sprintf(n->data, "%s", str.name);
err = pohmelfs_insert_name(new_parent, n);
mutex_unlock(&new_parent->offset_lock);
if (err)
goto err_out_exit;
mutex_lock(&old_parent->offset_lock);
n = pohmelfs_search_hash(old_parent, old_hash);
if (n)
pohmelfs_name_del(old_parent, n);
mutex_unlock(&old_parent->offset_lock);
mark_inode_dirty(inode);
mark_inode_dirty(&new_parent->vfs_inode);
WARN_ON_ONCE(list_empty(&inode->i_dentry));
return 0;
err_out_exit:
clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state);
return err;
}
/*
* POHMELFS directory inode operations.
*/
const struct inode_operations pohmelfs_dir_inode_ops = {
.link = pohmelfs_link,
.symlink = pohmelfs_symlink,
.unlink = pohmelfs_unlink,
.mkdir = pohmelfs_mkdir,
.rmdir = pohmelfs_rmdir,
.create = pohmelfs_create,
.lookup = pohmelfs_lookup,
.setattr = pohmelfs_setattr,
.rename = pohmelfs_rename,
};
/*
* 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/module.h>
#include <linux/backing-dev.h>
#include <linux/crypto.h>
#include <linux/fs.h>
#include <linux/jhash.h>
#include <linux/hash.h>
#include <linux/ktime.h>
#include <linux/mm.h>
#include <linux/mount.h>
#include <linux/pagemap.h>
#include <linux/pagevec.h>
#include <linux/parser.h>
#include <linux/swap.h>
#include <linux/slab.h>
#include <linux/statfs.h>
#include <linux/writeback.h>
#include <linux/prefetch.h>
#include "netfs.h"
#define POHMELFS_MAGIC_NUM 0x504f482e
static struct kmem_cache *pohmelfs_inode_cache;
static atomic_t psb_bdi_num = ATOMIC_INIT(0);
/*
* Removes inode from all trees, drops local name cache and removes all queued
* requests for object removal.
*/
void pohmelfs_inode_del_inode(struct pohmelfs_sb *psb, struct pohmelfs_inode *pi)
{
mutex_lock(&pi->offset_lock);
pohmelfs_free_names(pi);
mutex_unlock(&pi->offset_lock);
dprintk("%s: deleted stuff in ino: %llu.\n", __func__, pi->ino);
}
/*
* Sync inode to server.
* Returns zero in success and negative error value otherwise.
* It will gather path to root directory into structures containing
* creation mode, permissions and names, so that the whole path
* to given inode could be created using only single network command.
*/
int pohmelfs_write_inode_create(struct inode *inode, struct netfs_trans *trans)
{
struct pohmelfs_inode *pi = POHMELFS_I(inode);
int err = -ENOMEM, size;
struct netfs_cmd *cmd;
void *data;
int cur_len = netfs_trans_cur_len(trans);
if (unlikely(cur_len < 0))
return -ETOOSMALL;
cmd = netfs_trans_current(trans);
cur_len -= sizeof(struct netfs_cmd);
data = (void *)(cmd + 1);
err = pohmelfs_construct_path_string(pi, data, cur_len);
if (err < 0)
goto err_out_exit;
size = err;
cmd->start = i_size_read(inode);
cmd->cmd = NETFS_CREATE;
cmd->size = size;
cmd->id = pi->ino;
cmd->ext = inode->i_mode;
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, trans, size);
return 0;
err_out_exit:
printk("%s: completed ino: %llu, err: %d.\n", __func__, pi->ino, err);
return err;
}
static int pohmelfs_write_trans_complete(struct page **pages, unsigned int page_num,
void *private, int err)
{
unsigned i;
dprintk("%s: pages: %lu-%lu, page_num: %u, err: %d.\n",
__func__, pages[0]->index, pages[page_num-1]->index,
page_num, err);
for (i = 0; i < page_num; i++) {
struct page *page = pages[i];
if (!page)
continue;
end_page_writeback(page);
if (err < 0) {
SetPageError(page);
set_page_dirty(page);
}
unlock_page(page);
page_cache_release(page);
/* dprintk("%s: %3u/%u: page: %p.\n", __func__, i, page_num, page); */
}
return err;
}
static int pohmelfs_inode_has_dirty_pages(struct address_space *mapping, pgoff_t index)
{
int ret;
struct page *page;
rcu_read_lock();
ret = radix_tree_gang_lookup_tag(&mapping->page_tree,
(void **)&page, index, 1, PAGECACHE_TAG_DIRTY);
rcu_read_unlock();
return ret;
}
static int pohmelfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
{
struct inode *inode = mapping->host;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
int err = 0;
int done = 0;
int nr_pages;
pgoff_t index;
pgoff_t end; /* Inclusive */
int scanned = 0;
int range_whole = 0;
if (wbc->range_cyclic) {
index = mapping->writeback_index; /* Start from prev offset */
end = -1;
} else {
index = wbc->range_start >> PAGE_CACHE_SHIFT;
end = wbc->range_end >> PAGE_CACHE_SHIFT;
if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
range_whole = 1;
scanned = 1;
}
retry:
while (!done && (index <= end)) {
unsigned int i = min(end - index, (pgoff_t)psb->trans_max_pages);
int path_len;
struct netfs_trans *trans;
err = pohmelfs_inode_has_dirty_pages(mapping, index);
if (!err)
break;
err = pohmelfs_path_length(pi);
if (err < 0)
break;
path_len = err;
if (path_len <= 2) {
err = -ENOENT;
break;
}
trans = netfs_trans_alloc(psb, path_len, 0, i);
if (!trans) {
err = -ENOMEM;
break;
}
trans->complete = &pohmelfs_write_trans_complete;
trans->page_num = nr_pages = find_get_pages_tag(mapping, &index,
PAGECACHE_TAG_DIRTY, trans->page_num,
trans->pages);
dprintk("%s: t: %p, nr_pages: %u, end: %lu, index: %lu, max: %u.\n",
__func__, trans, nr_pages, end, index, trans->page_num);
if (!nr_pages)
goto err_out_reset;
err = pohmelfs_write_inode_create(inode, trans);
if (err)
goto err_out_reset;
err = 0;
scanned = 1;
for (i = 0; i < trans->page_num; i++) {
struct page *page = trans->pages[i];
lock_page(page);
if (unlikely(page->mapping != mapping))
goto out_continue;
if (!wbc->range_cyclic && page->index > end) {
done = 1;
goto out_continue;
}
if (wbc->sync_mode != WB_SYNC_NONE)
wait_on_page_writeback(page);
if (PageWriteback(page) ||
!clear_page_dirty_for_io(page)) {
dprintk("%s: not clear for io page: %p, writeback: %d.\n",
__func__, page, PageWriteback(page));
goto out_continue;
}
set_page_writeback(page);
trans->attached_size += page_private(page);
trans->attached_pages++;
#if 0
dprintk("%s: %u/%u added trans: %p, gen: %u, page: %p, [High: %d], size: %lu, idx: %lu.\n",
__func__, i, trans->page_num, trans, trans->gen, page,
!!PageHighMem(page), page_private(page), page->index);
#endif
wbc->nr_to_write--;
if (wbc->nr_to_write <= 0)
done = 1;
continue;
out_continue:
unlock_page(page);
trans->pages[i] = NULL;
}
err = netfs_trans_finish(trans, psb);
if (err)
break;
continue;
err_out_reset:
trans->result = err;
netfs_trans_reset(trans);
netfs_trans_put(trans);
break;
}
if (!scanned && !done) {
/*
* We hit the last page and there is more work to be done: wrap
* back to the start of the file
*/
scanned = 1;
index = 0;
goto retry;
}
if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))
mapping->writeback_index = index;
return err;
}
/*
* Inode writeback creation completion callback.
* Only invoked for just created inodes, which do not have pages attached,
* like dirs and empty files.
*/
static int pohmelfs_write_inode_complete(struct page **pages, unsigned int page_num,
void *private, int err)
{
struct inode *inode = private;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
if (inode) {
if (err) {
mark_inode_dirty(inode);
clear_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state);
} else {
set_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state);
}
pohmelfs_put_inode(pi);
}
return err;
}
int pohmelfs_write_create_inode(struct pohmelfs_inode *pi)
{
struct netfs_trans *t;
struct inode *inode = &pi->vfs_inode;
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
int err;
if (test_bit(NETFS_INODE_REMOTE_SYNCED, &pi->state))
return 0;
dprintk("%s: started ino: %llu.\n", __func__, pi->ino);
err = pohmelfs_path_length(pi);
if (err < 0)
goto err_out_exit;
t = netfs_trans_alloc(psb, err + 1, 0, 0);
if (!t) {
err = -ENOMEM;
goto err_out_exit;
}
t->complete = pohmelfs_write_inode_complete;
t->private = igrab(inode);
if (!t->private) {
err = -ENOENT;
goto err_out_put;
}
err = pohmelfs_write_inode_create(inode, t);
if (err)
goto err_out_put;
netfs_trans_finish(t, POHMELFS_SB(inode->i_sb));
return 0;
err_out_put:
t->result = err;
netfs_trans_put(t);
err_out_exit:
return err;
}
/*
* Sync all not-yet-created children in given directory to the server.
*/
static int pohmelfs_write_inode_create_children(struct inode *inode)
{
struct pohmelfs_inode *parent = POHMELFS_I(inode);
struct super_block *sb = inode->i_sb;
struct pohmelfs_name *n;
while (!list_empty(&parent->sync_create_list)) {
n = NULL;
mutex_lock(&parent->offset_lock);
if (!list_empty(&parent->sync_create_list)) {
n = list_first_entry(&parent->sync_create_list,
struct pohmelfs_name, sync_create_entry);
list_del_init(&n->sync_create_entry);
}
mutex_unlock(&parent->offset_lock);
if (!n)
break;
inode = ilookup(sb, n->ino);
dprintk("%s: parent: %llu, ino: %llu, inode: %p.\n",
__func__, parent->ino, n->ino, inode);
if (inode && (inode->i_state & I_DIRTY)) {
struct pohmelfs_inode *pi = POHMELFS_I(inode);
pohmelfs_write_create_inode(pi);
/* pohmelfs_meta_command(pi, NETFS_INODE_INFO, 0, NULL, NULL, 0); */
iput(inode);
}
}
return 0;
}
/*
* Removes given child from given inode on server.
*/
int pohmelfs_remove_child(struct pohmelfs_inode *pi, struct pohmelfs_name *n)
{
return pohmelfs_meta_command_data(pi, pi->ino, NETFS_REMOVE, NULL, 0, NULL, NULL, 0);
}
/*
* Writeback for given inode.
*/
static int pohmelfs_write_inode(struct inode *inode,
struct writeback_control *wbc)
{
struct pohmelfs_inode *pi = POHMELFS_I(inode);
pohmelfs_write_create_inode(pi);
pohmelfs_write_inode_create_children(inode);
return 0;
}
/*
* It is not exported, sorry...
*/
static inline wait_queue_head_t *page_waitqueue(struct page *page)
{
const struct zone *zone = page_zone(page);
return &zone->wait_table[hash_ptr(page, zone->wait_table_bits)];
}
static int pohmelfs_wait_on_page_locked(struct page *page)
{
struct pohmelfs_sb *psb = POHMELFS_SB(page->mapping->host->i_sb);
long ret = psb->wait_on_page_timeout;
DEFINE_WAIT_BIT(wait, &page->flags, PG_locked);
int err = 0;
if (!PageLocked(page))
return 0;
for (;;) {
prepare_to_wait(page_waitqueue(page),
&wait.wait, TASK_INTERRUPTIBLE);
dprintk("%s: page: %p, locked: %d, uptodate: %d, error: %d, flags: %lx.\n",
__func__, page, PageLocked(page), PageUptodate(page),
PageError(page), page->flags);
if (!PageLocked(page))
break;
if (!signal_pending(current)) {
ret = schedule_timeout(ret);
if (!ret)
break;
continue;
}
ret = -ERESTARTSYS;
break;
}
finish_wait(page_waitqueue(page), &wait.wait);
if (!ret)
err = -ETIMEDOUT;
if (!err)
SetPageUptodate(page);
if (err)
printk("%s: page: %p, uptodate: %d, locked: %d, err: %d.\n",
__func__, page, PageUptodate(page), PageLocked(page), err);
return err;
}
static int pohmelfs_read_page_complete(struct page **pages, unsigned int page_num,
void *private, int err)
{
struct page *page = private;
if (PageChecked(page))
return err;
if (err < 0) {
dprintk("%s: page: %p, err: %d.\n", __func__, page, err);
SetPageError(page);
}
unlock_page(page);
return err;
}
/*
* Read a page from remote server.
* Function will wait until page is unlocked.
*/
static int pohmelfs_readpage(struct file *file, struct page *page)
{
struct inode *inode = page->mapping->host;
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
struct pohmelfs_inode *pi = POHMELFS_I(inode);
struct netfs_trans *t;
struct netfs_cmd *cmd;
int err, path_len;
void *data;
u64 isize;
err = pohmelfs_data_lock(pi, page->index << PAGE_CACHE_SHIFT,
PAGE_SIZE, POHMELFS_READ_LOCK);
if (err)
goto err_out_exit;
isize = i_size_read(inode);
if (isize <= page->index << PAGE_CACHE_SHIFT) {
SetPageUptodate(page);
unlock_page(page);
return 0;
}
path_len = pohmelfs_path_length(pi);
if (path_len < 0) {
err = path_len;
goto err_out_exit;
}
t = netfs_trans_alloc(psb, path_len, NETFS_TRANS_SINGLE_DST, 0);
if (!t) {
err = -ENOMEM;
goto err_out_exit;
}
t->complete = pohmelfs_read_page_complete;
t->private = page;
cmd = netfs_trans_current(t);
data = (void *)(cmd + 1);
err = pohmelfs_construct_path_string(pi, data, path_len);
if (err < 0)
goto err_out_free;
path_len = err;
cmd->id = pi->ino;
cmd->start = page->index;
cmd->start <<= PAGE_CACHE_SHIFT;
cmd->size = PAGE_CACHE_SIZE + path_len;
cmd->cmd = NETFS_READ_PAGE;
cmd->ext = path_len;
dprintk("%s: path: '%s', page: %p, ino: %llu, start: %llu, size: %lu.\n",
__func__, (char *)data, page, pi->ino, cmd->start, PAGE_CACHE_SIZE);
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, path_len);
err = netfs_trans_finish(t, psb);
if (err)
goto err_out_return;
return pohmelfs_wait_on_page_locked(page);
err_out_free:
t->result = err;
netfs_trans_put(t);
err_out_exit:
SetPageError(page);
if (PageLocked(page))
unlock_page(page);
err_out_return:
printk("%s: page: %p, start: %lu, size: %lu, err: %d.\n",
__func__, page, page->index << PAGE_CACHE_SHIFT, PAGE_CACHE_SIZE, err);
return err;
}
/*
* Write begin/end magic.
* Allocates a page and writes inode if it was not synced to server before.
*/
static int pohmelfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
{
struct inode *inode = mapping->host;
struct page *page;
pgoff_t index;
unsigned start, end;
int err;
*pagep = NULL;
index = pos >> PAGE_CACHE_SHIFT;
start = pos & (PAGE_CACHE_SIZE - 1);
end = start + len;
page = grab_cache_page(mapping, index);
#if 0
dprintk("%s: page: %p pos: %llu, len: %u, index: %lu, start: %u, end: %u, uptodate: %d.\n",
__func__, page, pos, len, index, start, end, PageUptodate(page));
#endif
if (!page) {
err = -ENOMEM;
goto err_out_exit;
}
while (!PageUptodate(page)) {
if (start && test_bit(NETFS_INODE_REMOTE_SYNCED, &POHMELFS_I(inode)->state)) {
err = pohmelfs_readpage(file, page);
if (err)
goto err_out_exit;
lock_page(page);
continue;
}
if (len != PAGE_CACHE_SIZE) {
void *kaddr = kmap_atomic(page, KM_USER0);
memset(kaddr + start, 0, PAGE_CACHE_SIZE - start);
flush_dcache_page(page);
kunmap_atomic(kaddr, KM_USER0);
}
SetPageUptodate(page);
}
set_page_private(page, end);
*pagep = page;
return 0;
err_out_exit:
page_cache_release(page);
*pagep = NULL;
return err;
}
static int pohmelfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
struct inode *inode = mapping->host;
if (copied != len) {
unsigned from = pos & (PAGE_CACHE_SIZE - 1);
void *kaddr = kmap_atomic(page, KM_USER0);
memset(kaddr + from + copied, 0, len - copied);
flush_dcache_page(page);
kunmap_atomic(kaddr, KM_USER0);
}
SetPageUptodate(page);
set_page_dirty(page);
#if 0
dprintk("%s: page: %p [U: %d, D: %d, L: %d], pos: %llu, len: %u, copied: %u.\n",
__func__, page,
PageUptodate(page), PageDirty(page), PageLocked(page),
pos, len, copied);
#endif
flush_dcache_page(page);
unlock_page(page);
page_cache_release(page);
if (pos + copied > inode->i_size) {
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
psb->avail_size -= pos + copied - inode->i_size;
i_size_write(inode, pos + copied);
}
return copied;
}
static int pohmelfs_readpages_trans_complete(struct page **__pages, unsigned int page_num,
void *private, int err)
{
struct pohmelfs_inode *pi = private;
unsigned int i, num;
struct page **pages, *page = (struct page *)__pages;
loff_t index = page->index;
pages = kzalloc(sizeof(void *) * page_num, GFP_NOIO);
if (!pages)
return -ENOMEM;
num = find_get_pages_contig(pi->vfs_inode.i_mapping, index, page_num, pages);
if (num <= 0) {
err = num;
goto err_out_free;
}
for (i = 0; i < num; ++i) {
page = pages[i];
if (err)
printk("%s: %u/%u: page: %p, index: %lu, uptodate: %d, locked: %d, err: %d.\n",
__func__, i, num, page, page->index,
PageUptodate(page), PageLocked(page), err);
if (!PageChecked(page)) {
if (err < 0)
SetPageError(page);
unlock_page(page);
}
page_cache_release(page);
page_cache_release(page);
}
err_out_free:
kfree(pages);
return err;
}
static int pohmelfs_send_readpages(struct pohmelfs_inode *pi, struct page *first, unsigned int num)
{
struct netfs_trans *t;
struct netfs_cmd *cmd;
struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb);
int err, path_len;
void *data;
err = pohmelfs_data_lock(pi, first->index << PAGE_CACHE_SHIFT,
num * PAGE_SIZE, POHMELFS_READ_LOCK);
if (err)
goto err_out_exit;
path_len = pohmelfs_path_length(pi);
if (path_len < 0) {
err = path_len;
goto err_out_exit;
}
t = netfs_trans_alloc(psb, path_len, NETFS_TRANS_SINGLE_DST, 0);
if (!t) {
err = -ENOMEM;
goto err_out_exit;
}
cmd = netfs_trans_current(t);
data = (void *)(cmd + 1);
t->complete = pohmelfs_readpages_trans_complete;
t->private = pi;
t->page_num = num;
t->pages = (struct page **)first;
err = pohmelfs_construct_path_string(pi, data, path_len);
if (err < 0)
goto err_out_put;
path_len = err;
cmd->cmd = NETFS_READ_PAGES;
cmd->start = first->index;
cmd->start <<= PAGE_CACHE_SHIFT;
cmd->size = (num << 8 | PAGE_CACHE_SHIFT);
cmd->id = pi->ino;
cmd->ext = path_len;
dprintk("%s: t: %p, gen: %u, path: '%s', path_len: %u, "
"start: %lu, num: %u.\n",
__func__, t, t->gen, (char *)data, path_len,
first->index, num);
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, path_len);
return netfs_trans_finish(t, psb);
err_out_put:
netfs_trans_free(t);
err_out_exit:
pohmelfs_readpages_trans_complete((struct page **)first, num, pi, err);
return err;
}
#define list_to_page(head) (list_entry((head)->prev, struct page, lru))
static int pohmelfs_readpages(struct file *file, struct address_space *mapping,
struct list_head *pages, unsigned nr_pages)
{
unsigned int page_idx, num = 0;
struct page *page = NULL, *first = NULL;
for (page_idx = 0; page_idx < nr_pages; page_idx++) {
page = list_to_page(pages);
prefetchw(&page->flags);
list_del(&page->lru);
if (!add_to_page_cache_lru(page, mapping,
page->index, GFP_KERNEL)) {
if (!num) {
num = 1;
first = page;
continue;
}
dprintk("%s: added to lru page: %p, page_index: %lu, first_index: %lu.\n",
__func__, page, page->index, first->index);
if (unlikely(first->index + num != page->index) || (num > 500)) {
pohmelfs_send_readpages(POHMELFS_I(mapping->host),
first, num);
first = page;
num = 0;
}
num++;
}
}
pohmelfs_send_readpages(POHMELFS_I(mapping->host), first, num);
/*
* This will be sync read, so when last page is processed,
* all previous are alerady unlocked and ready to be used.
*/
return 0;
}
/*
* Small address space operations for POHMELFS.
*/
const struct address_space_operations pohmelfs_aops = {
.readpage = pohmelfs_readpage,
.readpages = pohmelfs_readpages,
.writepages = pohmelfs_writepages,
.write_begin = pohmelfs_write_begin,
.write_end = pohmelfs_write_end,
.set_page_dirty = __set_page_dirty_nobuffers,
};
static void pohmelfs_i_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);
kmem_cache_free(pohmelfs_inode_cache, POHMELFS_I(inode));
}
/*
* ->destroy_inode() callback. Deletes inode from the caches
* and frees private data.
*/
static void pohmelfs_destroy_inode(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct pohmelfs_sb *psb = POHMELFS_SB(sb);
struct pohmelfs_inode *pi = POHMELFS_I(inode);
/* pohmelfs_data_unlock(pi, 0, inode->i_size, POHMELFS_READ_LOCK); */
pohmelfs_inode_del_inode(psb, pi);
dprintk("%s: pi: %p, inode: %p, ino: %llu.\n",
__func__, pi, &pi->vfs_inode, pi->ino);
atomic_long_dec(&psb->total_inodes);
call_rcu(&inode->i_rcu, pohmelfs_i_callback);
}
/*
* ->alloc_inode() callback. Allocates inode and initializes private data.
*/
static struct inode *pohmelfs_alloc_inode(struct super_block *sb)
{
struct pohmelfs_inode *pi;
pi = kmem_cache_alloc(pohmelfs_inode_cache, GFP_NOIO);
if (!pi)
return NULL;
pi->hash_root = RB_ROOT;
mutex_init(&pi->offset_lock);
INIT_LIST_HEAD(&pi->sync_create_list);
INIT_LIST_HEAD(&pi->inode_entry);
pi->lock_type = 0;
pi->state = 0;
pi->total_len = 0;
pi->drop_count = 0;
dprintk("%s: pi: %p, inode: %p.\n", __func__, pi, &pi->vfs_inode);
atomic_long_inc(&POHMELFS_SB(sb)->total_inodes);
return &pi->vfs_inode;
}
/*
* We want fsync() to work on POHMELFS.
*/
static int pohmelfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
{
struct inode *inode = file->f_mapping->host;
int err = filemap_write_and_wait_range(inode->i_mapping, start, end);
if (!err) {
mutex_lock(&inode->i_mutex);
err = sync_inode_metadata(inode, 1);
mutex_unlock(&inode->i_mutex);
}
return err;
}
ssize_t pohmelfs_write(struct file *file, const char __user *buf,
size_t len, loff_t *ppos)
{
struct address_space *mapping = file->f_mapping;
struct inode *inode = mapping->host;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
struct kiocb kiocb;
ssize_t ret;
loff_t pos = *ppos;
init_sync_kiocb(&kiocb, file);
kiocb.ki_pos = pos;
kiocb.ki_left = len;
dprintk("%s: len: %zu, pos: %llu.\n", __func__, len, pos);
mutex_lock(&inode->i_mutex);
ret = pohmelfs_data_lock(pi, pos, len, POHMELFS_WRITE_LOCK);
if (ret)
goto err_out_unlock;
ret = __generic_file_aio_write(&kiocb, &iov, 1, &kiocb.ki_pos);
*ppos = kiocb.ki_pos;
mutex_unlock(&inode->i_mutex);
WARN_ON(ret < 0);
if (ret > 0) {
ssize_t err;
err = generic_write_sync(file, pos, ret);
if (err < 0)
ret = err;
WARN_ON(ret < 0);
}
return ret;
err_out_unlock:
mutex_unlock(&inode->i_mutex);
return ret;
}
static const struct file_operations pohmelfs_file_ops = {
.open = generic_file_open,
.fsync = pohmelfs_fsync,
.llseek = generic_file_llseek,
.read = do_sync_read,
.aio_read = generic_file_aio_read,
.mmap = generic_file_mmap,
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
.write = pohmelfs_write,
.aio_write = generic_file_aio_write,
};
const struct inode_operations pohmelfs_symlink_inode_operations = {
.readlink = generic_readlink,
.follow_link = page_follow_link_light,
.put_link = page_put_link,
};
int pohmelfs_setattr_raw(struct inode *inode, struct iattr *attr)
{
int err;
err = inode_change_ok(inode, attr);
if (err) {
dprintk("%s: ino: %llu, inode changes are not allowed.\n", __func__, POHMELFS_I(inode)->ino);
goto err_out_exit;
}
if ((attr->ia_valid & ATTR_SIZE) &&
attr->ia_size != i_size_read(inode)) {
err = vmtruncate(inode, attr->ia_size);
if (err) {
dprintk("%s: ino: %llu, failed to set the attributes.\n", __func__, POHMELFS_I(inode)->ino);
goto err_out_exit;
}
}
setattr_copy(inode, attr);
mark_inode_dirty(inode);
dprintk("%s: ino: %llu, mode: %o -> %o, uid: %u -> %u, gid: %u -> %u, size: %llu -> %llu.\n",
__func__, POHMELFS_I(inode)->ino, inode->i_mode, attr->ia_mode,
inode->i_uid, attr->ia_uid, inode->i_gid, attr->ia_gid, inode->i_size, attr->ia_size);
return 0;
err_out_exit:
return err;
}
int pohmelfs_setattr(struct dentry *dentry, struct iattr *attr)
{
struct inode *inode = dentry->d_inode;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
int err;
err = pohmelfs_data_lock(pi, 0, ~0, POHMELFS_WRITE_LOCK);
if (err)
goto err_out_exit;
err = security_inode_setattr(dentry, attr);
if (err)
goto err_out_exit;
err = pohmelfs_setattr_raw(inode, attr);
if (err)
goto err_out_exit;
return 0;
err_out_exit:
return err;
}
static int pohmelfs_send_xattr_req(struct pohmelfs_inode *pi, u64 id, u64 start,
const char *name, const void *value, size_t attrsize, int command)
{
struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb);
int err, path_len, namelen = strlen(name) + 1; /* 0-byte */
struct netfs_trans *t;
struct netfs_cmd *cmd;
void *data;
dprintk("%s: id: %llu, start: %llu, name: '%s', attrsize: %zu, cmd: %d.\n",
__func__, id, start, name, attrsize, command);
path_len = pohmelfs_path_length(pi);
if (path_len < 0) {
err = path_len;
goto err_out_exit;
}
t = netfs_trans_alloc(psb, namelen + path_len + attrsize, 0, 0);
if (!t) {
err = -ENOMEM;
goto err_out_exit;
}
cmd = netfs_trans_current(t);
data = cmd + 1;
path_len = pohmelfs_construct_path_string(pi, data, path_len);
if (path_len < 0) {
err = path_len;
goto err_out_put;
}
data += path_len;
/*
* 'name' is a NUL-terminated string already and
* 'namelen' includes 0-byte.
*/
memcpy(data, name, namelen);
data += namelen;
memcpy(data, value, attrsize);
cmd->cmd = command;
cmd->id = id;
cmd->start = start;
cmd->size = attrsize + namelen + path_len;
cmd->ext = path_len;
cmd->csize = 0;
cmd->cpad = 0;
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, namelen + path_len + attrsize);
return netfs_trans_finish(t, psb);
err_out_put:
t->result = err;
netfs_trans_put(t);
err_out_exit:
return err;
}
static int pohmelfs_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t attrsize, int flags)
{
struct inode *inode = dentry->d_inode;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
if (!(psb->state_flags & POHMELFS_FLAGS_XATTR))
return -EOPNOTSUPP;
return pohmelfs_send_xattr_req(pi, flags, attrsize, name,
value, attrsize, NETFS_XATTR_SET);
}
static ssize_t pohmelfs_getxattr(struct dentry *dentry, const char *name,
void *value, size_t attrsize)
{
struct inode *inode = dentry->d_inode;
struct pohmelfs_inode *pi = POHMELFS_I(inode);
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
struct pohmelfs_mcache *m;
int err;
long timeout = psb->mcache_timeout;
if (!(psb->state_flags & POHMELFS_FLAGS_XATTR))
return -EOPNOTSUPP;
m = pohmelfs_mcache_alloc(psb, 0, attrsize, value);
if (IS_ERR(m))
return PTR_ERR(m);
dprintk("%s: ino: %llu, name: '%s', size: %zu.\n",
__func__, pi->ino, name, attrsize);
err = pohmelfs_send_xattr_req(pi, m->gen, attrsize, name, value, 0, NETFS_XATTR_GET);
if (err)
goto err_out_put;
do {
err = wait_for_completion_timeout(&m->complete, timeout);
if (err) {
err = m->err;
break;
}
/*
* This loop is a bit ugly, since it waits until reference counter
* hits 1 and then puts the object here. Main goal is to prevent race with
* the network thread, when it can start processing the given request, i.e.
* increase its reference counter but yet not complete it, while
* we will exit from ->getxattr() with timeout, and although request
* will not be freed (its reference counter was increased by network
* thread), data pointer provided by user may be released, so we will
* overwrite an already freed area in the network thread.
*
* Now after timeout we remove request from the cache, so it can not be
* found by network thread, and wait for its reference counter to hit 1,
* i.e. if network thread already started to process this request, we wait
* for it to finish, and then free object locally. If reference counter is
* already 1, i.e. request is not used by anyone else, we can free it without
* problem.
*/
err = -ETIMEDOUT;
timeout = HZ;
pohmelfs_mcache_remove_locked(psb, m);
} while (atomic_read(&m->refcnt) != 1);
pohmelfs_mcache_put(psb, m);
dprintk("%s: ino: %llu, err: %d.\n", __func__, pi->ino, err);
return err;
err_out_put:
pohmelfs_mcache_put(psb, m);
return err;
}
static int pohmelfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
struct inode *inode = dentry->d_inode;
#if 0
struct pohmelfs_inode *pi = POHMELFS_I(inode);
int err;
err = pohmelfs_data_lock(pi, 0, ~0, POHMELFS_READ_LOCK);
if (err)
return err;
dprintk("%s: ino: %llu, mode: %o, uid: %u, gid: %u, size: %llu.\n",
__func__, pi->ino, inode->i_mode, inode->i_uid,
inode->i_gid, inode->i_size);
#endif
generic_fillattr(inode, stat);
return 0;
}
const struct inode_operations pohmelfs_file_inode_operations = {
.setattr = pohmelfs_setattr,
.getattr = pohmelfs_getattr,
.setxattr = pohmelfs_setxattr,
.getxattr = pohmelfs_getxattr,
};
/*
* Fill inode data: mode, size, operation callbacks and so on...
*/
void pohmelfs_fill_inode(struct inode *inode, struct netfs_inode_info *info)
{
inode->i_mode = info->mode;
set_nlink(inode, info->nlink);
inode->i_uid = info->uid;
inode->i_gid = info->gid;
inode->i_blocks = info->blocks;
inode->i_rdev = info->rdev;
inode->i_size = info->size;
inode->i_version = info->version;
inode->i_blkbits = ffs(info->blocksize);
dprintk("%s: inode: %p, num: %lu/%llu inode is regular: %d, dir: %d, link: %d, mode: %o, size: %llu.\n",
__func__, inode, inode->i_ino, info->ino,
S_ISREG(inode->i_mode), S_ISDIR(inode->i_mode),
S_ISLNK(inode->i_mode), inode->i_mode, inode->i_size);
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC;
/*
* i_mapping is a pointer to i_data during inode initialization.
*/
inode->i_data.a_ops = &pohmelfs_aops;
if (S_ISREG(inode->i_mode)) {
inode->i_fop = &pohmelfs_file_ops;
inode->i_op = &pohmelfs_file_inode_operations;
} else if (S_ISDIR(inode->i_mode)) {
inode->i_fop = &pohmelfs_dir_fops;
inode->i_op = &pohmelfs_dir_inode_ops;
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &pohmelfs_symlink_inode_operations;
inode->i_fop = &pohmelfs_file_ops;
} else {
inode->i_fop = &generic_ro_fops;
}
}
static int pohmelfs_drop_inode(struct inode *inode)
{
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
struct pohmelfs_inode *pi = POHMELFS_I(inode);
spin_lock(&psb->ino_lock);
list_del_init(&pi->inode_entry);
spin_unlock(&psb->ino_lock);
return generic_drop_inode(inode);
}
static struct pohmelfs_inode *pohmelfs_get_inode_from_list(struct pohmelfs_sb *psb,
struct list_head *head, unsigned int *count)
{
struct pohmelfs_inode *pi = NULL;
spin_lock(&psb->ino_lock);
if (!list_empty(head)) {
pi = list_entry(head->next, struct pohmelfs_inode,
inode_entry);
list_del_init(&pi->inode_entry);
*count = pi->drop_count;
pi->drop_count = 0;
}
spin_unlock(&psb->ino_lock);
return pi;
}
static void pohmelfs_flush_transactions(struct pohmelfs_sb *psb)
{
struct pohmelfs_config *c;
mutex_lock(&psb->state_lock);
list_for_each_entry(c, &psb->state_list, config_entry) {
pohmelfs_state_flush_transactions(&c->state);
}
mutex_unlock(&psb->state_lock);
}
/*
* ->put_super() callback. Invoked before superblock is destroyed,
* so it has to clean all private data.
*/
static void pohmelfs_put_super(struct super_block *sb)
{
struct pohmelfs_sb *psb = POHMELFS_SB(sb);
struct pohmelfs_inode *pi;
unsigned int count = 0;
unsigned int in_drop_list = 0;
struct inode *inode, *tmp;
dprintk("%s.\n", __func__);
/*
* Kill pending transactions, which could affect inodes in-flight.
*/
pohmelfs_flush_transactions(psb);
while ((pi = pohmelfs_get_inode_from_list(psb, &psb->drop_list, &count))) {
inode = &pi->vfs_inode;
dprintk("%s: ino: %llu, pi: %p, inode: %p, count: %u.\n",
__func__, pi->ino, pi, inode, count);
if (atomic_read(&inode->i_count) != count) {
printk("%s: ino: %llu, pi: %p, inode: %p, count: %u, i_count: %d.\n",
__func__, pi->ino, pi, inode, count,
atomic_read(&inode->i_count));
count = atomic_read(&inode->i_count);
in_drop_list++;
}
while (count--)
iput(&pi->vfs_inode);
}
list_for_each_entry_safe(inode, tmp, &sb->s_inodes, i_sb_list) {
pi = POHMELFS_I(inode);
dprintk("%s: ino: %llu, pi: %p, inode: %p, i_count: %u.\n",
__func__, pi->ino, pi, inode, atomic_read(&inode->i_count));
/*
* These are special inodes, they were created during
* directory reading or lookup, and were not bound to dentry,
* so they live here with reference counter being 1 and prevent
* umount from succeed since it believes that they are busy.
*/
count = atomic_read(&inode->i_count);
if (count) {
list_del_init(&inode->i_sb_list);
while (count--)
iput(&pi->vfs_inode);
}
}
psb->trans_scan_timeout = psb->drop_scan_timeout = 0;
cancel_delayed_work_sync(&psb->dwork);
cancel_delayed_work_sync(&psb->drop_dwork);
flush_scheduled_work();
dprintk("%s: stopped workqueues.\n", __func__);
pohmelfs_crypto_exit(psb);
pohmelfs_state_exit(psb);
bdi_destroy(&psb->bdi);
kfree(psb);
sb->s_fs_info = NULL;
}
static int pohmelfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct super_block *sb = dentry->d_sb;
struct pohmelfs_sb *psb = POHMELFS_SB(sb);
/*
* There are no filesystem size limits yet.
*/
memset(buf, 0, sizeof(struct kstatfs));
buf->f_type = POHMELFS_MAGIC_NUM; /* 'POH.' */
buf->f_bsize = sb->s_blocksize;
buf->f_files = psb->ino;
buf->f_namelen = 255;
buf->f_files = atomic_long_read(&psb->total_inodes);
buf->f_bfree = buf->f_bavail = psb->avail_size >> PAGE_SHIFT;
buf->f_blocks = psb->total_size >> PAGE_SHIFT;
dprintk("%s: total: %llu, avail: %llu, inodes: %llu, bsize: %lu.\n",
__func__, psb->total_size, psb->avail_size, buf->f_files, sb->s_blocksize);
return 0;
}
static int pohmelfs_show_options(struct seq_file *seq, struct dentry *root)
{
struct pohmelfs_sb *psb = POHMELFS_SB(root->d_sb);
seq_printf(seq, ",idx=%u", psb->idx);
seq_printf(seq, ",trans_scan_timeout=%u", jiffies_to_msecs(psb->trans_scan_timeout));
seq_printf(seq, ",drop_scan_timeout=%u", jiffies_to_msecs(psb->drop_scan_timeout));
seq_printf(seq, ",wait_on_page_timeout=%u", jiffies_to_msecs(psb->wait_on_page_timeout));
seq_printf(seq, ",trans_retries=%u", psb->trans_retries);
seq_printf(seq, ",crypto_thread_num=%u", psb->crypto_thread_num);
seq_printf(seq, ",trans_max_pages=%u", psb->trans_max_pages);
seq_printf(seq, ",mcache_timeout=%u", jiffies_to_msecs(psb->mcache_timeout));
if (psb->crypto_fail_unsupported)
seq_printf(seq, ",crypto_fail_unsupported");
return 0;
}
enum {
pohmelfs_opt_idx,
pohmelfs_opt_crypto_thread_num,
pohmelfs_opt_trans_max_pages,
pohmelfs_opt_crypto_fail_unsupported,
/* Remountable options */
pohmelfs_opt_trans_scan_timeout,
pohmelfs_opt_drop_scan_timeout,
pohmelfs_opt_wait_on_page_timeout,
pohmelfs_opt_trans_retries,
pohmelfs_opt_mcache_timeout,
};
static struct match_token pohmelfs_tokens[] = {
{pohmelfs_opt_idx, "idx=%u"},
{pohmelfs_opt_crypto_thread_num, "crypto_thread_num=%u"},
{pohmelfs_opt_trans_max_pages, "trans_max_pages=%u"},
{pohmelfs_opt_crypto_fail_unsupported, "crypto_fail_unsupported"},
{pohmelfs_opt_trans_scan_timeout, "trans_scan_timeout=%u"},
{pohmelfs_opt_drop_scan_timeout, "drop_scan_timeout=%u"},
{pohmelfs_opt_wait_on_page_timeout, "wait_on_page_timeout=%u"},
{pohmelfs_opt_trans_retries, "trans_retries=%u"},
{pohmelfs_opt_mcache_timeout, "mcache_timeout=%u"},
};
static int pohmelfs_parse_options(char *options, struct pohmelfs_sb *psb, int remount)
{
char *p;
substring_t args[MAX_OPT_ARGS];
int option, err;
if (!options)
return 0;
while ((p = strsep(&options, ",")) != NULL) {
int token;
if (!*p)
continue;
token = match_token(p, pohmelfs_tokens, args);
err = match_int(&args[0], &option);
if (err)
return err;
if (remount && token <= pohmelfs_opt_crypto_fail_unsupported)
continue;
switch (token) {
case pohmelfs_opt_idx:
psb->idx = option;
break;
case pohmelfs_opt_trans_scan_timeout:
psb->trans_scan_timeout = msecs_to_jiffies(option);
break;
case pohmelfs_opt_drop_scan_timeout:
psb->drop_scan_timeout = msecs_to_jiffies(option);
break;
case pohmelfs_opt_wait_on_page_timeout:
psb->wait_on_page_timeout = msecs_to_jiffies(option);
break;
case pohmelfs_opt_mcache_timeout:
psb->mcache_timeout = msecs_to_jiffies(option);
break;
case pohmelfs_opt_trans_retries:
psb->trans_retries = option;
break;
case pohmelfs_opt_crypto_thread_num:
psb->crypto_thread_num = option;
break;
case pohmelfs_opt_trans_max_pages:
psb->trans_max_pages = option;
break;
case pohmelfs_opt_crypto_fail_unsupported:
psb->crypto_fail_unsupported = 1;
break;
default:
return -EINVAL;
}
}
return 0;
}
static int pohmelfs_remount(struct super_block *sb, int *flags, char *data)
{
int err;
struct pohmelfs_sb *psb = POHMELFS_SB(sb);
unsigned long old_sb_flags = sb->s_flags;
err = pohmelfs_parse_options(data, psb, 1);
if (err)
goto err_out_restore;
if (!(*flags & MS_RDONLY))
sb->s_flags &= ~MS_RDONLY;
return 0;
err_out_restore:
sb->s_flags = old_sb_flags;
return err;
}
static void pohmelfs_flush_inode(struct pohmelfs_inode *pi, unsigned int count)
{
struct inode *inode = &pi->vfs_inode;
dprintk("%s: %p: ino: %llu, owned: %d.\n",
__func__, inode, pi->ino, test_bit(NETFS_INODE_OWNED, &pi->state));
mutex_lock(&inode->i_mutex);
if (test_and_clear_bit(NETFS_INODE_OWNED, &pi->state)) {
filemap_fdatawrite(inode->i_mapping);
inode->i_sb->s_op->write_inode(inode, 0);
}
#ifdef POHMELFS_TRUNCATE_ON_INODE_FLUSH
truncate_inode_pages(inode->i_mapping, 0);
#endif
pohmelfs_data_unlock(pi, 0, ~0, POHMELFS_WRITE_LOCK);
mutex_unlock(&inode->i_mutex);
}
static void pohmelfs_put_inode_count(struct pohmelfs_inode *pi, unsigned int count)
{
dprintk("%s: ino: %llu, pi: %p, inode: %p, count: %u.\n",
__func__, pi->ino, pi, &pi->vfs_inode, count);
if (test_and_clear_bit(NETFS_INODE_NEED_FLUSH, &pi->state))
pohmelfs_flush_inode(pi, count);
while (count--)
iput(&pi->vfs_inode);
}
static void pohmelfs_drop_scan(struct work_struct *work)
{
struct pohmelfs_sb *psb =
container_of(work, struct pohmelfs_sb, drop_dwork.work);
struct pohmelfs_inode *pi;
unsigned int count = 0;
while ((pi = pohmelfs_get_inode_from_list(psb, &psb->drop_list, &count)))
pohmelfs_put_inode_count(pi, count);
pohmelfs_check_states(psb);
if (psb->drop_scan_timeout)
schedule_delayed_work(&psb->drop_dwork, psb->drop_scan_timeout);
}
/*
* Run through all transactions starting from the oldest,
* drop transaction from current state and try to send it
* to all remote nodes, which are currently installed.
*/
static void pohmelfs_trans_scan_state(struct netfs_state *st)
{
struct rb_node *rb_node;
struct netfs_trans_dst *dst;
struct pohmelfs_sb *psb = st->psb;
unsigned int timeout = psb->trans_scan_timeout;
struct netfs_trans *t;
int err;
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);
t = dst->trans;
if (timeout && time_after(dst->send_time + timeout, jiffies)
&& dst->retries == 0)
break;
dprintk("%s: t: %p, gen: %u, st: %p, retries: %u, max: %u.\n",
__func__, t, t->gen, st, dst->retries, psb->trans_retries);
netfs_trans_get(t);
rb_node = rb_next(rb_node);
err = -ETIMEDOUT;
if (timeout && (++dst->retries < psb->trans_retries))
err = netfs_trans_resend(t, psb);
if (err || (t->flags & NETFS_TRANS_SINGLE_DST)) {
if (netfs_trans_remove_nolock(dst, st))
netfs_trans_drop_dst_nostate(dst);
}
t->result = err;
netfs_trans_put(t);
}
mutex_unlock(&st->trans_lock);
}
/*
* Walk through all installed network states and resend all
* transactions, which are old enough.
*/
static void pohmelfs_trans_scan(struct work_struct *work)
{
struct pohmelfs_sb *psb =
container_of(work, struct pohmelfs_sb, dwork.work);
struct netfs_state *st;
struct pohmelfs_config *c;
mutex_lock(&psb->state_lock);
list_for_each_entry(c, &psb->state_list, config_entry) {
st = &c->state;
pohmelfs_trans_scan_state(st);
}
mutex_unlock(&psb->state_lock);
/*
* If no timeout specified then system is in the middle of umount process,
* so no need to reschedule scanning process again.
*/
if (psb->trans_scan_timeout)
schedule_delayed_work(&psb->dwork, psb->trans_scan_timeout);
}
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)
{
struct inode *inode = &pi->vfs_inode;
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
int err = 0, sz;
struct netfs_trans *t;
int path_len, addon_len = 0;
void *data;
struct netfs_inode_info *info;
struct netfs_cmd *cmd;
dprintk("%s: ino: %llu, cmd: %u, addon: %p.\n", __func__, pi->ino, cmd_op, addon);
path_len = pohmelfs_path_length(pi);
if (path_len < 0) {
err = path_len;
goto err_out_exit;
}
if (addon)
addon_len = strlen(addon) + 1; /* 0-byte */
sz = addon_len;
if (cmd_op == NETFS_INODE_INFO)
sz += sizeof(struct netfs_inode_info);
t = netfs_trans_alloc(psb, sz + path_len, flags, 0);
if (!t) {
err = -ENOMEM;
goto err_out_exit;
}
t->complete = complete;
t->private = priv;
cmd = netfs_trans_current(t);
data = (void *)(cmd + 1);
if (cmd_op == NETFS_INODE_INFO) {
info = (struct netfs_inode_info *)(cmd + 1);
data = (void *)(info + 1);
/*
* We are under i_mutex, can read and change whatever we want...
*/
info->mode = inode->i_mode;
info->nlink = inode->i_nlink;
info->uid = inode->i_uid;
info->gid = inode->i_gid;
info->blocks = inode->i_blocks;
info->rdev = inode->i_rdev;
info->size = inode->i_size;
info->version = inode->i_version;
netfs_convert_inode_info(info);
}
path_len = pohmelfs_construct_path_string(pi, data, path_len);
if (path_len < 0)
goto err_out_free;
dprintk("%s: path_len: %d.\n", __func__, path_len);
if (addon) {
path_len--; /* Do not place null-byte before the addon */
path_len += sprintf(data + path_len, "/%s", addon) + 1; /* 0 - byte */
}
sz += path_len;
cmd->cmd = cmd_op;
cmd->ext = path_len;
cmd->size = sz;
cmd->id = id;
cmd->start = start;
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, sz);
/*
* Note, that it is possible to leak error here: transaction callback will not
* be invoked for allocation path failure.
*/
return netfs_trans_finish(t, psb);
err_out_free:
netfs_trans_free(t);
err_out_exit:
if (complete)
complete(NULL, 0, priv, err);
return err;
}
int pohmelfs_meta_command(struct pohmelfs_inode *pi, unsigned int cmd_op, unsigned int flags,
netfs_trans_complete_t complete, void *priv, u64 start)
{
return pohmelfs_meta_command_data(pi, pi->ino, cmd_op, NULL, flags, complete, priv, start);
}
/*
* Send request and wait for POHMELFS root capabilities response,
* which will update server's informaion about size of the export,
* permissions, number of objects, available size and so on.
*/
static int pohmelfs_root_handshake(struct pohmelfs_sb *psb)
{
struct netfs_trans *t;
struct netfs_cmd *cmd;
int err = -ENOMEM;
t = netfs_trans_alloc(psb, 0, 0, 0);
if (!t)
goto err_out_exit;
cmd = netfs_trans_current(t);
cmd->cmd = NETFS_CAPABILITIES;
cmd->id = POHMELFS_ROOT_CAPABILITIES;
cmd->size = 0;
cmd->start = 0;
cmd->ext = 0;
cmd->csize = 0;
netfs_convert_cmd(cmd);
netfs_trans_update(cmd, t, 0);
err = netfs_trans_finish(t, psb);
if (err)
goto err_out_exit;
psb->flags = ~0;
err = wait_event_interruptible_timeout(psb->wait,
(psb->flags != ~0),
psb->wait_on_page_timeout);
if (!err)
err = -ETIMEDOUT;
else if (err > 0)
err = -psb->flags;
if (err)
goto err_out_exit;
return 0;
err_out_exit:
return err;
}
static int pohmelfs_show_stats(struct seq_file *m, struct dentry *root)
{
struct netfs_state *st;
struct pohmelfs_ctl *ctl;
struct pohmelfs_sb *psb = POHMELFS_SB(root->d_sb);
struct pohmelfs_config *c;
mutex_lock(&psb->state_lock);
seq_printf(m, "\nidx addr(:port) socket_type protocol active priority permissions\n");
list_for_each_entry(c, &psb->state_list, config_entry) {
st = &c->state;
ctl = &st->ctl;
seq_printf(m, "%u ", ctl->idx);
if (ctl->addr.sa_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)&st->ctl.addr;
seq_printf(m, "%pI4:%u", &sin->sin_addr.s_addr, ntohs(sin->sin_port));
} else if (ctl->addr.sa_family == AF_INET6) {
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&st->ctl.addr;
seq_printf(m, "%pi6:%u", &sin->sin6_addr, ntohs(sin->sin6_port));
} else {
unsigned int i;
for (i = 0; i < ctl->addrlen; ++i)
seq_printf(m, "%02x.", ctl->addr.addr[i]);
}
seq_printf(m, " %u %u %d %u %x\n",
ctl->type, ctl->proto,
st->socket != NULL,
ctl->prio, ctl->perm);
}
mutex_unlock(&psb->state_lock);
return 0;
}
static const struct super_operations pohmelfs_sb_ops = {
.alloc_inode = pohmelfs_alloc_inode,
.destroy_inode = pohmelfs_destroy_inode,
.drop_inode = pohmelfs_drop_inode,
.write_inode = pohmelfs_write_inode,
.put_super = pohmelfs_put_super,
.remount_fs = pohmelfs_remount,
.statfs = pohmelfs_statfs,
.show_options = pohmelfs_show_options,
.show_stats = pohmelfs_show_stats,
};
/*
* Allocate private superblock and create root dir.
*/
static int pohmelfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct pohmelfs_sb *psb;
int err = -ENOMEM;
struct inode *root;
struct pohmelfs_inode *npi;
struct qstr str;
psb = kzalloc(sizeof(struct pohmelfs_sb), GFP_KERNEL);
if (!psb)
goto err_out_exit;
err = bdi_init(&psb->bdi);
if (err)
goto err_out_free_sb;
err = bdi_register(&psb->bdi, NULL, "pfs-%d", atomic_inc_return(&psb_bdi_num));
if (err) {
bdi_destroy(&psb->bdi);
goto err_out_free_sb;
}
sb->s_fs_info = psb;
sb->s_op = &pohmelfs_sb_ops;
sb->s_magic = POHMELFS_MAGIC_NUM;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE;
sb->s_bdi = &psb->bdi;
psb->sb = sb;
psb->ino = 2;
psb->idx = 0;
psb->active_state = NULL;
psb->trans_retries = 5;
psb->trans_data_size = PAGE_SIZE;
psb->drop_scan_timeout = msecs_to_jiffies(1000);
psb->trans_scan_timeout = msecs_to_jiffies(5000);
psb->wait_on_page_timeout = msecs_to_jiffies(5000);
init_waitqueue_head(&psb->wait);
spin_lock_init(&psb->ino_lock);
INIT_LIST_HEAD(&psb->drop_list);
mutex_init(&psb->mcache_lock);
psb->mcache_root = RB_ROOT;
psb->mcache_timeout = msecs_to_jiffies(5000);
atomic_long_set(&psb->mcache_gen, 0);
psb->trans_max_pages = 100;
psb->crypto_align_size = 16;
psb->crypto_attached_size = 0;
psb->hash_strlen = 0;
psb->cipher_strlen = 0;
psb->perform_crypto = 0;
psb->crypto_thread_num = 2;
psb->crypto_fail_unsupported = 0;
mutex_init(&psb->crypto_thread_lock);
INIT_LIST_HEAD(&psb->crypto_ready_list);
INIT_LIST_HEAD(&psb->crypto_active_list);
atomic_set(&psb->trans_gen, 1);
atomic_long_set(&psb->total_inodes, 0);
mutex_init(&psb->state_lock);
INIT_LIST_HEAD(&psb->state_list);
err = pohmelfs_parse_options((char *) data, psb, 0);
if (err)
goto err_out_free_bdi;
err = pohmelfs_copy_crypto(psb);
if (err)
goto err_out_free_bdi;
err = pohmelfs_state_init(psb);
if (err)
goto err_out_free_strings;
err = pohmelfs_crypto_init(psb);
if (err)
goto err_out_state_exit;
err = pohmelfs_root_handshake(psb);
if (err)
goto err_out_crypto_exit;
str.name = "/";
str.hash = jhash("/", 1, 0);
str.len = 1;
npi = pohmelfs_create_entry_local(psb, NULL, &str, 0, 0755|S_IFDIR);
if (IS_ERR(npi)) {
err = PTR_ERR(npi);
goto err_out_crypto_exit;
}
set_bit(NETFS_INODE_REMOTE_SYNCED, &npi->state);
clear_bit(NETFS_INODE_OWNED, &npi->state);
root = &npi->vfs_inode;
sb->s_root = d_alloc_root(root);
if (!sb->s_root)
goto err_out_put_root;
INIT_DELAYED_WORK(&psb->drop_dwork, pohmelfs_drop_scan);
schedule_delayed_work(&psb->drop_dwork, psb->drop_scan_timeout);
INIT_DELAYED_WORK(&psb->dwork, pohmelfs_trans_scan);
schedule_delayed_work(&psb->dwork, psb->trans_scan_timeout);
return 0;
err_out_put_root:
iput(root);
err_out_crypto_exit:
pohmelfs_crypto_exit(psb);
err_out_state_exit:
pohmelfs_state_exit(psb);
err_out_free_strings:
kfree(psb->cipher_string);
kfree(psb->hash_string);
err_out_free_bdi:
bdi_destroy(&psb->bdi);
err_out_free_sb:
kfree(psb);
err_out_exit:
dprintk("%s: err: %d.\n", __func__, err);
return err;
}
/*
* Some VFS magic here...
*/
static struct dentry *pohmelfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_nodev(fs_type, flags, data, pohmelfs_fill_super);
}
/*
* We need this to sync all inodes earlier, since when writeback
* is invoked from the umount/mntput path dcache is already shrunk,
* see generic_shutdown_super(), and no inodes can access the path.
*/
static void pohmelfs_kill_super(struct super_block *sb)
{
sync_inodes_sb(sb);
kill_anon_super(sb);
}
static struct file_system_type pohmel_fs_type = {
.owner = THIS_MODULE,
.name = "pohmel",
.mount = pohmelfs_mount,
.kill_sb = pohmelfs_kill_super,
};
/*
* Cache and module initializations and freeing routings.
*/
static void pohmelfs_init_once(void *data)
{
struct pohmelfs_inode *pi = data;
inode_init_once(&pi->vfs_inode);
}
static int __init pohmelfs_init_inodecache(void)
{
pohmelfs_inode_cache = kmem_cache_create("pohmelfs_inode_cache",
sizeof(struct pohmelfs_inode),
0, (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD),
pohmelfs_init_once);
if (!pohmelfs_inode_cache)
return -ENOMEM;
return 0;
}
static void pohmelfs_destroy_inodecache(void)
{
kmem_cache_destroy(pohmelfs_inode_cache);
}
static int __init init_pohmel_fs(void)
{
int err;
err = pohmelfs_config_init();
if (err)
goto err_out_exit;
err = pohmelfs_init_inodecache();
if (err)
goto err_out_config_exit;
err = pohmelfs_mcache_init();
if (err)
goto err_out_destroy;
err = netfs_trans_init();
if (err)
goto err_out_mcache_exit;
err = register_filesystem(&pohmel_fs_type);
if (err)
goto err_out_trans;
return 0;
err_out_trans:
netfs_trans_exit();
err_out_mcache_exit:
pohmelfs_mcache_exit();
err_out_destroy:
pohmelfs_destroy_inodecache();
err_out_config_exit:
pohmelfs_config_exit();
err_out_exit:
return err;
}
static void __exit exit_pohmel_fs(void)
{
unregister_filesystem(&pohmel_fs_type);
pohmelfs_destroy_inodecache();
pohmelfs_mcache_exit();
pohmelfs_config_exit();
netfs_trans_exit();
}
module_init(init_pohmel_fs);
module_exit(exit_pohmel_fs);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
MODULE_DESCRIPTION("Pohmel filesystem");
/*
* 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/module.h>
#include <linux/backing-dev.h>
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/mempool.h>
#include "netfs.h"
static int pohmelfs_send_lock_trans(struct pohmelfs_inode *pi,
u64 id, u64 start, u32 size, int type)
{
struct inode *inode = &pi->vfs_inode;
struct pohmelfs_sb *psb = POHMELFS_SB(inode->i_sb);
struct netfs_trans *t;
struct netfs_cmd *cmd;
int path_len, err;
void *data;
struct netfs_lock *l;
int isize = (type & POHMELFS_LOCK_GRAB) ? 0 : sizeof(struct netfs_inode_info);
err = pohmelfs_path_length(pi);
if (err < 0)
goto err_out_exit;
path_len = err;
err = -ENOMEM;
t = netfs_trans_alloc(psb, path_len + sizeof(struct netfs_lock) + isize,
NETFS_TRANS_SINGLE_DST, 0);
if (!t)
goto err_out_exit;
cmd = netfs_trans_current(t);
data = cmd + 1;
err = pohmelfs_construct_path_string(pi, data, path_len);
if (err < 0)
goto err_out_free;
path_len = err;
l = data + path_len;
l->start = start;
l->size = size;
l->type = type;
l->ino = pi->ino;
cmd->cmd = NETFS_LOCK;
cmd->start = 0;
cmd->id = id;
cmd->size = sizeof(struct netfs_lock) + path_len + isize;
cmd->ext = path_len;
cmd->csize = 0;
netfs_convert_cmd(cmd);
netfs_convert_lock(l);
if (isize) {
struct netfs_inode_info *info = (struct netfs_inode_info *)(l + 1);
info->mode = inode->i_mode;
info->nlink = inode->i_nlink;
info->uid = inode->i_uid;
info->gid = inode->i_gid;
info->blocks = inode->i_blocks;
info->rdev = inode->i_rdev;
info->size = inode->i_size;
info->version = inode->i_version;
netfs_convert_inode_info(info);
}
netfs_trans_update(cmd, t, path_len + sizeof(struct netfs_lock) + isize);
return netfs_trans_finish(t, psb);
err_out_free:
netfs_trans_free(t);
err_out_exit:
printk("%s: err: %d.\n", __func__, err);
return err;
}
int pohmelfs_data_lock(struct pohmelfs_inode *pi, u64 start, u32 size, int type)
{
struct pohmelfs_sb *psb = POHMELFS_SB(pi->vfs_inode.i_sb);
struct pohmelfs_mcache *m;
int err = -ENOMEM;
struct iattr iattr;
struct inode *inode = &pi->vfs_inode;
dprintk("%s: %p: ino: %llu, start: %llu, size: %u, "
"type: %d, locked as: %d, owned: %d.\n",
__func__, &pi->vfs_inode, pi->ino,
start, size, type, pi->lock_type,
!!test_bit(NETFS_INODE_OWNED, &pi->state));
if (!pohmelfs_need_lock(pi, type))
return 0;
m = pohmelfs_mcache_alloc(psb, start, size, NULL);
if (IS_ERR(m))
return PTR_ERR(m);
err = pohmelfs_send_lock_trans(pi, m->gen, start, size,
type | POHMELFS_LOCK_GRAB);
if (err)
goto err_out_put;
err = wait_for_completion_timeout(&m->complete, psb->mcache_timeout);
if (err)
err = m->err;
else
err = -ETIMEDOUT;
if (err) {
printk("%s: %p: ino: %llu, mgen: %llu, start: %llu, size: %u, err: %d.\n",
__func__, &pi->vfs_inode, pi->ino, m->gen, start, size, err);
}
if (err && (err != -ENOENT))
goto err_out_put;
if (!err) {
netfs_convert_inode_info(&m->info);
iattr.ia_valid = ATTR_MODE | ATTR_UID | ATTR_GID | ATTR_SIZE | ATTR_ATIME;
iattr.ia_mode = m->info.mode;
iattr.ia_uid = m->info.uid;
iattr.ia_gid = m->info.gid;
iattr.ia_size = m->info.size;
iattr.ia_atime = CURRENT_TIME;
dprintk("%s: %p: ino: %llu, mgen: %llu, start: %llu, isize: %llu -> %llu.\n",
__func__, &pi->vfs_inode, pi->ino, m->gen, start, inode->i_size, m->info.size);
err = pohmelfs_setattr_raw(inode, &iattr);
if (!err) {
struct dentry *dentry = d_find_alias(inode);
if (dentry) {
fsnotify_change(dentry, iattr.ia_valid);
dput(dentry);
}
}
}
pi->lock_type = type;
set_bit(NETFS_INODE_OWNED, &pi->state);
pohmelfs_mcache_put(psb, m);
return 0;
err_out_put:
pohmelfs_mcache_put(psb, m);
return err;
}
int pohmelfs_data_unlock(struct pohmelfs_inode *pi, u64 start, u32 size, int type)
{
dprintk("%s: %p: ino: %llu, start: %llu, size: %u, type: %d.\n",
__func__, &pi->vfs_inode, pi->ino, start, size, type);
pi->lock_type = 0;
clear_bit(NETFS_INODE_REMOTE_DIR_SYNCED, &pi->state);
clear_bit(NETFS_INODE_OWNED, &pi->state);
return pohmelfs_send_lock_trans(pi, pi->ino, start, size, type);
}
/*
* 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/module.h>
#include <linux/slab.h>
#include <linux/mempool.h>
#include "netfs.h"
static struct kmem_cache *pohmelfs_mcache_cache;
static mempool_t *pohmelfs_mcache_pool;
static inline int pohmelfs_mcache_cmp(u64 gen, u64 new)
{
if (gen < new)
return 1;
if (gen > new)
return -1;
return 0;
}
struct pohmelfs_mcache *pohmelfs_mcache_search(struct pohmelfs_sb *psb, u64 gen)
{
struct rb_root *root = &psb->mcache_root;
struct rb_node *n = root->rb_node;
struct pohmelfs_mcache *tmp, *ret = NULL;
int cmp;
while (n) {
tmp = rb_entry(n, struct pohmelfs_mcache, mcache_entry);
cmp = pohmelfs_mcache_cmp(tmp->gen, gen);
if (cmp < 0)
n = n->rb_left;
else if (cmp > 0)
n = n->rb_right;
else {
ret = tmp;
pohmelfs_mcache_get(ret);
break;
}
}
return ret;
}
static int pohmelfs_mcache_insert(struct pohmelfs_sb *psb, struct pohmelfs_mcache *m)
{
struct rb_root *root = &psb->mcache_root;
struct rb_node **n = &root->rb_node, *parent = NULL;
struct pohmelfs_mcache *ret = NULL, *tmp;
int cmp;
while (*n) {
parent = *n;
tmp = rb_entry(parent, struct pohmelfs_mcache, mcache_entry);
cmp = pohmelfs_mcache_cmp(tmp->gen, m->gen);
if (cmp < 0)
n = &parent->rb_left;
else if (cmp > 0)
n = &parent->rb_right;
else {
ret = tmp;
break;
}
}
if (ret)
return -EEXIST;
rb_link_node(&m->mcache_entry, parent, n);
rb_insert_color(&m->mcache_entry, root);
return 0;
}
static int pohmelfs_mcache_remove(struct pohmelfs_sb *psb, struct pohmelfs_mcache *m)
{
if (m && m->mcache_entry.rb_parent_color) {
rb_erase(&m->mcache_entry, &psb->mcache_root);
m->mcache_entry.rb_parent_color = 0;
return 1;
}
return 0;
}
void pohmelfs_mcache_remove_locked(struct pohmelfs_sb *psb, struct pohmelfs_mcache *m)
{
mutex_lock(&psb->mcache_lock);
pohmelfs_mcache_remove(psb, m);
mutex_unlock(&psb->mcache_lock);
}
struct pohmelfs_mcache *pohmelfs_mcache_alloc(struct pohmelfs_sb *psb, u64 start,
unsigned int size, void *data)
{
struct pohmelfs_mcache *m;
int err = -ENOMEM;
m = mempool_alloc(pohmelfs_mcache_pool, GFP_KERNEL);
if (!m)
goto err_out_exit;
init_completion(&m->complete);
m->err = 0;
atomic_set(&m->refcnt, 1);
m->data = data;
m->start = start;
m->size = size;
m->gen = atomic_long_inc_return(&psb->mcache_gen);
mutex_lock(&psb->mcache_lock);
err = pohmelfs_mcache_insert(psb, m);
mutex_unlock(&psb->mcache_lock);
if (err)
goto err_out_free;
return m;
err_out_free:
mempool_free(m, pohmelfs_mcache_pool);
err_out_exit:
return ERR_PTR(err);
}
void pohmelfs_mcache_free(struct pohmelfs_sb *psb, struct pohmelfs_mcache *m)
{
pohmelfs_mcache_remove_locked(psb, m);
mempool_free(m, pohmelfs_mcache_pool);
}
int __init pohmelfs_mcache_init(void)
{
pohmelfs_mcache_cache = kmem_cache_create("pohmelfs_mcache_cache",
sizeof(struct pohmelfs_mcache),
0, (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD), NULL);
if (!pohmelfs_mcache_cache)
goto err_out_exit;
pohmelfs_mcache_pool = mempool_create_slab_pool(256, pohmelfs_mcache_cache);
if (!pohmelfs_mcache_pool)
goto err_out_free;
return 0;
err_out_free:
kmem_cache_destroy(pohmelfs_mcache_cache);
err_out_exit:
return -ENOMEM;
}
void pohmelfs_mcache_exit(void)
{
mempool_destroy(pohmelfs_mcache_pool);
kmem_cache_destroy(pohmelfs_mcache_cache);
}
/*
* 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/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);
}
}
/*
* 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>
#include <linux/backing-dev.h>
#define POHMELFS_CN_IDX 5
#define POHMELFS_CN_VAL 0
#define POHMELFS_CTLINFO_ACK 1
#define POHMELFS_NOINFO_ACK 2
#define POHMELFS_NULL_IDX 65535
/*
* 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 */
POHMELFS_FLAGS_MODIFY, /* Network state modification message */
POHMELFS_FLAGS_DUMP, /* Network state control message for SHOW ALL */
POHMELFS_FLAGS_FLUSH, /* Network state control message for FLUSH */
};
/*
* 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 */
};
#define POHMELFS_IO_PERM_READ (1<<0)
#define POHMELFS_IO_PERM_WRITE (1<<1)
/*
* Configuration command used to create table of different remote servers.
*/
struct pohmelfs_ctl {
__u32 idx; /* Config index */
__u32 type; /* Socket type */
__u32 proto; /* Socket protocol */
__u16 addrlen; /* Size of the address */
__u16 perm; /* IO permission */
__u16 prio; /* IO priority */
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 information (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;
struct backing_dev_info bdi;
/*
* 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, umode_t 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);
}
/*#define POHMELFS_TRUNCATE_ON_INODE_FLUSH
*/
#endif /* __KERNEL__*/
#endif /* __NETFS_H */
/*
* 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/module.h>
#include <linux/fs.h>
#include <linux/ktime.h>
#include <linux/fs_struct.h>
#include <linux/pagemap.h>
#include <linux/writeback.h>
#include <linux/mount.h>
#include <linux/mm.h>
#include "netfs.h"
#define UNHASHED_OBSCURE_STRING_SIZE sizeof(" (deleted)")
/*
* Create path from root for given inode.
* Path is formed as set of stuctures, containing name of the object
* and its inode data (mode, permissions and so on).
*/
int pohmelfs_construct_path_string(struct pohmelfs_inode *pi, void *data, int len)
{
struct path path;
struct dentry *d;
char *ptr;
int err = 0, strlen, reduce = 0;
d = d_find_alias(&pi->vfs_inode);
if (!d) {
printk("%s: no alias, list_empty: %d.\n", __func__, list_empty(&pi->vfs_inode.i_dentry));
return -ENOENT;
}
spin_lock(&current->fs->lock);
path.mnt = mntget(current->fs->root.mnt);
spin_unlock(&current->fs->lock);
path.dentry = d;
if (!IS_ROOT(d) && d_unhashed(d))
reduce = 1;
ptr = d_path(&path, data, len);
if (IS_ERR(ptr)) {
err = PTR_ERR(ptr);
goto out;
}
if (reduce && len >= UNHASHED_OBSCURE_STRING_SIZE) {
char *end = data + len - UNHASHED_OBSCURE_STRING_SIZE;
*end = '\0';
}
strlen = len - (ptr - (char *)data);
memmove(data, ptr, strlen);
ptr = data;
err = strlen;
dprintk("%s: dname: '%s', len: %u, maxlen: %u, name: '%s', strlen: %d.\n",
__func__, d->d_name.name, d->d_name.len, len, ptr, strlen);
out:
dput(d);
mntput(path.mnt);
return err;
}
int pohmelfs_path_length(struct pohmelfs_inode *pi)
{
struct dentry *d, *root, *first;
int len;
unsigned seq;
first = d_find_alias(&pi->vfs_inode);
if (!first) {
dprintk("%s: ino: %llu, mode: %o.\n", __func__, pi->ino, pi->vfs_inode.i_mode);
return -ENOENT;
}
spin_lock(&current->fs->lock);
root = dget(current->fs->root.dentry);
spin_unlock(&current->fs->lock);
rename_retry:
len = 1; /* Root slash */
d = first;
seq = read_seqbegin(&rename_lock);
rcu_read_lock();
if (!IS_ROOT(d) && d_unhashed(d))
len += UNHASHED_OBSCURE_STRING_SIZE; /* Obscure " (deleted)" string */
while (d && d != root && !IS_ROOT(d)) {
len += d->d_name.len + 1; /* Plus slash */
d = d->d_parent;
}
rcu_read_unlock();
if (read_seqretry(&rename_lock, seq))
goto rename_retry;
dput(root);
dput(first);
return len + 1; /* Including zero-byte */
}
/*
* 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/module.h>
#include <linux/crypto.h>
#include <linux/fs.h>
#include <linux/jhash.h>
#include <linux/hash.h>
#include <linux/ktime.h>
#include <linux/mempool.h>
#include <linux/mm.h>
#include <linux/mount.h>
#include <linux/pagemap.h>
#include <linux/parser.h>
#include <linux/poll.h>
#include <linux/swap.h>
#include <linux/slab.h>
#include <linux/statfs.h>
#include <linux/writeback.h>
#include "netfs.h"
static struct kmem_cache *netfs_trans_dst;
static mempool_t *netfs_trans_dst_pool;
static void netfs_trans_init_static(struct netfs_trans *t, int num, int size)
{
t->page_num = num;
t->total_size = size;
atomic_set(&t->refcnt, 1);
spin_lock_init(&t->dst_lock);
INIT_LIST_HEAD(&t->dst_list);
}
static int netfs_trans_send_pages(struct netfs_trans *t, struct netfs_state *st)
{
int err = 0;
unsigned int i, attached_pages = t->attached_pages, ci;
struct msghdr msg;
struct page **pages = (t->eng) ? t->eng->pages : t->pages;
struct page *p;
unsigned int size;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = MSG_WAITALL | MSG_MORE;
ci = 0;
for (i = 0; i < t->page_num; ++i) {
struct page *page = pages[ci];
struct netfs_cmd cmd;
struct iovec io;
p = t->pages[i];
if (!p)
continue;
size = page_private(p);
io.iov_base = &cmd;
io.iov_len = sizeof(struct netfs_cmd);
cmd.cmd = NETFS_WRITE_PAGE;
cmd.ext = 0;
cmd.id = 0;
cmd.size = size;
cmd.start = p->index;
cmd.start <<= PAGE_CACHE_SHIFT;
cmd.csize = 0;
cmd.cpad = 0;
cmd.iv = pohmelfs_gen_iv(t);
netfs_convert_cmd(&cmd);
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_flags = MSG_WAITALL | MSG_MORE;
err = kernel_sendmsg(st->socket, &msg, (struct kvec *)msg.msg_iov, 1, sizeof(struct netfs_cmd));
if (err <= 0) {
printk("%s: %d/%d failed to send transaction header: t: %p, gen: %u, err: %d.\n",
__func__, i, t->page_num, t, t->gen, err);
if (err == 0)
err = -ECONNRESET;
goto err_out;
}
msg.msg_flags = MSG_WAITALL | (attached_pages == 1 ? 0 :
MSG_MORE);
err = kernel_sendpage(st->socket, page, 0, size, msg.msg_flags);
if (err <= 0) {
printk("%s: %d/%d failed to send transaction page: t: %p, gen: %u, size: %u, err: %d.\n",
__func__, i, t->page_num, t, t->gen, size, err);
if (err == 0)
err = -ECONNRESET;
goto err_out;
}
dprintk("%s: %d/%d sent t: %p, gen: %u, page: %p/%p, size: %u.\n",
__func__, i, t->page_num, t, t->gen, page, p, size);
err = 0;
attached_pages--;
if (!attached_pages)
break;
ci++;
continue;
err_out:
printk("%s: t: %p, gen: %u, err: %d.\n", __func__, t, t->gen, err);
netfs_state_exit(st);
break;
}
return err;
}
int netfs_trans_send(struct netfs_trans *t, struct netfs_state *st)
{
int err;
struct msghdr msg;
BUG_ON(!t->iovec.iov_len);
BUG_ON(t->iovec.iov_len > 1024*1024*1024);
netfs_state_lock_send(st);
if (!st->socket) {
err = netfs_state_init(st);
if (err)
goto err_out_unlock_return;
}
msg.msg_iov = &t->iovec;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = MSG_WAITALL;
if (t->attached_pages)
msg.msg_flags |= MSG_MORE;
err = kernel_sendmsg(st->socket, &msg, (struct kvec *)msg.msg_iov, 1, t->iovec.iov_len);
if (err <= 0) {
printk("%s: failed to send contig transaction: t: %p, gen: %u, size: %zu, err: %d.\n",
__func__, t, t->gen, t->iovec.iov_len, err);
if (err == 0)
err = -ECONNRESET;
goto err_out_unlock_return;
}
dprintk("%s: sent %s transaction: t: %p, gen: %u, size: %zu, page_num: %u.\n",
__func__, (t->page_num) ? "partial" : "full",
t, t->gen, t->iovec.iov_len, t->page_num);
err = 0;
if (t->attached_pages)
err = netfs_trans_send_pages(t, st);
err_out_unlock_return:
if (st->need_reset)
netfs_state_exit(st);
netfs_state_unlock_send(st);
dprintk("%s: t: %p, gen: %u, err: %d.\n",
__func__, t, t->gen, err);
t->result = err;
return err;
}
static inline int netfs_trans_cmp(unsigned int gen, unsigned int new)
{
if (gen < new)
return 1;
if (gen > new)
return -1;
return 0;
}
struct netfs_trans_dst *netfs_trans_search(struct netfs_state *st, unsigned int gen)
{
struct rb_root *root = &st->trans_root;
struct rb_node *n = root->rb_node;
struct netfs_trans_dst *tmp, *ret = NULL;
struct netfs_trans *t;
int cmp;
while (n) {
tmp = rb_entry(n, struct netfs_trans_dst, state_entry);
t = tmp->trans;
cmp = netfs_trans_cmp(t->gen, gen);
if (cmp < 0)
n = n->rb_left;
else if (cmp > 0)
n = n->rb_right;
else {
ret = tmp;
break;
}
}
return ret;
}
static int netfs_trans_insert(struct netfs_trans_dst *ndst, struct netfs_state *st)
{
struct rb_root *root = &st->trans_root;
struct rb_node **n = &root->rb_node, *parent = NULL;
struct netfs_trans_dst *ret = NULL, *tmp;
struct netfs_trans *t = NULL, *new = ndst->trans;
int cmp;
while (*n) {
parent = *n;
tmp = rb_entry(parent, struct netfs_trans_dst, state_entry);
t = tmp->trans;
cmp = netfs_trans_cmp(t->gen, new->gen);
if (cmp < 0)
n = &parent->rb_left;
else if (cmp > 0)
n = &parent->rb_right;
else {
ret = tmp;
break;
}
}
if (ret) {
printk("%s: exist: old: gen: %u, flags: %x, send_time: %lu, "
"new: gen: %u, flags: %x, send_time: %lu.\n",
__func__, t->gen, t->flags, ret->send_time,
new->gen, new->flags, ndst->send_time);
return -EEXIST;
}
rb_link_node(&ndst->state_entry, parent, n);
rb_insert_color(&ndst->state_entry, root);
ndst->send_time = jiffies;
return 0;
}
int netfs_trans_remove_nolock(struct netfs_trans_dst *dst, struct netfs_state *st)
{
if (dst && dst->state_entry.rb_parent_color) {
rb_erase(&dst->state_entry, &st->trans_root);
dst->state_entry.rb_parent_color = 0;
return 1;
}
return 0;
}
static int netfs_trans_remove_state(struct netfs_trans_dst *dst)
{
int ret;
struct netfs_state *st = dst->state;
mutex_lock(&st->trans_lock);
ret = netfs_trans_remove_nolock(dst, st);
mutex_unlock(&st->trans_lock);
return ret;
}
/*
* Create new destination for given transaction associated with given network state.
* Transaction's reference counter is bumped and will be dropped when either
* reply is received or when async timeout detection task will fail resending
* and drop transaction.
*/
static int netfs_trans_push_dst(struct netfs_trans *t, struct netfs_state *st)
{
struct netfs_trans_dst *dst;
int err;
dst = mempool_alloc(netfs_trans_dst_pool, GFP_KERNEL);
if (!dst)
return -ENOMEM;
dst->retries = 0;
dst->send_time = 0;
dst->state = st;
dst->trans = t;
netfs_trans_get(t);
mutex_lock(&st->trans_lock);
err = netfs_trans_insert(dst, st);
mutex_unlock(&st->trans_lock);
if (err)
goto err_out_free;
spin_lock(&t->dst_lock);
list_add_tail(&dst->trans_entry, &t->dst_list);
spin_unlock(&t->dst_lock);
return 0;
err_out_free:
t->result = err;
netfs_trans_put(t);
mempool_free(dst, netfs_trans_dst_pool);
return err;
}
static void netfs_trans_free_dst(struct netfs_trans_dst *dst)
{
netfs_trans_put(dst->trans);
mempool_free(dst, netfs_trans_dst_pool);
}
static void netfs_trans_remove_dst(struct netfs_trans_dst *dst)
{
if (netfs_trans_remove_state(dst))
netfs_trans_free_dst(dst);
}
/*
* Drop destination transaction entry when we know it.
*/
void netfs_trans_drop_dst(struct netfs_trans_dst *dst)
{
struct netfs_trans *t = dst->trans;
spin_lock(&t->dst_lock);
list_del_init(&dst->trans_entry);
spin_unlock(&t->dst_lock);
netfs_trans_remove_dst(dst);
}
/*
* Drop destination transaction entry when we know it and when we
* already removed dst from state tree.
*/
void netfs_trans_drop_dst_nostate(struct netfs_trans_dst *dst)
{
struct netfs_trans *t = dst->trans;
spin_lock(&t->dst_lock);
list_del_init(&dst->trans_entry);
spin_unlock(&t->dst_lock);
netfs_trans_free_dst(dst);
}
/*
* This drops destination transaction entry from appropriate network state
* tree and drops related reference counter. It is possible that transaction
* will be freed here if its reference counter hits zero.
* Destination transaction entry will be freed.
*/
void netfs_trans_drop_trans(struct netfs_trans *t, struct netfs_state *st)
{
struct netfs_trans_dst *dst, *tmp, *ret = NULL;
spin_lock(&t->dst_lock);
list_for_each_entry_safe(dst, tmp, &t->dst_list, trans_entry) {
if (dst->state == st) {
ret = dst;
list_del(&dst->trans_entry);
break;
}
}
spin_unlock(&t->dst_lock);
if (ret)
netfs_trans_remove_dst(ret);
}
/*
* This drops destination transaction entry from appropriate network state
* tree and drops related reference counter. It is possible that transaction
* will be freed here if its reference counter hits zero.
* Destination transaction entry will be freed.
*/
void netfs_trans_drop_last(struct netfs_trans *t, struct netfs_state *st)
{
struct netfs_trans_dst *dst, *tmp, *ret;
spin_lock(&t->dst_lock);
ret = list_entry(t->dst_list.prev, struct netfs_trans_dst, trans_entry);
if (ret->state != st) {
ret = NULL;
list_for_each_entry_safe(dst, tmp, &t->dst_list, trans_entry) {
if (dst->state == st) {
ret = dst;
list_del_init(&dst->trans_entry);
break;
}
}
} else {
list_del(&ret->trans_entry);
}
spin_unlock(&t->dst_lock);
if (ret)
netfs_trans_remove_dst(ret);
}
static int netfs_trans_push(struct netfs_trans *t, struct netfs_state *st)
{
int err;
err = netfs_trans_push_dst(t, st);
if (err)
return err;
err = netfs_trans_send(t, st);
if (err)
goto err_out_free;
if (t->flags & NETFS_TRANS_SINGLE_DST)
pohmelfs_switch_active(st->psb);
return 0;
err_out_free:
t->result = err;
netfs_trans_drop_last(t, st);
return err;
}
int netfs_trans_finish_send(struct netfs_trans *t, struct pohmelfs_sb *psb)
{
struct pohmelfs_config *c;
int err = -ENODEV;
struct netfs_state *st;
#if 0
dprintk("%s: t: %p, gen: %u, size: %u, page_num: %u, active: %p.\n",
__func__, t, t->gen, t->iovec.iov_len, t->page_num, psb->active_state);
#endif
mutex_lock(&psb->state_lock);
list_for_each_entry(c, &psb->state_list, config_entry) {
st = &c->state;
if (t->flags & NETFS_TRANS_SINGLE_DST) {
if (!(st->ctl.perm & POHMELFS_IO_PERM_READ))
continue;
} else {
if (!(st->ctl.perm & POHMELFS_IO_PERM_WRITE))
continue;
}
if (psb->active_state && (psb->active_state->state.ctl.prio >= st->ctl.prio) &&
(t->flags & NETFS_TRANS_SINGLE_DST))
st = &psb->active_state->state;
err = netfs_trans_push(t, st);
if (!err && (t->flags & NETFS_TRANS_SINGLE_DST))
break;
}
mutex_unlock(&psb->state_lock);
#if 0
dprintk("%s: fully sent t: %p, gen: %u, size: %u, page_num: %u, err: %d.\n",
__func__, t, t->gen, t->iovec.iov_len, t->page_num, err);
#endif
if (err)
t->result = err;
return err;
}
int netfs_trans_finish(struct netfs_trans *t, struct pohmelfs_sb *psb)
{
int err;
struct netfs_cmd *cmd = t->iovec.iov_base;
t->gen = atomic_inc_return(&psb->trans_gen);
cmd->size = t->iovec.iov_len - sizeof(struct netfs_cmd) +
t->attached_size + t->attached_pages * sizeof(struct netfs_cmd);
cmd->cmd = NETFS_TRANS;
cmd->start = t->gen;
cmd->id = 0;
if (psb->perform_crypto) {
cmd->ext = psb->crypto_attached_size;
cmd->csize = psb->crypto_attached_size;
}
dprintk("%s: t: %u, size: %u, iov_len: %zu, attached_size: %u, attached_pages: %u.\n",
__func__, t->gen, cmd->size, t->iovec.iov_len, t->attached_size, t->attached_pages);
err = pohmelfs_trans_crypt(t, psb);
if (err) {
t->result = err;
netfs_convert_cmd(cmd);
dprintk("%s: trans: %llu, crypto_attached_size: %u, attached_size: %u, attached_pages: %d, trans_size: %u, err: %d.\n",
__func__, cmd->start, psb->crypto_attached_size, t->attached_size, t->attached_pages, cmd->size, err);
}
netfs_trans_put(t);
return err;
}
/*
* Resend transaction to remote server(s).
* If new servers were added into superblock, we can try to send data
* to them too.
*
* It is called under superblock's state_lock, so we can safely
* dereference psb->state_list. Also, transaction's reference counter is
* bumped, so it can not go away under us, thus we can safely access all
* its members. State is locked.
*
* This function returns 0 if transaction was successfully sent to at
* least one destination target.
*/
int netfs_trans_resend(struct netfs_trans *t, struct pohmelfs_sb *psb)
{
struct netfs_trans_dst *dst;
struct netfs_state *st;
struct pohmelfs_config *c;
int err, exist, error = -ENODEV;
list_for_each_entry(c, &psb->state_list, config_entry) {
st = &c->state;
exist = 0;
spin_lock(&t->dst_lock);
list_for_each_entry(dst, &t->dst_list, trans_entry) {
if (st == dst->state) {
exist = 1;
break;
}
}
spin_unlock(&t->dst_lock);
if (exist) {
if (!(t->flags & NETFS_TRANS_SINGLE_DST) ||
(c->config_entry.next == &psb->state_list)) {
dprintk("%s: resending st: %p, t: %p, gen: %u.\n",
__func__, st, t, t->gen);
err = netfs_trans_send(t, st);
if (!err)
error = 0;
}
continue;
}
dprintk("%s: pushing/resending st: %p, t: %p, gen: %u.\n",
__func__, st, t, t->gen);
err = netfs_trans_push(t, st);
if (err)
continue;
error = 0;
if (t->flags & NETFS_TRANS_SINGLE_DST)
break;
}
t->result = error;
return error;
}
void *netfs_trans_add(struct netfs_trans *t, unsigned int size)
{
struct iovec *io = &t->iovec;
void *ptr;
if (size > t->total_size) {
ptr = ERR_PTR(-EINVAL);
goto out;
}
if (io->iov_len + size > t->total_size) {
dprintk("%s: too big size t: %p, gen: %u, iov_len: %zu, size: %u, total: %u.\n",
__func__, t, t->gen, io->iov_len, size, t->total_size);
ptr = ERR_PTR(-E2BIG);
goto out;
}
ptr = io->iov_base + io->iov_len;
io->iov_len += size;
out:
dprintk("%s: t: %p, gen: %u, size: %u, total: %zu.\n",
__func__, t, t->gen, size, io->iov_len);
return ptr;
}
void netfs_trans_free(struct netfs_trans *t)
{
if (t->eng)
pohmelfs_crypto_thread_make_ready(t->eng->thread);
kfree(t);
}
struct netfs_trans *netfs_trans_alloc(struct pohmelfs_sb *psb, unsigned int size,
unsigned int flags, unsigned int nr)
{
struct netfs_trans *t;
unsigned int num, cont, pad, size_no_trans;
unsigned int crypto_added = 0;
struct netfs_cmd *cmd;
if (psb->perform_crypto)
crypto_added = psb->crypto_attached_size;
/*
* |sizeof(struct netfs_trans)|
* |sizeof(struct netfs_cmd)| - transaction header
* |size| - buffer with requested size
* |padding| - crypto padding, zero bytes
* |nr * sizeof(struct page *)| - array of page pointers
*
* Overall size should be less than PAGE_SIZE for guaranteed allocation.
*/
cont = size;
size = ALIGN(size, psb->crypto_align_size);
pad = size - cont;
size_no_trans = size + sizeof(struct netfs_cmd) * 2 + crypto_added;
cont = sizeof(struct netfs_trans) + size_no_trans;
num = (PAGE_SIZE - cont)/sizeof(struct page *);
if (nr > num)
nr = num;
t = kzalloc(cont + nr*sizeof(struct page *), GFP_NOIO);
if (!t)
goto err_out_exit;
t->iovec.iov_base = (void *)(t + 1);
t->pages = (struct page **)(t->iovec.iov_base + size_no_trans);
/*
* Reserving space for transaction header.
*/
t->iovec.iov_len = sizeof(struct netfs_cmd) + crypto_added;
netfs_trans_init_static(t, nr, size_no_trans);
t->flags = flags;
t->psb = psb;
cmd = (struct netfs_cmd *)t->iovec.iov_base;
cmd->size = size;
cmd->cpad = pad;
cmd->csize = crypto_added;
dprintk("%s: t: %p, gen: %u, size: %u, padding: %u, align_size: %u, flags: %x, "
"page_num: %u, base: %p, pages: %p.\n",
__func__, t, t->gen, size, pad, psb->crypto_align_size, flags, nr,
t->iovec.iov_base, t->pages);
return t;
err_out_exit:
return NULL;
}
int netfs_trans_init(void)
{
int err = -ENOMEM;
netfs_trans_dst = kmem_cache_create("netfs_trans_dst", sizeof(struct netfs_trans_dst),
0, 0, NULL);
if (!netfs_trans_dst)
goto err_out_exit;
netfs_trans_dst_pool = mempool_create_slab_pool(256, netfs_trans_dst);
if (!netfs_trans_dst_pool)
goto err_out_free;
return 0;
err_out_free:
kmem_cache_destroy(netfs_trans_dst);
err_out_exit:
return err;
}
void netfs_trans_exit(void)
{
mempool_destroy(netfs_trans_dst_pool);
kmem_cache_destroy(netfs_trans_dst);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment