From: Namjae Jeon <linkinjeon@kernel.org>
To: sj1557.seo@samsung.com, yuezhang.mo@sony.com, brauner@kernel.org,
djwong@kernel.org, hch@lst.de
Cc: linux-fsdevel@vger.kernel.org, anmuxixixi@gmail.com,
dxdt@dev.snart.me, chizhiling@kylinos.cn,
linux-kernel@vger.kernel.org, Namjae Jeon <linkinjeon@kernel.org>
Subject: [PATCH v2 9/9] exfat: add support for SEEK_HOLE and SEEK_DATA in llseek
Date: Thu, 7 May 2026 21:42:38 +0900 [thread overview]
Message-ID: <20260507124238.7313-10-linkinjeon@kernel.org> (raw)
In-Reply-To: <20260507124238.7313-1-linkinjeon@kernel.org>
Adds exfat_file_llseek() that implements these whence values via
the iomap layer (iomap_seek_hole() and iomap_seek_data()) using the
existing exfat_read_iomap_ops.
Unlike many other modern filesystems, exFAT does not support sparse files
with unallocated clusters (holes). In exFAT, clusters are always fully
allocated once they are written or preallocated. In addition, exFAT
maintains a separate "Valid Data Length" (valid_size) that is distinct
from the logical file size. This affects how holes are reported during
seeking. In exfat_iomap_begin(), ranges where the offset is greater
than or equal to ei->valid_size are mapped as IOMAP_UNWRITTEN, while ranges
below valid_size are mapped as IOMAP_MAPPED. This mapping behavior is used
by the iomap seek functions to correctly report SEEK_HOLE and SEEK_DATA
positions.
- Ranges with offset >= ei->valid_size are mapped as IOMAP_HOLE.
- Ranges with offset < ei->valid_size are mapped as IOMAP_MAPPED.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/exfat/file.c | 25 ++++++++++++++++++++++++-
fs/exfat/iomap.c | 32 ++++++++++++++++++++++++++++----
2 files changed, 52 insertions(+), 5 deletions(-)
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index c4e6afc21bfe..c1567d206253 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -911,9 +911,32 @@ static int exfat_file_open(struct inode *inode, struct file *filp)
return 0;
}
+static loff_t exfat_file_llseek(struct file *file, loff_t offset, int whence)
+{
+ struct inode *inode = file->f_mapping->host;
+
+ switch (whence) {
+ case SEEK_HOLE:
+ inode_lock_shared(inode);
+ offset = iomap_seek_hole(inode, offset, &exfat_iomap_ops);
+ inode_unlock_shared(inode);
+ break;
+ case SEEK_DATA:
+ inode_lock_shared(inode);
+ offset = iomap_seek_data(inode, offset, &exfat_iomap_ops);
+ inode_unlock_shared(inode);
+ break;
+ default:
+ return generic_file_llseek(file, offset, whence);
+ }
+ if (offset < 0)
+ return offset;
+ return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
+}
+
const struct file_operations exfat_file_operations = {
.open = exfat_file_open,
- .llseek = generic_file_llseek,
+ .llseek = exfat_file_llseek,
.read_iter = exfat_file_read_iter,
.write_iter = exfat_file_write_iter,
.unlocked_ioctl = exfat_ioctl,
diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index 69308d66c55a..9b1499a30d39 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -79,15 +79,39 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
else if (iomap->offset + iomap->length >= ei->valid_size)
iomap->flags = IOMAP_F_ZERO_TAIL;
} else {
+ /*
+ * valid_size is tracked in byte granularity and
+ * marks the exact boundary between valid data and
+ * holes (or unwritten space).
+ *
+ * When IOMAP_REPORT is set (used by lseek(SEEK_HOLE)
+ * and SEEK_DATA), we return IOMAP_HOLE. This allows
+ * iomap_seek_hole_iter() to directly return the
+ * precise byte position.
+ *
+ * For normal I/O paths (without IOMAP_REPORT) we
+ * return IOMAP_UNWRITTEN so the write path can
+ * distinguish it from a real hole.
+ */
if (offset >= ei->valid_size)
- iomap->type = IOMAP_UNWRITTEN;
+ iomap->type = flags & IOMAP_REPORT ?
+ IOMAP_HOLE : IOMAP_UNWRITTEN;
if (iomap->type == IOMAP_MAPPED &&
iomap->offset < ei->valid_size &&
iomap->offset + iomap->length > ei->valid_size) {
- iomap->length = round_up(ei->valid_size,
- 1 << inode->i_blkbits) -
- iomap->offset;
+ if (flags & IOMAP_REPORT) {
+ /*
+ * For SEEK_HOLE/SEEK_DATA, clip the length
+ * to the exact byte boundary (valid_size).
+ * This ensures the caller gets the precise
+ * hole position in byte units.
+ */
+ iomap->length = ei->valid_size - iomap->offset;
+ } else
+ iomap->length = round_up(ei->valid_size,
+ 1 << inode->i_blkbits) -
+ iomap->offset;
}
}
--
2.25.1
prev parent reply other threads:[~2026-05-07 12:45 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-07 12:42 [PATCH v2 0/9] exfat: convert to iomap Namjae Jeon
2026-05-07 12:42 ` [PATCH v2 1/9] exfat: replace unsafe macros with static inline functions Namjae Jeon
2026-05-07 13:41 ` CharSyam
2026-05-07 23:36 ` Namjae Jeon
2026-05-07 12:42 ` [PATCH v2 2/9] exfat: add balloc parameter to exfat_map_cluster() for iomap support Namjae Jeon
2026-05-07 12:42 ` [PATCH v2 3/9] exfat: add exfat_file_open() Namjae Jeon
2026-05-07 13:52 ` CharSyam
2026-05-07 23:37 ` Namjae Jeon
2026-05-07 12:42 ` [PATCH v2 4/9] exfat: add support for multi-cluster allocation Namjae Jeon
2026-05-07 14:09 ` CharSyam
2026-05-08 0:27 ` Namjae Jeon
2026-05-10 13:32 ` Chi Zhiling
2026-05-11 0:20 ` Namjae Jeon
2026-05-11 0:45 ` Chi Zhiling
2026-05-07 12:42 ` [PATCH v2 5/9] iomap: introduce IOMAP_F_ZERO_TAIL flag Namjae Jeon
2026-05-09 9:59 ` Chi Zhiling
2026-05-09 14:30 ` Namjae Jeon
2026-05-11 12:45 ` Christoph Hellwig
2026-05-11 13:46 ` Namjae Jeon
2026-05-07 12:42 ` [PATCH v2 6/9] exfat: add data_start_bytes and exfat_cluster_to_phys() helper Namjae Jeon
2026-05-07 12:42 ` [PATCH v2 7/9] exfat: add iomap buffered I/O support Namjae Jeon
2026-05-07 12:42 ` [PATCH v2 8/9] exfat: add iomap direct " Namjae Jeon
2026-05-07 12:42 ` Namjae Jeon [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260507124238.7313-10-linkinjeon@kernel.org \
--to=linkinjeon@kernel.org \
--cc=anmuxixixi@gmail.com \
--cc=brauner@kernel.org \
--cc=chizhiling@kylinos.cn \
--cc=djwong@kernel.org \
--cc=dxdt@dev.snart.me \
--cc=hch@lst.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=sj1557.seo@samsung.com \
--cc=yuezhang.mo@sony.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox