diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c index 0cd15a0983fe..3e188d6c229f 100644 --- a/fs/ntfs3/attrib.c +++ b/fs/ntfs3/attrib.c @@ -940,7 +940,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn, if (!attr_b->non_res) { *lcn = RESIDENT_LCN; - *len = 1; + *len = le32_to_cpu(attr_b->res.data_size); goto out; } @@ -950,7 +950,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn, err = -EINVAL; } else { *len = 1; - *lcn = SPARSE_LCN; + *lcn = EOF_LCN; } goto out; } diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index a88045ab549f..c89b1e7e734c 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -1474,6 +1474,31 @@ int ntfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync) return ret; } +/* + * ntfs_llseek - file_operations::llseek + */ +static loff_t ntfs_llseek(struct file *file, loff_t offset, int whence) +{ + struct inode *inode = file->f_mapping->host; + struct ntfs_inode *ni = ntfs_i(inode); + loff_t maxbytes = ntfs_get_maxbytes(ni); + loff_t ret; + + if (whence == SEEK_DATA || whence == SEEK_HOLE) { + inode_lock_shared(inode); + /* Scan fragments for hole or data. */ + ret = ni_seek_data_or_hole(ni, offset, whence == SEEK_DATA); + inode_unlock_shared(inode); + + if (ret >= 0) + ret = vfs_setpos(file, ret, maxbytes); + } else { + ret = generic_file_llseek_size(file, offset, whence, maxbytes, + i_size_read(inode)); + } + return ret; +} + // clang-format off const struct inode_operations ntfs_file_inode_operations = { .getattr = ntfs_getattr, @@ -1485,7 +1510,7 @@ const struct inode_operations ntfs_file_inode_operations = { }; const struct file_operations ntfs_file_operations = { - .llseek = generic_file_llseek, + .llseek = ntfs_llseek, .read_iter = ntfs_file_read_iter, .write_iter = ntfs_file_write_iter, .unlocked_ioctl = ntfs_ioctl, diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c index a123e3f0acde..03dcb66b5f6c 100644 --- a/fs/ntfs3/frecord.c +++ b/fs/ntfs3/frecord.c @@ -3001,6 +3001,82 @@ bool ni_is_dirty(struct inode *inode) return false; } +/* + * ni_seek_data_or_hole + * + * Helper function for ntfs_llseek( SEEK_DATA/SEEK_HOLE ) + */ +loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data) +{ + int err; + u8 cluster_bits = ni->mi.sbi->cluster_bits; + CLST vcn, lcn, clen; + loff_t vbo; + + /* Enumerate all fragments. */ + for (vcn = offset >> cluster_bits;; vcn += clen) { + err = attr_data_get_block(ni, vcn, 1, &lcn, &clen, NULL, false); + if (err) { + return err; + } + + if (lcn == RESIDENT_LCN) { + /* clen - resident size in bytes. clen == ni->vfs_inode.i_size */ + if (offset >= clen) { + /* check eof. */ + return -ENXIO; + } + + if (data) { + return offset; + } + + return clen; + } + + if (lcn == EOF_LCN) { + if (data) { + return -ENXIO; + } + + /* implicit hole at the end of file. */ + return ni->vfs_inode.i_size; + } + + if (data) { + /* + * Adjust the file offset to the next location in the file greater than + * or equal to offset containing data. If offset points to data, then + * the file offset is set to offset. + */ + if (lcn != SPARSE_LCN) { + vbo = (u64)vcn << cluster_bits; + return max(vbo, offset); + } + } else { + /* + * Adjust the file offset to the next hole in the file greater than or + * equal to offset. If offset points into the middle of a hole, then the + * file offset is set to offset. If there is no hole past offset, then the + * file offset is adjusted to the end of the file + * (i.e., there is an implicit hole at the end of any file). + */ + if (lcn == SPARSE_LCN && + /* native compression hole begins at aligned vcn. */ + (!(ni->std_fa & FILE_ATTRIBUTE_COMPRESSED) || + !(vcn & (NTFS_LZNT_CLUSTERS - 1)))) { + vbo = (u64)vcn << cluster_bits; + return max(vbo, offset); + } + } + + if (!clen) { + /* Corrupted file. */ + return -EINVAL; + } + } +} + /* * ni_write_parents * diff --git a/fs/ntfs3/ntfs.h b/fs/ntfs3/ntfs.h index 552b97905813..ae0a6ba102c0 100644 --- a/fs/ntfs3/ntfs.h +++ b/fs/ntfs3/ntfs.h @@ -81,6 +81,7 @@ typedef u32 CLST; #define SPARSE_LCN ((CLST)-1) #define RESIDENT_LCN ((CLST)-2) #define COMPRESSED_LCN ((CLST)-3) +#define EOF_LCN ((CLST)-4) enum RECORD_NUM { MFT_REC_MFT = 0, diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 482722438bd9..32823e1428a7 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -591,6 +591,7 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni, struct NTFS_DE *new_de); bool ni_is_dirty(struct inode *inode); +loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data); int ni_write_parents(struct ntfs_inode *ni, int sync); /* Globals from fslog.c */ @@ -1107,6 +1108,13 @@ static inline int is_resident(struct ntfs_inode *ni) return ni->ni_flags & NI_FLAG_RESIDENT; } +static inline loff_t ntfs_get_maxbytes(struct ntfs_inode *ni) +{ + struct ntfs_sb_info *sbi = ni->mi.sbi; + return is_sparsed(ni) || is_compressed(ni) ? sbi->maxbytes_sparse : + sbi->maxbytes; +} + static inline void le16_sub_cpu(__le16 *var, u16 val) { *var = cpu_to_le16(le16_to_cpu(*var) - val);