mirror of https://lore.kernel.org/lkml/
 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 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


             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®