/*
 * 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;
}
