pastebin - collaborative debugging tool
rovema.kpaste.net RSS


msnfs41client: Patches for winstreamsutil.exe, tests+misc, 2026-01-22
Posted by Anonymous on Thu 22nd Jan 2026 15:02
raw | new post

  1. From e6146273ff88629bf67b9cfc9854ad623ea115f2 Mon Sep 17 00:00:00 2001
  2. From: Roland Mainz <roland.mainz@nrubsig.org>
  3. Date: Thu, 22 Jan 2026 12:49:08 +0100
  4. Subject: [PATCH 1/2] cygwin,tests: Add new winstreamsutil utility
  5.  
  6. Add new winstreamsutil utility to get info about Win32 named streams,
  7. find them, rename them, delete etc.
  8.  
  9. Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
  10. ---
  11. cygwin/Makefile                       |   4 +
  12.  cygwin/Makefile.install               |   2 +
  13.  cygwin/devel/msnfs41client.bash       |   1 +
  14.  tests/manual_testing.txt              |   6 +-
  15.  tests/winstreamsutil/Makefile         |  26 ++
  16.  tests/winstreamsutil/winstreamsutil.c | 590 ++++++++++++++++++++++++++
  17.  6 files changed, 627 insertions(+), 2 deletions(-)
  18.  create mode 100644 tests/winstreamsutil/Makefile
  19.  create mode 100644 tests/winstreamsutil/winstreamsutil.c
  20.  
  21. diff --git a/cygwin/Makefile b/cygwin/Makefile
  22. index 5d058a9..6d7f71d 100644
  23. --- a/cygwin/Makefile
  24. +++ b/cygwin/Makefile
  25. @@ -30,6 +30,7 @@ $(PROJECT_BASEDIR_DIR)/tests/ea/nfs_ea.exe \
  26.         $(PROJECT_BASEDIR_DIR)/tests/lockincfile1/lockincfile1.exe \
  27.         $(PROJECT_BASEDIR_DIR)/tests/winclonefile/winclonefile.exe \
  28.         $(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.exe \
  29. +       $(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.exe \
  30.         $(PROJECT_BASEDIR_DIR)/tests/winrunassystem/winrunassystem.exe \
  31.         $(PROJECT_BASEDIR_DIR)/tests/winsg/winsg.exe: build_testutils
  32.  
  33. @@ -85,6 +86,7 @@ build_testutils:
  34.         (cd "$(PROJECT_BASEDIR_DIR)/tests/lockincfile1" && make all)
  35.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winclonefile" && make all)
  36.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile" && make all)
  37. +       (cd "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil" && make all)
  38.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winrunassystem" && make all)
  39.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winsg" && make all)
  40.  
  41. @@ -127,6 +129,7 @@ clean:
  42.         (cd "$(PROJECT_BASEDIR_DIR)/tests/lockincfile1" && make clean)
  43.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winclonefile" && make clean)
  44.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile" && make clean)
  45. +       (cd "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil" && make clean)
  46.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winrunassystem" && make clean)
  47.         (cd "$(PROJECT_BASEDIR_DIR)/tests/winsg" && make clean)
  48.  
  49. @@ -142,6 +145,7 @@ installdest_util: \
  50.         $(PROJECT_BASEDIR_DIR)/tests/lockincfile1/lockincfile1.exe \
  51.         $(PROJECT_BASEDIR_DIR)/tests/winclonefile/winclonefile.exe \
  52.         $(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.exe \
  53. +       $(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.exe \
  54.         $(PROJECT_BASEDIR_DIR)/tests/winrunassystem/winrunassystem.exe \
  55.         $(PROJECT_BASEDIR_DIR)/tests/winsg/winsg.exe \
  56.         $(CYGWIN_MAKEFILE_DIR)/devel/msnfs41client.bash
  57. diff --git a/cygwin/Makefile.install b/cygwin/Makefile.install
  58. index c003e1e..f4c2983 100644
  59. --- a/cygwin/Makefile.install
  60. +++ b/cygwin/Makefile.install
  61. @@ -167,6 +167,8 @@ installdest:
  62.         cp "$(PROJECT_BASEDIR_DIR)/tests/winclonefile/winclonefile.i686.exe" $(DESTDIR)/bin/winclonefile.i686.exe
  63.         cp "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.x86_64.exe" $(DESTDIR)/bin/winoffloadcopyfile.x86_64.exe
  64.         cp "$(PROJECT_BASEDIR_DIR)/tests/winoffloadcopyfile/winoffloadcopyfile.i686.exe" $(DESTDIR)/bin/winoffloadcopyfile.i686.exe
  65. +       cp "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.x86_64.exe" $(DESTDIR)/bin/winstreamsutil.x86_64.exe
  66. +       cp "$(PROJECT_BASEDIR_DIR)/tests/winstreamsutil/winstreamsutil.i686.exe" $(DESTDIR)/bin/winstreamsutil.i686.exe
  67.         cp "$(PROJECT_BASEDIR_DIR)/tests/catdbgprint/catdbgprint.x86_64.exe" $(DESTDIR)/sbin/catdbgprint.x86_64.exe
  68.         cp "$(PROJECT_BASEDIR_DIR)/tests/catdbgprint/catdbgprint.i686.exe" $(DESTDIR)/sbin/catdbgprint.i686.exe
  69.         cp "$(PROJECT_BASEDIR_DIR)/tests/winrunassystem/winrunassystem.x86_64.exe" $(DESTDIR)/sbin/winrunassystem.x86_64.exe
  70. diff --git a/cygwin/devel/msnfs41client.bash b/cygwin/devel/msnfs41client.bash
  71. index c963e45..7a21fbc 100755
  72. --- a/cygwin/devel/msnfs41client.bash
  73. +++ b/cygwin/devel/msnfs41client.bash
  74. @@ -247,6 +247,7 @@ function nfsclient_install
  75.                 'bin/winfsinfo'
  76.                 'bin/winclonefile'
  77.                 'bin/winoffloadcopyfile'
  78. +               'bin/winstreamsutil'
  79.                 'bin/winsg'
  80.                 'bin/nfs_ea'
  81.                 'sbin/catdbgprint'
  82. diff --git a/tests/manual_testing.txt b/tests/manual_testing.txt
  83. index 4cf0bb2..44088fb 100644
  84. --- a/tests/manual_testing.txt
  85. +++ b/tests/manual_testing.txt
  86. @@ -1,5 +1,5 @@
  87.  #
  88. -# ms-nfs41-client manual testing sequence, 2026-01-17
  89. +# ms-nfs41-client manual testing sequence, 2026-01-22
  90.  #
  91.  # Draft version, needs to be turned into automated tests
  92.  # if possible
  93. @@ -235,8 +235,10 @@ rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:files
  94.  powershell -Command 'Get-Item -LiteralPath file1 -Stream * | Select *'
  95.  
  96.  # FIXME: tests for stream removal, enumeration and sparse data
  97. -# FIXME: Add tests for stream NTFS-style renaming (e.g. FileRenameInformation with "relative stream name", e.g. ':mystrname1:$DATA')
  98.  
  99. +# NTFS-style renaming (e.g. FileRenameInformation with "relative stream name", e.g. ':mystrname1:$DATA')
  100. +# (last line should print "file1_line1")
  101. +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'
  102.  
  103.  #
  104.  # Tests for Cycgwin/UWIN/SFU Nfs3Attr EA-based local uid/gid
  105. diff --git a/tests/winstreamsutil/Makefile b/tests/winstreamsutil/Makefile
  106. new file mode 100644
  107. index 0000000..31f368f
  108. --- /dev/null
  109. +++ b/tests/winstreamsutil/Makefile
  110. @@ -0,0 +1,26 @@
  111. +#
  112. +# Makefile for winstreamsutil
  113. +#
  114. +
  115. +# signtool.exe can be in either '/cygdrive/c/Program Files/' or '/cygdrive/c/Program Files (x86)/'
  116. +SIGNTOOL := $(shell ls -1 '/cygdrive/c/Program Files'*'/Microsoft SDKs/ClickOnce/SignTool/signtool.exe' | head -n 1)
  117. +
  118. +all: winstreamsutil.i686.exe winstreamsutil.x86_64.exe winstreamsutil.exe
  119. +
  120. +winstreamsutil.i686.exe: winstreamsutil.c
  121. +       clang -target i686-pc-windows-gnu -std=gnu17 -Wall -Wextra -DUNICODE=1 -D_UNICODE=1 -municode -I../../include -g winstreamsutil.c -lntdll -o $@
  122. +       bash -x -c '"$(SIGNTOOL)" sign /ph /fd "sha256" /sha1 "$${CERTIFICATE_THUMBPRINT%$$(printf "\r")}" $@'
  123. +
  124. +winstreamsutil.x86_64.exe: winstreamsutil.c
  125. +       clang -target x86_64-pc-windows-gnu -std=gnu17 -Wall -Wextra -DUNICODE=1 -D_UNICODE=1 -municode -I../../include -g winstreamsutil.c -lntdll -o $@
  126. +       bash -x -c '"$(SIGNTOOL)" sign /ph /fd "sha256" /sha1 "$${CERTIFICATE_THUMBPRINT%$$(printf "\r")}" $@'
  127. +
  128. +winstreamsutil.exe: winstreamsutil.x86_64.exe
  129. +       ln -s winstreamsutil.x86_64.exe winstreamsutil.exe
  130. +
  131. +clean:
  132. +       rm -fv \
  133. +               winstreamsutil.i686.exe \
  134. +               winstreamsutil.x86_64.exe \
  135. +               winstreamsutil.exe
  136. +# EOF.
  137. diff --git a/tests/winstreamsutil/winstreamsutil.c b/tests/winstreamsutil/winstreamsutil.c
  138. new file mode 100644
  139. index 0000000..54e2429
  140. --- /dev/null
  141. +++ b/tests/winstreamsutil/winstreamsutil.c
  142. @@ -0,0 +1,590 @@
  143. +/*
  144. + * MIT License
  145. + *
  146. + * Copyright (c) 2004-2026 Roland Mainz <roland.mainz@nrubsig.org>
  147. + *
  148. + * Permission is hereby granted, free of charge, to any person obtaining a copy
  149. + * of this software and associated documentation files (the "Software"), to deal
  150. + * in the Software without restriction, including without limitation the rights
  151. + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  152. + * copies of the Software, and to permit persons to whom the Software is
  153. + * furnished to do so, subject to the following conditions:
  154. + *
  155. + * The above copyright notice and this permission notice shall be included in all
  156. + * copies or substantial portions of the Software.
  157. + *
  158. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  159. + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  160. + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  161. + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  162. + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  163. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  164. + * SOFTWARE.
  165. + */
  166. +
  167. +/*
  168. + * winstreamsutil.c - Win32 named streams utility
  169. + *
  170. + * Written by Roland Mainz <roland.mainz@nrubsig.org>
  171. + */
  172. +
  173. +/*
  174. + * Compile with
  175. + * $ clang -target x86_64-pc-windows-gnu -std=gnu17 -Wall -Wextra \
  176. + * -municode -g winstreamsutil.c \
  177. + * -lntdll -o winstreamsutil.exe
  178. + */
  179. +
  180. +#define UNICODE 1
  181. +#define _UNICODE 1
  182. +
  183. +#include <windows.h>
  184. +#include <stdlib.h>
  185. +#include <stdbool.h>
  186. +#include <stdio.h>
  187. +#include <wchar.h>
  188. +
  189. +#define        EXIT_USAGE (2) /* Traditional UNIX exit code for usage */
  190. +
  191. +#define NT_MAX_LONG_PATH 4096/*32767*/
  192. +
  193. +static
  194. +int lsstream_list_streams(const wchar_t *restrict progname,
  195. +    const wchar_t *restrict path,
  196. +    bool skip_default_stream,
  197. +    bool print_details)
  198. +{
  199. +    WIN32_FIND_STREAM_DATA sd;
  200. +    (void)memset(&sd, 0, sizeof(sd));
  201. +
  202. +    HANDLE h = FindFirstStreamW(path, FindStreamInfoStandard, &sd, 0);
  203. +    if (h == INVALID_HANDLE_VALUE) {
  204. +        DWORD e = GetLastError();
  205. +        if (e == ERROR_HANDLE_EOF)
  206. +            return EXIT_SUCCESS;
  207. +        (void)fwprintf(stderr,
  208. +            L"%ls: FindFirstStreamW(path='%ls'), lasterr=%d\n",
  209. +            progname, path, (int)e);
  210. +        return 3;
  211. +    }
  212. +
  213. +    for (;;) {
  214. +        if (skip_default_stream) {
  215. +            if (wcscmp(sd.cStreamName, L"::$DATA") == 0)
  216. +                goto nextstr;
  217. +        }
  218. +
  219. +        if (print_details) {
  220. +            (void)wprintf(L"filename='%ls%ls' size=%lld\n",
  221. +                path,
  222. +                sd.cStreamName,
  223. +                (long long)sd.StreamSize.QuadPart);
  224. +        }
  225. +        else {
  226. +            (void)wprintf(L"%ls%ls\n", path, sd.cStreamName);
  227. +        }
  228. +
  229. +nextstr:
  230. +        if (!FindNextStreamW(h, &sd)) {
  231. +            DWORD e = GetLastError();
  232. +            if (e == ERROR_HANDLE_EOF)
  233. +                break;
  234. +            (void)fwprintf(stderr,
  235. +                L"%ls: FindNextStreamW() returned lasterr=%d\n",
  236. +                progname, (int)e);
  237. +            (void)FindClose(h);
  238. +            return 4;
  239. +        }
  240. +    }
  241. +
  242. +    (void)FindClose(h);
  243. +    return EXIT_SUCCESS;
  244. +}
  245. +
  246. +static
  247. +int lsstream_walk(const wchar_t *restrict progname,
  248. +    const wchar_t *restrict path,
  249. +    bool find_recursive,
  250. +    bool print_details)
  251. +{
  252. +    wchar_t pattern[NT_MAX_LONG_PATH];
  253. +    (void)swprintf(pattern, NT_MAX_LONG_PATH, L"%ls\\*", path);
  254. +
  255. +    WIN32_FIND_DATAW fd;
  256. +    HANDLE h = FindFirstFileW(pattern, &fd);
  257. +    if (h == INVALID_HANDLE_VALUE) {
  258. +        (void)fwprintf(stderr,
  259. +            L"%ls: FindFirstFileW(path=%ls) returned lasterr=%d\n",
  260. +            progname, path, (int)GetLastError());
  261. +        return EXIT_FAILURE;
  262. +    }
  263. +
  264. +    do {
  265. +        if (wcscmp(fd.cFileName, L".") == 0 ||
  266. +            wcscmp(fd.cFileName, L"..") == 0)
  267. +            continue;
  268. +
  269. +        wchar_t full[NT_MAX_LONG_PATH];
  270. +        (void)swprintf(full, NT_MAX_LONG_PATH, L"%ls\\%ls", path, fd.cFileName);
  271. +
  272. +        lsstream_list_streams(progname, full, true, print_details);
  273. +
  274. +        if (find_recursive && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
  275. +            lsstream_walk(progname, full, find_recursive, print_details);
  276. +        }
  277. +    } while (FindNextFileW(h, &fd));
  278. +
  279. +    DWORD e = GetLastError();
  280. +    if (e != ERROR_NO_MORE_FILES) {
  281. +        (void)fwprintf(stderr,
  282. +            L"%ls: FindNextFileW() returned lasterr=%d\n",
  283. +            progname, (int)e);
  284. +    }
  285. +
  286. +    (void)FindClose(h);
  287. +    return EXIT_SUCCESS;
  288. +}
  289. +
  290. +static
  291. +int cmd_find(int ac, wchar_t *av[])
  292. +{
  293. +    const wchar_t *progname = av[0];
  294. +    bool find_recursive = false;
  295. +    bool print_details = false;
  296. +    bool print_usage = false;
  297. +    int i;
  298. +    wchar_t *find_path = NULL;
  299. +
  300. +    for (i=2 ; i < ac ; i++) {
  301. +        if (av[i][0] == L'/') {
  302. +            if (wcscmp(av[i], L"/?") == 0)
  303. +                print_usage = true;
  304. +            else if (wcscmp(av[i], L"/s") == 0)
  305. +                find_recursive = true;
  306. +            else if (wcscmp(av[i], L"/-s") == 0)
  307. +                find_recursive = false;
  308. +            else if (wcscmp(av[i], L"/l") == 0)
  309. +                print_details = true;
  310. +            else if (wcscmp(av[i], L"/-l") == 0)
  311. +                print_details = false;
  312. +            else {
  313. +                (void)fwprintf(stderr,
  314. +                    L"%ls: Unknown option '%ls'\n", progname, av[i]);
  315. +                return EXIT_FAILURE;
  316. +            }
  317. +        }
  318. +        else {
  319. +            find_path = av[i];
  320. +        }
  321. +    }
  322. +
  323. +    if (print_usage) {
  324. +        (void)fwprintf(stderr,
  325. +            L"Usage: winstreamutil find [/s} [path]\n"
  326. +            L"\t/s\tRecurse into subdirs.\n"
  327. +            L"\t/l\tPrint details.\n"
  328. +            L"\tpath\tPath to search.");
  329. +        return EXIT_USAGE;
  330. +    }
  331. +
  332. +    if (find_path == NULL) {
  333. +        (void)fwprintf(stderr,
  334. +            L"%ls: No path given.\n", progname);
  335. +            return EXIT_FAILURE;
  336. +    }
  337. +
  338. +    lsstream_walk(progname, find_path, find_recursive, print_details);
  339. +    return EXIT_SUCCESS;
  340. +}
  341. +
  342. +typedef LONG NTSTATUS;
  343. +#ifndef NT_SUCCESS
  344. +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
  345. +#endif
  346. +
  347. +typedef struct _IO_STATUS_BLOCK {
  348. +    union { NTSTATUS Status; PVOID Pointer; } DUMMYUNIONNAME;
  349. +    ULONG_PTR Information;
  350. +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
  351. +
  352. +typedef enum _FILE_INFORMATION_CLASS {
  353. +    FileRenameInformation = 10
  354. +} FILE_INFORMATION_CLASS;
  355. +
  356. +typedef struct _FILE_RENAME_INFORMATION {
  357. +    BOOLEAN ReplaceIfExists;
  358. +    HANDLE  RootDirectory;
  359. +    ULONG   FileNameLength;
  360. +    WCHAR   FileName[1];
  361. +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;
  362. +
  363. +NTSTATUS NTAPI NtSetInformationFile(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS);
  364. +ULONG NTAPI RtlNtStatusToDosError(NTSTATUS);
  365. +
  366. +static
  367. +HANDLE OpenForRenameW(const wchar_t *restrict path)
  368. +{
  369. +    DWORD share  = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
  370. +    DWORD flags  = FILE_FLAG_BACKUP_SEMANTICS;
  371. +    return CreateFileW(path, DELETE | SYNCHRONIZE, share, NULL, OPEN_EXISTING, flags, NULL);
  372. +}
  373. +
  374. +static
  375. +int cmd_renamestream(int ac, wchar_t *av[])
  376. +{
  377. +    int res;
  378. +    bool print_usage = false;
  379. +    int i;
  380. +    const wchar_t *progname = av[0];
  381. +    wchar_t *base_path = NULL;
  382. +    wchar_t *src_streamname = NULL;
  383. +    wchar_t *dst_streamname = NULL;
  384. +
  385. +    for (i=2 ; i < ac ; i++) {
  386. +        if (av[i][0] == L'/') {
  387. +            if (wcscmp(av[i], L"/?") == 0)
  388. +                print_usage = true;
  389. +            else {
  390. +                (void)fwprintf(stderr, L"%ls: Unknown option '%ls'\n",
  391. +                    progname, av[i]);
  392. +                return EXIT_FAILURE;
  393. +            }
  394. +        }
  395. +        else {
  396. +            if (base_path == NULL)
  397. +                base_path = av[i];
  398. +            else if (src_streamname == NULL)
  399. +                src_streamname = av[i];
  400. +            else if (dst_streamname == NULL)
  401. +                dst_streamname = av[i];
  402. +            else {
  403. +                (void)fwprintf(stderr,
  404. +                    L"%ls: Too many filenames\n", progname);
  405. +                return EXIT_FAILURE;
  406. +            }
  407. +        }
  408. +    }
  409. +
  410. +    if ((base_path == NULL) && (src_streamname == NULL) && (dst_streamname == NULL))
  411. +        print_usage = true;
  412. +
  413. +    if (print_usage) {
  414. +        (void)fwprintf(stderr,
  415. +            L"Usage: winstreamutil renamestream path srcstreamname dststreamname\n"
  416. +            L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n"
  417. +            L"\tsrcstreamname\tsrc stream name (e.g. \":mystr1:$DATA\")\n"
  418. +            L"\tdststreamname\tdst stream name (e.g. \":mystr2:$DATA\")\n");
  419. +        return EXIT_USAGE;
  420. +    }
  421. +
  422. +    if ((base_path == NULL) || (src_streamname == NULL) || (dst_streamname == NULL)) {
  423. +        (void)fwprintf(stderr,
  424. +            L"%ls: Missing paths/stream.\n", progname);
  425. +            return EXIT_FAILURE;
  426. +    }
  427. +
  428. +    if ((src_streamname[0] != L':') || (dst_streamname[0] != L':')) {
  429. +        (void)fwprintf(stderr,
  430. +            L"%ls: Stream names must start with ':'\n", progname);
  431. +            return EXIT_FAILURE;
  432. +    }
  433. +
  434. +    PFILE_RENAME_INFORMATION fri = calloc(1,
  435. +        sizeof(FILE_RENAME_INFORMATION)+256*sizeof(wchar_t));
  436. +    if (fri == NULL) {
  437. +        (void)fwprintf(stderr,
  438. +            L"%ls: Out of memory for fri.\n", progname);
  439. +            return EXIT_FAILURE;
  440. +    }
  441. +
  442. +    wchar_t src_stream_path[NT_MAX_LONG_PATH];
  443. +    (void)swprintf(src_stream_path, NT_MAX_LONG_PATH,
  444. +        L"%ls%ls", base_path, src_streamname);
  445. +
  446. +    HANDLE bh = OpenForRenameW(src_stream_path);
  447. +    if (bh == INVALID_HANDLE_VALUE) {
  448. +        (void)fwprintf(stderr,
  449. +            L"%ls: Cannot open src stream '%ls', lasterr=%d\n",
  450. +            progname,
  451. +            src_stream_path, (int)GetLastError());
  452. +        free(fri);
  453. +        return EXIT_FAILURE;
  454. +    }
  455. +
  456. +    fri->ReplaceIfExists = FALSE;
  457. +    fri->RootDirectory   = NULL;
  458. +    fri->FileNameLength  = wcslen(dst_streamname)*sizeof(wchar_t);
  459. +    (void)wcscpy(fri->FileName, dst_streamname);
  460. +
  461. +    IO_STATUS_BLOCK iosb = { 0 };
  462. +    NTSTATUS status = NtSetInformationFile(bh, &iosb,
  463. +        fri,
  464. +        (sizeof(FILE_RENAME_INFORMATION)+fri->FileNameLength),
  465. +        FileRenameInformation);
  466. +
  467. +    bool ok = (bool)NT_SUCCESS(status);
  468. +    if (ok) {
  469. +        (void)fwprintf(stdout, L"Renamed stream '%ls%ls' to '%ls%ls'.\n",
  470. +            base_path, src_streamname,
  471. +            base_path, dst_streamname);
  472. +        res = EXIT_SUCCESS;
  473. +    }
  474. +    else {
  475. +        (void)fwprintf(stderr,
  476. +            L"%ls: Renaming stream '%ls%ls' to '%ls%ls' failed with lasterr=%d\n",
  477. +            progname,
  478. +            base_path, src_streamname,
  479. +            base_path, dst_streamname,
  480. +            (int)RtlNtStatusToDosError(status));
  481. +        res = EXIT_FAILURE;
  482. +    }
  483. +
  484. +    (void)CloseHandle(bh);
  485. +    free(fri);
  486. +
  487. +    return res;
  488. +}
  489. +
  490. +static
  491. +int cmd_deletestream(int ac, wchar_t *av[])
  492. +{
  493. +    int res;
  494. +    bool print_usage = false;
  495. +    int i;
  496. +    const wchar_t *progname = av[0];
  497. +    wchar_t *base_path = NULL;
  498. +    wchar_t *streamname = NULL;
  499. +
  500. +    for (i=2 ; i < ac ; i++) {
  501. +        if (av[i][0] == L'/') {
  502. +            if (wcscmp(av[i], L"/?") == 0)
  503. +                print_usage = true;
  504. +            else {
  505. +                (void)fwprintf(stderr,
  506. +                    L"%ls: Unknown option '%ls'\n",
  507. +                    progname, av[i]);
  508. +                return EXIT_FAILURE;
  509. +            }
  510. +        }
  511. +        else {
  512. +            if (base_path == NULL)
  513. +                base_path = av[i];
  514. +            else if (streamname == NULL)
  515. +                streamname = av[i];
  516. +            else {
  517. +                (void)fwprintf(stderr,
  518. +                    L"%ls: Too many filenames\n",
  519. +                    progname);
  520. +                return EXIT_FAILURE;
  521. +            }
  522. +        }
  523. +    }
  524. +
  525. +    if ((base_path == NULL) && (streamname == NULL))
  526. +        print_usage = true;
  527. +
  528. +    if (print_usage) {
  529. +        (void)fwprintf(stderr,
  530. +            L"Usage: winstreamutil deletestream path streamname\n"
  531. +            L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n"
  532. +            L"\tstreamname\tdst stream name (e.g. \":mystr2:$DATA\")\n");
  533. +        return EXIT_USAGE;
  534. +    }
  535. +
  536. +    if ((base_path == NULL) || (streamname == NULL)) {
  537. +        (void)fwprintf(stderr,
  538. +            L"%ls: Missing paths/stream.\n", progname);
  539. +            return EXIT_FAILURE;
  540. +    }
  541. +
  542. +    if (streamname[0] != L':') {
  543. +        (void)fwprintf(stderr,
  544. +            L"%ls: Stream names must start with ':'\n", progname);
  545. +            return EXIT_FAILURE;
  546. +    }
  547. +
  548. +    wchar_t stream_path[NT_MAX_LONG_PATH];
  549. +    (void)swprintf(stream_path, NT_MAX_LONG_PATH,
  550. +        L"%ls%ls", base_path, streamname);
  551. +
  552. +    bool ok = (bool)DeleteFileW(stream_path);
  553. +    if (ok) {
  554. +        (void)fwprintf(stdout, L"Deleted stream '%ls%ls'.\n",
  555. +            base_path, streamname);
  556. +        res = EXIT_SUCCESS;
  557. +    }
  558. +    else {
  559. +        (void)fwprintf(stderr,
  560. +            L"%ls: Delete failed with lasterr=%d\n",
  561. +            progname, (int)GetLastError());
  562. +        res = EXIT_FAILURE;
  563. +    }
  564. +
  565. +    return res;
  566. +}
  567. +
  568. +static
  569. +int cmd_info(int ac, wchar_t *av[])
  570. +{
  571. +    int res = EXIT_FAILURE;
  572. +    bool ok;
  573. +    PFILE_STREAM_INFO fsi = NULL;
  574. +
  575. +    bool print_usage = false;
  576. +    int i;
  577. +    const wchar_t *progname = av[0];
  578. +    const wchar_t *filename = NULL;
  579. +
  580. +    for (i=2 ; i < ac ; i++) {
  581. +        if (av[i][0] == L'/') {
  582. +            if (wcscmp(av[i], L"/?") == 0)
  583. +                print_usage = true;
  584. +            else {
  585. +                (void)fwprintf(stderr,
  586. +                    L"%ls: Unknown option '%ls'\n",
  587. +                    progname, av[i]);
  588. +                return EXIT_FAILURE;
  589. +            }
  590. +        }
  591. +        else {
  592. +            if (filename == NULL)
  593. +                filename = av[i];
  594. +            else {
  595. +                (void)fwprintf(stderr,
  596. +                    L"%ls: Too many filenames\n", progname);
  597. +                return EXIT_FAILURE;
  598. +            }
  599. +        }
  600. +    }
  601. +
  602. +    if (filename == NULL)
  603. +        print_usage = true;
  604. +
  605. +    if (print_usage) {
  606. +        (void)fwprintf(stderr,
  607. +            L"Usage: winstreamutil info path\n"
  608. +            L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n");
  609. +        return EXIT_USAGE;
  610. +    }
  611. +
  612. +    HANDLE fileHandle = CreateFileW(filename,
  613. +        GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
  614. +        FILE_FLAG_BACKUP_SEMANTICS, NULL);
  615. +    if (fileHandle == INVALID_HANDLE_VALUE) {
  616. +        (void)fwprintf(stderr,
  617. +            L"%ls: Error opening file '%ls', lasterr=%d.\n",
  618. +            progname,
  619. +            filename,
  620. +            (int)GetLastError());
  621. +        return EXIT_FAILURE;
  622. +    }
  623. +
  624. +#define MAX_STREAM_INFOS (16)
  625. +#define FSI_MAXCHARS (256)
  626. +    size_t fsi_size = (sizeof(FILE_STREAM_INFO)+sizeof(wchar_t)*FSI_MAXCHARS)*MAX_STREAM_INFOS;
  627. +    fsi = calloc(1, fsi_size);
  628. +    if (fsi == NULL) {
  629. +         (void)fwprintf(stderr,
  630. +            L"%ls: Out of memory.\n",
  631. +            progname);
  632. +        return EXIT_FAILURE;
  633. +    }
  634. +
  635. +    ok = GetFileInformationByHandleEx(fileHandle,
  636. +        FileStreamInfo,
  637. +        fsi, fsi_size);
  638. +
  639. +    if (!ok) {
  640. +        (void)fwprintf(stderr,
  641. +            L"%ls: GetFileInformationByHandleEx() error, lasterr=%d.\n",
  642. +            progname,
  643. +            (int)GetLastError());
  644. +        res = EXIT_FAILURE;
  645. +        goto done;
  646. +    }
  647. +
  648. +    int streamindex;
  649. +    const FILE_STREAM_INFO *stream;
  650. +
  651. +    /*
  652. +     * Output data as ksh93 compound variable (CPV), ksh93 can read+print
  653. +     * this format with $ typeset -C var ; read -C var ; print -v var #
  654. +     */
  655. +    (void)wprintf(L"(\n");
  656. +    (void)wprintf(L"\tfilename='%ls'\n", filename);
  657. +    (void)wprintf(L"\ttypeset -a streams=(\n");
  658. +
  659. +    for (stream = fsi, streamindex = 0 ; ; streamindex++) {
  660. +        (void)wprintf(L"\t\t[%d]=(\n", streamindex);
  661. +        (void)wprintf(L"\t\t\tStreamName='%.*ls'\n",
  662. +            (int)(stream->StreamNameLength/sizeof(WCHAR)),
  663. +            stream->StreamName);
  664. +        (void)wprintf(L"\t\t\tStreamSize=%lld\n",
  665. +            (long long)stream->StreamSize.QuadPart);
  666. +        (void)wprintf(L"\t\t\tStreamAllocationSize=%lld\n",
  667. +            (long long)stream->StreamAllocationSize.QuadPart);
  668. +        (void)wprintf(L"\t\t)\n");
  669. +
  670. +        if (stream->NextEntryOffset == 0)
  671. +            break;
  672. +
  673. +        stream = (const FILE_STREAM_INFO *)(((char *)stream) + stream->NextEntryOffset);
  674. +    }
  675. +    (void)wprintf(L"\t)\n");
  676. +    (void)wprintf(L")\n");
  677. +
  678. +    res = EXIT_SUCCESS;
  679. +
  680. +done:
  681. +    free(fsi);
  682. +    (void)CloseHandle(fileHandle);
  683. +    return res;
  684. +}
  685. +
  686. +static
  687. +void usage(const wchar_t *restrict progname)
  688. +{
  689. +    (void)fwprintf(stderr,
  690. +        L"%ls: Win32 named streams utility\n"
  691. +        L"(written by Roland Mainz <roland.mainz@nrubsig.org> "
  692. +        L"for the ms-nfs41-client project)\n\n"
  693. +        L"Available commands:\n"
  694. +        L"info\tprint info about a stream as ksh93 compound variable\n"
  695. +        L"find\tfind all non-default named streams in path\n"
  696. +        L"renamestream\trename stream\n"
  697. +        L"deletestream\tdelete stream\n",
  698. +        progname);
  699. +}
  700. +
  701. +int wmain(int ac, wchar_t *av[])
  702. +{
  703. +    if (ac < 2) {
  704. +        (void)usage(av[0]);
  705. +        return EXIT_USAGE;
  706. +    }
  707. +
  708. +    /*
  709. +     * FIXME: ToDO: Add more sub commands:
  710. +     * createnew, cat
  711. +     */
  712. +
  713. +    if (wcscmp(av[1], L"info") == 0) {
  714. +        return cmd_info(ac, av);
  715. +    }
  716. +    else if (wcscmp(av[1], L"find") == 0) {
  717. +        return cmd_find(ac, av);
  718. +    }
  719. +    else if (wcscmp(av[1], L"renamestream") == 0) {
  720. +        return cmd_renamestream(ac, av);
  721. +    }
  722. +    else if (wcscmp(av[1], L"deletestream") == 0) {
  723. +        return cmd_deletestream(ac, av);
  724. +    }
  725. +    else {
  726. +        (void)fwprintf(stderr,
  727. +            L"%ls: Unknown subcmd '%ls':\n",
  728. +            av[0], av[1]);
  729. +    }
  730. +
  731. +    return EXIT_SUCCESS;
  732. +}
  733. --
  734. 2.51.0
  735.  
  736. From 7693cb1084bbc3f657dfc1f24f63b2376c4cd022 Mon Sep 17 00:00:00 2001
  737. From: Roland Mainz <roland.mainz@nrubsig.org>
  738. Date: Thu, 22 Jan 2026 15:28:29 +0100
  739. Subject: [PATCH 2/2] daemon: Return |ERROR_NOT_SUPPORTED| for set/get Windows
  740.  EA and get |FileStreamsInformation| with streamname (not filename)
  741.  
  742. Setting/getting Windows EA (extended attributes) and getting
  743. |FileStreamsInformation| with streamname (not filename) is currently
  744. not implemented - make sure we return |ERROR_NOT_SUPPORTED| for now.
  745.  
  746. Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
  747. ---
  748. daemon/ea.c         | 40 ++++++++++++++++++++++++++++++++++++++++
  749.  daemon/winstreams.c |  9 ++++++---
  750.  2 files changed, 46 insertions(+), 3 deletions(-)
  751.  
  752. diff --git a/daemon/ea.c b/daemon/ea.c
  753. index 7c7b42e..dfff3f3 100644
  754. --- a/daemon/ea.c
  755. +++ b/daemon/ea.c
  756. @@ -25,12 +25,16 @@
  757.  #include <stdio.h>
  758.  #include <strsafe.h>
  759.  
  760. +#include "nfs41_build_features.h"
  761.  #include "from_kernel.h"
  762.  #include "nfs41_ops.h"
  763.  #include "delegation.h"
  764.  #include "upcall.h"
  765.  #include "daemon_debug.h"
  766.  #include "nfs_ea.h"
  767. +#ifdef NFS41_WINSTREAMS_SUPPORT
  768. +#include "winstreams.h"
  769. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  770.  
  771.  /*
  772.   * Compile safeguard to see whether |NFS4_EASIZE+header| will still fit into
  773. @@ -226,6 +230,24 @@ static int handle_setexattr(void *daemon_context, nfs41_upcall *upcall)
  774.          goto out;
  775.      }
  776.  
  777. +#ifdef NFS41_WINSTREAMS_SUPPORT
  778. +    /*
  779. +     * FIXME: Setting EA with stream name is not supported
  780. +     * (yet), the expectation is that doing this for stream "abc:str1" will
  781. +     * set the EA for "abc"
  782. +     *
  783. +     * FIXME: What about setting mode etc. with Cygwin/SFU EAs ?
  784. +     * This should effect the stream itself, right ?
  785. +     */
  786. +    if (is_stream_path_fh(&state->file)) {
  787. +        DPRINTF(0,
  788. +            ("handle_setexattr(name='%.*s'): "
  789. +            "Setting EA with stream name not implemented yet\n",
  790. +            (int)state->file.name.len, state->file.name.name));
  791. +        return ERROR_NOT_SUPPORTED;
  792. +    }
  793. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  794. +
  795.      /* break read delegations before SETATTR */
  796.      nfs41_delegation_return(state->session, &state->file,
  797.          OPEN_DELEGATE_READ, FALSE);
  798. @@ -583,6 +605,24 @@ static int handle_getexattr(void *daemon_context, nfs41_upcall *upcall)
  799.      uint32_t remaining, needed, index = 0;
  800.      int status;
  801.  
  802. +#ifdef NFS41_WINSTREAMS_SUPPORT
  803. +    /*
  804. +     * FIXME: Getting EA with stream name is not supported
  805. +     * (yet), the expectation is that doing this for stream "abc:str1" will
  806. +     * return EAs for "abc"
  807. +     *
  808. +     * FIXME: What about getting mode etc. with Cygwin/SFU EAs ?
  809. +     * This should effect the stream itself, right ?
  810. +     */
  811. +    if (is_stream_path_fh(&state->file)) {
  812. +        DPRINTF(0,
  813. +            ("handle_getexattr(name='%.*s'): "
  814. +            "Getting EA with stream name not implemented yet\n",
  815. +            (int)state->file.name.len, state->file.name.name));
  816. +        return ERROR_NOT_SUPPORTED;
  817. +    }
  818. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  819. +
  820.      status = nfs41_rpc_openattr(state->session, &state->file, FALSE, &parent.fh);
  821.      if (status == NFS4ERR_NOENT) { /* no named attribute directory */
  822.          DPRINTF(EALVL, ("no named attribute directory for '%s'\n", args->path));
  823. diff --git a/daemon/winstreams.c b/daemon/winstreams.c
  824. index 5f3e5af..e7dac64 100644
  825. --- a/daemon/winstreams.c
  826. +++ b/daemon/winstreams.c
  827. @@ -471,18 +471,21 @@ int get_streaminformation(
  828.      }
  829.  
  830.      /*
  831. -     * FIXME: |FileStreamInformation| for |NF4NAMEDATTR| is not supported
  832. +     * FIXME: |FileStreamInformation| for streams is not supported
  833.       * (yet), the expectation is that doing this for stream "abc:str1" will
  834.       * return all streams for "abc"
  835.       */
  836. -    if (basefile_info->type == NF4NAMEDATTR) {
  837. +    if (is_stream_path_fh(&state->file)) {
  838.          DPRINTF(0,
  839.              ("get_streaminformation(name='%.*s'): "
  840. -            "stream info for NF4NAMEDATTR not implemented yet\n",
  841. +            "stream info with stream name not implemented yet\n",
  842.              (int)state->file.name.len, state->file.name.name));
  843.          return ERROR_NOT_SUPPORTED;
  844.      }
  845.  
  846. +    EASSERT(basefile_info->type != NF4ATTRDIR);
  847. +    EASSERT(basefile_info->type != NF4NAMEDATTR);
  848. +
  849.      status = nfs41_rpc_openattr(state->session, &state->file, FALSE,
  850.          &parent.fh);
  851.  
  852. --
  853. 2.51.0

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.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at