%PDF- %PDF-
| Direktori : /usr/src/blksnap-6.3.0.73/ |
| Current File : //usr/src/blksnap-6.3.0.73/diff_area.c |
// SPDX-License-Identifier: GPL-2.0
#define pr_fmt(fmt) KBUILD_MODNAME "-diff-area: " fmt
#ifdef HAVE_GENHD_H
#include <linux/genhd.h>
#endif
#include <linux/blkdev.h>
#include <linux/slab.h>
#ifdef STANDALONE_BDEVFILTER
#include "blk_snap.h"
#else
#include <linux/blk_snap.h>
#endif
#include "memory_checker.h"
#include "params.h"
#include "chunk.h"
#include "diff_buffer.h"
#include "diff_storage.h"
#include "diff_io.h"
#ifdef STANDALONE_BDEVFILTER
#include "log.h"
#include "log_histogram.h"
#endif
#ifndef HAVE_BDEV_NR_SECTORS
static inline sector_t bdev_nr_sectors(struct block_device *bdev)
{
return i_size_read(bdev->bd_inode) >> 9;
};
#endif
static inline sector_t diff_area_chunk_offset(struct diff_area *diff_area,
sector_t sector)
{
return sector & ((1ull << (diff_area->chunk_shift - SECTOR_SHIFT)) - 1);
}
static inline unsigned long diff_area_chunk_number(struct diff_area *diff_area,
sector_t sector)
{
return (unsigned long)(sector >>
(diff_area->chunk_shift - SECTOR_SHIFT));
};
static inline sector_t chunk_sector(struct chunk *chunk)
{
return (sector_t)(chunk->number)
<< (chunk->diff_area->chunk_shift - SECTOR_SHIFT);
}
static inline void recalculate_last_chunk_size(struct chunk *chunk)
{
sector_t capacity;
capacity = bdev_nr_sectors(chunk->diff_area->orig_bdev);
if (capacity > round_down(capacity, chunk->sector_count))
chunk->sector_count =
capacity - round_down(capacity, chunk->sector_count);
}
static inline unsigned long long count_by_shift(sector_t capacity,
unsigned long long shift)
{
unsigned long long shift_sector = (shift - SECTOR_SHIFT);
return round_up(capacity, (1ull << shift_sector)) >> shift_sector;
}
static void diff_area_calculate_chunk_size(struct diff_area *diff_area)
{
unsigned long long shift = chunk_minimum_shift;
unsigned long long count;
sector_t capacity;
sector_t min_io_sect;
min_io_sect = (sector_t)(bdev_io_min(diff_area->orig_bdev) >>
SECTOR_SHIFT);
capacity = bdev_nr_sectors(diff_area->orig_bdev);
pr_debug("Minimal IO block %llu sectors\n", min_io_sect);
pr_debug("Device capacity %llu sectors\n", capacity);
count = count_by_shift(capacity, shift);
pr_debug("Chunks count %llu\n", count);
while ((count > get_chunk_maximum_count()) ||
((1ull << (shift - SECTOR_SHIFT)) < min_io_sect)) {
shift++;
count = count_by_shift(capacity, shift);
pr_debug("Chunks count %llu\n", count);
}
diff_area->chunk_shift = shift;
diff_area->chunk_count = (unsigned long)DIV_ROUND_UP_ULL(capacity,
(1ul << (shift - SECTOR_SHIFT)));
}
void diff_area_free(struct kref *kref)
{
unsigned long inx = 0;
struct chunk *chunk;
struct diff_area *diff_area =
container_of(kref, struct diff_area, kref);
might_sleep();
atomic_set(&diff_area->corrupt_flag, 1);
flush_work(&diff_area->cache_release_work);
xa_for_each(&diff_area->chunk_map, inx, chunk)
if (chunk)
chunk_free(diff_area, chunk);
xa_destroy(&diff_area->chunk_map);
#ifdef STANDALONE_BDEVFILTER
pr_debug("Difference area statistic for device [%d:%d]\n",
MAJOR(diff_area->orig_bdev->bd_dev),
MINOR(diff_area->orig_bdev->bd_dev));
pr_debug("%llu MiB was processed\n", atomic64_read(&diff_area->stat_processed) >> (20 - SECTOR_SHIFT));
pr_debug("%llu MiB was copied\n", atomic64_read(&diff_area->stat_copied) >> (20 - SECTOR_SHIFT));
pr_debug("%llu MiB was read from image\n", atomic64_read(&diff_area->stat_image_read) >> (20 - SECTOR_SHIFT));
pr_debug("%llu MiB was written to image\n", atomic64_read(&diff_area->stat_image_written) >> (20 - SECTOR_SHIFT));
pr_debug("Reading IO units statistic:\n");
log_histogram_show(&diff_area->read_hg);
pr_debug("Redirection IO units statistic:\n");
log_histogram_show(&diff_area->redirect_hg);
#endif
if (diff_area->orig_bdev) {
#if defined(HAVE_BDEV_FILE_OPEN)
bdev_fput(diff_area->orig_bdev_file);
#elif defined(HAVE_BDEV_HANDLE)
bdev_release(diff_area->orig_bdev_handler);
diff_area->orig_bdev_handler = NULL;
#elif defined(HAVE_BLK_HOLDER_OPS)
blkdev_put(diff_area->orig_bdev, NULL);
#else
blkdev_put(diff_area->orig_bdev, FMODE_READ | FMODE_WRITE);
#endif
diff_area->orig_bdev = NULL;
}
/* Clean up free_diff_buffers */
diff_buffer_cleanup(diff_area);
kfree(diff_area);
memory_object_dec(memory_object_diff_area);
}
static inline struct chunk *
get_chunk_from_cache_and_write_lock(struct diff_area *diff_area, int wr)
{
struct list_head *cache_queue;
atomic_t *cache_count;
struct chunk *iter;
struct chunk *chunk = NULL;
#ifdef BLK_SNAP_DEBUG_DIFF_BUFFER
int locked_mutex_counter = 0;
#endif
if (wr) {
cache_queue = &diff_area->write_cache_queue;
cache_count = &diff_area->write_cache_count;
} else {
cache_queue = &diff_area->read_cache_queue;
cache_count = &diff_area->read_cache_count;
}
spin_lock(&diff_area->chunk_map_lock);
list_for_each_entry(iter, cache_queue, link) {
if (!down_trylock(&iter->lock)) {
chunk = iter;
break;
}
/*
* If it is not possible to lock a chunk for writing,
* then it is currently in use, and we try to clean up the
* next chunk.
*/
#ifdef BLK_SNAP_DEBUG_DIFF_BUFFER
locked_mutex_counter++;
#endif
}
if (likely(chunk)) {
atomic_dec(cache_count);
list_del_init(&chunk->link);
chunk->diff_area = diff_area_get(diff_area);
atomic_inc(&chunk->refcount);
}
spin_unlock(&diff_area->chunk_map_lock);
#ifdef BLK_SNAP_DEBUG_DIFF_BUFFER
if (locked_mutex_counter)
pr_debug("Found %d locked chunk\n", locked_mutex_counter);
#endif
return chunk;
}
static struct chunk *
diff_area_get_chunk_from_cache_and_write_lock(struct diff_area *diff_area)
{
if (atomic_read(&diff_area->read_cache_count) >
chunk_maximum_in_cache) {
struct chunk *chunk = get_chunk_from_cache_and_write_lock(
diff_area, READ);
if (chunk) {
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
if (chunk_state_check(chunk, CHUNK_ST_DIRTY))
pr_err("get and lock dirty chunk #%ld from read cache\n",
chunk->number);
#endif
return chunk;
}
}
if (atomic_read(&diff_area->write_cache_count) >
chunk_maximum_in_cache) {
struct chunk *chunk = get_chunk_from_cache_and_write_lock(
diff_area, WRITE);
if (chunk) {
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
if (chunk_state_check(chunk, CHUNK_ST_DIRTY))
pr_debug(
"get and lock dirty chunk #%ld from write cache\n",
chunk->number);
else
pr_err("get and lock pure chunk #%ld from write cache\n",
chunk->number);
#endif
return chunk;
}
}
return NULL;
}
static void diff_area_cache_release(struct diff_area *diff_area)
{
struct chunk *chunk;
while (!diff_area_is_corrupted(diff_area) &&
(chunk = diff_area_get_chunk_from_cache_and_write_lock(
diff_area))) {
/*
* There cannot be a chunk in the cache whose buffer is
* not ready.
*/
if (WARN(!chunk_state_check(chunk, CHUNK_ST_BUFFER_READY),
"Cannot release empty buffer for chunk #%ld",
chunk->number)) {
chunk_up(chunk);
continue;
}
if (chunk_state_check(chunk, CHUNK_ST_DIRTY)) {
int ret;
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
pr_debug("Storing chunk #%ld\n", chunk->number);
#endif
ret = chunk_schedule_storing(chunk, false);
if (ret) {
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
pr_debug("Failed to store chunk #%ld\n",
chunk->number);
#endif
chunk_store_failed(chunk, ret);
}
} else {
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
pr_debug("Release buffer for chunk #%ld\n",
chunk->number);
#endif
chunk_diff_buffer_release(diff_area, chunk);
if (chunk_state_check(chunk, CHUNK_ST_STORE_READY))
chunk_up(chunk);
else {
/*
* A chunk is erased from the map if it does not
* store snapshot differences and does not
* contain snapshots changes.
* Such a chunk was created to read data from a
* snapshot image and does not require storage.
*/
chunk_up_and_free(chunk);
}
}
}
}
static void diff_area_cache_release_work(struct work_struct *work)
{
struct diff_area *diff_area =
container_of(work, struct diff_area, cache_release_work);
diff_area_cache_release(diff_area);
}
struct diff_area *diff_area_new(dev_t dev_id, struct diff_storage *diff_storage)
{
int ret = 0;
struct diff_area *diff_area = NULL;
#if defined(HAVE_BDEV_FILE_OPEN)
struct file *bdev;
#elif defined(HAVE_BDEV_HANDLE)
struct bdev_handle *bdev;
#else
struct block_device *bdev;
#endif
pr_debug("Open device [%u:%u]\n", MAJOR(dev_id), MINOR(dev_id));
#if defined(HAVE_BDEV_FILE_OPEN)
bdev = bdev_file_open_by_dev(dev_id, FMODE_READ | FMODE_WRITE, NULL, NULL);
#elif defined(HAVE_BDEV_HANDLE)
bdev = bdev_open_by_dev(dev_id, FMODE_READ | FMODE_WRITE, NULL, NULL);
#elif defined(HAVE_BLK_HOLDER_OPS)
bdev = blkdev_get_by_dev(dev_id, FMODE_READ | FMODE_WRITE, NULL, NULL);
#else
bdev = blkdev_get_by_dev(dev_id, FMODE_READ | FMODE_WRITE, NULL);
#endif
if (IS_ERR(bdev)) {
pr_err("Failed to open device. errno=%d\n",
abs((int)PTR_ERR(bdev)));
return ERR_PTR(PTR_ERR(bdev));
}
diff_area = kzalloc(sizeof(struct diff_area), GFP_KERNEL);
if (!diff_area) {
#if defined(HAVE_BDEV_FILE_OPEN)
bdev_fput(bdev);
#elif defined(HAVE_BDEV_HANDLE)
bdev_release(bdev);
#elif defined(HAVE_BLK_HOLDER_OPS)
blkdev_put(bdev, NULL);
#else
blkdev_put(bdev, FMODE_READ | FMODE_WRITE);
#endif
return ERR_PTR(-ENOMEM);
}
memory_object_inc(memory_object_diff_area);
#if defined(HAVE_BDEV_FILE_OPEN)
diff_area->orig_bdev_file = bdev;
diff_area->orig_bdev = file_bdev(bdev);
#elif defined(HAVE_BDEV_HANDLE)
diff_area->orig_bdev_handler = bdev;
diff_area->orig_bdev = bdev->bdev;
#else
diff_area->orig_bdev = bdev;
#endif
diff_area->diff_storage = diff_storage;
diff_area_calculate_chunk_size(diff_area);
if (diff_area->chunk_shift > chunk_maximum_shift) {
pr_info("The maximum allowable chunk size has been reached.\n");
return ERR_PTR(-EFAULT);
}
pr_info("The optimal chunk size was calculated as %llu bytes for device [%d:%d]\n",
(1ull << diff_area->chunk_shift),
MAJOR(diff_area->orig_bdev->bd_dev),
MINOR(diff_area->orig_bdev->bd_dev));
kref_init(&diff_area->kref);
xa_init(&diff_area->chunk_map);
if (!diff_storage->capacity) {
#ifdef CONFIG_BLK_SNAP_ALLOW_DIFF_STORAGE_IN_MEMORY
diff_area->in_memory = true;
pr_debug("Difference storage is empty.\n");
pr_debug("Only the memory cache will be used to store the snapshots difference.\n");
#else
pr_err("Difference storage is empty.\n");
pr_err("In-memory difference storage is not supported");
return ERR_PTR(-EFAULT);
#endif
}
spin_lock_init(&diff_area->chunk_map_lock);
INIT_LIST_HEAD(&diff_area->read_cache_queue);
atomic_set(&diff_area->read_cache_count, 0);
INIT_LIST_HEAD(&diff_area->write_cache_queue);
atomic_set(&diff_area->write_cache_count, 0);
INIT_WORK(&diff_area->cache_release_work, diff_area_cache_release_work);
spin_lock_init(&diff_area->free_diff_buffers_lock);
INIT_LIST_HEAD(&diff_area->free_diff_buffers);
atomic_set(&diff_area->free_diff_buffers_count, 0);
atomic_set(&diff_area->corrupt_flag, 0);
#ifdef STANDALONE_BDEVFILTER
atomic64_set(&diff_area->stat_processed, 0);
atomic64_set(&diff_area->stat_copied, 0);
atomic64_set(&diff_area->stat_processed, 0);
atomic64_set(&diff_area->stat_image_written, 0);
log_histogram_init(&diff_area->read_hg, 4096);
log_histogram_init(&diff_area->redirect_hg, 4096);
#endif
if (ret) {
diff_area_put(diff_area);
return ERR_PTR(ret);
}
atomic_set(&diff_area->corrupt_flag, 0);
return diff_area;
}
static void diff_area_take_chunk_from_cache(struct diff_area *diff_area,
struct chunk *chunk)
{
spin_lock(&diff_area->chunk_map_lock);
if (!list_is_first(&chunk->link, &chunk->link)) {
list_del_init(&chunk->link);
if (chunk_state_check(chunk, CHUNK_ST_DIRTY)) {
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
pr_debug("Take chunk #%lu from write cache",
chunk->number);
#endif
atomic_dec(&diff_area->write_cache_count);
} else
atomic_dec(&diff_area->read_cache_count);
}
spin_unlock(&diff_area->chunk_map_lock);
}
static struct chunk *diff_area_chunk_take(struct diff_area *diff_area,
unsigned long nr)
{
int ret;
struct chunk *chunk, *new_chunk;
spin_lock(&diff_area->chunk_map_lock);
chunk = xa_load(&diff_area->chunk_map, nr);
if (chunk)
atomic_inc(&chunk->refcount);
spin_unlock(&diff_area->chunk_map_lock);
if (chunk)
return chunk;
new_chunk = chunk_alloc(diff_area, nr);
if (!new_chunk) {
pr_debug("Could not allocate a chunk to write the image\n");
diff_area_set_corrupted(diff_area, -ENOMEM);
return ERR_PTR(-ENOMEM);
}
spin_lock(&diff_area->chunk_map_lock);
ret = xa_insert(&diff_area->chunk_map, nr, new_chunk, GFP_ATOMIC);
if (likely(!ret)) {
chunk = new_chunk;
new_chunk = NULL;
} else if (ret == -EBUSY) {
/* another chunk has just been created */
chunk_free(diff_area, new_chunk);
chunk = xa_load(&diff_area->chunk_map, nr);
if (likely(chunk)) {
atomic_inc(&chunk->refcount);
ret = 0;
}
}
spin_unlock(&diff_area->chunk_map_lock);
if (unlikely(ret)) {
if (ret == -EBUSY) {
pr_err("Could not get a chunk from chunk map\n");
ret = -EINVAL;
} else
pr_err("Could not insert a chunk to chunk map\n");
diff_area_set_corrupted(diff_area, ret);
chunk = ERR_PTR(ret);
}
return chunk;
}
/**
* diff_area_copy() - Implements the copy-on-write mechanism.
*
*
*/
int diff_area_copy(struct diff_area *diff_area, sector_t sector, sector_t count,
const bool is_nowait)
{
int ret = 0;
sector_t offset;
struct chunk *chunk;
struct diff_buffer *diff_buffer;
sector_t area_sect_first;
sector_t chunk_sectors = diff_area_chunk_sectors(diff_area);
#ifdef STANDALONE_BDEVFILTER
atomic64_add(count, &diff_area->stat_processed);
#endif
area_sect_first = round_down(sector, chunk_sectors);
for (offset = area_sect_first; offset < (sector + count);
offset += chunk_sectors) {
chunk = diff_area_chunk_take(diff_area,
diff_area_chunk_number(diff_area, offset));
if (!chunk)
return PTR_ERR(chunk);
if (is_nowait) {
if (down_trylock(&chunk->lock))
return -EAGAIN;
} else {
ret = down_killable(&chunk->lock);
if (unlikely(ret))
return ret;
}
chunk->diff_area = diff_area_get(diff_area);
WARN_ON(diff_area_chunk_number(diff_area, offset) != chunk->number);
if (chunk_state_check(chunk, CHUNK_ST_FAILED | CHUNK_ST_DIRTY |
CHUNK_ST_STORE_READY)) {
/*
* The chunk has already been:
* - Failed, when the snapshot is corrupted
* - Overwritten in the snapshot image
* - Already stored in the diff storage
*/
chunk_up(chunk);
continue;
}
if (unlikely(chunk_state_check(
chunk, CHUNK_ST_LOADING | CHUNK_ST_STORING))) {
pr_err("Invalid chunk state\n");
ret = -EFAULT;
goto fail_unlock_chunk;
}
if (chunk_state_check(chunk, CHUNK_ST_BUFFER_READY)) {
diff_area_take_chunk_from_cache(diff_area, chunk);
/**
* The chunk has already been read, but now we need
* to store it to diff_storage.
*/
ret = chunk_schedule_storing(chunk, is_nowait);
if (unlikely(ret))
goto fail_unlock_chunk;
} else {
diff_buffer =
diff_buffer_take(chunk->diff_area, is_nowait);
if (IS_ERR(diff_buffer)) {
ret = PTR_ERR(diff_buffer);
goto fail_unlock_chunk;
}
WARN(chunk->diff_buffer, "Chunks buffer has been lost");
chunk->diff_buffer = diff_buffer;
#ifdef STANDALONE_BDEVFILTER
atomic64_add(chunk->sector_count, &diff_area->stat_copied);
#endif
ret = chunk_async_load_orig(chunk, is_nowait);
if (unlikely(ret))
goto fail_unlock_chunk;
}
}
return ret;
fail_unlock_chunk:
WARN_ON(!chunk);
chunk_store_failed(chunk, ret);
return ret;
}
int diff_area_wait(struct diff_area *diff_area, sector_t sector, sector_t count,
const bool is_nowait)
{
int ret = 0;
sector_t offset;
struct chunk *chunk;
sector_t area_sect_first;
sector_t chunk_sectors = diff_area_chunk_sectors(diff_area);
area_sect_first = round_down(sector, chunk_sectors);
for (offset = area_sect_first; offset < (sector + count);
offset += chunk_sectors) {
spin_lock(&diff_area->chunk_map_lock);
chunk = xa_load(&diff_area->chunk_map,
diff_area_chunk_number(diff_area, offset));
if (chunk)
atomic_inc(&chunk->refcount);
spin_unlock(&diff_area->chunk_map_lock);
if (!chunk) {
pr_err("Could not get a chunk from chunk map\n");
diff_area_set_corrupted(diff_area, -EINVAL);
return -EINVAL;
}
WARN_ON(diff_area_chunk_number(diff_area, offset) != chunk->number);
if (is_nowait) {
if (down_trylock(&chunk->lock)) {
atomic_dec(&chunk->refcount);
return -EAGAIN;
}
} else {
ret = down_killable(&chunk->lock);
if (unlikely(ret)) {
atomic_dec(&chunk->refcount);
return ret;
}
}
chunk->diff_area = diff_area_get(diff_area);
if (chunk_state_check(chunk, CHUNK_ST_FAILED )) {
/*
* The chunk has already been:
* - Failed, when the snapshot is corrupted
* - Overwritten in the snapshot image
* - Already stored in the diff storage
*/
chunk_up(chunk);
ret = -EFAULT;
break;
}
if (chunk_state_check(chunk, CHUNK_ST_BUFFER_READY |
CHUNK_ST_DIRTY | CHUNK_ST_STORE_READY)) {
/*
* The chunk has already been:
* - Read
* - Overwritten in the snapshot image
* - Already stored in the diff storage
*/
chunk_up(chunk);
continue;
}
}
return ret;
}
static inline void diff_area_image_put_chunk(struct diff_area_image_ctx *io_ctx)
{
if (io_ctx->is_write) {
/*
* Since the chunk was taken to perform writing,
* we mark it as dirty.
*/
chunk_state_set(io_ctx->chunk, CHUNK_ST_DIRTY);
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
pr_debug("chunk #%ld marked as dirty\n", io_ctx->chunk->number);
#endif
}
chunk_schedule_caching(io_ctx->chunk);
}
void diff_area_image_ctx_done(struct diff_area_image_ctx *io_ctx)
{
if (!io_ctx->chunk)
return;
diff_area_image_put_chunk(io_ctx);
}
static int diff_area_load_chunk_from_storage(struct diff_area *diff_area,
struct chunk *chunk)
{
struct diff_buffer *diff_buffer;
diff_buffer = diff_buffer_take(diff_area, false);
if (IS_ERR(diff_buffer))
return PTR_ERR(diff_buffer);
WARN_ON(chunk->diff_buffer);
chunk->diff_buffer = diff_buffer;
if (chunk_state_check(chunk, CHUNK_ST_STORE_READY)) {
#ifdef BLK_SNAP_DEBUG_IMAGE_WRITE
pr_debug("Read chunk #%lu from diff storage", chunk->number);
#endif
return chunk_load_diff(chunk);
}
return chunk_load_orig(chunk);
}
static struct chunk *diff_area_image_context_get_chunk(
struct diff_area_image_ctx *io_ctx, sector_t sector)
{
int ret;
struct chunk *chunk;
struct diff_area *diff_area = io_ctx->diff_area;
unsigned long nr = diff_area_chunk_number(diff_area, sector);
if (io_ctx->chunk) {
if (io_ctx->chunk->number == nr)
return io_ctx->chunk;
/*
* If the sector falls into a new chunk, then we release
* the old chunk.
*/
diff_area_image_put_chunk(io_ctx);
io_ctx->chunk = NULL;
}
/* Take a next chunk. */
chunk = diff_area_chunk_take(diff_area, nr);
ret = down_killable(&chunk->lock);
if (ret) {
atomic_dec(&chunk->refcount);
return ERR_PTR(ret);
}
chunk->diff_area = diff_area_get(diff_area);
if (unlikely(chunk_state_check(chunk, CHUNK_ST_FAILED))) {
pr_err("Chunk #%ld corrupted\n", chunk->number);
pr_debug("nr=%ld\n", nr);
pr_debug("sector=%llu\n", sector);
pr_debug("Chunk size %llu in bytes\n",
(1ull << diff_area->chunk_shift));
pr_debug("Chunk count %lu\n", diff_area->chunk_count);
ret = -EIO;
goto fail_unlock_chunk;
}
/*
* If there is already data in the buffer, then nothing needs to be loaded.
* Otherwise, the chunk needs to be loaded from the original device or
* from the difference storage.
*/
if (!chunk_state_check(chunk, CHUNK_ST_BUFFER_READY)) {
ret = diff_area_load_chunk_from_storage(diff_area, chunk);
if (unlikely(ret))
goto fail_unlock_chunk;
/* Set the flag that the buffer contains the required data. */
chunk_state_set(chunk, CHUNK_ST_BUFFER_READY);
} else
diff_area_take_chunk_from_cache(diff_area, chunk);
io_ctx->chunk = chunk;
return chunk;
fail_unlock_chunk:
pr_err("Failed to load chunk #%ld\n", chunk->number);
chunk_up(chunk);
return ERR_PTR(ret);
}
/**
* diff_area_image_io - Implements copying data from the chunk to bio_vec when
* reading or from bio_vec to the chunk when writing.
*/
blk_status_t diff_area_image_io(struct diff_area_image_ctx *io_ctx,
const struct bio_vec *bvec, sector_t *pos)
{
unsigned int bv_len = bvec->bv_len;
struct iov_iter iter;
iov_iter_bvec(&iter, io_ctx->is_write ? WRITE : READ, bvec, 1, bv_len);
while (bv_len) {
struct diff_buffer_iter diff_buffer_iter;
struct chunk *chunk;
size_t buff_offset;
chunk = diff_area_image_context_get_chunk(io_ctx, *pos);
if (IS_ERR(chunk))
return BLK_STS_IOERR;
buff_offset = (size_t)(*pos - chunk_sector(chunk))
<< SECTOR_SHIFT;
while (bv_len &&
diff_buffer_iter_get(chunk->diff_buffer, buff_offset,
&diff_buffer_iter)) {
size_t sz;
if (io_ctx->is_write)
sz = copy_page_from_iter(
diff_buffer_iter.page,
diff_buffer_iter.offset,
diff_buffer_iter.bytes,
&iter);
else
sz = copy_page_to_iter(
diff_buffer_iter.page,
diff_buffer_iter.offset,
diff_buffer_iter.bytes,
&iter);
if (!sz)
return BLK_STS_IOERR;
buff_offset += sz;
*pos += (sz >> SECTOR_SHIFT);
bv_len -= sz;
}
}
return BLK_STS_OK;
}
static inline void diff_area_event_corrupted(struct diff_area *diff_area,
int err_code)
{
struct blk_snap_event_corrupted data = {
.orig_dev_id.mj = MAJOR(diff_area->orig_bdev->bd_dev),
.orig_dev_id.mn = MINOR(diff_area->orig_bdev->bd_dev),
.err_code = abs(err_code),
};
event_gen(&diff_area->diff_storage->event_queue, GFP_NOIO,
blk_snap_event_code_corrupted, &data,
sizeof(struct blk_snap_event_corrupted));
}
void diff_area_set_corrupted(struct diff_area *diff_area, int err_code)
{
if (atomic_inc_return(&diff_area->corrupt_flag) != 1)
return;
diff_area_event_corrupted(diff_area, err_code);
pr_err("Set snapshot device is corrupted for [%u:%u] with error code %d\n",
MAJOR(diff_area->orig_bdev->bd_dev),
MINOR(diff_area->orig_bdev->bd_dev), abs(err_code));
}
void diff_area_throttling_io(struct diff_area *diff_area)
{
}
#ifdef BLK_SNAP_DEBUG_SECTOR_STATE
int diff_area_get_sector_state(struct diff_area *diff_area, sector_t sector,
unsigned int *chunk_state)
{
struct chunk *chunk;
sector_t chunk_sectors = diff_area_chunk_sectors(diff_area);
sector_t offset = round_down(sector, chunk_sectors);
spin_lock(&diff_area->chunk_map_lock);
chunk = xa_load(&diff_area->chunk_map, diff_area_chunk_number(diff_area, offset));
if (chunk)
atomic_inc(&chunk->refcount);
spin_unlock(&diff_area->chunk_map_lock);
if (!chunk)
return -EINVAL;
WARN_ON(diff_area_chunk_number(diff_area, offset) != chunk->number);
down(&chunk->lock);
*chunk_state = atomic_read(&chunk->state);
chunk_up(chunk);
return 0;
}
int diff_area_get_sector_image(struct diff_area *diff_area, sector_t pos,
void *buf)
{
struct chunk *chunk;
struct diff_area_image_ctx io_ctx;
struct diff_buffer_iter diff_buffer_iter;
diff_area_image_ctx_init(&io_ctx, diff_area, false);
chunk = diff_area_image_context_get_chunk(&io_ctx, pos);
if (IS_ERR(chunk))
return PTR_ERR(chunk);
diff_buffer_iter_get(chunk->diff_buffer, pos - chunk_sector(chunk),
&diff_buffer_iter);
memcpy(buf,
page_address(diff_buffer_iter.page) + diff_buffer_iter.offset,
SECTOR_SIZE);
diff_area_image_ctx_done(&io_ctx);
return 0;
}
#endif