[RFC 4/9] dm: add llseek(SEEK_HOLE/SEEK_DATA) support

Stefan Hajnoczi
2024-03-28T20:40:11Z

Delegate SEEK_HOLE/SEEK_DATA to device-mapper targets. The new
dm_seek_hole_data() callback allows target types to customize behavior.
The default implementation treats the target as all data with no holes.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
include/linux/device-mapper.h | 5 +++
drivers/md/dm.c | 68 +++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+)

diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h
index 82b2195efaca7..e89ebaab6507a 100644
--- a/include/linux/device-mapper.h
+++ b/include/linux/device-mapper.h
@@ -161,6 +161,10 @@ typedef int (*dm_dax_zero_page_range_fn)(struct dm_target *ti, pgoff_t pgoff,
typedef size_t (*dm_dax_recovery_write_fn)(struct dm_target *ti, pgoff_t pgoff,
void *addr, size_t bytes, struct iov_iter *i);

+/* Like llseek(SEEK_HOLE/SEEK_DATA) */
+typedef loff_t (*dm_seek_hole_data)(struct dm_target *ti, loff_t offset,
+ int whence);
+
void dm_error(const char *message);

struct dm_dev {
@@ -210,6 +214,7 @@ struct target_type {
dm_dax_direct_access_fn direct_access;
dm_dax_zero_page_range_fn dax_zero_page_range;
dm_dax_recovery_write_fn dax_recovery_write;
+ dm_seek_hole_data seek_hole_data;

/* For internal device-mapper use. */
struct list_head list;
diff --git a/drivers/md/dm.c b/drivers/md/dm.c
index 56aa2a8b9d715..3c921bdbd17fc 100644
--- a/drivers/md/dm.c
+++ b/drivers/md/dm.c
@@ -3167,6 +3167,72 @@ void dm_free_md_mempools(struct dm_md_mempools *pools)
kfree(pools);
}

+/* Default implementation for targets that do not implement the callback */
+static loff_t dm_blk_seek_hole_data_default(loff_t offset, int whence,
+ loff_t size)
+{
+ switch (whence) {
+ case SEEK_DATA:
+ if ((unsigned long long)offset >= size)
+ return -ENXIO;
+ return offset;
+ case SEEK_HOLE:
+ if ((unsigned long long)offset >= size)
+ return -ENXIO;
+ return size;
+ default:
+ return -EINVAL;
+ }
+}
+
+static loff_t dm_blk_do_seek_hole_data(struct dm_table *table, loff_t offset,
+ int whence)
+{
+ struct dm_target *ti;
+ loff_t end;
+
+ /* Loop when the end of a target is reached */
+ do {
+ ti = dm_table_find_target(table, offset >> SECTOR_SHIFT);
+ if (!ti)
+ return whence == SEEK_DATA ? -ENXIO : offset;
+
+ end = (ti->begin + ti->len) << SECTOR_SHIFT;
+
+ if (ti->type->seek_hole_data)
+ offset = ti->type->seek_hole_data(ti, offset, whence);
+ else
+ offset = dm_blk_seek_hole_data_default(offset, whence, end);
+
+ if (whence == SEEK_DATA && offset == -ENXIO)
+ offset = end;
+ } while (offset == end);
+
+ return offset;
+}
+
+static loff_t dm_blk_seek_hole_data(struct block_device *bdev, loff_t offset,
+ int whence)
+{
+ struct mapped_device *md = bdev->bd_disk->private_data;
+ struct dm_table *table;
+ int srcu_idx;
+ loff_t ret;
+
+ if (dm_suspended_md(md))
+ return -EAGAIN;
+
+ table = dm_get_live_table(md, &srcu_idx);
+ if (!table)
+ return -EIO;
+
+ ret = dm_blk_do_seek_hole_data(table, offset, whence);
+
+ dm_put_live_table(md, srcu_idx);
+
+ return ret;
+}
+
struct dm_pr {
u64 old_key;
u64 new_key;
@@ -3493,6 +3559,7 @@ static const struct block_device_operations dm_blk_dops = {
.getgeo = dm_blk_getgeo,
.report_zones = dm_blk_report_zones,
.pr_ops = &dm_pr_ops,
+ .seek_hole_data = dm_blk_seek_hole_data,
.owner = THIS_MODULE
};

@@ -3502,6 +3569,7 @@ static const struct block_device_operations dm_rq_blk_dops = {
.ioctl = dm_blk_ioctl,
.getgeo = dm_blk_getgeo,
.pr_ops = &dm_pr_ops,
+ .seek_hole_data = dm_blk_seek_hole_data,
.owner = THIS_MODULE
};

--
2.44.0