[PATCH v3 2/2] exfat: EXFAT_IOC_GET_VALID_DATA ioctl

From: David Timber

Date: Wed Mar 11 2026 - 18:26:39 EST


When a file in exfat fs gets truncated up or fallocate()'d up, only
additional clusters are allocated and isize updated whilst VDL(valid
data length) remains unchanged. If an application writes to the file
past the VDL, significant IO delay can occur as the skipped range
[VDL, offset) has to be zeroed out before returning to userspace. Some
users may find this caveat unacceptible.

Some niche applications(especially embedded systems) may want to query
the discrepancy between the VDL and isize before doing lseek() and
write() to estimate the delay from implicit zeroring.

The commit introduces a new ioctl EXFAT_IOC_GET_VALID_DATA, which
correspond to `fsutil file queryvaliddata ...` available on Windows.
With the new ioctl, applications could assess the delay that may incur
and make decisions accordingly before seeking past the VDL to write.

Signed-off-by: David Timber <dxdt@xxxxxxxxxxxx>
---
fs/exfat/file.c | 22 ++++++++++++++++++++++
include/uapi/linux/exfat.h | 6 ++++--
2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 90cd540afeaa..a13044a7065a 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -449,6 +449,22 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr)
return err;
}

+static int exfat_ioctl_get_valid_data(struct inode *inode, unsigned long arg)
+{
+ u64 valid_size;
+
+ /*
+ * Doesn't really make sense to acquire lock for a getter op but we have
+ * to stay consistent with the grandfather clause -
+ * ioctl_get_attributes().
+ */
+ inode_lock(inode);
+ valid_size = EXFAT_I(inode)->valid_size;
+ inode_unlock(inode);
+
+ return put_user(valid_size, (__u64 __user *)arg);
+}
+
static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
{
struct fstrim_range range;
@@ -544,10 +560,15 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
u32 __user *user_attr = (u32 __user *)arg;

switch (cmd) {
+ /* inode-specific ops */
case FAT_IOCTL_GET_ATTRIBUTES:
return exfat_ioctl_get_attributes(inode, user_attr);
case FAT_IOCTL_SET_ATTRIBUTES:
return exfat_ioctl_set_attributes(filp, user_attr);
+ case EXFAT_IOC_GET_VALID_DATA:
+ return exfat_ioctl_get_valid_data(inode, arg);
+
+ /* fs-wide ops */
case EXFAT_IOC_SHUTDOWN:
return exfat_ioctl_shutdown(inode->i_sb, arg);
case FITRIM:
@@ -556,6 +577,7 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return exfat_ioctl_get_volume_label(inode->i_sb, arg);
case FS_IOC_SETFSLABEL:
return exfat_ioctl_set_volume_label(inode->i_sb, arg);
+
default:
return -ENOTTY;
}
diff --git a/include/uapi/linux/exfat.h b/include/uapi/linux/exfat.h
index 050dcea0aa12..cbc10458122e 100644
--- a/include/uapi/linux/exfat.h
+++ b/include/uapi/linux/exfat.h
@@ -12,8 +12,10 @@
* exfat-specific ioctl commands
*/

-#define EXFAT_IOCTL_MAGIC 0xEF /* shared with ntfs3 */
-#define EXFAT_IOC_SHUTDOWN _IOR('X', 125, __u32)
+#define EXFAT_IOCTL_MAGIC 0xEF /* shared with ntfs */
+#define EXFAT_IOC_SHUTDOWN _IOR('X', 125, __u32)
+/* Get the current valid data length(VDL) of a file */
+#define EXFAT_IOC_GET_VALID_DATA _IOR(EXFAT_IOCTL_MAGIC, 0x01, __u64)

/*
* Flags used by EXFAT_IOC_SHUTDOWN
--
2.53.0.1.ga224b40d3f.dirty