pastebin - collaborative debugging tool
nrubsig.kpaste.net RSS


winstreamsutil.c - Win32 named streams utility
Posted by Anonymous on Wed 21st Jan 2026 23:38
raw | new post

  1. /*
  2.  * MIT License
  3.  *
  4.  * Copyright (c) 2004-2026 Roland Mainz <roland.mainz@nrubsig.org>
  5.  *
  6.  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7.  * of this software and associated documentation files (the "Software"), to deal
  8.  * in the Software without restriction, including without limitation the rights
  9.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10.  * copies of the Software, and to permit persons to whom the Software is
  11.  * furnished to do so, subject to the following conditions:
  12.  *
  13.  * The above copyright notice and this permission notice shall be included in all
  14.  * copies or substantial portions of the Software.
  15.  *
  16.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22.  * SOFTWARE.
  23.  */
  24.  
  25. /*
  26.  * winstreamsutil.c - Win32 named streams utility
  27.  *
  28.  * Written by Roland Mainz <roland.mainz@nrubsig.org>
  29.  */
  30.  
  31. /*
  32.  * Compile with
  33.  * $ clang -target x86_64-pc-windows-gnu -std=gnu17 -Wall -Wextra \
  34.  * -municode -g winstreamsutil.c \
  35.  * -lntdll -o winstreamsutil.exe
  36.  */
  37.  
  38. #define UNICODE 1
  39. #define _UNICODE 1
  40.  
  41. #include <windows.h>
  42. #include <stdlib.h>
  43. #include <stdbool.h>
  44. #include <stdio.h>
  45. #include <wchar.h>
  46.  
  47. #define EXIT_USAGE 2 /* Traditional UNIX exit for fore |usage()| */
  48.  
  49. #define NT_MAX_LONG_PATH 4096/*32767*/
  50.  
  51. static
  52. int lsstream_list_streams(const wchar_t *restrict path,
  53.     bool skip_default_stream,
  54.     bool print_details)
  55. {
  56.     WIN32_FIND_STREAM_DATA sd;
  57.     ZeroMemory(&sd, sizeof(sd));
  58.  
  59.     HANDLE h = FindFirstStreamW(path, FindStreamInfoStandard, &sd, 0);
  60.     if (h == INVALID_HANDLE_VALUE) {
  61.         DWORD e = GetLastError();
  62.         if (e == ERROR_HANDLE_EOF)
  63.             return EXIT_SUCCESS;
  64.         (void)fwprintf(stderr, L"FindFirstStreamW(path='%ls'), lasterr=%d\n",
  65.             path, (int)e);
  66.         return 3;
  67.     }
  68.  
  69.     for (;;) {
  70.         if (skip_default_stream) {
  71.             if (wcscmp(sd.cStreamName, L"::$DATA") == 0)
  72.                 goto nextstr;
  73.         }
  74.  
  75.         if (print_details) {
  76.             (void)wprintf(L"filename='%ls%ls' size=%lld\n",
  77.                 path,
  78.                 sd.cStreamName,
  79.                 (long long)sd.StreamSize.QuadPart);
  80.         }
  81.         else {
  82.             (void)wprintf(L"%ls%ls\n", path, sd.cStreamName);
  83.         }
  84.  
  85. nextstr:
  86.         if (!FindNextStreamW(h, &sd)) {
  87.             DWORD e = GetLastError();
  88.             if (e == ERROR_HANDLE_EOF)
  89.                 break;
  90.             (void)fwprintf(stderr, L"FindNextStreamW() returned lasterr=%d\n", (int)e);
  91.             FindClose(h);
  92.             return 4;
  93.         }
  94.     }
  95.  
  96.     FindClose(h);
  97.     return EXIT_SUCCESS;
  98. }
  99.  
  100. static
  101. int lsstream_walk(const wchar_t *restrict path,
  102.     bool find_recursive,
  103.     bool print_details)
  104. {
  105.     wchar_t pattern[NT_MAX_LONG_PATH];
  106.     (void)swprintf(pattern, NT_MAX_LONG_PATH, L"%ls\\*", path);
  107.  
  108.     WIN32_FIND_DATAW fd;
  109.     HANDLE h = FindFirstFileW(pattern, &fd);
  110.     if (h == INVALID_HANDLE_VALUE) {
  111.         (void)fwprintf(stderr,
  112.             L"FindFirstFileW(path=%ls) returned lasterr=%d\n",
  113.             path, (int)GetLastError());
  114.         return EXIT_FAILURE;
  115.     }
  116.  
  117.     do {
  118.         if (wcscmp(fd.cFileName, L".") == 0 ||
  119.             wcscmp(fd.cFileName, L"..") == 0)
  120.             continue;
  121.  
  122.         wchar_t full[NT_MAX_LONG_PATH];
  123.         (void)swprintf(full, NT_MAX_LONG_PATH, L"%ls\\%ls", path, fd.cFileName);
  124.  
  125.         lsstream_list_streams(full, true, print_details);
  126.  
  127.         if (find_recursive && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
  128.             lsstream_walk(full, find_recursive, print_details);
  129.         }
  130.     } while (FindNextFileW(h, &fd));
  131.  
  132.     DWORD e = GetLastError();
  133.     if (e != ERROR_NO_MORE_FILES) {
  134.         (void)fwprintf(stderr, L"FindNextFileW() returned lasterr=%d\n", (int)e);
  135.     }
  136.  
  137.     FindClose(h);
  138.     return EXIT_SUCCESS;
  139. }
  140.  
  141. static
  142. int cmd_find(int ac, wchar_t *av[])
  143. {
  144.     bool find_recursive = false;
  145.     bool print_details = false;
  146.     bool print_usage = false;
  147.     int i;
  148.     wchar_t *find_path = NULL;
  149.  
  150.     for (i=2 ; i < ac ; i++) {
  151.         if (av[i][0] == L'/') {
  152.             if (wcscmp(av[i], L"/?") == 0)
  153.                 print_usage = true;
  154.             else if (wcscmp(av[i], L"/s") == 0)
  155.                 find_recursive = true;
  156.             else if (wcscmp(av[i], L"/-s") == 0)
  157.                 find_recursive = false;
  158.             else if (wcscmp(av[i], L"/l") == 0)
  159.                 print_details = true;
  160.             else if (wcscmp(av[i], L"/-l") == 0)
  161.                 print_details = false;
  162.             else {
  163.                 (void)fwprintf(stderr, L"Unknown option '%ls'\n", av[i]);
  164.                 return EXIT_FAILURE;
  165.             }
  166.         }
  167.         else {
  168.             find_path = av[i];
  169.         }
  170.     }
  171.  
  172.     if (print_usage) {
  173.         (void)fwprintf(stderr,
  174.             L"Usage: winstreamutil find [/s} [path]\n"
  175.             L"\t/s\tRecurse into subdirs.\n"
  176.             L"\t/l\tPrint details.\n"
  177.             L"\tpath\tPath to search.");
  178.         return EXIT_USAGE;
  179.     }
  180.  
  181.     if (find_path == NULL) {
  182.         (void)fwprintf(stderr, L"No path given.\n");
  183.             return EXIT_FAILURE;
  184.     }
  185.  
  186.     lsstream_walk(find_path, find_recursive, print_details);
  187.     return EXIT_SUCCESS;
  188. }
  189.  
  190. typedef LONG NTSTATUS;
  191. #ifndef NT_SUCCESS
  192. #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
  193. #endif
  194.  
  195. typedef struct _IO_STATUS_BLOCK {
  196.     union { NTSTATUS Status; PVOID Pointer; } DUMMYUNIONNAME;
  197.     ULONG_PTR Information;
  198. } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
  199.  
  200. typedef enum _FILE_INFORMATION_CLASS {
  201.     FileRenameInformation = 10
  202. } FILE_INFORMATION_CLASS;
  203.  
  204. typedef struct _FILE_RENAME_INFORMATION {
  205.     BOOLEAN ReplaceIfExists;
  206.     HANDLE  RootDirectory;
  207.     ULONG   FileNameLength;
  208.     WCHAR   FileName[1];
  209. } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;
  210.  
  211. NTSTATUS NTAPI NtSetInformationFile(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS);
  212. ULONG NTAPI RtlNtStatusToDosError(NTSTATUS);
  213.  
  214. static
  215. HANDLE OpenForRenameW(const wchar_t *restrict path)
  216. {
  217.     DWORD share  = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
  218.     DWORD flags  = FILE_FLAG_BACKUP_SEMANTICS;
  219.     return CreateFileW(path, DELETE | SYNCHRONIZE, share, NULL, OPEN_EXISTING, flags, NULL);
  220. }
  221.  
  222. static
  223. int cmd_renamestream(int ac, wchar_t *av[])
  224. {
  225.     int res;
  226.     bool print_usage = false;
  227.     int i;
  228.     wchar_t *base_path = NULL;
  229.     wchar_t *src_streamname = NULL;
  230.     wchar_t *dst_streamname = NULL;
  231.  
  232.     for (i=2 ; i < ac ; i++) {
  233.         if (av[i][0] == L'/') {
  234.             if (wcscmp(av[i], L"/?") == 0)
  235.                 print_usage = true;
  236.             else {
  237.                 (void)fwprintf(stderr, L"Unknown option '%ls'\n", av[i]);
  238.                 return EXIT_FAILURE;
  239.             }
  240.         }
  241.         else {
  242.             if (base_path == NULL)
  243.                 base_path = av[i];
  244.             else if (src_streamname == NULL)
  245.                 src_streamname = av[i];
  246.             else if (dst_streamname == NULL)
  247.                 dst_streamname = av[i];
  248.             else {
  249.                 (void)fwprintf(stderr, L"Too many filenames\n");
  250.                 return EXIT_FAILURE;
  251.             }
  252.         }
  253.     }
  254.  
  255.     if ((base_path == NULL) && (src_streamname == NULL) && (dst_streamname == NULL))
  256.         print_usage = true;
  257.  
  258.     if (print_usage) {
  259.         (void)fwprintf(stderr,
  260.             L"Usage: winstreamutil renamestream path srcstreamname dststreamname\n"
  261.             L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n"
  262.             L"\tsrcstreamname\tsrc stream name (e.g. \":mystr1:$DATA\")\n"
  263.             L"\tdststreamname\tdst stream name (e.g. \":mystr2:$DATA\")\n");
  264.         return EXIT_USAGE;
  265.     }
  266.  
  267.     if ((base_path == NULL) || (src_streamname == NULL) || (dst_streamname == NULL)) {
  268.         (void)fwprintf(stderr, L"Missing paths/stream.\n");
  269.             return EXIT_FAILURE;
  270.     }
  271.  
  272.     if ((src_streamname[0] != L':') || (dst_streamname[0] != L':')) {
  273.         (void)fwprintf(stderr, L"Stream names must start with ':'\n");
  274.             return EXIT_FAILURE;
  275.     }
  276.  
  277.     PFILE_RENAME_INFORMATION fri = calloc(1,
  278.         sizeof(FILE_RENAME_INFORMATION)+256*sizeof(wchar_t));
  279.     if (fri == NULL) {
  280.         (void)fwprintf(stderr, L"Out of memory for fri.\n");
  281.             return EXIT_FAILURE;
  282.     }
  283.  
  284.     wchar_t src_stream_path[NT_MAX_LONG_PATH];
  285.     (void)swprintf(src_stream_path, NT_MAX_LONG_PATH,
  286.         L"%ls%ls", base_path, src_streamname);
  287.  
  288.     HANDLE bh = OpenForRenameW(src_stream_path);
  289.     if (bh == INVALID_HANDLE_VALUE) {
  290.         (void)fwprintf(stderr, L"Cannot open src stream '%ls', lasterr=%d\n",
  291.             src_stream_path, (int)GetLastError());
  292.         free(fri);
  293.         return EXIT_FAILURE;
  294.     }
  295.  
  296.     fri->ReplaceIfExists = FALSE;
  297.     fri->RootDirectory   = NULL;
  298.     fri->FileNameLength  = wcslen(dst_streamname)*sizeof(wchar_t);
  299.     (void)wcscpy(fri->FileName, dst_streamname);
  300.  
  301.     IO_STATUS_BLOCK iosb = { 0 };
  302.     NTSTATUS status = NtSetInformationFile(bh, &iosb,
  303.         fri,
  304.         (sizeof(FILE_RENAME_INFORMATION)+fri->FileNameLength),
  305.         FileRenameInformation);
  306.  
  307.     bool ok = (bool)NT_SUCCESS(status);
  308.     if (ok) {
  309.         (void)fwprintf(stderr, L"Renamed stream '%ls%ls' to '%ls%ls'.\n",
  310.             base_path, src_streamname,
  311.             base_path, dst_streamname);
  312.         res = EXIT_SUCCESS;
  313.     }
  314.     else {
  315.         (void)fwprintf(stderr,
  316.             L"Renaming stream '%ls%ls' to '%ls%ls' failed with lasterr=%d\n",
  317.             base_path, src_streamname,
  318.             base_path, dst_streamname,
  319.             (int)RtlNtStatusToDosError(status));
  320.         res = EXIT_FAILURE;
  321.     }
  322.  
  323.     (void)CloseHandle(bh);
  324.     free(fri);
  325.  
  326.     return res;
  327. }
  328.  
  329. static
  330. int cmd_deletestream(int ac, wchar_t *av[])
  331. {
  332.     int res;
  333.     bool print_usage = false;
  334.     int i;
  335.     wchar_t *base_path = NULL;
  336.     wchar_t *streamname = NULL;
  337.  
  338.     for (i=2 ; i < ac ; i++) {
  339.         if (av[i][0] == L'/') {
  340.             if (wcscmp(av[i], L"/?") == 0)
  341.                 print_usage = true;
  342.             else {
  343.                 (void)fwprintf(stderr, L"Unknown option '%ls'\n", av[i]);
  344.                 return EXIT_FAILURE;
  345.             }
  346.         }
  347.         else {
  348.             if (base_path == NULL)
  349.                 base_path = av[i];
  350.             else if (streamname == NULL)
  351.                 streamname = av[i];
  352.             else {
  353.                 (void)fwprintf(stderr, L"Too many filenames\n");
  354.                 return EXIT_FAILURE;
  355.             }
  356.         }
  357.     }
  358.  
  359.     if ((base_path == NULL) && (streamname == NULL))
  360.         print_usage = true;
  361.  
  362.     if (print_usage) {
  363.         (void)fwprintf(stderr,
  364.             L"Usage: winstreamutil deletestream path streamname\n"
  365.             L"\tpath\tPath of base file/dir (e.g. C:\\foo.txt)\n"
  366.             L"\tstreamname\tdst stream name (e.g. \":mystr2:$DATA\")\n");
  367.         return EXIT_USAGE;
  368.     }
  369.  
  370.     if ((base_path == NULL) || (streamname == NULL)) {
  371.         (void)fwprintf(stderr, L"Missing paths/stream.\n");
  372.             return EXIT_FAILURE;
  373.     }
  374.  
  375.     if (streamname[0] != L':') {
  376.         (void)fwprintf(stderr, L"Stream names must start with ':'\n");
  377.             return EXIT_FAILURE;
  378.     }
  379.  
  380.     wchar_t stream_path[NT_MAX_LONG_PATH];
  381.     (void)swprintf(stream_path, NT_MAX_LONG_PATH,
  382.         L"%ls%ls", base_path, streamname);
  383.  
  384.     bool ok = (bool)DeleteFileW(stream_path);
  385.     if (ok) {
  386.         (void)fwprintf(stderr, L"Deleted stream '%ls%ls'.\n",
  387.             base_path, streamname);
  388.         res = EXIT_SUCCESS;
  389.     }
  390.     else {
  391.         (void)fwprintf(stderr,
  392.             L"Delete failed with lasterr=%d\n",
  393.             (int)GetLastError());
  394.         res = EXIT_FAILURE;
  395.     }
  396.  
  397.     return res;
  398. }
  399.  
  400. static
  401. void usage(const wchar_t *restrict progname)
  402. {
  403.     (void)fwprintf(stderr,
  404.         L"%ls: Win32 named streams utility\n"
  405.         L"(written by Roland Mainz <roland.mainz@nrubsig.org>)\n\n"
  406.         L"Available commands:\n"
  407.         L"find\tfind all non-default named streams in path\n"
  408.         L"renamestream\trename stream\n"
  409.         L"deletestream\tdelete stream\n",
  410.         progname);
  411. }
  412.  
  413. int wmain(int argc, wchar_t *argv[])
  414. {
  415.     if (argc < 2) {
  416.         (void)usage(argv[0]);
  417.         return EXIT_USAGE;
  418.     }
  419.  
  420.     /*
  421.      * FIXME: ToDO: Add more sub commands:
  422.      * createnew, queryallocatedranges
  423.      */
  424.  
  425.     if (wcscmp(argv[1], L"find") == 0) {
  426.         return cmd_find(argc, argv);
  427.     }
  428.     else if (wcscmp(argv[1], L"renamestream") == 0) {
  429.         return cmd_renamestream(argc, argv);
  430.     }
  431.     else if (wcscmp(argv[1], L"deletestream") == 0) {
  432.         return cmd_deletestream(argc, argv);
  433.     }
  434.     else {
  435.         (void)fwprintf(stderr,
  436.             L"%ls: Unknown subcmd '%ls':\n",
  437.             argv[0], argv[1]);
  438.     }
  439.  
  440.     return EXIT_SUCCESS;
  441. }

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