fs/ntfs3: Restore correct state after ENOSPC in attr_data_get_block
Added new function ntfs_check_for_free_space. Added undo mechanism in attr_data_get_block. Fixes xfstest generic/083 Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
This commit is contained in:
parent
0ad9dfcb8d
commit
910013f7c7
@ -891,8 +891,10 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
|
|||||||
struct ATTR_LIST_ENTRY *le, *le_b;
|
struct ATTR_LIST_ENTRY *le, *le_b;
|
||||||
struct mft_inode *mi, *mi_b;
|
struct mft_inode *mi, *mi_b;
|
||||||
CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0, alen;
|
CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0, alen;
|
||||||
|
CLST alloc, evcn;
|
||||||
unsigned fr;
|
unsigned fr;
|
||||||
u64 total_size;
|
u64 total_size, total_size0;
|
||||||
|
int step = 0;
|
||||||
|
|
||||||
if (new)
|
if (new)
|
||||||
*new = false;
|
*new = false;
|
||||||
@ -932,7 +934,12 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
|
|||||||
|
|
||||||
asize = le64_to_cpu(attr_b->nres.alloc_size) >> cluster_bits;
|
asize = le64_to_cpu(attr_b->nres.alloc_size) >> cluster_bits;
|
||||||
if (vcn >= asize) {
|
if (vcn >= asize) {
|
||||||
err = -EINVAL;
|
if (new) {
|
||||||
|
err = -EINVAL;
|
||||||
|
} else {
|
||||||
|
*len = 1;
|
||||||
|
*lcn = SPARSE_LCN;
|
||||||
|
}
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1036,10 +1043,12 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
|
|||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
*new = true;
|
*new = true;
|
||||||
|
step = 1;
|
||||||
|
|
||||||
end = vcn + alen;
|
end = vcn + alen;
|
||||||
total_size = le64_to_cpu(attr_b->nres.total_size) +
|
/* Save 'total_size0' to restore if error. */
|
||||||
((u64)alen << cluster_bits);
|
total_size0 = le64_to_cpu(attr_b->nres.total_size);
|
||||||
|
total_size = total_size0 + ((u64)alen << cluster_bits);
|
||||||
|
|
||||||
if (vcn != vcn0) {
|
if (vcn != vcn0) {
|
||||||
if (!run_lookup_entry(run, vcn0, lcn, len, NULL)) {
|
if (!run_lookup_entry(run, vcn0, lcn, len, NULL)) {
|
||||||
@ -1081,7 +1090,7 @@ repack:
|
|||||||
if (!ni->attr_list.size) {
|
if (!ni->attr_list.size) {
|
||||||
err = ni_create_attr_list(ni);
|
err = ni_create_attr_list(ni);
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto undo1;
|
||||||
/* Layout of records is changed. */
|
/* Layout of records is changed. */
|
||||||
le_b = NULL;
|
le_b = NULL;
|
||||||
attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
|
attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
|
||||||
@ -1098,67 +1107,83 @@ repack:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The code below may require additional cluster (to extend attribute list)
|
||||||
|
* and / or one MFT record
|
||||||
|
* It is too complex to undo operations if -ENOSPC occurs deep inside
|
||||||
|
* in 'ni_insert_nonresident'.
|
||||||
|
* Return in advance -ENOSPC here if there are no free cluster and no free MFT.
|
||||||
|
*/
|
||||||
|
if (!ntfs_check_for_free_space(sbi, 1, 1)) {
|
||||||
|
/* Undo step 1. */
|
||||||
|
err = -ENOSPC;
|
||||||
|
goto undo1;
|
||||||
|
}
|
||||||
|
|
||||||
|
step = 2;
|
||||||
svcn = evcn1;
|
svcn = evcn1;
|
||||||
|
|
||||||
/* Estimate next attribute. */
|
/* Estimate next attribute. */
|
||||||
attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi);
|
attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi);
|
||||||
|
|
||||||
if (attr) {
|
if (!attr) {
|
||||||
CLST alloc = bytes_to_cluster(
|
/* Insert new attribute segment. */
|
||||||
sbi, le64_to_cpu(attr_b->nres.alloc_size));
|
goto ins_ext;
|
||||||
CLST evcn = le64_to_cpu(attr->nres.evcn);
|
}
|
||||||
|
|
||||||
if (end < next_svcn)
|
/* Try to update existed attribute segment. */
|
||||||
end = next_svcn;
|
alloc = bytes_to_cluster(sbi, le64_to_cpu(attr_b->nres.alloc_size));
|
||||||
while (end > evcn) {
|
evcn = le64_to_cpu(attr->nres.evcn);
|
||||||
/* Remove segment [svcn : evcn). */
|
|
||||||
mi_remove_attr(NULL, mi, attr);
|
|
||||||
|
|
||||||
if (!al_remove_le(ni, le)) {
|
if (end < next_svcn)
|
||||||
err = -EINVAL;
|
end = next_svcn;
|
||||||
goto out;
|
while (end > evcn) {
|
||||||
}
|
/* Remove segment [svcn : evcn). */
|
||||||
|
mi_remove_attr(NULL, mi, attr);
|
||||||
|
|
||||||
if (evcn + 1 >= alloc) {
|
if (!al_remove_le(ni, le)) {
|
||||||
/* Last attribute segment. */
|
err = -EINVAL;
|
||||||
evcn1 = evcn + 1;
|
goto out;
|
||||||
goto ins_ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ni_load_mi(ni, le, &mi)) {
|
|
||||||
attr = NULL;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
attr = mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0,
|
|
||||||
&le->id);
|
|
||||||
if (!attr) {
|
|
||||||
err = -EINVAL;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
svcn = le64_to_cpu(attr->nres.svcn);
|
|
||||||
evcn = le64_to_cpu(attr->nres.evcn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end < svcn)
|
if (evcn + 1 >= alloc) {
|
||||||
end = svcn;
|
/* Last attribute segment. */
|
||||||
|
evcn1 = evcn + 1;
|
||||||
|
goto ins_ext;
|
||||||
|
}
|
||||||
|
|
||||||
err = attr_load_runs(attr, ni, run, &end);
|
if (ni_load_mi(ni, le, &mi)) {
|
||||||
if (err)
|
attr = NULL;
|
||||||
goto out;
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
evcn1 = evcn + 1;
|
attr = mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0, &le->id);
|
||||||
attr->nres.svcn = cpu_to_le64(next_svcn);
|
if (!attr) {
|
||||||
err = mi_pack_runs(mi, attr, run, evcn1 - next_svcn);
|
err = -EINVAL;
|
||||||
if (err)
|
|
||||||
goto out;
|
goto out;
|
||||||
|
}
|
||||||
le->vcn = cpu_to_le64(next_svcn);
|
svcn = le64_to_cpu(attr->nres.svcn);
|
||||||
ni->attr_list.dirty = true;
|
evcn = le64_to_cpu(attr->nres.evcn);
|
||||||
mi->dirty = true;
|
|
||||||
|
|
||||||
next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (end < svcn)
|
||||||
|
end = svcn;
|
||||||
|
|
||||||
|
err = attr_load_runs(attr, ni, run, &end);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
evcn1 = evcn + 1;
|
||||||
|
attr->nres.svcn = cpu_to_le64(next_svcn);
|
||||||
|
err = mi_pack_runs(mi, attr, run, evcn1 - next_svcn);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
le->vcn = cpu_to_le64(next_svcn);
|
||||||
|
ni->attr_list.dirty = true;
|
||||||
|
mi->dirty = true;
|
||||||
|
next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
|
||||||
|
|
||||||
ins_ext:
|
ins_ext:
|
||||||
if (evcn1 > next_svcn) {
|
if (evcn1 > next_svcn) {
|
||||||
err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
|
err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
|
||||||
@ -1170,10 +1195,26 @@ ins_ext:
|
|||||||
ok:
|
ok:
|
||||||
run_truncate_around(run, vcn);
|
run_truncate_around(run, vcn);
|
||||||
out:
|
out:
|
||||||
|
if (err && step > 1) {
|
||||||
|
/* Too complex to restore. */
|
||||||
|
_ntfs_bad_inode(&ni->vfs_inode);
|
||||||
|
}
|
||||||
up_write(&ni->file.run_lock);
|
up_write(&ni->file.run_lock);
|
||||||
ni_unlock(ni);
|
ni_unlock(ni);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
|
undo1:
|
||||||
|
/* Undo step1. */
|
||||||
|
attr_b->nres.total_size = cpu_to_le64(total_size0);
|
||||||
|
inode_set_bytes(&ni->vfs_inode, total_size0);
|
||||||
|
|
||||||
|
if (run_deallocate_ex(sbi, run, vcn, alen, NULL, false) ||
|
||||||
|
!run_add_entry(run, vcn, SPARSE_LCN, alen, false) ||
|
||||||
|
mi_pack_runs(mi, attr, run, max(end, evcn1) - svcn)) {
|
||||||
|
_ntfs_bad_inode(&ni->vfs_inode);
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
int attr_data_read_resident(struct ntfs_inode *ni, struct page *page)
|
int attr_data_read_resident(struct ntfs_inode *ni, struct page *page)
|
||||||
|
@ -443,6 +443,39 @@ up_write:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ntfs_check_for_free_space
|
||||||
|
*
|
||||||
|
* Check if it is possible to allocate 'clen' clusters and 'mlen' Mft records
|
||||||
|
*/
|
||||||
|
bool ntfs_check_for_free_space(struct ntfs_sb_info *sbi, CLST clen, CLST mlen)
|
||||||
|
{
|
||||||
|
size_t free, zlen, avail;
|
||||||
|
struct wnd_bitmap *wnd;
|
||||||
|
|
||||||
|
wnd = &sbi->used.bitmap;
|
||||||
|
down_read_nested(&wnd->rw_lock, BITMAP_MUTEX_CLUSTERS);
|
||||||
|
free = wnd_zeroes(wnd);
|
||||||
|
zlen = wnd_zone_len(wnd);
|
||||||
|
up_read(&wnd->rw_lock);
|
||||||
|
|
||||||
|
if (free < zlen + clen)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
avail = free - (zlen + clen);
|
||||||
|
|
||||||
|
wnd = &sbi->mft.bitmap;
|
||||||
|
down_read_nested(&wnd->rw_lock, BITMAP_MUTEX_MFT);
|
||||||
|
free = wnd_zeroes(wnd);
|
||||||
|
zlen = wnd_zone_len(wnd);
|
||||||
|
up_read(&wnd->rw_lock);
|
||||||
|
|
||||||
|
if (free >= zlen + mlen)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return avail >= bytes_to_cluster(sbi, mlen << sbi->record_bits);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ntfs_extend_mft - Allocate additional MFT records.
|
* ntfs_extend_mft - Allocate additional MFT records.
|
||||||
*
|
*
|
||||||
|
@ -588,6 +588,7 @@ int ntfs_loadlog_and_replay(struct ntfs_inode *ni, struct ntfs_sb_info *sbi);
|
|||||||
int ntfs_look_for_free_space(struct ntfs_sb_info *sbi, CLST lcn, CLST len,
|
int ntfs_look_for_free_space(struct ntfs_sb_info *sbi, CLST lcn, CLST len,
|
||||||
CLST *new_lcn, CLST *new_len,
|
CLST *new_lcn, CLST *new_len,
|
||||||
enum ALLOCATE_OPT opt);
|
enum ALLOCATE_OPT opt);
|
||||||
|
bool ntfs_check_for_free_space(struct ntfs_sb_info *sbi, CLST clen, CLST mlen);
|
||||||
int ntfs_look_free_mft(struct ntfs_sb_info *sbi, CLST *rno, bool mft,
|
int ntfs_look_free_mft(struct ntfs_sb_info *sbi, CLST *rno, bool mft,
|
||||||
struct ntfs_inode *ni, struct mft_inode **mi);
|
struct ntfs_inode *ni, struct mft_inode **mi);
|
||||||
void ntfs_mark_rec_free(struct ntfs_sb_info *sbi, CLST rno, bool is_mft);
|
void ntfs_mark_rec_free(struct ntfs_sb_info *sbi, CLST rno, bool is_mft);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user