[PATCH 1/1] ntfs: add ioctl support for named data streams

From: Namjae Jeon

Date: Wed Jul 29 2026 - 07:49:34 EST


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@xxxxxxxxxx>
---
.../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@xxxxxxxxxxxxxxxx>
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