aboutsummaryrefslogtreecommitdiffstats
diff options
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>2026-05-22 14:52:37 +0200
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>2026-05-28 15:42:13 +0200
commitb759e6b60ebf26c68c399db49cb2885c8cf901b3 (patch)
tree574d597d4373e649e5d8cb321f019c9c44679739
parent1ea75fcef715fa59cd85d255121d5ab8edd7f753 (diff)
downloadlinux-next-history-b759e6b60ebf26c68c399db49cb2885c8cf901b3.tar.gz
fs/ntfs3: handle delayed allocation overlap in run lookup
Introduce run_lookup_entry_da() to look up data runs while taking delayed allocation into account. ntfs3 may have both committed extents and delayed allocation extents for the same VCN range. The new helper checks delayed allocation first and falls back to the real run, then corrects the returned range when a real run overlaps with a delayed allocation run. Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
-rw-r--r--fs/ntfs3/attrib.c17
-rw-r--r--fs/ntfs3/ntfs_fs.h3
-rw-r--r--fs/ntfs3/run.c61
3 files changed, 69 insertions, 12 deletions
diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index e61c5bf7e27e4..0caf2f8a8c1e8 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -962,11 +962,8 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
/* Try to find in cache. */
down_read(&ni->file.run_lock);
- if (!no_da && run_lookup_entry(&ni->file.run_da, vcn, lcn, len, NULL)) {
- /* The requested vcn is delay allocated. */
- *lcn = DELALLOC_LCN;
- } else if (run_lookup_entry(&ni->file.run, vcn, lcn, len, NULL)) {
- /* The requested vcn is known in current run. */
+ if (run_lookup_entry_da(&ni->file.run, !no_da ? &ni->file.run_da : NULL,
+ vcn, lcn, len)) {
} else {
*len = 0;
}
@@ -1011,11 +1008,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
int step;
again:
- if (da && run_lookup_entry(run_da, vcn, lcn, len, NULL)) {
- /* The requested vcn is delay allocated. */
- *lcn = DELALLOC_LCN;
- } else if (run_lookup_entry(run, vcn, lcn, len, NULL)) {
- /* The requested vcn is known in current run. */
+ if (run_lookup_entry_da(run, da ? &ni->file.run_da : NULL, vcn, lcn,
+ len)) {
} else {
*len = 0;
}
@@ -1100,7 +1094,8 @@ again:
}
if (!*len) {
- if (run_lookup_entry(run, vcn, lcn, len, NULL)) {
+ if (run_lookup_entry_da(run, da ? run_da : NULL, vcn, lcn,
+ len)) {
if (*lcn != SPARSE_LCN || !new)
goto ok; /* Slow normal way without allocation. */
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 9939556dcdc1e..d98d7e474476c 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -858,6 +858,9 @@ static inline void mi_get_ref(const struct mft_inode *mi, struct MFT_REF *ref)
/* Globals from run.c */
bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
CLST *len, size_t *index);
+bool run_lookup_entry_da(const struct runs_tree *run,
+ const struct runs_tree *run_da, CLST vcn, CLST *lcn,
+ CLST *len);
void run_truncate(struct runs_tree *run, CLST vcn);
void run_truncate_head(struct runs_tree *run, CLST vcn);
void run_truncate_around(struct runs_tree *run, CLST vcn);
diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c
index ad7db67514ef7..3ebf0154eda39 100644
--- a/fs/ntfs3/run.c
+++ b/fs/ntfs3/run.c
@@ -224,6 +224,66 @@ bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
}
/*
+ * run_overlaps
+ *
+ * true if run overlaps with range [svcn, svcn + len)
+ */
+static bool run_overlaps(const struct runs_tree *run, CLST svcn, CLST len,
+ CLST *vcn, CLST *clen)
+{
+ size_t i;
+ const struct ntfs_run *r = run->runs;
+ CLST end = svcn + len;
+
+ for (i = 0; i < run->count; i++, r++) {
+ /* Check if [r->vcn, r->vcn+r->len) overlaps [svcn, end). */
+ if (r->vcn < end && svcn < r->vcn + r->len) {
+ if (vcn)
+ *vcn = r->vcn;
+ if (clen)
+ *clen = r->len;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * run_lookup_entry_da
+ *
+ * - lookup vcn in delalloc run
+ * - lookup vcn in real run
+ * - correct result if real run overlaps with delalloc
+ */
+bool run_lookup_entry_da(const struct runs_tree *run,
+ const struct runs_tree *run_da, CLST vcn, CLST *lcn,
+ CLST *len)
+{
+ CLST vcn1, len1;
+
+ if (run_da && run_lookup_entry(run_da, vcn, lcn, len, NULL)) {
+ *lcn = DELALLOC_LCN;
+ return true;
+ }
+
+ if (!run_lookup_entry(run, vcn, lcn, len, NULL))
+ return false;
+
+ if (run_da && run_overlaps(run_da, vcn, *len, &vcn1, &len1)) {
+ /* Correct return value. */
+ if (vcn1 > vcn) {
+ *len = vcn1 - vcn;
+ } else {
+ *lcn = DELALLOC_LCN;
+ *len = len1;
+ }
+ }
+
+ return true;
+}
+
+/*
* run_truncate_head - Decommit the range before vcn.
*/
void run_truncate_head(struct runs_tree *run, CLST vcn)
@@ -1286,7 +1346,6 @@ bool run_remove_range(struct runs_tree *run, CLST vcn, CLST len, CLST *done)
return true;
}
-
e = run->runs + run->count;
r = run->runs + index;
end = vcn + len;