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

Staging: pohmelfs: crypto processing.

POHMELFS is able to encrypt the whole network channel or
attach the strong checksum to own packets to catch faulty media.

This patch implements crypto initialization, its autoconfiguration
and sync with the server.
Signed-off-by: default avatarEvgeniy Polyakov <zbr@ioremap.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 9ce8b619
/*
* 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;
}
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