if (!attr_b->non_res) {
*lcn = RESIDENT_LCN;
- *len = 1;
+ *len = le32_to_cpu(attr_b->res.data_size);
goto out;
}
err = -EINVAL;
} else {
*len = 1;
- *lcn = SPARSE_LCN;
+ *lcn = EOF_LCN;
}
goto out;
}
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,
};
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,
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
*
#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,
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 */
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);