diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 9cd34149a188..c5ff2a97a465 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -926,9 +926,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 7ad94d5806d9..3ac1eebe997f 100644 --- a/fs/exfat/iomap.c +++ b/fs/exfat/iomap.c @@ -100,12 +100,36 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length 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; } else if (offset + iomap->length > ei->valid_size) { - iomap->length = round_up(ei->valid_size, - i_blocksize(inode)) - - 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, + i_blocksize(inode)) - + iomap->offset; } }