blob: 247245d30d699d4099a84a73d1db6ce6f2f6b69d [file] [log] [blame]
/*
* Copyright (C) 2011 The Chromium OS Authors <chromium-os-dev@chromium.org>
*
* Device-Mapper block hash tree interface.
*
* This file is released under the GPL.
*/
#include <errno.h>
#include <string.h>
#include <asm/atomic.h>
#include <asm/page.h>
#include <linux/device-mapper.h>
#include <linux/dm-bht.h>
#include <linux/gfp.h>
#include <linux/scatterlist.h>
#define DM_MSG_PREFIX "dm bht"
/**
* dm_bht_compute_hash: hashes a page of data
*/
static int dm_bht_compute_hash(struct dm_bht *bht, struct page *pg,
unsigned int offset, u8 *digest)
{
struct hash_desc *hash_desc = &bht->hash_desc[0];
struct scatterlist sg;
sg_init_table(&sg, 1);
sg_set_page(&sg, pg, PAGE_SIZE, offset);
/* Note, this is synchronous. */
if (crypto_hash_init(hash_desc)) {
DMCRIT("failed to reinitialize crypto hash");
return -EINVAL;
}
if (crypto_hash_update(hash_desc, &sg, PAGE_SIZE)) {
DMCRIT("crypto_hash_update failed");
return -EINVAL;
}
if (bht->have_salt) {
sg_set_buf(&sg, bht->salt, sizeof(bht->salt));
if (crypto_hash_update(hash_desc, &sg, sizeof(bht->salt))) {
DMCRIT("crypto_hash_update failed");
return -EINVAL;
}
}
if (crypto_hash_final(hash_desc, digest)) {
DMCRIT("crypto_hash_final failed");
return -EINVAL;
}
return 0;
}
void dm_bht_set_buffer(struct dm_bht *bht, void *buffer) {
int depth;
for (depth = 0; depth < bht->depth; ++depth) {
struct dm_bht_level *level = dm_bht_get_level(bht, depth);
struct dm_bht_entry *entry_end = level->entries + level->count;
struct dm_bht_entry *entry;
for (entry = level->entries; entry < entry_end; ++entry) {
entry->nodes = buffer;
memset(buffer, 0, PAGE_SIZE);
buffer += PAGE_SIZE;
}
}
}
/**
* dm_bht_compute - computes and updates all non-block-level hashes in a tree
* @bht: pointer to a dm_bht_create()d bht
*
* Returns 0 on success, >0 when data is pending, and <0 when a IO or other
* error has occurred.
*
* Walks the tree and computes the hashes at each level from the
* hashes below.
*/
int dm_bht_compute(struct dm_bht *bht)
{
int depth, r = 0;
for (depth = bht->depth - 2; depth >= 0; depth--) {
struct dm_bht_level *level = dm_bht_get_level(bht, depth);
struct dm_bht_level *child_level = level + 1;
struct dm_bht_entry *entry = level->entries;
struct dm_bht_entry *child = child_level->entries;
unsigned int i, j;
for (i = 0; i < level->count; i++, entry++) {
unsigned int count = bht->node_count;
memset(entry->nodes, 0, PAGE_SIZE);
atomic_set(&entry->state, DM_BHT_ENTRY_READY);
if (i == (level->count - 1))
count = child_level->count % bht->node_count;
if (count == 0)
count = bht->node_count;
for (j = 0; j < count; j++, child++) {
struct page *pg = virt_to_page(child->nodes);
u8 *digest = dm_bht_node(bht, entry, j);
r = dm_bht_compute_hash(bht, pg, 0, digest);
if (r) {
DMERR("Failed to update (d=%d,i=%u)",
depth, i);
goto out;
}
}
}
}
r = dm_bht_compute_hash(bht,
virt_to_page(bht->levels[0].entries->nodes),
0, bht->root_digest);
if (r)
DMERR("Failed to update root hash");
out:
return r;
}
/**
* dm_bht_store_block - sets a given block's hash in the tree
* @bht: pointer to a dm_bht_create()d bht
* @block: numeric index of the block in the tree
* @block_data: array of u8s containing the block of data to hash
*
* Returns 0 on success.
*
* If the containing entry in the tree is unallocated, it will allocate memory
* and mark the entry as ready. All other block entries will be 0s.
*
* It is up to the users of the update interface to ensure the entry data is
* fully populated prior to use. The number of updated entries is NOT tracked.
*/
int dm_bht_store_block(struct dm_bht *bht, unsigned int block,
u8 *block_data)
{
int depth = bht->depth;
struct dm_bht_entry *entry = dm_bht_get_entry(bht, depth - 1, block);
u8 *node = dm_bht_get_node(bht, entry, depth, block);
return dm_bht_compute_hash(bht, virt_to_page(block_data), 0, node);
}