pastebin - collaborative debugging tool
nrubsig.kpaste.net RSS


Win32 streams support
Posted by Anonymous on Fri 2nd Jan 2026 18:22
raw | new post
modification of post by Anonymous (view diff)

  1. diff --git a/build.vc19/nfsd/nfsd.vcxproj b/build.vc19/nfsd/nfsd.vcxproj
  2. index 8589a2a..face630 100644
  3. --- a/build.vc19/nfsd/nfsd.vcxproj
  4. +++ b/build.vc19/nfsd/nfsd.vcxproj
  5. @@ -331,6 +331,7 @@
  6.      <ClCompile Include="..\..\daemon\upcall.c" />
  7.      <ClCompile Include="..\..\daemon\util.c" />
  8.      <ClCompile Include="..\..\daemon\volume.c" />
  9. +    <ClCompile Include="..\..\daemon\winstreams.c" />
  10.    </ItemGroup>
  11.    <ItemGroup>
  12.      <ClInclude Include="..\..\daemon\accesstoken.h" />
  13. @@ -356,6 +357,7 @@
  14.      <ClInclude Include="..\..\daemon\tree.h" />
  15.      <ClInclude Include="..\..\daemon\upcall.h" />
  16.      <ClInclude Include="..\..\daemon\util.h" />
  17. +    <ClInclude Include="..\..\daemon\winstreams.h" />
  18.      <ClInclude Include="..\..\include\from_kernel.h" />
  19.      <ClInclude Include="..\..\include\nfs_ea.h" />
  20.    </ItemGroup>
  21. diff --git a/build.vc19/nfsd/nfsd.vcxproj.filters b/build.vc19/nfsd/nfsd.vcxproj.filters
  22. index 134404f..8e7c552 100644
  23. --- a/build.vc19/nfsd/nfsd.vcxproj.filters
  24. +++ b/build.vc19/nfsd/nfsd.vcxproj.filters
  25. @@ -147,6 +147,9 @@
  26.      <ClCompile Include="..\..\daemon\volume.c">
  27.        <Filter>Source Files</Filter>
  28.      </ClCompile>
  29. +    <ClCompile Include="..\..\daemon\winstreams.c">
  30. +      <Filter>Source Files</Filter>
  31. +    </ClCompile>
  32.      <ClCompile Include="..\..\daemon\accesstoken.c">
  33.        <Filter>Source Files</Filter>
  34.      </ClCompile>
  35. @@ -227,5 +230,8 @@
  36.      <ClInclude Include="..\..\daemon\accesstoken.h">
  37.        <Filter>Header Files</Filter>
  38.      </ClInclude>
  39. +    <ClInclude Include="..\..\daemon\winstreams.h">
  40. +      <Filter>Header Files</Filter>
  41. +    </ClInclude>
  42.    </ItemGroup>
  43.  </Project>
  44. \ No newline at end of file
  45. diff --git a/daemon/fileinfoutil.c b/daemon/fileinfoutil.c
  46. index 0ed40c8..d745fd2 100644
  47. --- a/daemon/fileinfoutil.c
  48. +++ b/daemon/fileinfoutil.c
  49. @@ -1,6 +1,6 @@
  50.  /* NFSv4.1 client for Windows
  51.   * Copyright (C) 2012 The Regents of the University of Michigan
  52. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  53. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  54.   *
  55.   * Olga Kornievskaia <aglo@umich.edu>
  56.   * Casey Bodley <cbodley@umich.edu>
  57. @@ -59,7 +59,7 @@ ULONG nfs_file_info_to_attributes(
  58.          if (info->symlink_dir)
  59.              attrs |= FILE_ATTRIBUTE_DIRECTORY;
  60.      }
  61. -    else if (info->type == NF4REG) {
  62. +    else if ((info->type == NF4REG) || (info->type == NF4NAMEDATTR)) {
  63.          if (superblock->sparse_file_support) {
  64.              /* FIXME: What about pNFS ? */
  65.              attrs |= FILE_ATTRIBUTE_SPARSE_FILE;
  66. @@ -383,6 +383,7 @@ void nfs_to_stat_lx_info(
  67.      stat_lx_out->LxMode = 0UL;
  68.      switch(info->type) {
  69.          case NF4REG:
  70. +        case NF4NAMEDATTR:
  71.              stat_lx_out->LxMode |= LX_MODE_S_IFREG;
  72.              break;
  73.          case NF4DIR:
  74. diff --git a/daemon/getattr.c b/daemon/getattr.c
  75. index 4744285..63b985e 100644
  76. --- a/daemon/getattr.c
  77. +++ b/daemon/getattr.c
  78. @@ -1,6 +1,6 @@
  79.  /* NFSv4.1 client for Windows
  80.   * Copyright (C) 2012 The Regents of the University of Michigan
  81. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  82. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  83.   *
  84.   * Olga Kornievskaia <aglo@umich.edu>
  85.   * Casey Bodley <cbodley@umich.edu>
  86. @@ -32,6 +32,9 @@
  87.  #include "upcall.h"
  88.  #include "fileinfoutil.h"
  89.  #include "daemon_debug.h"
  90. +#ifdef NFS41_WINSTREAMS_SUPPORT
  91. +#include "winstreams.h"
  92. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  93.  
  94.  
  95.  int nfs41_cached_getattr(
  96. @@ -204,6 +207,31 @@ static int handle_getattr(void *daemon_context, nfs41_upcall *upcall)
  97.              &args->stat_lx_info);
  98.          break;
  99.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  100. +#ifdef NFS41_WINSTREAMS_SUPPORT
  101. +    case FileStreamInformation:
  102. +        args->stream_info_list = NULL;
  103. +
  104. +        nfs41_path_fh parent = { 0 };
  105. +
  106. +        status = nfs41_rpc_openattr(state->session, &state->file, FALSE, &parent.fh);
  107. +        /* No named attribute directory ? */
  108. +        if (status == NFS4ERR_NOENT) {
  109. +            DPRINTF(0, ("FileStreamInformation: no named attribute directory for '%s'\n", state->file.name.name));
  110. +            /* FIXME: We should return a default "file::$DATA" entry */
  111. +        } else if (status) {
  112. +            eprintf("FileStreamInformation: "
  113. +                "nfs41_rpc_openattr() failed with '%s'\n",
  114. +                nfs_error_string(status));
  115. +            status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
  116. +            goto out;
  117. +        }
  118. +
  119. +        status = get_stream_list(state,
  120. +            &parent,
  121. +            &args->stream_info_list,
  122. +            &args->stream_info_list_size);
  123. +        break;
  124. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  125.      default:
  126.          eprintf("handle_getattr(state->path.path='%s'): "
  127.              "unhandled file query class %d\n",
  128. @@ -222,7 +250,7 @@ static int marshall_getattr(
  129.      nfs41_upcall *restrict upcall)
  130.  {
  131.      int status;
  132. -    const getattr_upcall_args *args = &upcall->args.getattr;
  133. +    getattr_upcall_args *args = &upcall->args.getattr;
  134.      uint32_t info_len;
  135.  
  136.      switch (args->query_class) {
  137. @@ -292,6 +320,15 @@ static int marshall_getattr(
  138.          if (status) goto out;
  139.          break;
  140.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  141. +#ifdef NFS41_WINSTREAMS_SUPPORT
  142. +    case FileStreamInformation:
  143. +        info_len = args->stream_info_list_size;
  144. +        status = safe_write(&buffer, length, &info_len, sizeof(info_len));
  145. +        if (status) goto out;
  146. +        status = safe_write(&buffer, length, args->stream_info_list, info_len);
  147. +        if (status) goto out;
  148. +        break;
  149. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  150.      default:
  151.          eprintf("marshall_getattr: unknown file query class %d\n",
  152.              args->query_class);
  153. @@ -302,6 +339,13 @@ static int marshall_getattr(
  154.      if (status) goto out;
  155.      DPRINTF(1, ("NFS41_SYSOP_FILE_QUERY: downcall changattr=%llu\n", args->ctime));
  156.  out:
  157. +#ifdef NFS41_WINSTREAMS_SUPPORT
  158. +    if (args->query_class == FileStreamInformation) {
  159. +        free(args->stream_info_list);
  160. +        args->stream_info_list = NULL;
  161. +    }
  162. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  163. +
  164.      return status;
  165.  }
  166.  
  167. diff --git a/daemon/lookup.c b/daemon/lookup.c
  168. index 3e9e591..39074e2 100644
  169. --- a/daemon/lookup.c
  170. +++ b/daemon/lookup.c
  171. @@ -1,6 +1,6 @@
  172.  /* NFSv4.1 client for Windows
  173.   * Copyright (C) 2012 The Regents of the University of Michigan
  174. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  175. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  176.   *
  177.   * Olga Kornievskaia <aglo@umich.edu>
  178.   * Casey Bodley <cbodley@umich.edu>
  179. @@ -25,12 +25,16 @@
  180.  #include <strsafe.h>
  181.  #include <time.h>
  182.  
  183. +#include "nfs41_build_features.h"
  184.  #include "nfs41_compound.h"
  185.  #include "nfs41_ops.h"
  186.  #include "name_cache.h"
  187.  #include "fileinfoutil.h"
  188.  #include "util.h"
  189.  #include "daemon_debug.h"
  190. +#ifdef NFS41_WINSTREAMS_SUPPORT
  191. +#include "winstreams.h"
  192. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  193.  
  194.  
  195.  #define LULVL 2 /* dprintf level for lookup logging */
  196. @@ -502,6 +506,156 @@ int nfs41_lookup(
  197.  
  198.      DPRINTF(LULVL, ("--> nfs41_lookup('%s')\n", path.path));
  199.  
  200. +#ifdef NFS41_WINSTREAMS_SUPPORT
  201. +    /* Fast check for potential stream: Look for a colon */
  202. +    if (is_stream_path(path_inout)) {
  203. +        char base_name[NFS41_MAX_PATH_LEN+1];
  204. +        char stream_name[NFS41_MAX_COMPONENT_LEN+1];
  205. +        bool is_stream = false;
  206. +
  207. +        /* Parse the stream syntax */
  208. +        status = parse_win32stream_name(path.path, &is_stream, base_name, stream_name);
  209. +        if (status)
  210. +            goto out;
  211. +
  212. +        if (is_stream) {
  213. +            if (stream_name[0] == '\0') {
  214. +                /* "foo::$DATA" case -> treat as "foo" */
  215. +                DPRINTF(2, ("nfs41_lookup: 'foo::$DATA' case -> treat as 'foo'\n"));
  216. +                size_t base_len = strlen(base_name);
  217. +
  218. +                (void)memcpy(path.path, base_name, base_len);
  219. +                path.path[base_len] = '\0';
  220. +                path.len = (unsigned short)base_len;
  221. +                path_pos = path.path;
  222. +                path_end = path.path + path.len;
  223. +
  224. +                /* Continue to normal lookup with stripped name */
  225. +            } else {
  226. +                /* "foo:bar" case */
  227. +                DPRINTF(2, ("nfs41_lookup: base_name='%s', stream_name='%s'\n",
  228. +                    base_name, stream_name));
  229. +
  230. +                /*
  231. +                 * Recursively lookup the base file ...
  232. +                 */
  233. +                nfs41_abs_path base_path = path;
  234. +                size_t base_len = strlen(base_name);
  235. +
  236. +                (void)memcpy(base_path.path, base_name, base_len);
  237. +                base_path.path[base_len] = '\0';
  238. +                base_path.len = (unsigned short)base_len;
  239. +
  240. +                nfs41_path_fh attr_target;
  241. +                if (target_out == NULL) {
  242. +                    target_out = &attr_target;
  243. +                }
  244. +
  245. +                status = nfs41_lookup(root, session, casesensitive, &base_path,
  246. +                    parent_out, target_out, info_out, session_out);
  247. +                if (status)
  248. +                    goto out;
  249. +
  250. +                /*
  251. +                 * ... and then lookup the NFSv4 named attribute
  252. +                 */
  253. +                nfs41_session *lookup_session;
  254. +                nfs41_compound compound;
  255. +                nfs_argop4 argops[8];
  256. +                nfs_resop4 resops[8];
  257. +                nfs41_sequence_args sequence_args;
  258. +                nfs41_sequence_res sequence_res;
  259. +                nfs41_openattr_args openattr_args;
  260. +                nfs41_openattr_res openattr_res;
  261. +                nfs41_putfh_args putfh_args = { 0 };
  262. +                nfs41_putfh_res putfh_res;
  263. +                nfs41_lookup_args lookup_args = { 0 };
  264. +                nfs41_lookup_res lookup_res;
  265. +                nfs41_getfh_res getfh_res;
  266. +                nfs41_getattr_args getattr_args = { 0 };
  267. +                nfs41_getattr_res getattr_res = { 0 };
  268. +                nfs41_component stream_comp = {
  269. +                    .name = stream_name,
  270. +                    .len = (unsigned short)strlen(stream_name)
  271. +                };
  272. +                /* Minimal attributes for stream */
  273. +                bitmap4 attr_request = {
  274. +                    .count = 2,
  275. +                    .arr = {
  276. +                        [0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE |
  277. +                            FATTR4_WORD0_SIZE | FATTR4_WORD0_FSID |
  278. +                            FATTR4_WORD0_FILEID,
  279. +                        [1] = FATTR4_WORD1_MODE | FATTR4_WORD1_SPACE_USED |
  280. +                            FATTR4_WORD1_TIME_ACCESS |
  281. +                            FATTR4_WORD1_TIME_CREATE |
  282. +                            FATTR4_WORD1_TIME_MODIFY |
  283. +                            FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP,
  284. +                        [2] = 0
  285. +                    }
  286. +                };
  287. +
  288. +                DPRINTF(1,
  289. +                    ("nfs41_lookup: "
  290. +                    "Looking up base_name='%s'/stream_name='%s'\n",
  291. +                    base_name, stream_name));
  292. +
  293. +                /* Use the session returned by the base lookup if applicable */
  294. +                if ((session_out != NULL) && (*session_out != NULL))
  295. +                    lookup_session = *session_out;
  296. +                else
  297. +                    lookup_session = session;
  298. +
  299. +                compound_init(&compound,
  300. +                    lookup_session->client->root->nfsminorvers,
  301. +                    argops, resops, "lookup_win32stream");
  302. +
  303. +                compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
  304. +                nfs41_session_sequence(&sequence_args, lookup_session, 0);
  305. +
  306. +                compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
  307. +                putfh_args.file = target_out;
  308. +                putfh_args.in_recovery = FALSE;
  309. +
  310. +                compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  311. +                openattr_args.createdir = FALSE;
  312. +
  313. +                compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  314. +                lookup_args.name = &stream_comp;
  315. +
  316. +                compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
  317. +                getfh_res.fh = &target_out->fh;
  318. +
  319. +                compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
  320. +                getattr_args.attr_request = &attr_request;
  321. +                /* If caller wanted info, fill it; else dummy */
  322. +                nfs41_file_info dummy_info;
  323. +                if (info_out)
  324. +                    getattr_res.info = info_out;
  325. +                else
  326. +                    getattr_res.info = &dummy_info;
  327. +                getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT_ATTR;
  328. +
  329. +                status = compound_encode_send_decode(lookup_session, &compound, TRUE);
  330. +
  331. +                if (status == 0)
  332. +                    status = compound.res.status;
  333. +
  334. +                if (status == 0) {
  335. +                    /* We MUST return a |NF4NAMEDATTR|, and never a |NF4ATTRDIR| */
  336. +                    EASSERT(getattr_res.info->type != NF4ATTRDIR);
  337. +                    EASSERT(getattr_res.info->type == NF4NAMEDATTR);
  338. +                }
  339. +                else {
  340. +                    DPRINTF(1, ("nfs41_lookup: failed for attr, status=%d\n",
  341. +                        (int)status));
  342. +                }
  343. +
  344. +                goto out;
  345. +            }
  346. +        }
  347. +    }
  348. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  349. +
  350.      if (parent_out == NULL) parent_out = &parent;
  351.      if (target_out == NULL) target_out = &target;
  352.      parent_out->fh.len = target_out->fh.len = 0;
  353. diff --git a/daemon/nfs41_ops.c b/daemon/nfs41_ops.c
  354. index 9012db4..6a1a395 100644
  355. --- a/daemon/nfs41_ops.c
  356. +++ b/daemon/nfs41_ops.c
  357. @@ -1,6 +1,6 @@
  358.  /* NFSv4.1 client for Windows
  359.   * Copyright (C) 2012 The Regents of the University of Michigan
  360. - * Copyright (C) 2024-2025 Roland Mainz <roland.mainz@nrubsig.org>
  361. + * Copyright (C) 2024-2026 Roland Mainz <roland.mainz@nrubsig.org>
  362.   *
  363.   * Olga Kornievskaia <aglo@umich.edu>
  364.   * Casey Bodley <cbodley@umich.edu>
  365. @@ -36,6 +36,9 @@
  366.  #include "delegation.h"
  367.  #include "daemon_debug.h"
  368.  #include "util.h"
  369. +#ifdef NFS41_WINSTREAMS_SUPPORT
  370. +#include "winstreams.h"
  371. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  372.  
  373.  #ifdef NFS41_DRIVER_STABILITY_HACKS
  374.  /*
  375. @@ -453,8 +456,8 @@ int nfs41_open(
  376.  {
  377.      int status;
  378.      nfs41_compound compound;
  379. -    nfs_argop4 argops[8];
  380. -    nfs_resop4 resops[8];
  381. +    nfs_argop4 argops[12];
  382. +    nfs_resop4 resops[12];
  383.      nfs41_sequence_args sequence_args;
  384.      nfs41_sequence_res sequence_res;
  385.      nfs41_putfh_args putfh_args[2];
  386. @@ -471,10 +474,39 @@ int nfs41_open(
  387.      bool_t current_fh_is_dir;
  388.      bool_t already_delegated = delegation->type == OPEN_DELEGATE_READ
  389.          || delegation->type == OPEN_DELEGATE_WRITE;
  390. +#ifdef NFS41_WINSTREAMS_SUPPORT
  391. +    char base_buf[NFS41_MAX_PATH_LEN+1];
  392. +    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
  393. +    bool is_stream = false;
  394. +    nfs41_component base_comp = {0};
  395. +    nfs41_component stream_comp = {0};
  396. +    nfs41_lookup_args lookup_args;
  397. +    nfs41_lookup_res lookup_res;
  398. +    nfs41_openattr_args openattr_args;
  399. +    nfs41_openattr_res openattr_res;
  400. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  401.  
  402.      EASSERT_IS_VALID_NON_NULL_PTR(parent);
  403.      EASSERT_IS_VALID_NON_NULL_PTR(parent->fh.superblock);
  404.  
  405. +#ifdef NFS41_WINSTREAMS_SUPPORT
  406. +    if (file->name.name) {
  407. +        if (is_stream_path_fh(file)) {
  408. +            status = parse_win32stream_name(file->name.name,
  409. +                &is_stream, base_buf, stream_buf);
  410. +            if (status)
  411. +                goto out;
  412. +
  413. +            if (is_stream) {
  414. +                base_comp.name = base_buf;
  415. +                base_comp.len = (unsigned short)strlen(base_buf);
  416. +                stream_comp.name = stream_buf;
  417. +                stream_comp.len = (unsigned short)strlen(stream_buf);
  418. +            }
  419. +        }
  420. +    }
  421. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  422. +
  423.      /* depending on the claim type, OPEN expects CURRENT_FH set
  424.       * to either the parent directory, or to the file itself */
  425.      switch (claim->claim) {
  426. @@ -524,6 +556,16 @@ int nfs41_open(
  427.          putfh_args[0].in_recovery = 0;
  428.      }
  429.  
  430. +#ifdef NFS41_WINSTREAMS_SUPPORT
  431. +    if (is_stream && (stream_comp.len > 0)) {
  432. +        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  433. +        lookup_args.name = &base_comp;
  434. +        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  435. +        /* If we are creating the stream, we should allow creating the attr dir */
  436. +        openattr_args.createdir = (create == OPEN4_CREATE) ? TRUE : FALSE;
  437. +    }
  438. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  439. +
  440.      compound_add_op(&compound, OP_OPEN, &open_args, &open_res);
  441.      open_args.seqid = 0;
  442.  #ifdef DISABLE_FILE_DELEGATIONS
  443. @@ -549,6 +591,27 @@ int nfs41_open(
  444.              parent->fh.superblock, &createattrs->attrmask);
  445.      }
  446.      open_args.claim = claim;
  447. +
  448. +#ifdef NFS41_WINSTREAMS_SUPPORT
  449. +    if (is_stream &&
  450. +        ((claim->claim == CLAIM_NULL) ||
  451. +            (claim->claim == CLAIM_DELEGATE_CUR))) {
  452. +        if (claim->claim == CLAIM_NULL) {
  453. +            if (stream_comp.len > 0)
  454. +                claim->u.null.filename = &stream_comp;
  455. +            else
  456. +                claim->u.null.filename = &base_comp;
  457. +        }
  458. +        else if (claim->claim == CLAIM_DELEGATE_CUR) {
  459. +            claim->u.deleg_cur.name = &stream_comp;
  460. +            if (stream_comp.len > 0)
  461. +                claim->u.deleg_cur.name = &stream_comp;
  462. +            else
  463. +                claim->u.deleg_cur.name = &base_comp;
  464. +        }
  465. +    }
  466. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  467. +
  468.      open_res.resok4.stateid = stateid;
  469.      open_res.resok4.delegation = delegation;
  470.  
  471. @@ -582,6 +645,7 @@ int nfs41_open(
  472.      if (compound_error(status = compound.res.status))
  473.          goto out;
  474.  
  475. +    /* This can happen if |nfs41_open()| is called by the EA code */
  476.      if (dir_info.type == NF4ATTRDIR) {
  477.          file->fh.superblock = parent->fh.superblock;
  478.          goto out;
  479. @@ -614,8 +678,8 @@ int nfs41_create(
  480.  {
  481.      int status;
  482.      nfs41_compound compound;
  483. -    nfs_argop4 argops[8];
  484. -    nfs_resop4 resops[8];
  485. +    nfs_argop4 argops[12];
  486. +    nfs_resop4 resops[12];
  487.      nfs41_sequence_args sequence_args;
  488.      nfs41_sequence_res sequence_res;
  489.      nfs41_putfh_args putfh_args;
  490. @@ -629,6 +693,35 @@ int nfs41_create(
  491.      nfs41_file_info dir_info;
  492.      nfs41_savefh_res savefh_res;
  493.      nfs41_restorefh_res restorefh_res;
  494. +#ifdef NFS41_WINSTREAMS_SUPPORT
  495. +    char base_buf[NFS41_MAX_PATH_LEN+1];
  496. +    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
  497. +    bool is_stream = false;
  498. +    nfs41_component base_comp = {0};
  499. +    nfs41_component stream_comp = {0};
  500. +    nfs41_lookup_args lookup_args;
  501. +    nfs41_lookup_res lookup_res;
  502. +    nfs41_openattr_args openattr_args;
  503. +    nfs41_openattr_res openattr_res;
  504. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  505. +
  506. +#ifdef NFS41_WINSTREAMS_SUPPORT
  507. +    if (file->name.name) {
  508. +        if (is_stream_path_fh(file)) {
  509. +            status = parse_win32stream_name(file->name.name,
  510. +                &is_stream, base_buf, stream_buf);
  511. +            if (status)
  512. +                goto out;
  513. +
  514. +            if (is_stream) {
  515. +                base_comp.name = base_buf;
  516. +                base_comp.len = (unsigned short)strlen(base_buf);
  517. +                stream_comp.name = stream_buf;
  518. +                stream_comp.len = (unsigned short)strlen(stream_buf);
  519. +            }
  520. +        }
  521. +    }
  522. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  523.  
  524.      nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
  525.  
  526. @@ -644,13 +737,34 @@ int nfs41_create(
  527.  
  528.      compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
  529.  
  530. +#ifdef NFS41_WINSTREAMS_SUPPORT
  531. +    if (is_stream && (stream_comp.len > 0)) {
  532. +        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  533. +        lookup_args.name = &base_comp;
  534. +        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  535. +        openattr_args.createdir = TRUE;
  536. +    }
  537. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  538. +
  539.      compound_add_op(&compound, OP_CREATE, &create_args, &create_res);
  540.      create_args.objtype.type = type;
  541.      if (type == NF4LNK) {
  542.          create_args.objtype.u.lnk.linkdata = symlink;
  543.          create_args.objtype.u.lnk.linkdata_len = (uint32_t)strlen(symlink);
  544.      }
  545. -    create_args.name = &file->name;
  546. +
  547. +#ifdef NFS41_WINSTREAMS_SUPPORT
  548. +    if (is_stream) {
  549. +        if (stream_comp.len > 0)
  550. +            create_args.name = &stream_comp;
  551. +        else
  552. +            create_args.name = &base_comp;
  553. +    }
  554. +    else
  555. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  556. +    {
  557. +        create_args.name = &file->name;
  558. +    }
  559.      create_args.createattrs = createattrs;
  560.      nfs41_superblock_supported_attrs(
  561.                  parent->fh.superblock, &createattrs->attrmask);
  562. @@ -1283,8 +1397,8 @@ int nfs41_remove(
  563.  {
  564.      int status;
  565.      nfs41_compound compound;
  566. -    nfs_argop4 argops[4];
  567. -    nfs_resop4 resops[4];
  568. +    nfs_argop4 argops[8];
  569. +    nfs_resop4 resops[8];
  570.      nfs41_sequence_args sequence_args;
  571.      nfs41_sequence_res sequence_res;
  572.      nfs41_putfh_args putfh_args;
  573. @@ -1295,6 +1409,33 @@ int nfs41_remove(
  574.      nfs41_getattr_res getattr_res NDSH(= { 0 });
  575.      bitmap4 attr_request;
  576.      nfs41_file_info info;
  577. +#ifdef NFS41_WINSTREAMS_SUPPORT
  578. +    char base_buf[NFS41_MAX_PATH_LEN+1];
  579. +    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
  580. +    bool is_stream = false;
  581. +    nfs41_component base_comp = {0};
  582. +    nfs41_component stream_comp = {0};
  583. +    nfs41_lookup_args lookup_args;
  584. +    nfs41_lookup_res lookup_res;
  585. +    nfs41_openattr_args openattr_args;
  586. +    nfs41_openattr_res openattr_res;
  587. +
  588. +    if (target->name) {
  589. +        if (is_stream_component(target)) {
  590. +            status = parse_win32stream_name(target->name,
  591. +                &is_stream, base_buf, stream_buf);
  592. +            if (status)
  593. +                goto out;
  594. +
  595. +            if (is_stream) {
  596. +                base_comp.name = base_buf;
  597. +                base_comp.len = (unsigned short)strlen(base_buf);
  598. +                stream_comp.name = stream_buf;
  599. +                stream_comp.len = (unsigned short)strlen(stream_buf);
  600. +            }
  601. +        }
  602. +    }
  603. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  604.  
  605.      nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
  606.  
  607. @@ -1308,8 +1449,28 @@ int nfs41_remove(
  608.      putfh_args.file = parent;
  609.      putfh_args.in_recovery = 0;
  610.  
  611. +#ifdef NFS41_WINSTREAMS_SUPPORT
  612. +    if (is_stream && (stream_comp.len > 0)) {
  613. +        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  614. +        lookup_args.name = &base_comp;
  615. +        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  616. +        openattr_args.createdir = FALSE;
  617. +    }
  618. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  619. +
  620.      compound_add_op(&compound, OP_REMOVE, &remove_args, &remove_res);
  621. -    remove_args.target = target;
  622. +#ifdef NFS41_WINSTREAMS_SUPPORT
  623. +    if (is_stream) {
  624. +        if (stream_comp.len > 0)
  625. +            remove_args.target = &stream_comp;
  626. +        else
  627. +            remove_args.target = &base_comp;
  628. +    }
  629. +    else
  630. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  631. +    {
  632. +        remove_args.target = target;
  633. +    }
  634.  
  635.      compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
  636.      getattr_args.attr_request = &attr_request;
  637. @@ -1369,6 +1530,17 @@ int nfs41_rename(
  638.      bitmap4 attr_request;
  639.      nfs41_restorefh_res restorefh_res;
  640.  
  641. +#ifdef NFS41_WINSTREAMS_SUPPORT
  642. +    if (src_name->name) {
  643. +        EASSERT_MSG(is_stream_component(src_name) == false,
  644. +            ("nfs41_rename: Streams not implemented yet"));
  645. +    }
  646. +    if (dst_name->name) {
  647. +        EASSERT_MSG(is_stream_component(dst_name) == false,
  648. +            ("nfs41_rename: Streams not implemented yet"));
  649. +    }
  650. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  651. +
  652.      nfs41_superblock_getattr_mask(src_dir->fh.superblock, &attr_request);
  653.  
  654.      compound_init(&compound, session->client->root->nfsminorvers,
  655. @@ -1555,6 +1727,21 @@ int nfs41_link(
  656.      nfs41_file_info info = { 0 };
  657.      nfs41_path_fh file;
  658.  
  659. +#ifdef NFS41_WINSTREAMS_SUPPORT
  660. +    if (src->name.name) {
  661. +        EASSERT_MSG(is_stream_path_fh(src) == false,
  662. +            ("nfs41_link: Streams not implemented yet"));
  663. +    }
  664. +    if (dst_dir->name.name) {
  665. +        EASSERT_MSG(is_stream_path_fh(dst_dir) == false,
  666. +            ("nfs41_link: Streams not implemented yet"));
  667. +    }
  668. +    if (target->name) {
  669. +        EASSERT_MSG(is_stream_component(target) == false,
  670. +            ("nfs41_link: Streams not implemented yet"));
  671. +    }
  672. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  673. +
  674.  #ifdef NFS41_DRIVER_STABILITY_HACKS
  675.      /* gisburn: fixme, see comment about |NDSH| above */
  676.      (void)memset(getattr_res, 0, sizeof(getattr_res));
  677. @@ -1655,6 +1842,13 @@ int nfs41_readlink(
  678.      nfs41_putfh_res putfh_res;
  679.      nfs41_readlink_res readlink_res;
  680.  
  681. +#ifdef NFS41_WINSTREAMS_SUPPORT
  682. +    if (file->name.name) {
  683. +        EASSERT_MSG(is_stream_path_fh(file) == false,
  684. +            ("nfs41_readlink: Streams not implemented yet"));
  685. +    }
  686. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  687. +
  688.      compound_init(&compound, session->client->root->nfsminorvers,
  689.          argops, resops, "readlink");
  690.  
  691. @@ -1700,6 +1894,13 @@ int nfs41_access(
  692.      nfs41_access_args access_args;
  693.      nfs41_access_res access_res;
  694.  
  695. +#ifdef NFS41_WINSTREAMS_SUPPORT
  696. +    if (file->name.name) {
  697. +        EASSERT_MSG(is_stream_path_fh(file) == false,
  698. +            ("nfs41_access: Streams not implemented yet"));
  699. +    }
  700. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  701. +
  702.      compound_init(&compound, session->client->root->nfsminorvers,
  703.          argops, resops, "access");
  704.  
  705. @@ -1937,6 +2138,13 @@ int nfs41_secinfo(
  706.      nfs41_secinfo_args secinfo_args;
  707.      nfs41_secinfo_no_name_res secinfo_res;
  708.  
  709. +#ifdef NFS41_WINSTREAMS_SUPPORT
  710. +    if (name->name) {
  711. +        EASSERT_MSG(is_stream_component(name) == false,
  712. +            ("nfs41_secinfo: Streams not implemented yet"));
  713. +    }
  714. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  715. +
  716.      compound_init(&compound, session->client->root->nfsminorvers,
  717.          argops, resops, "secinfo");
  718.  
  719. diff --git a/daemon/nfs41_superblock.c b/daemon/nfs41_superblock.c
  720. index 5ffb171..1a767d3 100644
  721. --- a/daemon/nfs41_superblock.c
  722. +++ b/daemon/nfs41_superblock.c
  723. @@ -1,6 +1,6 @@
  724.  /* NFSv4.1 client for Windows
  725.   * Copyright (C) 2012 The Regents of the University of Michigan
  726. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  727. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  728.   *
  729.   * Olga Kornievskaia <aglo@umich.edu>
  730.   * Casey Bodley <cbodley@umich.edu>
  731. @@ -303,8 +303,12 @@ void nfs41_superblock_fs_attributes(
  732.          FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_HARD_LINKS;
  733.      if (superblock->symlink_support)
  734.          FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_REPARSE_POINTS;
  735. -    if (superblock->ea_support)
  736. +    if (superblock->ea_support) {
  737.          FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES;
  738. +#ifdef NFS41_WINSTREAMS_SUPPORT
  739. +        FsAttrs->FileSystemAttributes |= FILE_NAMED_STREAMS;
  740. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  741. +    }
  742.      if (superblock->case_preserving)
  743.          FsAttrs->FileSystemAttributes |= FILE_CASE_PRESERVED_NAMES;
  744.      if (!superblock->case_insensitive)
  745. diff --git a/daemon/open.c b/daemon/open.c
  746. index b5e72c7..d9163bc 100644
  747. --- a/daemon/open.c
  748. +++ b/daemon/open.c
  749. @@ -1,6 +1,6 @@
  750.  /* NFSv4.1 client for Windows
  751.   * Copyright (C) 2012 The Regents of the University of Michigan
  752. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  753. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  754.   *
  755.   * Olga Kornievskaia <aglo@umich.edu>
  756.   * Casey Bodley <cbodley@umich.edu>
  757. @@ -37,6 +37,7 @@
  758.  #include "fileinfoutil.h"
  759.  #include "util.h"
  760.  #include "idmap.h"
  761. +#include "winstreams.h"
  762.  
  763.  static int create_open_state(
  764.      IN const char *path,
  765. @@ -880,8 +881,12 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
  766.      // first check if windows told us it's a directory
  767.      if (args->create_opts & FILE_DIRECTORY_FILE)
  768.          state->type = NF4DIR;
  769. -    else
  770. -        state->type = NF4REG;
  771. +    else {
  772. +        if (strstr(args->path, ":"))
  773. +            state->type = NF4NAMEDATTR;
  774. +        else
  775. +            state->type = NF4REG;
  776. +    }
  777.  
  778.  #ifdef NFS41_DRIVER_ALLOW_CREATEFILE_ACLS
  779.      if (args->sec_desc_len) {
  780. @@ -1007,20 +1012,26 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
  781.                      goto out_free_state;
  782.                  }
  783.              }
  784. -        } else if (info.type == NF4REG) {
  785. +        } else if ((info.type == NF4REG) || (info.type == NF4NAMEDATTR)) {
  786.              DPRINTF(2, ("handle nfs41_open: FILE\n"));
  787.              if (args->create_opts & FILE_DIRECTORY_FILE) {
  788. -                DPRINTF(1, ("trying to open file '%s' as a directory\n",
  789. -                    state->path.path));
  790. +                DPRINTF(1,
  791. +                    ("trying to open file state->path.path='%s',"
  792. +                    "info.type=%d as a directory\n",
  793. +                    state->path.path, (int)info.type));
  794.                  /*
  795.                   * Notes:
  796.                   * - SMB returns |STATUS_OBJECT_TYPE_MISMATCH|
  797.                   * while NTFS returns |STATUS_NOT_A_DIRECTORY|
  798.                   * - See |map_open_errors()| for the mapping to
  799.                   * |STATUS_*|
  800. +                 * - We do not return |ERROR_DIRECTORY| when opening a Win32
  801. +                 * stream
  802.                   */
  803. -                status = ERROR_DIRECTORY;
  804. -                goto out_free_state;
  805. +                if (is_stream_path(&state->path) == false) {
  806. +                    status = ERROR_DIRECTORY;
  807. +                    goto out_free_state;
  808. +                }
  809.              }
  810.          } else if (info.type == NF4LNK) {
  811.              DPRINTF(2, ("handle nfs41_open: SYMLINK\n"));
  812. @@ -1324,7 +1335,7 @@ supersede_retry:
  813.  
  814.  #ifdef DEBUG_OPEN_SPARSE_FILES
  815.      if ((status == 0) &&
  816. -        (info.type == NF4REG) &&
  817. +        (info.type == NF4REG | info.type == NF4NAMEDATTR) &&
  818.          (state->session->client->root->supports_nfs42_seek)) {
  819.          //debug_list_sparsefile_holes(state);
  820.          debug_list_sparsefile_datasections(state);
  821. @@ -1524,7 +1535,7 @@ static int handle_close(void *deamon_context, nfs41_upcall *upcall)
  822.      nfs41_open_state *state = upcall->state_ref;
  823.  
  824.      /* return associated file layouts if necessary */
  825. -    if (state->type == NF4REG)
  826. +    if (state->type == NF4REG || state->type == NF4NAMEDATTR)
  827.          pnfs_layout_state_close(state->session, state, args->remove);
  828.  
  829.      if (state->srv_open == args->srv_open)
  830. diff --git a/daemon/upcall.h b/daemon/upcall.h
  831. index 1563c25..01a84ed 100644
  832. --- a/daemon/upcall.h
  833. +++ b/daemon/upcall.h
  834. @@ -1,6 +1,6 @@
  835.  /* NFSv4.1 client for Windows
  836.   * Copyright (C) 2012 The Regents of the University of Michigan
  837. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  838. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  839.   *
  840.   * Olga Kornievskaia <aglo@umich.edu>
  841.   * Casey Bodley <cbodley@umich.edu>
  842. @@ -122,6 +122,10 @@ typedef struct __getattr_upcall_args {
  843.      FILE_STAT_INFORMATION stat_info;
  844.      FILE_STAT_LX_INFORMATION stat_lx_info;
  845.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  846. +#ifdef NFS41_WINSTREAMS_SUPPORT
  847. +    PFILE_STREAM_INFORMATION stream_info_list;
  848. +    ULONG stream_info_list_size;
  849. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  850.      int query_class;
  851.      int buf_len;
  852.      int query_reply_len;
  853. diff --git a/daemon/winstreams.c b/daemon/winstreams.c
  854. new file mode 100644
  855. index 0000000..be0af14
  856. --- /dev/null
  857. +++ b/daemon/winstreams.c
  858. @@ -0,0 +1,455 @@
  859. +/* NFSv4.1 client for Windows
  860. + * Copyright (C) 2025-2026 Roland Mainz <roland.mainz@nrubsig.org>
  861. + *
  862. + * Roland Mainz <roland.mainz@nrubsig.org>
  863. + *
  864. + * This library is free software; you can redistribute it and/or modify it
  865. + * under the terms of the GNU Lesser General Public License as published by
  866. + * the Free Software Foundation; either version 2.1 of the License, or (at
  867. + * your option) any later version.
  868. + *
  869. + * This library is distributed in the hope that it will be useful, but
  870. + * without any warranty; without even the implied warranty of merchantability
  871. + * or fitness for a particular purpose.  See the GNU Lesser General Public
  872. + * License for more details.
  873. + *
  874. + * You should have received a copy of the GNU Lesser General Public License
  875. + * along with this library; if not, write to the Free Software Foundation,
  876. + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  877. + */
  878. +
  879. +#include <stdlib.h>
  880. +#include <stdbool.h>
  881. +
  882. +#include "nfs41_build_features.h"
  883. +#include "winstreams.h"
  884. +#include "from_kernel.h"
  885. +#include "nfs41_ops.h"
  886. +#include "delegation.h"
  887. +#include "upcall.h"
  888. +#include "daemon_debug.h"
  889. +#include "util.h"
  890. +
  891. +#ifdef NFS41_WINSTREAMS_SUPPORT
  892. +/*
  893. + * |WIN_NFS4_STREAMS_NAME_PREFIX| - Prefix for Windows streams in NFSv4
  894. + * XATTR (extended attributes) namespace
  895. + *
  896. + * We need such a prefix to avoid colliding with other users
  897. + * in the NFSv4 XATTR namespace - for example SUN Microsystrems
  898. + * (Solaris, Illumos, ...) uses "SUNWattr_" as prefix, and setting
  899. + * such attributes can cause data corruption (or in case of
  900. + * "SUNWattr_ro" will fail, because the attribute file is
  901. + * read-only).
  902. + */
  903. +#define WIN_NFS4_STREAMS_NAME_PREFIX "win32.streams."
  904. +#define WIN_NFS4_STREAMS_NAME_PREFIX_LEN (14)
  905. +
  906. +static
  907. +int parse_stream_filename_streamname_streamtype(
  908. +    const char *restrict path,
  909. +    char *restrict filename,
  910. +    char *restrict streamname,
  911. +    char *restrict streamtype)
  912. +{
  913. +    const char *sep;
  914. +    const char *base;
  915. +    int colon_count;
  916. +    const char *c1;
  917. +    const char *c2;
  918. +    const char *p;
  919. +    size_t len_prefix;
  920. +    size_t len_sn;
  921. +
  922. +    filename[0]   = '\0';
  923. +    streamname[0] = '\0';
  924. +    streamtype[0] = '\0';
  925. +
  926. +    /* Take the last component after the last backslash */
  927. +    sep  = strrchr(path, '\\');
  928. +    base = sep ? (sep + 1) : path;
  929. +
  930. +    /* Count colons and error out if >= 3 in the final component */
  931. +    colon_count = 0;
  932. +    for (p = base ; *p ; p++) {
  933. +        if (*p == ':') {
  934. +            colon_count++;
  935. +            if (colon_count >= 3)
  936. +                return ERROR_INVALID_NAME;
  937. +        }
  938. +    }
  939. +
  940. +    /* Find first colon (if any) */
  941. +    c1 = strchr(base, ':');
  942. +    if (c1 == NULL) {
  943. +        /* No colon: filename is the entire path */
  944. +        (void)strcpy(filename, path);
  945. +        return NO_ERROR;
  946. +    }
  947. +
  948. +    /* First colon exists: filename before ':' must be non-empty */
  949. +    if (c1 == base)
  950. +        return ERROR_INVALID_NAME;
  951. +
  952. +    /*
  953. +     * One colon case
  954. +     */
  955. +    if (colon_count == 1) {
  956. +        if (*(c1 + 1) == '\0')
  957. +            return ERROR_INVALID_NAME; /* "filename:" => invalid */
  958. +
  959. +        /* filename = full path up to first colon in base */
  960. +        len_prefix = (size_t)(c1 - path);
  961. +        (void)memcpy(filename, path, len_prefix);
  962. +        filename[len_prefix] = '\0';
  963. +
  964. +        /* streamname after first colon */
  965. +        (void)strcpy(streamname, c1 + 1);
  966. +        /* streamtype stays empty */
  967. +        return NO_ERROR;
  968. +    }
  969. +
  970. +    /*
  971. +     * Two colons case
  972. +     */
  973. +    c2 = strchr(c1 + 1, ':');
  974. +    if (c2 == NULL) {
  975. +        /* Should not happen when colon_count == 2 */
  976. +        return ERROR_INVALID_NAME;
  977. +    }
  978. +
  979. +    if (*(c2 + 1) == '\0')
  980. +        return ERROR_INVALID_NAME; /* type must be non-empty */
  981. +
  982. +    /* filename = full path up to first colon */
  983. +    len_prefix = (size_t)(c1 - path);
  984. +    (void)memcpy(filename, path, len_prefix);
  985. +    filename[len_prefix] = '\0';
  986. +
  987. +    /* streamname = [c1+1, c2] (may be empty, e.g., filename::$DATA) */
  988. +    len_sn = (size_t)(c2 - (c1 + 1));
  989. +    (void)memcpy(streamname, c1 + 1, len_sn);
  990. +    streamname[len_sn] = '\0';
  991. +
  992. +    /* streamtype = (c2+1 .. end) */
  993. +    (void)strcpy(streamtype, c2 + 1);
  994. +
  995. +    return NO_ERROR;
  996. +}
  997. +
  998. +int parse_win32stream_name(
  999. +    IN const char *restrict path,
  1000. +    OUT bool *restrict is_stream,
  1001. +    OUT char *restrict base_name,
  1002. +    OUT char *restrict stream_name)
  1003. +{
  1004. +    int status;
  1005. +    char filenamebuff[NFS41_MAX_PATH_LEN+1];
  1006. +    char streamnamebuff[NFS41_MAX_COMPONENT_LEN+1];
  1007. +    /* |streamtypebuff| must include space for prefix+suffix */
  1008. +    char streamtypebuff[NFS41_MAX_COMPONENT_LEN+1+128];
  1009. +    char *p;
  1010. +
  1011. +    status = parse_stream_filename_streamname_streamtype(path,
  1012. +        filenamebuff, streamnamebuff, streamtypebuff);
  1013. +    if (status) {
  1014. +        eprintf("parse_win32stream_name: "
  1015. +            "parsing for path='%s' failed, status=%d\n",
  1016. +            path, status);
  1017. +        return status;
  1018. +    }
  1019. +
  1020. +    DPRINTF(2,
  1021. +        ("parse_win32stream_name: "
  1022. +        "parse_stream_filename_streamname_streamtype(path='%s') returned "
  1023. +        "filenamebuff='%s', streamnamebuff='%s', streamtypebuff='%s'\n",
  1024. +        path, filenamebuff, streamnamebuff, streamtypebuff));
  1025. +
  1026. +    if ((streamnamebuff[0] == '\0') && (streamtypebuff[0] == '\0')) {
  1027. +        return ERROR_INVALID_NAME;
  1028. +    }
  1029. +
  1030. +    /* We do not support any stream types except "$DATA" (yet) */
  1031. +    if ((streamtypebuff[0] != '\0') &&
  1032. +        ((_stricmp(streamtypebuff, "$DATA") != 0))) {
  1033. +        eprintf("parse_win32stream_name: "
  1034. +            "Unsupported stream type, path='%s', "
  1035. +            "stream='%s', streamtype='%s'\n",
  1036. +            path,
  1037. +            streamnamebuff,
  1038. +            streamtypebuff);
  1039. +        return ERROR_INVALID_NAME;
  1040. +    }
  1041. +
  1042. +    /* "foo::$DATA" refers to "foo" */
  1043. +    if (streamnamebuff[0] == '\0') {
  1044. +        *is_stream = true;
  1045. +        (void)strcpy(base_name, filenamebuff);
  1046. +        stream_name[0] = '\0';
  1047. +        return NO_ERROR;
  1048. +    }
  1049. +
  1050. +    /*
  1051. +     * If we have a stream name, then add our NFS attr prefix for Windows
  1052. +     * streams, and ":$DATA" as suffix
  1053. +     */
  1054. +    *is_stream = true;
  1055. +    (void)strcpy(base_name, filenamebuff);
  1056. +    p = stpcpy(stream_name, WIN_NFS4_STREAMS_NAME_PREFIX);
  1057. +    p = stpcpy(p, streamnamebuff);
  1058. +    (void)stpcpy(p, ":$DATA");
  1059. +
  1060. +    return NO_ERROR;
  1061. +}
  1062. +
  1063. +#define READDIR_LEN_INITIAL 8192
  1064. +#define READDIR_LEN_MIN 2048
  1065. +
  1066. +/* call readdir repeatedly to get a complete list of entries */
  1067. +static
  1068. +int read_entire_dir(
  1069. +    IN nfs41_session *session,
  1070. +    IN nfs41_path_fh *dir,
  1071. +    OUT unsigned char **restrict buffer_out,
  1072. +    OUT uint32_t *restrict length_out)
  1073. +{
  1074. +    nfs41_readdir_cookie cookie = { 0 };
  1075. +    nfs41_readdir_entry *last_entry;
  1076. +    unsigned char *buffer;
  1077. +    uint32_t buffer_len, len, total_len;
  1078. +    bool_t eof;
  1079. +    int status = NO_ERROR;
  1080. +    /* Attributes for stream */
  1081. +    bitmap4 attr_request = {
  1082. +        .count = 2,
  1083. +        .arr = {
  1084. +            [0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE |
  1085. +                FATTR4_WORD0_SIZE | FATTR4_WORD0_FSID |
  1086. +                FATTR4_WORD0_FILEID,
  1087. +            [1] = FATTR4_WORD1_SPACE_USED,
  1088. +            [2] = 0
  1089. +        }
  1090. +    };
  1091. +
  1092. +    /* allocate the buffer for readdir entries */
  1093. +    buffer_len = READDIR_LEN_INITIAL;
  1094. +    buffer = calloc(1, buffer_len);
  1095. +    if (buffer == NULL) {
  1096. +        status = GetLastError();
  1097. +        goto out;
  1098. +    }
  1099. +
  1100. +    last_entry = NULL;
  1101. +    total_len = 0;
  1102. +    eof = FALSE;
  1103. +
  1104. +    while (!eof) {
  1105. +        len = buffer_len - total_len;
  1106. +        if (len < READDIR_LEN_MIN) {
  1107. +            const ptrdiff_t diff = (unsigned char*)last_entry - buffer;
  1108. +            /* realloc the buffer to fit more entries */
  1109. +            unsigned char *tmp = realloc(buffer, (size_t)buffer_len * 2L);
  1110. +            if (tmp == NULL) {
  1111. +                status = GetLastError();
  1112. +                goto out_free;
  1113. +            }
  1114. +
  1115. +            if (last_entry) /* fix last_entry pointer */
  1116. +                last_entry = (nfs41_readdir_entry*)(tmp + diff);
  1117. +            buffer = tmp;
  1118. +            buffer_len *= 2;
  1119. +            len = buffer_len - total_len;
  1120. +        }
  1121. +
  1122. +        /* fetch the next group of entries */
  1123. +        status = nfs41_readdir(session, dir, &attr_request,
  1124. +            &cookie, buffer + total_len, &len, &eof);
  1125. +        if (status)
  1126. +            goto out_free;
  1127. +
  1128. +        if (last_entry == NULL) {
  1129. +            /* initialize last_entry to the front of the list */
  1130. +            last_entry = (nfs41_readdir_entry *)(buffer + total_len);
  1131. +        } else if (len) {
  1132. +            /* link the previous list to the new one */
  1133. +            last_entry->next_entry_offset = (uint32_t)FIELD_OFFSET(
  1134. +                nfs41_readdir_entry, name) + last_entry->name_len;
  1135. +        }
  1136. +
  1137. +        /* find the new last entry */
  1138. +        while (last_entry->next_entry_offset) {
  1139. +            last_entry = (nfs41_readdir_entry*)((char*)last_entry +
  1140. +                last_entry->next_entry_offset);
  1141. +        }
  1142. +
  1143. +        cookie.cookie = last_entry->cookie;
  1144. +        total_len += len;
  1145. +    }
  1146. +
  1147. +    *buffer_out = buffer;
  1148. +    *length_out = total_len;
  1149. +out:
  1150. +    return status;
  1151. +
  1152. +out_free:
  1153. +    free(buffer);
  1154. +    goto out;
  1155. +}
  1156. +
  1157. +#define ALIGNED_STREAMINFOSIZE(namebytelen) \
  1158. +    (align8(sizeof(FILE_STREAM_INFORMATION) + (namebytelen)))
  1159. +
  1160. +static
  1161. +uint32_t calculate_stream_list_length(
  1162. +    IN nfs41_component *streamfilename,
  1163. +    IN const unsigned char *restrict position,
  1164. +    IN uint32_t remaining)
  1165. +{
  1166. +    const nfs41_readdir_entry *entry;
  1167. +    uint32_t length = 0;
  1168. +
  1169. +    /* FIXME: We always have to add a dummy ::$DATA entry for the default stream! */
  1170. +
  1171. +    while (remaining) {
  1172. +        entry = (const nfs41_readdir_entry *)position;
  1173. +
  1174. +        if ((entry->name_len > WIN_NFS4_STREAMS_NAME_PREFIX_LEN) &&
  1175. +            (memcmp(entry->name,
  1176. +                WIN_NFS4_STREAMS_NAME_PREFIX,
  1177. +                WIN_NFS4_STREAMS_NAME_PREFIX_LEN) == 0)) {
  1178. +            /* FIXME: We should return the correct length of the UTF-8 string in |WCHAR| bytes*/
  1179. +            length +=
  1180. +                ALIGNED_STREAMINFOSIZE((streamfilename->len + 1 + entry->name_len - WIN_NFS4_STREAMS_NAME_PREFIX_LEN)*sizeof(WCHAR));
  1181. +        }
  1182. +
  1183. +        if (!entry->next_entry_offset)
  1184. +            break;
  1185. +
  1186. +        position += entry->next_entry_offset;
  1187. +        remaining -= entry->next_entry_offset;
  1188. +    }
  1189. +    return length;
  1190. +}
  1191. +
  1192. +static
  1193. +void populate_stream_list(
  1194. +    IN nfs41_component *streamfilename,
  1195. +    IN const unsigned char *position,
  1196. +    OUT FILE_STREAM_INFORMATION *restrict stream_list)
  1197. +{
  1198. +    const nfs41_readdir_entry *entry;
  1199. +    PFILE_STREAM_INFORMATION stream = stream_list;
  1200. +    PFILE_STREAM_INFORMATION last_win_stream = NULL;
  1201. +    bool is_win_stream;
  1202. +
  1203. +    /* FIXME: We always have to add a dummy ::$DATA entry for the default stream! */
  1204. +
  1205. +    for (;;) {
  1206. +        entry = (const nfs41_readdir_entry *)position;
  1207. +
  1208. +        if ((entry->name_len > WIN_NFS4_STREAMS_NAME_PREFIX_LEN) &&
  1209. +            (memcmp(entry->name,
  1210. +                WIN_NFS4_STREAMS_NAME_PREFIX,
  1211. +                WIN_NFS4_STREAMS_NAME_PREFIX_LEN) == 0)) {
  1212. +            is_win_stream = true;
  1213. +        }
  1214. +        else {
  1215. +            is_win_stream = false;
  1216. +        }
  1217. +
  1218. +        if (is_win_stream) {
  1219. +            size_t streamnamelen_chars =
  1220. +                (streamfilename->len + 1 + entry->name_len - WIN_NFS4_STREAMS_NAME_PREFIX_LEN);
  1221. +            stream->StreamNameLength = (ULONG)(streamnamelen_chars*sizeof(WCHAR));
  1222. +
  1223. +            EASSERT(bitmap_isset(&entry->attr_info.attrmask, 0,
  1224. +                FATTR4_WORD0_SIZE));
  1225. +            EASSERT(bitmap_isset(&entry->attr_info.attrmask, 1,
  1226. +                FATTR4_WORD1_SPACE_USED));
  1227. +            stream->StreamSize.QuadPart = entry->attr_info.size;
  1228. +            stream->StreamAllocationSize.QuadPart = entry->attr_info.space_used;
  1229. +
  1230. +            /* FIXME: Unicode!! */
  1231. +            ULONG i;
  1232. +            wchar_t *ssn = stream->StreamName;
  1233. +            for (i=0 ; i < streamfilename->len ; i++)
  1234. +                *ssn++ = streamfilename->name[i];
  1235. +            *ssn++ = L':';
  1236. +            for (i=0 ; i < streamnamelen_chars ; i++)
  1237. +                *ssn++ = entry->name[i+WIN_NFS4_STREAMS_NAME_PREFIX_LEN];
  1238. +
  1239. +            DPRINTF(0,
  1240. +                ("populate_streams_list: adding stream "
  1241. +                    "entry->(name='%.*s' name_len=%d) "
  1242. +                    "stream->(StreamName='%.*ls', StreamNameLength=%d, "
  1243. +                    "StreamSize=%lld, StreamAllocationSize=%lld)\n",
  1244. +                    (int)entry->name_len,
  1245. +                    entry->name,
  1246. +                    (int)entry->name_len,
  1247. +                    (int)stream->StreamNameLength/sizeof(WCHAR),
  1248. +                    stream->StreamName,
  1249. +                    (int)stream->StreamNameLength,
  1250. +                    (long long)stream->StreamSize.QuadPart,
  1251. +                    (long long)stream->StreamAllocationSize.QuadPart));
  1252. +            last_win_stream = stream;
  1253. +        }
  1254. +
  1255. +        if (!entry->next_entry_offset) {
  1256. +            (last_win_stream?last_win_stream:stream)->NextEntryOffset = 0;
  1257. +            break;
  1258. +        }
  1259. +
  1260. +#define STREAMINFO_NEXT_ENTRY(str) ((PBYTE)(str) + (str)->NextEntryOffset)
  1261. +        if (is_win_stream) {
  1262. +            stream->NextEntryOffset =
  1263. +                ALIGNED_STREAMINFOSIZE(stream->StreamNameLength);
  1264. +            stream = (PFILE_STREAM_INFORMATION)STREAMINFO_NEXT_ENTRY(stream);
  1265. +        }
  1266. +
  1267. +        position += entry->next_entry_offset;
  1268. +    }
  1269. +}
  1270. +
  1271. +/*static*/
  1272. +int get_stream_list(
  1273. +    IN OUT nfs41_open_state *state,
  1274. +    IN nfs41_path_fh *streamfile,
  1275. +    OUT PFILE_STREAM_INFORMATION *restrict streamlist_out,
  1276. +    OUT ULONG *streamlist_out_size)
  1277. +{
  1278. +    unsigned char *entry_list;
  1279. +    PFILE_STREAM_INFORMATION stream_list;
  1280. +    uint32_t entry_len, stream_list_size;
  1281. +    int status = NO_ERROR;
  1282. +
  1283. +    /* read the entire directory into a |nfs41_readdir_entry| buffer */
  1284. +    status = read_entire_dir(state->session, streamfile,
  1285. +        &entry_list, &entry_len);
  1286. +    if (status)
  1287. +        goto out;
  1288. +
  1289. +    stream_list_size = calculate_stream_list_length(&streamfile->name, entry_list, entry_len);
  1290. +    if (stream_list_size == 0) {
  1291. +        *streamlist_out = NULL;
  1292. +        *streamlist_out_size = 0UL;
  1293. +        goto out_free;
  1294. +    }
  1295. +    stream_list = calloc(1, stream_list_size);
  1296. +    if (stream_list == NULL) {
  1297. +        status = GetLastError();
  1298. +        goto out_free;
  1299. +    }
  1300. +
  1301. +    populate_stream_list(&streamfile->name, entry_list, stream_list);
  1302. +
  1303. +    DPRINTF(0, ("get_stream_list: stream_list=%p, stream_list_size=%ld\n",
  1304. +        stream_list, (long)stream_list_size));
  1305. +    *streamlist_out = stream_list;
  1306. +    *streamlist_out_size = stream_list_size;
  1307. +out_free:
  1308. +    free(entry_list); /* allocated by |read_entire_dir()| */
  1309. +out:
  1310. +    return status;
  1311. +}
  1312. +
  1313. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  1314. diff --git a/daemon/winstreams.h b/daemon/winstreams.h
  1315. new file mode 100644
  1316. index 0000000..f102afb
  1317. --- /dev/null
  1318. +++ b/daemon/winstreams.h
  1319. @@ -0,0 +1,73 @@
  1320. +/* NFSv4.1 client for Windows
  1321. + * Copyright (C) 2025-2026 Roland Mainz <roland.mainz@nrubsig.org>
  1322. + *
  1323. + * Roland Mainz <roland.mainz@nrubsig.org>
  1324. + *
  1325. + * This library is free software; you can redistribute it and/or modify it
  1326. + * under the terms of the GNU Lesser General Public License as published by
  1327. + * the Free Software Foundation; either version 2.1 of the License, or (at
  1328. + * your option) any later version.
  1329. + *
  1330. + * This library is distributed in the hope that it will be useful, but
  1331. + * without any warranty; without even the implied warranty of merchantability
  1332. + * or fitness for a particular purpose.  See the GNU Lesser General Public
  1333. + * License for more details.
  1334. + *
  1335. + * You should have received a copy of the GNU Lesser General Public License
  1336. + * along with this library; if not, write to the Free Software Foundation,
  1337. + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  1338. + */
  1339. +
  1340. +#ifndef __NFS41_DAEMON_WINSTREAMS_H__
  1341. +#define __NFS41_DAEMON_WINSTREAMS_H__ 1
  1342. +
  1343. +#include <stdlib.h>
  1344. +#include <stdbool.h>
  1345. +
  1346. +#include "nfs41_build_features.h"
  1347. +#include "nfs41_types.h"
  1348. +#include "from_kernel.h"
  1349. +
  1350. +
  1351. +static __inline
  1352. +bool is_stream_path(const nfs41_abs_path *restrict path)
  1353. +{
  1354. +    if (memchr(path->path, ':', path->len) != NULL)
  1355. +        return true;
  1356. +    return false;
  1357. +}
  1358. +
  1359. +static __inline
  1360. +bool is_stream_path_fh(const nfs41_path_fh *restrict path)
  1361. +{
  1362. +    if (memchr(path->name.name, ':', path->name.len) != NULL)
  1363. +        return true;
  1364. +    return false;
  1365. +}
  1366. +
  1367. +static __inline
  1368. +bool is_stream_component(const nfs41_component *restrict comp)
  1369. +{
  1370. +    if (memchr(comp->name, ':', comp->len) != NULL)
  1371. +        return true;
  1372. +    return false;
  1373. +}
  1374. +
  1375. +#ifdef NFS41_WINSTREAMS_SUPPORT
  1376. +
  1377. +int parse_win32stream_name(
  1378. +    IN const char *restrict path,
  1379. +    OUT bool *restrict is_stream,
  1380. +    OUT char *restrict base_name,
  1381. +    OUT char *restrict stream_name);
  1382. +
  1383. +typedef struct __nfs41_open_state nfs41_open_state;
  1384. +
  1385. +int get_stream_list(
  1386. +    IN OUT nfs41_open_state *state,
  1387. +    IN nfs41_path_fh *streamdir,
  1388. +    OUT PFILE_STREAM_INFORMATION *restrict streamlist_out,
  1389. +    OUT ULONG *streamlist_out_size);
  1390. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  1391. +
  1392. +#endif /* !__NFS41_DAEMON_WINSTREAMS_H__ */
  1393. diff --git a/include/from_kernel.h b/include/from_kernel.h
  1394. index b9640a1..c0a24b3 100644
  1395. --- a/include/from_kernel.h
  1396. +++ b/include/from_kernel.h
  1397. @@ -1,6 +1,6 @@
  1398.  /* NFSv4.1 client for Windows
  1399.   * Copyright (C) 2012 The Regents of the University of Michigan
  1400. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  1401. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  1402.   *
  1403.   * Olga Kornievskaia <aglo@umich.edu>
  1404.   * Casey Bodley <cbodley@umich.edu>
  1405. @@ -191,6 +191,14 @@ typedef struct _FILE_RENAME_INFORMATION {
  1406.      WCHAR FileName[1];
  1407.  } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;
  1408.  
  1409. +typedef struct _FILE_STREAM_INFORMATION {
  1410. +    ULONG NextEntryOffset;
  1411. +    ULONG StreamNameLength;
  1412. +    LARGE_INTEGER StreamSize;
  1413. +    LARGE_INTEGER StreamAllocationSize;
  1414. +    WCHAR StreamName[1];
  1415. +} FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION;
  1416. +
  1417.  /* FileAttributeTagInformation==35 */
  1418.  typedef struct _FILE_ATTRIBUTE_TAG_INFORMATION {
  1419.      ULONG FileAttributes;
  1420. diff --git a/nfs41_build_features.h b/nfs41_build_features.h
  1421. index fb744f5..227008f 100644
  1422. --- a/nfs41_build_features.h
  1423. +++ b/nfs41_build_features.h
  1424. @@ -1,6 +1,6 @@
  1425.  /* NFSv4.1 client for Windows
  1426.   * Copyright (C) 2012 The Regents of the University of Michigan
  1427. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  1428. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  1429.   *
  1430.   * Olga Kornievskaia <aglo@umich.edu>
  1431.   * Casey Bodley <cbodley@umich.edu>
  1432. @@ -273,4 +273,10 @@
  1433.   */
  1434.  #define NFS41_DRIVER_ALLOW_CREATEFILE_ACLS 1
  1435.  
  1436. +/*
  1437. + * |NFS41_WINSTREAMS_SUPPORT| - Enable Win32 named streams support using
  1438. + * NFSv4.1 named attributes
  1439. + */
  1440. +#define NFS41_WINSTREAMS_SUPPORT 1
  1441. +
  1442.  #endif /* !_NFS41_DRIVER_BUILDFEATURES_ */
  1443. diff --git a/sys/nfs41sys_fileinfo.c b/sys/nfs41sys_fileinfo.c
  1444. index f871915..f12b4e8 100644
  1445. --- a/sys/nfs41sys_fileinfo.c
  1446. +++ b/sys/nfs41sys_fileinfo.c
  1447. @@ -1,6 +1,6 @@
  1448.  /* NFSv4.1 client for Windows
  1449.   * Copyright (C) 2012 The Regents of the University of Michigan
  1450. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  1451. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  1452.   *
  1453.   * Olga Kornievskaia <aglo@umich.edu>
  1454.   * Casey Bodley <cbodley@umich.edu>
  1455. @@ -171,8 +171,19 @@ void unmarshal_nfs41_getattr(
  1456.      nfs41_updowncall_entry *cur,
  1457.      const unsigned char *restrict *restrict buf)
  1458.  {
  1459. -    unmarshal_nfs41_attrget(cur,
  1460. -        cur->u.QueryFile.buf, &cur->u.QueryFile.buf_len, buf, FALSE);
  1461. +#ifdef NFS41_WINSTREAMS_SUPPORT
  1462. +    if (cur->u.QueryFile.InfoClass == FileStreamInformation) {
  1463. +        /* FIXME: If we do a partial read, what happens to ChangeTime below ? */
  1464. +        unmarshal_nfs41_attrget(cur,
  1465. +            cur->u.QueryFile.buf, &cur->u.QueryFile.buf_len, buf, TRUE);
  1466. +    }
  1467. +    else
  1468. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  1469. +    {
  1470. +        unmarshal_nfs41_attrget(cur,
  1471. +            cur->u.QueryFile.buf, &cur->u.QueryFile.buf_len, buf, FALSE);
  1472. +    }
  1473. +
  1474.      RtlCopyMemory(&cur->ChangeTime, *buf, sizeof(cur->ChangeTime));
  1475.      *buf += sizeof(cur->ChangeTime);
  1476.  #ifdef DEBUG_MARSHAL_DETAIL
  1477. @@ -398,6 +409,9 @@ NTSTATUS nfs41_QueryFileInformation(
  1478.      case FileStatInformation:
  1479.      case FileStatLxInformation:
  1480.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  1481. +#ifdef NFS41_WINSTREAMS_SUPPORT
  1482. +    case FileStreamInformation:
  1483. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  1484.          break;
  1485.      default:
  1486.          print_error("nfs41_QueryFileInformation: "
  1487. @@ -432,7 +446,23 @@ NTSTATUS nfs41_QueryFileInformation(
  1488.          print_error("nfs41_QueryFileInformation: "
  1489.              "entry->status == STATUS_BUFFER_TOO_SMALL\n");
  1490.          status = STATUS_BUFFER_TOO_SMALL;
  1491. -    } else if (entry->status == STATUS_SUCCESS) {
  1492. +    }
  1493. +    else
  1494. +#ifdef NFS41_WINSTREAMS_SUPPORT
  1495. +    if ((InfoClass == FileStreamInformation) &&
  1496. +        (entry->status == STATUS_BUFFER_OVERFLOW)) {
  1497. +        /*
  1498. +         * |FileStreamInformation| must return |STATUS_BUFFER_OVERFLOW| if
  1499. +         * the buffer is too small to store all data
  1500. +         */
  1501. +        RxContext->InformationToReturn = entry->u.QueryFile.buf_len;
  1502. +        print_error("nfs41_QueryFileInformation: "
  1503. +            "FileStreamInformation: "
  1504. +            "entry->status == STATUS_BUFFER_OVERFLOW\n");
  1505. +        status = STATUS_BUFFER_OVERFLOW;
  1506. +    } else
  1507. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  1508. +    if (entry->status == STATUS_SUCCESS) {
  1509.  #ifdef DEBUG_FILE_QUERY
  1510.          print_error("nfs41_QueryFileInformation: entry->status == STATUS_SUCCESS\n");
  1511.  #endif
  1512. @@ -500,6 +530,9 @@ NTSTATUS nfs41_QueryFileInformation(
  1513.          case FileStatInformation:
  1514.          case FileStatLxInformation:
  1515.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  1516. +#ifdef NFS41_WINSTREAMS_SUPPORT
  1517. +        case FileStreamInformation:
  1518. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  1519.              break;
  1520.          default:
  1521.              print_error("nfs41_QueryFileInformation: "
  1522. diff --git a/sys/nfs41sys_openclose.c b/sys/nfs41sys_openclose.c
  1523. index 93c95b1..8c24ad1 100644
  1524. --- a/sys/nfs41sys_openclose.c
  1525. +++ b/sys/nfs41sys_openclose.c
  1526. @@ -1,6 +1,6 @@
  1527.  /* NFSv4.1 client for Windows
  1528.   * Copyright (C) 2012 The Regents of the University of Michigan
  1529. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  1530. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  1531.   *
  1532.   * Olga Kornievskaia <aglo@umich.edu>
  1533.   * Casey Bodley <cbodley@umich.edu>
  1534. @@ -574,11 +574,13 @@ NTSTATUS check_nfs41_create_args(
  1535.          goto out;
  1536.      }
  1537.  
  1538. +#ifndef NFS41_WINSTREAMS_SUPPORT
  1539.      if (isStream(SrvOpen->pAlreadyPrefixedName)) {
  1540.          DbgP("nfs41_Create: Streams not supported (yet)\n");
  1541.          status = STATUS_NOT_SUPPORTED;
  1542.          goto out;
  1543.      }
  1544. +#endif /* !NFS41_WINSTREAMS_SUPPORT */
  1545.  
  1546.      if (pVNetRootContext->read_only &&
  1547.              (params->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
  1548. diff --git a/sys/nfs41sys_updowncall.c b/sys/nfs41sys_updowncall.c
  1549. index bc9093e..755798b 100644
  1550. --- a/sys/nfs41sys_updowncall.c
  1551. +++ b/sys/nfs41sys_updowncall.c
  1552. @@ -1,6 +1,6 @@
  1553.  /* NFSv4.1 client for Windows
  1554.   * Copyright (C) 2012 The Regents of the University of Michigan
  1555. - * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  1556. + * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  1557.   *
  1558.   * Olga Kornievskaia <aglo@umich.edu>
  1559.   * Casey Bodley <cbodley@umich.edu>
  1560. @@ -807,14 +807,18 @@ NTSTATUS nfs41_downcall(
  1561.          if ((header_tmp->opcode != NFS41_SYSOP_VOLUME_QUERY) &&
  1562.              (bytesread_from_inbuf != inbuf_len)) {
  1563.              print_error("nfs41_downcall: ASSERT: '%s' (xid=%lld): "
  1564. -                "(inbuf(=0x%p)-inbuf_orig(=0x%p))(=%ld) != inbuf_len(=%ld)\n",
  1565. +                "(inbuf(=0x%p)-inbuf_orig(=0x%p))(=%ld) != inbuf_len(=%ld), "
  1566. +                "cur->status=%d\n",
  1567.                  opcode2string(header_tmp->opcode),
  1568.                  cur->xid,
  1569.                  inbuf,
  1570.                  inbuf_orig,
  1571.                  (long)bytesread_from_inbuf,
  1572. -                (long)inbuf_len);
  1573. -            status = STATUS_BUFFER_OVERFLOW;
  1574. +                (long)inbuf_len,
  1575. +                cur->status);
  1576. +            if (cur->status == STATUS_SUCCESS) {
  1577. +                status = STATUS_BUFFER_OVERFLOW;
  1578. +            }
  1579.          }
  1580.      }
  1581.      ExReleaseFastMutexUnsafe(&cur->lock);
  1582. diff --git a/tests/manual_testing.txt b/tests/manual_testing.txt
  1583. index a520bfb..386ba0f 100644
  1584. --- a/tests/manual_testing.txt
  1585. +++ b/tests/manual_testing.txt
  1586. @@ -1,5 +1,5 @@
  1587.  #
  1588. -# ms-nfs41-client manual testing sequence, 2025-12-12
  1589. +# ms-nfs41-client manual testing sequence, 2026-01-02
  1590.  #
  1591.  # Draft version, needs to be turned into automated tests
  1592.  # if possible
  1593. @@ -218,6 +218,21 @@ $ rm -f d1 d2 d1.diff ; printf '1\n2\n' >d1 ; cp d1 d2 ; printf '3\n' >> d2 ; di
  1594.  The test should print "# test OK"
  1595.  
  1596.  
  1597. +#
  1598. +# Tests for Win32 streams
  1599. +#
  1600. +
  1601. +# this test should print $'dir_line1\ndir_line2\n'
  1602. +rm -Rf dir1 && mkdir dir1 && cmd /c 'echo dir_line1 >>dir1:dirstr1 & echo dir_line2 >>dir1:dirstr1 & cat <dir1:dirstr1'
  1603. +# these two tests should print $'file1data\nfile1_line1\nfile1_line2\n'
  1604. +rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:filestr1 & echo file1_line2 >>file1:filestr1:$DATA & C:\cygwin64\bin\cat.exe <file1 & C:\cygwin64\bin\cat.exe <file1:filestr1'
  1605. +rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:filestr1 & echo file1_line2 >>file1:filestr1:$DATA && type file1' && powershell -Command 'Get-Content -Path .\file1 -Stream filestr1'# this should list stream "filestr1"
  1606. +# list all streams for file "file1" (should be "filestr1")
  1607. +powershell -Command 'Get-Item -LiteralPath file1 -Stream * | Select *'
  1608. +
  1609. +# FIXME: tests for stream removal, enumeration and sparse data
  1610. +
  1611. +
  1612.  #
  1613.  # Tests for Cycgwin/UWIN/SFU Nfs3Attr EA-based local uid/gid
  1614.  #
  1615. diff --git a/tests/winfsinfo1/winfsinfo.c b/tests/winfsinfo1/winfsinfo.c
  1616. index edc3255..74ac2b7 100644
  1617. --- a/tests/winfsinfo1/winfsinfo.c
  1618. +++ b/tests/winfsinfo1/winfsinfo.c
  1619. @@ -1,7 +1,7 @@
  1620.  /*
  1621.   * MIT License
  1622.   *
  1623. - * Copyright (c) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
  1624. + * Copyright (c) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
  1625.   *
  1626.   * Permission is hereby granted, free of charge, to any person obtaining a copy
  1627.   * of this software and associated documentation files (the "Software"), to deal
  1628. @@ -1518,6 +1518,85 @@ done:
  1629.      return res;
  1630.  }
  1631.  
  1632. +static
  1633. +int get_filestreaminfo(const char *progname, const char *filename)
  1634. +{
  1635. +    int res = EXIT_FAILURE;
  1636. +    NTSTATUS status;
  1637. +    IO_STATUS_BLOCK iostatus;
  1638. +    PFILE_STREAM_INFORMATION fsi = NULL;
  1639. +
  1640. +    HANDLE fileHandle = CreateFileA(filename,
  1641. +        GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
  1642. +        FILE_FLAG_BACKUP_SEMANTICS, NULL);
  1643. +    if (fileHandle == INVALID_HANDLE_VALUE) {
  1644. +        (void)fprintf(stderr,
  1645. +            "%s: Error opening file '%s'. Last error was %d.\n",
  1646. +            progname,
  1647. +            filename,
  1648. +            (int)GetLastError());
  1649. +        return EXIT_FAILURE;
  1650. +    }
  1651. +
  1652. +#define MAX_STREAM_INFOS (16)
  1653. +#define FSI_MAXCHARS (256)
  1654. +    fsi = calloc(1,
  1655. +        (sizeof(FILE_STREAM_INFORMATION)+sizeof(wchar_t)*FSI_MAXCHARS)*MAX_STREAM_INFOS);
  1656. +    if (fsi == NULL) {
  1657. +         (void)fprintf(stderr,
  1658. +            "%s: Out of memory.\n",
  1659. +            progname);
  1660. +        return EXIT_FAILURE;
  1661. +    }
  1662. +
  1663. +    status = ZwQueryInformationFile(fileHandle,
  1664. +        &iostatus,
  1665. +        fsi,
  1666. +        ((sizeof(FILE_STREAM_INFORMATION)+sizeof(wchar_t)*FSI_MAXCHARS)*MAX_STREAM_INFOS),
  1667. +        FileStreamInformation);
  1668. +
  1669. +    if (status != STATUS_SUCCESS) {
  1670. +        (void)fprintf(stderr, "%s: ZwQueryInformationFile() "
  1671. +            "error. status==0x%lx.\n",
  1672. +            progname,
  1673. +            (long)status);
  1674. +        res = EXIT_FAILURE;
  1675. +        goto done;
  1676. +    }
  1677. +
  1678. +    int streamindex;
  1679. +    FILE_STREAM_INFORMATION *stream;
  1680. +
  1681. +    (void)printf("(\n");
  1682. +    (void)printf("\tfilename='%s'\n", filename);
  1683. +    (void)printf("\ttypeset -a streams=(\n");
  1684. +    for (stream = fsi, streamindex = 0 ; ; streamindex++) {
  1685. +        (void)printf("\t\t[%d]=(\n", streamindex);
  1686. +        (void)printf("\t\t\tStreamName='%.*ls'\n",
  1687. +            (int)(stream->StreamNameLength/sizeof(WCHAR)),
  1688. +            stream->StreamName);
  1689. +        (void)printf("\t\t\tStreamSize=%lld\n",
  1690. +            (long long)stream->StreamSize.QuadPart);
  1691. +        (void)printf("\t\t\tStreamAllocationSize=%lld\n",
  1692. +            (long long)stream->StreamAllocationSize.QuadPart);
  1693. +        (void)printf("\t\t)\n");
  1694. +
  1695. +        if (stream->NextEntryOffset == 0)
  1696. +            break;
  1697. +
  1698. +        stream = (FILE_STREAM_INFORMATION *)((char *)stream + stream->NextEntryOffset);
  1699. +    }
  1700. +    (void)printf("\t)\n");
  1701. +
  1702. +    (void)printf(")\n");
  1703. +    res = EXIT_SUCCESS;
  1704. +
  1705. +done:
  1706. +    free(fsi);
  1707. +    (void)CloseHandle(fileHandle);
  1708. +    return res;
  1709. +}
  1710. +
  1711.  #define BUFFER_SIZE 2048
  1712.  
  1713.  static
  1714. @@ -1769,6 +1848,7 @@ void usage(void)
  1715.          "fileremoteprotocolinfo|"
  1716.          "fileidinfo|"
  1717.          "filenetworkphysicalnameinfo|"
  1718. +        "filestreaminfo|"
  1719.          "fsctlqueryallocatedranges|"
  1720.          "get_wnetgetresourceinformation|"
  1721.          "get_wnetgetresourceparent"
  1722. @@ -1851,6 +1931,9 @@ int main(int ac, char *av[])
  1723.      else if (!strcmp(subcmd, "filenetworkphysicalnameinfo")) {
  1724.          return get_filenetworkphysicalnameinfo(av[0], av[2]);
  1725.      }
  1726. +    else if (!strcmp(subcmd, "filestreaminfo")) {
  1727. +        return get_filestreaminfo(av[0], av[2]);
  1728. +    }
  1729.      else if (!strcmp(subcmd, "fsctlqueryallocatedranges")) {
  1730.          return fsctlqueryallocatedranges(av[0], av[2]);
  1731.      }

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