#include <sys/dmu_tx.h>
#include <sys/dsl_pool.h>
#include <sys/spa.h>
#include <sys/vdev_impl.h>
#include <sys/vdev_indirect_mapping.h>
#include <sys/zfeature.h>
#include <sys/dmu_objset.h>
static boolean_t
vdev_indirect_mapping_verify(vdev_indirect_mapping_t *vim)
{
ASSERT(vim != NULL);
ASSERT(vim->vim_object != 0);
ASSERT(vim->vim_objset != NULL);
ASSERT(vim->vim_phys != NULL);
ASSERT(vim->vim_dbuf != NULL);
EQUIV(vim->vim_phys->vimp_num_entries > 0,
vim->vim_entries != NULL);
if (vim->vim_phys->vimp_num_entries > 0) {
vdev_indirect_mapping_entry_phys_t *last_entry =
&vim->vim_entries[vim->vim_phys->vimp_num_entries - 1];
uint64_t offset = DVA_MAPPING_GET_SRC_OFFSET(last_entry);
uint64_t size = DVA_GET_ASIZE(&last_entry->vimep_dst);
ASSERT3U(vim->vim_phys->vimp_max_offset, >=, offset + size);
}
if (vim->vim_havecounts) {
ASSERT(vim->vim_phys->vimp_counts_object != 0);
}
return (B_TRUE);
}
uint64_t
vdev_indirect_mapping_num_entries(vdev_indirect_mapping_t *vim)
{
ASSERT(vdev_indirect_mapping_verify(vim));
return (vim->vim_phys->vimp_num_entries);
}
uint64_t
vdev_indirect_mapping_max_offset(vdev_indirect_mapping_t *vim)
{
ASSERT(vdev_indirect_mapping_verify(vim));
return (vim->vim_phys->vimp_max_offset);
}
uint64_t
vdev_indirect_mapping_object(vdev_indirect_mapping_t *vim)
{
ASSERT(vdev_indirect_mapping_verify(vim));
return (vim->vim_object);
}
uint64_t
vdev_indirect_mapping_bytes_mapped(vdev_indirect_mapping_t *vim)
{
ASSERT(vdev_indirect_mapping_verify(vim));
return (vim->vim_phys->vimp_bytes_mapped);
}
uint64_t
vdev_indirect_mapping_size(vdev_indirect_mapping_t *vim)
{
return (vim->vim_phys->vimp_num_entries * sizeof (*vim->vim_entries));
}
static int
dva_mapping_overlap_compare(const void *v_key, const void *v_array_elem)
{
const uint64_t *key = v_key;
const vdev_indirect_mapping_entry_phys_t *array_elem =
v_array_elem;
uint64_t src_offset = DVA_MAPPING_GET_SRC_OFFSET(array_elem);
if (*key < src_offset) {
return (-1);
} else if (*key < src_offset + DVA_GET_ASIZE(&array_elem->vimep_dst)) {
return (0);
} else {
return (1);
}
}
static vdev_indirect_mapping_entry_phys_t *
vdev_indirect_mapping_entry_for_offset_impl(vdev_indirect_mapping_t *vim,
uint64_t offset, boolean_t next_if_missing)
{
ASSERT(vdev_indirect_mapping_verify(vim));
ASSERT(vim->vim_phys->vimp_num_entries > 0);
vdev_indirect_mapping_entry_phys_t *entry = NULL;
uint64_t last = vim->vim_phys->vimp_num_entries - 1;
uint64_t base = 0;
uint64_t mid;
int result;
while (last >= base) {
mid = base + ((last - base) >> 1);
result = dva_mapping_overlap_compare(&offset,
&vim->vim_entries[mid]);
if (result == 0) {
entry = &vim->vim_entries[mid];
break;
} else if (result < 0) {
last = mid - 1;
} else {
base = mid + 1;
}
}
if (entry == NULL && next_if_missing) {
ASSERT3U(base, ==, last + 1);
ASSERT(mid == base || mid == last);
ASSERT3S(result, !=, 0);
uint64_t index;
if (result < 0)
index = mid;
else
index = mid + 1;
ASSERT3U(index, <=, vim->vim_phys->vimp_num_entries);
if (index == vim->vim_phys->vimp_num_entries) {
ASSERT3S(dva_mapping_overlap_compare(&offset,
&vim->vim_entries[index - 1]), >, 0);
return (NULL);
} else {
ASSERT3S(dva_mapping_overlap_compare(&offset,
&vim->vim_entries[index]), <, 0);
IMPLY(index >= 1, dva_mapping_overlap_compare(&offset,
&vim->vim_entries[index - 1]) > 0);
return (&vim->vim_entries[index]);
}
} else {
return (entry);
}
}
vdev_indirect_mapping_entry_phys_t *
vdev_indirect_mapping_entry_for_offset(vdev_indirect_mapping_t *vim,
uint64_t offset)
{
return (vdev_indirect_mapping_entry_for_offset_impl(vim, offset,
B_FALSE));
}
vdev_indirect_mapping_entry_phys_t *
vdev_indirect_mapping_entry_for_offset_or_next(vdev_indirect_mapping_t *vim,
uint64_t offset)
{
return (vdev_indirect_mapping_entry_for_offset_impl(vim, offset,
B_TRUE));
}
void
vdev_indirect_mapping_close(vdev_indirect_mapping_t *vim)
{
ASSERT(vdev_indirect_mapping_verify(vim));
if (vim->vim_phys->vimp_num_entries > 0) {
uint64_t map_size = vdev_indirect_mapping_size(vim);
kmem_free(vim->vim_entries, map_size);
vim->vim_entries = NULL;
}
dmu_buf_rele(vim->vim_dbuf, vim);
vim->vim_objset = NULL;
vim->vim_object = 0;
vim->vim_dbuf = NULL;
vim->vim_phys = NULL;
kmem_free(vim, sizeof (*vim));
}
uint64_t
vdev_indirect_mapping_alloc(objset_t *os, dmu_tx_t *tx)
{
uint64_t object;
ASSERT(dmu_tx_is_syncing(tx));
uint64_t bonus_size = VDEV_INDIRECT_MAPPING_SIZE_V0;
if (spa_feature_is_enabled(os->os_spa, SPA_FEATURE_OBSOLETE_COUNTS)) {
bonus_size = sizeof (vdev_indirect_mapping_phys_t);
}
object = dmu_object_alloc(os,
DMU_OTN_UINT64_METADATA, SPA_OLD_MAXBLOCKSIZE,
DMU_OTN_UINT64_METADATA, bonus_size,
tx);
if (spa_feature_is_enabled(os->os_spa, SPA_FEATURE_OBSOLETE_COUNTS)) {
dmu_buf_t *dbuf;
vdev_indirect_mapping_phys_t *vimp;
VERIFY0(dmu_bonus_hold(os, object, FTAG, &dbuf));
dmu_buf_will_dirty(dbuf, tx);
vimp = dbuf->db_data;
vimp->vimp_counts_object = dmu_object_alloc(os,
DMU_OTN_UINT32_METADATA, SPA_OLD_MAXBLOCKSIZE,
DMU_OT_NONE, 0, tx);
spa_feature_incr(os->os_spa, SPA_FEATURE_OBSOLETE_COUNTS, tx);
dmu_buf_rele(dbuf, FTAG);
}
return (object);
}
vdev_indirect_mapping_t *
vdev_indirect_mapping_open(objset_t *os, uint64_t mapping_object)
{
vdev_indirect_mapping_t *vim = kmem_zalloc(sizeof (*vim), KM_SLEEP);
dmu_object_info_t doi;
VERIFY0(dmu_object_info(os, mapping_object, &doi));
vim->vim_objset = os;
vim->vim_object = mapping_object;
VERIFY0(dmu_bonus_hold(os, vim->vim_object, vim,
&vim->vim_dbuf));
vim->vim_phys = vim->vim_dbuf->db_data;
vim->vim_havecounts =
(doi.doi_bonus_size > VDEV_INDIRECT_MAPPING_SIZE_V0);
if (vim->vim_phys->vimp_num_entries > 0) {
uint64_t map_size = vdev_indirect_mapping_size(vim);
vim->vim_entries = kmem_alloc(map_size, KM_SLEEP);
VERIFY0(dmu_read(os, vim->vim_object, 0, map_size,
vim->vim_entries, DMU_READ_PREFETCH));
}
ASSERT(vdev_indirect_mapping_verify(vim));
return (vim);
}
void
vdev_indirect_mapping_free(objset_t *os, uint64_t object, dmu_tx_t *tx)
{
vdev_indirect_mapping_t *vim = vdev_indirect_mapping_open(os, object);
if (vim->vim_havecounts) {
VERIFY0(dmu_object_free(os, vim->vim_phys->vimp_counts_object,
tx));
spa_feature_decr(os->os_spa, SPA_FEATURE_OBSOLETE_COUNTS, tx);
}
vdev_indirect_mapping_close(vim);
VERIFY0(dmu_object_free(os, object, tx));
}
void
vdev_indirect_mapping_add_entries(vdev_indirect_mapping_t *vim,
list_t *list, dmu_tx_t *tx)
{
vdev_indirect_mapping_entry_phys_t *mapbuf;
uint64_t old_size;
uint32_t *countbuf = NULL;
vdev_indirect_mapping_entry_phys_t *old_entries;
uint64_t old_count;
uint64_t entries_written = 0;
ASSERT(vdev_indirect_mapping_verify(vim));
ASSERT(dmu_tx_is_syncing(tx));
ASSERT(dsl_pool_sync_context(dmu_tx_pool(tx)));
ASSERT(!list_is_empty(list));
old_size = vdev_indirect_mapping_size(vim);
old_entries = vim->vim_entries;
old_count = vim->vim_phys->vimp_num_entries;
dmu_buf_will_dirty(vim->vim_dbuf, tx);
mapbuf = zio_buf_alloc(SPA_OLD_MAXBLOCKSIZE);
if (vim->vim_havecounts) {
countbuf = zio_buf_alloc(SPA_OLD_MAXBLOCKSIZE);
ASSERT(spa_feature_is_active(vim->vim_objset->os_spa,
SPA_FEATURE_OBSOLETE_COUNTS));
}
while (!list_is_empty(list)) {
uint64_t i;
for (i = 0; i < SPA_OLD_MAXBLOCKSIZE / sizeof (*mapbuf); i++) {
vdev_indirect_mapping_entry_t *entry =
list_remove_head(list);
if (entry == NULL)
break;
uint64_t size =
DVA_GET_ASIZE(&entry->vime_mapping.vimep_dst);
uint64_t src_offset =
DVA_MAPPING_GET_SRC_OFFSET(&entry->vime_mapping);
ASSERT3U(entry->vime_obsolete_count, <, size);
IMPLY(entry->vime_obsolete_count != 0,
vim->vim_havecounts);
mapbuf[i] = entry->vime_mapping;
if (vim->vim_havecounts)
countbuf[i] = entry->vime_obsolete_count;
vim->vim_phys->vimp_bytes_mapped += size;
ASSERT3U(src_offset, >=,
vim->vim_phys->vimp_max_offset);
vim->vim_phys->vimp_max_offset = src_offset + size;
entries_written++;
kmem_free(entry, sizeof (*entry));
}
dmu_write(vim->vim_objset, vim->vim_object,
vim->vim_phys->vimp_num_entries * sizeof (*mapbuf),
i * sizeof (*mapbuf),
mapbuf, tx);
if (vim->vim_havecounts) {
dmu_write(vim->vim_objset,
vim->vim_phys->vimp_counts_object,
vim->vim_phys->vimp_num_entries *
sizeof (*countbuf),
i * sizeof (*countbuf), countbuf, tx);
}
vim->vim_phys->vimp_num_entries += i;
}
zio_buf_free(mapbuf, SPA_OLD_MAXBLOCKSIZE);
if (vim->vim_havecounts)
zio_buf_free(countbuf, SPA_OLD_MAXBLOCKSIZE);
uint64_t new_size = vdev_indirect_mapping_size(vim);
ASSERT3U(new_size, >, old_size);
ASSERT3U(new_size - old_size, ==,
entries_written * sizeof (vdev_indirect_mapping_entry_phys_t));
vim->vim_entries = kmem_alloc(new_size, KM_SLEEP);
if (old_size > 0) {
bcopy(old_entries, vim->vim_entries, old_size);
kmem_free(old_entries, old_size);
}
VERIFY0(dmu_read(vim->vim_objset, vim->vim_object, old_size,
new_size - old_size, &vim->vim_entries[old_count],
DMU_READ_PREFETCH));
zfs_dbgmsg("txg %llu: wrote %llu entries to "
"indirect mapping obj %llu; max offset=0x%llx",
(u_longlong_t)dmu_tx_get_txg(tx),
(u_longlong_t)entries_written,
(u_longlong_t)vim->vim_object,
(u_longlong_t)vim->vim_phys->vimp_max_offset);
}
void
vdev_indirect_mapping_increment_obsolete_count(vdev_indirect_mapping_t *vim,
uint64_t offset, uint64_t length, uint32_t *counts)
{
vdev_indirect_mapping_entry_phys_t *mapping;
uint64_t index;
mapping = vdev_indirect_mapping_entry_for_offset(vim, offset);
ASSERT(length > 0);
ASSERT3P(mapping, !=, NULL);
index = mapping - vim->vim_entries;
while (length > 0) {
ASSERT3U(index, <, vdev_indirect_mapping_num_entries(vim));
uint64_t size = DVA_GET_ASIZE(&mapping->vimep_dst);
uint64_t inner_offset = offset -
DVA_MAPPING_GET_SRC_OFFSET(mapping);
VERIFY3U(inner_offset, <, size);
uint64_t inner_size = MIN(length, size - inner_offset);
VERIFY3U(counts[index] + inner_size, <=, size);
counts[index] += inner_size;
offset += inner_size;
length -= inner_size;
mapping++;
index++;
}
}
typedef struct load_obsolete_space_map_arg {
vdev_indirect_mapping_t *losma_vim;
uint32_t *losma_counts;
} load_obsolete_space_map_arg_t;
static int
load_obsolete_sm_callback(space_map_entry_t *sme, void *arg)
{
load_obsolete_space_map_arg_t *losma = arg;
ASSERT3S(sme->sme_type, ==, SM_ALLOC);
vdev_indirect_mapping_increment_obsolete_count(losma->losma_vim,
sme->sme_offset, sme->sme_run, losma->losma_counts);
return (0);
}
void
vdev_indirect_mapping_load_obsolete_spacemap(vdev_indirect_mapping_t *vim,
uint32_t *counts, space_map_t *obsolete_space_sm)
{
load_obsolete_space_map_arg_t losma;
losma.losma_counts = counts;
losma.losma_vim = vim;
VERIFY0(space_map_iterate(obsolete_space_sm,
space_map_length(obsolete_space_sm),
load_obsolete_sm_callback, &losma));
}
uint32_t *
vdev_indirect_mapping_load_obsolete_counts(vdev_indirect_mapping_t *vim)
{
ASSERT(vdev_indirect_mapping_verify(vim));
uint64_t counts_size =
vim->vim_phys->vimp_num_entries * sizeof (uint32_t);
uint32_t *counts = kmem_alloc(counts_size, KM_SLEEP);
if (vim->vim_havecounts) {
VERIFY0(dmu_read(vim->vim_objset,
vim->vim_phys->vimp_counts_object,
0, counts_size,
counts, DMU_READ_PREFETCH));
} else {
bzero(counts, counts_size);
}
return (counts);
}
extern void
vdev_indirect_mapping_free_obsolete_counts(vdev_indirect_mapping_t *vim,
uint32_t *counts)
{
ASSERT(vdev_indirect_mapping_verify(vim));
kmem_free(counts, vim->vim_phys->vimp_num_entries * sizeof (uint32_t));
}