mirror of https://github.com/torvalds/linux.git
fs/ntfs3: correct attr_collapse_range when file is too fragmented
Fix incorrect VCN adjustments in attr_collapse_range() that caused filesystem errors or corruption on very fragmented NTFS files when performing collapse-range operations. Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
This commit is contained in:
parent
aee4d5a521
commit
2109b08024
|
|
@ -1860,7 +1860,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
struct ATTRIB *attr = NULL, *attr_b;
|
||||
struct ATTR_LIST_ENTRY *le, *le_b;
|
||||
struct mft_inode *mi, *mi_b;
|
||||
CLST svcn, evcn1, len, dealloc, alen;
|
||||
CLST svcn, evcn1, len, dealloc, alen, done;
|
||||
CLST vcn, end;
|
||||
u64 valid_size, data_size, alloc_size, total_size;
|
||||
u32 mask;
|
||||
|
|
@ -1923,6 +1923,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
len = bytes >> sbi->cluster_bits;
|
||||
end = vcn + len;
|
||||
dealloc = 0;
|
||||
done = 0;
|
||||
|
||||
svcn = le64_to_cpu(attr_b->nres.svcn);
|
||||
evcn1 = le64_to_cpu(attr_b->nres.evcn) + 1;
|
||||
|
|
@ -1931,23 +1932,28 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
attr = attr_b;
|
||||
le = le_b;
|
||||
mi = mi_b;
|
||||
} else if (!le_b) {
|
||||
goto check_seg;
|
||||
}
|
||||
|
||||
if (!le_b) {
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
} else {
|
||||
le = le_b;
|
||||
attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
|
||||
&mi);
|
||||
if (!attr) {
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
svcn = le64_to_cpu(attr->nres.svcn);
|
||||
evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
|
||||
le = le_b;
|
||||
attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, &mi);
|
||||
if (!attr) {
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
CLST vcn1, eat, next_svcn;
|
||||
|
||||
svcn = le64_to_cpu(attr->nres.svcn);
|
||||
evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
|
||||
|
||||
check_seg:
|
||||
if (svcn >= end) {
|
||||
/* Shift VCN- */
|
||||
attr->nres.svcn = cpu_to_le64(svcn - len);
|
||||
|
|
@ -1957,22 +1963,25 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
ni->attr_list.dirty = true;
|
||||
}
|
||||
mi->dirty = true;
|
||||
} else if (svcn < vcn || end < evcn1) {
|
||||
CLST vcn1, eat, next_svcn;
|
||||
goto next_attr;
|
||||
}
|
||||
|
||||
run_truncate(run, 0);
|
||||
err = attr_load_runs(attr, ni, run, &svcn);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
vcn1 = vcn + done; /* original vcn in attr/run. */
|
||||
eat = min(end, evcn1) - vcn1;
|
||||
|
||||
err = run_deallocate_ex(sbi, run, vcn1, eat, &dealloc, true);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (svcn + eat < evcn1) {
|
||||
/* Collapse a part of this attribute segment. */
|
||||
err = attr_load_runs(attr, ni, run, &svcn);
|
||||
if (err)
|
||||
goto out;
|
||||
vcn1 = max(vcn, svcn);
|
||||
eat = min(end, evcn1) - vcn1;
|
||||
|
||||
err = run_deallocate_ex(sbi, run, vcn1, eat, &dealloc,
|
||||
true);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (!run_collapse_range(run, vcn1, eat)) {
|
||||
if (!run_collapse_range(run, vcn1, eat, done)) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -1980,7 +1989,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
if (svcn >= vcn) {
|
||||
/* Shift VCN */
|
||||
attr->nres.svcn = cpu_to_le64(vcn);
|
||||
if (le) {
|
||||
if (le && attr->nres.svcn != le->vcn) {
|
||||
le->vcn = attr->nres.svcn;
|
||||
ni->attr_list.dirty = true;
|
||||
}
|
||||
|
|
@ -1991,7 +2000,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
goto out;
|
||||
|
||||
next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
|
||||
if (next_svcn + eat < evcn1) {
|
||||
if (next_svcn + eat + done < evcn1) {
|
||||
err = ni_insert_nonresident(
|
||||
ni, ATTR_DATA, NULL, 0, run, next_svcn,
|
||||
evcn1 - eat - next_svcn, a_flags, &attr,
|
||||
|
|
@ -2005,18 +2014,9 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
|
||||
/* Free all allocated memory. */
|
||||
run_truncate(run, 0);
|
||||
done += eat;
|
||||
} else {
|
||||
u16 le_sz;
|
||||
u16 roff = le16_to_cpu(attr->nres.run_off);
|
||||
|
||||
if (roff > le32_to_cpu(attr->size)) {
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
run_unpack_ex(RUN_DEALLOCATE, sbi, ni->mi.rno, svcn,
|
||||
evcn1 - 1, svcn, Add2Ptr(attr, roff),
|
||||
le32_to_cpu(attr->size) - roff);
|
||||
|
||||
/* Delete this attribute segment. */
|
||||
mi_remove_attr(NULL, mi, attr);
|
||||
|
|
@ -2029,6 +2029,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
goto out;
|
||||
}
|
||||
|
||||
done += evcn1 - svcn;
|
||||
if (evcn1 >= alen)
|
||||
break;
|
||||
|
||||
|
|
@ -2046,11 +2047,12 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
goto next_attr;
|
||||
continue;
|
||||
}
|
||||
le = (struct ATTR_LIST_ENTRY *)((u8 *)le - le_sz);
|
||||
}
|
||||
|
||||
next_attr:
|
||||
if (evcn1 >= alen)
|
||||
break;
|
||||
|
||||
|
|
@ -2059,10 +2061,6 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
next_attr:
|
||||
svcn = le64_to_cpu(attr->nres.svcn);
|
||||
evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
|
||||
}
|
||||
|
||||
if (!attr_b) {
|
||||
|
|
@ -2552,7 +2550,7 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
|
|||
if (attr_load_runs(attr, ni, run, NULL))
|
||||
goto bad_inode;
|
||||
|
||||
if (!run_collapse_range(run, vcn, len))
|
||||
if (!run_collapse_range(run, vcn, len, 0))
|
||||
goto bad_inode;
|
||||
|
||||
if (mi_pack_runs(mi, attr, run, evcn1 + len - svcn))
|
||||
|
|
|
|||
|
|
@ -777,7 +777,7 @@ bool mi_remove_attr(struct ntfs_inode *ni, struct mft_inode *mi,
|
|||
struct ATTRIB *attr);
|
||||
bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes);
|
||||
int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr,
|
||||
struct runs_tree *run, CLST len);
|
||||
const struct runs_tree *run, CLST len);
|
||||
static inline bool mi_is_ref(const struct mft_inode *mi,
|
||||
const struct MFT_REF *ref)
|
||||
{
|
||||
|
|
@ -812,7 +812,7 @@ void run_truncate_head(struct runs_tree *run, CLST vcn);
|
|||
void run_truncate_around(struct runs_tree *run, CLST vcn);
|
||||
bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len,
|
||||
bool is_mft);
|
||||
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len);
|
||||
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len, CLST sub);
|
||||
bool run_insert_range(struct runs_tree *run, CLST vcn, CLST len);
|
||||
bool run_get_entry(const struct runs_tree *run, size_t index, CLST *vcn,
|
||||
CLST *lcn, CLST *len);
|
||||
|
|
|
|||
|
|
@ -621,7 +621,7 @@ bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes)
|
|||
* If failed record is not changed.
|
||||
*/
|
||||
int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr,
|
||||
struct runs_tree *run, CLST len)
|
||||
const struct runs_tree *run, CLST len)
|
||||
{
|
||||
int err = 0;
|
||||
struct ntfs_sb_info *sbi = mi->sbi;
|
||||
|
|
|
|||
|
|
@ -487,7 +487,7 @@ bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len,
|
|||
* Helper for attr_collapse_range(),
|
||||
* which is helper for fallocate(collapse_range).
|
||||
*/
|
||||
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
|
||||
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len, CLST sub)
|
||||
{
|
||||
size_t index, eat;
|
||||
struct ntfs_run *r, *e, *eat_start, *eat_end;
|
||||
|
|
@ -511,7 +511,7 @@ bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
|
|||
/* Collapse a middle part of normal run, split. */
|
||||
if (!run_add_entry(run, vcn, SPARSE_LCN, len, false))
|
||||
return false;
|
||||
return run_collapse_range(run, vcn, len);
|
||||
return run_collapse_range(run, vcn, len, sub);
|
||||
}
|
||||
|
||||
r += 1;
|
||||
|
|
@ -545,6 +545,13 @@ bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
|
|||
memmove(eat_start, eat_end, (e - eat_end) * sizeof(*r));
|
||||
run->count -= eat;
|
||||
|
||||
if (sub) {
|
||||
e -= eat;
|
||||
for (r = run->runs; r < e; r++) {
|
||||
r->vcn -= sub;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue