[PATCH 0/1] ntfs: introduce named data stream ioctls

From: Namjae Jeon

Date: Wed Jul 29 2026 - 07:28:29 EST


NTFS supports multiple named $DATA attributes, commonly known as
alternate data streams. This adds NTFS-specific ioctls to list,
read, write, and remove named streams from an opened file.

Example code (ntfs-stream-ioctl.c):

Usage: ./ntfs-stream-ioctl <file> <command> [args...] [--utf16]

Commands:
list List all named streams
read <stream_name> Read content of a stream
write <stream_name> <data> Write data to a stream
remove <stream_name> Delete a named stream

Options:
--utf16 Use raw UTF-16LE mode (lossless)


Example usage:

$ ./ntfs-stream-ioctl /mnt/file write Note "This is a secret note"
Successfully written to stream: Note

$ ./ntfs-stream-ioctl /mnt/file read Note
This is a secret note

$ ./ntfs-stream-ioctl /mnt/file list
Total named streams: 2
Note (size: 21 bytes)
stream0 (size: 12 bytes)


ntfs-stream-ioctl.c:

#include <errno.h>
#include <fcntl.h>
#include <linux/ntfs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>

static void die(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}

static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s <file> <command> [args...] [--utf16]\n\n", prog);
fprintf(stderr, "Commands:\n");
fprintf(stderr, " list List all named streams\n");
fprintf(stderr, " read <stream_name> Read content of a stream\n");
fprintf(stderr, " write <stream_name> <data> Write data to a stream\n");
fprintf(stderr, " remove <stream_name> Delete a named stream\n\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " --utf16 Use raw UTF-16LE mode (lossless)\n\n");
exit(EXIT_FAILURE);
}

static void copy_name_utf16le(void *dest, const char *src, size_t len)
{
uint8_t *d = dest;
for (size_t i = 0; i < len; i++) {
d[i*2] = (uint8_t)src[i]; // Little-endian low byte
d[i*2 + 1] = 0; // high byte = 0 (ASCII)
}
}

static void stream_write(int fd, const char *name, const char *data, bool utf16)
{
size_t name_len = strlen(name);
size_t data_len = strlen(data);
size_t name_buf_len = utf16 ? name_len * 2 : name_len;
size_t req_len = sizeof(struct ntfs_stream) + name_buf_len + data_len;

struct ntfs_stream *req = calloc(1, req_len);
if (!req) die("calloc");

req->flags = utf16 ? NTFS_STREAM_FL_UTF16 : 0;
req->name_len = name_buf_len;
req->io_len = data_len;

if (utf16)
copy_name_utf16le(req->buffer, name, name_len);
else
memcpy(req->buffer, name, name_len);

memcpy(req->buffer + name_buf_len, data, data_len);

if (ioctl(fd, NTFS_IOC_STREAM_WRITE, req) < 0)
die("NTFS_IOC_STREAM_WRITE");

printf("Successfully written to stream: %s\n", name);
free(req);
}

static void stream_read(int fd, const char *name, bool utf16)
{
size_t name_len = strlen(name);
size_t name_buf_len = utf16 ? name_len * 2 : name_len;
size_t buf_size = 8192;
size_t req_len = sizeof(struct ntfs_stream) + name_buf_len + buf_size;

struct ntfs_stream *req = calloc(1, req_len);
if (!req) die("calloc");

req->flags = utf16 ? NTFS_STREAM_FL_UTF16 : 0;
req->name_len = name_buf_len;
req->io_len = buf_size;
req->stream_offset = 0;

if (utf16)
copy_name_utf16le(req->buffer, name, name_len);
else
memcpy(req->buffer, name, name_len);

if (ioctl(fd, NTFS_IOC_STREAM_READ, req) < 0)
die("NTFS_IOC_STREAM_READ");

printf("%.*s\n", (int)req->bytes_returned,
(char *)req->buffer + req->name_len);

free(req);
}

static void list_streams(int fd, bool utf16)
{
struct ntfs_list_streams *list;
struct ntfs_stream_entry *entry;
size_t buf_len = 8192 * 4;
size_t off = 0;

list = calloc(1, sizeof(*list) + buf_len);
if (!list) die("calloc");

list->buffer_size = buf_len;
list->flags = utf16 ? NTFS_STREAM_FL_UTF16 : 0;

if (ioctl(fd, NTFS_IOC_LIST_STREAMS, list) < 0)
die("NTFS_IOC_LIST_STREAMS");

printf("Total named streams: %llu\n", (unsigned long long)list->stream_count);

while (off < list->bytes_returned) {
entry = (struct ntfs_stream_entry *)(list->buffer + off);

if (utf16) {
printf(" UTF16: ");
for (size_t i = 0; i + 1 < entry->name_len; i += 2) {
uint16_t c = entry->name[i] | (entry->name[i+1] << 8);
if (c >= 0x20 && c < 0x7F)
putchar(c);
else
printf("\\u%04x", c);
}
printf(" (len=%u bytes, size=%llu)\n",
entry->name_len, (unsigned long long)entry->size);
} else {
printf(" %.*s (size: %llu bytes)\n",
(int)entry->name_len, entry->name,
(unsigned long long)entry->size);
}

if (entry->next_entry_off == 0)
break;

off += entry->next_entry_off;
}

free(list);
}

static void stream_remove(int fd, const char *name, bool utf16)
{
size_t name_len = strlen(name);
size_t name_buf_len = utf16 ? name_len * 2 : name_len;
size_t req_len = sizeof(struct ntfs_stream) + name_buf_len;

struct ntfs_stream *req = calloc(1, req_len);
if (!req) die("calloc");

req->flags = utf16 ? NTFS_STREAM_FL_UTF16 : 0;
req->name_len = name_buf_len;
req->io_len = 0;

if (utf16)
copy_name_utf16le(req->buffer, name, name_len);
else
memcpy(req->buffer, name, name_len);

if (ioctl(fd, NTFS_IOC_STREAM_REMOVE, req) < 0)
die("NTFS_IOC_STREAM_REMOVE");

printf("Successfully removed stream: %s\n", name);
free(req);
}

int main(int argc, char **argv)
{
int fd;
const char *filename;
bool utf16 = false;
int cmd_idx = 2;

if (argc < 3)
usage(argv[0]);

filename = argv[1];

for (int i = 3; i < argc; i++) {
if (strcmp(argv[i], "--utf16") == 0) {
utf16 = true;
argc = i;
break;
}
}

fd = open(filename, O_RDWR);
if (fd < 0)
die("open");

if (strcmp(argv[cmd_idx], "list") == 0) {
list_streams(fd, utf16);
}
else if (strcmp(argv[cmd_idx], "read") == 0) {
if (argc < cmd_idx + 2) usage(argv[0]);
stream_read(fd, argv[cmd_idx + 1], utf16);
}
else if (strcmp(argv[cmd_idx], "write") == 0) {
if (argc < cmd_idx + 3) usage(argv[0]);
stream_write(fd, argv[cmd_idx + 1], argv[cmd_idx + 2], utf16);
}
else if (strcmp(argv[cmd_idx], "remove") == 0) {
if (argc < cmd_idx + 2) usage(argv[0]);
stream_remove(fd, argv[cmd_idx + 1], utf16);
}
else {
fprintf(stderr, "Unknown command: %s\n", argv[cmd_idx]);
usage(argv[0]);
}

close(fd);
return EXIT_SUCCESS;
}

Namjae Jeon (1):
ntfs: add ioctl support for named data streams

.../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

--
2.25.1