| /* |
| * 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/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) |
| { |
| if (e->hash) |
| crypto_free_hash(e->hash); |
| if (e->cipher) |
| 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 |
| 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 |
| 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 acceptible |
| * 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; |
| } |