Skip to content

Commit c2662b1

Browse files
mingnusMikulas Patocka
authored andcommitted
dm cache: support shrinking the origin device
This patch introduces formal support for shrinking the cache origin by reducing the cache target length via table reloads. Cache blocks mapped beyond the new target length must be clean and are invalidated during preresume. If any dirty blocks exist in the area being removed, the preresume operation fails without setting the NEEDS_CHECK flag in superblock, and the resume ioctl returns EFBIG. The cache device remains suspended until a table reload with target length that fits existing mappings is performed. Without this patch, reducing the cache target length could result in io errors (RHBZ: 2134334), out-of-bounds memory access to the discard bitset, and security concerns regarding data leakage. Verification steps: 1. create a cache metadata with some cached blocks mapped to the tail of the origin device. Here we use cache_restore v1.0 to build a metadata with one clean block mapped to the last origin block. cat <<EOF >> cmeta.xml <superblock uuid="" block_size="128" nr_cache_blocks="512" \ policy="smq" hint_width="4"> <mappings> <mapping cache_block="0" origin_block="4095" dirty="false"/> </mappings> </superblock> EOF dmsetup create cmeta --table "0 8192 linear /dev/sdc 0" cache_restore -i cmeta.xml -o /dev/mapper/cmeta --metadata-version=2 dmsetup remove cmeta 2. bring up the cache whilst shrinking the cache origin by one block: dmsetup create cmeta --table "0 8192 linear /dev/sdc 0" dmsetup create cdata --table "0 65536 linear /dev/sdc 8192" dmsetup create corig --table "0 524160 linear /dev/sdc 262144" dmsetup create cache --table "0 524160 cache /dev/mapper/cmeta \ /dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 writethrough smq 0" 3. check the number of cached data blocks via dmsetup status. It is expected to be zero. dmsetup status cache | cut -d ' ' -f 7 In addition to the script above, this patch can be verified using the "cache/resize" tests in dmtest-python: ./dmtest run --rx cache/resize/shrink_origin --result-set default Signed-off-by: Ming-Hung Tsai <mtsai@redhat.com> Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
1 parent 5da692e commit c2662b1

File tree

1 file changed

+69
-3
lines changed

1 file changed

+69
-3
lines changed

drivers/md/dm-cache-target.c

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,12 @@ struct cache {
406406
mempool_t migration_pool;
407407

408408
struct bio_set bs;
409+
410+
/*
411+
* Cache_size entries. Set bits indicate blocks mapped beyond the
412+
* target length, which are marked for invalidation.
413+
*/
414+
unsigned long *invalid_bitset;
409415
};
410416

411417
struct per_bio_data {
@@ -1922,6 +1928,9 @@ static void __destroy(struct cache *cache)
19221928
if (cache->discard_bitset)
19231929
free_bitset(cache->discard_bitset);
19241930

1931+
if (cache->invalid_bitset)
1932+
free_bitset(cache->invalid_bitset);
1933+
19251934
if (cache->copier)
19261935
dm_kcopyd_client_destroy(cache->copier);
19271936

@@ -2510,6 +2519,13 @@ static int cache_create(struct cache_args *ca, struct cache **result)
25102519
}
25112520
clear_bitset(cache->discard_bitset, from_dblock(cache->discard_nr_blocks));
25122521

2522+
cache->invalid_bitset = alloc_bitset(from_cblock(cache->cache_size));
2523+
if (!cache->invalid_bitset) {
2524+
*error = "could not allocate bitset for invalid blocks";
2525+
goto bad;
2526+
}
2527+
clear_bitset(cache->invalid_bitset, from_cblock(cache->cache_size));
2528+
25132529
cache->copier = dm_kcopyd_client_create(&dm_kcopyd_throttle);
25142530
if (IS_ERR(cache->copier)) {
25152531
*error = "could not create kcopyd client";
@@ -2808,6 +2824,24 @@ static int load_mapping(void *context, dm_oblock_t oblock, dm_cblock_t cblock,
28082824
return policy_load_mapping(cache->policy, oblock, cblock, dirty, hint, hint_valid);
28092825
}
28102826

2827+
static int load_filtered_mapping(void *context, dm_oblock_t oblock, dm_cblock_t cblock,
2828+
bool dirty, uint32_t hint, bool hint_valid)
2829+
{
2830+
struct cache *cache = context;
2831+
2832+
if (from_oblock(oblock) >= from_oblock(cache->origin_blocks)) {
2833+
if (dirty) {
2834+
DMERR("%s: unable to shrink origin; cache block %u is dirty",
2835+
cache_device_name(cache), from_cblock(cblock));
2836+
return -EFBIG;
2837+
}
2838+
set_bit(from_cblock(cblock), cache->invalid_bitset);
2839+
return 0;
2840+
}
2841+
2842+
return load_mapping(context, oblock, cblock, dirty, hint, hint_valid);
2843+
}
2844+
28112845
/*
28122846
* The discard block size in the on disk metadata is not
28132847
* necessarily the same as we're currently using. So we have to
@@ -2962,6 +2996,24 @@ static int resize_cache_dev(struct cache *cache, dm_cblock_t new_size)
29622996
return 0;
29632997
}
29642998

2999+
static int truncate_oblocks(struct cache *cache)
3000+
{
3001+
uint32_t nr_blocks = from_cblock(cache->cache_size);
3002+
uint32_t i;
3003+
int r;
3004+
3005+
for_each_set_bit(i, cache->invalid_bitset, nr_blocks) {
3006+
r = dm_cache_remove_mapping(cache->cmd, to_cblock(i));
3007+
if (r) {
3008+
DMERR_LIMIT("%s: invalidation failed; couldn't update on disk metadata",
3009+
cache_device_name(cache));
3010+
return r;
3011+
}
3012+
}
3013+
3014+
return 0;
3015+
}
3016+
29653017
static int cache_preresume(struct dm_target *ti)
29663018
{
29673019
int r = 0;
@@ -2986,11 +3038,25 @@ static int cache_preresume(struct dm_target *ti)
29863038
}
29873039

29883040
if (!cache->loaded_mappings) {
3041+
/*
3042+
* The fast device could have been resized since the last
3043+
* failed preresume attempt. To be safe we start by a blank
3044+
* bitset for cache blocks.
3045+
*/
3046+
clear_bitset(cache->invalid_bitset, from_cblock(cache->cache_size));
3047+
29893048
r = dm_cache_load_mappings(cache->cmd, cache->policy,
2990-
load_mapping, cache);
3049+
load_filtered_mapping, cache);
29913050
if (r) {
29923051
DMERR("%s: could not load cache mappings", cache_device_name(cache));
2993-
metadata_operation_failed(cache, "dm_cache_load_mappings", r);
3052+
if (r != -EFBIG)
3053+
metadata_operation_failed(cache, "dm_cache_load_mappings", r);
3054+
return r;
3055+
}
3056+
3057+
r = truncate_oblocks(cache);
3058+
if (r) {
3059+
metadata_operation_failed(cache, "dm_cache_remove_mapping", r);
29943060
return r;
29953061
}
29963062

@@ -3450,7 +3516,7 @@ static void cache_io_hints(struct dm_target *ti, struct queue_limits *limits)
34503516

34513517
static struct target_type cache_target = {
34523518
.name = "cache",
3453-
.version = {2, 2, 0},
3519+
.version = {2, 3, 0},
34543520
.module = THIS_MODULE,
34553521
.ctr = cache_ctr,
34563522
.dtr = cache_dtr,

0 commit comments

Comments
 (0)