All of lore.kernel.org
 help / color / mirror / Atom feed
From: Namjae Jeon <linkinjeon@kernel.org>
To: hyc.lee@gmail.com
Cc: ntfs@lists.linux.dev, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, sebastian.n.feld@gmail.com,
	cedric.blancher@gmail.com, Lionelcons1972@gmail.com,
	Namjae Jeon <linkinjeon@kernel.org>
Subject: [PATCH 1/1] ntfs: ntfs: add ioctl support for named data streams
Date: Wed, 29 Jul 2026 19:52:59 +0900	[thread overview]
Message-ID: <20260729105259.11810-2-linkinjeon@kernel.org> (raw)
In-Reply-To: <20260729105259.11810-1-linkinjeon@kernel.org>

Sebastian and Cedric requested NTFS driver support for an interface that
WINE can use to access named data streams.

NTFS supports multiple named $DATA attributes, commonly known as alternate
data streams, but they are not directly accessible through the existing
file operations. Add NTFS-specific ioctl commands to let userspace list,
read, write, and remove named data streams on an opened file.

The new UAPI provides separate commands for each operation:

  - NTFS_IOC_STREAM_READ
  - NTFS_IOC_STREAM_WRITE
  - NTFS_IOC_STREAM_REMOVE
  - NTFS_IOC_LIST_STREAMS

The read and write commands use an offset and length based request format.
Writes create the named stream when it does not already exist. The list
command returns variable-length stream entries containing stream size,
allocated size, and stream name.

Stream names use the mounted filesystem NLS by default, or raw UTF-16LE
when the UTF-16 flag is requested. Read and write transfers are limited to
16 MiB per request. If the list buffer is too small, the list command
returns -ENOSPC while reporting the required size and stream count.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
 .../userspace-api/ioctl/ioctl-number.rst      |   1 +
 fs/ntfs/file.c                                | 509 ++++++++++++++++++
 include/uapi/linux/ntfs.h                     | 115 ++++
 3 files changed, 625 insertions(+)
 create mode 100644 include/uapi/linux/ntfs.h

diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 3f0ef1e27eb0..2b9869aeb811 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -399,6 +399,7 @@ Code  Seq#    Include File                                             Comments
 0xE5  00-3F  linux/fuse.h
 0xEC  00-01  drivers/platform/chrome/cros_ec_dev.h                     ChromeOS EC driver
 0xEE  00-09  uapi/linux/pfrut.h                                        Platform Firmware Runtime Update and Telemetry
+0xEF  00-0F  uapi/linux/ntfs.h                                         NTFS
 0xF3  00-3F  drivers/usb/misc/sisusbvga/sisusb.h                       sisfb (in development)
                                                                        <mailto:thomas@winischhofer.net>
 0xF6  all                                                              LTTng Linux Trace Toolkit Next Generation
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index d4282822b3ce..961b94f545f2 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -15,6 +15,8 @@
 #include <linux/posix_acl_xattr.h>
 #include <linux/compat.h>
 #include <linux/falloc.h>
+#include <linux/overflow.h>
+#include <uapi/linux/ntfs.h>
 
 #include "lcnalloc.h"
 #include "ntfs.h"
@@ -23,6 +25,7 @@
 #include "iomap.h"
 #include "bitmap.h"
 #include "volume.h"
+#include "mft.h"
 
 #include <linux/filelock.h>
 
@@ -839,6 +842,504 @@ static int ntfs_ioctl_fitrim(struct ntfs_volume *vol, unsigned long arg)
 	return 0;
 }
 
+#define NTFS_STREAM_MAX_IO	(16 * 1024 * 1024) /* 16MB limit */
+
+/*
+ * ntfs_read_named_stream - Read data from a named $DATA stream.
+ *
+ * @ni:		NTFS inode
+ * @uname:	Stream name
+ * @uname_len:	Length of name in Unicode characters
+ * @offset:	Byte offset in the stream
+ * @len:	Number of bytes to read
+ * @data_buf:	Buffer to copy data to
+ *
+ * Return:	0 on success, negative error code otherwise.
+ *		If offset >= stream size, returns 0.
+ */
+static int ntfs_read_named_stream(struct ntfs_inode *ni, __le16 *uname,
+		u32 uname_len, u64 offset, u64 len, void *data_buf,
+		u64 *rbytes)
+{
+	struct ntfs_attr_search_ctx *ctx;
+	struct inode *attr_vi;
+	int err = 0;
+	u64 data_size;
+	s64 bytes;
+	char *rbuf = NULL;
+
+	if (!ni || !uname || uname_len == 0)
+		return -EINVAL;
+
+	if (len == 0 || len > NTFS_STREAM_MAX_IO)
+		return -EINVAL;
+
+	mutex_lock(&ni->mrec_lock);
+	ctx = ntfs_attr_get_search_ctx(ni, NULL);
+	if (!ctx) {
+		mutex_unlock(&ni->mrec_lock);
+		return -ENOMEM;
+	}
+
+	err = ntfs_attr_lookup(AT_DATA, uname, uname_len, CASE_SENSITIVE, 0,
+			NULL, 0, ctx);
+	if (err)
+		goto out_lock;
+
+	if (ctx->attr->non_resident)
+		data_size = le64_to_cpu(ctx->attr->data.non_resident.data_size);
+	else
+		data_size = le32_to_cpu(ctx->attr->data.resident.value_length);
+
+	if (offset >= data_size) {
+		*rbytes = 0;
+		/* EOF: nothing to read */
+		goto out_lock;
+	}
+
+	/* Adjust length to not read beyond EOF */
+	if (len > data_size - offset)
+		len = data_size - offset;
+
+	attr_vi = ntfs_attr_iget(VFS_I(ni), AT_DATA, uname, uname_len);
+	ntfs_attr_put_search_ctx(ctx);
+	mutex_unlock(&ni->mrec_lock);
+
+	if (IS_ERR(attr_vi)) {
+		err = PTR_ERR(attr_vi);
+		goto out_free;
+	}
+
+	rbuf = kvzalloc(len, GFP_NOFS);
+	if (!rbuf) {
+		err = -ENOMEM;
+		iput(attr_vi);
+		goto out_free;
+	}
+
+	bytes = ntfs_inode_attr_pread(attr_vi, offset, len, rbuf);
+	iput(attr_vi);
+	if (bytes < 0) {
+		err = bytes;
+		goto out_free;
+	}
+
+	*rbytes = bytes;
+	memcpy(data_buf, rbuf, bytes);
+
+out_free:
+	kvfree(rbuf);
+	return err;
+
+out_lock:
+	ntfs_attr_put_search_ctx(ctx);
+	mutex_unlock(&ni->mrec_lock);
+	return err;
+}
+
+/*
+ * ntfs_write_named_stream - Write data from a named $DATA stream.
+ *
+ * @ni:		NTFS inode
+ * @uname:	Stream name
+ * @uname_len:	Length of name in Unicode characters
+ * @offset:	Byte offset in the stream
+ * @len:	Number of bytes to write
+ * @data_stream: Data to write
+ *
+ * Return:	0 on success, negative error code otherwise.
+ */
+static int ntfs_write_named_stream(struct ntfs_inode *ni, __le16 *uname,
+		u32 uname_len, u64 offset, u64 len, void *data_stream,
+		u64 *wbytes)
+{
+	struct ntfs_attr_search_ctx *ctx;
+	struct inode *attr_vi;
+	int err;
+	s64 bytes;
+
+	if (!ni || !uname || uname_len == 0 || !data_stream)
+		return -EINVAL;
+
+	if (len == 0 || len > NTFS_STREAM_MAX_IO)
+		return -EINVAL;
+
+	mutex_lock(&ni->mrec_lock);
+	ctx = ntfs_attr_get_search_ctx(ni, NULL);
+	if (!ctx) {
+		err = -ENOMEM;
+		goto out_unlock;
+	}
+
+	err = ntfs_attr_lookup(AT_DATA, uname, uname_len, CASE_SENSITIVE, 0,
+			NULL, 0, ctx);
+	if (err) {
+		if (err != -ENOENT)
+			goto out_unlock;
+		err = ntfs_attr_add(ni, AT_DATA, uname, uname_len, NULL, 0);
+		if (err)
+			goto out_unlock;
+		mark_mft_record_dirty(ni);
+	}
+
+	attr_vi = ntfs_attr_iget(VFS_I(ni), AT_DATA, uname, uname_len);
+	ntfs_attr_put_search_ctx(ctx);
+	mutex_unlock(&ni->mrec_lock);
+
+	if (IS_ERR(attr_vi)) {
+		err = PTR_ERR(attr_vi);
+		goto out;
+	}
+
+	bytes = ntfs_inode_attr_pwrite(attr_vi, offset, len, data_stream,
+				       false);
+	if (bytes < 0)
+		err = bytes;
+	else
+		*wbytes = bytes;
+	iput(attr_vi);
+
+out:
+	return err;
+
+out_unlock:
+	if (ctx)
+		ntfs_attr_put_search_ctx(ctx);
+	mutex_unlock(&ni->mrec_lock);
+	return err;
+}
+
+/*
+ * ntfs_remove_named_stream - Remove a named $DATA stream.
+ *
+ * @ni:		NTFS inode
+ * @uname:	Stream name
+ * @uname_len:	Name length in Unicode characters
+ *
+ * Return:	0 on success, negative error
+ *              -ENOENT if stream not found
+ */
+static int ntfs_remove_named_stream(struct ntfs_inode *ni, __le16 *uname,
+		u32 uname_len)
+{
+	if (!ni || !uname || uname_len == 0)
+		return -EINVAL;
+
+	return ntfs_attr_remove(ni, AT_DATA, uname, uname_len);
+}
+
+enum ntfs_stream_op {
+	NTFS_STREAM_OP_READ,
+	NTFS_STREAM_OP_WRITE,
+	NTFS_STREAM_OP_REMOVE,
+};
+
+static long ntfs_ioctl_stream(struct file *filp, unsigned long arg,
+			      enum ntfs_stream_op op)
+{
+	struct inode *inode = file_inode(filp);
+	struct ntfs_inode *ni = NTFS_I(inode);
+	struct ntfs_stream __user *ureq =
+		(struct ntfs_stream __user *)arg;
+	struct ntfs_stream *req;
+	size_t header_size = sizeof(struct ntfs_stream);
+	size_t data_size, total_size;
+	char *kname, *kdata;
+	__le16 *uname = NULL, *sname;
+	int err = 0, sname_len;
+	bool utf16;
+	struct ntfs_stream hdr;
+
+	/* First copy fixed header */
+	if (copy_from_user(&hdr, ureq, header_size))
+		return -EFAULT;
+
+	if (hdr.io_len > NTFS_STREAM_MAX_IO ||
+	    (hdr.flags & ~NTFS_STREAM_FL_UTF16) || hdr.reserved)
+		return -EINVAL;
+
+	utf16 = hdr.flags & NTFS_STREAM_FL_UTF16;
+
+	if (hdr.name_len == 0)
+		return -EINVAL;
+	if (utf16) {
+		/* UTF-16LE byte count: must be even and within the limit. */
+		if ((hdr.name_len & 1) ||
+		    hdr.name_len > NTFS_MAX_NAME_LEN * sizeof(__le16))
+			return -EINVAL;
+	} else if (hdr.name_len > NTFS_MAX_NAME_LEN) {
+		return -EINVAL;
+	}
+
+	switch (op) {
+	case NTFS_STREAM_OP_READ:
+		if (!(filp->f_mode & FMODE_READ) || hdr.io_len == 0)
+			return -EINVAL;
+		data_size = hdr.io_len;
+		break;
+	case NTFS_STREAM_OP_WRITE:
+		if (!(filp->f_mode & FMODE_WRITE) || hdr.io_len == 0)
+			return -EINVAL;
+		data_size = hdr.io_len;
+		break;
+	case NTFS_STREAM_OP_REMOVE:
+		if (!(filp->f_mode & FMODE_WRITE) || hdr.io_len != 0)
+			return -EINVAL;
+		data_size = 0;
+		break;
+	default:
+		return -ENOTTY;
+	}
+
+	if (check_add_overflow(header_size, (size_t)hdr.name_len, &total_size) ||
+	    check_add_overflow(total_size, data_size, &total_size))
+		return -EOVERFLOW;
+
+	req = memdup_user(ureq, total_size);
+	if (IS_ERR(req))
+		return PTR_ERR(req);
+
+	req->bytes_returned = 0;
+
+	kname = (char *)req->buffer;
+	kdata = (char *)req->buffer + req->name_len;
+
+	if (req->name_len != hdr.name_len || req->io_len != hdr.io_len ||
+	    req->flags != hdr.flags || req->reserved) {
+		err = -EINVAL;
+		goto out_free;
+	}
+
+	if (utf16) {
+		/* Name is already on-disk UTF-16LE; use it in place. */
+		sname = (__le16 *)kname;
+		sname_len = req->name_len / sizeof(__le16);
+	} else {
+		if (memchr(kname, '\0', req->name_len)) {
+			err = -EINVAL;
+			goto out_free;
+		}
+		sname_len = ntfs_nlstoucs(ni->vol, kname, req->name_len,
+				&uname, NTFS_MAX_NAME_LEN);
+		if (sname_len < 0) {
+			err = sname_len;
+			goto out_free;
+		}
+		sname = uname;
+	}
+
+	switch (op) {
+	case NTFS_STREAM_OP_READ:
+		err = ntfs_read_named_stream(ni, sname, sname_len,
+					     req->stream_offset, req->io_len,
+					     kdata,
+					     &req->bytes_returned);
+		if (!err) {
+			if (copy_to_user(ureq, req,
+					header_size + req->name_len + req->bytes_returned))
+				err = -EFAULT;
+		}
+		break;
+	case NTFS_STREAM_OP_WRITE:
+		err = mnt_want_write_file(filp);
+		if (err)
+			break;
+		err = ntfs_write_named_stream(ni, sname, sname_len,
+					      req->stream_offset, req->io_len,
+					      kdata,
+					      &req->bytes_returned);
+		mnt_drop_write_file(filp);
+		if (!err) {
+			if (copy_to_user(&ureq->bytes_returned,
+					 &req->bytes_returned,
+					 sizeof(req->bytes_returned)))
+				err = -EFAULT;
+		}
+		break;
+	case NTFS_STREAM_OP_REMOVE:
+		err = mnt_want_write_file(filp);
+		if (err)
+			break;
+		err = ntfs_remove_named_stream(ni, sname, sname_len);
+		mnt_drop_write_file(filp);
+		break;
+	default:
+		err = -ENOTTY;
+	}
+
+out_free:
+	if (uname)
+		kmem_cache_free(ntfs_name_cache, uname);
+	kfree(req);
+	return err;
+}
+
+static int ntfs_ioctl_list_streams(struct file *filp, unsigned long arg)
+{
+	struct inode *inode = file_inode(filp);
+	struct ntfs_inode *ni = NTFS_I(inode);
+	struct ntfs_list_streams hdr;
+	struct ntfs_stream_entry *entry, *last_entry = NULL;
+	size_t required = 0;
+	void *kbuf = NULL;
+	void __user *ubuf;
+	struct attr_record *a;
+	struct ntfs_attr_search_ctx *actx;
+	int ret = 0, err, count = 0;
+	size_t entry_size, offset = 0;
+	unsigned int name_len;
+	unsigned char *sn;
+	bool utf16;
+
+	if (copy_from_user(&hdr, (void __user *)arg, sizeof(hdr)))
+		return -EFAULT;
+
+	if ((hdr.flags & ~NTFS_STREAM_FL_UTF16) || hdr.reserved)
+		return -EINVAL;
+
+	utf16 = hdr.flags & NTFS_STREAM_FL_UTF16;
+
+	ubuf = (void __user *)arg + sizeof(hdr);
+
+	mutex_lock(&ni->mrec_lock);
+	actx = ntfs_attr_get_search_ctx(ni, NULL);
+	if (!actx) {
+		mutex_unlock(&ni->mrec_lock);
+		return -ENOMEM;
+	}
+
+	while ((err = ntfs_attrs_walk(actx)) == 0) {
+		a = actx->attr;
+		if (a->type != AT_DATA || !a->name_length)
+			continue;
+
+		if (utf16) {
+			name_len = a->name_length * sizeof(__le16);
+		} else {
+			sn = ntfs_attr_name_get(ni->vol,
+					(__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)),
+					a->name_length);
+			if (!sn) {
+				ret = -EIO;
+				goto out;
+			}
+			name_len = strlen(sn);
+			ntfs_attr_name_free(&sn);
+		}
+
+		entry_size = sizeof(*entry) + name_len;
+		entry_size = ALIGN(entry_size, 8);
+
+		if (required > SIZE_MAX - entry_size) {
+			ret = -EOVERFLOW;
+			goto out;
+		}
+		required += entry_size;
+		count++;
+	}
+	if (err != -ENOENT) {
+		ret = err;
+		goto out;
+	}
+
+	hdr.stream_count = count;
+	hdr.bytes_returned = required;
+
+	if (!count)
+		goto out;
+
+	if (hdr.buffer_size < required) {
+		ret = -ENOSPC;
+		goto out;
+	}
+
+	kbuf = kvzalloc(required, GFP_NOFS);
+	if (!kbuf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ntfs_attr_reinit_search_ctx(actx);
+	while ((err = ntfs_attrs_walk(actx)) == 0) {
+		a = actx->attr;
+		if (a->type != AT_DATA || !a->name_length)
+			continue;
+		if (utf16) {
+			sn = NULL;
+			name_len = a->name_length * sizeof(__le16);
+		} else {
+			sn = ntfs_attr_name_get(ni->vol,
+					(__le16 *)((u8 *)a + le16_to_cpu(a->name_offset)),
+					a->name_length);
+			if (!sn) {
+				ret = -EIO;
+				goto out;
+			}
+			name_len = strlen(sn);
+		}
+
+		entry_size = sizeof(*entry) + name_len;
+		entry_size = ALIGN(entry_size, 8);
+		if (offset > required - entry_size) {
+			ret = -EOVERFLOW;
+			ntfs_attr_name_free(&sn);
+			goto out;
+		}
+
+		entry = (struct ntfs_stream_entry *)(kbuf + offset);
+
+		if (a->non_resident) {
+			entry->size = le64_to_cpu(a->data.non_resident.data_size);
+			entry->alloc_size =
+				le64_to_cpu(a->data.non_resident.allocated_size);
+
+		} else {
+			entry->size = le32_to_cpu(a->data.resident.value_length);
+			entry->alloc_size = le32_to_cpu(a->length) -
+				le16_to_cpu(a->data.resident.value_offset);
+		}
+
+		entry->name_len = name_len;
+		if (utf16)
+			memcpy(entry->name,
+			       (u8 *)a + le16_to_cpu(a->name_offset), name_len);
+		else
+			memcpy(entry->name, sn, name_len);
+		ntfs_attr_name_free(&sn);
+
+		entry->next_entry_off = entry_size;
+		last_entry = entry;
+		offset += entry_size;
+	}
+	if (err != -ENOENT) {
+		ret = err;
+	} else {
+		/* The chain terminates at the last entry. */
+		if (last_entry)
+			last_entry->next_entry_off = 0;
+		hdr.bytes_returned = offset;
+	}
+
+out:
+	mutex_unlock(&ni->mrec_lock);
+	ntfs_attr_put_search_ctx(actx);
+
+	if (ret && ret != -ENOSPC)
+		goto out_free;
+
+	if (!ret && kbuf && copy_to_user(ubuf, kbuf, hdr.bytes_returned)) {
+		ret = -EFAULT;
+		goto out_free;
+	}
+
+	if (copy_to_user((void __user *)arg, &hdr, sizeof(hdr)))
+		ret = -EFAULT;
+
+out_free:
+	kvfree(kbuf);
+	return ret;
+}
+
 long ntfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
 	switch (cmd) {
@@ -850,6 +1351,14 @@ long ntfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 		return ntfs_ioctl_set_volume_label(filp, arg);
 	case FITRIM:
 		return ntfs_ioctl_fitrim(NTFS_SB(file_inode(filp)->i_sb), arg);
+	case NTFS_IOC_STREAM_READ:
+		return ntfs_ioctl_stream(filp, arg, NTFS_STREAM_OP_READ);
+	case NTFS_IOC_STREAM_WRITE:
+		return ntfs_ioctl_stream(filp, arg, NTFS_STREAM_OP_WRITE);
+	case NTFS_IOC_STREAM_REMOVE:
+		return ntfs_ioctl_stream(filp, arg, NTFS_STREAM_OP_REMOVE);
+	case NTFS_IOC_LIST_STREAMS:
+		return ntfs_ioctl_list_streams(filp, arg);
 	default:
 		return -ENOTTY;
 	}
diff --git a/include/uapi/linux/ntfs.h b/include/uapi/linux/ntfs.h
new file mode 100644
index 000000000000..a5783dc3b905
--- /dev/null
+++ b/include/uapi/linux/ntfs.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2026 LG Electronics Co., Ltd.
+ */
+
+#ifndef _UAPI_LINUX_NTFS_H
+#define _UAPI_LINUX_NTFS_H
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define NTFS_IOC_MAGIC	0xEF
+
+/*
+ * Flags for ntfs_stream.flags and ntfs_list_streams.flags.
+ *
+ * The stream name is raw UTF-16LE, exactly as stored on disk, instead of
+ * being encoded with the mounted filesystem NLS. This allows lossless
+ * round-tripping of stream names whose characters are not representable
+ * in the mount NLS (e.g. for Wine, which works in UTF-16 natively).
+ * When set, the name length fields count bytes of UTF-16LE and must
+ * therefore be even.
+ */
+#define NTFS_STREAM_FL_UTF16	0x1
+
+/*
+ * ntfs named stream ioctl structure.
+ *
+ * @stream_offset:	Offset within the named stream.
+ * @io_len:		Number of bytes to read/write.
+ * @bytes_returned:	Actual bytes transferred (out).
+ * @name_len:		Stream name length in bytes, not including any
+ *			terminating NUL (which must not be present). When
+ *			NTFS_STREAM_FL_UTF16 is set this is a UTF-16LE byte
+ *			count and must be even.
+ * @flags:		Bit mask of NTFS_STREAM_FL_* flags. other bits must be
+ *			zero.
+ * @reserved:		Must be zero.
+ * @buffer:		Stream name followed by stream data.
+ *
+ * The stream name is the bare stream name, without the leading ':' or the
+ * trailing ":$DATA" decoration used by Windows. By default it is encoded with
+ * the mounted filesystem NLS. If NTFS_STREAM_FL_UTF16 is set in @flags it is
+ * raw UTF-16LE instead. For read and write, @io_len bytes of data follow the
+ * name. For remove, only the name is required and @io_len must be zero.
+ */
+struct ntfs_stream {
+	__aligned_u64 stream_offset;
+	__aligned_u64 io_len;
+	__aligned_u64 bytes_returned;
+	__u32 name_len;
+	__u32 flags;
+	__aligned_u64 reserved;
+	__u8 buffer[]; /* name + data */
+};
+
+/*
+ * Single stream entry returned by NTFS_IOC_LIST_STREAMS.
+ *
+ * @next_entry_off:	Byte offset from the start of this entry to the next
+ *			entry, or zero for the last entry. Always a multiple
+ *			of 8. Consumers must use this to advance instead of
+ *			computing the stride themselves, so the entry can grow
+ *			new fields without breaking the ABI.
+ * @size:		Stream data size in bytes.
+ * @alloc_size:		Bytes allocated for the stream (cluster aligned for
+ *			non-resident streams).
+ * @name_len:		Stream name length in bytes, not including a NUL
+ *			terminator (none is stored). Counts UTF-16LE bytes
+ *			when NTFS_STREAM_FL_UTF16 was requested.
+ * @reserved:		Must be zero.
+ * @name:		Bare stream name; NLS encoded, or raw UTF-16LE when
+ *			NTFS_STREAM_FL_UTF16 was requested (see struct
+ *			ntfs_stream).
+ */
+struct ntfs_stream_entry {
+	__aligned_u64 next_entry_off;
+	__aligned_u64 size;
+	__aligned_u64 alloc_size;
+	__u32 name_len;
+	__u32 reserved;
+	__u8 name[];
+};
+
+/*
+ * ntfs list streams ioctl structure.
+ *
+ * @buffer_size:	user buffer size(in).
+ * @bytes_returned:	actual bytes written or required(out).
+ * @stream_count:	number of streams(out).
+ * @flags:		Bit mask of NTFS_STREAM_FL_* flags controlling the
+ *			encoding of the returned names; other bits must be zero.
+ * @reserved:		Must be zero.
+ * @buffer:		ntfs_stream_entry array.
+ *
+ * The variable-length entries follow the header in @buffer, each aligned on
+ * an 8-byte boundary and chained via ntfs_stream_entry.next_entry_off. If
+ * @buffer_size is too small, no data is copied, @stream_count and
+ * @bytes_returned report the required values and the ioctl fails with
+ * -ENOSPC.
+ */
+struct ntfs_list_streams {
+	__aligned_u64 buffer_size;
+	__aligned_u64 bytes_returned;
+	__aligned_u64 stream_count;
+	__u32 flags;
+	__u32 reserved;
+	__u8 buffer[];
+};
+
+#define NTFS_IOC_STREAM_READ		_IOWR(NTFS_IOC_MAGIC, 1, struct ntfs_stream)
+#define NTFS_IOC_STREAM_WRITE		_IOWR(NTFS_IOC_MAGIC, 2, struct ntfs_stream)
+#define NTFS_IOC_STREAM_REMOVE		_IOWR(NTFS_IOC_MAGIC, 3, struct ntfs_stream)
+#define NTFS_IOC_LIST_STREAMS		_IOWR(NTFS_IOC_MAGIC, 4, struct ntfs_list_streams)
+
+#endif /* _UAPI_LINUX_NTFS_H */
-- 
2.25.1


      reply	other threads:[~2026-07-29 10:57 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-29 10:52 [PATCH 0/1] ntfs: introduce named data stream ioctls Namjae Jeon
2026-07-29 10:52 ` Namjae Jeon [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260729105259.11810-2-linkinjeon@kernel.org \
    --to=linkinjeon@kernel.org \
    --cc=Lionelcons1972@gmail.com \
    --cc=cedric.blancher@gmail.com \
    --cc=hyc.lee@gmail.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ntfs@lists.linux.dev \
    --cc=sebastian.n.feld@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.