linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 04/10] fs/ntfs3: Add file operations and implementation
@ 2020-08-21 16:25 Konstantin Komarov
  2020-08-23  9:48 ` Pali Rohár
  0 siblings, 1 reply; 3+ messages in thread
From: Konstantin Komarov @ 2020-08-21 16:25 UTC (permalink / raw)
  To: viro, linux-kernel, linux-fsdevel; +Cc: Pali Rohár

This adds file operations and implementation

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
---
 fs/ntfs3/dir.c     |  529 +++++++++++
 fs/ntfs3/file.c    | 1179 ++++++++++++++++++++++++
 fs/ntfs3/frecord.c | 2179 ++++++++++++++++++++++++++++++++++++++++++++
 fs/ntfs3/namei.c   |  566 ++++++++++++
 fs/ntfs3/record.c  |  612 +++++++++++++
 fs/ntfs3/run.c     | 1188 ++++++++++++++++++++++++
 6 files changed, 6253 insertions(+)
 create mode 100644 fs/ntfs3/dir.c
 create mode 100644 fs/ntfs3/file.c
 create mode 100644 fs/ntfs3/frecord.c
 create mode 100644 fs/ntfs3/namei.c
 create mode 100644 fs/ntfs3/record.c
 create mode 100644 fs/ntfs3/run.c

diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
new file mode 100644
index 000000000000..5f1105f1283c
--- /dev/null
+++ b/fs/ntfs3/dir.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  linux/fs/ntfs3/dir.c
+ *
+ * Copyright (C) 2019-2020 Paragon Software GmbH, All rights reserved.
+ *
+ *  directory handling functions for ntfs-based filesystems
+ *
+ */
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/iversion.h>
+#include <linux/nls.h>
+
+#include "debug.h"
+#include "ntfs.h"
+#include "ntfs_fs.h"
+
+/*
+ * Convert little endian Unicode 16 to UTF-8.
+ */
+int uni_to_x8(ntfs_sb_info *sbi, const struct le_str *uni, u8 *buf, int buf_len)
+{
+	const __le16 *ip = uni->name;
+	u8 *op = buf;
+	struct nls_table *nls = sbi->nls;
+	int uni_len = uni->len;
+
+	static_assert(sizeof(wchar_t) == sizeof(__le16));
+
+	while (uni_len--) {
+		u16 ec;
+		int charlen;
+
+		if (buf_len < NLS_MAX_CHARSET_SIZE) {
+			ntfs_warning(
+				sbi->sb,
+				"filename was truncated while converting.");
+			break;
+		}
+
+		ec = le16_to_cpu(*ip++);
+		charlen = nls->uni2char(ec, op, buf_len);
+
+		if (charlen > 0) {
+			op += charlen;
+			buf_len -= charlen;
+		} else {
+			*op++ = ':';
+			op = hex_byte_pack(op, ec >> 8);
+			op = hex_byte_pack(op, ec);
+			buf_len -= 5;
+		}
+	}
+
+	*op = 0;
+	return op - buf;
+}
+
+static inline u8 get_digit(u8 d)
+{
+	u8 x = d & 0xf;
+
+	return x <= 9 ? ('0' + x) : ('A' + x - 10);
+}
+
+/*
+ * Convert input string to unicode
+ * max_ulen - maximum possible unicode length
+ * endian - unicode endian
+ */
+int x8_to_uni(ntfs_sb_info *sbi, const u8 *name, u32 name_len,
+	      struct cpu_str *uni, u32 max_ulen, enum utf16_endian endian)
+{
+	int i, ret, clen;
+	u32 tail;
+	const u8 *str = name;
+	const u8 *end = name + name_len;
+	u16 *uname = uni->name;
+	struct nls_table *nls = sbi->nls;
+	int warn = 0;
+
+	static_assert(sizeof(wchar_t) == sizeof(u16));
+
+	for (ret = 0; str < end; ret += 1, uname += 1, str += clen) {
+		if (ret >= max_ulen)
+			return -ENAMETOOLONG;
+		tail = end - str;
+
+		clen = nls->char2uni(str, tail, uname);
+		if (clen > 0)
+			continue;
+
+		if (!warn) {
+			warn = 1;
+			ntfs_warning(
+				sbi->sb,
+				"%s -> unicode failed: '%.*s', pos %d, chars %x %x %x",
+				nls->charset, name_len, name, (int)(str - name),
+				str[0], tail > 1 ? str[1] : 0,
+				tail > 2 ? str[2] : 0);
+		}
+
+		if (ret + 3 > max_ulen)
+			return -ENAMETOOLONG;
+
+		uname[0] = '%';
+		uname[1] = get_digit(*str >> 4);
+		uname[2] = get_digit(*str >> 0);
+
+		uname += 2;
+		ret += 2; // +1 will be added in for ( .... )
+		clen = 1;
+	}
+
+#ifdef __BIG_ENDIAN
+	if (endian == UTF16_LITTLE_ENDIAN) {
+		__le16 *uname = (__le16 *)uni->name;
+
+		for (i = 0; i < ret; i++, uname++)
+			*uname = cpu_to_le16(*name);
+	}
+#else
+	if (endian == UTF16_BIG_ENDIAN) {
+		__be16 *uname = (__be16 *)uni->name;
+
+		for (i = 0; i < ret; i++, uname++)
+			*uname = cpu_to_be16(*name);
+	}
+#endif
+
+	uni->len = ret;
+	return ret;
+}
+
+/* helper function */
+struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
+			   struct ntfs_fnd *fnd)
+{
+	int err = 0;
+	struct super_block *sb = dir->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	ntfs_inode *ni = ntfs_i(dir);
+	NTFS_DE *e;
+	int diff;
+	struct inode *inode = NULL;
+	struct ntfs_fnd *fnd_a = NULL;
+
+	if (!fnd) {
+		fnd_a = fnd_get(&ni->dir);
+		if (!fnd_a) {
+			err = -ENOMEM;
+			goto out;
+		}
+		fnd = fnd_a;
+	}
+
+	err = indx_find(&ni->dir, ni, NULL, uni, 0, sbi, &diff, &e, fnd);
+
+	if (err)
+		goto out;
+
+	if (diff) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	inode = ntfs_iget5(sb, &e->ref, uni);
+	if (!IS_ERR(inode) && is_bad_inode(inode)) {
+		iput(inode);
+		err = -EINVAL;
+	}
+out:
+	fnd_put(fnd_a);
+
+	return err == -ENOENT ? NULL : err ? ERR_PTR(err) : inode;
+}
+
+/* helper function */
+struct inode *dir_search(struct inode *dir, const struct qstr *name,
+			 struct ntfs_fnd *fnd)
+{
+	struct super_block *sb = dir->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	int err;
+	struct inode *inode;
+	struct cpu_str *uni = __getname();
+	const u8 *n = name->name;
+
+	if (!uni)
+		return ERR_PTR(-ENOMEM);
+
+	err = x8_to_uni(sbi, n, name->len, uni, NTFS_NAME_LEN,
+			UTF16_HOST_ENDIAN);
+
+	inode = err < 0 ? ERR_PTR(err) : dir_search_u(dir, uni, fnd);
+
+	__putname(uni);
+
+	return inode;
+}
+
+static inline int ntfs_filldir(ntfs_sb_info *sbi, ntfs_inode *ni,
+			       const NTFS_DE *e, u8 *name,
+			       struct dir_context *ctx)
+{
+	const ATTR_FILE_NAME *fname;
+	unsigned long ino;
+	int name_len;
+	u32 dt_type;
+
+	fname = Add2Ptr(e, sizeof(NTFS_DE));
+
+	if (fname->type == FILE_NAME_DOS)
+		return 0;
+
+	if (!mi_is_ref(&ni->mi, &fname->home))
+		return 0;
+
+	ino = ino_get(&e->ref);
+
+	if (ino == MFT_REC_ROOT)
+		return 0;
+
+	/* Skip meta files ( unless option to show metafiles is set ) */
+	if (!sbi->options.showmeta && ntfs_is_meta_file(sbi, ino))
+		return 0;
+
+	if (sbi->options.nohidden && (fname->dup.fa & FILE_ATTRIBUTE_HIDDEN))
+		return 0;
+
+	name_len = uni_to_x8(sbi, (struct le_str *)&fname->name_len, name,
+			     PATH_MAX);
+	if (name_len <= 0) {
+		ntfs_warning(sbi->sb, "failed to convert name for inode %lx.",
+			     ino);
+		return 0;
+	}
+
+	dt_type = (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY) ? DT_DIR : DT_REG;
+
+	return !dir_emit(ctx, (s8 *)name, name_len, ino, dt_type);
+}
+
+/*
+ * ntfs_read_hdr
+ *
+ * helper function 'ntfs_readdir'
+ */
+static int ntfs_read_hdr(ntfs_sb_info *sbi, ntfs_inode *ni,
+			 const INDEX_HDR *hdr, u64 vbo, u64 pos, u8 *name,
+			 struct dir_context *ctx)
+{
+	int err;
+	const NTFS_DE *e;
+	u32 e_size;
+	u32 end = le32_to_cpu(hdr->used);
+	u32 off = le32_to_cpu(hdr->de_off);
+
+next:
+	if (off + sizeof(NTFS_DE) > end)
+		return -1;
+
+	e = Add2Ptr(hdr, off);
+	e_size = le16_to_cpu(e->size);
+	if (e_size < sizeof(NTFS_DE) || off + e_size > end)
+		return -1;
+
+	if (de_is_last(e))
+		return 0;
+
+	/* Skip already enumerated*/
+	if (vbo + off < pos) {
+		off += e_size;
+		goto next;
+	}
+
+	if (le16_to_cpu(e->key_size) < SIZEOF_ATTRIBUTE_FILENAME)
+		return -1;
+
+	ctx->pos = vbo + off;
+
+	/* Submit the name to the filldir callback. */
+	err = ntfs_filldir(sbi, ni, e, name, ctx);
+	if (err)
+		return err;
+
+	off += e_size;
+	goto next;
+}
+
+/*
+ * file_operations::iterate_shared
+ *
+ * Use non sorted enumeration.
+ * We have an example of broken volume where sorted enumeration
+ * counts each name twice
+ */
+static int ntfs_readdir(struct file *file, struct dir_context *ctx)
+{
+	const INDEX_ROOT *root;
+	const INDEX_HDR *hdr;
+	u64 vbo;
+	size_t bit;
+	loff_t eod;
+	int err = 0;
+	struct inode *dir = file_inode(file);
+	ntfs_inode *ni = ntfs_i(dir);
+	struct super_block *sb = dir->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	loff_t i_size = dir->i_size;
+	u32 pos = ctx->pos;
+	u8 *name = NULL;
+	struct indx_node *node = NULL;
+	u8 index_bits = ni->dir.index_bits;
+
+	/* name is a buffer of PATH_MAX length */
+	static_assert(NTFS_NAME_LEN * 4 < PATH_MAX);
+
+	if (ni->dir.changed) {
+		ni->dir.changed = false;
+		pos = 0;
+	}
+
+	eod = i_size + sbi->record_size;
+
+	if (pos >= eod)
+		return 0;
+
+	if (!dir_emit_dots(file, ctx))
+		return 0;
+
+	name = __getname();
+	if (!name)
+		return -ENOMEM;
+
+	ni_lock(ni);
+
+	root = indx_get_root(&ni->dir, ni, NULL, NULL);
+	if (!root) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (pos >= sbi->record_size) {
+		bit = (pos - sbi->record_size) >> index_bits;
+		goto index_enum;
+	}
+
+	hdr = &root->ihdr;
+
+	err = ntfs_read_hdr(sbi, ni, hdr, 0, pos, name, ctx);
+	if (err)
+		goto out;
+
+	bit = 0;
+
+index_enum:
+
+	if (!i_size) {
+		ctx->pos = eod;
+		goto out;
+	}
+
+next_vcn:
+	vbo = (u64)bit << index_bits;
+	if (vbo >= i_size) {
+		ctx->pos = eod;
+		goto out;
+	}
+
+	err = indx_used_bit(&ni->dir, ni, &bit);
+	if (err)
+		goto out;
+
+	if (bit == MINUS_ONE_T) {
+		ctx->pos = eod;
+		goto out;
+	}
+
+	vbo = (u64)bit << index_bits;
+	if (vbo >= i_size)
+		goto fs_error;
+
+	err = indx_read(&ni->dir, ni, bit << ni->dir.idx2vbn_bits, &node);
+	if (err)
+		goto out;
+
+	hdr = &node->index->ihdr;
+	err = ntfs_read_hdr(sbi, ni, hdr, vbo + sbi->record_size, pos, name,
+			    ctx);
+	if (err)
+		goto out;
+
+	bit += 1;
+	goto next_vcn;
+
+fs_error:
+	ntfs_inode_error(dir, "Looks like your dir is corrupt");
+	err = -EINVAL;
+out:
+
+	__putname(name);
+	put_indx_node(node);
+
+	if (err == -ENOENT) {
+		err = 0;
+		ctx->pos = pos;
+	}
+
+	ni_unlock(ni);
+
+	return err;
+}
+
+static int ntfs_dir_count(struct inode *dir, bool *is_empty, size_t *dirs,
+			  size_t *files)
+{
+	int err = 0;
+	ntfs_inode *ni = ntfs_i(dir);
+	NTFS_DE *e = NULL;
+	INDEX_ROOT *root;
+	INDEX_HDR *hdr;
+	const ATTR_FILE_NAME *fname;
+	u32 e_size, off, end;
+	u64 vbo = 0;
+	size_t drs = 0, fles = 0, bit = 0;
+	loff_t i_size = ni->vfs_inode.i_size;
+	struct indx_node *node = NULL;
+	u8 index_bits = ni->dir.index_bits;
+
+	if (is_empty)
+		*is_empty = true;
+
+	root = indx_get_root(&ni->dir, ni, NULL, NULL);
+	if (!root)
+		return -EINVAL;
+
+	hdr = &root->ihdr;
+
+next_vcn:
+
+	end = le32_to_cpu(hdr->used);
+	off = le32_to_cpu(hdr->de_off);
+
+next_de:
+	if (off + sizeof(NTFS_DE) > end)
+		goto next_hdr;
+
+	e = Add2Ptr(hdr, off);
+	e_size = le16_to_cpu(e->size);
+	if (e_size < sizeof(NTFS_DE) || off + e_size > end)
+		goto next_hdr;
+
+	if (de_is_last(e))
+		goto next_hdr;
+
+	fname = de_get_fname(e);
+	if (!fname)
+		goto next_hdr;
+
+	if (fname->type == FILE_NAME_DOS)
+		goto next_de;
+
+	if (is_empty) {
+		*is_empty = false;
+		if (!dirs && !files)
+			goto out;
+	}
+
+	if (fname->dup.fa & FILE_ATTRIBUTE_DIRECTORY)
+		drs += 1;
+	else
+		fles += 1;
+
+	off += e_size;
+	goto next_de;
+
+next_hdr:
+	if (vbo >= i_size)
+		goto out;
+
+	err = indx_used_bit(&ni->dir, ni, &bit);
+	if (err)
+		goto out;
+
+	if (bit == MINUS_ONE_T)
+		goto out;
+
+	vbo = (u64)bit << index_bits;
+	if (vbo >= i_size)
+		goto out;
+
+	err = indx_read(&ni->dir, ni, bit << ni->dir.idx2vbn_bits, &node);
+	if (err)
+		goto out;
+
+	hdr = &node->index->ihdr;
+	bit += 1;
+	vbo = (u64)bit << ni->dir.idx2vbn_bits;
+	goto next_vcn;
+
+out:
+	put_indx_node(node);
+	if (dirs)
+		*dirs = drs;
+	if (files)
+		*files = fles;
+
+	return err;
+}
+
+bool dir_is_empty(struct inode *dir)
+{
+	bool is_empty = false;
+
+	ntfs_dir_count(dir, &is_empty, NULL, NULL);
+
+	return is_empty;
+}
+
+const struct file_operations ntfs_dir_operations = {
+	.llseek = generic_file_llseek,
+	.read = generic_read_dir,
+	.iterate = ntfs_readdir,
+	.fsync = ntfs_file_fsync,
+	.open = ntfs_file_open,
+};
diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
new file mode 100644
index 000000000000..e4f971d65586
--- /dev/null
+++ b/fs/ntfs3/file.c
@@ -0,0 +1,1179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  linux/fs/ntfs3/file.c
+ *
+ * Copyright (C) 2019-2020 Paragon Software GmbH, All rights reserved.
+ *
+ *  regular file handling primitives for ntfs-based filesystems
+ */
+#include <linux/backing-dev.h>
+#include <linux/buffer_head.h>
+#include <linux/compat.h>
+#include <linux/falloc.h>
+#include <linux/msdos_fs.h> /* FAT_IOCTL_XXX */
+#include <linux/nls.h>
+
+#include "debug.h"
+#include "ntfs.h"
+#include "ntfs_fs.h"
+
+static int ntfs_ioctl_fitrim(ntfs_sb_info *sbi, unsigned long arg)
+{
+	struct fstrim_range __user *user_range;
+	struct fstrim_range range;
+	struct request_queue *q = bdev_get_queue(sbi->sb->s_bdev);
+	int err;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	if (!blk_queue_discard(q))
+		return -EOPNOTSUPP;
+
+	user_range = (struct fstrim_range __user *)arg;
+	if (copy_from_user(&range, user_range, sizeof(range)))
+		return -EFAULT;
+
+	range.minlen = max_t(u32, range.minlen, q->limits.discard_granularity);
+
+	err = ntfs_trim_fs(sbi, &range);
+	if (err < 0)
+		return err;
+
+	if (copy_to_user(user_range, &range, sizeof(range)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long ntfs_ioctl(struct file *filp, u32 cmd, unsigned long arg)
+{
+	struct inode *inode = file_inode(filp);
+	ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
+	u32 __user *user_attr = (u32 __user *)arg;
+
+	switch (cmd) {
+	case FAT_IOCTL_GET_ATTRIBUTES:
+		return put_user(le32_to_cpu(ntfs_i(inode)->std_fa), user_attr);
+
+	case FAT_IOCTL_GET_VOLUME_ID:
+		return put_user(sbi->volume.ser_num, user_attr);
+
+	case FITRIM:
+		return ntfs_ioctl_fitrim(sbi, arg);
+	}
+	return -ENOTTY; /* Inappropriate ioctl for device */
+}
+
+#ifdef CONFIG_COMPAT
+static long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg)
+
+{
+	return ntfs_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+/*
+ * inode_operations::getattr
+ */
+int ntfs_getattr(const struct path *path, struct kstat *stat, u32 request_mask,
+		 u32 flags)
+{
+	struct inode *inode = d_inode(path->dentry);
+	struct super_block *sb = inode->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	ntfs_inode *ni = ntfs_i(inode);
+
+	if (is_compressed(ni))
+		stat->attributes |= STATX_ATTR_COMPRESSED;
+
+	if (is_encrypted(ni))
+		stat->attributes |= STATX_ATTR_ENCRYPTED;
+
+	stat->attributes_mask |= STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED;
+
+	generic_fillattr(inode, stat);
+
+	stat->result_mask |= STATX_BTIME;
+	stat->btime.tv_sec = ni->i_crtime.tv_sec;
+	stat->btime.tv_nsec = ni->i_crtime.tv_nsec;
+	stat->blksize = sbi->cluster_size;
+	stat->blocks <<= sbi->cluster_bits - 9;
+
+	return 0;
+}
+
+static int ntfs_extend_initialized_size(struct file *file, ntfs_inode *ni,
+					const loff_t valid,
+					const loff_t new_valid)
+{
+	struct inode *inode = &ni->vfs_inode;
+	struct address_space *mapping = inode->i_mapping;
+	ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
+	loff_t pos = valid;
+	int err;
+
+	WARN_ON(is_compressed(ni));
+	WARN_ON(valid >= new_valid);
+
+	for (;;) {
+		u32 zerofrom, len;
+		struct page *page;
+		void *fsdata;
+		u8 bits;
+		CLST vcn, lcn, clen;
+
+		if (is_sparsed(ni)) {
+			bits = sbi->cluster_bits;
+			vcn = pos >> bits;
+
+			err = attr_data_get_block(ni, vcn, &lcn, &clen, NULL);
+
+			if (err)
+				goto out;
+
+			if (lcn == SPARSE_LCN) {
+				loff_t vbo = (loff_t)vcn << bits;
+				loff_t to = vbo + ((loff_t)clen << bits);
+
+				if (to <= new_valid) {
+					ni->i_valid = to;
+					pos = to;
+					goto next;
+				}
+
+				if (vbo < pos)
+					pos = vbo;
+				else {
+					to = (new_valid >> bits) << bits;
+					if (pos < to) {
+						ni->i_valid = to;
+						pos = to;
+						goto next;
+					}
+				}
+			}
+		}
+
+		zerofrom = pos & (PAGE_SIZE - 1);
+		len = PAGE_SIZE - zerofrom;
+
+		if (pos + len > new_valid)
+			len = new_valid - pos;
+
+		err = pagecache_write_begin(file, mapping, pos, len, 0, &page,
+					    &fsdata);
+		if (err)
+			goto out;
+
+		zero_user_segment(page, zerofrom, PAGE_SIZE);
+
+		/* this function in any case puts page*/
+		err = pagecache_write_end(file, mapping, pos, len, len, page,
+					  fsdata);
+		if (err < 0)
+			goto out;
+		pos += len;
+
+next:
+		if (pos >= new_valid)
+			break;
+		balance_dirty_pages_ratelimited(mapping);
+	}
+
+	mark_inode_dirty(inode);
+
+	return 0;
+
+out:
+	ni->i_valid = valid;
+	ntfs_inode_warning(inode, "failed to extend initialized size to %llx.",
+			   new_valid);
+	return err;
+}
+
+static int ntfs_extend_initialized_size_cmpr(struct file *file, ntfs_inode *ni,
+					     const loff_t valid,
+					     const loff_t new_valid)
+{
+	struct inode *inode = &ni->vfs_inode;
+	struct address_space *mapping = inode->i_mapping;
+	ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
+	loff_t pos = valid;
+	u8 bits = NTFS_LZNT_CUNIT + sbi->cluster_bits;
+	int err;
+
+	WARN_ON(!is_compressed(ni));
+	WARN_ON(valid >= new_valid);
+
+	for (;;) {
+		u32 zerofrom, len;
+		struct page *page;
+		CLST frame, vcn, lcn, clen;
+
+		frame = pos >> bits;
+		vcn = frame << NTFS_LZNT_CUNIT;
+
+		err = attr_data_get_block(ni, vcn, &lcn, &clen, NULL);
+
+		if (err)
+			goto out;
+
+		if (lcn == SPARSE_LCN) {
+			loff_t vbo = (loff_t)frame << bits;
+			loff_t to = vbo + ((u64)clen << sbi->cluster_bits);
+
+			if (to <= new_valid) {
+				ni->i_valid = to;
+				pos = to;
+				goto next;
+			}
+
+			if (vbo >= pos) {
+				to = (new_valid >> bits) << bits;
+				if (pos < to) {
+					ni->i_valid = to;
+					pos = to;
+					goto next;
+				}
+			}
+		}
+
+		zerofrom = pos & (PAGE_SIZE - 1);
+		len = PAGE_SIZE - zerofrom;
+
+		if (pos + len > new_valid)
+			len = new_valid - pos;
+again:
+		page = find_or_create_page(mapping, pos >> PAGE_SHIFT,
+					   mapping_gfp_constraint(mapping,
+								  ~__GFP_FS));
+
+		if (!page) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		if (zerofrom && !PageUptodate(page)) {
+			err = ntfs_readpage(NULL, page);
+			lock_page(page);
+			if (page->mapping != mapping) {
+				unlock_page(page);
+				put_page(page);
+				goto again;
+			}
+			if (!PageUptodate(page)) {
+				err = -EIO;
+				unlock_page(page);
+				put_page(page);
+				goto out;
+			}
+		}
+
+		wait_on_page_writeback(page);
+
+		zero_user_segment(page, zerofrom, PAGE_SIZE);
+		if (!zerofrom)
+			SetPageUptodate(page);
+
+		ClearPageChecked(page);
+		set_page_dirty(page);
+		unlock_page(page);
+		put_page(page);
+		pos += len;
+		ni->i_valid = pos;
+next:
+		if (pos >= new_valid)
+			break;
+		balance_dirty_pages_ratelimited(mapping);
+	}
+
+	mark_inode_dirty(inode);
+
+	return 0;
+
+out:
+	ni->i_valid = valid;
+	ntfs_inode_warning(
+		inode, "failed to extend initialized compressed size to %llx.",
+		new_valid);
+	return err;
+}
+
+/*
+ * ntfs_sparse_cluster
+ *
+ * Helper function to zero a new allocated clusters
+ */
+void ntfs_sparse_cluster(struct inode *inode, struct page *page0, loff_t vbo,
+			 u32 bytes)
+{
+	struct address_space *mapping = inode->i_mapping;
+	ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
+	u32 blocksize = 1 << inode->i_blkbits;
+	pgoff_t idx0 = page0 ? page0->index : -1;
+	loff_t vbo_clst = vbo & sbi->cluster_mask_inv;
+	loff_t end = ntfs_up_cluster(sbi, vbo + bytes);
+	pgoff_t idx = vbo_clst >> PAGE_SHIFT;
+	u32 from = vbo_clst & (PAGE_SIZE - 1);
+	pgoff_t idx_end = (end + PAGE_SIZE - 1) >> PAGE_SHIFT;
+	loff_t page_off;
+	u32 to;
+	bool partial;
+	struct page *page;
+
+	for (; idx < idx_end; idx += 1, from = 0) {
+		page = idx == idx0 ? page0 : grab_cache_page(mapping, idx);
+
+		if (!page)
+			continue;
+
+		page_off = (loff_t)idx << PAGE_SHIFT;
+		to = (page_off + PAGE_SIZE) > end ? (end - page_off) :
+						    PAGE_SIZE;
+		partial = false;
+
+		if ((from || PAGE_SIZE != to) &&
+		    likely(!page_has_buffers(page))) {
+			create_empty_buffers(page, blocksize, 0);
+			if (!page_has_buffers(page)) {
+				ntfs_inode_error(
+					inode,
+					"failed to allocate page buffers.");
+				/*err = -ENOMEM;*/
+				goto unlock_page;
+			}
+		}
+
+		if (page_has_buffers(page)) {
+			struct buffer_head *head, *bh;
+			u32 bh_off = 0;
+
+			bh = head = page_buffers(page);
+			do {
+				u32 bh_next = bh_off + blocksize;
+
+				if (from <= bh_off && bh_next <= to) {
+					set_buffer_uptodate(bh);
+					mark_buffer_dirty(bh);
+				} else if (!buffer_uptodate(bh))
+					partial = true;
+				bh_off = bh_next;
+			} while (head != (bh = bh->b_this_page));
+		}
+
+		zero_user_segment(page, from, to);
+
+		if (!partial) {
+			if (!PageUptodate(page))
+				SetPageUptodate(page);
+			set_page_dirty(page);
+		}
+
+unlock_page:
+		if (idx != idx0) {
+			unlock_page(page);
+			put_page(page);
+		}
+	}
+
+	mark_inode_dirty(inode);
+}
+
+struct ntfs_file_vm_ops {
+	atomic_t refcnt;
+	loff_t to;
+
+	const struct vm_operations_struct *base;
+	struct vm_operations_struct vm_ops;
+};
+
+/*
+ * vm_operations_struct::open
+ */
+static void ntfs_filemap_open(struct vm_area_struct *vma)
+{
+	struct ntfs_file_vm_ops *vm_ops;
+	const struct vm_operations_struct *base;
+
+	vm_ops = container_of(vma->vm_ops, struct ntfs_file_vm_ops, vm_ops);
+	base = vm_ops->base;
+
+	atomic_inc(&vm_ops->refcnt);
+
+	if (base->open)
+		base->open(vma);
+}
+
+/*
+ * vm_operations_struct::close
+ */
+static void ntfs_filemap_close(struct vm_area_struct *vma)
+{
+	struct ntfs_file_vm_ops *vm_ops;
+	const struct vm_operations_struct *base;
+	struct inode *inode;
+	ntfs_inode *ni;
+	// unsigned long flags;
+
+	vm_ops = container_of(vma->vm_ops, struct ntfs_file_vm_ops, vm_ops);
+
+	if (!atomic_dec_and_test(&vm_ops->refcnt))
+		return;
+
+	base = vm_ops->base;
+	if (!(vma->vm_flags & VM_WRITE))
+		goto close_base;
+
+	inode = file_inode(vma->vm_file);
+	ni = ntfs_i(inode);
+
+	// Update valid size
+	// write_lock_irqsave( &ni->rwlock, flags );
+	ni->i_valid = max_t(loff_t, ni->i_valid,
+			    min_t(loff_t, i_size_read(inode), vm_ops->to));
+	// write_unlock_irqrestore( &u->rwlock, flags );
+
+close_base:
+	if (base->close)
+		base->close(vma);
+
+	vma->vm_ops = base;
+	ntfs_free(vm_ops);
+}
+
+/*
+ * vm_operations_struct::fault
+ */
+static vm_fault_t ntfs_filemap_fault(struct vm_fault *vmf)
+{
+	vm_fault_t ret;
+	struct ntfs_file_vm_ops *vm_ops;
+	struct vm_area_struct *vma = vmf->vma;
+
+	vm_ops = container_of(vma->vm_ops, struct ntfs_file_vm_ops, vm_ops);
+
+	/* Call base function */
+	ret = vm_ops->base->fault(vmf);
+
+	if (VM_FAULT_LOCKED & ret) {
+		/* Update maximum mapped range */
+		loff_t to = (loff_t)(vmf->pgoff + 1) << PAGE_SHIFT;
+
+		if (vm_ops->to < to)
+			vm_ops->to = to;
+	}
+
+	return ret;
+}
+
+/*
+ * file_operations::mmap
+ */
+static int ntfs_file_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct inode *inode = file->f_mapping->host;
+	ntfs_inode *ni = ntfs_i(inode);
+	u64 to, from = ((u64)vma->vm_pgoff << PAGE_SHIFT);
+	bool rw = vma->vm_flags & VM_WRITE;
+	struct ntfs_file_vm_ops *vm_ops = NULL;
+	int err;
+
+	if (is_encrypted(ni)) {
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	if (!rw)
+		goto generic;
+
+	if (is_compressed(ni)) {
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	/*
+	 * Allocate and init small struct to keep track the mapping operations
+	 * It is useful when mmap(size) + truncate(size/2) + unmap(). see
+	 * xfstests/generic/039
+	 */
+	vm_ops = ntfs_alloc(sizeof(struct ntfs_file_vm_ops), 1);
+	if (unlikely(!vm_ops)) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	// map for write
+	inode_lock(inode);
+
+	to = from + vma->vm_end - vma->vm_start;
+
+	if (to > inode->i_size)
+		to = inode->i_size;
+
+	if (is_sparsed(ni)) {
+		/* allocate clusters for rw map */
+		ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
+		CLST vcn, lcn, len;
+		CLST end = bytes_to_cluster(sbi, to);
+		bool new;
+
+		for (vcn = from >> sbi->cluster_bits; vcn < end; vcn += len) {
+			err = attr_data_get_block(ni, vcn, &lcn, &len, &new);
+			if (err) {
+				inode_unlock(inode);
+				goto out;
+			}
+			if (!new)
+				continue;
+			ntfs_sparse_cluster(inode, NULL,
+					    (u64)vcn << sbi->cluster_bits,
+					    sbi->cluster_size);
+		}
+	}
+
+	err = ni->i_valid < to ?
+		      ntfs_extend_initialized_size(file, ni, ni->i_valid, to) :
+		      0;
+
+	inode_unlock(inode);
+	if (err)
+		goto out;
+
+generic:
+	err = generic_file_mmap(file, vma);
+	if (err)
+		goto out;
+
+	if (rw) {
+		atomic_set(&vm_ops->refcnt, 1);
+		vm_ops->to = to;
+		vm_ops->base = vma->vm_ops;
+		memcpy(&vm_ops->vm_ops, vma->vm_ops,
+		       sizeof(struct vm_operations_struct));
+		vm_ops->vm_ops.fault = &ntfs_filemap_fault;
+		vm_ops->vm_ops.open = &ntfs_filemap_open;
+		vm_ops->vm_ops.close = &ntfs_filemap_close;
+		vma->vm_ops = &vm_ops->vm_ops;
+	}
+
+out:
+	if (err)
+		ntfs_free(vm_ops);
+
+	return err;
+}
+
+/*
+ * file_operations::fsync
+ */
+int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
+{
+	return generic_file_fsync(filp, start, end, datasync);
+}
+
+static int ntfs_extend_ex(struct inode *inode, loff_t pos, size_t count,
+			  struct file *file)
+{
+	ntfs_inode *ni = ntfs_i(inode);
+	struct address_space *mapping = inode->i_mapping;
+	loff_t end = pos + count;
+	int err;
+	bool extend_init = file && pos > ni->i_valid;
+
+	if (end <= inode->i_size && !extend_init)
+		return 0;
+
+	/*mark rw ntfs as dirty. it will be cleared at umount*/
+	ntfs_set_state(ni->mi.sbi, NTFS_DIRTY_DIRTY);
+
+	if (end > inode->i_size) {
+		err = ntfs_set_size(inode, end);
+		if (err)
+			goto out;
+		inode->i_size = end;
+	}
+
+	if (extend_init) {
+		err = (is_compressed(ni) ? ntfs_extend_initialized_size_cmpr :
+					   ntfs_extend_initialized_size)(
+			file, ni, ni->i_valid, pos);
+		if (err)
+			goto out;
+	}
+
+	inode->i_ctime = inode->i_mtime = current_time(inode);
+	ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
+	mark_inode_dirty(inode);
+
+	if (IS_SYNC(inode)) {
+		int err2;
+
+		err = filemap_fdatawrite_range(mapping, pos, end - 1);
+		err2 = sync_mapping_buffers(mapping);
+		if (!err)
+			err = err2;
+		err2 = write_inode_now(inode, 1);
+		if (!err)
+			err = err2;
+		if (!err)
+			err = filemap_fdatawait_range(mapping, pos, end - 1);
+	}
+
+out:
+	return err;
+}
+
+/*
+ * Preallocate space for a file. This implements ntfs's fallocate file
+ * operation, which gets called from sys_fallocate system call. User
+ * space requests len bytes at offset. If FALLOC_FL_KEEP_SIZE is set
+ * we just allocate clusters without zeroing them out. Otherwise we
+ * allocate and zero out clusters via an expanding truncate.
+ */
+static long ntfs_fallocate(struct file *file, int mode, loff_t offset,
+			   loff_t len)
+{
+	struct inode *inode = file->f_mapping->host;
+	struct super_block *sb = inode->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	loff_t end;
+	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 | FALLOC_FL_ZERO_RANGE |
+		     FALLOC_FL_INSERT_RANGE))
+		return -EOPNOTSUPP;
+
+	inode_lock(inode);
+
+	if (mode & FALLOC_FL_PUNCH_HOLE) {
+		if (!(mode & FALLOC_FL_KEEP_SIZE)) {
+			err = -EINVAL;
+			goto out;
+		}
+		/*TODO*/
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	if (mode & FALLOC_FL_COLLAPSE_RANGE) {
+		if (mode & ~FALLOC_FL_COLLAPSE_RANGE) {
+			err = -EINVAL;
+			goto out;
+		}
+
+		/*TODO*/
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	if (mode & FALLOC_FL_INSERT_RANGE) {
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	if (mode & FALLOC_FL_ZERO_RANGE) {
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	end = offset + len;
+	if (mode & FALLOC_FL_KEEP_SIZE) {
+		/* Start the allocation.We are not zeroing out the clusters */
+		err = ntfs_set_size(inode, bytes_to_cluster(sbi, end));
+		goto out;
+	}
+
+	err = 0;
+
+	if (end <= i_size_read(inode))
+		goto out;
+
+	/*
+	 * Allocate clusters but does not change 'valid'
+	 */
+	err = ntfs_extend_ex(inode, offset, len, NULL);
+
+out:
+	if (err == -EFBIG)
+		err = -ENOSPC;
+
+	inode_unlock(inode);
+	return err;
+}
+
+void ntfs_truncate_blocks(struct inode *inode, loff_t new_size)
+{
+	struct super_block *sb = inode->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	ntfs_inode *ni = ntfs_i(inode);
+	int err, dirty = 0;
+	u32 vcn;
+	u64 new_valid;
+
+	if (!S_ISREG(inode->i_mode))
+		return;
+
+	vcn = bytes_to_cluster(sbi, new_size);
+	new_valid = ntfs_up_block(sb, min(ni->i_valid, new_size));
+
+	ni_lock(ni);
+	down_write(&ni->file.run_lock);
+
+	truncate_setsize(inode, new_size);
+
+	err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
+			    &new_valid, true, NULL);
+
+	if (new_valid < ni->i_valid)
+		ni->i_valid = new_valid;
+
+	up_write(&ni->file.run_lock);
+	ni_unlock(ni);
+
+	ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
+	inode->i_ctime = inode->i_mtime = current_time(inode);
+	if (!IS_DIRSYNC(inode)) {
+		dirty = 1;
+	} else {
+		err = ntfs_sync_inode(inode);
+		if (err)
+			return;
+	}
+
+	inode->i_blocks = vcn;
+
+	if (dirty)
+		mark_inode_dirty(inode);
+
+	/*ntfs_flush_inodes(inode->i_sb, inode, NULL);*/
+}
+
+/*
+ * inode_operations::setattr
+ */
+int ntfs_setattr(struct dentry *dentry, struct iattr *attr)
+{
+	struct super_block *sb = dentry->d_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	struct inode *inode = d_inode(dentry);
+	ntfs_inode *ni = ntfs_i(inode);
+	u32 ia_valid = attr->ia_valid;
+	umode_t mode = inode->i_mode;
+	int err;
+
+	if (sbi->options.no_acs_rules) {
+		/* "no access rules" - force any changes of time etc. */
+		attr->ia_valid |= ATTR_FORCE;
+		/* and disable for editing some attributes */
+		attr->ia_valid &= ~(ATTR_UID | ATTR_GID | ATTR_MODE);
+		ia_valid = attr->ia_valid;
+	}
+
+	err = setattr_prepare(dentry, attr);
+	if (err) {
+		if (sbi->options.quiet)
+			err = 0;
+		goto out;
+	}
+
+	if (ia_valid & ATTR_SIZE) {
+		loff_t oldsize = inode->i_size;
+
+		inode_dio_wait(inode);
+
+		if (attr->ia_size < oldsize) {
+			err = block_truncate_page(inode->i_mapping,
+						  attr->ia_size,
+						  ntfs_get_block);
+			if (err)
+				goto out;
+			ntfs_truncate_blocks(inode, attr->ia_size);
+		} else if (attr->ia_size > oldsize) {
+			err = ntfs_extend_ex(inode, attr->ia_size, 0, NULL);
+			if (err)
+				goto out;
+		}
+
+		ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
+	}
+
+	setattr_copy(inode, attr);
+
+	if (mode != inode->i_mode) {
+		err = ntfs_acl_chmod(inode);
+		if (err)
+			goto out;
+
+		/* linux 'w' -> windows 'ro' */
+		if (0222 & inode->i_mode)
+			ni->std_fa &= ~FILE_ATTRIBUTE_READONLY;
+		else
+			ni->std_fa |= FILE_ATTRIBUTE_READONLY;
+	}
+
+	mark_inode_dirty(inode);
+out:
+
+	return err;
+}
+
+static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+	ssize_t err;
+	size_t count = iov_iter_count(iter);
+	struct file *file = iocb->ki_filp;
+	struct inode *inode = file->f_mapping->host;
+	ntfs_inode *ni = ntfs_i(inode);
+
+	if (is_encrypted(ni))
+		return -EOPNOTSUPP;
+
+	if (is_dedup(ni))
+		return -EOPNOTSUPP;
+
+	err = count ? generic_file_read_iter(iocb, iter) : 0;
+
+	return err;
+}
+
+/*
+ * on error we return an unlocked page and the error value
+ * on success we return a locked page and 0
+ */
+static int prepare_uptodate_page(struct inode *inode, struct page *page,
+				 u64 pos, bool force_uptodate)
+{
+	int err = 0;
+
+	if (((pos & (PAGE_SIZE - 1)) || force_uptodate) &&
+	    !PageUptodate(page)) {
+		err = ntfs_readpage(NULL, page);
+		if (err)
+			return err;
+		lock_page(page);
+		if (!PageUptodate(page)) {
+			unlock_page(page);
+			return -EIO;
+		}
+		if (page->mapping != inode->i_mapping) {
+			unlock_page(page);
+			return -EAGAIN;
+		}
+	}
+	return 0;
+}
+
+/*helper for ntfs_file_write_iter (compressed files)*/
+static noinline ssize_t ntfs_compress_write(struct kiocb *iocb,
+					    struct iov_iter *from)
+{
+	int err;
+	struct file *file = iocb->ki_filp;
+	size_t count = iov_iter_count(from);
+	loff_t pos = iocb->ki_pos;
+	loff_t end = pos + count;
+	struct inode *inode = file_inode(file);
+	struct address_space *mapping = inode->i_mapping;
+	ntfs_inode *ni = ntfs_i(inode);
+	// struct ntfs_sb_info *sbi = ni->mi.sbi;
+	struct page *page, **pages = NULL;
+	size_t ip, max_pages, written = 0;
+	bool force_uptodate = false;
+	pgoff_t from_idx, end_idx;
+	u32 off;
+	gfp_t mask = mapping_gfp_constraint(mapping, ~__GFP_FS) | __GFP_WRITE;
+
+	from_idx = pos >> PAGE_SHIFT;
+	end_idx = (end + PAGE_SIZE - 1) >> PAGE_SHIFT;
+	max_pages = end_idx - from_idx;
+	if (max_pages > 16)
+		max_pages = 16;
+	WARN_ON(end_idx <= from_idx);
+
+	pages = ntfs_alloc(max_pages * sizeof(struct page *), 1);
+	if (!pages)
+		return -ENOMEM;
+
+	current->backing_dev_info = inode_to_bdi(inode);
+	err = file_remove_privs(file);
+	if (err)
+		goto out;
+
+	err = file_update_time(file);
+	if (err)
+		goto out;
+
+	while (count) {
+		pgoff_t index = pos >> PAGE_SHIFT;
+		size_t offset = offset_in_page(pos);
+		size_t bytes = max_pages * PAGE_SIZE - offset;
+		size_t wpages, copied;
+
+		if (bytes > count)
+			bytes = count;
+
+		wpages = (offset + bytes + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+		WARN_ON(wpages > max_pages);
+
+		if (unlikely(iov_iter_fault_in_readable(from, bytes))) {
+			err = -EFAULT;
+			goto out;
+		}
+
+		for (ip = 0; ip < wpages; ip++) {
+again:
+			page = find_or_create_page(mapping, index + ip, mask);
+			if (!page) {
+				err = -ENOMEM;
+fail:
+				while (ip--) {
+					page = pages[ip];
+					unlock_page(page);
+					put_page(page);
+				}
+
+				goto out;
+			}
+
+			pages[ip] = page;
+
+			if (!ip)
+				err = prepare_uptodate_page(inode, page, pos,
+							    force_uptodate);
+
+			if (!err && ip == wpages - 1)
+				err = prepare_uptodate_page(inode, page,
+							    pos + bytes, false);
+
+			if (err) {
+				put_page(page);
+				if (err == -EAGAIN) {
+					err = 0;
+					goto again;
+				}
+				goto fail;
+			}
+			wait_on_page_writeback(page);
+		}
+
+		WARN_ON(!bytes);
+		copied = 0;
+		ip = 0;
+		off = offset_in_page(pos);
+
+		for (;;) {
+			size_t tail = PAGE_SIZE - off;
+			size_t count = min(tail, bytes);
+			size_t cp;
+
+			page = pages[ip];
+
+			cp = iov_iter_copy_from_user_atomic(page, from, off,
+							    count);
+
+			flush_dcache_page(page);
+
+			if (!PageUptodate(page) && cp < count)
+				cp = 0;
+
+			iov_iter_advance(from, cp);
+			copied += cp;
+			bytes -= cp;
+			if (!bytes || !cp)
+				break;
+
+			if (cp < tail)
+				off += cp;
+			else {
+				ip++;
+				off = 0;
+			}
+		}
+
+		if (!copied)
+			force_uptodate = true;
+		else {
+			size_t dpages;
+
+			force_uptodate = false;
+			dpages =
+				(offset + copied + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+			for (ip = 0; ip < dpages; ip++) {
+				page = pages[ip];
+				SetPageUptodate(page);
+				ClearPageChecked(page);
+				set_page_dirty(page);
+			}
+		}
+
+		for (ip = 0; ip < wpages; ip++) {
+			page = pages[ip];
+			ClearPageChecked(page);
+			unlock_page(page);
+			put_page(page);
+		}
+
+		cond_resched();
+
+		balance_dirty_pages_ratelimited(mapping);
+
+		pos += copied;
+		written += copied;
+
+		count = iov_iter_count(from);
+	}
+
+out:
+	ntfs_free(pages);
+
+	current->backing_dev_info = NULL;
+
+	if (err < 0)
+		return err;
+
+	iocb->ki_pos += written;
+	if (iocb->ki_pos > ni->i_valid)
+		ni->i_valid = iocb->ki_pos;
+
+	return written;
+}
+
+/*
+ * file_operations::write_iter
+ */
+static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+	struct file *file = iocb->ki_filp;
+	struct address_space *mapping = file->f_mapping;
+	struct inode *inode = mapping->host;
+	ssize_t ret;
+	ntfs_inode *ni = ntfs_i(inode);
+
+	if (is_encrypted(ni)) {
+		ntfs_inode_warning(inode, "encrypted i/o not supported");
+		return -EOPNOTSUPP;
+	}
+
+	if (is_compressed(ni) && (iocb->ki_flags & IOCB_DIRECT)) {
+		ntfs_inode_warning(inode,
+				   "direct i/o + compressed not supported");
+		return -EOPNOTSUPP;
+	}
+
+	if (ni->ni_flags & NI_FLAG_COMPRESSED_MASK) {
+		ntfs_inode_warning(
+			inode,
+			"write into external compressed file not supported (temporary)");
+		return -EOPNOTSUPP;
+	}
+
+	if (is_dedup(ni)) {
+		ntfs_inode_warning(inode,
+				   "write into deduplicated not supported");
+		return -EOPNOTSUPP;
+	}
+
+	if (!inode_trylock(inode)) {
+		if (iocb->ki_flags & IOCB_NOWAIT)
+			return -EAGAIN;
+		inode_lock(inode);
+	}
+
+	ret = generic_write_checks(iocb, from);
+	if (ret <= 0)
+		goto out;
+
+	ret = ntfs_extend_ex(inode, iocb->ki_pos, ret, file);
+	if (ret)
+		goto out;
+
+	ret = is_compressed(ni) ? ntfs_compress_write(iocb, from) :
+				  __generic_file_write_iter(iocb, from);
+
+out:
+	inode_unlock(inode);
+
+	if (ret > 0)
+		ret = generic_write_sync(iocb, ret);
+
+	return ret;
+}
+
+/*
+ * file_operations::open
+ */
+int ntfs_file_open(struct inode *inode, struct file *file)
+{
+	ntfs_inode *ni = ntfs_i(inode);
+
+	if (unlikely((is_compressed(ni) || is_encrypted(ni)) &&
+		     (file->f_flags & O_DIRECT))) {
+		return -ENOTBLK;
+	}
+
+	return generic_file_open(inode, file);
+}
+
+#ifdef NTFS3_PREALLOCATE
+/*
+ * file_operations::release
+ */
+static int ntfs_file_release(struct inode *inode, struct file *file)
+{
+	ntfs_inode *ni = ntfs_i(inode);
+	int err;
+
+	/* if we are the last writer on the inode, drop the block reservation */
+	if (!(file->f_mode & FMODE_WRITE) ||
+	    atomic_read(&inode->i_writecount) != 1)
+		return 0;
+
+	ni_lock(ni);
+
+	err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run,
+			    inode->i_size, &ni->i_valid, false, NULL);
+
+	ni_unlock(ni);
+
+	/*congestion_wait(BLK_RW_ASYNC, HZ / 10);*/
+
+	return err;
+}
+#endif
+
+const struct inode_operations ntfs_file_inode_operations = {
+	.getattr = ntfs_getattr,
+	.setattr = ntfs_setattr,
+	.listxattr = ntfs_listxattr,
+	.permission = ntfs_permission,
+	.get_acl = ntfs_get_acl,
+	.set_acl = ntfs_set_acl,
+};
+
+const struct file_operations ntfs_file_operations = {
+	.llseek = generic_file_llseek,
+	.read_iter = ntfs_file_read_iter,
+	.write_iter = ntfs_file_write_iter,
+	.unlocked_ioctl = ntfs_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = ntfs_compat_ioctl,
+#endif
+	.splice_read = generic_file_splice_read,
+	.mmap = ntfs_file_mmap,
+	.open = ntfs_file_open,
+	.fsync = ntfs_file_fsync,
+	.splice_write = iter_file_splice_write,
+	.fallocate = ntfs_fallocate,
+#ifdef NTFS3_PREALLOCATE
+	.release = ntfs_file_release,
+#endif
+};
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
new file mode 100644
index 000000000000..918291e7f629
--- /dev/null
+++ b/fs/ntfs3/frecord.c
@@ -0,0 +1,2179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  linux/fs/ntfs3/frecord.c
+ *
+ * Copyright (C) 2019-2020 Paragon Software GmbH, All rights reserved.
+ *
+ */
+
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/nls.h>
+#include <linux/sched/signal.h>
+
+#include "debug.h"
+#include "ntfs.h"
+#include "ntfs_fs.h"
+
+static inline void get_mi_ref(const mft_inode *mi, MFT_REF *ref)
+{
+#ifdef NTFS3_64BIT_CLUSTER
+	ref->low = cpu_to_le32(mi->rno);
+	ref->high = cpu_to_le16(mi->rno >> 32);
+#else
+	ref->low = cpu_to_le32(mi->rno);
+	ref->high = 0;
+#endif
+	ref->seq = mi->mrec->seq;
+}
+
+static mft_inode *ni_ins_mi(ntfs_inode *ni, struct rb_root *tree, CLST ino,
+			    struct rb_node *ins)
+{
+	struct rb_node **p = &tree->rb_node;
+	struct rb_node *pr = NULL;
+
+	while (*p) {
+		mft_inode *mi;
+
+		pr = *p;
+		mi = rb_entry(pr, mft_inode, node);
+		if (mi->rno > ino)
+			p = &pr->rb_left;
+		else if (mi->rno < ino)
+			p = &pr->rb_right;
+		else
+			return mi;
+	}
+
+	if (!ins)
+		return NULL;
+
+	rb_link_node(ins, pr, p);
+	rb_insert_color(ins, tree);
+	return rb_entry(ins, mft_inode, node);
+}
+
+/*
+ * ni_find_mi
+ *
+ * finds mft_inode by record number
+ */
+static mft_inode *ni_find_mi(ntfs_inode *ni, CLST rno)
+{
+	return ni_ins_mi(ni, &ni->mi_tree, rno, NULL);
+}
+
+/*
+ * ni_add_mi
+ *
+ * adds new mft_inode into ntfs_inode
+ */
+static void ni_add_mi(ntfs_inode *ni, mft_inode *mi)
+{
+	ni_ins_mi(ni, &ni->mi_tree, mi->rno, &mi->node);
+}
+
+/*
+ * ni_remove_mi
+ *
+ * removes mft_inode from ntfs_inode
+ */
+void ni_remove_mi(ntfs_inode *ni, mft_inode *mi)
+{
+	rb_erase(&mi->node, &ni->mi_tree);
+}
+
+/*
+ * ni_std
+ *
+ * returns pointer into std_info from primary record
+ */
+ATTR_STD_INFO *ni_std(ntfs_inode *ni)
+{
+	const ATTRIB *attr;
+
+	attr = mi_find_attr(&ni->mi, NULL, ATTR_STD, NULL, 0, NULL);
+
+	return attr ? resident_data_ex(attr, sizeof(ATTR_STD_INFO)) : NULL;
+}
+
+/*
+ * ni_clear
+ *
+ * clears resources allocated by ntfs_inode
+ */
+void ni_clear(ntfs_inode *ni)
+{
+	struct rb_node *node;
+
+	if (!ni->vfs_inode.i_nlink)
+		ni_delete_all(ni);
+
+	al_destroy(ni);
+
+	for (node = rb_first(&ni->mi_tree); node;) {
+		struct rb_node *next = rb_next(node);
+		mft_inode *mi = rb_entry(node, mft_inode, node);
+
+		rb_erase(node, &ni->mi_tree);
+		mi_put(mi);
+		node = next;
+	}
+
+	/* bad inode always has mode == S_IFREG */
+	if (ni->ni_flags & NI_FLAG_DIR)
+		indx_clear(&ni->dir);
+	else
+		run_close(&ni->file.run);
+
+	mi_clear(&ni->mi);
+}
+
+/*
+ * ni_load_mi_ex
+ *
+ * finds mft_inode by record number.
+ */
+int ni_load_mi_ex(ntfs_inode *ni, CLST rno, mft_inode **mi)
+{
+	int err;
+	mft_inode *r;
+
+	r = ni_find_mi(ni, rno);
+	if (r)
+		goto out;
+
+	err = mi_get(ni->mi.sbi, rno, &r);
+	if (err)
+		return err;
+
+	ni_add_mi(ni, r);
+
+out:
+	if (mi)
+		*mi = r;
+	return 0;
+}
+
+/*
+ * ni_load_mi
+ *
+ * load mft_inode corresponded list_entry
+ */
+int ni_load_mi(ntfs_inode *ni, ATTR_LIST_ENTRY *le, mft_inode **mi)
+{
+	CLST rno;
+
+	if (!le) {
+		*mi = &ni->mi;
+		return 0;
+	}
+
+	rno = ino_get(&le->ref);
+	if (rno == ni->mi.rno) {
+		*mi = &ni->mi;
+		return 0;
+	}
+	return ni_load_mi_ex(ni, rno, mi);
+}
+
+/*
+ * ni_find_attr
+ *
+ * returns attribute and record this attribute belongs to
+ */
+ATTRIB *ni_find_attr(ntfs_inode *ni, ATTRIB *attr, ATTR_LIST_ENTRY **le_o,
+		     ATTR_TYPE type, const __le16 *name, u8 name_len,
+		     const CLST *vcn, mft_inode **mi)
+{
+	ATTR_LIST_ENTRY *le;
+	mft_inode *m;
+
+	if (!ni->attr_list.size ||
+	    (!name_len && (type == ATTR_LIST || type == ATTR_STD))) {
+		if (le_o)
+			*le_o = NULL;
+		if (mi)
+			*mi = &ni->mi;
+
+		/* Look for required attribute in primary record */
+		return mi_find_attr(&ni->mi, attr, type, name, name_len, NULL);
+	}
+
+	/* first look for list entry of required type */
+	le = al_find_ex(ni, le_o ? *le_o : NULL, type, name, name_len, vcn);
+	if (!le)
+		return NULL;
+
+	if (le_o)
+		*le_o = le;
+
+	/* Load record that contains this attribute */
+	if (ni_load_mi(ni, le, &m))
+		return NULL;
+
+	/* Look for required attribute */
+	attr = mi_find_attr(m, NULL, type, name, name_len, &le->id);
+
+	if (!attr)
+		goto out;
+
+	if (!attr->non_res) {
+		if (vcn && *vcn)
+			goto out;
+	} else if (!vcn) {
+		if (attr->nres.svcn)
+			goto out;
+	} else if (le64_to_cpu(attr->nres.svcn) > *vcn ||
+		   *vcn > le64_to_cpu(attr->nres.evcn)) {
+		goto out;
+	}
+
+	if (mi)
+		*mi = m;
+	return attr;
+
+out:
+	ntfs_set_state(ni->mi.sbi, NTFS_DIRTY_ERROR);
+	return NULL;
+}
+
+/*
+ * ni_enum_attr_ex
+ *
+ * enumerates attributes in ntfs_inode
+ */
+ATTRIB *ni_enum_attr_ex(ntfs_inode *ni, ATTRIB *attr, ATTR_LIST_ENTRY **le)
+{
+	mft_inode *mi;
+	ATTR_LIST_ENTRY *le2;
+
+	/* Do we have an attribute list? */
+	if (!ni->attr_list.size) {
+		*le = NULL;
+		/* Enum attributes in primary record */
+		return mi_enum_attr(&ni->mi, attr);
+	}
+
+	/* get next list entry */
+	le2 = *le = al_enumerate(ni, attr ? *le : NULL);
+	if (!le2)
+		return NULL;
+
+	/* Load record that contains the required atribute */
+	if (ni_load_mi(ni, le2, &mi))
+		return NULL;
+
+	/* Find attribute in loaded record */
+	attr = rec_find_attr_le(mi, le2);
+
+	return attr;
+}
+
+/*
+ * ni_load_attr
+ *
+ * loads attribute that contains given vcn
+ */
+ATTRIB *ni_load_attr(ntfs_inode *ni, ATTR_TYPE type, const __le16 *name,
+		     u8 name_len, CLST vcn, mft_inode **pmi)
+{
+	ATTR_LIST_ENTRY *le;
+	ATTRIB *attr;
+	mft_inode *mi;
+	ATTR_LIST_ENTRY *next;
+
+	if (!ni->attr_list.size) {
+		if (pmi)
+			*pmi = &ni->mi;
+		return mi_find_attr(&ni->mi, NULL, type, name, name_len, NULL);
+	}
+
+	le = al_find_ex(ni, NULL, type, name, name_len, NULL);
+
+	if (!le)
+		return NULL;
+
+	/*
+	 * Unfortunately ATTR_LIST_ENTRY contains only start vcn
+	 * So to find the ATTRIB segment that contains Vcn we should
+	 * enumerate some entries
+	 */
+	if (!vcn)
+		goto load_rec;
+
+next_le:
+	next = al_find_ex(ni, le, type, name, name_len, NULL);
+	if (!next || le64_to_cpu(next->vcn) > vcn)
+		goto load_rec;
+
+	le = next;
+	goto next_le;
+
+load_rec:
+	if (ni_load_mi(ni, le, &mi))
+		return NULL;
+
+	if (pmi)
+		*pmi = mi;
+
+	attr = mi_find_attr(mi, NULL, type, name, name_len, &le->id);
+	if (!attr)
+		return NULL;
+
+	if (!attr->non_res)
+		return attr;
+
+	if (le64_to_cpu(attr->nres.svcn) <= vcn &&
+	    vcn <= le64_to_cpu(attr->nres.evcn))
+		return attr;
+
+	return NULL;
+}
+
+/*
+ * ni_load_all_mi
+ *
+ * loads all subrecords
+ */
+int ni_load_all_mi(ntfs_inode *ni)
+{
+	int err;
+	ATTR_LIST_ENTRY *le;
+
+	if (!ni->attr_list.size)
+		return 0;
+
+	le = NULL;
+
+	while ((le = al_enumerate(ni, le))) {
+		CLST rno = ino_get(&le->ref);
+
+		if (rno == ni->mi.rno)
+			continue;
+
+		err = ni_load_mi_ex(ni, rno, NULL);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+/*
+ * ni_add_subrecord
+ *
+ * allocate + format + attach a new subrecord
+ */
+bool ni_add_subrecord(ntfs_inode *ni, CLST rno, mft_inode **mi)
+{
+	mft_inode *m;
+
+	m = ntfs_alloc(sizeof(mft_inode), 1);
+	if (!m)
+		return false;
+
+	if (mi_format_new(m, ni->mi.sbi, rno, 0, ni->mi.rno == MFT_REC_MFT)) {
+		mi_put(m);
+		return false;
+	}
+
+	get_mi_ref(&ni->mi, &m->mrec->parent_ref);
+
+	ni_add_mi(ni, m);
+	*mi = m;
+	return true;
+}
+
+/*
+ * ni_remove_attr
+ *
+ * removes all attributes for the given type/name/id
+ */
+int ni_remove_attr(ntfs_inode *ni, ATTR_TYPE type, const __le16 *name,
+		   size_t name_len, bool base_only, const __le16 *id)
+{
+	int err;
+	ATTRIB *attr;
+	ATTR_LIST_ENTRY *le;
+	mft_inode *mi;
+	u32 type_in;
+	int diff;
+
+	if (base_only || type == ATTR_LIST || !ni->attr_list.size) {
+		attr = mi_find_attr(&ni->mi, NULL, type, name, name_len, id);
+		if (!attr)
+			return -ENOENT;
+
+		mi_remove_attr(&ni->mi, attr);
+		return 0;
+	}
+
+	type_in = le32_to_cpu(type);
+	le = NULL;
+
+	for (;;) {
+		le = al_enumerate(ni, le);
+		if (!le)
+			return 0;
+
+next_le2:
+		diff = le32_to_cpu(le->type) - type_in;
+		if (diff < 0)
+			continue;
+
+		if (diff > 0)
+			return 0;
+
+		if (le->name_len != name_len)
+			continue;
+
+		if (name_len &&
+		    memcmp(le_name(le), name, name_len * sizeof(short)))
+			continue;
+
+		if (id && le->id != *id)
+			continue;
+		err = ni_load_mi(ni, le, &mi);
+		if (err)
+			return err;
+
+		al_remove_le(ni, le);
+
+		attr = mi_find_attr(mi, NULL, type, name, name_len, id);
+		if (!attr)
+			return -ENOENT;
+
+		mi_remove_attr(mi, attr);
+
+		if (PtrOffset(ni->attr_list.le, le) >= ni->attr_list.size)
+			return 0;
+		goto next_le2;
+	}
+}
+
+/*
+ * ni_ins_new_attr
+ *
+ * inserts the attribute into record
+ * Returns not full constructed attribute or NULL if not possible to create
+ */
+static ATTRIB *ni_ins_new_attr(ntfs_inode *ni, mft_inode *mi,
+			       ATTR_LIST_ENTRY *le, ATTR_TYPE type,
+			       const __le16 *name, u8 name_len, u32 asize,
+			       u16 name_off, CLST svcn)
+{
+	int err;
+	ATTRIB *attr;
+	bool le_added = false;
+	MFT_REF ref;
+
+	get_mi_ref(mi, &ref);
+
+	if (type != ATTR_LIST && !le && ni->attr_list.size) {
+		err = al_add_le(ni, type, name, name_len, svcn, cpu_to_le16(-1),
+				&ref, &le);
+		if (err)
+			return NULL;
+		le_added = true;
+
+		/*
+		 * al_add_le -> attr_set_size (list) -> ni_expand_list
+		 * which moves some attributes out of primary record
+		 * this means that name may point into moved memory
+		 * reinit 'name' from le
+		 */
+		name = le->name;
+	}
+
+	attr = mi_insert_attr(mi, type, name, name_len, asize, name_off);
+	if (!attr) {
+		if (le_added)
+			al_remove_le(ni, le);
+		return NULL;
+	}
+
+	if (type == ATTR_LIST)
+		goto out;
+
+	if (!le)
+		goto out;
+
+	/* Update ATTRIB Id and record reference */
+	le->id = attr->id;
+	ni->attr_list.dirty = true;
+	le->ref = ref;
+
+out:
+
+	return attr;
+}
+
+/*
+ * ni_create_attr_list
+ *
+ * generates an attribute list for this primary record
+ */
+int ni_create_attr_list(ntfs_inode *ni)
+{
+	ntfs_sb_info *sbi = ni->mi.sbi;
+	int err;
+	u32 lsize;
+	ATTRIB *attr;
+	ATTRIB *arr_move[3];
+	ATTR_LIST_ENTRY *le, *le_b[3];
+	MFT_REC *rec;
+	bool is_mft;
+	CLST rno = 0;
+	mft_inode *mi;
+	u32 free_b, nb, to_free, rs;
+	u16 sz;
+
+	is_mft = ni->mi.rno == MFT_REC_MFT;
+	rec = ni->mi.mrec;
+	rs = sbi->record_size;
+
+	/*
+	 * Skip estimating exact memory requirement
+	 * Looks like one record_size is always enough
+	 */
+	le = ntfs_alloc(al_aligned(rs), 0);
+	if (!le) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	get_mi_ref(&ni->mi, &le->ref);
+	ni->attr_list.le = le;
+
+	attr = NULL;
+	nb = 0;
+	free_b = 0;
+	attr = NULL;
+
+	for (; (attr = mi_enum_attr(&ni->mi, attr)); le = Add2Ptr(le, sz)) {
+		sz = le_size(attr->name_len);
+		WARN_ON(PtrOffset(ni->attr_list.le, le) + sz > rs);
+
+		le->type = attr->type;
+		le->size = cpu_to_le16(sz);
+		le->name_len = attr->name_len;
+		le->name_off = offsetof(ATTR_LIST_ENTRY, name);
+		le->vcn = 0;
+		if (le != ni->attr_list.le)
+			le->ref = ((ATTR_LIST_ENTRY *)ni->attr_list.le)->ref;
+		le->id = attr->id;
+
+		if (attr->name_len)
+			memcpy(le->name, attr_name(attr),
+			       sizeof(short) * attr->name_len);
+		else if (attr->type == ATTR_STD)
+			continue;
+		else if (attr->type == ATTR_LIST)
+			continue;
+		else if (is_mft && attr->type == ATTR_DATA)
+			continue;
+
+		if (!nb || nb < ARRAY_SIZE(arr_move)) {
+			le_b[nb] = le;
+			arr_move[nb++] = attr;
+			free_b += le32_to_cpu(attr->size);
+		}
+	}
+
+	lsize = PtrOffset(ni->attr_list.le, le);
+	ni->attr_list.size = lsize;
+
+	to_free = le32_to_cpu(rec->used) + lsize + SIZEOF_RESIDENT;
+	if (to_free <= rs)
+		to_free = 0;
+	else {
+		to_free -= rs;
+
+		if (to_free > free_b) {
+			err = -EINVAL;
+			goto out1;
+		}
+	}
+
+	/* Allocate child mft. */
+	err = ntfs_look_free_mft(sbi, &rno, is_mft, ni, &mi);
+	if (err)
+		goto out1;
+
+	/* Call 'mi_remove_attr' in reverse order to keep pointers 'arr_move' valid */
+	while (to_free > 0) {
+		ATTRIB *b = arr_move[--nb];
+		u32 asize = le32_to_cpu(b->size);
+		u16 name_off = le16_to_cpu(b->name_off);
+
+		attr = mi_insert_attr(mi, b->type, Add2Ptr(b, name_off),
+				      b->name_len, asize, name_off);
+		WARN_ON(!attr);
+
+		get_mi_ref(mi, &le_b[nb]->ref);
+		le_b[nb]->id = attr->id;
+
+		/* copy all except id */
+		memcpy(attr, b, asize);
+		attr->id = le_b[nb]->id;
+
+		WARN_ON(!mi_remove_attr(&ni->mi, b));
+
+		if (to_free <= asize)
+			break;
+		to_free -= asize;
+		WARN_ON(!nb);
+	}
+
+	attr = mi_insert_attr(&ni->mi, ATTR_LIST, NULL, 0,
+			      lsize + SIZEOF_RESIDENT, SIZEOF_RESIDENT);
+	WARN_ON(!attr);
+
+	attr->non_res = 0;
+	attr->flags = 0;
+	attr->res.data_size = cpu_to_le32(lsize);
+	attr->res.data_off = SIZEOF_RESIDENT_LE;
+	attr->res.flags = 0;
+	attr->res.res = 0;
+
+	memcpy(resident_data_ex(attr, lsize), ni->attr_list.le, lsize);
+
+	ni->attr_list.dirty = false;
+
+	mark_inode_dirty(&ni->vfs_inode);
+	goto out;
+
+out1:
+	ntfs_free(ni->attr_list.le);
+	ni->attr_list.le = NULL;
+	ni->attr_list.size = 0;
+
+out:
+	return err;
+}
+
+/*
+ * ni_ins_attr_ext
+ *
+ * This method adds an external attribute to the ntfs_inode.
+ */
+static int ni_ins_attr_ext(ntfs_inode *ni, ATTR_LIST_ENTRY *le, ATTR_TYPE type,
+			   const __le16 *name, u8 name_len, u32 asize,
+			   CLST svcn, u16 name_off, bool force_ext,
+			   ATTRIB **ins_attr, mft_inode **ins_mi)
+{
+	ATTRIB *attr;
+	mft_inode *mi;
+	MFT_REC *rec;
+	CLST rno;
+	u64 vbo;
+	struct rb_node *node;
+	int err;
+	bool is_mft, is_mft_data;
+	ntfs_sb_info *sbi = ni->mi.sbi;
+
+	rec = ni->mi.mrec;
+	is_mft = ni->mi.rno == MFT_REC_MFT;
+	is_mft_data = is_mft && type == ATTR_DATA && !name_len;
+
+	if (asize > sbi->max_bytes_per_attr) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * standard information and attr_list cannot be made external.
+	 * The Log File cannot have any external attributes
+	 */
+	if (type == ATTR_STD || type == ATTR_LIST ||
+	    ni->mi.rno == MFT_REC_LOG) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	/* Create attribute list if it is not already existed */
+	if (!ni->attr_list.size) {
+		err = ni_create_attr_list(ni);
+		if (err)
+			goto out;
+	}
+
+	vbo = is_mft_data ? (svcn << sbi->cluster_bits) : 0;
+
+	if (force_ext)
+		goto insert_ext;
+
+	/* Load all subrecords into memory. */
+	err = ni_load_all_mi(ni);
+	if (err)
+		goto out;
+
+	/* Check each of loaded subrecord */
+	for (node = rb_first(&ni->mi_tree); node; node = rb_next(node)) {
+		mi = rb_entry(node, mft_inode, node);
+
+		if (is_mft_data &&
+		    (mi_enum_attr(mi, NULL) ||
+		     vbo <= ((u64)mi->rno << sbi->record_bits))) {
+			/* We can't accept this record 'case MFT's bootstrapping */
+			continue;
+		}
+		if (is_mft &&
+		    mi_find_attr(mi, NULL, ATTR_DATA, NULL, 0, NULL)) {
+			/*
+			 * This child record already has a ATTR_DATA.
+			 * So it can't accept any other records.
+			 */
+			continue;
+		}
+
+		if ((type != ATTR_NAME || name_len) &&
+		    mi_find_attr(mi, NULL, type, name, name_len, NULL)) {
+			/* Only indexed attributes can share same record */
+			continue;
+		}
+
+		/* Try to insert attribute into this subrecord */
+		attr = ni_ins_new_attr(ni, mi, le, type, name, name_len, asize,
+				       name_off, svcn);
+		if (!attr)
+			continue;
+
+		if (ins_attr)
+			*ins_attr = attr;
+		return 0;
+	}
+
+insert_ext:
+	/* We have to allocate a new child subrecord*/
+	err = ntfs_look_free_mft(sbi, &rno, is_mft_data, ni, &mi);
+	if (err)
+		goto out;
+
+	if (is_mft_data && vbo <= ((u64)rno << sbi->record_bits)) {
+		err = -EINVAL;
+		goto out1;
+	}
+
+	attr = ni_ins_new_attr(ni, mi, le, type, name, name_len, asize,
+			       name_off, svcn);
+	if (!attr)
+		goto out2;
+
+	if (ins_attr)
+		*ins_attr = attr;
+	if (ins_mi)
+		*ins_mi = mi;
+
+	return 0;
+
+out2:
+	ni_remove_mi(ni, mi);
+	mi_put(mi);
+	err = -EINVAL;
+
+out1:
+	ntfs_mark_rec_free(sbi, rno);
+
+out:
+	return err;
+}
+
+/*
+ * ni_insert_attr
+ *
+ * inserts an attribute into the file.
+ *
+ * If the primary record has room, it will just insert the attribute.
+ * If not, it may make the attribute external.
+ * For $MFT::Data it may make room for the attribute by
+ * making other attributes external.
+ *
+ * NOTE:
+ * The ATTR_LIST and ATTR_STD cannot be made external.
+ * This function does not fill new attribute full
+ * It only fills 'size'/'type'/'id'/'name_len' fields
+ */
+static int ni_insert_attr(ntfs_inode *ni, ATTR_TYPE type, const __le16 *name,
+			  u8 name_len, u32 asize, u16 name_off, CLST svcn,
+			  ATTRIB **ins_attr, mft_inode **ins_mi)
+{
+	ntfs_sb_info *sbi = ni->mi.sbi;
+	int err;
+	ATTRIB *attr, *eattr;
+	MFT_REC *rec;
+	bool is_mft;
+	ATTR_LIST_ENTRY *le;
+	u32 list_reserve, max_free, free, used, t32;
+	__le16 id;
+	u16 t16;
+
+	is_mft = ni->mi.rno == MFT_REC_MFT;
+	rec = ni->mi.mrec;
+
+	list_reserve = SIZEOF_NONRESIDENT + 3 * (1 + 2 * sizeof(u32));
+	used = le32_to_cpu(rec->used);
+	free = sbi->record_size - used;
+
+	if (is_mft && type != ATTR_LIST) {
+		/* Reserve space for the ATTRIB List. */
+		if (free < list_reserve)
+			free = 0;
+		else
+			free -= list_reserve;
+	}
+
+	if (asize > free)
+		goto insert_ext;
+
+	attr = ni_ins_new_attr(ni, &ni->mi, NULL, type, name, name_len, asize,
+			       name_off, svcn);
+	if (attr) {
+		if (ins_attr)
+			*ins_attr = attr;
+		if (ins_mi)
+			*ins_mi = &ni->mi;
+		err = 0;
+		goto out;
+	}
+
+insert_ext:
+	if (!is_mft || type != ATTR_DATA || svcn) {
+		/* This ATTRIB will be external. */
+		err = ni_ins_attr_ext(ni, NULL, type, name, name_len, asize,
+				      svcn, name_off, false, ins_attr, ins_mi);
+		goto out;
+	}
+
+	/*
+	 * Here we have: "is_mft && type == ATTR_DATA && !svcn
+	 *
+	 * The first chunk of the $MFT::Data ATTRIB must be the base record.
+	 * Evict as many other attributes as possible.
+	 */
+	max_free = free;
+
+	/* Estimate the result of moving all possible attributes away.*/
+	attr = NULL;
+
+	while ((attr = mi_enum_attr(&ni->mi, attr))) {
+		if (attr->type == ATTR_STD)
+			continue;
+		if (attr->type == ATTR_LIST)
+			continue;
+		max_free += le32_to_cpu(attr->size);
+	}
+
+	if (max_free < asize + list_reserve) {
+		/* Impossible to insert this attribute into primary record */
+		err = -EINVAL;
+		goto out;
+	}
+
+	/* Start real attribute moving */
+	attr = NULL;
+next_move:
+
+	attr = mi_enum_attr(&ni->mi, attr);
+	if (!attr) {
+		/* We should never be here 'cause we have already check this case */
+		err = -EINVAL;
+		goto out;
+	}
+
+	/* Skip attributes that MUST be primary record */
+	if (attr->type == ATTR_STD)
+		goto next_move;
+	if (attr->type == ATTR_LIST)
+		goto next_move;
+
+	le = NULL;
+	if (ni->attr_list.size) {
+		le = al_find_le(ni, NULL, attr);
+		if (!le) {
+			/* Really this is a serious bug */
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+	t32 = le32_to_cpu(attr->size);
+	t16 = le16_to_cpu(attr->name_off);
+	err = ni_ins_attr_ext(ni, le, attr->type, Add2Ptr(attr, t16),
+			      attr->name_len, t32, attr_svcn(attr), t16, false,
+			      &eattr, NULL);
+	if (err)
+		return err;
+
+	id = eattr->id;
+	memcpy(eattr, attr, t32);
+	eattr->id = id;
+
+	/* remove attrib from primary record */
+	mi_remove_attr(&ni->mi, attr);
+
+	/* attr now points to next attribute */
+	if (attr->type == ATTR_END)
+		goto out;
+
+	/* Try to insert when the free space is enough */
+	if (asize + list_reserve > sbi->record_size - le32_to_cpu(rec->used))
+		goto next_move;
+
+	attr = ni_ins_new_attr(ni, &ni->mi, NULL, type, name, name_len, asize,
+			       name_off, svcn);
+	if (!attr) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (ins_attr)
+		*ins_attr = attr;
+	if (ins_mi)
+		*ins_mi = &ni->mi;
+
+out:
+	return err;
+}
+
+/*
+ * ni_expand_mft_list
+ *
+ * This method splits ATTR_DATA of $MFT
+ */
+static int ni_expand_mft_list(ntfs_inode *ni)
+{
+	int err = 0;
+	struct runs_tree *run = &ni->file.run;
+	u32 asize, run_size, done = 0;
+	ATTRIB *attr;
+	struct rb_node *node;
+	CLST mft_min, mft_new, svcn, evcn, plen;
+	mft_inode *mi, *mi_min, *mi_new;
+	ntfs_sb_info *sbi = ni->mi.sbi;
+
+	/* Find the nearest Mft */
+	mft_min = 0;
+	mft_new = 0;
+	mi_min = NULL;
+
+	for (node = rb_first(&ni->mi_tree); node; node = rb_next(node)) {
+		mi = rb_entry(node, mft_inode, node);
+
+		attr = mi_enum_attr(mi, NULL);
+
+		if (!attr) {
+			mft_min = mi->rno;
+			mi_min = mi;
+			break;
+		}
+	}
+
+	if (ntfs_look_free_mft(sbi, &mft_new, true, ni, &mi_new)) {
+		mft_new = 0;
+		// really this is not critical
+	} else if (mft_min > mft_new) {
+		mft_min = mft_new;
+		mi_min = mi_new;
+	} else {
+		ntfs_mark_rec_free(sbi, mft_new);
+		mft_new = 0;
+		ni_remove_mi(ni, mi_new);
+	}
+
+	attr = mi_find_attr(&ni->mi, NULL, ATTR_DATA, NULL, 0, NULL);
+	if (!attr) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	asize = le32_to_cpu(attr->size);
+
+	evcn = le64_to_cpu(attr->nres.evcn);
+	svcn = bytes_to_cluster(sbi, (u64)(mft_min + 1) << sbi->record_bits);
+	if (evcn + 1 >= svcn) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * split primary attribute [0 evcn] in two parts [0 svcn) + [svcn evcn]
+	 *
+	 * Update first part of ATTR_DATA in 'primary MFT
+	 */
+	err = run_pack(run, 0, svcn, Add2Ptr(attr, SIZEOF_NONRESIDENT),
+		       asize - SIZEOF_NONRESIDENT, &plen);
+	if (err < 0)
+		goto out;
+
+	run_size = QuadAlign(err);
+	err = 0;
+
+	if (plen < svcn) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	attr->nres.evcn = cpu_to_le64(svcn - 1);
+	attr->size = cpu_to_le32(run_size + SIZEOF_NONRESIDENT);
+	/* 'done' - how many bytes of primary MFT becomes free */
+	done = asize - run_size - SIZEOF_NONRESIDENT;
+	le32_sub_cpu(&ni->mi.mrec->used, done);
+
+	/* Estimate the size of second part: run_buf=NULL */
+	err = run_pack(run, svcn, evcn + 1 - svcn, NULL, sbi->record_size,
+		       &plen);
+	if (err < 0)
+		goto out;
+
+	run_size = QuadAlign(err);
+	err = 0;
+
+	if (plen < evcn + 1 - svcn) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * This function may implicitly call expand attr_list
+	 * Insert second part of ATTR_DATA in 'mi_min'
+	 */
+	attr = ni_ins_new_attr(ni, mi_min, NULL, ATTR_DATA, NULL, 0,
+			       SIZEOF_NONRESIDENT + run_size,
+			       SIZEOF_NONRESIDENT, svcn);
+	if (!attr) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	attr->non_res = 1;
+	attr->name_off = SIZEOF_NONRESIDENT_LE;
+	attr->flags = 0;
+
+	run_pack(run, svcn, evcn + 1 - svcn, Add2Ptr(attr, SIZEOF_NONRESIDENT),
+		 run_size, &plen);
+
+	attr->nres.svcn = cpu_to_le64(svcn);
+	attr->nres.evcn = cpu_to_le64(evcn);
+	attr->nres.run_off = cpu_to_le16(SIZEOF_NONRESIDENT);
+
+out:
+	if (mft_new) {
+		ntfs_mark_rec_free(sbi, mft_new);
+		ni_remove_mi(ni, mi_new);
+	}
+
+	return !err && !done ? -EOPNOTSUPP : err;
+}
+
+/*
+ * ni_expand_list
+ *
+ * This method moves all possible attributes out of primary record
+ */
+int ni_expand_list(ntfs_inode *ni)
+{
+	int err = 0;
+	u32 asize, done = 0;
+	ATTRIB *attr, *ins_attr;
+	ATTR_LIST_ENTRY *le;
+	bool is_mft;
+	MFT_REF ref;
+
+	is_mft = ni->mi.rno == MFT_REC_MFT;
+
+	get_mi_ref(&ni->mi, &ref);
+	le = NULL;
+
+	while ((le = al_enumerate(ni, le))) {
+		if (le->type == ATTR_STD)
+			continue;
+
+		if (memcmp(&ref, &le->ref, sizeof(MFT_REF)))
+			continue;
+
+		if (is_mft && le->type == ATTR_DATA)
+			continue;
+
+		/* Find attribute in primary record */
+		attr = rec_find_attr_le(&ni->mi, le);
+		if (!attr) {
+			err = -EINVAL;
+			goto out;
+		}
+
+		asize = le32_to_cpu(attr->size);
+
+		/* Always insert into new record to avoid collisions (deep recursive) */
+		err = ni_ins_attr_ext(ni, le, attr->type, attr_name(attr),
+				      attr->name_len, asize, attr_svcn(attr),
+				      le16_to_cpu(attr->name_off), true,
+				      &ins_attr, NULL);
+
+		if (err)
+			goto out;
+
+		memcpy(ins_attr, attr, asize);
+		ins_attr->id = le->id;
+		mi_remove_attr(&ni->mi, attr);
+
+		done += asize;
+		goto out;
+	}
+
+	if (!is_mft) {
+		err = -EFBIG; /* attr list is too big(?) */
+		goto out;
+	}
+
+	/* split mft data as much as possible */
+	err = ni_expand_mft_list(ni);
+	if (err)
+		goto out;
+
+out:
+	return !err && !done ? -EOPNOTSUPP : err;
+}
+
+/*
+ * ni_insert_nonresident
+ *
+ * inserts new nonresident attribute
+ */
+int ni_insert_nonresident(ntfs_inode *ni, ATTR_TYPE type, const __le16 *name,
+			  u8 name_len, const struct runs_tree *run, CLST svcn,
+			  CLST len, __le16 flags, ATTRIB **new_attr,
+			  mft_inode **mi)
+{
+	int err;
+	CLST plen;
+	ATTRIB *attr;
+	bool is_ext =
+		(flags & (ATTR_FLAG_SPARSED | ATTR_FLAG_COMPRESSED)) && !svcn;
+	u32 name_size = QuadAlign(name_len * sizeof(short));
+	u32 name_off = is_ext ? SIZEOF_NONRESIDENT_EX : SIZEOF_NONRESIDENT;
+	u32 run_off = name_off + name_size;
+	u32 run_size, asize;
+	ntfs_sb_info *sbi = ni->mi.sbi;
+
+	err = run_pack(run, svcn, len, NULL, sbi->max_bytes_per_attr - run_off,
+		       &plen);
+	if (err < 0)
+		goto out;
+
+	run_size = QuadAlign(err);
+
+	if (plen < len) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	asize = run_off + run_size;
+
+	if (asize > sbi->max_bytes_per_attr) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	err = ni_insert_attr(ni, type, name, name_len, asize, name_off, svcn,
+			     &attr, mi);
+
+	if (err)
+		goto out;
+
+	attr->non_res = 1;
+	attr->name_off = cpu_to_le16(name_off);
+	attr->flags = flags;
+
+	run_pack(run, svcn, len, Add2Ptr(attr, run_off), run_size, &plen);
+
+	attr->nres.svcn = cpu_to_le64(svcn);
+	attr->nres.evcn = cpu_to_le64((u64)svcn + len - 1);
+
+	err = 0;
+	if (new_attr)
+		*new_attr = attr;
+
+	*(__le64 *)&attr->nres.run_off = cpu_to_le64(run_off);
+
+	attr->nres.alloc_size =
+		svcn ? 0 : cpu_to_le64((u64)len << ni->mi.sbi->cluster_bits);
+	attr->nres.data_size = attr->nres.alloc_size;
+	attr->nres.valid_size = attr->nres.alloc_size;
+
+	if (is_ext) {
+		if (flags & ATTR_FLAG_COMPRESSED)
+			attr->nres.c_unit = COMPRESSION_UNIT;
+		attr->nres.total_size = attr->nres.alloc_size;
+	}
+
+out:
+	return err;
+}
+
+/*
+ * ni_insert_resident
+ *
+ * inserts new resident attribute
+ */
+int ni_insert_resident(ntfs_inode *ni, u32 data_size, ATTR_TYPE type,
+		       const __le16 *name, u8 name_len, ATTRIB **new_attr,
+		       mft_inode **mi)
+{
+	int err;
+	u32 name_size = QuadAlign(name_len * sizeof(short));
+	u32 asize = SIZEOF_RESIDENT + name_size + QuadAlign(data_size);
+	ATTRIB *attr;
+
+	err = ni_insert_attr(ni, type, name, name_len, asize, SIZEOF_RESIDENT,
+			     0, &attr, mi);
+	if (err)
+		return err;
+
+	attr->non_res = 0;
+	attr->flags = 0;
+
+	attr->res.data_size = cpu_to_le32(data_size);
+	attr->res.data_off = cpu_to_le16(SIZEOF_RESIDENT + name_size);
+	if (type == ATTR_NAME)
+		attr->res.flags = RESIDENT_FLAG_INDEXED;
+	attr->res.res = 0;
+
+	if (new_attr)
+		*new_attr = attr;
+	return 0;
+}
+
+/*
+ * ni_remove_attr_le
+ *
+ * removes attribute from record
+ */
+int ni_remove_attr_le(ntfs_inode *ni, ATTRIB *attr, ATTR_LIST_ENTRY *le)
+{
+	int err;
+	mft_inode *mi;
+
+	err = ni_load_mi(ni, le, &mi);
+	if (err)
+		return err;
+
+	mi_remove_attr(mi, attr);
+
+	if (le)
+		al_remove_le(ni, le);
+
+	return 0;
+}
+
+/*
+ * ni_delete_all
+ *
+ * removes all attributes and frees allocates space
+ * ntfs_evict_inode->ntfs_clear_inode->ni_delete_all (if no links)
+ */
+int ni_delete_all(ntfs_inode *ni)
+{
+	int err;
+	ATTR_LIST_ENTRY *le = NULL;
+	ATTRIB *attr = NULL;
+	struct rb_node *node;
+	u16 roff;
+	u32 asize;
+	CLST svcn, evcn;
+	ntfs_sb_info *sbi = ni->mi.sbi;
+	bool nt5 = is_nt5(sbi);
+	MFT_REF ref;
+
+next_attr:
+	attr = ni_enum_attr_ex(ni, attr, &le);
+	if (!attr)
+		goto attr_list;
+
+	if (!nt5 || attr->name_len)
+		;
+	else if (attr->type == ATTR_REPARSE) {
+		get_mi_ref(&ni->mi, &ref);
+		err = ntfs_remove_reparse(sbi, 0, &ref);
+	} else if (attr->type == ATTR_ID) {
+		if (!attr->non_res &&
+		    le32_to_cpu(attr->res.data_size) >= sizeof(GUID))
+			err = ntfs_objid_remove(sbi, resident_data(attr));
+	}
+
+	if (!attr->non_res)
+		goto next_attr;
+
+	svcn = le64_to_cpu(attr->nres.svcn);
+	evcn = le64_to_cpu(attr->nres.evcn);
+
+	if (evcn + 1 <= svcn)
+		goto next_attr;
+
+	asize = le32_to_cpu(attr->size);
+	roff = le16_to_cpu(attr->nres.run_off);
+
+	err = run_unpack_ex((struct runs_tree *)(size_t)1, sbi, ni->mi.rno,
+			    svcn, evcn, Add2Ptr(attr, roff), asize - roff);
+	if (err < 0)
+		goto next_attr;
+	err = 0;
+
+	goto next_attr;
+
+attr_list:
+	if (!ni->attr_list.size)
+		goto free_subrecord;
+
+	run_deallocate(ni->mi.sbi, &ni->attr_list.run, true);
+	al_destroy(ni);
+
+free_subrecord:
+	/* Free all subrecords */
+	for (node = rb_first(&ni->mi_tree); node;) {
+		struct rb_node *next = rb_next(node);
+		mft_inode *mi = rb_entry(node, mft_inode, node);
+
+		clear_rec_inuse(mi->mrec);
+		mi->dirty = true;
+		mi_write(mi, 0);
+
+		ntfs_mark_rec_free(sbi, mi->rno);
+		ni_remove_mi(ni, mi);
+		mi_put(mi);
+		node = next;
+	}
+
+	// Free base record
+	clear_rec_inuse(ni->mi.mrec);
+	ni->mi.dirty = true;
+	err = mi_write(&ni->mi, 0);
+	ntfs_mark_rec_free(sbi, ni->mi.rno);
+
+	return err;
+}
+
+/*
+ * ni_fname_name
+ *
+ * returns file name attribute by its value
+ */
+ATTR_FILE_NAME *ni_fname_name(ntfs_inode *ni, const struct cpu_str *uni,
+			      const MFT_REF *home_dir, ATTR_LIST_ENTRY **le)
+{
+	ATTRIB *attr = NULL;
+	ATTR_FILE_NAME *fname;
+
+	*le = NULL;
+
+	/* Enumerate all names */
+next:
+	attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL, NULL);
+	if (!attr)
+		return NULL;
+
+	fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
+	if (!fname)
+		goto next;
+
+	if (home_dir && memcmp(home_dir, &fname->home, sizeof(*home_dir)))
+		goto next;
+
+	if (!uni)
+		goto next;
+
+	if (uni->len != fname->name_len)
+		goto next;
+
+	if (ntfs_cmp_names_cpu(uni, (struct le_str *)&fname->name_len, NULL))
+		goto next;
+
+	return fname;
+}
+
+/*
+ * ni_fname_type
+ *
+ * returns file name attribute with given type
+ */
+ATTR_FILE_NAME *ni_fname_type(ntfs_inode *ni, u8 name_type,
+			      ATTR_LIST_ENTRY **le)
+{
+	ATTRIB *attr = NULL;
+	ATTR_FILE_NAME *fname;
+
+	*le = NULL;
+
+	/* Enumerate all names */
+next_name:
+	attr = ni_find_attr(ni, attr, le, ATTR_NAME, NULL, 0, NULL, NULL);
+	if (!attr)
+		return NULL;
+
+	fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
+	if (fname && name_type == fname->type)
+		return fname;
+	goto next_name;
+}
+
+/*
+ * ni_init_compress
+ *
+ * allocates and fill 'compress_ctx'
+ * used to decompress lzx and xpress
+ */
+int ni_init_compress(ntfs_inode *ni, struct COMPRESS_CTX *ctx)
+{
+	u32 c_format = ((ni->ni_flags & NI_FLAG_COMPRESSED_MASK) >> 8) - 1;
+	u32 chunk_bits;
+
+	switch (c_format) {
+	case WOF_COMPRESSION_XPRESS4K:
+		chunk_bits = 12; // 4k
+		break;
+	case WOF_COMPRESSION_LZX:
+		chunk_bits = 15; // 32k
+		break;
+	case WOF_COMPRESSION_XPRESS8K:
+		chunk_bits = 13; // 8k
+		break;
+	case WOF_COMPRESSION_XPRESS16K:
+		chunk_bits = 14; // 16k
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	ctx->chunk_bits = chunk_bits;
+	ctx->offset_bits = ni->vfs_inode.i_size < 0x100000000ull ?
+				   2 :
+				   3; // 32 or 64 bits per offsets
+
+	ctx->compress_format = c_format;
+	ctx->chunk_size = 1u << chunk_bits;
+	ctx->chunk_num = -1;
+	ctx->first_chunk = -1;
+	ctx->total_chunks = (ni->vfs_inode.i_size - 1) >> chunk_bits;
+	ctx->chunk0_off = ctx->total_chunks << ctx->offset_bits;
+
+	return 0;
+}
+
+/*
+ * ni_parse_reparse
+ *
+ * buffer is at least 24 bytes
+ */
+enum REPARSE_SIGN ni_parse_reparse(ntfs_inode *ni, ATTRIB *attr, void *buffer)
+{
+	const REPARSE_DATA_BUFFER *rp = NULL;
+	u32 c_format;
+	u16 len;
+	typeof(rp->CompressReparseBuffer) *cmpr;
+
+	/* Try to estimate reparse point */
+	if (!attr->non_res) {
+		rp = resident_data_ex(attr, sizeof(REPARSE_DATA_BUFFER));
+	} else if (le64_to_cpu(attr->nres.data_size) >=
+		   sizeof(REPARSE_DATA_BUFFER)) {
+		struct runs_tree run;
+
+		run_init(&run);
+
+		if (!attr_load_runs_vcn(ni, ATTR_REPARSE, NULL, 0, &run, 0) &&
+		    !ntfs_read_run_nb(ni->mi.sbi, &run, 0, buffer,
+				      sizeof(REPARSE_DATA_BUFFER), NULL)) {
+			rp = buffer;
+		}
+
+		run_close(&run);
+	}
+
+	if (!rp)
+		return REPARSE_NONE;
+
+	len = le16_to_cpu(rp->ReparseDataLength);
+	switch (rp->ReparseTag) {
+	case (IO_REPARSE_TAG_MICROSOFT | IO_REPARSE_TAG_SYMBOLIC_LINK):
+		break; /* Symbolic link */
+	case IO_REPARSE_TAG_MOUNT_POINT:
+		break; /* Mount points and junctions */
+	case IO_REPARSE_TAG_SYMLINK:
+		break;
+	case IO_REPARSE_TAG_COMPRESS:
+		cmpr = &rp->CompressReparseBuffer;
+		if (len < sizeof(*cmpr) ||
+		    cmpr->WofVersion != WOF_CURRENT_VERSION ||
+		    cmpr->WofProvider != WOF_PROVIDER_SYSTEM ||
+		    cmpr->ProviderVer != WOF_PROVIDER_CURRENT_VERSION) {
+			return REPARSE_NONE;
+		}
+		c_format = le32_to_cpu(cmpr->CompressionFormat);
+		if (c_format > 3)
+			return REPARSE_NONE;
+
+		ni->ni_flags |= (c_format + 1) << 8;
+		return REPARSE_COMPRESSED;
+
+	case IO_REPARSE_TAG_DEDUP:
+		ni->ni_flags |= NI_FLAG_DEDUPLICATED;
+		return REPARSE_DEDUPLICATED;
+
+	default:
+		if (rp->ReparseTag & IO_REPARSE_TAG_NAME_SURROGATE)
+			break;
+
+		return REPARSE_NONE;
+	}
+
+	/* Looks like normal symlink */
+	return REPARSE_LINK;
+}
+
+/*
+ * When decompressing, we typically obtain more than one page per reference.
+ * We inject the additional pages into the page cache.
+ */
+int ni_readpage_cmpr(ntfs_inode *ni, struct page *page)
+{
+	int err;
+	ntfs_sb_info *sbi = ni->mi.sbi;
+	struct address_space *mapping = page->mapping;
+	ATTR_LIST_ENTRY *le;
+	ATTRIB *attr;
+	u8 frame_bits;
+	u32 frame_size, i, idx;
+	CLST frame, clst_data;
+	struct page *pg;
+	pgoff_t index = page->index, end_index;
+	u64 vbo = (u64)index << PAGE_SHIFT;
+	u32 pages_per_frame = 0;
+	struct page **pages = NULL;
+	char *frame_buf = NULL;
+	char *frame_unc;
+	u32 cmpr_size, unc_size;
+	u64 frame_vbo, valid_size;
+	size_t unc_size_fin;
+	struct COMPRESS_CTX *ctx = NULL;
+	bool is_compr = false;
+
+	end_index = (ni->vfs_inode.i_size + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+	if (index >= end_index) {
+		SetPageUptodate(page);
+		err = 0;
+		goto out;
+	}
+
+	le = NULL;
+	attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, NULL);
+	if (!attr) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	WARN_ON(!attr->non_res);
+
+	if (ni->ni_flags & NI_FLAG_COMPRESSED_MASK) {
+		ctx = ntfs_alloc(sizeof(*ctx), 1);
+		if (!ctx) {
+			err = -ENOMEM;
+			goto out;
+		}
+		err = ni_init_compress(ni, ctx);
+		if (err)
+			goto out;
+
+		frame_bits = ctx->chunk_bits;
+		frame_size = ctx->chunk_size;
+		frame = vbo >> frame_bits;
+		frame_vbo = (u64)frame << frame_bits;
+
+		/* TODO: port lzx/xpress */
+		err = -EOPNOTSUPP;
+		goto out;
+	} else if (is_attr_compressed(attr)) {
+		if (sbi->cluster_size > NTFS_LZNT_MAX_CLUSTER) {
+			err = -EOPNOTSUPP;
+			goto out;
+		}
+
+		if (attr->nres.c_unit != NTFS_LZNT_CUNIT) {
+			err = -EOPNOTSUPP;
+			goto out;
+		}
+
+		frame_bits = 4 + sbi->cluster_bits;
+		frame_size = 16 << sbi->cluster_bits;
+		frame = vbo >> frame_bits;
+		frame_vbo = (u64)frame << frame_bits;
+
+		err = attr_is_frame_compressed(ni, attr, frame, &clst_data,
+					       &is_compr);
+		if (err)
+			goto out;
+	} else {
+		WARN_ON(1);
+		err = -EINVAL;
+		goto out;
+	}
+
+	pages_per_frame = frame_size >> PAGE_SHIFT;
+	pages = ntfs_alloc(pages_per_frame * sizeof(*pages), 1);
+	if (!pages) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	idx = (vbo - frame_vbo) >> PAGE_SHIFT;
+	pages[idx] = page;
+	index = frame_vbo >> PAGE_SHIFT;
+	kmap(page);
+
+	for (i = 0; i < pages_per_frame && index < end_index; i++, index++) {
+		if (i == idx)
+			continue;
+
+		pg = grab_cache_page_nowait(mapping, index);
+		if (!pg)
+			continue;
+
+		pages[i] = pg;
+		if (!PageDirty(pg) && (!PageUptodate(pg) || PageError(pg)))
+			ClearPageError(pg);
+		kmap(pg);
+	}
+
+	valid_size = ni->i_valid;
+
+	if (frame_vbo >= valid_size || !clst_data) {
+		for (i = 0; i < pages_per_frame; i++) {
+			pg = pages[i];
+			if (!pg || PageDirty(pg) ||
+			    (PageUptodate(pg) && !PageError(pg)))
+				continue;
+
+			memset(page_address(pg), 0, PAGE_SIZE);
+			flush_dcache_page(pg);
+			SetPageUptodate(pg);
+		}
+		err = 0;
+		goto out1;
+	}
+
+	unc_size = frame_vbo + frame_size > valid_size ?
+			   (valid_size - frame_vbo) :
+			   frame_size;
+
+	/* read 'clst_data' clusters from disk */
+	cmpr_size = clst_data << sbi->cluster_bits;
+	frame_buf = ntfs_alloc(cmpr_size, 0);
+	if (!frame_buf) {
+		err = -ENOMEM;
+		goto out1;
+	}
+
+	err = ntfs_read_run_nb(sbi, &ni->file.run, frame_vbo, frame_buf,
+			       cmpr_size, NULL);
+	if (err)
+		goto out2;
+
+	spin_lock(&sbi->compress.lock);
+	frame_unc = sbi->compress.frame_unc;
+
+	if (!is_compr) {
+		unc_size_fin = unc_size;
+		frame_unc = frame_buf;
+	} else {
+		/* decompress: frame_buf -> frame_unc */
+		unc_size_fin = decompress_lznt(frame_buf, cmpr_size, frame_unc,
+					       frame_size);
+		if ((ssize_t)unc_size_fin < 0) {
+			err = unc_size_fin;
+			goto out3;
+		}
+
+		if (!unc_size_fin || unc_size_fin > frame_size) {
+			err = -EINVAL;
+			goto out3;
+		}
+	}
+
+	for (i = 0; i < pages_per_frame; i++) {
+		u8 *pa;
+		u32 use, done;
+		loff_t vbo;
+
+		pg = pages[i];
+		if (!pg)
+			continue;
+
+		if (PageDirty(pg) || (PageUptodate(pg) && !PageError(pg)))
+			continue;
+
+		pa = page_address(pg);
+
+		use = 0;
+		done = i * PAGE_SIZE;
+		vbo = frame_vbo + done;
+
+		if (vbo < valid_size && unc_size_fin > done) {
+			use = unc_size_fin - done;
+			if (use > PAGE_SIZE)
+				use = PAGE_SIZE;
+			if (vbo + use > valid_size)
+				use = valid_size - vbo;
+			memcpy(pa, frame_unc + done, use);
+		}
+
+		if (use < PAGE_SIZE)
+			memset(pa + use, 0, PAGE_SIZE - use);
+
+		flush_dcache_page(pg);
+		SetPageUptodate(pg);
+	}
+
+out3:
+	spin_unlock(&sbi->compress.lock);
+
+out2:
+	ntfs_free(frame_buf);
+out1:
+	for (i = 0; i < pages_per_frame; i++) {
+		pg = pages[i];
+		if (i == idx || !pg)
+			continue;
+		kunmap(pg);
+		unlock_page(pg);
+		put_page(pg);
+	}
+
+	if (err)
+		SetPageError(page);
+	kunmap(page);
+
+out:
+	/* At this point, err contains 0 or -EIO depending on the "critical" page */
+	ntfs_free(pages);
+	unlock_page(page);
+
+	ntfs_free(ctx);
+
+	return err;
+}
+
+/*
+ * ni_writepage_cmpr
+ *
+ * helper for ntfs_writepage_cmpr
+ */
+int ni_writepage_cmpr(struct page *page, int sync)
+{
+	int err;
+	struct address_space *mapping = page->mapping;
+	struct inode *inode = mapping->host;
+	loff_t i_size = i_size_read(inode);
+	ntfs_inode *ni = ntfs_i(inode);
+	ntfs_sb_info *sbi = ni->mi.sbi;
+	pgoff_t index = page->index, end_index;
+	u64 vbo = (u64)index << PAGE_SHIFT;
+	u32 pages_per_frame = 0;
+	struct page **pages = NULL;
+	char *frame_buf = NULL;
+	ATTR_LIST_ENTRY *le;
+	ATTRIB *attr;
+	u8 frame_bits;
+	u32 frame_size, i, idx, unc_size;
+	CLST frame;
+	struct page *pg;
+	char *frame_unc;
+	u64 frame_vbo;
+	size_t cmpr_size_fin, cmpr_size_clst;
+	gfp_t mask;
+
+	end_index = (i_size + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+	if (index >= end_index) {
+		SetPageUptodate(page);
+		err = 0;
+		goto out;
+	}
+
+	le = NULL;
+	attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, NULL);
+	if (!attr) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	if (!attr->non_res) {
+		WARN_ON(1);
+		err = 0;
+		goto out;
+	}
+
+	if (!is_attr_compressed(attr)) {
+		WARN_ON(1);
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (sbi->cluster_size > NTFS_LZNT_MAX_CLUSTER) {
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	if (attr->nres.c_unit != NTFS_LZNT_CUNIT) {
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	frame_bits = NTFS_LZNT_CUNIT + sbi->cluster_bits;
+	frame_size = sbi->cluster_size << NTFS_LZNT_CUNIT;
+	frame = vbo >> frame_bits;
+	frame_vbo = (u64)frame << frame_bits;
+	unc_size = frame_vbo + frame_size > i_size ? (i_size - frame_vbo) :
+						     frame_size;
+
+	frame_buf = ntfs_alloc(frame_size, 0);
+	if (!frame_buf) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	pages_per_frame = frame_size >> PAGE_SHIFT;
+	pages = ntfs_alloc(pages_per_frame * sizeof(*pages), 1);
+	if (!pages) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	idx = (vbo - frame_vbo) >> PAGE_SHIFT;
+	pages[idx] = page;
+	index = frame_vbo >> PAGE_SHIFT;
+	mask = mapping_gfp_mask(mapping);
+	kmap(page);
+
+	for (i = 0; i < pages_per_frame && index < end_index; i++, index++) {
+		if (i == idx)
+			continue;
+
+		// added FGP_CREAT
+		pg = pagecache_get_page(
+			mapping, index,
+			FGP_LOCK | FGP_NOFS | FGP_CREAT | FGP_NOWAIT, mask);
+		if (!pg)
+			continue;
+
+		pages[i] = pg;
+
+		if (PageError(pg)) {
+			err = -EIO;
+			goto out2;
+		}
+
+		if (!PageDirty(pg) && !PageUptodate(pg)) {
+			memset(page_address(pg), 0, PAGE_SIZE);
+			flush_dcache_page(pg);
+			SetPageUptodate(pg);
+		}
+
+		kmap(pg);
+	}
+
+	spin_lock(&sbi->compress.lock);
+	frame_unc = sbi->compress.frame_unc;
+
+	for (i = 0; i < pages_per_frame; i++) {
+		pg = pages[i];
+		if (pg)
+			memcpy(frame_unc + i * PAGE_SIZE, page_address(pg),
+			       PAGE_SIZE);
+		else
+			memset(frame_unc + i * PAGE_SIZE, 0, PAGE_SIZE);
+	}
+
+	/* compress: frame_unc -> frame_buf */
+	cmpr_size_fin = compress_lznt(frame_unc, unc_size, frame_buf,
+				      frame_size, sbi->compress.ctx);
+
+	cmpr_size_clst = ntfs_up_cluster(sbi, cmpr_size_fin);
+	if (cmpr_size_clst + sbi->cluster_size > frame_size) {
+		/* write frame as is */
+		memcpy(frame_buf, frame_unc, frame_size);
+		cmpr_size_fin = frame_size;
+	} else if (cmpr_size_fin) {
+		memset(frame_buf + cmpr_size_fin, 0,
+		       cmpr_size_clst - cmpr_size_fin);
+	}
+	spin_unlock(&sbi->compress.lock);
+
+	err = attr_allocate_frame(ni, frame, cmpr_size_fin, ni->i_valid);
+	if (err)
+		goto out2;
+
+	if (!cmpr_size_clst)
+		goto out2;
+
+	err = ntfs_sb_write_run(sbi, &ni->file.run, frame_vbo, frame_buf,
+				cmpr_size_clst);
+	if (err)
+		goto out2;
+
+out2:
+	ntfs_free(frame_buf);
+	for (i = 0; i < pages_per_frame; i++) {
+		pg = pages[i];
+		if (!pg || i == idx)
+			continue;
+		kunmap(pg);
+		SetPageUptodate(pg);
+		/* clear page dirty so that writepages wouldn't work for us. */
+		ClearPageDirty(pg);
+		unlock_page(pg);
+		put_page(pg);
+	}
+
+	if (err)
+		SetPageError(page);
+	kunmap(page);
+
+out:
+	/* At this point, err contains 0 or -EIO depending on the "critical" page */
+	ntfs_free(pages);
+	set_page_writeback(page);
+	unlock_page(page);
+	end_page_writeback(page);
+
+	return err;
+}
+
+/*
+ * ni_write_inode
+ *
+ * write mft base record and all subrecords to disk
+ */
+int ni_write_inode(struct inode *inode, int sync, const char *hint)
+{
+	int err = 0, err2;
+	ntfs_inode *ni = ntfs_i(inode);
+	struct super_block *sb = inode->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	bool modified, re_dirty = false;
+	mft_inode *mi;
+	MFT_REC *rec;
+	ATTR_STD_INFO *std;
+	struct rb_node *node, *next;
+	ATTR_LIST_ENTRY *le;
+	ATTRIB *attr;
+	NTFS_DUP_INFO dup;
+	bool is_meta;
+
+	if (is_bad_inode(inode))
+		return 0;
+
+	if (!ni_trylock(ni)) {
+		/* 'ni' is under modification, skip for now */
+		return 0;
+	}
+
+	is_meta = ntfs_is_meta_file(sbi, inode->i_ino);
+	rec = ni->mi.mrec;
+
+	if (!is_rec_inuse(rec) || (sbi->flags & NTFS_FLAGS_LOG_REPLAING))
+		goto write_subrecords;
+
+	if (!inode->i_nlink)
+		goto write_subrecords;
+
+	/* update times in standard attribute */
+	std = ni_std(ni);
+	if (!std) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	modified = false;
+	/* Update the access times if they have changed. */
+	dup.m_time = kernel2nt(&inode->i_mtime);
+	if (std->m_time != dup.m_time) {
+		std->m_time = dup.m_time;
+		modified = true;
+	}
+
+	dup.c_time = kernel2nt(&inode->i_ctime);
+	if (std->c_time != dup.c_time) {
+		std->c_time = dup.c_time;
+		modified = true;
+	}
+
+	dup.a_time = kernel2nt(&inode->i_atime);
+	if (std->a_time != dup.a_time) {
+		std->a_time = dup.a_time;
+		modified = true;
+	}
+
+	dup.fa = ni->std_fa;
+	if (std->fa != dup.fa) {
+		std->fa = dup.fa;
+		modified = true;
+	}
+
+	if (modified)
+		ni->mi.dirty = true;
+
+	if (is_meta || (!modified && !(ni->ni_flags & NI_FLAG_UPDATE_PARENT)))
+		goto skip_dir_update;
+
+	dup.cr_time = std->cr_time;
+	le = NULL;
+
+	if (ni->mi.mrec->flags & RECORD_FLAG_DIR) {
+		dup.fa |= FILE_ATTRIBUTE_DIRECTORY;
+		attr = NULL;
+		dup.alloc_size = 0;
+		dup.data_size = 0;
+	} else {
+		dup.fa &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+		attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL,
+				    &mi);
+		if (!attr)
+			dup.alloc_size = dup.data_size = 0;
+		else if (!attr->non_res) {
+			u32 data_size = le32_to_cpu(attr->res.data_size);
+
+			dup.alloc_size = cpu_to_le64(QuadAlign(data_size));
+			dup.data_size = cpu_to_le64(data_size);
+		} else {
+			u64 new_valid = ni->i_valid;
+			u64 data_size = le64_to_cpu(attr->nres.data_size);
+
+			dup.alloc_size = is_attr_ext(attr) ?
+						 attr->nres.total_size :
+						 attr->nres.alloc_size;
+			dup.data_size = attr->nres.data_size;
+
+			if (new_valid > data_size)
+				new_valid = data_size;
+			if (new_valid != le64_to_cpu(attr->nres.valid_size)) {
+				attr->nres.valid_size = cpu_to_le64(new_valid);
+				mi->dirty = true;
+			}
+		}
+	}
+
+	/* TODO: fill reparse info */
+	dup.reparse = 0;
+	dup.ea_size = 0;
+
+	if (ni->ni_flags & NI_FLAG_EA) {
+		attr = ni_find_attr(ni, attr, &le, ATTR_EA_INFO, NULL, 0, NULL,
+				    NULL);
+		if (attr) {
+			const EA_INFO *info =
+				resident_data_ex(attr, sizeof(EA_INFO));
+
+			dup.ea_size = info->size_pack;
+		}
+	}
+
+	attr = NULL;
+	le = NULL;
+
+	while ((attr = ni_find_attr(ni, attr, &le, ATTR_NAME, NULL, 0, NULL,
+				    &mi))) {
+		struct inode *dir;
+		ATTR_FILE_NAME *fname;
+
+		fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
+		if (!fname)
+			continue;
+
+		if (!memcmp(&fname->dup, &dup, sizeof(fname->dup)))
+			continue;
+
+		dir = ntfs_iget5(sb, &fname->home, NULL);
+
+		if (IS_ERR(dir)) {
+			;
+		} else if (is_bad_inode(dir)) {
+			iput(dir);
+		} else {
+			ntfs_inode *dir_ni = ntfs_i(dir);
+
+			if (!ni_trylock(dir_ni)) {
+				re_dirty = true;
+				iput(dir);
+				break;
+			}
+
+			indx_update_dup(dir_ni, sbi, fname, &dup, sync);
+
+			ni_unlock(dir_ni);
+			iput(dir);
+		}
+
+		memcpy(&fname->dup, &dup, sizeof(fname->dup));
+		mi->dirty = true;
+	}
+
+	if (re_dirty)
+		ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
+	else
+		ni->ni_flags &= ~NI_FLAG_UPDATE_PARENT;
+
+skip_dir_update:
+	err = al_update(ni);
+	if (err)
+		goto out;
+
+write_subrecords:
+	for (node = rb_first(&ni->mi_tree); node; node = next) {
+		mft_inode *mi = rb_entry(node, mft_inode, node);
+		bool is_empty;
+
+		next = rb_next(node);
+
+		if (!mi->dirty)
+			continue;
+
+		is_empty = !mi_enum_attr(mi, NULL);
+
+		if (is_empty)
+			clear_rec_inuse(mi->mrec);
+
+		err2 = mi_write(mi, sync);
+		if (!err && err2)
+			err = err2;
+
+		if (is_empty) {
+			ntfs_mark_rec_free(sbi, mi->rno);
+			rb_erase(node, &ni->mi_tree);
+			mi_put(mi);
+		}
+	}
+
+	err2 = mi_write(&ni->mi, sync);
+	if (!err && err2)
+		err = err2;
+
+out:
+	ni_unlock(ni);
+
+	if (err) {
+		ntfs_error(sb, "%s r=%lx failed, %d.", hint, inode->i_ino, err);
+		ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
+		return err;
+	}
+
+	if (re_dirty && (sb->s_flags & SB_ACTIVE))
+		mark_inode_dirty_sync(inode);
+
+	if (inode->i_ino < sbi->mft.recs_mirr)
+		sbi->flags |= NTFS_FLAGS_MFTMIRR;
+	return 0;
+}
diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c
new file mode 100644
index 000000000000..a26da5c88344
--- /dev/null
+++ b/fs/ntfs3/namei.c
@@ -0,0 +1,566 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  linux/fs/ntfs3/namei.c
+ *
+ * Copyright (C) 2019-2020 Paragon Software GmbH, All rights reserved.
+ *
+ */
+
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/iversion.h>
+#include <linux/namei.h>
+#include <linux/nls.h>
+
+#include "debug.h"
+#include "ntfs.h"
+#include "ntfs_fs.h"
+
+/*
+ * fill_name_de
+ *
+ * formats NTFS_DE in 'buf'
+ */
+int fill_name_de(ntfs_sb_info *sbi, void *buf, const struct qstr *name)
+{
+	int err;
+	NTFS_DE *e = buf;
+	u16 data_size;
+	ATTR_FILE_NAME *fname = (ATTR_FILE_NAME *)(e + 1);
+
+#ifndef NTFS3_64BIT_CLUSTER
+	e->ref.high = fname->home.high = 0;
+#endif
+	/* Convert input string to unicode */
+	err = x8_to_uni(sbi, name->name, name->len,
+			(struct cpu_str *)&fname->name_len, NTFS_NAME_LEN,
+			UTF16_LITTLE_ENDIAN);
+	if (err < 0)
+		return err;
+
+	fname->type = FILE_NAME_POSIX;
+	data_size = fname_full_size(fname);
+
+	e->size = cpu_to_le16(QuadAlign(data_size) + sizeof(NTFS_DE));
+	e->key_size = cpu_to_le16(data_size);
+	e->flags = 0;
+	e->Reserved = 0;
+
+	return 0;
+}
+
+static struct dentry *__ntfs_lookup(struct inode *dir, struct dentry *dentry,
+				    struct ntfs_fnd *fnd)
+{
+	struct dentry *d;
+	struct inode *inode;
+
+	inode = dir_search(dir, &dentry->d_name, fnd);
+
+	if (!inode) {
+		d_add(dentry, NULL);
+		d = NULL;
+		goto out;
+	}
+
+	if (IS_ERR(inode)) {
+		d = ERR_CAST(inode);
+		goto out;
+	}
+
+	d = d_splice_alias(inode, dentry);
+	if (IS_ERR(d)) {
+		iput(inode);
+		goto out;
+	}
+
+out:
+	return d;
+}
+
+/*
+ * ntfs_lookup
+ *
+ * inode_operations::lookup
+ */
+static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
+				  u32 flags)
+{
+	struct dentry *de;
+	ntfs_inode *ni = ntfs_i(dir);
+
+	ni_lock(ni);
+
+	de = __ntfs_lookup(dir, dentry, NULL);
+
+	ni_unlock(ni);
+	return de;
+}
+
+/*
+ * ntfs_create
+ *
+ * inode_operations::create
+ */
+static int ntfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
+		       bool excl)
+{
+	int err;
+	ntfs_inode *ni = ntfs_i(dir);
+	struct inode *inode;
+
+	ni_lock(ni);
+
+	err = ntfs_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0,
+				excl, NULL, &inode);
+
+	ni_unlock(ni);
+
+	return err;
+}
+
+/*
+ * ntfs_link
+ *
+ * inode_operations::link
+ */
+static int ntfs_link(struct dentry *ode, struct inode *dir, struct dentry *de)
+{
+	int err;
+	struct inode *inode = d_inode(ode);
+	ntfs_inode *ni = ntfs_i(inode);
+
+	if (S_ISDIR(inode->i_mode))
+		return -EPERM;
+
+	if (inode->i_nlink >= NTFS_LINK_MAX)
+		return -EMLINK;
+
+	ni_lock(ni);
+
+	dir->i_ctime = dir->i_mtime = inode->i_ctime = current_time(inode);
+	inc_nlink(inode);
+	ihold(inode);
+
+	err = ntfs_link_inode(inode, de);
+	if (!err) {
+		mark_inode_dirty(inode);
+		d_instantiate(de, inode);
+	} else {
+		drop_nlink(inode);
+		iput(inode);
+	}
+
+	ni_unlock(ni);
+
+	return err;
+}
+
+/*
+ * ntfs_unlink
+ *
+ * inode_operations::unlink
+ */
+static int ntfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	ntfs_inode *ni = ntfs_i(dir);
+	int err;
+
+	ni_lock(ni);
+
+	err = ntfs_unlink_inode(dir, dentry);
+
+	ni_unlock(ni);
+
+	return err;
+}
+
+/*
+ * ntfs_symlink
+ *
+ * inode_operations::symlink
+ */
+static int ntfs_symlink(struct inode *dir, struct dentry *dentry,
+			const char *symname)
+{
+	int err;
+	u32 size = strlen(symname);
+	struct inode *inode;
+	ntfs_inode *ni = ntfs_i(dir);
+
+	ni_lock(ni);
+
+	err = ntfs_create_inode(dir, dentry, NULL, S_IFLNK | 0777, 0, symname,
+				size, 0, NULL, &inode);
+
+	ni_unlock(ni);
+
+	return err;
+}
+
+/*
+ * ntfs_mkdir
+ *
+ * inode_operations::mkdir
+ */
+static int ntfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+	int err;
+	struct inode *inode;
+	ntfs_inode *ni = ntfs_i(dir);
+
+	ni_lock(ni);
+
+	err = ntfs_create_inode(dir, dentry, NULL, S_IFDIR | mode, 0, NULL, -1,
+				0, NULL, &inode);
+
+	ni_unlock(ni);
+
+	return err;
+}
+
+/*
+ * ntfs_rmdir
+ *
+ * inode_operations::rm_dir
+ */
+static int ntfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	ntfs_inode *ni = ntfs_i(dir);
+	int err;
+
+	ni_lock(ni);
+
+	err = ntfs_unlink_inode(dir, dentry);
+
+	ni_unlock(ni);
+
+	return err;
+}
+
+/*
+ * ntfs_rename
+ *
+ * inode_operations::rename
+ */
+static int ntfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+		       struct inode *new_dir, struct dentry *new_dentry,
+		       u32 flags)
+{
+	int err;
+	struct super_block *sb = old_dir->i_sb;
+	ntfs_sb_info *sbi = sb->s_fs_info;
+	ntfs_inode *old_diri = ntfs_i(old_dir);
+	ntfs_inode *new_diri = ntfs_i(new_dir);
+	ntfs_inode *ni;
+	ATTR_FILE_NAME *old_name, *new_name, *fname;
+	u8 name_type;
+	bool is_same;
+	struct inode *old_inode, *new_inode;
+	NTFS_DE *old_de, *new_de;
+	ATTRIB *attr;
+	ATTR_LIST_ENTRY *le;
+	u16 new_de_key_size;
+
+	static_assert(SIZEOF_ATTRIBUTE_FILENAME_MAX + SIZEOF_RESIDENT < 1024);
+	static_assert(SIZEOF_ATTRIBUTE_FILENAME_MAX + sizeof(NTFS_DE) < 1024);
+	static_assert(PATH_MAX >= 4 * 1024);
+
+	if (flags & ~RENAME_NOREPLACE)
+		return -EINVAL;
+
+	old_inode = d_inode(old_dentry);
+	new_inode = d_inode(new_dentry);
+
+	ni = ntfs_i(old_inode);
+
+	ni_lock(ni);
+
+	is_same = old_dentry->d_name.len == new_dentry->d_name.len &&
+		  !memcmp(old_dentry->d_name.name, new_dentry->d_name.name,
+			  old_dentry->d_name.len);
+
+	if (is_same && old_dir == new_dir) {
+		/* Nothing to do */
+		err = 0;
+		goto out1;
+	}
+
+	if (ntfs_is_meta_file(sbi, old_inode->i_ino)) {
+		err = -EINVAL;
+		goto out1;
+	}
+
+	if (new_inode) {
+		/*target name exists. unlink it*/
+		dget(new_dentry);
+		err = ntfs_unlink_inode(new_dir, new_dentry);
+		dput(new_dentry);
+		if (err)
+			goto out1;
+	}
+
+	old_de = __getname();
+	if (!old_de) {
+		err = -ENOMEM;
+		goto out1;
+	}
+
+	err = fill_name_de(sbi, old_de, &old_dentry->d_name);
+	if (err < 0)
+		goto out2;
+
+	old_name = (ATTR_FILE_NAME *)(old_de + 1);
+
+	if (is_same) {
+		new_de = old_de;
+	} else {
+		new_de = Add2Ptr(old_de, 1024);
+		err = fill_name_de(sbi, new_de, &new_dentry->d_name);
+		if (err < 0)
+			goto out2;
+	}
+
+	old_name->home.low = cpu_to_le32(old_dir->i_ino);
+#ifdef NTFS3_64BIT_CLUSTER
+	old_name->home.high = cpu_to_le16(old_dir->i_ino >> 32);
+#endif
+	old_name->home.seq = ntfs_i(old_dir)->mi.mrec->seq;
+
+	/*get pointer to file_name in mft*/
+	fname = ni_fname_name(ni, (struct cpu_str *)&old_name->name_len,
+			      &old_name->home, &le);
+	if (!fname) {
+		err = -EINVAL;
+		goto out2;
+	}
+
+	/* Copy fname info from record into new fname */
+	new_name = (ATTR_FILE_NAME *)(new_de + 1);
+	memcpy(&new_name->dup, &fname->dup, sizeof(fname->dup));
+
+	name_type = paired_name(fname->type);
+
+	/* remove first name from directory */
+	err = indx_delete_entry(&old_diri->dir, old_diri, old_de + 1,
+				le16_to_cpu(old_de->key_size), sbi);
+	if (err)
+		goto out3;
+
+	/* remove first name from mft */
+	err = ni_remove_attr_le(ni, attr_from_name(fname), le);
+	if (err)
+		goto out4;
+
+	le16_add_cpu(&ni->mi.mrec->hard_links, -1);
+	ni->mi.dirty = true;
+
+	if (name_type == FILE_NAME_POSIX)
+		goto skip_short;
+
+	/* get paired name */
+	fname = ni_fname_type(ni, name_type, &le);
+	if (!fname)
+		goto skip_short;
+
+	/* remove second name from directory */
+	err = indx_delete_entry(&old_diri->dir, old_diri, fname,
+				fname_full_size(fname), sbi);
+	if (err)
+		goto out5;
+
+	/* remove second name from mft */
+	err = ni_remove_attr_le(ni, attr_from_name(fname), le);
+	if (err)
+		goto out6;
+
+	le16_add_cpu(&ni->mi.mrec->hard_links, -1);
+	ni->mi.dirty = true;
+
+skip_short:
+
+	/* Add new name */
+	new_de->ref.low = cpu_to_le32(old_inode->i_ino);
+#ifdef NTFS3_64BIT_CLUSTER
+	new_de->ref.high = cpu_to_le16(old_inode->i_ino >> 32);
+	new_name->home.high = cpu_to_le16(new_dir->i_ino >> 32);
+#endif
+	new_de->ref.seq = ni->mi.mrec->seq;
+
+	new_name->home.low = cpu_to_le32(new_dir->i_ino);
+	new_name->home.seq = ntfs_i(new_dir)->mi.mrec->seq;
+
+	new_de_key_size = le16_to_cpu(new_de->key_size);
+
+	/* insert new name in mft */
+	err = ni_insert_resident(ni, new_de_key_size, ATTR_NAME, NULL, 0, &attr,
+				 NULL);
+	if (err)
+		goto out7;
+
+	attr->res.flags = RESIDENT_FLAG_INDEXED;
+
+	memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), new_name, new_de_key_size);
+
+	le16_add_cpu(&ni->mi.mrec->hard_links, 1);
+	ni->mi.dirty = true;
+
+	/* insert new name in directory */
+	err = indx_insert_entry(&new_diri->dir, new_diri, new_de, sbi, NULL);
+	if (err)
+		goto out8;
+
+	if (IS_DIRSYNC(new_dir))
+		err = ntfs_sync_inode(old_inode);
+	else
+		mark_inode_dirty(old_inode);
+
+	old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir);
+	if (IS_DIRSYNC(old_dir))
+		(void)ntfs_sync_inode(old_dir);
+	else
+		mark_inode_dirty(old_dir);
+
+	if (old_dir != new_dir) {
+		new_dir->i_mtime = new_dir->i_ctime = old_dir->i_ctime;
+		mark_inode_dirty(new_dir);
+#ifdef NTFS_COUNT_CONTAINED
+		if (S_ISDIR(old_inode->i_mode)) {
+			drop_nlink(old_dir);
+			if (!new_inode)
+				inc_nlink(new_dir);
+		}
+#endif
+	}
+
+	if (old_inode) {
+		old_inode->i_ctime = old_dir->i_ctime;
+		mark_inode_dirty(old_inode);
+	}
+
+	err = 0;
+	goto out2;
+
+out8:
+	mi_remove_attr(&ni->mi, attr);
+
+out7:
+out6:
+out5:
+out4:
+	/* Undo:
+	 *err = indx_delete_entry(&old_diri->dir, old_diri, old_de + 1,
+	 *			old_de->key_size, NULL);
+	 */
+
+out3:
+out2:
+	__putname(old_de);
+out1:
+	ni_unlock(ni);
+
+	return err;
+}
+
+/*
+ * ntfs_atomic_open
+ *
+ * inode_operations::atomic_open
+ */
+static int ntfs_atomic_open(struct inode *dir, struct dentry *dentry,
+			    struct file *file, u32 flags, umode_t mode)
+{
+	int err;
+	bool excl = !!(flags & O_EXCL);
+	struct inode *inode;
+	struct ntfs_fnd *fnd = NULL;
+	ntfs_inode *ni = ntfs_i(dir);
+
+	ni_lock(ni);
+
+	if (d_in_lookup(dentry)) {
+		struct dentry *d;
+
+		fnd = fnd_get(&ntfs_i(dir)->dir);
+		if (!fnd) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		d = __ntfs_lookup(dir, dentry, fnd);
+		if (IS_ERR(d)) {
+			err = PTR_ERR(d);
+			d = NULL;
+			goto out1;
+		}
+
+		if (d)
+			dentry = d;
+
+		if (d_really_is_positive(dentry)) {
+			if (file->f_mode & FMODE_OPENED) {
+				dput(d);
+				err = 0;
+			} else
+				err = finish_no_open(file, d);
+			goto out1;
+		}
+		WARN_ON(d);
+	}
+
+	if (!(flags & O_CREAT)) {
+		err = -ENOENT;
+		goto out1;
+	}
+
+	err = ntfs_create_inode(dir, dentry, file, mode, 0, NULL, 0, excl, fnd,
+				&inode);
+
+out1:
+	fnd_put(fnd);
+out:
+	ni_unlock(ni);
+
+	return err;
+}
+
+struct dentry *ntfs_get_parent(struct dentry *child)
+{
+	struct inode *inode = d_inode(child);
+	ntfs_inode *ni = ntfs_i(inode);
+
+	ATTR_LIST_ENTRY *le = NULL;
+	ATTRIB *attr = NULL;
+	ATTR_FILE_NAME *fname;
+
+	while ((attr = ni_find_attr(ni, attr, &le, ATTR_NAME, NULL, 0, NULL,
+				    NULL))) {
+		fname = resident_data_ex(attr, SIZEOF_ATTRIBUTE_FILENAME);
+		if (!fname)
+			continue;
+
+		return d_obtain_alias(
+			ntfs_iget5(inode->i_sb, &fname->home, NULL));
+	}
+
+	return ERR_PTR(-ENOENT);
+}
+
+const struct inode_operations ntfs_dir_inode_operations = {
+	.lookup = ntfs_lookup,
+	.create = ntfs_create,
+	.link = ntfs_link,
+	.unlink = ntfs_unlink,
+	.symlink = ntfs_symlink,
+	.mkdir = ntfs_mkdir,
+	.rmdir = ntfs_rmdir,
+	.rename = ntfs_rename,
+	.permission = ntfs_permission,
+	.get_acl = ntfs_get_acl,
+	.set_acl = ntfs_set_acl,
+	.setattr = ntfs_setattr,
+	.getattr = ntfs_getattr,
+	.listxattr = ntfs_listxattr,
+	.atomic_open = ntfs_atomic_open,
+};
diff --git a/fs/ntfs3/record.c b/fs/ntfs3/record.c
new file mode 100644
index 000000000000..ffa08f552823
--- /dev/null
+++ b/fs/ntfs3/record.c
@@ -0,0 +1,612 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  linux/fs/ntfs3/record.c
+ *
+ * Copyright (C) 2019-2020 Paragon Software GmbH, All rights reserved.
+ *
+ */
+
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/nls.h>
+#include <linux/sched/signal.h>
+
+#include "debug.h"
+#include "ntfs.h"
+#include "ntfs_fs.h"
+
+static inline int compare_attr(const ATTRIB *left, ATTR_TYPE type,
+			       const __le16 *name, u8 name_len,
+			       const u16 *upcase)
+{
+	/* First, compare the type codes: */
+	int diff = le32_to_cpu(left->type) - le32_to_cpu(type);
+
+	if (diff)
+		return diff;
+
+	/*
+	 * They have the same type code, so we have to compare the names.
+	 * First compare case insensitive
+	 */
+	diff = ntfs_cmp_names(attr_name(left), left->name_len, name, name_len,
+			      upcase);
+	if (diff)
+		return diff;
+
+	/* Second compare case sensitive */
+	return ntfs_cmp_names(attr_name(left), left->name_len, name, name_len,
+			      NULL);
+}
+
+/*
+ * mi_new_attt_id
+ *
+ * returns unused attribute id that is less than mrec->next_attr_id
+ */
+static __le16 mi_new_attt_id(mft_inode *mi)
+{
+	u16 free_id, max_id, t16;
+	MFT_REC *rec = mi->mrec;
+	ATTRIB *attr;
+	__le16 id;
+
+	id = rec->next_attr_id;
+	free_id = le16_to_cpu(id);
+	if (free_id < 0x7FFF) {
+		rec->next_attr_id = cpu_to_le16(free_id + 1);
+		return id;
+	}
+
+	/* One record can store up to 1024/24 ~= 42 attributes */
+	free_id = 0;
+	max_id = 0;
+
+	attr = NULL;
+
+next_attr:
+	attr = mi_enum_attr(mi, attr);
+	if (!attr) {
+		rec->next_attr_id = cpu_to_le16(max_id + 1);
+		mi->dirty = true;
+		return cpu_to_le16(free_id);
+	}
+
+	t16 = le16_to_cpu(attr->id);
+	if (t16 == free_id) {
+		free_id += 1;
+		attr = NULL;
+		goto next_attr;
+	}
+
+	if (max_id < t16)
+		max_id = t16;
+	goto next_attr;
+}
+
+int mi_get(ntfs_sb_info *sbi, CLST rno, mft_inode **mi)
+{
+	int err;
+	mft_inode *m = ntfs_alloc(sizeof(mft_inode), 1);
+
+	if (!m)
+		return -ENOMEM;
+
+	err = mi_init(m, sbi, rno);
+	if (!err)
+		err = mi_read(m, false);
+
+	if (err) {
+		mi_put(m);
+		return err;
+	}
+
+	*mi = m;
+	return 0;
+}
+
+void mi_put(mft_inode *mi)
+{
+	mi_clear(mi);
+	ntfs_free(mi);
+}
+
+int mi_init(mft_inode *mi, ntfs_sb_info *sbi, CLST rno)
+{
+	mi->sbi = sbi;
+	mi->rno = rno;
+	mi->mrec = ntfs_alloc(sbi->record_size, 0);
+	if (!mi->mrec)
+		return -ENOMEM;
+
+	return 0;
+}
+
+/*
+ * mi_read
+ *
+ * reads MFT data
+ */
+int mi_read(mft_inode *mi, bool is_mft)
+{
+	int err;
+	MFT_REC *rec = mi->mrec;
+	ntfs_sb_info *sbi = mi->sbi;
+	u32 bpr = sbi->record_size;
+	u64 vbo = (u64)mi->rno << sbi->record_bits;
+	ntfs_inode *mft_ni = sbi->mft.ni;
+	struct runs_tree *run = mft_ni ? &mft_ni->file.run : NULL;
+	struct rw_semaphore *rw_lock = NULL;
+
+	if (is_mounted(sbi)) {
+		if (!is_mft) {
+			rw_lock = &mft_ni->file.run_lock;
+			down_read(rw_lock);
+		}
+	}
+
+	err = ntfs_read_bh_ex(sbi, run, vbo, &rec->rhdr, bpr, &mi->nb);
+	if (rw_lock)
+		up_read(rw_lock);
+	if (!err)
+		goto ok;
+
+	if (err == 1) {
+		mi->dirty = true;
+		goto ok;
+	}
+
+	if (err != -ENOENT)
+		goto out;
+
+	if (rw_lock) {
+		ni_lock(mft_ni);
+		down_write(rw_lock);
+	}
+	err = attr_load_runs_vcn(mft_ni, ATTR_DATA, NULL, 0, &mft_ni->file.run,
+				 vbo >> sbi->cluster_bits);
+	if (rw_lock) {
+		up_write(rw_lock);
+		ni_unlock(mft_ni);
+	}
+	if (err)
+		goto out;
+
+	if (rw_lock)
+		down_read(rw_lock);
+	err = ntfs_read_bh_ex(sbi, run, vbo, &rec->rhdr, bpr, &mi->nb);
+	if (rw_lock)
+		up_read(rw_lock);
+
+	if (err == 1) {
+		mi->dirty = true;
+		goto ok;
+	}
+	if (err)
+		goto out;
+
+ok:
+	/* check field 'total' only here */
+	if (le32_to_cpu(rec->total) != bpr) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	return 0;
+
+out:
+	return err;
+}
+
+ATTRIB *mi_enum_attr(mft_inode *mi, ATTRIB *attr)
+{
+	const MFT_REC *rec = mi->mrec;
+	u32 used = le32_to_cpu(rec->used);
+	u32 t32, off, asize;
+	u16 t16;
+
+	if (!attr) {
+		u32 total = le32_to_cpu(rec->total);
+
+		off = le16_to_cpu(rec->attr_off);
+
+		if (used > total)
+			goto out;
+
+		if (off >= used || off < MFTRECORD_FIXUP_OFFSET_1 ||
+		    !IsDwordAligned(off)) {
+			goto out;
+		}
+
+		/* Skip non-resident records */
+		if (!is_rec_inuse(rec))
+			goto out;
+
+		attr = Add2Ptr(rec, off);
+	} else {
+		/* Check if input attr inside record */
+		off = PtrOffset(rec, attr);
+		if (off >= used)
+			goto out;
+
+		asize = le32_to_cpu(attr->size);
+		if (asize < SIZEOF_RESIDENT)
+			goto out;
+
+		attr = Add2Ptr(attr, asize);
+		off += asize;
+	}
+
+	asize = le32_to_cpu(attr->size);
+
+	/* Can we use the first field (attr->type) */
+	if (off + 8 > used) {
+		static_assert(QuadAlign(sizeof(ATTR_TYPE)) == 8);
+		goto out;
+	}
+
+	if (attr->type == ATTR_END) {
+		if (used != off + 8)
+			goto out;
+		return NULL;
+	}
+
+	t32 = le32_to_cpu(attr->type);
+	if ((t32 & 0xf) || (t32 > 0x100))
+		goto out;
+
+	/* Check boundary */
+	if (off + asize > used)
+		goto out;
+
+	/* Check size of attribute */
+	if (!attr->non_res) {
+		if (asize < SIZEOF_RESIDENT)
+			goto out;
+
+		t16 = le16_to_cpu(attr->res.data_off);
+
+		if (t16 > asize)
+			goto out;
+
+		t32 = le32_to_cpu(attr->res.data_size);
+		if (t16 + t32 > asize)
+			goto out;
+
+		return attr;
+	}
+
+	/* Check some nonresident fields */
+	if (attr->name_len &&
+	    le16_to_cpu(attr->name_off) + sizeof(short) * attr->name_len >
+		    le16_to_cpu(attr->nres.run_off)) {
+		goto out;
+	}
+
+	if (attr->nres.svcn || !is_attr_ext(attr)) {
+		if (asize + 8 < SIZEOF_NONRESIDENT)
+			goto out;
+
+		if (attr->nres.c_unit)
+			goto out;
+	} else if (asize + 8 < SIZEOF_NONRESIDENT_EX)
+		goto out;
+
+	return attr;
+
+out:
+	return NULL;
+}
+
+/*
+ * mi_find_attr
+ *
+ * finds the attribute by type and name and id
+ */
+ATTRIB *mi_find_attr(mft_inode *mi, ATTRIB *attr, ATTR_TYPE type,
+		     const __le16 *name, size_t name_len, const __le16 *id)
+{
+	u32 type_in = le32_to_cpu(type);
+	u32 atype;
+
+next_attr:
+	attr = mi_enum_attr(mi, attr);
+	if (!attr)
+		return NULL;
+
+	atype = le32_to_cpu(attr->type);
+	if (atype > type_in)
+		return NULL;
+
+	if (atype < type_in)
+		goto next_attr;
+
+	if (attr->name_len != name_len)
+		goto next_attr;
+
+	if (name_len && memcmp(attr_name(attr), name, name_len * sizeof(short)))
+		goto next_attr;
+
+	if (id && *id != attr->id)
+		goto next_attr;
+
+	return attr;
+}
+
+int mi_write(mft_inode *mi, int wait)
+{
+	MFT_REC *rec;
+	int err;
+	ntfs_sb_info *sbi;
+
+	if (!mi->dirty)
+		return 0;
+
+	sbi = mi->sbi;
+	rec = mi->mrec;
+
+	err = ntfs_write_bh_ex(sbi, &rec->rhdr, &mi->nb, wait);
+	if (err)
+		return err;
+
+	mi->dirty = false;
+
+	return 0;
+}
+
+int mi_format_new(mft_inode *mi, ntfs_sb_info *sbi, CLST rno, __le16 flags,
+		  bool is_mft)
+{
+	int err;
+	u16 seq = 1;
+	MFT_REC *rec;
+	u64 vbo = (u64)rno << sbi->record_bits;
+
+	err = mi_init(mi, sbi, rno);
+	if (err)
+		return err;
+
+	rec = mi->mrec;
+
+	if (rno == MFT_REC_MFT)
+		;
+	else if (rno < MFT_REC_FREE)
+		seq = rno;
+	else if (rno >= sbi->mft.used)
+		;
+	else if (mi_read(mi, is_mft))
+		;
+	else if (rec->rhdr.sign == NTFS_FILE_SIGNATURE) {
+		/* Record is reused. Update its sequence number */
+		seq = le16_to_cpu(rec->seq) + 1;
+		if (!seq)
+			seq = 1;
+	}
+
+	memcpy(rec, sbi->new_rec, sbi->record_size);
+
+	rec->seq = cpu_to_le16(seq);
+	rec->flags = RECORD_FLAG_IN_USE | flags;
+
+	mi->dirty = true;
+
+	if (!mi->nb.nbufs) {
+		ntfs_inode *ni = sbi->mft.ni;
+		bool lock = false;
+
+		if (is_mounted(sbi) && !is_mft) {
+			down_read(&ni->file.run_lock);
+			lock = true;
+		}
+
+		err = ntfs_get_bh(sbi, &ni->file.run, vbo, sbi->record_size,
+				  &mi->nb);
+		if (lock)
+			up_read(&ni->file.run_lock);
+	}
+
+	return err;
+}
+
+/*
+ * mi_mark_free
+ *
+ * marks record as unused and marks it as free in bitmap
+ */
+void mi_mark_free(mft_inode *mi)
+{
+	CLST rno = mi->rno;
+	ntfs_sb_info *sbi = mi->sbi;
+
+	if (rno >= MFT_REC_RESERVED && rno < MFT_REC_FREE) {
+		ntfs_clear_mft_tail(sbi, rno, rno + 1);
+		mi->dirty = false;
+		return;
+	}
+
+	if (mi->mrec) {
+		clear_rec_inuse(mi->mrec);
+		mi->dirty = true;
+		mi_write(mi, 0);
+	}
+	ntfs_mark_rec_free(sbi, rno);
+}
+
+/*
+ * mi_insert_attr
+ *
+ * reserves space for new attribute
+ * returns not full constructed attribute or NULL if not possible to create
+ */
+ATTRIB *mi_insert_attr(mft_inode *mi, ATTR_TYPE type, const __le16 *name,
+		       u8 name_len, u32 asize, u16 name_off)
+{
+	size_t tail;
+	ATTRIB *attr;
+	__le16 id;
+	MFT_REC *rec = mi->mrec;
+	ntfs_sb_info *sbi = mi->sbi;
+	u32 used = le32_to_cpu(rec->used);
+	const u16 *upcase = sbi->upcase;
+	int diff;
+
+	/* Can we insert mi attribute? */
+	if (used + asize > mi->sbi->record_size)
+		return NULL;
+
+	/*
+	 * Scan through the list of attributes to find the point
+	 * at which we should insert it.
+	 */
+	attr = NULL;
+	while ((attr = mi_enum_attr(mi, attr))) {
+		diff = compare_attr(attr, type, name, name_len, upcase);
+		if (diff > 0)
+			break;
+		if (diff < 0)
+			continue;
+
+		if (!is_attr_indexed(attr))
+			return NULL;
+		break;
+	}
+
+	if (!attr) {
+		tail = 8; /* not used, just to suppress warning */
+		attr = Add2Ptr(rec, used - 8);
+	} else {
+		tail = used - PtrOffset(rec, attr);
+	}
+
+	id = mi_new_attt_id(mi);
+
+	memmove(Add2Ptr(attr, asize), attr, tail);
+	memset(attr, 0, asize);
+
+	attr->type = type;
+	attr->size = cpu_to_le32(asize);
+	attr->name_len = name_len;
+	attr->name_off = cpu_to_le16(name_off);
+	attr->id = id;
+
+	memmove(Add2Ptr(attr, name_off), name, name_len * sizeof(short));
+	rec->used = cpu_to_le32(used + asize);
+
+	mi->dirty = true;
+
+	return attr;
+}
+
+/*
+ * mi_remove_attr
+ *
+ * removes the attribute from record
+ * NOTE: The source attr will point to next attribute
+ */
+bool mi_remove_attr(mft_inode *mi, ATTRIB *attr)
+{
+	MFT_REC *rec = mi->mrec;
+	u32 aoff = PtrOffset(rec, attr);
+	u32 used = le32_to_cpu(rec->used);
+	u32 asize = le32_to_cpu(attr->size);
+
+	if (aoff + asize > used)
+		return false;
+
+	used -= asize;
+	memmove(attr, Add2Ptr(attr, asize), used - aoff);
+	rec->used = cpu_to_le32(used);
+	mi->dirty = true;
+
+	return true;
+}
+
+bool mi_resize_attr(mft_inode *mi, ATTRIB *attr, int bytes)
+{
+	MFT_REC *rec = mi->mrec;
+	u32 aoff = PtrOffset(rec, attr);
+	u32 total, used = le32_to_cpu(rec->used);
+	u32 nsize, asize = le32_to_cpu(attr->size);
+	u32 rsize = le32_to_cpu(attr->res.data_size);
+	int tail = (int)(used - aoff - asize);
+	int dsize;
+	char *next;
+
+	if (tail < 0 || aoff >= used)
+		return false;
+
+	if (!bytes)
+		return true;
+
+	total = le32_to_cpu(rec->total);
+	next = Add2Ptr(attr, asize);
+
+	if (bytes > 0) {
+		dsize = QuadAlign(bytes);
+		if (used + dsize > total)
+			return false;
+		nsize = asize + dsize;
+		// move tail
+		memmove(next + dsize, next, tail);
+		memset(next, 0, dsize);
+		used += dsize;
+		rsize += dsize;
+	} else {
+		dsize = QuadAlign(-bytes);
+		if (dsize > asize)
+			return false;
+		nsize = asize - dsize;
+		memmove(next - dsize, next, tail);
+		used -= dsize;
+		rsize -= dsize;
+	}
+
+	rec->used = cpu_to_le32(used);
+	attr->size = cpu_to_le32(nsize);
+	if (!attr->non_res)
+		attr->res.data_size = cpu_to_le32(rsize);
+	mi->dirty = true;
+
+	return true;
+}
+
+int mi_pack_runs(mft_inode *mi, ATTRIB *attr, struct runs_tree *run, CLST len)
+{
+	int err = 0;
+	ntfs_sb_info *sbi = mi->sbi;
+	u32 new_run_size;
+	CLST plen;
+	MFT_REC *rec = mi->mrec;
+	CLST svcn = le64_to_cpu(attr->nres.svcn);
+	u32 used = le32_to_cpu(rec->used);
+	u32 aoff = PtrOffset(rec, attr);
+	u32 asize = le32_to_cpu(attr->size);
+	char *next = Add2Ptr(attr, asize);
+	u16 run_off = le16_to_cpu(attr->nres.run_off);
+	u32 run_size = asize - run_off;
+	u32 tail = used - aoff - asize;
+	u32 dsize = sbi->record_size - used;
+
+	/* Make a maximum gap in current record */
+	memmove(next + dsize, next, tail);
+
+	/* Pack as much as possible */
+	err = run_pack(run, svcn, len, Add2Ptr(attr, run_off), run_size + dsize,
+		       &plen);
+	if (err < 0) {
+		memmove(next, next + dsize, tail);
+		return err;
+	}
+
+	new_run_size = QuadAlign(err);
+
+	memmove(next + new_run_size - run_size, next + dsize, tail);
+
+	attr->size = cpu_to_le32(asize + new_run_size - run_size);
+	attr->nres.evcn = cpu_to_le64(svcn + plen - 1);
+	rec->used = cpu_to_le32(used + new_run_size - run_size);
+	mi->dirty = true;
+
+	return 0;
+}
diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c
new file mode 100644
index 000000000000..b500200be231
--- /dev/null
+++ b/fs/ntfs3/run.c
@@ -0,0 +1,1188 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  linux/fs/ntfs3/run.c
+ *
+ * Copyright (C) 2019-2020 Paragon Software GmbH, All rights reserved.
+ *
+ */
+
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/nls.h>
+
+#include "debug.h"
+#include "ntfs.h"
+#include "ntfs_fs.h"
+
+struct ntfs_run {
+	CLST vcn; /* virtual cluster number */
+	CLST len; /* length in clusters */
+	CLST lcn; /* logical cluster number */
+};
+
+/*
+ * run_lookup
+ *
+ * Lookup the index of a MCB entry that is first <= vcn.
+ * case of success it will return non-zero value and set
+ * 'index' parameter to index of entry been found.
+ * case of entry missing from list 'index' will be set to
+ * point to insertion position for the entry question.
+ */
+bool run_lookup(const struct runs_tree *run, CLST vcn, size_t *index)
+{
+	size_t min_idx, max_idx, mid_idx;
+	struct ntfs_run *r;
+
+	if (!run->count) {
+		*index = 0;
+		return false;
+	}
+
+	min_idx = 0;
+	max_idx = run->count - 1;
+
+	/* Check boundary cases specially, 'cause they cover the often requests */
+	r = run->runs_;
+	if (vcn < r->vcn) {
+		*index = 0;
+		return false;
+	}
+
+	if (vcn < r->vcn + r->len) {
+		*index = 0;
+		return true;
+	}
+
+	r += max_idx;
+	if (vcn >= r->vcn + r->len) {
+		*index = run->count;
+		return false;
+	}
+
+	if (vcn >= r->vcn) {
+		*index = max_idx;
+		return true;
+	}
+
+	do {
+		mid_idx = min_idx + ((max_idx - min_idx) >> 1);
+		r = run->runs_ + mid_idx;
+
+		if (vcn < r->vcn) {
+			max_idx = mid_idx - 1;
+			if (!mid_idx)
+				break;
+		} else if (vcn >= r->vcn + r->len) {
+			min_idx = mid_idx + 1;
+		} else {
+			*index = mid_idx;
+			return true;
+		}
+	} while (min_idx <= max_idx);
+
+	*index = max_idx + 1;
+	return false;
+}
+
+/*
+ * run_consolidate
+ *
+ * consolidate runs starting from a given one.
+ */
+static void run_consolidate(struct runs_tree *run, size_t index)
+{
+	size_t i;
+	struct ntfs_run *r = run->runs_ + index;
+
+	while (index + 1 < run->count) {
+		/*
+		 * I should merge current run with next
+		 * if start of the next run lies inside one being tested.
+		 */
+		struct ntfs_run *n = r + 1;
+		CLST end = r->vcn + r->len;
+		CLST dl;
+
+		/* Stop if runs are not aligned one to another. */
+		if (n->vcn > end)
+			break;
+
+		dl = end - n->vcn;
+
+		/*
+		 * If range at index overlaps with next one
+		 * then I will either adjust it's start position
+		 * or (if completely matches) dust remove one from the list.
+		 */
+		if (dl > 0) {
+			if (n->len <= dl)
+				goto remove_next_range;
+
+			n->len -= dl;
+			n->vcn += dl;
+			if (n->lcn != SPARSE_LCN)
+				n->lcn += dl;
+		}
+
+		/*
+		 * Stop if sparse mode does not match
+		 * both current and next runs.
+		 */
+		if ((n->lcn == SPARSE_LCN) != (r->lcn == SPARSE_LCN)) {
+			index += 1;
+			r = n;
+			continue;
+		}
+
+		/*
+		 * Check if volume block
+		 * of a next run lcn does not match
+		 * last volume block of the current run.
+		 */
+		if (n->lcn != SPARSE_LCN && n->lcn != r->lcn + r->len)
+			break;
+
+		/*
+		 * Next and current are siblings.
+		 * Eat/join.
+		 */
+		r->len += n->len - dl;
+
+remove_next_range:
+		i = run->count - (index + 1);
+		if (i > 1)
+			memmove(n, n + 1, sizeof(*n) * (i - 1));
+
+		run->count -= 1;
+	}
+}
+
+bool run_is_mapped_full(const struct runs_tree *run, CLST svcn, CLST evcn)
+{
+	size_t index;
+
+	if (!run_lookup(run, svcn, &index))
+		return false;
+
+	do {
+		const struct ntfs_run *m = run->runs_ + index;
+		CLST end = m->vcn + m->len;
+
+		if (end > evcn)
+			return true;
+	} while (++index < run->count);
+
+	return false;
+}
+
+bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
+		      CLST *len, size_t *index)
+{
+	size_t idx;
+	CLST gap;
+	struct ntfs_run *r;
+
+	/* Fail immediately if nrun was not touched yet. */
+	if (!run->runs_)
+		return false;
+
+	if (!run_lookup(run, vcn, &idx))
+		return false;
+
+	r = run->runs_ + idx;
+
+	if (vcn >= r->vcn + r->len)
+		return false;
+
+	gap = vcn - r->vcn;
+	if (r->len <= gap)
+		return false;
+
+	*lcn = r->lcn == SPARSE_LCN ? SPARSE_LCN : (r->lcn + gap);
+
+	if (len)
+		*len = r->len - gap;
+	if (index)
+		*index = idx;
+
+	return true;
+}
+
+/*
+ * run_truncate_head
+ *
+ * decommit the range before vcn
+ */
+void run_truncate_head(struct runs_tree *run, CLST vcn)
+{
+	size_t index;
+	struct ntfs_run *r;
+
+	if (run_lookup(run, vcn, &index)) {
+		r = run->runs_ + index;
+
+		if (vcn > r->vcn) {
+			CLST dlen = vcn - r->vcn;
+
+			r->vcn = vcn;
+			r->len -= dlen;
+			if (r->lcn != SPARSE_LCN)
+				r->lcn += dlen;
+		}
+
+		if (!index)
+			return;
+	}
+	r = run->runs_;
+	memmove(r, r + index, sizeof(*r) * (run->count - index));
+
+	run->count -= index;
+
+	if (!run->count) {
+		ntfs_free(run->runs_);
+		run->runs_ = NULL;
+		run->allocated = 0;
+	}
+}
+
+/*
+ * run_truncate
+ *
+ * decommit the range after vcn
+ */
+void run_truncate(struct runs_tree *run, CLST vcn)
+{
+	size_t index;
+
+	/*
+	 * If I hit the range then
+	 * I have to truncate one.
+	 * If range to be truncated is becoming empty
+	 * then it will entirely be removed.
+	 */
+	if (run_lookup(run, vcn, &index)) {
+		struct ntfs_run *r = run->runs_ + index;
+
+		r->len = vcn - r->vcn;
+
+		if (r->len > 0)
+			index += 1;
+	}
+
+	/*
+	 * At this point 'index' is set to
+	 * position that should be thrown away (including index itself)
+	 * Simple one - just set the limit.
+	 */
+	run->count = index;
+
+	/* Do not reallocate array 'runs'. Only free if possible */
+	if (!index) {
+		ntfs_free(run->runs_);
+		run->runs_ = NULL;
+		run->allocated = 0;
+	}
+}
+
+/*
+ * run_add_entry
+ *
+ * sets location to known state.
+ * run to be added may overlap with existing location.
+ * returns false if of memory
+ */
+bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len)
+{
+	size_t used, index;
+	struct ntfs_run *r;
+	bool inrange;
+	CLST tail_vcn = 0, tail_len = 0, tail_lcn = 0;
+	bool should_add_tail = false;
+
+	/*
+	 * Lookup the insertion point.
+	 *
+	 * Execute bsearch for the entry containing
+	 * start position question.
+	 */
+	inrange = run_lookup(run, vcn, &index);
+
+	/*
+	 * Shortcut here would be case of
+	 * range not been found but one been added
+	 * continues previous run.
+	 * this case I can directly make use of
+	 * existing range as my start point.
+	 */
+	if (!inrange && index > 0) {
+		struct ntfs_run *t = run->runs_ + index - 1;
+
+		if (t->vcn + t->len == vcn &&
+		    (t->lcn == SPARSE_LCN) == (lcn == SPARSE_LCN) &&
+		    (lcn == SPARSE_LCN || lcn == t->lcn + t->len)) {
+			inrange = true;
+			index -= 1;
+		}
+	}
+
+	/*
+	 * At this point 'index' either points to the range
+	 * containing start position or to the insertion position
+	 * for a new range.
+	 * So first let's check if range I'm probing is here already.
+	 */
+	if (!inrange)
+		goto requires_new_range;
+
+	r = run->runs_ + index;
+
+	/*
+	 * If one of ranges was not allocated
+	 * then I have to split location I just matched.
+	 * and insert current one
+	 * a common case this requires tail to be reinserted
+	 * a recursive call.
+	 */
+	if (((lcn == SPARSE_LCN) != (r->lcn == SPARSE_LCN)) ||
+	    (lcn != SPARSE_LCN && lcn != r->lcn + (vcn - r->vcn))) {
+		CLST to_eat = vcn - r->vcn;
+		CLST Tovcn = to_eat + len;
+
+		should_add_tail = Tovcn < r->len;
+
+		if (should_add_tail) {
+			if (r->lcn == SPARSE_LCN)
+				tail_lcn = (CLST)SPARSE_LCN;
+			else
+				tail_lcn = r->lcn + Tovcn;
+			tail_vcn = r->vcn + Tovcn;
+			tail_len = r->len - Tovcn;
+		}
+
+		if (to_eat > 0) {
+			r->len = to_eat;
+			inrange = false;
+			index += 1;
+			goto requires_new_range;
+		}
+
+		/* lcn should match one I'm going to add. */
+		r->lcn = lcn;
+	}
+
+	/*
+	 * If existing range fits then I'm done.
+	 * Otherwise extend found one and fall back to range jocode.
+	 */
+	if (r->vcn + r->len < vcn + len)
+		r->len += len - ((r->vcn + r->len) - vcn);
+	goto consolidate;
+
+requires_new_range:
+	/*
+	 * Range was not found.
+	 * Insert at position.
+	 * 'index' points to the insertion point.
+	 * Here is also the place to check
+	 * if allocated size exceeded specified limit.
+	 * If it does, third part of allocation will be thrown away.
+	 */
+	used = run->count * sizeof(struct ntfs_run);
+
+	/*
+	 * Check allocated space.
+	 * If one is not enough to get one more entry
+	 * then it will be reallocated
+	 */
+	if (run->allocated < used + sizeof(struct ntfs_run)) {
+		size_t bytes;
+		struct ntfs_run *new_ptr;
+
+		/* Use power of 2 for 'bytes'*/
+		if (!used)
+			bytes = 64;
+		else if (used <= 16 * PAGE_SIZE) {
+			if (is_power_of2(run->allocated))
+				bytes = run->allocated << 1;
+			else
+				bytes = (size_t)1 << (2 + blksize_bits(used));
+		} else {
+			bytes = run->allocated + (16 * PAGE_SIZE);
+		}
+
+		new_ptr = ntfs_alloc(bytes, 0);
+		if (!new_ptr)
+			return false;
+
+		r = new_ptr + index;
+		memcpy(new_ptr, run->runs_, index * sizeof(struct ntfs_run));
+		memcpy(r + 1, run->runs_ + index,
+		       sizeof(struct ntfs_run) * (run->count - index));
+
+		ntfs_free(run->runs_);
+		run->runs_ = new_ptr;
+		run->allocated = bytes;
+
+	} else {
+		size_t i = run->count - index;
+
+		r = run->runs_ + index;
+
+		/* memmove appears to be a bottle neck here... */
+		if (i > 0)
+			memmove(r + 1, r, sizeof(struct ntfs_run) * i);
+	}
+
+	r->vcn = vcn;
+	r->lcn = lcn;
+	r->len = len;
+	run->count += 1;
+
+consolidate:
+
+	/*
+	 * And normalize it starting from insertion point.
+	 * It's possible that no insertion needed case if
+	 * start point lies withthe range of an entry
+	 * that 'index' points to.
+	 */
+	if (inrange && index > 0)
+		index -= 1;
+	run_consolidate(run, index);
+	run_consolidate(run, index + 1);
+
+	/*
+	 * a special case
+	 * I have to add extra range a tail.
+	 */
+	if (should_add_tail &&
+	    !run_add_entry(run, tail_vcn, tail_lcn, tail_len))
+		return false;
+
+	return true;
+}
+
+/*
+ * run_get_entry
+ *
+ * returns index-th mapped region
+ */
+bool run_get_entry(const struct runs_tree *run, size_t index, CLST *vcn,
+		   CLST *lcn, CLST *len)
+{
+	const struct ntfs_run *r;
+
+	if (index >= run->count)
+		return false;
+
+	r = run->runs_ + index;
+
+	if (!r->len)
+		return false;
+
+	if (vcn)
+		*vcn = r->vcn;
+	if (lcn)
+		*lcn = r->lcn;
+	if (len)
+		*len = r->len;
+	return true;
+}
+
+/*
+ * run_packed_size
+ *
+ * calculates the size of packed int64
+ */
+static inline int run_packed_size(const s64 *n)
+{
+#ifdef __BIG_ENDIAN
+	const u8 *p = (const u8 *)n + sizeof(*n) - 1;
+
+	if (*n >= 0) {
+		if (p[-7] || p[-6] || p[-5] || p[-4])
+			p -= 4;
+		if (p[-3] || p[-2])
+			p -= 2;
+		if (p[-1])
+			p -= 1;
+		if (p[0] & 0x80)
+			p -= 1;
+	} else {
+		if (p[-7] != 0xff || p[-6] != 0xff || p[-5] != 0xff ||
+		    p[-4] != 0xff)
+			p -= 4;
+		if (p[-3] != 0xff || p[-2] != 0xff)
+			p -= 2;
+		if (p[-1] != 0xff)
+			p -= 1;
+		if (!(p[0] & 0x80))
+			p -= 1;
+	}
+	return (const u8 *)n + sizeof(*n) - p;
+#else
+	const u8 *p = (const u8 *)n;
+
+	if (*n >= 0) {
+		if (p[7] || p[6] || p[5] || p[4])
+			p += 4;
+		if (p[3] || p[2])
+			p += 2;
+		if (p[1])
+			p += 1;
+		if (p[0] & 0x80)
+			p += 1;
+	} else {
+		if (p[7] != 0xff || p[6] != 0xff || p[5] != 0xff ||
+		    p[4] != 0xff)
+			p += 4;
+		if (p[3] != 0xff || p[2] != 0xff)
+			p += 2;
+		if (p[1] != 0xff)
+			p += 1;
+		if (!(p[0] & 0x80))
+			p += 1;
+	}
+
+	return 1 + p - (const u8 *)n;
+#endif
+}
+
+/*
+ * run_pack
+ *
+ * packs runs into buffer
+ * packed_vcns - how much runs we have packed
+ * packed_size - how much bytes we have used run_buf
+ */
+int run_pack(const struct runs_tree *run, CLST svcn, CLST len, u8 *run_buf,
+	     u32 run_buf_size, CLST *packed_vcns)
+{
+	CLST next_vcn, vcn, lcn;
+	CLST prev_lcn = 0;
+	CLST evcn1 = svcn + len;
+	int packed_size = 0;
+	size_t i;
+	bool ok;
+	s64 dlcn, len64;
+	int offset_size, size_size, t;
+	const u8 *p;
+
+	next_vcn = vcn = svcn;
+
+	*packed_vcns = 0;
+
+	if (!len)
+		goto out;
+
+	ok = run_lookup_entry(run, vcn, &lcn, &len, &i);
+
+	if (!ok)
+		goto error;
+
+	if (next_vcn != vcn)
+		goto error;
+
+next_entry:
+	/* offset of current fragment relatively to previous fragment */
+	dlcn = 0;
+	next_vcn = vcn + len;
+
+	if (next_vcn > evcn1)
+		len = evcn1 - vcn;
+
+	/*
+	 * mirror of len, but signed, because run_packed_size()
+	 * works with signed int only
+	 */
+	len64 = len;
+
+	/* how much bytes is packed len64 */
+	size_size = run_packed_size(&len64);
+
+	/* offset_size - how much bytes is packed dlcn */
+	if (lcn == SPARSE_LCN)
+		offset_size = 0;
+	else {
+		/* NOTE: lcn can be less than prev_lcn! */
+		dlcn = (s64)lcn - prev_lcn;
+		offset_size = run_packed_size(&dlcn);
+		prev_lcn = lcn;
+	}
+
+	t = run_buf_size - packed_size - 2 - offset_size;
+	if (t <= 0)
+		goto out;
+
+	/* can we store this entire run */
+	if (t < size_size)
+		goto out;
+
+	if (!run_buf)
+		goto skip_pack;
+
+	p = (u8 *)&len64;
+
+	/* pack run header */
+	run_buf[0] = ((u8)(size_size | (offset_size << 4)));
+	run_buf += 1;
+
+	/* Pack the length of run */
+	switch (size_size) {
+#ifdef __BIG_ENDIAN
+	case 8:
+		run_buf[7] = p[0];
+		fallthrough;
+	case 7:
+		run_buf[6] = p[1];
+		fallthrough;
+	case 6:
+		run_buf[5] = p[2];
+		fallthrough;
+	case 5:
+		run_buf[4] = p[3];
+		fallthrough;
+	case 4:
+		run_buf[3] = p[4];
+		fallthrough;
+	case 3:
+		run_buf[2] = p[5];
+		fallthrough;
+	case 2:
+		run_buf[1] = p[6];
+		fallthrough;
+	case 1:
+		run_buf[0] = p[7];
+#else
+	case 8:
+		run_buf[7] = p[7];
+		fallthrough;
+	case 7:
+		run_buf[6] = p[6];
+		fallthrough;
+	case 6:
+		run_buf[5] = p[5];
+		fallthrough;
+	case 5:
+		run_buf[4] = p[4];
+		fallthrough;
+	case 4:
+		run_buf[3] = p[3];
+		fallthrough;
+	case 3:
+		run_buf[2] = p[2];
+		fallthrough;
+	case 2:
+		run_buf[1] = p[1];
+		fallthrough;
+	case 1:
+		run_buf[0] = p[0];
+#endif
+	}
+
+	run_buf += size_size;
+	p = (u8 *)&dlcn;
+
+	/* Pack the offset from previous lcn */
+	switch (offset_size) {
+#ifdef __BIG_ENDIAN
+	case 8:
+		run_buf[7] = p[0];
+		fallthrough;
+	case 7:
+		run_buf[6] = p[1];
+		fallthrough;
+	case 6:
+		run_buf[5] = p[2];
+		fallthrough;
+	case 5:
+		run_buf[4] = p[3];
+		fallthrough;
+	case 4:
+		run_buf[3] = p[4];
+		fallthrough;
+	case 3:
+		run_buf[2] = p[5];
+		fallthrough;
+	case 2:
+		run_buf[1] = p[6];
+		fallthrough;
+	case 1:
+		run_buf[0] = p[7];
+#else
+	case 8:
+		run_buf[7] = p[7];
+		fallthrough;
+	case 7:
+		run_buf[6] = p[6];
+		fallthrough;
+	case 6:
+		run_buf[5] = p[5];
+		fallthrough;
+	case 5:
+		run_buf[4] = p[4];
+		fallthrough;
+	case 4:
+		run_buf[3] = p[3];
+		fallthrough;
+	case 3:
+		run_buf[2] = p[2];
+		fallthrough;
+	case 2:
+		run_buf[1] = p[1];
+		fallthrough;
+	case 1:
+		run_buf[0] = p[0];
+#endif
+	}
+
+	run_buf += offset_size;
+
+skip_pack:
+
+	packed_size += 1 + offset_size + size_size;
+	*packed_vcns += len;
+
+	if (packed_size + 1 >= run_buf_size || next_vcn >= evcn1)
+		goto out;
+
+	ok = run_get_entry(run, ++i, &vcn, &lcn, &len);
+	if (!ok)
+		goto error;
+
+	if (next_vcn != vcn)
+		goto error;
+
+	goto next_entry;
+
+out:
+	/* Store last zero */
+	if (run_buf)
+		run_buf[0] = 0;
+
+	return packed_size + 1;
+
+error:
+	return -EOPNOTSUPP;
+}
+
+/*
+ * run_unpack
+ *
+ * unpacks packed runs from "run_buf"
+ * returns error, if negative, or real used bytes
+ */
+int run_unpack(struct runs_tree *run, ntfs_sb_info *sbi, CLST ino, CLST svcn,
+	       CLST evcn, const u8 *run_buf, u32 run_buf_size)
+{
+	u64 prev_lcn, vcn;
+	const u8 *run_last, *run_0;
+	u64 lcn, len;
+	/* size_size - how much bytes is packed len */
+	u8 size_size;
+	/* offset_size - how much bytes is packed dlcn */
+	u8 offset_size;
+	u8 *p;
+	u64 next_vcn;
+	s64 dlcn;
+
+	/* Check for empty */
+	if (evcn + 1 == svcn)
+		return 0;
+
+	if (evcn < svcn)
+		return -EINVAL;
+
+	run_0 = run_buf;
+	run_last = run_buf + run_buf_size;
+	prev_lcn = 0;
+	vcn = svcn;
+
+	/* Read all runs the chain */
+next_run:
+	/* size_size - how much bytes is packed len */
+	size_size = *run_buf & 0xF;
+	/* offset_size - how much bytes is packed dlcn */
+	offset_size = *run_buf++ >> 4;
+
+	/*
+	 * Unpack runs.
+	 * NOTE: runs are stored little endian order
+	 * "len" is unsigned value, "dlcn" is signed
+	 */
+	if (!size_size)
+		goto out;
+
+	/*
+	 * Large positive number requires to store 5 bytes
+	 * e.g.: 05 FF 7E FF FF 00 00 00
+	 */
+	len = 0;
+	p = (u8 *)&len;
+
+	switch (size_size) {
+	default:
+error:
+		return -EINVAL;
+
+#ifdef __BIG_ENDIAN
+	case 8:
+		p[0] = run_buf[7];
+		fallthrough;
+	case 7:
+		p[1] = run_buf[6];
+		fallthrough;
+	case 6:
+		p[2] = run_buf[5];
+		fallthrough;
+	case 5:
+		p[3] = run_buf[4];
+		fallthrough;
+	case 4:
+		p[4] = run_buf[3];
+		fallthrough;
+	case 3:
+		p[5] = run_buf[2];
+		fallthrough;
+	case 2:
+		p[6] = run_buf[1];
+		fallthrough;
+	case 1:
+		p[7] = run_buf[0];
+#else
+	case 8:
+		p[7] = run_buf[7];
+		fallthrough;
+	case 7:
+		p[6] = run_buf[6];
+		fallthrough;
+	case 6:
+		p[5] = run_buf[5];
+		fallthrough;
+	case 5:
+		p[4] = run_buf[4];
+		fallthrough;
+	case 4:
+		p[3] = run_buf[3];
+		fallthrough;
+	case 3:
+		p[2] = run_buf[2];
+		fallthrough;
+	case 2:
+		p[1] = run_buf[1];
+		fallthrough;
+	case 1:
+		p[0] = run_buf[0];
+#endif
+	}
+
+	/* skip size_size */
+	run_buf += size_size;
+
+	if (!len)
+		goto error;
+
+	if (!offset_size) {
+		lcn = SPARSE_LCN;
+		goto next_vcn;
+	}
+	/* Check sign */
+	dlcn = (run_buf[offset_size - 1] & 0x80) ? (s64)-1 : 0;
+	p = (u8 *)&dlcn;
+
+	switch (offset_size) {
+	default:
+		goto error;
+
+#ifdef __BIG_ENDIAN
+	case 8:
+		p[0] = run_buf[7];
+		fallthrough;
+	case 7:
+		p[1] = run_buf[6];
+		fallthrough;
+	case 6:
+		p[2] = run_buf[5];
+		fallthrough;
+	case 5:
+		p[3] = run_buf[4];
+		fallthrough;
+	case 4:
+		p[4] = run_buf[3];
+		fallthrough;
+	case 3:
+		p[5] = run_buf[2];
+		fallthrough;
+	case 2:
+		p[6] = run_buf[1];
+		fallthrough;
+	case 1:
+		p[7] = run_buf[0];
+#else
+	case 8:
+		p[7] = run_buf[7];
+		fallthrough;
+	case 7:
+		p[6] = run_buf[6];
+		fallthrough;
+	case 6:
+		p[5] = run_buf[5];
+		fallthrough;
+	case 5:
+		p[4] = run_buf[4];
+		fallthrough;
+	case 4:
+		p[3] = run_buf[3];
+		fallthrough;
+	case 3:
+		p[2] = run_buf[2];
+		fallthrough;
+	case 2:
+		p[1] = run_buf[1];
+		fallthrough;
+	case 1:
+		p[0] = run_buf[0];
+#endif
+	}
+
+	/* skip offset_size */
+	run_buf += offset_size;
+	lcn = prev_lcn + dlcn;
+	prev_lcn = lcn;
+
+next_vcn:
+	next_vcn = vcn + len;
+	/* check boundary */
+	if (next_vcn > evcn + 1)
+		goto error;
+
+#ifndef NTFS3_64BIT_CLUSTER
+	if ((vcn >> 32)
+	    /* 0xffffffffffffffff is a valid 'lcn' */
+	    || (lcn + 1) > 0x100000000ull || (len >> 32)) {
+		goto error;
+	}
+#endif
+
+	if (!run)
+		; /* called from check_attr(fslog.c) to check run */
+	else if ((size_t)run == 1) {
+		/* called from ni_delete_all to free clusters without storing in run */
+		if (lcn != SPARSE_LCN)
+			mark_as_free_ex(sbi, lcn, len, true);
+	} else if (!run_add_entry(run, vcn, lcn, len))
+		return -ENOMEM;
+
+	if (lcn != SPARSE_LCN && lcn + len > sbi->used.bitmap.nbits)
+		return -EINVAL;
+
+	vcn = next_vcn;
+
+	if (run_buf < run_last)
+		goto next_run;
+
+out:
+	/* Check vcn consistency */
+	if (vcn == evcn + 1)
+		return run_buf - run_0;
+
+	return -EINVAL;
+}
+
+#ifdef NTFS3_CHECK_FREE_CLST
+/*
+ * run_unpack_ex
+ *
+ * unpacks packed runs from "run_buf"
+ * checks unpacked runs to be used in bitmap
+ * returns error, if negative, or real used bytes
+ */
+int run_unpack_ex(struct runs_tree *run, ntfs_sb_info *sbi, CLST ino, CLST svcn,
+		  CLST evcn, const u8 *run_buf, u32 run_buf_size)
+{
+	int ret, err;
+	CLST next_vcn, vcn, lcn, len;
+	size_t index;
+	bool ok;
+	wnd_bitmap *wnd;
+
+	ret = run_unpack(run, sbi, ino, svcn, evcn, run_buf, run_buf_size);
+	if (ret < 0)
+		return ret;
+
+	if (!sbi->used.bitmap.sb || !run || (size_t)run == 1)
+		return ret;
+
+	if (ino == MFT_REC_BADCLUST)
+		return ret;
+
+	next_vcn = vcn = svcn;
+	wnd = &sbi->used.bitmap;
+
+	for (ok = run_lookup_entry(run, vcn, &lcn, &len, &index);
+	     next_vcn <= evcn;
+	     ok = run_get_entry(run, ++index, &vcn, &lcn, &len)) {
+		CLST real_free, i;
+
+		if (!ok || next_vcn != vcn)
+			return -EINVAL;
+
+		next_vcn = vcn + len;
+
+		if (lcn == SPARSE_LCN)
+			continue;
+
+		if (sbi->flags & NTFS_FLAGS_NEED_REPLAY)
+			continue;
+
+next:
+		down_read_nested(&wnd->rw_lock, BITMAP_MUTEX_CLUSTERS);
+		/* Check for free blocks */
+		ok = wnd_is_used(wnd, lcn, len);
+		up_read(&wnd->rw_lock);
+		if (ok)
+			continue;
+
+		ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
+
+		if (!down_write_trylock(&wnd->rw_lock))
+			continue;
+
+		/* Find first free */
+		real_free = len;
+		while (real_free && !wnd_is_free(wnd, lcn, 1)) {
+			lcn += 1;
+			real_free -= 1;
+		}
+
+		if (!real_free) {
+			up_write(&wnd->rw_lock);
+			continue;
+		}
+
+		/* Find total free */
+		i = 1;
+		while (i < real_free && wnd_is_free(wnd, lcn + i, 1))
+			i += 1;
+
+		real_free = i;
+
+		err = wnd_set_used(wnd, lcn, real_free);
+		up_write(&wnd->rw_lock);
+
+		if (err)
+			return err;
+
+		if (len != real_free) {
+			len -= real_free + 1;
+			lcn += real_free + 1;
+			goto next;
+		}
+	}
+
+	return ret;
+}
+#endif
+
+/*
+ * run_get_highest_vcn
+ *
+ * returns the highest vcn from a mapping pairs array
+ * it used while replaying log file
+ */
+int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn)
+{
+	const u8 *run = run_buf;
+	u64 vcn64 = vcn;
+	u8 size_size;
+	u8 offset_size;
+	u64 len;
+	u8 *p;
+
+next_run:
+	size_size = *run & 0xF;
+	if (!size_size)
+		goto out;
+
+	offset_size = *run++ >> 4;
+
+	len = 0;
+	p = (u8 *)&len;
+
+	switch (size_size) {
+	default:
+error:
+		return -EINVAL;
+
+#ifdef __BIG_ENDIAN
+	case 8:
+		p[0] = run[7];
+		fallthrough;
+	case 7:
+		p[1] = run[6];
+		fallthrough;
+	case 6:
+		p[2] = run[5];
+		fallthrough;
+	case 5:
+		p[3] = run[4];
+		fallthrough;
+	case 4:
+		p[4] = run[3];
+		fallthrough;
+	case 3:
+		p[5] = run[2];
+		fallthrough;
+	case 2:
+		p[6] = run[1];
+		fallthrough;
+	case 1:
+		p[7] = run[0];
+#else
+	case 8:
+		p[7] = run[7];
+		fallthrough;
+	case 7:
+		p[6] = run[6];
+		fallthrough;
+	case 6:
+		p[5] = run[5];
+		fallthrough;
+	case 5:
+		p[4] = run[4];
+		fallthrough;
+	case 4:
+		p[3] = run[3];
+		fallthrough;
+	case 3:
+		p[2] = run[2];
+		fallthrough;
+	case 2:
+		p[1] = run[1];
+		fallthrough;
+	case 1:
+		p[0] = run[0];
+#endif
+	}
+
+	/* skip size_size */
+	run += size_size;
+
+	if (!len)
+		goto error;
+
+	run += offset_size;
+
+#ifdef NTFS3_64BIT_CLUSTER
+	if ((vcn >> 32) || (len >> 32))
+		goto error;
+#endif
+	vcn64 += len;
+	goto next_run;
+
+out:
+	*highest_vcn = vcn64 - 1;
+	return 0;
+}
-- 
2.25.2


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

* Re: [PATCH v2 04/10] fs/ntfs3: Add file operations and implementation
  2020-08-21 16:25 [PATCH v2 04/10] fs/ntfs3: Add file operations and implementation Konstantin Komarov
@ 2020-08-23  9:48 ` Pali Rohár
  2020-08-27 16:19   ` Konstantin Komarov
  0 siblings, 1 reply; 3+ messages in thread
From: Pali Rohár @ 2020-08-23  9:48 UTC (permalink / raw)
  To: Konstantin Komarov; +Cc: viro, linux-kernel, linux-fsdevel

Hello Konstantin!

On Friday 21 August 2020 16:25:15 Konstantin Komarov wrote:
> diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
> new file mode 100644
> index 000000000000..5f1105f1283c
> --- /dev/null
> +++ b/fs/ntfs3/dir.c
> @@ -0,0 +1,529 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + *  linux/fs/ntfs3/dir.c
> + *
> + * Copyright (C) 2019-2020 Paragon Software GmbH, All rights reserved.
> + *
> + *  directory handling functions for ntfs-based filesystems
> + *
> + */
> +#include <linux/blkdev.h>
> +#include <linux/buffer_head.h>
> +#include <linux/fs.h>
> +#include <linux/iversion.h>
> +#include <linux/nls.h>
> +
> +#include "debug.h"
> +#include "ntfs.h"
> +#include "ntfs_fs.h"
> +
> +/*
> + * Convert little endian Unicode 16 to UTF-8.

I guess that by "Unicode 16" you mean UTF-16, right?

Anyway, comment is incorrect as function does not support UTF-16 nor
UTF-8. This function works only with UCS-2 encoding (not full UTD-16)
and converts input buffer to NLS encoding, not UTF-8. Moreover kernel's
NLS API does not support full UTF-8 and NLS's UTF-8 encoding is semi
broken and limited to just 3-byte sequences. Which means it does not
allow to access all UNICODE filenames.

So result is that comment for uni_to_x8 function is incorrect.

I would suggest to not use NLS API for encoding from/to UTF-8, but
rather use utf16s_to_utf8s() and utf8s_to_utf16s() functions.

See for example how it is implemented in exfat driver:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/exfat/nls.c
Look for functions exfat_utf16_to_nls() and exfat_nls_to_utf16().

Ideally check if you can store character 💩 (Pile of Poo, U+1F4A9, does
not fit into 3byte UTF-8 sequence) into filename and if there is correct
interoperability between Windows and this new ntfs3 implementation.

> + */
> +int uni_to_x8(ntfs_sb_info *sbi, const struct le_str *uni, u8 *buf, int buf_len)
> +{
> +	const __le16 *ip = uni->name;
> +	u8 *op = buf;
> +	struct nls_table *nls = sbi->nls;
> +	int uni_len = uni->len;
> +
> +	static_assert(sizeof(wchar_t) == sizeof(__le16));
> +
> +	while (uni_len--) {
> +		u16 ec;
> +		int charlen;
> +
> +		if (buf_len < NLS_MAX_CHARSET_SIZE) {
> +			ntfs_warning(
> +				sbi->sb,
> +				"filename was truncated while converting.");
> +			break;
> +		}
> +
> +		ec = le16_to_cpu(*ip++);
> +		charlen = nls->uni2char(ec, op, buf_len);
> +
> +		if (charlen > 0) {
> +			op += charlen;
> +			buf_len -= charlen;
> +		} else {
> +			*op++ = ':';
> +			op = hex_byte_pack(op, ec >> 8);
> +			op = hex_byte_pack(op, ec);
> +			buf_len -= 5;
> +		}
> +	}
> +
> +	*op = 0;
> +	return op - buf;
> +}
> +
> +static inline u8 get_digit(u8 d)
> +{
> +	u8 x = d & 0xf;
> +
> +	return x <= 9 ? ('0' + x) : ('A' + x - 10);
> +}
> +
> +/*
> + * Convert input string to unicode
> + * max_ulen - maximum possible unicode length
> + * endian - unicode endian
> + */
> +int x8_to_uni(ntfs_sb_info *sbi, const u8 *name, u32 name_len,
> +	      struct cpu_str *uni, u32 max_ulen, enum utf16_endian endian)
> +{
> +	int i, ret, clen;
> +	u32 tail;
> +	const u8 *str = name;
> +	const u8 *end = name + name_len;
> +	u16 *uname = uni->name;
> +	struct nls_table *nls = sbi->nls;
> +	int warn = 0;
> +
> +	static_assert(sizeof(wchar_t) == sizeof(u16));
> +
> +	for (ret = 0; str < end; ret += 1, uname += 1, str += clen) {
> +		if (ret >= max_ulen)
> +			return -ENAMETOOLONG;
> +		tail = end - str;
> +
> +		clen = nls->char2uni(str, tail, uname);
> +		if (clen > 0)
> +			continue;
> +
> +		if (!warn) {
> +			warn = 1;
> +			ntfs_warning(
> +				sbi->sb,
> +				"%s -> unicode failed: '%.*s', pos %d, chars %x %x %x",
> +				nls->charset, name_len, name, (int)(str - name),
> +				str[0], tail > 1 ? str[1] : 0,
> +				tail > 2 ? str[2] : 0);
> +		}
> +
> +		if (ret + 3 > max_ulen)
> +			return -ENAMETOOLONG;
> +
> +		uname[0] = '%';
> +		uname[1] = get_digit(*str >> 4);
> +		uname[2] = get_digit(*str >> 0);
> +
> +		uname += 2;
> +		ret += 2; // +1 will be added in for ( .... )
> +		clen = 1;
> +	}
> +
> +#ifdef __BIG_ENDIAN
> +	if (endian == UTF16_LITTLE_ENDIAN) {
> +		__le16 *uname = (__le16 *)uni->name;
> +
> +		for (i = 0; i < ret; i++, uname++)
> +			*uname = cpu_to_le16(*name);
> +	}
> +#else
> +	if (endian == UTF16_BIG_ENDIAN) {
> +		__be16 *uname = (__be16 *)uni->name;
> +
> +		for (i = 0; i < ret; i++, uname++)
> +			*uname = cpu_to_be16(*name);
> +	}
> +#endif
> +
> +	uni->len = ret;
> +	return ret;
> +}

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

* RE: [PATCH v2 04/10] fs/ntfs3: Add file operations and implementation
  2020-08-23  9:48 ` Pali Rohár
@ 2020-08-27 16:19   ` Konstantin Komarov
  0 siblings, 0 replies; 3+ messages in thread
From: Konstantin Komarov @ 2020-08-27 16:19 UTC (permalink / raw)
  To: Pali Rohár; +Cc: viro, linux-kernel, linux-fsdevel

From: Pali Rohár <pali@kernel.org>
Sent: Sunday, August 23, 2020 12:49 PM
> 
> Hello Konstantin!
> 
> On Friday 21 August 2020 16:25:15 Konstantin Komarov wrote:
> > diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
> > new file mode 100644
> > index 000000000000..5f1105f1283c
> > --- /dev/null
[]
> > +#include <linux/iversion.h>
> > +#include <linux/nls.h>
> > +
> > +#include "debug.h"
> > +#include "ntfs.h"
> > +#include "ntfs_fs.h"
> > +
> > +/*
> > + * Convert little endian Unicode 16 to UTF-8.
> 
> I guess that by "Unicode 16" you mean UTF-16, right?
> 
> Anyway, comment is incorrect as function does not support UTF-16 nor
> UTF-8. This function works only with UCS-2 encoding (not full UTD-16)
> and converts input buffer to NLS encoding, not UTF-8. Moreover kernel's
> NLS API does not support full UTF-8 and NLS's UTF-8 encoding is semi
> broken and limited to just 3-byte sequences. Which means it does not
> allow to access all UNICODE filenames.
> 
> So result is that comment for uni_to_x8 function is incorrect.
> 
> I would suggest to not use NLS API for encoding from/to UTF-8, but
> rather use utf16s_to_utf8s() and utf8s_to_utf16s() functions.
> 
> See for example how it is implemented in exfat driver:
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/exfat/nls.c
> Look for functions exfat_utf16_to_nls() and exfat_nls_to_utf16().
> 

Hi Pali! Thank you. In V3 we'll rewrite this part alike to how its done in exfat. We also faced an issue in our encodings support tests once moved to utf8s_to_utf16s(). Had to copy the utf8s_to_utf16s() implementation from the kernel into our code, fix the issues for the problem part (also, fixed numerous sparse warnings for that code) and use the modified version. Could you please take a look there in V3 once posted, and share the feedback on do these changes to utf8s_to_utf16s() worth to be prepared separately as the patch to adapt in kernel?

> Ideally check if you can store character 💩 (Pile of Poo, U+1F4A9, does
> not fit into 3byte UTF-8 sequence) into filename and if there is correct
> interoperability between Windows and this new ntfs3 implementation.
> 

This is great test case. We are placing it into our set of compatibility tests.


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

end of thread, other threads:[~2020-08-27 16:19 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-08-21 16:25 [PATCH v2 04/10] fs/ntfs3: Add file operations and implementation Konstantin Komarov
2020-08-23  9:48 ` Pali Rohár
2020-08-27 16:19   ` 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).