ntfs3.lists.linux.dev archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] fs/ntfs3: FALLOC_FL_INSERT_RANGE support
@ 2022-06-21 11:44 Konstantin Komarov
  2022-06-21 11:45 ` [PATCH 1/2] fs/ntfs3: Fallocate (FALLOC_FL_INSERT_RANGE) implementation Konstantin Komarov
  2022-06-21 11:45 ` [PATCH 2/2] fs/ntfs3: Enable FALLOC_FL_INSERT_RANGE Konstantin Komarov
  0 siblings, 2 replies; 3+ messages in thread
From: Konstantin Komarov @ 2022-06-21 11:44 UTC (permalink / raw)
  To: ntfs3; +Cc: linux-kernel, linux-fsdevel

Added support for FALLOC_FL_INSERT_RANGE

Konstantin Komarov (2):
   fs/ntfs3: Fallocate (FALLOC_FL_INSERT_RANGE) implementation
   fs/ntfs3: Enable FALLOC_FL_INSERT_RANGE

  fs/ntfs3/attrib.c  | 176 +++++++++++++++++++++++++++++++++++++++++++++
  fs/ntfs3/file.c    |  97 ++++++++++++++-----------
  fs/ntfs3/ntfs_fs.h |   4 +-
  fs/ntfs3/run.c     |  43 +++++++++++
  4 files changed, 277 insertions(+), 43 deletions(-)

-- 
2.36.1


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] fs/ntfs3: Fallocate (FALLOC_FL_INSERT_RANGE) implementation
  2022-06-21 11:44 [PATCH 0/2] fs/ntfs3: FALLOC_FL_INSERT_RANGE support Konstantin Komarov
@ 2022-06-21 11:45 ` Konstantin Komarov
  2022-06-21 11:45 ` [PATCH 2/2] fs/ntfs3: Enable FALLOC_FL_INSERT_RANGE Konstantin Komarov
  1 sibling, 0 replies; 3+ messages in thread
From: Konstantin Komarov @ 2022-06-21 11:45 UTC (permalink / raw)
  To: ntfs3; +Cc: linux-kernel, linux-fsdevel

Add functions for inserting hole in file and inserting range in run.

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
---
  fs/ntfs3/attrib.c  | 176 +++++++++++++++++++++++++++++++++++++++++++++
  fs/ntfs3/ntfs_fs.h |   4 +-
  fs/ntfs3/run.c     |  43 +++++++++++
  3 files changed, 222 insertions(+), 1 deletion(-)

diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index fc0623b029e6..86e688b95ad5 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -2081,3 +2081,179 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
  
  	return err;
  }
+
+/*
+ * attr_insert_range - Insert range (hole) in file.
+ * Not for normal files.
+ */
+int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
+{
+	int err = 0;
+	struct runs_tree *run = &ni->file.run;
+	struct ntfs_sb_info *sbi = ni->mi.sbi;
+	struct ATTRIB *attr = NULL, *attr_b;
+	struct ATTR_LIST_ENTRY *le, *le_b;
+	struct mft_inode *mi, *mi_b;
+	CLST vcn, svcn, evcn1, len, next_svcn;
+	u64 data_size, alloc_size;
+	u32 mask;
+	__le16 a_flags;
+
+	if (!bytes)
+		return 0;
+
+	le_b = NULL;
+	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+	if (!attr_b)
+		return -ENOENT;
+
+	if (!is_attr_ext(attr_b)) {
+		/* It was checked above. See fallocate. */
+		return -EOPNOTSUPP;
+	}
+
+	if (!attr_b->non_res) {
+		data_size = le32_to_cpu(attr_b->res.data_size);
+		mask = sbi->cluster_mask; /* cluster_size - 1 */
+	} else {
+		data_size = le64_to_cpu(attr_b->nres.data_size);
+		mask = (sbi->cluster_size << attr_b->nres.c_unit) - 1;
+	}
+
+	if (vbo > data_size) {
+		/* Insert range after the file size is not allowed. */
+		return -EINVAL;
+	}
+
+	if ((vbo & mask) || (bytes & mask)) {
+		/* Allow to insert only frame aligned ranges. */
+		return -EINVAL;
+	}
+
+	vcn = vbo >> sbi->cluster_bits;
+	len = bytes >> sbi->cluster_bits;
+
+	down_write(&ni->file.run_lock);
+
+	if (!attr_b->non_res) {
+		err = attr_set_size(ni, ATTR_DATA, NULL, 0, run,
+				    data_size + bytes, NULL, false, &attr);
+		if (err)
+			goto out;
+		if (!attr->non_res) {
+			/* Still resident. */
+			char *data = Add2Ptr(attr, attr->res.data_off);
+
+			memmove(data + bytes, data, bytes);
+			memset(data, 0, bytes);
+			err = 0;
+			goto out;
+		}
+		/* Resident files becomes nonresident. */
+		le_b = NULL;
+		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+				      &mi_b);
+		if (!attr_b)
+			return -ENOENT;
+		if (!attr_b->non_res) {
+			err = -EINVAL;
+			goto out;
+		}
+		data_size = le64_to_cpu(attr_b->nres.data_size);
+		alloc_size = le64_to_cpu(attr_b->nres.alloc_size);
+	}
+
+	/*
+	 * Enumerate all attribute segments and shift start vcn.
+	 */
+	a_flags = attr_b->flags;
+	svcn = le64_to_cpu(attr_b->nres.svcn);
+	evcn1 = le64_to_cpu(attr_b->nres.evcn) + 1;
+
+	if (svcn <= vcn && vcn < evcn1) {
+		attr = attr_b;
+		le = le_b;
+		mi = mi_b;
+	} else 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;
+	}
+
+	run_truncate(run, 0); /* clear cached values. */
+	err = attr_load_runs(attr, ni, run, NULL);
+	if (err)
+		goto out;
+
+	if (!run_insert_range(run, vcn, len)) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	/* Try to pack in current record as much as possible. */
+	err = mi_pack_runs(mi, attr, run, evcn1 + len - svcn);
+	if (err)
+		goto out;
+
+	next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
+	run_truncate_head(run, next_svcn);
+
+	while ((attr = ni_enum_attr_ex(ni, attr, &le, &mi)) &&
+	       attr->type == ATTR_DATA && !attr->name_len) {
+		le64_add_cpu(&attr->nres.svcn, len);
+		le64_add_cpu(&attr->nres.evcn, len);
+		if (le) {
+			le->vcn = attr->nres.svcn;
+			ni->attr_list.dirty = true;
+		}
+		mi->dirty = true;
+	}
+
+	/*
+	 * Update primary attribute segment in advance.
+	 * pointer attr_b may become invalid (layout of mft is changed)
+	 */
+	if (vbo <= ni->i_valid)
+		ni->i_valid += bytes;
+
+	attr_b->nres.data_size = le64_to_cpu(data_size + bytes);
+	attr_b->nres.alloc_size = le64_to_cpu(alloc_size + bytes);
+
+	/* ni->valid may be not equal valid_size (temporary). */
+	if (ni->i_valid > data_size + bytes)
+		attr_b->nres.valid_size = attr_b->nres.data_size;
+	else
+		attr_b->nres.valid_size = cpu_to_le64(ni->i_valid);
+	mi_b->dirty = true;
+
+	if (next_svcn < evcn1 + len) {
+		err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
+					    next_svcn, evcn1 + len - next_svcn,
+					    a_flags, NULL, NULL);
+		if (err)
+			goto out;
+	}
+
+	ni->vfs_inode.i_size += bytes;
+	ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
+	mark_inode_dirty(&ni->vfs_inode);
+
+out:
+	run_truncate(run, 0); /* clear cached values. */
+
+	up_write(&ni->file.run_lock);
+	if (err)
+		make_bad_inode(&ni->vfs_inode);
+
+	return err;
+}
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index fb825059d488..1f92e3a05f61 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -440,6 +440,7 @@ int attr_is_frame_compressed(struct ntfs_inode *ni, struct ATTRIB *attr,
  int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
  			u64 new_valid);
  int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes);
+int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes);
  int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size);
  
  /* Functions from attrlist.c */
@@ -775,10 +776,11 @@ bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
  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);
-bool run_lookup(const struct runs_tree *run, CLST vcn, size_t *Index);
+bool run_lookup(const struct runs_tree *run, CLST vcn, size_t *index);
  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_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);
  bool run_is_mapped_full(const struct runs_tree *run, CLST svcn, CLST evcn);
diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c
index a8fec651f973..7609d45a2d72 100644
--- a/fs/ntfs3/run.c
+++ b/fs/ntfs3/run.c
@@ -547,6 +547,49 @@ bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
  	return true;
  }
  
+/* run_insert_range
+ *
+ * Helper for attr_insert_range(),
+ * which is helper for fallocate(insert_range).
+ */
+bool run_insert_range(struct runs_tree *run, CLST vcn, CLST len)
+{
+	size_t index;
+	struct ntfs_run *r, *e;
+
+	if (WARN_ON(!run_lookup(run, vcn, &index)))
+		return false; /* Should never be here. */
+
+	e = run->runs + run->count;
+	r = run->runs + index;
+
+	r = run->runs + index;
+	if (vcn > r->vcn)
+		r += 1;
+
+	for (; r < e; r++)
+		r->vcn += len;
+
+	r = run->runs + index;
+
+	if (vcn > r->vcn) {
+		/* split fragment. */
+		CLST len1 = vcn - r->vcn;
+		CLST len2 = r->len - len1;
+		CLST lcn2 = r->lcn == SPARSE_LCN ? SPARSE_LCN : (r->lcn + len1);
+
+		r->len = len1;
+
+		if (!run_add_entry(run, vcn + len, lcn2, len2, false))
+			return false;
+	}
+
+	if (!run_add_entry(run, vcn, SPARSE_LCN, len, false))
+		return false;
+
+	return true;
+}
+
  /*
   * run_get_entry - Return index-th mapped region.
   */
-- 
2.36.1



^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] fs/ntfs3: Enable FALLOC_FL_INSERT_RANGE
  2022-06-21 11:44 [PATCH 0/2] fs/ntfs3: FALLOC_FL_INSERT_RANGE support Konstantin Komarov
  2022-06-21 11:45 ` [PATCH 1/2] fs/ntfs3: Fallocate (FALLOC_FL_INSERT_RANGE) implementation Konstantin Komarov
@ 2022-06-21 11:45 ` Konstantin Komarov
  1 sibling, 0 replies; 3+ messages in thread
From: Konstantin Komarov @ 2022-06-21 11:45 UTC (permalink / raw)
  To: ntfs3; +Cc: linux-kernel, linux-fsdevel

Changed logic in ntfs_fallocate - more clear checks in beginning
instead of the middle of function and added FALLOC_FL_INSERT_RANGE.
Fixes xfstest generic/064
Fixes: 4342306f0f0d ("fs/ntfs3: Add file operations and implementation")

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
---
  fs/ntfs3/file.c | 97 ++++++++++++++++++++++++++++---------------------
  1 file changed, 55 insertions(+), 42 deletions(-)

diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index 27c32692513c..bdffe4b8554b 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -533,21 +533,35 @@ static int ntfs_truncate(struct inode *inode, loff_t new_size)
  static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
  {
  	struct inode *inode = file->f_mapping->host;
+	struct address_space *mapping = inode->i_mapping;
  	struct super_block *sb = inode->i_sb;
  	struct ntfs_sb_info *sbi = sb->s_fs_info;
  	struct ntfs_inode *ni = ntfs_i(inode);
  	loff_t end = vbo + len;
  	loff_t vbo_down = round_down(vbo, PAGE_SIZE);
-	loff_t i_size;
+	bool is_supported_holes = is_sparsed(ni) || is_compressed(ni);
+	loff_t i_size, new_size;
+	bool map_locked;
  	int err;
  
  	/* No support for dir. */
  	if (!S_ISREG(inode->i_mode))
  		return -EOPNOTSUPP;
  
-	/* Return error if mode is not supported. */
-	if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
-		     FALLOC_FL_COLLAPSE_RANGE)) {
+	/*
+	 * vfs_fallocate checks all possible combinations of mode.
+	 * Do additional checks here before ntfs_set_state(dirty).
+	 */
+	if (mode & FALLOC_FL_PUNCH_HOLE) {
+		if (!is_supported_holes)
+			return -EOPNOTSUPP;
+	} else if (mode & FALLOC_FL_COLLAPSE_RANGE) {
+	} else if (mode & FALLOC_FL_INSERT_RANGE) {
+		if (!is_supported_holes)
+			return -EOPNOTSUPP;
+	} else if (mode &
+		   ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
+		     FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_INSERT_RANGE)) {
  		ntfs_inode_warn(inode, "fallocate(0x%x) is not supported",
  				mode);
  		return -EOPNOTSUPP;
@@ -557,6 +571,8 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
  
  	inode_lock(inode);
  	i_size = inode->i_size;
+	new_size = max(end, i_size);
+	map_locked = false;
  
  	if (WARN_ON(ni->ni_flags & NI_FLAG_COMPRESSED_MASK)) {
  		/* Should never be here, see ntfs_file_open. */
@@ -564,38 +580,27 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
  		goto out;
  	}
  
+	if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE |
+		    FALLOC_FL_INSERT_RANGE)) {
+		inode_dio_wait(inode);
+		filemap_invalidate_lock(mapping);
+		map_locked = true;
+	}
+
  	if (mode & FALLOC_FL_PUNCH_HOLE) {
  		u32 frame_size;
  		loff_t mask, vbo_a, end_a, tmp;
  
-		if (!(mode & FALLOC_FL_KEEP_SIZE)) {
-			err = -EINVAL;
-			goto out;
-		}
-
-		err = filemap_write_and_wait_range(inode->i_mapping, vbo,
-						   end - 1);
+		err = filemap_write_and_wait_range(mapping, vbo, end - 1);
  		if (err)
  			goto out;
  
-		err = filemap_write_and_wait_range(inode->i_mapping, end,
-						   LLONG_MAX);
+		err = filemap_write_and_wait_range(mapping, end, LLONG_MAX);
  		if (err)
  			goto out;
  
-		inode_dio_wait(inode);
-
  		truncate_pagecache(inode, vbo_down);
  
-		if (!is_sparsed(ni) && !is_compressed(ni)) {
-			/*
-			 * Normal file, can't make hole.
-			 * TODO: Try to find way to save info about hole.
-			 */
-			err = -EOPNOTSUPP;
-			goto out;
-		}
-
  		ni_lock(ni);
  		err = attr_punch_hole(ni, vbo, len, &frame_size);
  		ni_unlock(ni);
@@ -627,17 +632,11 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
  			ni_unlock(ni);
  		}
  	} else if (mode & FALLOC_FL_COLLAPSE_RANGE) {
-		if (mode & ~FALLOC_FL_COLLAPSE_RANGE) {
-			err = -EINVAL;
-			goto out;
-		}
-
  		/*
  		 * Write tail of the last page before removed range since
  		 * it will get removed from the page cache below.
  		 */
-		err = filemap_write_and_wait_range(inode->i_mapping, vbo_down,
-						   vbo);
+		err = filemap_write_and_wait_range(mapping, vbo_down, vbo);
  		if (err)
  			goto out;
  
@@ -645,34 +644,45 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
  		 * Write data that will be shifted to preserve them
  		 * when discarding page cache below.
  		 */
-		err = filemap_write_and_wait_range(inode->i_mapping, end,
-						   LLONG_MAX);
+		err = filemap_write_and_wait_range(mapping, end, LLONG_MAX);
  		if (err)
  			goto out;
  
-		/* Wait for existing dio to complete. */
-		inode_dio_wait(inode);
-
  		truncate_pagecache(inode, vbo_down);
  
  		ni_lock(ni);
  		err = attr_collapse_range(ni, vbo, len);
  		ni_unlock(ni);
-	} else {
-		/*
-		 * Normal file: Allocate clusters, do not change 'valid' size.
-		 */
-		loff_t new_size = max(end, i_size);
+	} else if (mode & FALLOC_FL_INSERT_RANGE) {
+		/* Check new size. */
+		err = inode_newsize_ok(inode, new_size);
+		if (err)
+			goto out;
+
+		/* Write out all dirty pages. */
+		err = filemap_write_and_wait_range(mapping, vbo_down,
+						   LLONG_MAX);
+		if (err)
+			goto out;
+		truncate_pagecache(inode, vbo_down);
  
+		ni_lock(ni);
+		err = attr_insert_range(ni, vbo, len);
+		ni_unlock(ni);
+	} else {
+		/* Check new size. */
  		err = inode_newsize_ok(inode, new_size);
  		if (err)
  			goto out;
  
+		/*
+		 * Allocate clusters, do not change 'valid' size.
+		 */
  		err = ntfs_set_size(inode, new_size);
  		if (err)
  			goto out;
  
-		if (is_sparsed(ni) || is_compressed(ni)) {
+		if (is_supported_holes) {
  			CLST vcn_v = ni->i_valid >> sbi->cluster_bits;
  			CLST vcn = vbo >> sbi->cluster_bits;
  			CLST cend = bytes_to_cluster(sbi, end);
@@ -720,6 +730,9 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
  	}
  
  out:
+	if (map_locked)
+		filemap_invalidate_unlock(mapping);
+
  	if (err == -EFBIG)
  		err = -ENOSPC;
  
-- 
2.36.1



^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2022-06-21 11:49 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-21 11:44 [PATCH 0/2] fs/ntfs3: FALLOC_FL_INSERT_RANGE support Konstantin Komarov
2022-06-21 11:45 ` [PATCH 1/2] fs/ntfs3: Fallocate (FALLOC_FL_INSERT_RANGE) implementation Konstantin Komarov
2022-06-21 11:45 ` [PATCH 2/2] fs/ntfs3: Enable FALLOC_FL_INSERT_RANGE Konstantin Komarov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).