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 0/1] ntfs: introduce named data stream ioctls
Date: Wed, 29 Jul 2026 19:52:58 +0900 [thread overview]
Message-ID: <20260729105259.11810-1-linkinjeon@kernel.org> (raw)
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
next reply other threads:[~2026-07-29 10:56 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-29 10:52 Namjae Jeon [this message]
2026-07-29 10:52 ` [PATCH 1/1] ntfs: ntfs: add ioctl support for named data streams Namjae Jeon
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-1-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®