Unverified Commit 910013f7 authored by Konstantin Komarov's avatar Konstantin Komarov
Browse files

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: default avatarKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
parent 0ad9dfcb
Loading
Loading
Loading
Loading
+91 −50
Original line number Diff line number Diff line
@@ -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 mft_inode *mi, *mi_b;
	CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0, alen;
	CLST alloc, evcn;
	unsigned fr;
	u64 total_size;
	u64 total_size, total_size0;
	int step = 0;

	if (new)
		*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;
	if (vcn >= asize) {
		if (new) {
			err = -EINVAL;
		} else {
			*len = 1;
			*lcn = SPARSE_LCN;
		}
		goto out;
	}

@@ -1036,10 +1043,12 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
	if (err)
		goto out;
	*new = true;
	step = 1;

	end = vcn + alen;
	total_size = le64_to_cpu(attr_b->nres.total_size) +
		     ((u64)alen << cluster_bits);
	/* Save 'total_size0' to restore if error. */
	total_size0 = le64_to_cpu(attr_b->nres.total_size);
	total_size = total_size0 + ((u64)alen << cluster_bits);

	if (vcn != vcn0) {
		if (!run_lookup_entry(run, vcn0, lcn, len, NULL)) {
@@ -1081,7 +1090,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
		if (!ni->attr_list.size) {
			err = ni_create_attr_list(ni);
			if (err)
				goto out;
				goto undo1;
			/* Layout of records is changed. */
			le_b = NULL;
			attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
@@ -1098,15 +1107,33 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
		}
	}

	/* 
	 * 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;

	/* Estimate next attribute. */
	attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi);

	if (attr) {
		CLST alloc = bytes_to_cluster(
			sbi, le64_to_cpu(attr_b->nres.alloc_size));
		CLST evcn = le64_to_cpu(attr->nres.evcn);
	if (!attr) {
		/* Insert new attribute segment. */
		goto ins_ext;
	}

	/* Try to update existed attribute segment. */
	alloc = bytes_to_cluster(sbi, le64_to_cpu(attr_b->nres.alloc_size));
	evcn = le64_to_cpu(attr->nres.evcn);

	if (end < next_svcn)
		end = next_svcn;
@@ -1130,8 +1157,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
			goto out;
		}

			attr = mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0,
					    &le->id);
		attr = mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0, &le->id);
		if (!attr) {
			err = -EINVAL;
			goto out;
@@ -1156,9 +1182,8 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
	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:
	if (evcn1 > next_svcn) {
		err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
@@ -1170,10 +1195,26 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
ok:
	run_truncate_around(run, vcn);
out:
	if (err && step > 1) {
		/* Too complex to restore. */
		_ntfs_bad_inode(&ni->vfs_inode);
	}
	up_write(&ni->file.run_lock);
	ni_unlock(ni);

	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)
+33 −0
Original line number Diff line number Diff line
@@ -443,6 +443,39 @@ int ntfs_look_for_free_space(struct ntfs_sb_info *sbi, CLST lcn, CLST len,
	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.
 *
+1 −0
Original line number Diff line number Diff line
@@ -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,
			     CLST *new_lcn, CLST *new_len,
			     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,
		       struct ntfs_inode *ni, struct mft_inode **mi);
void ntfs_mark_rec_free(struct ntfs_sb_info *sbi, CLST rno, bool is_mft);