Commit 6e48321a authored by Anton Altaparmakov's avatar Anton Altaparmakov
Browse files

NTFS: Add ntfs_rl_punch_nolock() which punches a caller specified hole into a runlist.

parent 3ffc5a44
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@ ToDo/Notes:
	  pointing this out.
	- Change ntfs_rl_truncate_nolock() to throw away the runlist if the new
	  length is zero.
	- Add runlist.[hc]::ntfs_rl_punch_nolock() which punches a caller
	  specified hole into a runlist.

2.1.23 - Implement extension of resident files and make writing safe as well as
	 many bug fixes, cleanups, and enhancements...
+284 −0
Original line number Diff line number Diff line
@@ -1601,4 +1601,288 @@ int ntfs_rl_truncate_nolock(const ntfs_volume *vol, runlist *const runlist,
	return 0;
}

/**
 * ntfs_rl_punch_nolock - punch a hole into a runlist
 * @vol:	ntfs volume (needed for error output)
 * @runlist:	runlist to punch a hole into
 * @start:	starting VCN of the hole to be created
 * @length:	size of the hole to be created in units of clusters
 *
 * Punch a hole into the runlist @runlist starting at VCN @start and of size
 * @length clusters.
 *
 * Return 0 on success and -errno on error, in which case @runlist has not been
 * modified.
 *
 * If @start and/or @start + @length are outside the runlist return error code
 * -ENOENT.
 *
 * If the runlist contains unmapped or error elements between @start and @start
 * + @length return error code -EINVAL.
 *
 * Locking: The caller must hold @runlist->lock for writing.
 */
int ntfs_rl_punch_nolock(const ntfs_volume *vol, runlist *const runlist,
		const VCN start, const s64 length)
{
	const VCN end = start + length;
	s64 delta;
	runlist_element *rl, *rl_end, *rl_real_end, *trl;
	int old_size;
	BOOL lcn_fixup = FALSE;

	ntfs_debug("Entering for start 0x%llx, length 0x%llx.",
			(long long)start, (long long)length);
	BUG_ON(!runlist);
	BUG_ON(start < 0);
	BUG_ON(length < 0);
	BUG_ON(end < 0);
	rl = runlist->rl;
	if (unlikely(!rl)) {
		if (likely(!start && !length))
			return 0;
		return -ENOENT;
	}
	/* Find @start in the runlist. */
	while (likely(rl->length && start >= rl[1].vcn))
		rl++;
	rl_end = rl;
	/* Find @end in the runlist. */
	while (likely(rl_end->length && end >= rl_end[1].vcn)) {
		/* Verify there are no unmapped or error elements. */
		if (unlikely(rl_end->lcn < LCN_HOLE))
			return -EINVAL;
		rl_end++;
	}
	/* Check the last element. */
	if (unlikely(rl_end->length && rl_end->lcn < LCN_HOLE))
		return -EINVAL;
	/* This covers @start being out of bounds, too. */
	if (!rl_end->length && end > rl_end->vcn)
		return -ENOENT;
	if (!length)
		return 0;
	if (!rl->length)
		return -ENOENT;
	rl_real_end = rl_end;
	/* Determine the runlist size. */
	while (likely(rl_real_end->length))
		rl_real_end++;
	old_size = rl_real_end - runlist->rl + 1;
	/* If @start is in a hole simply extend the hole. */
	if (rl->lcn == LCN_HOLE) {
		/*
		 * If both @start and @end are in the same sparse run, we are
		 * done.
		 */
		if (end <= rl[1].vcn) {
			ntfs_debug("Done (requested hole is already sparse).");
			return 0;
		}
extend_hole:
		/* Extend the hole. */
		rl->length = end - rl->vcn;
		/* If @end is in a hole, merge it with the current one. */
		if (rl_end->lcn == LCN_HOLE) {
			rl_end++;
			rl->length = rl_end->vcn - rl->vcn;
		}
		/* We have done the hole.  Now deal with the remaining tail. */
		rl++;
		/* Cut out all runlist elements up to @end. */
		if (rl < rl_end)
			memmove(rl, rl_end, (rl_real_end - rl_end + 1) *
					sizeof(*rl));
		/* Adjust the beginning of the tail if necessary. */
		if (end > rl->vcn) {
			s64 delta = end - rl->vcn;
			rl->vcn = end;
			rl->length -= delta;
			/* Only adjust the lcn if it is real. */
			if (rl->lcn >= 0)
				rl->lcn += delta;
		}
shrink_allocation:
		/* Reallocate memory if the allocation changed. */
		if (rl < rl_end) {
			rl = ntfs_rl_realloc(runlist->rl, old_size,
					old_size - (rl_end - rl));
			if (IS_ERR(rl))
				ntfs_warning(vol->sb, "Failed to shrink "
						"runlist buffer.  This just "
						"wastes a bit of memory "
						"temporarily so we ignore it "
						"and return success.");
			else
				runlist->rl = rl;
		}
		ntfs_debug("Done (extend hole).");
		return 0;
	}
	/*
	 * If @start is at the beginning of a run things are easier as there is
	 * no need to split the first run.
	 */
	if (start == rl->vcn) {
		/*
		 * @start is at the beginning of a run.
		 *
		 * If the previous run is sparse, extend its hole.
		 *
		 * If @end is not in the same run, switch the run to be sparse
		 * and extend the newly created hole.
		 *
		 * Thus both of these cases reduce the problem to the above
		 * case of "@start is in a hole".
		 */
		if (rl > runlist->rl && (rl - 1)->lcn == LCN_HOLE) {
			rl--;
			goto extend_hole;
		}
		if (end >= rl[1].vcn) {
			rl->lcn = LCN_HOLE;
			goto extend_hole;
		}
		/*
		 * The final case is when @end is in the same run as @start.
		 * For this need to split the run into two.  One run for the
		 * sparse region between the beginning of the old run, i.e.
		 * @start, and @end and one for the remaining non-sparse
		 * region, i.e. between @end and the end of the old run.
		 */
		trl = ntfs_rl_realloc(runlist->rl, old_size, old_size + 1);
		if (IS_ERR(trl))
			goto enomem_out;
		old_size++;
		if (runlist->rl != trl) {
			rl = trl + (rl - runlist->rl);
			rl_end = trl + (rl_end - runlist->rl);
			rl_real_end = trl + (rl_real_end - runlist->rl);
			runlist->rl = trl;
		}
split_end:
		/* Shift all the runs up by one. */
		memmove(rl + 1, rl, (rl_real_end - rl + 1) * sizeof(*rl));
		/* Finally, setup the two split runs. */
		rl->lcn = LCN_HOLE;
		rl->length = length;
		rl++;
		rl->vcn += length;
		/* Only adjust the lcn if it is real. */
		if (rl->lcn >= 0 || lcn_fixup)
			rl->lcn += length;
		rl->length -= length;
		ntfs_debug("Done (split one).");
		return 0;
	}
	/*
	 * @start is neither in a hole nor at the beginning of a run.
	 *
	 * If @end is in a hole, things are easier as simply truncating the run
	 * @start is in to end at @start - 1, deleting all runs after that up
	 * to @end, and finally extending the beginning of the run @end is in
	 * to be @start is all that is needed.
	 */
	if (rl_end->lcn == LCN_HOLE) {
		/* Truncate the run containing @start. */
		rl->length = start - rl->vcn;
		rl++;
		/* Cut out all runlist elements up to @end. */
		if (rl < rl_end)
			memmove(rl, rl_end, (rl_real_end - rl_end + 1) *
					sizeof(*rl));
		/* Extend the beginning of the run @end is in to be @start. */
		rl->vcn = start;
		rl->length = rl[1].vcn - start;
		goto shrink_allocation;
	}
	/* 
	 * If @end is not in a hole there are still two cases to distinguish.
	 * Either @end is or is not in the same run as @start.
	 *
	 * The second case is easier as it can be reduced to an already solved
	 * problem by truncating the run @start is in to end at @start - 1.
	 * Then, if @end is in the next run need to split the run into a sparse
	 * run followed by a non-sparse run (already covered above) and if @end
	 * is not in the next run switching it to be sparse, again reduces the
	 * problem to the already covered case of "@start is in a hole".
	 */
	if (end >= rl[1].vcn) {
		/*
		 * If @end is not in the next run, reduce the problem to the
		 * case of "@start is in a hole".
		 */
		if (rl[1].length && end >= rl[2].vcn) {
			/* Truncate the run containing @start. */
			rl->length = start - rl->vcn;
			rl++;
			rl->vcn = start;
			rl->lcn = LCN_HOLE;
			goto extend_hole;
		}
		trl = ntfs_rl_realloc(runlist->rl, old_size, old_size + 1);
		if (IS_ERR(trl))
			goto enomem_out;
		old_size++;
		if (runlist->rl != trl) {
			rl = trl + (rl - runlist->rl);
			rl_end = trl + (rl_end - runlist->rl);
			rl_real_end = trl + (rl_real_end - runlist->rl);
			runlist->rl = trl;
		}
		/* Truncate the run containing @start. */
		rl->length = start - rl->vcn;
		rl++;
		/*
		 * @end is in the next run, reduce the problem to the case
		 * where "@start is at the beginning of a run and @end is in
		 * the same run as @start".
		 */
		delta = rl->vcn - start;
		rl->vcn = start;
		if (rl->lcn >= 0) {
			rl->lcn -= delta;
			/* Need this in case the lcn just became negative. */
			lcn_fixup = TRUE;
		}
		rl->length += delta;
		goto split_end;
	}
	/*
	 * The first case from above, i.e. @end is in the same run as @start.
	 * We need to split the run into three.  One run for the non-sparse
	 * region between the beginning of the old run and @start, one for the
	 * sparse region between @start and @end, and one for the remaining
	 * non-sparse region, i.e. between @end and the end of the old run.
	 */
	trl = ntfs_rl_realloc(runlist->rl, old_size, old_size + 2);
	if (IS_ERR(trl))
		goto enomem_out;
	old_size += 2;
	if (runlist->rl != trl) {
		rl = trl + (rl - runlist->rl);
		rl_end = trl + (rl_end - runlist->rl);
		rl_real_end = trl + (rl_real_end - runlist->rl);
		runlist->rl = trl;
	}
	/* Shift all the runs up by two. */
	memmove(rl + 2, rl, (rl_real_end - rl + 1) * sizeof(*rl));
	/* Finally, setup the three split runs. */
	rl->length = start - rl->vcn;
	rl++;
	rl->vcn = start;
	rl->lcn = LCN_HOLE;
	rl->length = length;
	rl++;
	delta = end - rl->vcn;
	rl->vcn = end;
	rl->lcn += delta;
	rl->length -= delta;
	ntfs_debug("Done (split both).");
	return 0;
enomem_out:
	ntfs_error(vol->sb, "Not enough memory to extend runlist buffer.");
	return -ENOMEM;
}

#endif /* NTFS_RW */
+3 −0
Original line number Diff line number Diff line
@@ -94,6 +94,9 @@ extern int ntfs_mapping_pairs_build(const ntfs_volume *vol, s8 *dst,
extern int ntfs_rl_truncate_nolock(const ntfs_volume *vol,
		runlist *const runlist, const s64 new_length);

int ntfs_rl_punch_nolock(const ntfs_volume *vol, runlist *const runlist,
		const VCN start, const s64 length);

#endif /* NTFS_RW */

#endif /* _LINUX_NTFS_RUNLIST_H */