[RFC PATCH] fs/ntfs3: Add basic support for alternative data streams

Konstantin Komarov posted 1 patch 9 hours ago
fs/ntfs3/attrib.c   | 157 +++++++++++++++----------
fs/ntfs3/attrlist.c |  15 ++-
fs/ntfs3/dir.c      |  91 +++++++++-----
fs/ntfs3/file.c     |  23 +++-
fs/ntfs3/frecord.c  | 159 +++++++++++++++++++++----
fs/ntfs3/fsntfs.c   |  26 +++-
fs/ntfs3/inode.c    | 280 ++++++++++++++++++++++++++++++++++++++------
fs/ntfs3/namei.c    |  55 ++++++---
fs/ntfs3/ntfs.h     |   3 +-
fs/ntfs3/ntfs_fs.h  |  45 +++++--
fs/ntfs3/super.c    |  14 +++
11 files changed, 684 insertions(+), 184 deletions(-)
[RFC PATCH] fs/ntfs3: Add basic support for alternative data streams
Posted by Konstantin Komarov 9 hours ago
An ADS (alternative data stream) is a named $DATA (0x80) attribute.
Until now ntfs3 ignored named data attributes entirely, so the only
stream reachable from userspace was the unnamed one.

Introduce a colon-delimited name syntax so streams can be reached
through the regular VFS interfaces. ntfs_nls_to_utf16() now splits a
lookup name at ':' and stores the stream part in cpu_str::ads_len (the
previously unused padding byte). ntfs_iget5_flags() first instantiates
the base inode, then allocates a second inode whose ->base points at it
and whose ->file.ads holds the stream name. ntfs_test_inode() compares
the stream name as well as the MFT reference, so base and streams get
distinct inodes for the same record.

Because the MFT record belongs to the base inode, the many helpers in
frecord.c and attrlist.c that operate on the record redirect to
ni->base, and ni_lock() and friends take the base inode's mutex. The
attrib.c paths that used to hardcode an unnamed $DATA lookup now pass
ni->file.ads.{name,len}. ni_write_inode() and ni_write_parents() are
no-ops for stream inodes, and ntfs_setattr() drops ATTR_SIZE for them.

Usage, for a file with streams 'ads1' and 'ads2':

  cat file:query_streams  - list stream names, one per line
  cat file:ads1           - read a stream
  touch file:ads3         - create a stream on an existing file
  rm file:ads1            - remove a stream

The pseudo-stream 'query_streams' is handled in ntfs_file_read_iter()
via ni_query_ads(), which enumerates named $DATA attributes and returns
their names separated by '\n'.

The feature is controlled by the new 'ads' mount option, enabled by
default; mount with 'ads=0' to restore the previous behaviour.

Not implemented yet:
 - creating a file and a stream in a single call
 - renaming (moving) a stream

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
---
 fs/ntfs3/attrib.c   | 157 +++++++++++++++----------
 fs/ntfs3/attrlist.c |  15 ++-
 fs/ntfs3/dir.c      |  91 +++++++++-----
 fs/ntfs3/file.c     |  23 +++-
 fs/ntfs3/frecord.c  | 159 +++++++++++++++++++++----
 fs/ntfs3/fsntfs.c   |  26 +++-
 fs/ntfs3/inode.c    | 280 ++++++++++++++++++++++++++++++++++++++------
 fs/ntfs3/namei.c    |  55 ++++++---
 fs/ntfs3/ntfs.h     |   3 +-
 fs/ntfs3/ntfs_fs.h  |  45 +++++--
 fs/ntfs3/super.c    |  14 +++
 11 files changed, 684 insertions(+), 184 deletions(-)

diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index 3628a737d7cc..b1c315206ffa 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -278,7 +278,7 @@ int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr,
 	next = Add2Ptr(attr, asize);
 	aoff = PtrOffset(rec, attr);
 	rsize = le32_to_cpu(attr->res.data_size);
-	is_data = attr->type == ATTR_DATA && !attr->name_len;
+	is_data = attr->type == ATTR_DATA;
 
 	/* len - how many clusters required to store 'rsize' bytes */
 	if (is_attr_compressed(attr)) {
@@ -433,6 +433,7 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type,
 		     struct ATTRIB **ret, bool no_da)
 {
 	int err = 0;
+	struct ntfs_inode *nb = ni->base;
 	struct ntfs_sb_info *sbi = ni->mi.sbi;
 	u8 cluster_bits = sbi->cluster_bits;
 	bool is_mft = ni->mi.rno == MFT_REC_MFT && type == ATTR_DATA &&
@@ -703,8 +704,8 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type,
 			goto again;
 		}
 
-		if (!ni->attr_list.size) {
-			err = ni_create_attr_list(ni);
+		if (!nb->attr_list.size) {
+			err = ni_create_attr_list(nb);
 			/* In case of error layout of records is not changed. */
 			if (err)
 				goto undo_2;
@@ -877,8 +878,7 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type,
 	if (ret)
 		*ret = attr_b;
 
-	if (((type == ATTR_DATA && !name_len) ||
-	     (type == ATTR_ALLOC && name == I30_NAME))) {
+	if ((type == ATTR_DATA || (type == ATTR_ALLOC && name == I30_NAME))) {
 		/* Update inode_set_bytes. */
 		if (attr_b->non_res &&
 		    inode_get_bytes(&ni->vfs_inode) != new_alloc) {
@@ -1001,7 +1001,6 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 	struct ATTRIB *attr, *attr_b;
 	struct ATTR_LIST_ENTRY *le, *le_b;
 	struct mft_inode *mi, *mi_b;
-	struct page *page;
 	CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0;
 	CLST alloc, evcn;
 	unsigned fr;
@@ -1026,7 +1025,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 	step = 0;
 
 	le_b = NULL;
-	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+			      ni->file.ads.len, NULL, &mi_b);
 	if (!attr_b) {
 		err = -ENOENT;
 		goto out;
@@ -1036,11 +1036,15 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 		u32 data_size = le32_to_cpu(attr_b->res.data_size);
 		*lcn = RESIDENT_LCN;
 		*len = data_size;
-		if (res && data_size) {
-			page = alloc_page(GFP_KERNEL);
-			if (!page) {
-				err = -ENOMEM;
-			} else {
+		if (res) {
+			*res = NULL;
+			if (data_size) {
+				struct page *page = alloc_page(GFP_KERNEL);
+				if (!page) {
+					err = -ENOMEM;
+					goto out;
+				}
+
 				*res = page_address(page);
 				memcpy(*res, resident_data(attr_b), data_size);
 			}
@@ -1067,7 +1071,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 	mi = mi_b;
 
 	if (le_b && (vcn < svcn || evcn1 <= vcn)) {
-		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+				    ni->file.ads.name, ni->file.ads.len, &vcn,
 				    &mi);
 		if (!attr) {
 			err = -EINVAL;
@@ -1140,8 +1145,9 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 		if (vcn < svcn || evcn1 <= vcn) {
 			struct ATTRIB *attr2;
 			/* Load runs for truncated vcn. */
-			attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL,
-					     0, &vcn, &mi);
+			attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA,
+					     ni->file.ads.name,
+					     ni->file.ads.len, &vcn, &mi);
 			if (!attr2) {
 				err = -EINVAL;
 				goto out;
@@ -1155,8 +1161,9 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 		if (vcn0 < svcn || evcn1 <= vcn0) {
 			struct ATTRIB *attr2;
 
-			attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA, NULL,
-					     0, &vcn0, &mi);
+			attr2 = ni_find_attr(ni, attr_b, &le_b, ATTR_DATA,
+					     ni->file.ads.name,
+					     ni->file.ads.len, &vcn0, &mi);
 			if (!attr2) {
 				err = -EINVAL;
 				goto out;
@@ -1269,8 +1276,9 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 				goto undo1;
 			/* Layout of records is changed. */
 			le_b = NULL;
-			attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
-					      0, NULL, &mi_b);
+			attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+					      ni->file.ads.name,
+					      ni->file.ads.len, NULL, &mi_b);
 			if (!attr_b) {
 				err = -ENOENT;
 				goto out;
@@ -1300,7 +1308,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 	svcn = evcn1;
 
 	/* Estimate next attribute. */
-	attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi);
+	attr = ni_find_attr(ni, attr, &le, ATTR_DATA, ni->file.ads.name,
+			    ni->file.ads.len, &svcn, &mi);
 
 	if (!attr) {
 		/* Insert new attribute segment. */
@@ -1333,7 +1342,8 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 			goto out;
 		}
 
-		attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0, &le->id);
+		attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, ni->file.ads.name,
+				    ni->file.ads.len, &le->id);
 		if (!attr) {
 			err = -EINVAL;
 			goto out;
@@ -1362,9 +1372,10 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
 
 ins_ext:
 	if (evcn1 > next_svcn) {
-		err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
-					    next_svcn, evcn1 - next_svcn,
-					    attr_b->flags, &attr, &mi, NULL);
+		err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name,
+					    ni->file.ads.len, run, next_svcn,
+					    evcn1 - next_svcn, attr_b->flags,
+					    &attr, &mi, NULL);
 		if (err)
 			goto out;
 	}
@@ -1398,7 +1409,8 @@ int attr_data_write_resident(struct ntfs_inode *ni, struct folio *folio)
 	struct ATTRIB *attr;
 	u32 data_size;
 
-	attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
+	attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, ni->file.ads.name,
+			    ni->file.ads.len, NULL, &mi);
 	if (!attr)
 		return -EINVAL;
 
@@ -1775,7 +1787,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
 	u64 total_size, valid_size, data_size;
 
 	le_b = NULL;
-	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+			      ni->file.ads.len, NULL, &mi_b);
 	if (!attr_b)
 		return -ENOENT;
 
@@ -1798,7 +1811,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
 		goto out;
 	} else {
 		le = le_b;
-		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+				    ni->file.ads.name, ni->file.ads.len, &vcn,
 				    &mi);
 		if (!attr) {
 			err = -EINVAL;
@@ -1885,8 +1899,9 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
 				goto out;
 			/* Layout of records is changed. */
 			le_b = NULL;
-			attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL,
-					      0, NULL, &mi_b);
+			attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+					      ni->file.ads.name,
+					      ni->file.ads.len, NULL, &mi_b);
 			if (!attr_b) {
 				err = -ENOENT;
 				goto out;
@@ -1902,7 +1917,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
 	svcn = evcn1;
 
 	/* Estimate next attribute. */
-	attr = ni_find_attr(ni, attr, &le, ATTR_DATA, NULL, 0, &svcn, &mi);
+	attr = ni_find_attr(ni, attr, &le, ATTR_DATA, ni->file.ads.name,
+			    ni->file.ads.len, &svcn, &mi);
 
 	if (attr) {
 		CLST alloc = bytes_to_cluster(
@@ -1931,7 +1947,8 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
 				goto out;
 			}
 
-			attr = mi_find_attr(ni, mi, NULL, ATTR_DATA, NULL, 0,
+			attr = mi_find_attr(ni, mi, NULL, ATTR_DATA,
+					    ni->file.ads.name, ni->file.ads.len,
 					    &le->id);
 			if (!attr) {
 				err = -EINVAL;
@@ -1962,9 +1979,10 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
 	}
 ins_ext:
 	if (evcn1 > next_svcn) {
-		err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
-					    next_svcn, evcn1 - next_svcn,
-					    attr_b->flags, &attr, &mi, NULL);
+		err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name,
+					    ni->file.ads.len, run, next_svcn,
+					    evcn1 - next_svcn, attr_b->flags,
+					    &attr, &mi, NULL);
 		if (err)
 			goto out;
 	}
@@ -2007,7 +2025,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 		return 0;
 
 	le_b = NULL;
-	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+			      ni->file.ads.len, NULL, &mi_b);
 	if (!attr_b)
 		return -ENOENT;
 
@@ -2037,7 +2056,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 
 		/* Simple truncate file at 'vbo'. */
 		truncate_setsize(&ni->vfs_inode, vbo);
-		err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, vbo,
+		err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name,
+				    ni->file.ads.len, &ni->file.run, vbo,
 				    &valid_size, true);
 
 		if (!err && valid_size < ni->i_valid)
@@ -2061,7 +2081,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 			/*
 			 * The requested range is full in delayed clusters.
 			 */
-			err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, run,
+			err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name,
+					       ni->file.ads.len, run,
 					       i_size - bytes, NULL, false,
 					       NULL, true);
 			goto out;
@@ -2074,7 +2095,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 
 		/* Layout of records maybe changed. */
 		le_b = NULL;
-		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+				      ni->file.ads.name, ni->file.ads.len, NULL,
 				      &mi_b);
 		if (!attr_b || !attr_b->non_res) {
 			err = -ENOENT;
@@ -2105,7 +2127,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 	}
 
 	le = le_b;
-	attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, &mi);
+	attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, ni->file.ads.name,
+			    ni->file.ads.len, &vcn, &mi);
 	if (!attr) {
 		err = -EINVAL;
 		goto out;
@@ -2169,7 +2192,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 			next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
 			if (next_svcn + eat + done < evcn1) {
 				err = ni_insert_nonresident(
-					ni, ATTR_DATA, NULL, 0, run, next_svcn,
+					ni, ATTR_DATA, ni->file.ads.name,
+					ni->file.ads.len, run, next_svcn,
 					evcn1 - eat - next_svcn, a_flags, &attr,
 					&mi, &le);
 				if (err)
@@ -2209,7 +2233,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 
 				/* Look for required attribute. */
 				attr = mi_find_attr(ni, mi, NULL, ATTR_DATA,
-						    NULL, 0, &le->id);
+						    ni->file.ads.name,
+						    ni->file.ads.len, &le->id);
 				if (!attr) {
 					err = -EINVAL;
 					goto out;
@@ -2232,7 +2257,8 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 
 	if (!attr_b) {
 		le_b = NULL;
-		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+				      ni->file.ads.name, ni->file.ads.len, NULL,
 				      &mi_b);
 		if (!attr_b) {
 			err = -ENOENT;
@@ -2293,7 +2319,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
 		return 0;
 
 	le_b = NULL;
-	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+			      ni->file.ads.len, NULL, &mi_b);
 	if (!attr_b)
 		return -ENOENT;
 
@@ -2364,7 +2391,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
 		goto bad_inode;
 	} else {
 		le = le_b;
-		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+				    ni->file.ads.name, ni->file.ads.len, &vcn,
 				    &mi);
 		if (!attr) {
 			err = -EINVAL;
@@ -2416,10 +2444,10 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
 		next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
 		if (next_svcn < evcn1) {
 			/* Insert new attribute segment. */
-			err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
-						    next_svcn,
-						    evcn1 - next_svcn, a_flags,
-						    &attr, &mi, &le);
+			err = ni_insert_nonresident(
+				ni, ATTR_DATA, ni->file.ads.name,
+				ni->file.ads.len, run, next_svcn,
+				evcn1 - next_svcn, a_flags, &attr, &mi, &le);
 			if (err)
 				goto undo_punch;
 
@@ -2454,7 +2482,8 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
 		goto out;
 
 	if (!attr_b) {
-		attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL,
+		attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA,
+				      ni->file.ads.name, ni->file.ads.len, NULL,
 				      &mi_b);
 		if (!attr_b) {
 			err = -EINVAL;
@@ -2512,7 +2541,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 		return 0;
 
 	le_b = NULL;
-	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL, &mi_b);
+	attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, ni->file.ads.name,
+			      ni->file.ads.len, NULL, &mi_b);
 	if (!attr_b)
 		return -ENOENT;
 
@@ -2559,11 +2589,13 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 	down_write(&ni->file.run_lock);
 
 	if (!attr_b->non_res) {
-		err = attr_set_size(ni, ATTR_DATA, NULL, 0, run,
-				    data_size + bytes, NULL, false);
+		err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name,
+				    ni->file.ads.len, run, data_size + bytes,
+				    NULL, false);
 
 		le_b = NULL;
-		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+				      ni->file.ads.name, ni->file.ads.len, NULL,
 				      &mi_b);
 		if (!attr_b) {
 			err = -EINVAL;
@@ -2604,7 +2636,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 		goto bad_inode;
 	} else {
 		le = le_b;
-		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+				    ni->file.ads.name, ni->file.ads.len, &vcn,
 				    &mi);
 		if (!attr) {
 			err = -EINVAL;
@@ -2647,12 +2680,14 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 	}
 
 	if (next_svcn < evcn1 + len) {
-		err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
-					    next_svcn, evcn1 + len - next_svcn,
-					    a_flags, NULL, NULL, NULL);
+		err = ni_insert_nonresident(ni, ATTR_DATA, ni->file.ads.name,
+					    ni->file.ads.len, run, next_svcn,
+					    evcn1 + len - next_svcn, a_flags,
+					    NULL, NULL, NULL);
 
 		le_b = NULL;
-		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA, NULL, 0, NULL,
+		attr_b = ni_find_attr(ni, NULL, &le_b, ATTR_DATA,
+				      ni->file.ads.name, ni->file.ads.len, NULL,
 				      &mi_b);
 		if (!attr_b) {
 			err = -EINVAL;
@@ -2709,7 +2744,8 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 		goto bad_inode;
 	} else {
 		le = le_b;
-		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
+		attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA,
+				    ni->file.ads.name, ni->file.ads.len, &vcn,
 				    &mi);
 		if (!attr) {
 			goto bad_inode;
@@ -2754,7 +2790,8 @@ int attr_force_nonresident(struct ntfs_inode *ni)
 	struct ATTR_LIST_ENTRY *le = NULL;
 	struct mft_inode *mi;
 
-	attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, &mi);
+	attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, ni->file.ads.name,
+			    ni->file.ads.len, NULL, &mi);
 	if (!attr) {
 		_ntfs_bad_inode(&ni->vfs_inode);
 		return -ENOENT;
diff --git a/fs/ntfs3/attrlist.c b/fs/ntfs3/attrlist.c
index 270a29323530..8710560cf294 100644
--- a/fs/ntfs3/attrlist.c
+++ b/fs/ntfs3/attrlist.c
@@ -19,6 +19,7 @@
 static inline bool al_is_valid_le(const struct ntfs_inode *ni,
 				  struct ATTR_LIST_ENTRY *le)
 {
+	ni = ni->base;
 	if (!le || !ni->attr_list.le || !ni->attr_list.size)
 		return false;
 
@@ -28,6 +29,7 @@ static inline bool al_is_valid_le(const struct ntfs_inode *ni,
 
 void al_destroy(struct ntfs_inode *ni)
 {
+	ni = ni->base;
 	run_close(&ni->attr_list.run);
 	kvfree(ni->attr_list.le);
 	ni->attr_list.le = NULL;
@@ -47,6 +49,7 @@ int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
 	size_t lsize;
 	void *le = NULL;
 
+	ni = ni->base;
 	if (ni->attr_list.size)
 		return 0;
 
@@ -199,6 +202,7 @@ struct ATTR_LIST_ENTRY *al_find_ex(struct ntfs_inode *ni,
 	struct ATTR_LIST_ENTRY *ret = NULL;
 	u32 type_in = le32_to_cpu(type);
 
+	ni = ni->base;
 	while ((le = al_enumerate(ni, le))) {
 		u64 le_vcn;
 		int diff = le32_to_cpu(le->type) - type_in;
@@ -256,6 +260,7 @@ static struct ATTR_LIST_ENTRY *al_find_le_to_insert(struct ntfs_inode *ni,
 	struct ATTR_LIST_ENTRY *le = NULL, *prev;
 	u32 type_in = le32_to_cpu(type);
 
+	ni = ni->base;
 	/* List entries are sorted by type, name and VCN. */
 	while ((le = al_enumerate(ni, prev = le))) {
 		int diff = le32_to_cpu(le->type) - type_in;
@@ -305,6 +310,7 @@ int al_add_le(struct ntfs_inode *ni, enum ATTR_TYPE type, const __le16 *name,
 	u64 new_size;
 	typeof(ni->attr_list) *al = &ni->attr_list;
 
+	ni = ni->base;
 	/*
 	 * Compute the size of the new 'le'
 	 */
@@ -374,8 +380,10 @@ bool al_remove_le(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le)
 {
 	u16 size;
 	size_t off;
-	typeof(ni->attr_list) *al = &ni->attr_list;
+	typeof(ni->attr_list) *al;
 
+	ni = ni->base;
+	al = &ni->attr_list;
 	if (!al_is_valid_le(ni, le))
 		return false;
 
@@ -395,7 +403,10 @@ int al_update(struct ntfs_inode *ni, int sync)
 {
 	int err;
 	struct ATTRIB *attr;
-	typeof(ni->attr_list) *al = &ni->attr_list;
+	typeof(ni->attr_list) *al;
+
+	ni = ni->base;
+	al = &ni->attr_list;
 
 	if (!al->dirty || !al->size)
 		return 0;
diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index 6f8ad8428f49..eb9152e9fa22 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -184,7 +184,7 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
 		      struct cpu_str *uni, u32 max_ulen,
 		      enum utf16_endian endian)
 {
-	int ret, slen;
+	int ret, slen, i;
 	const u8 *end;
 	struct nls_table *nls = sbi->options->nls;
 	u16 *uname = uni->name;
@@ -194,50 +194,83 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
 	if (!nls) {
 		/* utf8 -> utf16 */
 		ret = _utf8s_to_utf16s(name, name_len, endian, uname, max_ulen);
-		uni->len = ret;
-		return ret;
-	}
-
-	for (ret = 0, end = name + name_len; name < end; ret++, name += slen) {
-		if (ret >= max_ulen)
-			return -ENAMETOOLONG;
+	} else {
+		for (ret = 0, end = name + name_len; name < end;
+		     ret++, name += slen) {
+			if (ret >= max_ulen)
+				return -ENAMETOOLONG;
 
-		slen = nls->char2uni(name, end - name, uname + ret);
-		if (!slen)
-			return -EINVAL;
-		if (slen < 0)
-			return slen;
-	}
+			slen = nls->char2uni(name, end - name, uname + ret);
+			if (!slen)
+				return -EINVAL;
+			if (slen < 0)
+				return slen;
+		}
 
 #ifdef __BIG_ENDIAN
-	if (endian == UTF16_LITTLE_ENDIAN) {
-		int i = ret;
+		if (endian == UTF16_LITTLE_ENDIAN) {
+			i = ret;
 
-		while (i--) {
-			__cpu_to_le16s(uname);
-			uname++;
+			while (i--) {
+				__cpu_to_le16s(uname);
+				uname++;
+			}
 		}
-	}
 #else
-	if (endian == UTF16_BIG_ENDIAN) {
-		int i = ret;
+		if (endian == UTF16_BIG_ENDIAN) {
+			i = ret;
 
-		while (i--) {
-			__cpu_to_be16s(uname);
-			uname++;
+			while (i--) {
+				__cpu_to_be16s(uname);
+				uname++;
+			}
 		}
-	}
 #endif
+	}
 
 	uni->len = ret;
+	uni->ads_len = 0;
+	if (ret > 0 && sbi->options->ads) {
+		uname = uni->name;
+		/* Find delimiter in range [1 : ret-2). */
+		for (i = 1; i + 1 < ret; i++) {
+			if (uname[i] == ':') {
+				uni->ads_len = ret - i - 1;
+				uni->len = i;
+				uname[i] = 0;
+				ret = i;
+
+				uname += i + 1;
+				i = uni->ads_len;
+				/* Return ADS name as little endian. Always */
+#ifdef __BIG_ENDIAN
+				if (endian == UTF16_LITTLE_ENDIAN) {
+					while (i--) {
+						__cpu_to_le16s(uname);
+						uname++;
+					}
+				}
+#else
+				if (endian == UTF16_BIG_ENDIAN) {
+					while (i--) {
+						__cpu_to_be16s(uname);
+						uname++;
+					}
+				}
+#endif
+				break;
+			}
+		}
+	}
+
 	return ret;
 }
 
 /*
  * dir_search_u - Helper function.
  */
-struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
-			   struct ntfs_fnd *fnd)
+struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni,
+			       struct ntfs_fnd *fnd, u32 flags)
 {
 	int err = 0;
 	struct super_block *sb = dir->i_sb;
@@ -267,7 +300,7 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
 		goto out;
 	}
 
-	inode = ntfs_iget5(sb, &e->ref, uni);
+	inode = ntfs_iget5_flags(sb, &e->ref, uni, flags);
 	if (!IS_ERR(inode) && is_bad_inode(inode)) {
 		iput(inode);
 		err = -EINVAL;
diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index 1eda8c31b054..2abf334bfa0c 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -753,7 +753,9 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 
 	setattr_copy(idmap, inode, attr);
 
-	if (mode != inode->i_mode) {
+	if (!is_ni_base(ni)) {
+		ia_valid &= ~ATTR_SIZE;
+	} else if (mode != inode->i_mode) {
 		err = ntfs_acl_chmod(idmap, dentry);
 		if (err)
 			goto out;
@@ -829,6 +831,22 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
 	if (!bytes)
 		return 0; /* skip atime */
 
+	if (ni->file.ads.len == ARRAY_SIZE(QUERY_STREAMS) &&
+	    !memcmp(ni->file.ads.name, QUERY_STREAMS, sizeof(QUERY_STREAMS))) {
+		/* Query ADS. */
+		if (unlikely(iocb->ki_flags & IOCB_DIRECT)) {
+			ntfs_inode_warn(
+				inode,
+				"direct I/O for streams is not supported");
+			return -EOPNOTSUPP;
+		}
+
+		inode_lock_shared(inode);
+		ret = ni_query_ads(ni, &iocb->ki_pos, iter);
+		inode_unlock_shared(inode);
+		return ret;
+	}
+
 	if (is_compressed(ni)) {
 		if (iocb->ki_flags & IOCB_DIRECT) {
 			ntfs_inode_warn(
@@ -1421,7 +1439,8 @@ static int ntfs_file_release(struct inode *inode, struct file *file)
 		down_write(&ni->file.run_lock);
 
 		/* Deallocate preallocated. */
-		err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run,
+		err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name,
+				       ni->file.ads.len, &ni->file.run,
 				       inode->i_size, &ni->i_valid, false, NULL,
 				       true);
 
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index 3ad309d38fe8..77a206d875c1 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -132,6 +132,13 @@ void ni_clear(struct ntfs_inode *ni)
 			ni->file.offs_folio = NULL;
 		}
 #endif
+		kfree(ni->file.ads.name);
+		ni->file.ads.name = NULL;
+	}
+
+	if (ni->base && ni->base != ni) {
+		iput(&ni->base->vfs_inode);
+		ni->base = NULL;
 	}
 
 	mi_clear(&ni->mi);
@@ -145,6 +152,7 @@ int ni_load_mi_ex(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi)
 	int err;
 	struct mft_inode *r;
 
+	ni = ni->base;
 	r = ni_find_mi(ni, rno);
 	if (r)
 		goto out;
@@ -171,6 +179,7 @@ int ni_load_mi(struct ntfs_inode *ni, const struct ATTR_LIST_ENTRY *le,
 {
 	u64 rno;
 
+	ni = ni->base;
 	if (!le) {
 		*mi = &ni->mi;
 		return 0;
@@ -197,6 +206,7 @@ struct ATTRIB *ni_find_attr(struct ntfs_inode *ni, struct ATTRIB *attr,
 	struct ATTR_LIST_ENTRY *le;
 	struct mft_inode *m;
 
+	ni = ni->base;
 	if (!ni->attr_list.size ||
 	    (!name_len && (type == ATTR_LIST || type == ATTR_STD))) {
 		if (le_o)
@@ -257,6 +267,7 @@ struct ATTRIB *ni_enum_attr_ex(struct ntfs_inode *ni, struct ATTRIB *attr,
 	struct mft_inode *mi2;
 	struct ATTR_LIST_ENTRY *le2;
 
+	ni = ni->base;
 	/* Do we have an attribute list? */
 	if (!ni->attr_list.size) {
 		*le = NULL;
@@ -290,6 +301,7 @@ int ni_load_all_mi(struct ntfs_inode *ni)
 	int err;
 	struct ATTR_LIST_ENTRY *le;
 
+	ni = ni->base;
 	if (!ni->attr_list.size)
 		return 0;
 
@@ -316,6 +328,7 @@ bool ni_add_subrecord(struct ntfs_inode *ni, CLST rno, struct mft_inode **mi)
 {
 	struct mft_inode *m;
 
+	ni = ni->base;
 	m = kzalloc_obj(struct mft_inode, GFP_NOFS);
 	if (!m)
 		return false;
@@ -348,6 +361,7 @@ int ni_remove_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
 	u32 type_in;
 	int diff;
 
+	ni = ni->base;
 	if (base_only || type == ATTR_LIST || !ni->attr_list.size) {
 		attr = mi_find_attr(ni, &ni->mi, NULL, type, name, name_len,
 				    id);
@@ -417,6 +431,7 @@ ni_ins_new_attr(struct ntfs_inode *ni, struct mft_inode *mi,
 	bool le_added = false;
 	struct MFT_REF ref;
 
+	ni = ni->base;
 	mi_get_ref(mi, &ref);
 
 	if (type != ATTR_LIST && !le && ni->attr_list.size) {
@@ -618,6 +633,7 @@ static int ni_try_remove_attr_list(struct ntfs_inode *ni)
 	struct MFT_REC *mrec;
 	__le16 id;
 
+	ni = ni->base;
 	if (!ni->attr_list.dirty)
 		return 0;
 
@@ -763,6 +779,7 @@ int ni_create_attr_list(struct ntfs_inode *ni)
 	u32 free_b, nb, to_free, rs;
 	u16 sz;
 
+	ni = ni->base;
 	is_mft = ni->mi.rno == MFT_REC_MFT;
 	rec = ni->mi.mrec;
 	rs = sbi->record_size;
@@ -917,6 +934,7 @@ static int ni_ins_attr_ext(struct ntfs_inode *ni, struct ATTR_LIST_ENTRY *le,
 	bool is_mft, is_mft_data;
 	struct ntfs_sb_info *sbi = ni->mi.sbi;
 
+	ni = ni->base;
 	is_mft = ni->mi.rno == MFT_REC_MFT;
 	is_mft_data = is_mft && type == ATTR_DATA && !name_len;
 
@@ -1069,6 +1087,7 @@ static int ni_insert_attr(struct ntfs_inode *ni, enum ATTR_TYPE type,
 	__le16 id;
 	u16 t16;
 
+	ni = ni->base;
 	is_mft = ni->mi.rno == MFT_REC_MFT;
 	rec = ni->mi.mrec;
 
@@ -1215,6 +1234,7 @@ static int ni_expand_mft_list(struct ntfs_inode *ni)
 	struct mft_inode *mi, *mi_min, *mi_new;
 	struct ntfs_sb_info *sbi = ni->mi.sbi;
 
+	ni = ni->base;
 	/* Find the nearest MFT. */
 	mft_min = 0;
 	mft_new = 0;
@@ -1347,6 +1367,7 @@ int ni_expand_list(struct ntfs_inode *ni)
 	bool is_mft = ni->mi.rno == MFT_REC_MFT;
 	struct MFT_REF ref;
 
+	ni = ni->base;
 	mi_get_ref(&ni->mi, &ref);
 	le = NULL;
 
@@ -1419,6 +1440,7 @@ int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type,
 	u32 run_size, asize;
 	struct ntfs_sb_info *sbi = ni->mi.sbi;
 
+	ni = ni->base;
 	/* Estimate packed size (run_buf=NULL). */
 	err = run_pack(run, svcn, len, NULL, sbi->max_bytes_per_attr - run_off,
 		       &plen);
@@ -1488,6 +1510,7 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
 	u32 asize = SIZEOF_RESIDENT + name_size + ALIGN(data_size, 8);
 	struct ATTRIB *attr;
 
+	ni = ni->base;
 	err = ni_insert_attr(ni, type, name, name_len, asize, SIZEOF_RESIDENT,
 			     0, &attr, mi, le);
 	if (err)
@@ -1519,6 +1542,7 @@ int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
 void ni_remove_attr_le(struct ntfs_inode *ni, struct ATTRIB *attr,
 		       struct mft_inode *mi, struct ATTR_LIST_ENTRY *le)
 {
+	ni = ni->base;
 	mi_remove_attr(ni, mi, attr);
 
 	if (le)
@@ -1543,6 +1567,7 @@ int ni_delete_all(struct ntfs_inode *ni)
 	bool nt3 = is_ntfs3(sbi);
 	struct MFT_REF ref;
 
+	ni = ni->base;
 	while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) {
 		if (!nt3 || attr->name_len) {
 			;
@@ -1620,6 +1645,7 @@ struct ATTR_FILE_NAME *ni_fname_name(struct ntfs_inode *ni,
 	struct ATTRIB *attr = NULL;
 	struct ATTR_FILE_NAME *fname;
 
+	ni = ni->base;
 	if (le)
 		*le = NULL;
 
@@ -1661,6 +1687,7 @@ struct ATTR_FILE_NAME *ni_fname_type(struct ntfs_inode *ni, u8 name_type,
 	struct ATTR_FILE_NAME *fname;
 
 	*le = NULL;
+	ni = ni->base;
 
 	if (name_type == FILE_NAME_POSIX)
 		return NULL;
@@ -1691,6 +1718,7 @@ int ni_new_attr_flags(struct ntfs_inode *ni, enum FILE_ATTRIBUTE new_fa)
 	__le16 new_aflags;
 	u32 new_asize;
 
+	ni = ni->base;
 	attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, &mi);
 	if (!attr)
 		return -EINVAL;
@@ -1772,6 +1800,7 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
 	u16 len;
 	typeof(rp->CompressReparseBuffer) *cmpr;
 
+	ni = ni->base;
 	/* Try to estimate reparse point. */
 	if (!attr->non_res) {
 		rp = resident_data_ex(attr, sizeof(struct REPARSE_DATA_BUFFER));
@@ -2000,6 +2029,7 @@ int ni_decompress_file(struct ntfs_inode *ni)
 	struct mft_inode *mi;
 	int err;
 
+	ni = ni->base;
 	/* Clusters for decompressed data. */
 	cend = bytes_to_cluster(sbi, i_size);
 
@@ -2261,6 +2291,7 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages,
 	struct ATTRIB *attr;
 	CLST frame, clst_data;
 
+	ni = ni->base;
 	/*
 	 * To simplify decompress algorithm do vmap for source
 	 * and target pages.
@@ -2510,6 +2541,7 @@ int ni_write_frame(struct ntfs_inode *ni, struct page **pages,
 	size_t compr_size, ondisk_size;
 	struct lznt *lznt;
 
+	ni = ni->base;
 	attr = ni_find_attr(ni, NULL, &le, ATTR_DATA, NULL, 0, NULL, &mi);
 	if (!attr) {
 		err = -ENOENT;
@@ -2635,6 +2667,7 @@ int ni_remove_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
 	u16 de_key_size = le16_to_cpu(de->key_size);
 	u8 name_type;
 
+	ni = ni->base;
 	*undo_step = 0;
 
 	/* Find name in record. */
@@ -2697,6 +2730,7 @@ bool ni_remove_name_undo(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
 	struct ATTRIB *attr;
 	u16 de_key_size;
 
+	ni = ni->base;
 	switch (undo_step) {
 	case 4:
 		de_key_size = le16_to_cpu(de2->key_size);
@@ -2747,6 +2781,7 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
 	struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1);
 	u16 de_key_size = le16_to_cpu(de->key_size);
 
+	ni = ni->base;
 	if (sbi->options->windows_names &&
 	    !valid_windows_name(sbi, (struct le_str *)&de_name->name_len))
 		return -EINVAL;
@@ -2794,6 +2829,7 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
 	int err;
 	struct NTFS_DE *de2 = NULL;
 	int undo = 0;
+	const int way = 1; /* Hope compiler removes below 'else'. */
 
 	/*
 	 * There are two possible ways to rename:
@@ -2805,29 +2841,30 @@ int ni_rename(struct ntfs_inode *dir_ni, struct ntfs_inode *new_dir_ni,
 	 * Second way may result to bad inode if we can't add new name
 	 * and then can't restore (add) old name.
 	 */
-
-	/*
-	 * Way 1 - Add new + remove old.
-	 */
-	err = ni_add_name(new_dir_ni, ni, new_de);
-	if (!err) {
+	if (way == 1) {
+		/*
+		 * Way 1 - Add new + remove old.
+		 */
+		err = ni_add_name(new_dir_ni, ni, new_de);
+		if (!err) {
+			err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
+			if (err &&
+			    ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
+				_ntfs_bad_inode(&ni->vfs_inode);
+		}
+	} else {
+		/*
+		 * Way 2 - Remove old + add new.
+		 */
 		err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
-		if (err && ni_remove_name(new_dir_ni, ni, new_de, &de2, &undo))
-			_ntfs_bad_inode(&ni->vfs_inode);
+		if (!err) {
+			err = ni_add_name(new_dir_ni, ni, new_de);
+			if (err &&
+			    !ni_remove_name_undo(dir_ni, ni, de, de2, undo))
+				_ntfs_bad_inode(&ni->vfs_inode);
+		}
 	}
 
-	/*
-	 * Way 2 - Remove old + add new.
-	 */
-	/*
-	 *	err = ni_remove_name(dir_ni, ni, de, &de2, &undo);
-	 *	if (!err) {
-	 *		err = ni_add_name(new_dir_ni, ni, new_de);
-	 *		if (err && !ni_remove_name_undo(dir_ni, ni, de, de2, undo))
-	 *			*is_bad = true;
-	 *	}
-	 */
-
 	return err;
 }
 
@@ -2950,6 +2987,9 @@ int ni_write_parents(struct ntfs_inode *ni, int sync)
 	struct ntfs_sb_info *sbi = ni->mi.sbi;
 	struct super_block *sb = sbi->sb;
 
+	if (!is_ni_base(ni))
+		return 0;
+
 	while ((attr = ni_find_attr(ni, attr, &le, ATTR_NAME, NULL, 0, NULL,
 				    NULL))) {
 		struct inode *dir;
@@ -3132,6 +3172,9 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint)
 	if (is_bad_inode(inode) || sb_rdonly(sb))
 		return 0;
 
+	if (!is_ni_base(ni))
+		return 0;
+
 	/* Avoid any operation if inode is bad. */
 	if (unlikely(is_bad_ni(ni)))
 		return -EINVAL;
@@ -3316,10 +3359,84 @@ int ni_allocate_da_blocks_locked(struct ntfs_inode *ni)
 		/*
 		 * Normal file allocates clusters in 'attr_set_size'
 		 */
-		err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run,
+		err = attr_set_size_ex(ni, ATTR_DATA, ni->file.ads.name,
+				       ni->file.ads.len, &ni->file.run,
 				       ni->vfs_inode.i_size, &ni->i_valid,
 				       false, NULL, true);
 	}
 
 	return err;
 }
+
+/*
+ * Helper function to read ADS.
+ * bytes = iov_iter_count(iter) is not 0. Checked by caller;
+ */
+ssize_t ni_query_ads(struct ntfs_inode *ni, loff_t *pos, struct iov_iter *iter)
+{
+	ssize_t ret = 0;
+	struct ntfs_sb_info *sbi = ni->mi.sbi;
+	size_t bytes = iov_iter_count(iter);
+	loff_t end = *pos + bytes;
+	char *buf = NULL;
+	struct ATTR_LIST_ENTRY *le = NULL;
+	u64 vbo = 0;
+	struct ATTRIB *attr;
+	size_t done, used;
+	int err;
+
+	/*
+	 * Enumerate ADS.
+	 */
+	ni = ni->base;
+	attr = NULL;
+	while ((attr = ni_enum_attr_ex(ni, attr, &le, NULL))) {
+		if (attr->type != ATTR_DATA || !attr->name_len)
+			continue;
+
+		if (!buf) {
+			buf = kmalloc(PAGE_SIZE, GFP_NOFS);
+			if (!buf)
+				return -ENOMEM;
+		}
+
+		/* attr - named DATA attribute (ADS). */
+		err = ntfs_utf16_to_nls(sbi, attr_name(attr), attr->name_len,
+					buf, PAGE_SIZE);
+		if (err < 0) {
+			ret = err;
+			break;
+		}
+
+		/* 
+		 * err is the length of ADS name in bytes.
+		 * Copy pseudo data interval [vbo, err + 1).
+		 * into 'iter': [*pos, bytes)
+		 */
+		/* Add \n as streams name separator. */
+		buf[err++] = '\n';
+
+		if (vbo >= end) {
+			iov_iter_zero(bytes, iter);
+			break;
+		}
+
+		if (vbo + err > *pos) {
+			size_t off = *pos - vbo;
+			used = err - off;
+			done = copy_to_iter(Add2Ptr(buf, off), min(used, bytes),
+					    iter);
+		} else {
+			done = 0;
+		}
+
+		ret += done;
+		*pos += done;
+		if (done >= bytes)
+			break;
+		bytes -= done;
+		vbo += err;
+	}
+	kfree(buf);
+	return ret;
+}
diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c
index 7c4db816c43d..97c04ab2763a 100644
--- a/fs/ntfs3/fsntfs.c
+++ b/fs/ntfs3/fsntfs.c
@@ -88,6 +88,13 @@ const __le16 SQ_NAME[2] = {
 const __le16 SR_NAME[2] = {
 	cpu_to_le16('$'), cpu_to_le16('R'),
 };
+const __le16 QUERY_STREAMS[13] = {
+  cpu_to_le16('q'), cpu_to_le16('u'), cpu_to_le16('e'), cpu_to_le16('r'),
+  cpu_to_le16('y'), cpu_to_le16('_'), cpu_to_le16('s'), cpu_to_le16('t'),
+  cpu_to_le16('r'), cpu_to_le16('e'), cpu_to_le16('a'), cpu_to_le16('m'),
+  cpu_to_le16('s'),
+};
+
 
 #ifdef CONFIG_NTFS3_LZX_XPRESS
 const __le16 WOF_NAME[17] = {
@@ -122,7 +129,6 @@ static const __le16 COM_NAME[3] = {
 static const __le16 LPT_NAME[3] = {
 	cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'),
 };
-
 // clang-format on
 
 /*
@@ -236,7 +242,7 @@ int ntfs_extend_init(struct ntfs_sb_info *sbi)
 	}
 
 	/* Try to find $ObjId */
-	inode2 = dir_search_u(inode, &NAME_OBJID, NULL);
+	inode2 = dir_search(inode, &NAME_OBJID);
 	if (inode2 && !IS_ERR(inode2)) {
 		if (is_bad_inode(inode2)) {
 			iput(inode2);
@@ -247,21 +253,21 @@ int ntfs_extend_init(struct ntfs_sb_info *sbi)
 	}
 
 	/* Try to find $Quota */
-	inode2 = dir_search_u(inode, &NAME_QUOTA, NULL);
+	inode2 = dir_search(inode, &NAME_QUOTA);
 	if (inode2 && !IS_ERR(inode2)) {
 		sbi->quota_no = inode2->i_ino;
 		iput(inode2);
 	}
 
 	/* Try to find $Reparse */
-	inode2 = dir_search_u(inode, &NAME_REPARSE, NULL);
+	inode2 = dir_search(inode, &NAME_REPARSE);
 	if (inode2 && !IS_ERR(inode2)) {
 		sbi->reparse.ni = ntfs_i(inode2);
 		sbi->reparse_no = inode2->i_ino;
 	}
 
 	/* Try to find $UsnJrnl */
-	inode2 = dir_search_u(inode, &NAME_USNJRNL, NULL);
+	inode2 = dir_search(inode, &NAME_USNJRNL);
 	if (inode2 && !IS_ERR(inode2)) {
 		sbi->usn_jrnl_no = inode2->i_ino;
 		iput(inode2);
@@ -475,7 +481,7 @@ bool ntfs_check_free_space(struct ntfs_sb_info *sbi, CLST clen, CLST mlen,
 
 	avail = free - (zlen + clen);
 
-	/* 
+	/*
 	 * When delalloc is active then keep in mind some reserved space.
 	 * The worst case: 1 mft record per each ~500 clusters.
 	 */
@@ -1705,6 +1711,8 @@ struct ntfs_inode *ntfs_new_inode(struct ntfs_sb_info *sbi, CLST rno,
 		goto out;
 	}
 
+	ni->base = ni;
+
 out:
 	if (err) {
 		make_bad_inode(inode);
@@ -2664,6 +2672,12 @@ int ntfs_set_label(struct ntfs_sb_info *sbi, u8 *label, int len)
 	if (err < 0)
 		goto out;
 
+	if (uni->ads_len) {
+		/* Undo delimiter parse */
+		uni->len += uni->ads_len + 1;
+		uni->ads_len = 0;
+	}
+
 	uni_bytes = uni->len * sizeof(u16);
 	if (uni_bytes > NTFS_LABEL_MAX_LENGTH * sizeof(u16)) {
 		ntfs_warn(sbi->sb, "new label is too long");
diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index aca2ad950d95..4e15f7df89b1 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -18,12 +18,16 @@
 #include "ntfs.h"
 #include "ntfs_fs.h"
 
+struct IGET5_PARAM {
+	const struct MFT_REF *ref;
+	const struct cpu_str *name;
+};
+
 /*
  * ntfs_read_mft - Read record and parse MFT.
  */
-static struct inode *ntfs_read_mft(struct inode *inode,
-				   const struct cpu_str *name,
-				   const struct MFT_REF *ref)
+static int ntfs_read_mft(struct inode *inode, const struct cpu_str *name,
+			 const struct MFT_REF *ref)
 {
 	int err = 0;
 	struct ntfs_inode *ni = ntfs_i(inode);
@@ -46,6 +50,7 @@ static struct inode *ntfs_read_mft(struct inode *inode,
 	struct MFT_REC *rec;
 	struct runs_tree *run;
 	struct timespec64 ts;
+	const __le16 *aname;
 
 	inode->i_op = NULL;
 	/* Setup 'uid' and 'gid' */
@@ -144,6 +149,7 @@ static struct inode *ntfs_read_mft(struct inode *inode,
 	roff = attr->non_res ? 0 : le16_to_cpu(attr->res.data_off);
 	rsize = attr->non_res ? 0 : le32_to_cpu(attr->res.data_size);
 	asize = le32_to_cpu(attr->size);
+	aname = attr_name(attr);
 
 	/*
 	 * Really this check was done in 'ni_enum_attr_ex' -> ... 'mi_enum_attr'.
@@ -230,10 +236,10 @@ static struct inode *ntfs_read_mft(struct inode *inode,
 		if (attr->name_len &&
 		    ((ino != MFT_REC_BADCLUST || !attr->non_res ||
 		      attr->name_len != ARRAY_SIZE(BAD_NAME) ||
-		      memcmp(attr_name(attr), BAD_NAME, sizeof(BAD_NAME))) &&
+		      memcmp(aname, BAD_NAME, sizeof(BAD_NAME))) &&
 		     (ino != MFT_REC_SECURE || !attr->non_res ||
 		      attr->name_len != ARRAY_SIZE(SDS_NAME) ||
-		      memcmp(attr_name(attr), SDS_NAME, sizeof(SDS_NAME))))) {
+		      memcmp(aname, SDS_NAME, sizeof(SDS_NAME))))) {
 			/* File contains stream attribute. Ignore it. */
 			goto next_attr;
 		}
@@ -253,14 +259,11 @@ static struct inode *ntfs_read_mft(struct inode *inode,
 		else
 			ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED;
 
-		if (!attr->non_res) {
-			ni->i_valid = inode->i_size = rsize;
-			inode_set_bytes(inode, rsize);
-		}
-
 		mode = S_IFREG | (0777 & sbi->options->fs_fmask_inv);
 
 		if (!attr->non_res) {
+			ni->i_valid = inode->i_size = rsize;
+			inode_set_bytes(inode, rsize);
 			ni->ni_flags |= NI_FLAG_RESIDENT;
 			goto next_attr;
 		}
@@ -501,16 +504,136 @@ static struct inode *ntfs_read_mft(struct inode *inode,
 	if (ino == MFT_REC_MFT && !sb->s_root)
 		sbi->mft.ni = NULL;
 
-	unlock_new_inode(inode);
-
-	return inode;
+	return 0;
 
 out:
 	if (ino == MFT_REC_MFT && !sb->s_root)
 		sbi->mft.ni = NULL;
 
-	iget_failed(inode);
-	return ERR_PTR(err);
+	return err;
+}
+
+/*
+ * ntfs_init_ads_node
+ *
+ * This function scans base inode for given ADS.
+ * And init inode associated with this ADS
+ */
+static int ntfs_init_ads_node(struct inode *inode, const __le16 *ads_name,
+			      u8 ads_len, u32 flags)
+{
+	int err = -EINVAL;
+	struct ntfs_inode *ni = ntfs_i(inode);
+	struct ntfs_inode *nb = ni->base;
+	struct ntfs_sb_info *sbi = nb->mi.sbi;
+	struct ATTR_LIST_ENTRY *le = NULL;
+	struct ATTRIB *attr = NULL;
+	u16 roff, asize;
+	u64 svcn;
+
+	if (nb->ni_flags & NI_FLAG_DIR)
+		return -EINVAL; /* no ADS for directories. */
+
+	ni->mi.sbi = sbi;
+	ni->mi.rno = inode->i_ino;
+
+	if (ads_len == ARRAY_SIZE(QUERY_STREAMS) &&
+	    !memcmp(ads_name, QUERY_STREAMS, sizeof(QUERY_STREAMS))) {
+		goto ok; /* use goto to reduce tab pressure. */
+	}
+
+	/* Enumerate all attributes in record. */
+	while ((attr = ni_enum_attr_ex(nb, attr, &le, NULL))) {
+		if (attr->type == ATTR_DATA && attr->name_len &&
+		    ads_len == attr->name_len &&
+		    !memcmp(ads_name, attr_name(attr), ads_len * sizeof(u16))) {
+			/* We have found the ADS to open. */
+			break;
+		}
+	}
+
+	if (!attr) {
+		if (!(flags & LOOKUP_CREATE)) {
+			/* Do not create ADS. */
+			return -ENOENT;
+		}
+
+		/* Create new ADS. */
+		err = ni_insert_resident(nb, 0, ATTR_DATA, ads_name, ads_len,
+					 &attr, NULL, NULL);
+		if (err) {
+			/* Looks like the only reasons: ENOSPC/ENOMEM .*/
+			return err;
+		}
+	}
+
+	if (is_attr_sparsed(attr))
+		ni->std_fa |= FILE_ATTRIBUTE_SPARSE_FILE;
+	else
+		ni->std_fa &= ~FILE_ATTRIBUTE_SPARSE_FILE;
+
+	if (is_attr_compressed(attr))
+		ni->std_fa |= FILE_ATTRIBUTE_COMPRESSED;
+	else
+		ni->std_fa &= ~FILE_ATTRIBUTE_COMPRESSED;
+
+	if (is_attr_encrypted(attr))
+		ni->std_fa |= FILE_ATTRIBUTE_ENCRYPTED;
+	else
+		ni->std_fa &= ~FILE_ATTRIBUTE_ENCRYPTED;
+
+	if (!attr->non_res) {
+		ni->ni_flags |= NI_FLAG_RESIDENT;
+		ni->i_valid = inode->i_size = le32_to_cpu(attr->res.data_size);
+		inode_set_bytes(inode, inode->i_size);
+		goto ok;
+	}
+
+	inode_set_bytes(inode, attr_ondisk_size(attr));
+	ni->i_valid = le64_to_cpu(attr->nres.valid_size);
+	inode->i_size = le64_to_cpu(attr->nres.data_size);
+
+	if (!attr->nres.alloc_size)
+		goto ok;
+
+	roff = le16_to_cpu(attr->nres.run_off);
+	asize = le32_to_cpu(attr->size);
+
+	if (roff > asize) {
+		/* This case should be checked in mi_enum_attr */
+		return -EINVAL;
+	}
+
+	svcn = le64_to_cpu(attr->nres.svcn);
+	err = run_unpack_ex(&ni->file.run, sbi, ni->mi.rno, svcn,
+			    le64_to_cpu(attr->nres.evcn), svcn,
+			    Add2Ptr(attr, roff), asize - roff);
+	if (err < 0) {
+		/* run_unpack_ex marks volume dirty, if logical error. */
+		return err;
+	}
+
+ok:
+	/* Keep ADS name (little endian). */
+	ni->file.ads.name = kmemdup(ads_name, ads_len * sizeof(u16), GFP_NOFS);
+	if (!ni->file.ads.name)
+		return -ENOMEM;
+	ni->file.ads.len = ads_len;
+
+	set_nlink(inode, 1);
+
+	init_rwsem(&ni->file.run_lock);
+	/* Most fields are the same as the base's? */
+	inode->i_op = nb->vfs_inode.i_op;
+	inode->i_fop = nb->vfs_inode.i_fop;
+	inode->i_mapping->a_ops = nb->vfs_inode.i_mapping->a_ops;
+	inode->i_flags = nb->vfs_inode.i_flags;
+	inode->i_mode = nb->vfs_inode.i_mode;
+	inode->i_uid = nb->vfs_inode.i_uid;
+	inode->i_gid = nb->vfs_inode.i_gid;
+	inode->i_generation = nb->vfs_inode.i_generation;
+
+	return 0;
 }
 
 /*
@@ -520,44 +643,120 @@ static struct inode *ntfs_read_mft(struct inode *inode,
  */
 static int ntfs_test_inode(struct inode *inode, void *data)
 {
-	struct MFT_REF *ref = data;
+	const struct IGET5_PARAM *ig5 = data;
+	struct ntfs_inode *ni;
+	const struct cpu_str *name;
+
+	if (ino_get(ig5->ref) != inode->i_ino)
+		return 0;
+
+	ni = ntfs_i(inode);
+
+	if (ni->ni_flags & NI_FLAG_DIR) {
+		/* No ads for directories. */
+		return 1;
+	}
+
+	name = ig5->name;
+	if (!name || !name->ads_len) {
+		if (!ni->file.ads.len) {
+			/* default file (not ads) match. */
+			return 1;
+		}
+	} else if (ni->file.ads.len == name->ads_len &&
+		   !memcmp(ni->file.ads.name, &name->name[name->len + 1],
+			   name->ads_len * sizeof(u16))) {
+		/* ads name match. */
+		return 1;
+	}
 
-	return ino_get(ref) == inode->i_ino;
+	return 0;
 }
 
 static int ntfs_set_inode(struct inode *inode, void *data)
 {
-	const struct MFT_REF *ref = data;
+	const struct IGET5_PARAM *ig5 = data;
 
-	inode->i_ino = ino_get(ref);
+	inode->i_ino = ino_get(ig5->ref);
 	return 0;
 }
 
-struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
-			 const struct cpu_str *name)
+struct inode *ntfs_iget5_flags(struct super_block *sb,
+			       const struct MFT_REF *ref,
+			       const struct cpu_str *name, u32 flags)
 {
-	struct inode *inode;
+	int err;
+	/* Pack params to pass in iget5_locked. */
+	struct IGET5_PARAM ig5 = { ref, name };
+	u64 ino = ino_get(ref);
+	struct inode *inode, *base = NULL;
+	bool ads = name && name->ads_len;
+	struct ntfs_inode *ni;
+
+	if (ads) {
+		/* First get base inode */
+		base = ntfs_iget5_flags(sb, ref, NULL, 0);
+		if (IS_ERR(base))
+			return base;
+	}
+
+	inode = iget5_locked(sb, ino, ntfs_test_inode, ntfs_set_inode, &ig5);
+	if (unlikely(!inode)) {
+		err = -ENOMEM;
+		goto out;
+	}
 
-	inode = iget5_locked(sb, ino_get(ref), ntfs_test_inode, ntfs_set_inode,
-			     (void *)ref);
-	if (unlikely(!inode))
-		return ERR_PTR(-ENOMEM);
+	ni = ntfs_i(inode);
 
 	/* If this is a freshly allocated inode, need to read it now. */
-	if (inode_state_read_once(inode) & I_NEW)
-		inode = ntfs_read_mft(inode, name, ref);
-	else if (ref->seq != ntfs_i(inode)->mi.mrec->seq) {
+	if (inode_state_read_once(inode) & I_NEW) {
+		if (!base) {
+			/* default inode. generic file/dir. */
+			ni->base = ni;
+		} else {
+			/* inode + ads */
+			ni->base = ntfs_i(base);
+			base = NULL; /* keep reference incremented (instead of ihold). */
+		}
+
+		if (ads) {
+			/* base record is loaded. Init ads node. */
+			err = ntfs_init_ads_node(
+				inode, (__le16 *)&name->name[name->len + 1],
+				name->ads_len, flags);
+		} else {
+			err = ntfs_read_mft(inode, name, ref);
+		}
+
+		if (!err) {
+			unlock_new_inode(inode);
+		} else {
+			iget_failed(inode);
+			/* Do not mark volume dirty if ADS not found. */
+			if (ads)
+				goto out;
+		}
+	} else if (!ads && ref->seq != ni->mi.mrec->seq) {
 		/*
 		 * Sequence number is not expected.
 		 * Looks like inode was reused but caller uses the old reference
 		 */
 		iput(inode);
-		inode = ERR_PTR(-ESTALE);
+		err = -ESTALE;
+	} else {
+		err = 0;
 	}
 
-	if (IS_ERR(inode))
+	if (err)
 		ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR);
 
+out:
+	if (base)
+		iput(base);
+
+	if (err)
+		return ERR_PTR(err);
+
 	return inode;
 }
 
@@ -709,8 +908,8 @@ int ntfs_set_size(struct inode *inode, u64 new_size)
 		ni->i_valid = new_size;
 
 	/* last 'true' means keep preallocated. */
-	err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
-			    &ni->i_valid, true);
+	err = attr_set_size(ni, ATTR_DATA, ni->file.ads.name, ni->file.ads.len,
+			    &ni->file.run, new_size, &ni->i_valid, true);
 
 	up_write(&ni->file.run_lock);
 	ni_unlock(ni);
@@ -804,7 +1003,8 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
 
 	if (lcn == RESIDENT_LCN) {
 		if (offset >= clen) {
-			__free_page(virt_to_page(res));
+			if (res)
+				__free_page(virt_to_page(res));
 			if (flags & IOMAP_REPORT) {
 				/* special code for report. */
 				return -ENOENT;
@@ -895,7 +1095,8 @@ static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
 			struct ATTRIB *attr;
 			struct mft_inode *mi;
 
-			attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0,
+			attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA,
+					    ni->file.ads.name, ni->file.ads.len,
 					    NULL, &mi);
 			if (!attr || attr->non_res) {
 				err = -EINVAL;
@@ -1217,6 +1418,15 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
 	if (!fnd)
 		ni_lock_dir(dir_ni);
 
+	if (sbi->options->ads) {
+		const char *ads = strchr(name->name + 1, ':');
+		if (ads && ads[1]) {
+			ntfs_warn(sb, "failed to create ads");
+			err = -EINVAL;
+			goto out1;
+		}
+	}
+
 	dir_root = indx_get_root(&dir_ni->dir, dir_ni, NULL, NULL);
 	if (!dir_root) {
 		err = -EINVAL;
diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c
index 937d349e841d..c248e8418477 100644
--- a/fs/ntfs3/namei.c
+++ b/fs/ntfs3/namei.c
@@ -78,21 +78,23 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
 	int err;
 
 	if (!uni)
-		inode = ERR_PTR(-ENOMEM);
-	else {
-		err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name,
-					dentry->d_name.len, uni, NTFS_NAME_LEN,
-					UTF16_HOST_ENDIAN);
-		if (err < 0)
-			inode = ERR_PTR(err);
-		else {
-			ni_lock_dir(ni);
-			inode = dir_search_u(dir, uni, NULL);
-			ni_unlock(ni);
-		}
+		return ERR_PTR(-ENOMEM);
+
+	err = ntfs_nls_to_utf16(ni->mi.sbi, dentry->d_name.name,
+				dentry->d_name.len, uni, NTFS_NAME_LEN,
+				UTF16_HOST_ENDIAN);
+
+	if (err < 0) {
 		kfree(uni);
+		return ERR_PTR(err);
 	}
 
+	ni_lock_dir(ni);
+	inode = dir_search_flags(dir, uni, NULL, flags);
+	ni_unlock(ni);
+
+	kfree(uni);
+
 	/*
 	 * Check for a null pointer
 	 * If the MFT record of ntfs inode is not a base record, inode->i_op can be NULL.
@@ -100,7 +102,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
 	 */
 	if (!IS_ERR_OR_NULL(inode) && !inode->i_op) {
 		iput(inode);
-		inode = ERR_PTR(-EINVAL);
+		return ERR_PTR(-EINVAL);
 	}
 
 	return d_splice_alias(inode, dentry);
@@ -173,7 +175,9 @@ static int ntfs_link(struct dentry *ode, struct inode *dir, struct dentry *de)
  */
 static int ntfs_unlink(struct inode *dir, struct dentry *dentry)
 {
-	struct ntfs_inode *ni = ntfs_i(dir);
+	struct ntfs_inode *dir_ni = ntfs_i(dir);
+	struct inode *inode = d_inode(dentry);
+	struct ntfs_inode *ni = ntfs_i(inode);
 	int err;
 
 	/* Avoid any operation if inode is bad. */
@@ -183,11 +187,21 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry)
 	if (unlikely(ntfs3_forced_shutdown(dir->i_sb)))
 		return -EIO;
 
-	ni_lock_dir(ni);
-
-	err = ntfs_unlink_inode(dir, dentry);
+	if (likely(is_ni_base(ni))) {
+		ni_lock_dir(dir_ni);
+		/* Remove general file/dir. */
+		err = ntfs_unlink_inode(dir, dentry);
+		ni_unlock(dir_ni);
+	} else {
+		ni_lock(ni);
+		/* Remove ADS. */
+		err = ni_remove_attr(ni, ATTR_DATA, ni->file.ads.name,
+				     ni->file.ads.len, false, NULL);
+		ni_unlock(ni);
 
-	ni_unlock(ni);
+		if (!err)
+			drop_nlink(inode);
+	}
 
 	return err;
 }
@@ -273,6 +287,11 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir,
 		      1024);
 	static_assert(PATH_MAX >= 4 * 1024);
 
+	if (!is_ni_base(ni)) {
+		/* No rename for ADS. */
+		return EOPNOTSUPP;
+	}
+
 	/* Avoid any operation if inode is bad. */
 	if (unlikely(is_bad_ni(ni)))
 		return -EINVAL;
diff --git a/fs/ntfs3/ntfs.h b/fs/ntfs3/ntfs.h
index 892f13e65d42..4589b16329c9 100644
--- a/fs/ntfs3/ntfs.h
+++ b/fs/ntfs3/ntfs.h
@@ -58,7 +58,7 @@ struct GUID {
  */
 struct cpu_str {
 	u8 len;
-	u8 unused;
+	u8 ads_len;
 	u16 name[];
 };
 
@@ -170,6 +170,7 @@ extern const __le16 SDH_NAME[4];
 extern const __le16 SO_NAME[2];
 extern const __le16 SQ_NAME[2];
 extern const __le16 SR_NAME[2];
+extern const __le16 QUERY_STREAMS[13];
 
 extern const __le16 BAD_NAME[4];
 extern const __le16 SDS_NAME[4];
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 6bd1a439e92f..5811d89d67b3 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -110,6 +110,7 @@ struct ntfs_mount_options {
 	unsigned prealloc : 1; /* Preallocate space when file is growing. */
 	unsigned nocase : 1; /* case insensitive. */
 	unsigned delalloc : 1; /* delay allocation. */
+	unsigned ads : 1; /* ads support. */
 };
 
 /* Special value to unpack and deallocate. */
@@ -410,6 +411,11 @@ struct ntfs_inode {
 #ifdef CONFIG_NTFS3_LZX_XPRESS
 			struct folio *offs_folio;
 #endif
+			/* Alternative data stream */
+			struct {
+				__le16 *name;
+				u8 len;
+			} ads;
 		} file;
 	};
 
@@ -421,6 +427,7 @@ struct ntfs_inode {
 	} attr_list;
 
 	size_t ni_flags; // NI_FLAG_XXX
+	struct ntfs_inode *base; /* ADS: points to base inode. Other: this. */
 
 	struct inode vfs_inode;
 };
@@ -444,6 +451,11 @@ enum REPARSE_SIGN {
 	REPARSE_LINK = 3
 };
 
+static inline bool is_ni_base(const struct ntfs_inode *ni)
+{
+	return ni == ni->base;
+}
+
 /* Functions from attrib.c */
 int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run,
 			   struct runs_tree *run_da, CLST vcn, CLST lcn,
@@ -526,8 +538,14 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len,
 int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
 		      struct cpu_str *uni, u32 max_ulen,
 		      enum utf16_endian endian);
-struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
-			   struct ntfs_fnd *fnd);
+struct inode *dir_search_flags(struct inode *dir, const struct cpu_str *uni,
+			       struct ntfs_fnd *fnd, u32 flags);
+static inline struct inode *dir_search(struct inode *dir,
+				       const struct cpu_str *uni)
+{
+	return dir_search_flags(dir, uni, NULL, 0);
+}
+
 bool dir_is_empty(struct inode *dir);
 extern const struct file_operations ntfs_dir_operations;
 
@@ -622,6 +640,7 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data);
 int ni_write_parents(struct ntfs_inode *ni, int sync);
 int ni_allocate_da_blocks(struct ntfs_inode *ni);
 int ni_allocate_da_blocks_locked(struct ntfs_inode *ni);
+ssize_t ni_query_ads(struct ntfs_inode *ni, loff_t *pos, struct iov_iter *iter);
 
 /* Globals from fslog.c */
 bool check_index_header(const struct INDEX_HDR *hdr, size_t bytes);
@@ -679,7 +698,6 @@ static inline int ntfs_read_bh(struct ntfs_sb_info *sbi,
 {
 	return ntfs_read_bh_ra(sbi, run, vbo, rhdr, bytes, nb, NULL);
 }
-
 int ntfs_get_bh(struct ntfs_sb_info *sbi, const struct runs_tree *run, u64 vbo,
 		u32 bytes, struct ntfs_buffers *nb);
 int ntfs_write_bh(struct ntfs_sb_info *sbi, struct NTFS_RECORD_HEADER *rhdr,
@@ -772,8 +790,15 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi,
 		    const struct NTFS_DUP_INFO *dup, int sync);
 
 /* Globals from inode.c */
-struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
-			 const struct cpu_str *name);
+struct inode *ntfs_iget5_flags(struct super_block *sb,
+			       const struct MFT_REF *ref,
+			       const struct cpu_str *name, u32 flags);
+static inline struct inode *ntfs_iget5(struct super_block *sb,
+				       const struct MFT_REF *ref,
+				       const struct cpu_str *name)
+{
+	return ntfs_iget5_flags(sb, ref, name, 0);
+}
 int ntfs_set_size(struct inode *inode, u64 new_size);
 int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc);
 int ntfs_sync_inode(struct inode *inode);
@@ -1219,27 +1244,27 @@ static inline void mi_clear(struct mft_inode *mi)
 
 static inline void ni_lock(struct ntfs_inode *ni)
 {
-	mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_NORMAL);
+	mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_NORMAL);
 }
 
 static inline void ni_lock_dir(struct ntfs_inode *ni)
 {
-	mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_PARENT);
+	mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_PARENT);
 }
 
 static inline void ni_lock_dir2(struct ntfs_inode *ni)
 {
-	mutex_lock_nested(&ni->ni_lock, NTFS_INODE_MUTEX_PARENT2);
+	mutex_lock_nested(&ni->base->ni_lock, NTFS_INODE_MUTEX_PARENT2);
 }
 
 static inline void ni_unlock(struct ntfs_inode *ni)
 {
-	mutex_unlock(&ni->ni_lock);
+	mutex_unlock(&ni->base->ni_lock);
 }
 
 static inline int ni_trylock(struct ntfs_inode *ni)
 {
-	return mutex_trylock(&ni->ni_lock);
+	return mutex_trylock(&ni->base->ni_lock);
 }
 
 static inline int attr_load_runs_attr(struct ntfs_inode *ni,
diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index 94cb57007893..ea9dde56f968 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -23,6 +23,7 @@
  * allocated_size                - Total size of clusters allocated for non-resident content
  * total_size                    - Actual size of allocated clusters for sparse or compressed attributes
  *                               - Constraint: valid_size <= data_size <= allocated_size
+ * ADS                           - Alternative data stream: Named data attribute (0x80)
  *
  * WSL - Windows Subsystem for Linux
  * https://docs.microsoft.com/en-us/windows/wsl/file-permissions
@@ -271,6 +272,8 @@ enum Opt {
 	Opt_nocase,
 	Opt_delalloc,
 	Opt_delalloc_bool,
+	Opt_ads,
+	Opt_ads_bool,
 	Opt_err,
 };
 
@@ -297,6 +300,8 @@ static const struct fs_parameter_spec ntfs_fs_parameters[] = {
 	fsparam_flag("nocase",		Opt_nocase),
 	fsparam_flag("delalloc",	Opt_delalloc),
 	fsparam_bool("delalloc",	Opt_delalloc_bool),
+	fsparam_flag("ads",		Opt_ads),
+	fsparam_bool("ads",		Opt_ads_bool),
 	{}
 };
 // clang-format on
@@ -420,6 +425,12 @@ static int ntfs_fs_parse_param(struct fs_context *fc,
 	case Opt_delalloc_bool:
 		opts->delalloc = result.boolean;
 		break;
+	case Opt_ads:
+		opts->ads = 1;
+		break;
+	case Opt_ads_bool:
+		opts->ads = result.boolean;
+		break;
 	default:
 		/* Should not be here unless we forget add case. */
 		return -EINVAL;
@@ -791,6 +802,8 @@ static int ntfs_show_options(struct seq_file *m, struct dentry *root)
 		seq_puts(m, ",nocase");
 	if (opts->delalloc)
 		seq_puts(m, ",delalloc");
+	if (opts->ads)
+		seq_puts(m, ",ads");
 
 	return 0;
 }
@@ -1871,6 +1884,7 @@ static int ntfs_init_fs_context(struct fs_context *fc)
 	opts->fs_gid = current_gid();
 	opts->fs_fmask_inv = opts->fs_dmask_inv = ~current_umask();
 	opts->prealloc = 1;
+	opts->ads = 1;
 
 #ifdef CONFIG_NTFS3_FS_POSIX_ACL
 	/* Set the default value 'acl' */
-- 
2.43.0