]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
zloop: forget write cache on force removal
authorChristoph Hellwig <hch@lst.de>
Mon, 23 Mar 2026 07:11:50 +0000 (08:11 +0100)
committerJens Axboe <axboe@kernel.dk>
Wed, 25 Mar 2026 12:49:01 +0000 (06:49 -0600)
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>
Documentation/admin-guide/blockdev/zoned_loop.rst
drivers/block/zloop.c

index 6aa865424ac383e9a49edffaebb622583a157638..a01f857b36adb7f443a84bb3ebcef55a72abfb79 100644 (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
index 8ca37ca1935a5c9408d0b16a237ca2bcbe606419..86a1324c27b383864e4d4b21a5703c0b42001b01 100644 (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 @@ 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);