Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

dm cache: prevent entering passthrough mode after unclean shutdown

dm-cache assumes all cache blocks are dirty when it recovers from an
unclean shutdown. Given that the passthrough mode doesn't handle dirty
blocks, we should not load a cache in passthrough mode if it was not
cleanly shut down; or we'll risk data loss while updating an actually
dirty block.

Also bump the target version to 2.4.0 to mark completion of passthrough
mode fixes.

Reproduce steps:

1. Create a writeback cache with zero migration_threshold to produce
dirty blocks.

dmsetup create cmeta --table "0 8192 linear /dev/sdc 0"
dmsetup create cdata --table "0 131072 linear /dev/sdc 8192"
dmsetup create corig --table "0 262144 linear /dev/sdc 262144"
dd if=/dev/zero of=/dev/mapper/cmeta bs=4k count=1 oflag=direct
dmsetup create cache --table "0 262144 cache /dev/mapper/cmeta \
/dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 writeback smq \
2 migration_threshold 0"

2. Write the first cache block dirty

fio --filename=/dev/mapper/cache --name=populate --rw=write --bs=4k \
--direct=1 --size=64k

3. Ensure the number of dirty blocks is 1. This status query triggers
metadata commit without flushing the dirty bitset, setting up the
unclean shutdown state.

dmsetup status cache | awk '{print $14}'

4. Force reboot, leaving the cache uncleanly shutdown.

echo b > /proc/sysrq-trigger

5. Activate the above cache components, and verify the first data block
remains dirty.

dmsetup create cmeta --table "0 8192 linear /dev/sdc 0"
dmsetup create cdata --table "0 131072 linear /dev/sdc 8192"
dmsetup create corig --table "0 262144 linear /dev/sdc 262144"
dd if=/dev/mapper/cdata of=/tmp/cb0.bin bs=64k count=1
dd if=/dev/mapper/corig of=/tmp/ob0.bin bs=64k count=1
md5sum /tmp/cb0.bin /tmp/ob0.bin # expected to be different

6. Try bringing up the cache in passthrough mode. It succeeds, while the
first cache block was loaded dirty due to unclean shutdown, violates
the passthrough mode's constraints.

dmsetup create cache --table "0 262144 cache /dev/mapper/cmeta \
/dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 passthrough smq 0"
dmsetup status cache | awk '{print $14}'

7. (Optional) Demonstrate the integrity issue: invalidating the dirty
block in passthrough mode doesn't write back the dirty data, causing
data loss.

fio --filename=/dev/mapper/cache --name=invalidate --rw=write --bs=4k \
--direct=1 --size=4k # overwrite the first 4k to trigger invalidation
dmsetup remove cache
dd if=/dev/mapper/corig of=/tmp/ob0new.bin bs=64k count=1
cb0sum=$(dd if=/tmp/cb0.bin bs=4k count=15 skip=1 | md5sum | \
awk '{print $1}')
ob0newsum=$(dd if=/tmp/ob0new.bin bs=4k count=15 skip=1 | md5sum | \
awk '{print $1}')
echo "$cb0sum, $ob0newsum" # remaining 60k should differ (data loss)

Signed-off-by: Ming-Hung Tsai <mtsai@redhat.com>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

authored by

Ming-Hung Tsai and committed by
Mikulas Patocka
a373b3d5 32258674

+32 -1
+9
drivers/md/dm-cache-metadata.c
··· 1813 1813 1814 1814 return r; 1815 1815 } 1816 + 1817 + int dm_cache_metadata_clean_when_opened(struct dm_cache_metadata *cmd, bool *result) 1818 + { 1819 + READ_LOCK(cmd); 1820 + *result = cmd->clean_when_opened; 1821 + READ_UNLOCK(cmd); 1822 + 1823 + return 0; 1824 + }
+5
drivers/md/dm-cache-metadata.h
··· 141 141 void dm_cache_metadata_set_read_write(struct dm_cache_metadata *cmd); 142 142 int dm_cache_metadata_abort(struct dm_cache_metadata *cmd); 143 143 144 + /* 145 + * Query method. Was the metadata cleanly shut down when opened? 146 + */ 147 + int dm_cache_metadata_clean_when_opened(struct dm_cache_metadata *cmd, bool *result); 148 + 144 149 /*----------------------------------------------------------------*/ 145 150 146 151 #endif /* DM_CACHE_METADATA_H */
+18 -1
drivers/md/dm-cache-target.c
··· 2952 2952 2953 2953 static bool can_resume(struct cache *cache) 2954 2954 { 2955 + bool clean_when_opened; 2956 + int r; 2957 + 2955 2958 /* 2956 2959 * Disallow retrying the resume operation for devices that failed the 2957 2960 * first resume attempt, as the failure leaves the policy object partially ··· 2969 2966 DMERR("%s: unable to resume cache due to missing proper cache table reload", 2970 2967 cache_device_name(cache)); 2971 2968 return false; 2969 + } 2970 + 2971 + if (passthrough_mode(cache)) { 2972 + r = dm_cache_metadata_clean_when_opened(cache->cmd, &clean_when_opened); 2973 + if (r) { 2974 + DMERR("%s: failed to query metadata flags", cache_device_name(cache)); 2975 + return false; 2976 + } 2977 + 2978 + if (!clean_when_opened) { 2979 + DMERR("%s: unable to resume into passthrough mode after unclean shutdown", 2980 + cache_device_name(cache)); 2981 + return false; 2982 + } 2972 2983 } 2973 2984 2974 2985 return true; ··· 3550 3533 3551 3534 static struct target_type cache_target = { 3552 3535 .name = "cache", 3553 - .version = {2, 3, 0}, 3536 + .version = {2, 4, 0}, 3554 3537 .module = THIS_MODULE, 3555 3538 .ctr = cache_ctr, 3556 3539 .dtr = cache_dtr,