zloop: forget write cache on force removal

Add a new options that causes zloop to truncate the zone files to the
write pointer value recorded at the last cache flush to simulate
unclean shutdowns.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Link: https://patch.msgid.link/20260323071156.2940772-3-hch@lst.de
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Christoph Hellwig
2026-03-23 08:11:50 +01:00
committed by Jens Axboe
parent eff8d1656e
commit 829def1e35
2 changed files with 102 additions and 0 deletions

View File

@@ -104,6 +104,11 @@ ordered_zone_append Enable zloop mitigation of zone append reordering.
(extents), as when enabled, this can significantly reduce
the number of data extents needed to for a file data
mapping.
discard_write_cache Discard all data that was not explicitly persisted using a
flush operation when the device is removed by truncating
each zone file to the size recorded during the last flush
operation. This simulates power fail events where
uncommitted data is lost.
=================== =========================================================
3) Deleting a Zoned Device

View File

@@ -17,6 +17,7 @@
#include <linux/mutex.h>
#include <linux/parser.h>
#include <linux/seq_file.h>
#include <linux/xattr.h>
/*
* Options for adding (and removing) a device.
@@ -34,6 +35,7 @@ enum {
ZLOOP_OPT_BUFFERED_IO = (1 << 8),
ZLOOP_OPT_ZONE_APPEND = (1 << 9),
ZLOOP_OPT_ORDERED_ZONE_APPEND = (1 << 10),
ZLOOP_OPT_DISCARD_WRITE_CACHE = (1 << 11),
};
static const match_table_t zloop_opt_tokens = {
@@ -48,6 +50,7 @@ static const match_table_t zloop_opt_tokens = {
{ ZLOOP_OPT_BUFFERED_IO, "buffered_io" },
{ ZLOOP_OPT_ZONE_APPEND, "zone_append=%u" },
{ ZLOOP_OPT_ORDERED_ZONE_APPEND, "ordered_zone_append" },
{ ZLOOP_OPT_DISCARD_WRITE_CACHE, "discard_write_cache" },
{ ZLOOP_OPT_ERR, NULL }
};
@@ -79,6 +82,7 @@ struct zloop_options {
bool buffered_io;
bool zone_append;
bool ordered_zone_append;
bool discard_write_cache;
};
/*
@@ -119,6 +123,7 @@ struct zloop_device {
bool buffered_io;
bool zone_append;
bool ordered_zone_append;
bool discard_write_cache;
const char *base_dir;
struct file *data_dir;
@@ -550,6 +555,41 @@ static void zloop_rw(struct zloop_cmd *cmd)
zloop_put_cmd(cmd);
}
static inline bool zloop_zone_is_active(struct zloop_zone *zone)
{
switch (zone->cond) {
case BLK_ZONE_COND_EXP_OPEN:
case BLK_ZONE_COND_IMP_OPEN:
case BLK_ZONE_COND_CLOSED:
return true;
default:
return false;
}
}
static int zloop_record_safe_wps(struct zloop_device *zlo)
{
unsigned int i;
int ret;
for (i = 0; i < zlo->nr_zones; i++) {
struct zloop_zone *zone = &zlo->zones[i];
struct file *file = zone->file;
if (!zloop_zone_is_active(zone))
continue;
ret = vfs_setxattr(file_mnt_idmap(file), file_dentry(file),
"user.zloop.wp", &zone->wp, sizeof(zone->wp), 0);
if (ret) {
pr_err("%pg: failed to record write pointer (%d)\n",
zlo->disk->part0, ret);
return ret;
}
}
return 0;
}
/*
* Sync the entire FS containing the zone files instead of walking all files.
*/
@@ -558,6 +598,12 @@ static int zloop_flush(struct zloop_device *zlo)
struct super_block *sb = file_inode(zlo->data_dir)->i_sb;
int ret;
if (zlo->discard_write_cache) {
ret = zloop_record_safe_wps(zlo);
if (ret)
return ret;
}
down_read(&sb->s_umount);
ret = sync_filesystem(sb);
up_read(&sb->s_umount);
@@ -1054,6 +1100,7 @@ static int zloop_ctl_add(struct zloop_options *opts)
zlo->zone_append = opts->zone_append;
if (zlo->zone_append)
zlo->ordered_zone_append = opts->ordered_zone_append;
zlo->discard_write_cache = opts->discard_write_cache;
zlo->workqueue = alloc_workqueue("zloop%d", WQ_UNBOUND | WQ_FREEZABLE,
opts->nr_queues * opts->queue_depth, zlo->id);
@@ -1176,6 +1223,49 @@ static int zloop_ctl_add(struct zloop_options *opts)
return ret;
}
static void zloop_truncate(struct file *file, loff_t pos)
{
struct mnt_idmap *idmap = file_mnt_idmap(file);
struct dentry *dentry = file_dentry(file);
struct iattr newattrs;
newattrs.ia_size = pos;
newattrs.ia_valid = ATTR_SIZE;
inode_lock(dentry->d_inode);
notify_change(idmap, dentry, &newattrs, NULL);
inode_unlock(dentry->d_inode);
}
static void zloop_forget_cache(struct zloop_device *zlo)
{
unsigned int i;
int ret;
pr_info("%pg: discarding volatile write cache\n", zlo->disk->part0);
for (i = 0; i < zlo->nr_zones; i++) {
struct zloop_zone *zone = &zlo->zones[i];
struct file *file = zone->file;
sector_t old_wp;
if (!zloop_zone_is_active(zone))
continue;
ret = vfs_getxattr(file_mnt_idmap(file), file_dentry(file),
"user.zloop.wp", &old_wp, sizeof(old_wp));
if (ret == -ENODATA) {
old_wp = 0;
} else if (ret != sizeof(old_wp)) {
pr_err("%pg: failed to retrieve write pointer (%d)\n",
zlo->disk->part0, ret);
continue;
}
if (old_wp < zone->wp)
zloop_truncate(file, old_wp);
}
}
static int zloop_ctl_remove(struct zloop_options *opts)
{
struct zloop_device *zlo;
@@ -1210,6 +1300,10 @@ static int zloop_ctl_remove(struct zloop_options *opts)
return ret;
del_gendisk(zlo->disk);
if (zlo->discard_write_cache)
zloop_forget_cache(zlo);
put_disk(zlo->disk);
pr_info("Removed device %d\n", opts->id);
@@ -1361,6 +1455,9 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)
case ZLOOP_OPT_ORDERED_ZONE_APPEND:
opts->ordered_zone_append = true;
break;
case ZLOOP_OPT_DISCARD_WRITE_CACHE:
opts->discard_write_cache = true;
break;
case ZLOOP_OPT_ERR:
default:
pr_warn("unknown parameter or missing value '%s'\n", p);