From: Christoph Hellwig Date: Mon, 23 Mar 2026 07:11:50 +0000 (+0100) Subject: zloop: forget write cache on force removal X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=829def1e35ca3a6ef07d53d47089ef7cff0fd127;p=thirdparty%2Fkernel%2Flinux.git 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 Reviewed-by: Bart Van Assche Reviewed-by: Damien Le Moal Reviewed-by: Martin K. Petersen Link: https://patch.msgid.link/20260323071156.2940772-3-hch@lst.de Signed-off-by: Jens Axboe --- diff --git a/Documentation/admin-guide/blockdev/zoned_loop.rst b/Documentation/admin-guide/blockdev/zoned_loop.rst index 6aa865424ac38..a01f857b36adb 100644 --- a/Documentation/admin-guide/blockdev/zoned_loop.rst +++ b/Documentation/admin-guide/blockdev/zoned_loop.rst @@ -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 diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index 8ca37ca1935a5..86a1324c27b38 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -17,6 +17,7 @@ #include #include #include +#include /* * 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 @@ out: 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 @@ out: 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);