Skip to content

Commit c613269

Browse files
fs/ntfs3: implement llseek SEEK_DATA/SEEK_HOLE by scanning data runs
The generic llseek implementation does not understand ntfs data runs, sparse regions, or compression semantics, and therefore cannot correctly locate data or holes in files. Add a filesystem-specific llseek handler that scans attribute data runs to find the next data or hole starting at the given offset. Handle resident attributes, sparse runs, compressed holes, and the implicit hole at end-of-file. Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
1 parent 356fa24 commit c613269

5 files changed

Lines changed: 113 additions & 3 deletions

File tree

fs/ntfs3/attrib.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
940940

941941
if (!attr_b->non_res) {
942942
*lcn = RESIDENT_LCN;
943-
*len = 1;
943+
*len = le32_to_cpu(attr_b->res.data_size);
944944
goto out;
945945
}
946946

@@ -950,7 +950,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
950950
err = -EINVAL;
951951
} else {
952952
*len = 1;
953-
*lcn = SPARSE_LCN;
953+
*lcn = EOF_LCN;
954954
}
955955
goto out;
956956
}

fs/ntfs3/file.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,31 @@ int ntfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync)
14741474
return ret;
14751475
}
14761476

1477+
/*
1478+
* ntfs_llseek - file_operations::llseek
1479+
*/
1480+
static loff_t ntfs_llseek(struct file *file, loff_t offset, int whence)
1481+
{
1482+
struct inode *inode = file->f_mapping->host;
1483+
struct ntfs_inode *ni = ntfs_i(inode);
1484+
loff_t maxbytes = ntfs_get_maxbytes(ni);
1485+
loff_t ret;
1486+
1487+
if (whence == SEEK_DATA || whence == SEEK_HOLE) {
1488+
inode_lock_shared(inode);
1489+
/* Scan fragments for hole or data. */
1490+
ret = ni_seek_data_or_hole(ni, offset, whence == SEEK_DATA);
1491+
inode_unlock_shared(inode);
1492+
1493+
if (ret >= 0)
1494+
ret = vfs_setpos(file, ret, maxbytes);
1495+
} else {
1496+
ret = generic_file_llseek_size(file, offset, whence, maxbytes,
1497+
i_size_read(inode));
1498+
}
1499+
return ret;
1500+
}
1501+
14771502
// clang-format off
14781503
const struct inode_operations ntfs_file_inode_operations = {
14791504
.getattr = ntfs_getattr,
@@ -1485,7 +1510,7 @@ const struct inode_operations ntfs_file_inode_operations = {
14851510
};
14861511

14871512
const struct file_operations ntfs_file_operations = {
1488-
.llseek = generic_file_llseek,
1513+
.llseek = ntfs_llseek,
14891514
.read_iter = ntfs_file_read_iter,
14901515
.write_iter = ntfs_file_write_iter,
14911516
.unlocked_ioctl = ntfs_ioctl,

fs/ntfs3/frecord.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3001,6 +3001,82 @@ bool ni_is_dirty(struct inode *inode)
30013001
return false;
30023002
}
30033003

3004+
/*
3005+
* ni_seek_data_or_hole
3006+
*
3007+
* Helper function for ntfs_llseek( SEEK_DATA/SEEK_HOLE )
3008+
*/
3009+
loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data)
3010+
{
3011+
int err;
3012+
u8 cluster_bits = ni->mi.sbi->cluster_bits;
3013+
CLST vcn, lcn, clen;
3014+
loff_t vbo;
3015+
3016+
/* Enumerate all fragments. */
3017+
for (vcn = offset >> cluster_bits;; vcn += clen) {
3018+
err = attr_data_get_block(ni, vcn, 1, &lcn, &clen, NULL, false);
3019+
if (err) {
3020+
return err;
3021+
}
3022+
3023+
if (lcn == RESIDENT_LCN) {
3024+
/* clen - resident size in bytes. clen == ni->vfs_inode.i_size */
3025+
if (offset >= clen) {
3026+
/* check eof. */
3027+
return -ENXIO;
3028+
}
3029+
3030+
if (data) {
3031+
return offset;
3032+
}
3033+
3034+
return clen;
3035+
}
3036+
3037+
if (lcn == EOF_LCN) {
3038+
if (data) {
3039+
return -ENXIO;
3040+
}
3041+
3042+
/* implicit hole at the end of file. */
3043+
return ni->vfs_inode.i_size;
3044+
}
3045+
3046+
if (data) {
3047+
/*
3048+
* Adjust the file offset to the next location in the file greater than
3049+
* or equal to offset containing data. If offset points to data, then
3050+
* the file offset is set to offset.
3051+
*/
3052+
if (lcn != SPARSE_LCN) {
3053+
vbo = (u64)vcn << cluster_bits;
3054+
return max(vbo, offset);
3055+
}
3056+
} else {
3057+
/*
3058+
* Adjust the file offset to the next hole in the file greater than or
3059+
* equal to offset. If offset points into the middle of a hole, then the
3060+
* file offset is set to offset. If there is no hole past offset, then the
3061+
* file offset is adjusted to the end of the file
3062+
* (i.e., there is an implicit hole at the end of any file).
3063+
*/
3064+
if (lcn == SPARSE_LCN &&
3065+
/* native compression hole begins at aligned vcn. */
3066+
(!(ni->std_fa & FILE_ATTRIBUTE_COMPRESSED) ||
3067+
!(vcn & (NTFS_LZNT_CLUSTERS - 1)))) {
3068+
vbo = (u64)vcn << cluster_bits;
3069+
return max(vbo, offset);
3070+
}
3071+
}
3072+
3073+
if (!clen) {
3074+
/* Corrupted file. */
3075+
return -EINVAL;
3076+
}
3077+
}
3078+
}
3079+
30043080
/*
30053081
* ni_write_parents
30063082
*

fs/ntfs3/ntfs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ typedef u32 CLST;
8181
#define SPARSE_LCN ((CLST)-1)
8282
#define RESIDENT_LCN ((CLST)-2)
8383
#define COMPRESSED_LCN ((CLST)-3)
84+
#define EOF_LCN ((CLST)-4)
8485

8586
enum RECORD_NUM {
8687
MFT_REC_MFT = 0,

fs/ntfs3/ntfs_fs.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,7 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
591591
struct NTFS_DE *new_de);
592592

593593
bool ni_is_dirty(struct inode *inode);
594+
loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data);
594595
int ni_write_parents(struct ntfs_inode *ni, int sync);
595596

596597
/* Globals from fslog.c */
@@ -1107,6 +1108,13 @@ static inline int is_resident(struct ntfs_inode *ni)
11071108
return ni->ni_flags & NI_FLAG_RESIDENT;
11081109
}
11091110

1111+
static inline loff_t ntfs_get_maxbytes(struct ntfs_inode *ni)
1112+
{
1113+
struct ntfs_sb_info *sbi = ni->mi.sbi;
1114+
return is_sparsed(ni) || is_compressed(ni) ? sbi->maxbytes_sparse :
1115+
sbi->maxbytes;
1116+
}
1117+
11101118
static inline void le16_sub_cpu(__le16 *var, u16 val)
11111119
{
11121120
*var = cpu_to_le16(le16_to_cpu(*var) - val);

0 commit comments

Comments
 (0)