libblkid: add blkid_probe_set_vfs() for pluggable I/O

Add public API to set custom VFS I/O operations on a blkid probe.
This allows callers (e.g., systemd fibers) to replace standard
read/write/lseek/open/close/fsync with custom implementations.

The ops struct is copied into a private allocation owned by the
probe. NULL function pointers fall back to standard syscalls.
Passing NULL resets to defaults.

New public symbol: blkid_probe_set_vfs()
New struct in public header: struct ul_vfs_ops (with include guard
shared with include/vfs.h)

Addresses: https://github.com/util-linux/util-linux/issues/4308
Signed-off-by: Karel Zak <kzak@redhat.com>
diff --git a/libblkid/docs/libblkid-sections.txt b/libblkid/docs/libblkid-sections.txt
index 60a43b5..75b2b3a 100644
--- a/libblkid/docs/libblkid-sections.txt
+++ b/libblkid/docs/libblkid-sections.txt
@@ -62,6 +62,7 @@
 blkid_probe_set_device
 blkid_probe_set_hint
 blkid_probe_set_sectorsize
+blkid_probe_set_vfs
 blkid_probe_step_back
 blkid_reset_probe
 BLKID_PROBE_OK
diff --git a/libblkid/src/blkid.h.in b/libblkid/src/blkid.h.in
index 8dfea21..adfd3c3 100644
--- a/libblkid/src/blkid.h.in
+++ b/libblkid/src/blkid.h.in
@@ -243,6 +243,25 @@
 	                blkid_loff_t off, blkid_loff_t size)
 			__ul_attribute__((nonnull));
 
+#ifndef UL_VFS_OPS_DEFINED
+#define UL_VFS_OPS_DEFINED
+
+struct ul_vfs_ops {
+	size_t size;
+
+	ssize_t (*vfs_read)(int fd, void *buf, size_t count);
+	ssize_t (*vfs_write)(int fd, const void *buf, size_t count);
+	int     (*vfs_open)(const char *pathname, int flags, mode_t mode);
+	int     (*vfs_close)(int fd);
+	off_t   (*vfs_lseek)(int fd, off_t offset, int whence);
+	int     (*vfs_fsync)(int fd);
+};
+
+#endif /* UL_VFS_OPS_DEFINED */
+
+extern int blkid_probe_set_vfs(blkid_probe pr, const struct ul_vfs_ops *ops)
+			__ul_attribute__((nonnull(1)));
+
 extern dev_t blkid_probe_get_devno(blkid_probe pr)
 			__ul_attribute__((nonnull));
 
diff --git a/libblkid/src/blkidP.h b/libblkid/src/blkidP.h
index 753dbb5..66ef69a 100644
--- a/libblkid/src/blkidP.h
+++ b/libblkid/src/blkidP.h
@@ -32,6 +32,7 @@
 #include "blkdev.h"
 
 #include "debug.h"
+#include "vfs.h"
 #include "blkid.h"
 #include "list.h"
 #include "encode.h"
@@ -232,6 +233,8 @@
 
 	struct blkid_struct_probe *parent;	/* for clones */
 	struct blkid_struct_probe *disk_probe;	/* whole-disk probing */
+
+	struct ul_vfs_ops	*vfs;		/* pluggable I/O ops (owned, or NULL) */
 };
 
 /* private flags library flags */
diff --git a/libblkid/src/libblkid.sym b/libblkid/src/libblkid.sym
index 01b6170..cf520d9 100644
--- a/libblkid/src/libblkid.sym
+++ b/libblkid/src/libblkid.sym
@@ -194,4 +194,5 @@
 
 BLKID_2_43 {
     blkid_evaluate_tag2;
+    blkid_probe_set_vfs;
 } BLKID_2_40;
diff --git a/libblkid/src/probe.c b/libblkid/src/probe.c
index 45691f4..3464a37 100644
--- a/libblkid/src/probe.c
+++ b/libblkid/src/probe.c
@@ -2245,6 +2245,45 @@
 }
 
 /**
+ * blkid_probe_set_vfs:
+ * @pr: probe
+ * @ops: VFS operations or NULL to reset to defaults
+ *
+ * Sets custom I/O operations for the probe. This allows replacing
+ * standard read/write/lseek/etc. with custom implementations
+ * (e.g., fiber-aware I/O).
+ *
+ * The @ops struct is copied into a private allocation owned by the
+ * probe. The caller sets ops->size to sizeof(struct ul_vfs_ops) to
+ * enable forward/backward compatibility. NULL function pointers fall
+ * back to standard syscalls. Passing @ops as NULL frees the private
+ * copy and resets the probe to default (direct syscall) I/O.
+ *
+ * Note: blkid_new_probe_from_filename() opens the device before VFS
+ * can be set. VFS users should use blkid_new_probe(), then
+ * blkid_probe_set_vfs(), then blkid_probe_open_device().
+ *
+ * Since: 2.43
+ *
+ * Returns: 0 on success, or <0 in case of error.
+ */
+int blkid_probe_set_vfs(blkid_probe pr, const struct ul_vfs_ops *ops)
+{
+	if (!ops) {
+		free(pr->vfs);
+		pr->vfs = NULL;
+		return 0;
+	}
+	if (!pr->vfs) {
+		pr->vfs = calloc(1, sizeof(*pr->vfs));
+		if (!pr->vfs)
+			return -ENOMEM;
+	}
+	ul_vfs_init(pr->vfs, ops);
+	return 0;
+}
+
+/**
  * blkid_probe_get_sectors:
  * @pr: probe
  *