// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 Red Hat, Inc.
*
* This file is released under the GPL.
*/
#include "dm-array.h"
#include "dm-space-map.h"
#include "dm-transaction-manager.h"
#include <linux/export.h>
#include <linux/device-mapper.h>
#define DM_MSG_PREFIX "array"
/*----------------------------------------------------------------*/
/*
* The array is implemented as a fully populated btree, which points to
* blocks that contain the packed values. This is more space efficient
* than just using a btree since we don't store 1 key per value.
*/
struct array_block {
__le32 csum;
__le32 max_entries;
__le32 nr_entries;
__le32 value_size;
__le64 blocknr; /* Block this node is supposed to live in. */
} __packed;
/*----------------------------------------------------------------*/
/*
* Validator methods. As usual we calculate a checksum, and also write the
* block location into the header (paranoia about ssds remapping areas by
* mistake).
*/
#define CSUM_XOR 595846735
/*
* Each array block can hold this many values.
*/
static uint32_t calc_max_entries(size_t value_size, size_t size_of_block)
{
return (size_of_block - sizeof(struct array_block)) / value_size;
}
static void array_block_prepare_for_write(const struct dm_block_validator *v,
struct dm_block *b,
size_t size_of_block)
{
struct array_block *bh_le = dm_block_data(b);
bh_le->blocknr = cpu_to_le64(dm_block_location(b));
bh_le->csum = cpu_to_le32(dm_bm_checksum(&bh_le->max_entries,
size_of_block - sizeof(__le32),
CSUM_XOR));
}
static int array_block_check(const struct dm_block_validator *v,
struct dm_block *b,
size_t size_of_block)
{
struct array_block *bh_le = dm_block_data(b);
uint32_t nr_entries, max_entries, value_size;
__le32 csum_disk;
if (dm_block_location(b) != le64_to_cpu(bh_le->blocknr)) {
DMERR_LIMIT("%s failed: blocknr %llu != wanted %llu", __func__,
(unsigned long long) le64_to_cpu(bh_le->blocknr),
(unsigned long long) dm_block_location(b));
return -ENOTBLK;
}
csum_disk = cpu_to_le32(dm_bm_checksum(&bh_le->max_entries,
size_of_block - sizeof(__le32),
CSUM_XOR));
if (csum_disk != bh_le->csum) {
DMERR_LIMIT("%s failed: csum %u != wanted %u", __func__,
(unsigned int) le32_to_cpu(csum_disk),
(unsigned int) le32_to_cpu(bh_le->csum));
return -EILSEQ;
}
nr_entries = le32_to_cpu(bh_le->nr_entries);
max_entries = le32_to_cpu(bh_le->max_entries);
value_size = le32_to_cpu(bh_le->value_size);
if (!value_size) {
DMERR_LIMIT("%s failed: value_size is zero", __func__);
return -EILSEQ;
}
if (max_entries != calc_max_entries(value_size, size_of_block)) {
DMERR_LIMIT("%s failed: max_entries %u invalid for value_size %u",
__func__, max_entries, value_size);
return -EILSEQ;
}
if (nr_entries > max_entries) {
DMERR_LIMIT("%s failed: too many entries", __func__);
return -EILSEQ;
}
return 0;
}
static const struct dm_block_validator array_validator = {
.name = "array",
.prepare_for_write = array_block_prepare_for_write,
.check = array_block_check
};
/*----------------------------------------------------------------*/
/*
* Functions for manipulating the array blocks.
*/
/*
* Returns a pointer to a value within an array block.
*
* index - The index into _this_ specific block.
*/
static void *element_at(struct dm_array_info *info, struct array_block *ab,
unsigned int index)
{
unsigned char *entry = (unsigned char *) (ab + 1);
entry += index * info->value_type.size;
return entry;
}
/*
* Utility function that calls one of the value_type methods on every value
* in an array block.
*/
static void on_entries(struct dm_array_info *info, struct array_block *ab,
void (*fn)(void *, const void *,