- From e6146273ff88629bf67b9cfc9854ad623ea115f2 Mon Sep 17 00:00:00 2001
- From: Roland Mainz <roland.mainz@nrubsig.org>
- Date: Thu, 22 Jan 2026 12:49:08 +0100
- Subject: [PATCH 1/2] cygwin,tests: Add new winstreamsutil utility
- Add new winstreamsutil utility to get info about Win32 named streams,
- find them, rename them, delete etc.
- Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
- ---
- cygwin/Makefile | 4 +
- cygwin/Makefile.install | 2 +
- cygwin/devel/msnfs41client.bash | 1 +
- tests/manual_testing.txt | 6 +-
- tests/winstreamsutil/Makefile | 26 ++
- tests/winstreamsutil/winstreamsutil.c | 590 ++++++++++++++++++++++++++
- 6 files changed, 627 insertions(+), 2 deletions(-)
- create mode 100644 tests/winstreamsutil/Makefile
- create mode 100644 tests/winstreamsutil/winstreamsutil.c
- diff --git a/cygwin/Makefile b/cygwin/Makefile
- index 5d058a9..6d7f71d 100644
- --- a/cygwin/Makefile
- +++ b/cygwin/Makefile
- @@ -30,6 +30,7 @@ $(PROJECT_BASEDIR_DIR)/tests/ea/nfs_ea.exe \
- $(PROJECT_BASEDIR_DIR)/tests/lockincfile1/lockincfile1.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winclonefile/winclonefile.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.exe \
- + $(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winrunassystem/winrunassystem.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winsg/winsg.exe: build_testutils
- @@ -85,6 +86,7 @@ build_testutils:
- (cd "$(PROJECT_BASEDIR_DIR)/tests/lockincfile1" && make all)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winclonefile" && make all)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile" && make all)
- + (cd "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil" && make all)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winrunassystem" && make all)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winsg" && make all)
- @@ -127,6 +129,7 @@ clean:
- (cd "$(PROJECT_BASEDIR_DIR)/tests/lockincfile1" && make clean)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winclonefile" && make clean)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile" && make clean)
- + (cd "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil" && make clean)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winrunassystem" && make clean)
- (cd "$(PROJECT_BASEDIR_DIR)/tests/winsg" && make clean)
- @@ -142,6 +145,7 @@ installdest_util: \
- $(PROJECT_BASEDIR_DIR)/tests/lockincfile1/lockincfile1.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winclonefile/winclonefile.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.exe \
- + $(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winrunassystem/winrunassystem.exe \
- $(PROJECT_BASEDIR_DIR)/tests/winsg/winsg.exe \
- $(CYGWIN_MAKEFILE_DIR)/devel/msnfs41client.bash
- diff --git a/cygwin/Makefile.install b/cygwin/Makefile.install
- index c003e1e..f4c2983 100644
- --- a/cygwin/Makefile.install
- +++ b/cygwin/Makefile.install
- @@ -167,6 +167,8 @@ installdest:
- cp "$(PROJECT_BASEDIR_DIR)/tests/winclonefile/winclonefile.i686.exe" $(DESTDIR)/bin/winclonefile.i686.exe
- cp "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.x86_64.exe" $(DESTDIR)/bin/winoffloadcopyfile.x86_64.exe
- cp "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.i686.exe" $(DESTDIR)/bin/winoffloadcopyfile.i686.exe
- + cp "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.x86_64.exe" $(DESTDIR)/bin/winstreamsutil.x86_64.exe
- + cp "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.i686.exe" $(DESTDIR)/bin/winstreamsutil.i686.exe
- cp "$(PROJECT_BASEDIR_DIR)/tests/catdbgprint/catdbgprint.x86_64.exe" $(DESTDIR)/sbin/catdbgprint.x86_64.exe
- cp "$(PROJECT_BASEDIR_DIR)/tests/catdbgprint/catdbgprint.i686.exe" $(DESTDIR)/sbin/catdbgprint.i686.exe
- cp "$(PROJECT_BASEDIR_DIR)/tests/winrunassystem/winrunassystem.x86_64.exe" $(DESTDIR)/sbin/winrunassystem.x86_64.exe
- diff --git a/cygwin/devel/msnfs41client.bash b/cygwin/devel/msnfs41client.bash
- index c963e45..7a21fbc 100755
- --- a/cygwin/devel/msnfs41client.bash
- +++ b/cygwin/devel/msnfs41client.bash
- @@ -247,6 +247,7 @@ function nfsclient_install
- 'bin/winfsinfo'
- 'bin/winclonefile'
- 'bin/winoffloadcopyfile'
- + 'bin/winstreamsutil'
- 'bin/winsg'
- 'bin/nfs_ea'
- 'sbin/catdbgprint'
- diff --git a/tests/manual_testing.txt b/tests/manual_testing.txt
- index 4cf0bb2..44088fb 100644
- --- a/tests/manual_testing.txt
- +++ b/tests/manual_testing.txt
- @@ -1,5 +1,5 @@
- #
- -# ms-nfs41-client manual testing sequence, 2026-01-17
- +# ms-nfs41-client manual testing sequence, 2026-01-22
- #
- # Draft version, needs to be turned into automated tests
- # if possible
- @@ -235,8 +235,10 @@ rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:files
- powershell -Command 'Get-Item -LiteralPath file1 -Stream * | Select *'
- # FIXME: tests for stream removal, enumeration and sparse data
- -# FIXME: Add tests for stream NTFS-style renaming (e.g. FileRenameInformation with "relative stream name", e.g. ':mystrname1:$DATA')
- +# NTFS-style renaming (e.g. FileRenameInformation with "relative stream name", e.g. ':mystrname1:$DATA')
- +# (last line should print "file1_line1")
- +rm -f file1 && touch file1 && cmd /c 'echo file1_line1 >>file1:mystrname1' && winstreamsutil renamestream file1 ':mystrname1' ':mystrname1_renamed' && cmd /C 'C:\cygwin64\bin\cat.exe <file1:mystrname1_renamed'
- #
- # Tests for Cycgwin/UWIN/SFU Nfs3Attr EA-based local uid/gid
- diff --git a/tests/winstreamsutil/Makefile b/tests/winstreamsutil/Makefile
- new file mode 100644
- index 0000000..31f368f
- --- /dev/null
- +++ b/tests/winstreamsutil/Makefile
- @@ -0,0 +1,26 @@
- +#
- +# Makefile for winstreamsutil
- +#
- +
- +# signtool.exe can be in either '/cygdrive/c/Program Files/' or '/cygdrive/c/Program Files (x86)/'
- +SIGNTOOL := $(shell ls -1 '/cygdrive/c/Program Files'*'/Microsoft SDKs/ClickOnce/SignTool/signtool.exe' | head -n 1)
- +
- +all: winstreamsutil.i686.exe winstreamsutil.x86_64.exe winstreamsutil.exe
- +
- +winstreamsutil.i686.exe: winstreamsutil.c
- + clang -target i686-pc-windows-gnu -std=gnu17 -Wall -Wextra -DUNICODE=1 -D_UNICODE=1 -municode -I../../include -g winstreamsutil.c -lntdll -o $@
- + bash -x -c '"$(SIGNTOOL)" sign /ph /fd "sha256" /sha1 "$${CERTIFICATE_THUMBPRINT%$$(printf "\r")}" $@'
- +
- +winstreamsutil.x86_64.exe: winstreamsutil.c
- + clang -target x86_64-pc-windows-gnu -std=gnu17 -Wall -Wextra -DUNICODE=1 -D_UNICODE=1 -municode -I../../include -g winstreamsutil.c -lntdll -o $@
- + bash -x -c '"$(SIGNTOOL)" sign /ph /fd "sha256" /sha1 "$${CERTIFICATE_THUMBPRINT%$$(printf "\r")}" $@'
- +
- +winstreamsutil.exe: winstreamsutil.x86_64.exe
- + ln -s winstreamsutil.x86_64.exe winstreamsutil.exe
- +
- +clean:
- + rm -fv \
- + winstreamsutil.i686.exe \
- + winstreamsutil.x86_64.exe \
- + winstreamsutil.exe
- +# EOF.
- diff --git a/tests/winstreamsutil/winstreamsutil.c b/tests/winstreamsutil/winstreamsutil.c
- new file mode 100644
- index 0000000..54e2429
- --- /dev/null
- +++ b/tests/winstreamsutil/winstreamsutil.c
- @@ -0,0 +1,590 @@
- +/*
- + * MIT License
- + *
- + * Copyright (c) 2004-2026 Roland Mainz <roland.mainz@nrubsig.org>
- + *
- + * Permission is hereby granted, free of charge, to any person obtaining a copy
- + * of this software and associated documentation files (the "Software"), to deal
- + * in the Software without restriction, including without limitation the rights
- + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- + * copies of the Software, and to permit persons to whom the Software is
- + * furnished to do so, subject to the following conditions:
- + *
- + * The above copyright notice and this permission notice shall be included in all
- + * copies or substantial portions of the Software.
- + *
- + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- + * SOFTWARE.
- + */
- +
- +/*
- + * winstreamsutil.c - Win32 named streams utility
- + *
- + * Written by Roland Mainz <roland.mainz@nrubsig.org>
- + */
- +
- +/*
- + * Compile with
- + * $ clang -target x86_64-pc-windows-gnu -std=gnu17 -Wall -Wextra \
- + * -municode -g winstreamsutil.c \
- + * -lntdll -o winstreamsutil.exe
- + */
- +
- +#define UNICODE 1
- +#define _UNICODE 1
- +
- +#include <windows.h>
- +#include <stdlib.h>
- +#include <stdbool.h>
- +#include <stdio.h>
- +#include <wchar.h>
- +
- +#define EXIT_USAGE (2) /* Traditional UNIX exit code for usage */
- +
- +#define NT_MAX_LONG_PATH 4096/*32767*/
- +
- +static
- +int lsstream_list_streams(const wchar_t *restrict progname,
- + const wchar_t *restrict path,
- + bool skip_default_stream,
- + bool print_details)
- +{
- + WIN32_FIND_STREAM_DATA sd;
- + (void)memset(&sd, 0, sizeof(sd));
- +
- + HANDLE h = FindFirstStreamW(path, FindStreamInfoStandard, &sd, 0);
- + if (h == INVALID_HANDLE_VALUE) {
- + DWORD e = GetLastError();
- + if (e == ERROR_HANDLE_EOF)
- + return EXIT_SUCCESS;
- + (void)fwprintf(stderr,
- + L"%ls: FindFirstStreamW(path='%ls'), lasterr=%d\n",
- + progname, path, (int)e);
- + return 3;
- + }
- +
- + for (;;) {
- + if (skip_default_stream) {
- + if (wcscmp(sd.cStreamName, L"::$DATA") == 0)
- + goto nextstr;
- + }
- +
- + if (print_details) {
- + (void)wprintf(L"filename='%ls%ls' size=%lld\n",
- + path,
- + sd.cStreamName,
- + (long long)sd.StreamSize.QuadPart);
- + }
- + else {
- + (void)wprintf(L"%ls%ls\n", path, sd.cStreamName);
- + }
- +
- +nextstr:
- + if (!FindNextStreamW(h, &sd)) {
- + DWORD e = GetLastError();
- + if (e == ERROR_HANDLE_EOF)
- + break;
- + (void)fwprintf(stderr,
- + L"%ls: FindNextStreamW() returned lasterr=%d\n",
- + progname, (int)e);
- + (void)FindClose(h);
- + return 4;
- + }
- + }
- +
- + (void)FindClose(h);
- + return EXIT_SUCCESS;
- +}
- +
- +static
- +int lsstream_walk(const wchar_t *restrict progname,
- + const wchar_t *restrict path,
- + bool find_recursive,
- + bool print_details)
- +{
- + wchar_t pattern[NT_MAX_LONG_PATH];
- + (void)swprintf(pattern, NT_MAX_LONG_PATH, L"%ls\\*", path);
- +
- + WIN32_FIND_DATAW fd;
- + HANDLE h = FindFirstFileW(pattern, &fd);
- + if (h == INVALID_HANDLE_VALUE) {
- + (void)fwprintf(stderr,
- + L"%ls: FindFirstFileW(path=%ls) returned lasterr=%d\n",
- + progname, path, (int)GetLastError());
- + return EXIT_FAILURE;
- + }
- +
- + do {
- + if (wcscmp(fd.cFileName, L".") == 0 ||
- + wcscmp(fd.cFileName, L"..") == 0)
- + continue;
- +
- + wchar_t full[NT_MAX_LONG_PATH];
- + (void)swprintf(full, NT_MAX_LONG_PATH, L"%ls\\%ls", path, fd.cFileName);
- +
- + lsstream_list_streams(progname, full, true, print_details);
- +
- + if (find_recursive && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
- + lsstream_walk(progname, full, find_recursive, print_details);
- + }
- + } while (FindNextFileW(h, &fd));
- +
- + DWORD e = GetLastError();
- + if (e != ERROR_NO_MORE_FILES) {
- + (void)fwprintf(stderr,
- + L"%ls: FindNextFileW() returned lasterr=%d\n",
- + progname, (int)e);
- + }
- +
- + (void)FindClose(h);
- + return EXIT_SUCCESS;
- +}
- +
- +static
- +int cmd_find(int ac, wchar_t *av[])
- +{
- + const wchar_t *progname = av[0];
- + bool find_recursive = false;
- + bool print_details = false;
- + bool print_usage = false;
- + int i;
- + wchar_t *find_path = NULL;
- +
- + for (i=2 ; i < ac ; i++) {
- + if (av[i][0] == L'/') {
- + if (wcscmp(av[i], L"/?") == 0)
- + print_usage = true;
- + else if (wcscmp(av[i], L"/s") == 0)
- + find_recursive = true;
- + else if (wcscmp(av[i], L"/-s") == 0)
- + find_recursive = false;
- + else if (wcscmp(av[i], L"/l") == 0)
- + print_details = true;
- + else if (wcscmp(av[i], L"/-l") == 0)
- + print_details = false;
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Unknown option '%ls'\n", progname, av[i]);
- + return EXIT_FAILURE;
- + }
- + }
- + else {
- + find_path = av[i];
- + }
- + }
- +
- + if (print_usage) {
- + (void)fwprintf(stderr,
- + L"Usage: winstreamutil find [/s} [path]\n"
- + L"\t/s\tRecurse into subdirs.\n"
- + L"\t/l\tPrint details.\n"
- + L"\tpath\tPath to search.");
- + return EXIT_USAGE;
- + }
- +
- + if (find_path == NULL) {
- + (void)fwprintf(stderr,
- + L"%ls: No path given.\n", progname);
- + return EXIT_FAILURE;
- + }
- +
- + lsstream_walk(progname, find_path, find_recursive, print_details);
- + return EXIT_SUCCESS;
- +}
- +
- +typedef LONG NTSTATUS;
- +#ifndef NT_SUCCESS
- +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
- +#endif
- +
- +typedef struct _IO_STATUS_BLOCK {
- + union { NTSTATUS Status; PVOID Pointer; } DUMMYUNIONNAME;
- + ULONG_PTR Information;
- +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
- +
- +typedef enum _FILE_INFORMATION_CLASS {
- + FileRenameInformation = 10
- +} FILE_INFORMATION_CLASS;
- +
- +typedef struct _FILE_RENAME_INFORMATION {
- + BOOLEAN ReplaceIfExists;
- + HANDLE RootDirectory;
- + ULONG FileNameLength;
- + WCHAR FileName[1];
- +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;
- +
- +NTSTATUS NTAPI NtSetInformationFile(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS);
- +ULONG NTAPI RtlNtStatusToDosError(NTSTATUS);
- +
- +static
- +HANDLE OpenForRenameW(const wchar_t *restrict path)
- +{
- + DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
- + DWORD flags = FILE_FLAG_BACKUP_SEMANTICS;
- + return CreateFileW(path, DELETE | SYNCHRONIZE, share, NULL, OPEN_EXISTING, flags, NULL);
- +}
- +
- +static
- +int cmd_renamestream(int ac, wchar_t *av[])
- +{
- + int res;
- + bool print_usage = false;
- + int i;
- + const wchar_t *progname = av[0];
- + wchar_t *base_path = NULL;
- + wchar_t *src_streamname = NULL;
- + wchar_t *dst_streamname = NULL;
- +
- + for (i=2 ; i < ac ; i++) {
- + if (av[i][0] == L'/') {
- + if (wcscmp(av[i], L"/?") == 0)
- + print_usage = true;
- + else {
- + (void)fwprintf(stderr, L"%ls: Unknown option '%ls'\n",
- + progname, av[i]);
- + return EXIT_FAILURE;
- + }
- + }
- + else {
- + if (base_path == NULL)
- + base_path = av[i];
- + else if (src_streamname == NULL)
- + src_streamname = av[i];
- + else if (dst_streamname == NULL)
- + dst_streamname = av[i];
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Too many filenames\n", progname);
- + return EXIT_FAILURE;
- + }
- + }
- + }
- +
- + if ((base_path == NULL) && (src_streamname == NULL) && (dst_streamname == NULL))
- + print_usage = true;
- +
- + if (print_usage) {
- + (void)fwprintf(stderr,
- + L"Usage: winstreamutil renamestream path srcstreamname dststreamname\n"
- + L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n"
- + L"\tsrcstreamname\tsrc stream name (e.g. \":mystr1:$DATA\")\n"
- + L"\tdststreamname\tdst stream name (e.g. \":mystr2:$DATA\")\n");
- + return EXIT_USAGE;
- + }
- +
- + if ((base_path == NULL) || (src_streamname == NULL) || (dst_streamname == NULL)) {
- + (void)fwprintf(stderr,
- + L"%ls: Missing paths/stream.\n", progname);
- + return EXIT_FAILURE;
- + }
- +
- + if ((src_streamname[0] != L':') || (dst_streamname[0] != L':')) {
- + (void)fwprintf(stderr,
- + L"%ls: Stream names must start with ':'\n", progname);
- + return EXIT_FAILURE;
- + }
- +
- + PFILE_RENAME_INFORMATION fri = calloc(1,
- + sizeof(FILE_RENAME_INFORMATION)+256*sizeof(wchar_t));
- + if (fri == NULL) {
- + (void)fwprintf(stderr,
- + L"%ls: Out of memory for fri.\n", progname);
- + return EXIT_FAILURE;
- + }
- +
- + wchar_t src_stream_path[NT_MAX_LONG_PATH];
- + (void)swprintf(src_stream_path, NT_MAX_LONG_PATH,
- + L"%ls%ls", base_path, src_streamname);
- +
- + HANDLE bh = OpenForRenameW(src_stream_path);
- + if (bh == INVALID_HANDLE_VALUE) {
- + (void)fwprintf(stderr,
- + L"%ls: Cannot open src stream '%ls', lasterr=%d\n",
- + progname,
- + src_stream_path, (int)GetLastError());
- + free(fri);
- + return EXIT_FAILURE;
- + }
- +
- + fri->ReplaceIfExists = FALSE;
- + fri->RootDirectory = NULL;
- + fri->FileNameLength = wcslen(dst_streamname)*sizeof(wchar_t);
- + (void)wcscpy(fri->FileName, dst_streamname);
- +
- + IO_STATUS_BLOCK iosb = { 0 };
- + NTSTATUS status = NtSetInformationFile(bh, &iosb,
- + fri,
- + (sizeof(FILE_RENAME_INFORMATION)+fri->FileNameLength),
- + FileRenameInformation);
- +
- + bool ok = (bool)NT_SUCCESS(status);
- + if (ok) {
- + (void)fwprintf(stdout, L"Renamed stream '%ls%ls' to '%ls%ls'.\n",
- + base_path, src_streamname,
- + base_path, dst_streamname);
- + res = EXIT_SUCCESS;
- + }
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Renaming stream '%ls%ls' to '%ls%ls' failed with lasterr=%d\n",
- + progname,
- + base_path, src_streamname,
- + base_path, dst_streamname,
- + (int)RtlNtStatusToDosError(status));
- + res = EXIT_FAILURE;
- + }
- +
- + (void)CloseHandle(bh);
- + free(fri);
- +
- + return res;
- +}
- +
- +static
- +int cmd_deletestream(int ac, wchar_t *av[])
- +{
- + int res;
- + bool print_usage = false;
- + int i;
- + const wchar_t *progname = av[0];
- + wchar_t *base_path = NULL;
- + wchar_t *streamname = NULL;
- +
- + for (i=2 ; i < ac ; i++) {
- + if (av[i][0] == L'/') {
- + if (wcscmp(av[i], L"/?") == 0)
- + print_usage = true;
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Unknown option '%ls'\n",
- + progname, av[i]);
- + return EXIT_FAILURE;
- + }
- + }
- + else {
- + if (base_path == NULL)
- + base_path = av[i];
- + else if (streamname == NULL)
- + streamname = av[i];
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Too many filenames\n",
- + progname);
- + return EXIT_FAILURE;
- + }
- + }
- + }
- +
- + if ((base_path == NULL) && (streamname == NULL))
- + print_usage = true;
- +
- + if (print_usage) {
- + (void)fwprintf(stderr,
- + L"Usage: winstreamutil deletestream path streamname\n"
- + L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n"
- + L"\tstreamname\tdst stream name (e.g. \":mystr2:$DATA\")\n");
- + return EXIT_USAGE;
- + }
- +
- + if ((base_path == NULL) || (streamname == NULL)) {
- + (void)fwprintf(stderr,
- + L"%ls: Missing paths/stream.\n", progname);
- + return EXIT_FAILURE;
- + }
- +
- + if (streamname[0] != L':') {
- + (void)fwprintf(stderr,
- + L"%ls: Stream names must start with ':'\n", progname);
- + return EXIT_FAILURE;
- + }
- +
- + wchar_t stream_path[NT_MAX_LONG_PATH];
- + (void)swprintf(stream_path, NT_MAX_LONG_PATH,
- + L"%ls%ls", base_path, streamname);
- +
- + bool ok = (bool)DeleteFileW(stream_path);
- + if (ok) {
- + (void)fwprintf(stdout, L"Deleted stream '%ls%ls'.\n",
- + base_path, streamname);
- + res = EXIT_SUCCESS;
- + }
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Delete failed with lasterr=%d\n",
- + progname, (int)GetLastError());
- + res = EXIT_FAILURE;
- + }
- +
- + return res;
- +}
- +
- +static
- +int cmd_info(int ac, wchar_t *av[])
- +{
- + int res = EXIT_FAILURE;
- + bool ok;
- + PFILE_STREAM_INFO fsi = NULL;
- +
- + bool print_usage = false;
- + int i;
- + const wchar_t *progname = av[0];
- + const wchar_t *filename = NULL;
- +
- + for (i=2 ; i < ac ; i++) {
- + if (av[i][0] == L'/') {
- + if (wcscmp(av[i], L"/?") == 0)
- + print_usage = true;
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Unknown option '%ls'\n",
- + progname, av[i]);
- + return EXIT_FAILURE;
- + }
- + }
- + else {
- + if (filename == NULL)
- + filename = av[i];
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Too many filenames\n", progname);
- + return EXIT_FAILURE;
- + }
- + }
- + }
- +
- + if (filename == NULL)
- + print_usage = true;
- +
- + if (print_usage) {
- + (void)fwprintf(stderr,
- + L"Usage: winstreamutil info path\n"
- + L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n");
- + return EXIT_USAGE;
- + }
- +
- + HANDLE fileHandle = CreateFileW(filename,
- + GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
- + FILE_FLAG_BACKUP_SEMANTICS, NULL);
- + if (fileHandle == INVALID_HANDLE_VALUE) {
- + (void)fwprintf(stderr,
- + L"%ls: Error opening file '%ls', lasterr=%d.\n",
- + progname,
- + filename,
- + (int)GetLastError());
- + return EXIT_FAILURE;
- + }
- +
- +#define MAX_STREAM_INFOS (16)
- +#define FSI_MAXCHARS (256)
- + size_t fsi_size = (sizeof(FILE_STREAM_INFO)+sizeof(wchar_t)*FSI_MAXCHARS)*MAX_STREAM_INFOS;
- + fsi = calloc(1, fsi_size);
- + if (fsi == NULL) {
- + (void)fwprintf(stderr,
- + L"%ls: Out of memory.\n",
- + progname);
- + return EXIT_FAILURE;
- + }
- +
- + ok = GetFileInformationByHandleEx(fileHandle,
- + FileStreamInfo,
- + fsi, fsi_size);
- +
- + if (!ok) {
- + (void)fwprintf(stderr,
- + L"%ls: GetFileInformationByHandleEx() error, lasterr=%d.\n",
- + progname,
- + (int)GetLastError());
- + res = EXIT_FAILURE;
- + goto done;
- + }
- +
- + int streamindex;
- + const FILE_STREAM_INFO *stream;
- +
- + /*
- + * Output data as ksh93 compound variable (CPV), ksh93 can read+print
- + * this format with $ typeset -C var ; read -C var ; print -v var #
- + */
- + (void)wprintf(L"(\n");
- + (void)wprintf(L"\tfilename='%ls'\n", filename);
- + (void)wprintf(L"\ttypeset -a streams=(\n");
- +
- + for (stream = fsi, streamindex = 0 ; ; streamindex++) {
- + (void)wprintf(L"\t\t[%d]=(\n", streamindex);
- + (void)wprintf(L"\t\t\tStreamName='%.*ls'\n",
- + (int)(stream->StreamNameLength/sizeof(WCHAR)),
- + stream->StreamName);
- + (void)wprintf(L"\t\t\tStreamSize=%lld\n",
- + (long long)stream->StreamSize.QuadPart);
- + (void)wprintf(L"\t\t\tStreamAllocationSize=%lld\n",
- + (long long)stream->StreamAllocationSize.QuadPart);
- + (void)wprintf(L"\t\t)\n");
- +
- + if (stream->NextEntryOffset == 0)
- + break;
- +
- + stream = (const FILE_STREAM_INFO *)(((char *)stream) + stream->NextEntryOffset);
- + }
- + (void)wprintf(L"\t)\n");
- + (void)wprintf(L")\n");
- +
- + res = EXIT_SUCCESS;
- +
- +done:
- + free(fsi);
- + (void)CloseHandle(fileHandle);
- + return res;
- +}
- +
- +static
- +void usage(const wchar_t *restrict progname)
- +{
- + (void)fwprintf(stderr,
- + L"%ls: Win32 named streams utility\n"
- + L"(written by Roland Mainz <roland.mainz@nrubsig.org> "
- + L"for the ms-nfs41-client project)\n\n"
- + L"Available commands:\n"
- + L"info\tprint info about a stream as ksh93 compound variable\n"
- + L"find\tfind all non-default named streams in path\n"
- + L"renamestream\trename stream\n"
- + L"deletestream\tdelete stream\n",
- + progname);
- +}
- +
- +int wmain(int ac, wchar_t *av[])
- +{
- + if (ac < 2) {
- + (void)usage(av[0]);
- + return EXIT_USAGE;
- + }
- +
- + /*
- + * FIXME: ToDO: Add more sub commands:
- + * createnew, cat
- + */
- +
- + if (wcscmp(av[1], L"info") == 0) {
- + return cmd_info(ac, av);
- + }
- + else if (wcscmp(av[1], L"find") == 0) {
- + return cmd_find(ac, av);
- + }
- + else if (wcscmp(av[1], L"renamestream") == 0) {
- + return cmd_renamestream(ac, av);
- + }
- + else if (wcscmp(av[1], L"deletestream") == 0) {
- + return cmd_deletestream(ac, av);
- + }
- + else {
- + (void)fwprintf(stderr,
- + L"%ls: Unknown subcmd '%ls':\n",
- + av[0], av[1]);
- + }
- +
- + return EXIT_SUCCESS;
- +}
- --
- 2.51.0
- From 7693cb1084bbc3f657dfc1f24f63b2376c4cd022 Mon Sep 17 00:00:00 2001
- From: Roland Mainz <roland.mainz@nrubsig.org>
- Date: Thu, 22 Jan 2026 15:28:29 +0100
- Subject: [PATCH 2/2] daemon: Return |ERROR_NOT_SUPPORTED| for set/get Windows
- EA and get |FileStreamsInformation| with streamname (not filename)
- Setting/getting Windows EA (extended attributes) and getting
- |FileStreamsInformation| with streamname (not filename) is currently
- not implemented - make sure we return |ERROR_NOT_SUPPORTED| for now.
- Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
- ---
- daemon/ea.c | 40 ++++++++++++++++++++++++++++++++++++++++
- daemon/winstreams.c | 9 ++++++---
- 2 files changed, 46 insertions(+), 3 deletions(-)
- diff --git a/daemon/ea.c b/daemon/ea.c
- index 7c7b42e..dfff3f3 100644
- --- a/daemon/ea.c
- +++ b/daemon/ea.c
- @@ -25,12 +25,16 @@
- #include <stdio.h>
- #include <strsafe.h>
- +#include "nfs41_build_features.h"
- #include "from_kernel.h"
- #include "nfs41_ops.h"
- #include "delegation.h"
- #include "upcall.h"
- #include "daemon_debug.h"
- #include "nfs_ea.h"
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- +#include "winstreams.h"
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- /*
- * Compile safeguard to see whether |NFS4_EASIZE+header| will still fit into
- @@ -226,6 +230,24 @@ static int handle_setexattr(void *daemon_context, nfs41_upcall *upcall)
- goto out;
- }
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + /*
- + * FIXME: Setting EA with stream name is not supported
- + * (yet), the expectation is that doing this for stream "abc:str1" will
- + * set the EA for "abc"
- + *
- + * FIXME: What about setting mode etc. with Cygwin/SFU EAs ?
- + * This should effect the stream itself, right ?
- + */
- + if (is_stream_path_fh(&state->file)) {
- + DPRINTF(0,
- + ("handle_setexattr(name='%.*s'): "
- + "Setting EA with stream name not implemented yet\n",
- + (int)state->file.name.len, state->file.name.name));
- + return ERROR_NOT_SUPPORTED;
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- /* break read delegations before SETATTR */
- nfs41_delegation_return(state->session, &state->file,
- OPEN_DELEGATE_READ, FALSE);
- @@ -583,6 +605,24 @@ static int handle_getexattr(void *daemon_context, nfs41_upcall *upcall)
- uint32_t remaining, needed, index = 0;
- int status;
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + /*
- + * FIXME: Getting EA with stream name is not supported
- + * (yet), the expectation is that doing this for stream "abc:str1" will
- + * return EAs for "abc"
- + *
- + * FIXME: What about getting mode etc. with Cygwin/SFU EAs ?
- + * This should effect the stream itself, right ?
- + */
- + if (is_stream_path_fh(&state->file)) {
- + DPRINTF(0,
- + ("handle_getexattr(name='%.*s'): "
- + "Getting EA with stream name not implemented yet\n",
- + (int)state->file.name.len, state->file.name.name));
- + return ERROR_NOT_SUPPORTED;
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- status = nfs41_rpc_openattr(state->session, &state->file, FALSE, &parent.fh);
- if (status == NFS4ERR_NOENT) { /* no named attribute directory */
- DPRINTF(EALVL, ("no named attribute directory for '%s'\n", args->path));
- diff --git a/daemon/winstreams.c b/daemon/winstreams.c
- index 5f3e5af..e7dac64 100644
- --- a/daemon/winstreams.c
- +++ b/daemon/winstreams.c
- @@ -471,18 +471,21 @@ int get_streaminformation(
- }
- /*
- - * FIXME: |FileStreamInformation| for |NF4NAMEDATTR| is not supported
- + * FIXME: |FileStreamInformation| for streams is not supported
- * (yet), the expectation is that doing this for stream "abc:str1" will
- * return all streams for "abc"
- */
- - if (basefile_info->type == NF4NAMEDATTR) {
- + if (is_stream_path_fh(&state->file)) {
- DPRINTF(0,
- ("get_streaminformation(name='%.*s'): "
- - "stream info for NF4NAMEDATTR not implemented yet\n",
- + "stream info with stream name not implemented yet\n",
- (int)state->file.name.len, state->file.name.name));
- return ERROR_NOT_SUPPORTED;
- }
- + EASSERT(basefile_info->type != NF4ATTRDIR);
- + EASSERT(basefile_info->type != NF4NAMEDATTR);
- +
- status = nfs41_rpc_openattr(state->session, &state->file, FALSE,
- &parent.fh);
- --
- 2.51.0
msnfs41client: Patches for winstreamsutil.exe, tests+misc, 2026-01-22
Posted by Anonymous on Thu 22nd Jan 2026 15:02
raw | new post
Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.
rovema.kpaste.net RSS