pastebin - collaborative debugging tool
nrubsig.kpaste.net RSS


Win32 streams support
Posted by Anonymous on Wed 31st Dec 2025 00:10
raw | new post
view followups (newest first): Win32 streams support by Anonymous
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..bbd6d28 100644
  47. --- a/daemon/fileinfoutil.c
  48. +++ b/daemon/fileinfoutil.c
  49. @@ -59,7 +59,7 @@ ULONG nfs_file_info_to_attributes(
  50.          if (info->symlink_dir)
  51.              attrs |= FILE_ATTRIBUTE_DIRECTORY;
  52.      }
  53. -    else if (info->type == NF4REG) {
  54. +    else if ((info->type == NF4REG) || (info->type == NF4NAMEDATTR)) {
  55.          if (superblock->sparse_file_support) {
  56.              /* FIXME: What about pNFS ? */
  57.              attrs |= FILE_ATTRIBUTE_SPARSE_FILE;
  58. @@ -383,6 +383,7 @@ void nfs_to_stat_lx_info(
  59.      stat_lx_out->LxMode = 0UL;
  60.      switch(info->type) {
  61.          case NF4REG:
  62. +        case NF4NAMEDATTR:
  63.              stat_lx_out->LxMode |= LX_MODE_S_IFREG;
  64.              break;
  65.          case NF4DIR:
  66. diff --git a/daemon/getattr.c b/daemon/getattr.c
  67. index 4744285..1069a8d 100644
  68. --- a/daemon/getattr.c
  69. +++ b/daemon/getattr.c
  70. @@ -204,6 +204,9 @@ static int handle_getattr(void *daemon_context, nfs41_upcall *upcall)
  71.              &args->stat_lx_info);
  72.          break;
  73.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  74. +#ifdef NFS41_WINSTREAMS_SUPPORT
  75. +    case FileStreamInformation:
  76. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  77.      default:
  78.          eprintf("handle_getattr(state->path.path='%s'): "
  79.              "unhandled file query class %d\n",
  80. diff --git a/daemon/lookup.c b/daemon/lookup.c
  81. index 3e9e591..a7fb7e7 100644
  82. --- a/daemon/lookup.c
  83. +++ b/daemon/lookup.c
  84. @@ -31,6 +31,9 @@
  85.  #include "fileinfoutil.h"
  86.  #include "util.h"
  87.  #include "daemon_debug.h"
  88. +#ifdef NFS41_WINSTREAMS_SUPPORT
  89. +#include "winstreams.h"
  90. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  91.  
  92.  
  93.  #define LULVL 2 /* dprintf level for lookup logging */
  94. @@ -502,6 +505,156 @@ int nfs41_lookup(
  95.  
  96.      DPRINTF(LULVL, ("--> nfs41_lookup('%s')\n", path.path));
  97.  
  98. +#ifdef NFS41_WINSTREAMS_SUPPORT
  99. +    /* Fast check for potential stream: Look for a colon */
  100. +    if (memchr(path_inout->path, ':', path_inout->len)) {
  101. +        char base_name[NFS41_MAX_PATH_LEN+1];
  102. +        char stream_name[NFS41_MAX_COMPONENT_LEN+1];
  103. +        bool is_stream = false;
  104. +
  105. +        /* Parse the stream syntax */
  106. +        status = parse_win32stream_name(path.path, &is_stream, base_name, stream_name);
  107. +        if (status)
  108. +            goto out;
  109. +
  110. +        if (is_stream) {
  111. +            if (stream_name[0] == '\0') {
  112. +                /* "foo::$DATA" case -> treat as "foo" */
  113. +                DPRINTF(2, ("nfs41_lookup: 'foo::$DATA' case -> treat as 'foo'\n"));
  114. +                size_t base_len = strlen(base_name);
  115. +
  116. +                (void)memcpy(path.path, base_name, base_len);
  117. +                path.path[base_len] = '\0';
  118. +                path.len = (unsigned short)base_len;
  119. +                path_pos = path.path;
  120. +                path_end = path.path + path.len;
  121. +
  122. +                /* Continue to normal lookup with stripped name */
  123. +            } else {
  124. +                /* "foo:bar" case */
  125. +                DPRINTF(2, ("nfs41_lookup: base_name='%s', stream_name='%s'\n",
  126. +                    base_name, stream_name));
  127. +
  128. +                /*
  129. +                 * Recursively lookup the base file ...
  130. +                 */
  131. +                nfs41_abs_path base_path = path;
  132. +                size_t base_len = strlen(base_name);
  133. +
  134. +                (void)memcpy(base_path.path, base_name, base_len);
  135. +                base_path.path[base_len] = '\0';
  136. +                base_path.len = (unsigned short)base_len;
  137. +
  138. +                nfs41_path_fh attr_target;
  139. +                if (target_out == NULL) {
  140. +                    target_out = &attr_target;
  141. +                }
  142. +
  143. +                status = nfs41_lookup(root, session, casesensitive, &base_path,
  144. +                    parent_out, target_out, info_out, session_out);
  145. +                if (status)
  146. +                    goto out;
  147. +
  148. +                /*
  149. +                 * ... and then lookup the NFSv4 named attribute
  150. +                 */
  151. +                nfs41_session *lookup_session;
  152. +                nfs41_compound compound;
  153. +                nfs_argop4 argops[8];
  154. +                nfs_resop4 resops[8];
  155. +                nfs41_sequence_args sequence_args;
  156. +                nfs41_sequence_res sequence_res;
  157. +                nfs41_openattr_args openattr_args;
  158. +                nfs41_openattr_res openattr_res;
  159. +                nfs41_putfh_args putfh_args = { 0 };
  160. +                nfs41_putfh_res putfh_res;
  161. +                nfs41_lookup_args lookup_args = { 0 };
  162. +                nfs41_lookup_res lookup_res;
  163. +                nfs41_getfh_res getfh_res;
  164. +                nfs41_getattr_args getattr_args = { 0 };
  165. +                nfs41_getattr_res getattr_res = { 0 };
  166. +                nfs41_component stream_comp = {
  167. +                    .name = stream_name,
  168. +                    .len = (unsigned short)strlen(stream_name)
  169. +                };
  170. +                /* Minimal attributes for stream */
  171. +                bitmap4 attr_request = {
  172. +                    .count = 2,
  173. +                    .arr = {
  174. +                        [0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE |
  175. +                            FATTR4_WORD0_SIZE | FATTR4_WORD0_FSID |
  176. +                            FATTR4_WORD0_FILEID,
  177. +                        [1] = FATTR4_WORD1_MODE | FATTR4_WORD1_SPACE_USED |
  178. +                            FATTR4_WORD1_TIME_ACCESS |
  179. +                            FATTR4_WORD1_TIME_CREATE |
  180. +                            FATTR4_WORD1_TIME_MODIFY |
  181. +                            FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP,
  182. +                        [2] = 0
  183. +                    }
  184. +                };
  185. +
  186. +                DPRINTF(1,
  187. +                    ("nfs41_lookup: "
  188. +                    "Looking up base_name='%s'/stream_name='%s'\n",
  189. +                    base_name, stream_name));
  190. +
  191. +                /* Use the session returned by the base lookup if applicable */
  192. +                if ((session_out != NULL) && (*session_out != NULL))
  193. +                    lookup_session = *session_out;
  194. +                else
  195. +                    lookup_session = session;
  196. +
  197. +                compound_init(&compound,
  198. +                    lookup_session->client->root->nfsminorvers,
  199. +                    argops, resops, "lookup_win32stream");
  200. +
  201. +                compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
  202. +                nfs41_session_sequence(&sequence_args, lookup_session, 0);
  203. +
  204. +                compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
  205. +                putfh_args.file = target_out;
  206. +                putfh_args.in_recovery = FALSE;
  207. +
  208. +                compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  209. +                openattr_args.createdir = FALSE;
  210. +
  211. +                compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  212. +                lookup_args.name = &stream_comp;
  213. +
  214. +                compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
  215. +                getfh_res.fh = &target_out->fh;
  216. +
  217. +                compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
  218. +                getattr_args.attr_request = &attr_request;
  219. +                /* If caller wanted info, fill it; else dummy */
  220. +                nfs41_file_info dummy_info;
  221. +                if (info_out)
  222. +                    getattr_res.info = info_out;
  223. +                else
  224. +                    getattr_res.info = &dummy_info;
  225. +                getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT_ATTR;
  226. +
  227. +                status = compound_encode_send_decode(lookup_session, &compound, TRUE);
  228. +
  229. +                if (status == 0)
  230. +                    status = compound.res.status;
  231. +
  232. +                if (status == 0) {
  233. +                    /* We MUST return a |NF4NAMEDATTR|, and never a |NF4ATTRDIR| */
  234. +                    EASSERT(getattr_res.info->type != NF4ATTRDIR);
  235. +                    EASSERT(getattr_res.info->type == NF4NAMEDATTR);
  236. +                }
  237. +                else {
  238. +                    DPRINTF(1, ("nfs41_lookup: failed for attr, status=%d\n",
  239. +                        (int)status));
  240. +                }
  241. +
  242. +                goto out;
  243. +            }
  244. +        }
  245. +    }
  246. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  247. +
  248.      if (parent_out == NULL) parent_out = &parent;
  249.      if (target_out == NULL) target_out = &target;
  250.      parent_out->fh.len = target_out->fh.len = 0;
  251. diff --git a/daemon/nfs41_ops.c b/daemon/nfs41_ops.c
  252. index 9012db4..ebe0bd5 100644
  253. --- a/daemon/nfs41_ops.c
  254. +++ b/daemon/nfs41_ops.c
  255. @@ -36,6 +36,9 @@
  256.  #include "delegation.h"
  257.  #include "daemon_debug.h"
  258.  #include "util.h"
  259. +#ifdef NFS41_WINSTREAMS_SUPPORT
  260. +#include "winstreams.h"
  261. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  262.  
  263.  #ifdef NFS41_DRIVER_STABILITY_HACKS
  264.  /*
  265. @@ -453,8 +456,8 @@ int nfs41_open(
  266.  {
  267.      int status;
  268.      nfs41_compound compound;
  269. -    nfs_argop4 argops[8];
  270. -    nfs_resop4 resops[8];
  271. +    nfs_argop4 argops[12];
  272. +    nfs_resop4 resops[12];
  273.      nfs41_sequence_args sequence_args;
  274.      nfs41_sequence_res sequence_res;
  275.      nfs41_putfh_args putfh_args[2];
  276. @@ -471,10 +474,39 @@ int nfs41_open(
  277.      bool_t current_fh_is_dir;
  278.      bool_t already_delegated = delegation->type == OPEN_DELEGATE_READ
  279.          || delegation->type == OPEN_DELEGATE_WRITE;
  280. +#ifdef NFS41_WINSTREAMS_SUPPORT
  281. +    char base_buf[NFS41_MAX_PATH_LEN+1];
  282. +    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
  283. +    bool is_stream = false;
  284. +    nfs41_component base_comp = {0};
  285. +    nfs41_component stream_comp = {0};
  286. +    nfs41_lookup_args lookup_args;
  287. +    nfs41_lookup_res lookup_res;
  288. +    nfs41_openattr_args openattr_args;
  289. +    nfs41_openattr_res openattr_res;
  290. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  291.  
  292.      EASSERT_IS_VALID_NON_NULL_PTR(parent);
  293.      EASSERT_IS_VALID_NON_NULL_PTR(parent->fh.superblock);
  294.  
  295. +#ifdef NFS41_WINSTREAMS_SUPPORT
  296. +    if (file->name.name) {
  297. +        if ((memchr(file->name.name, ':', file->name.len) != NULL)) {
  298. +            status = parse_win32stream_name(file->name.name,
  299. +                &is_stream, base_buf, stream_buf);
  300. +            if (status)
  301. +                goto out;
  302. +
  303. +            if (is_stream) {
  304. +                base_comp.name = base_buf;
  305. +                base_comp.len = (unsigned short)strlen(base_buf);
  306. +                stream_comp.name = stream_buf;
  307. +                stream_comp.len = (unsigned short)strlen(stream_buf);
  308. +            }
  309. +        }
  310. +    }
  311. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  312. +
  313.      /* depending on the claim type, OPEN expects CURRENT_FH set
  314.       * to either the parent directory, or to the file itself */
  315.      switch (claim->claim) {
  316. @@ -524,6 +556,16 @@ int nfs41_open(
  317.          putfh_args[0].in_recovery = 0;
  318.      }
  319.  
  320. +#ifdef NFS41_WINSTREAMS_SUPPORT
  321. +    if (is_stream && (stream_comp.len > 0)) {
  322. +        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  323. +        lookup_args.name = &base_comp;
  324. +        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  325. +        /* If we are creating the stream, we should allow creating the attr dir */
  326. +        openattr_args.createdir = (create == OPEN4_CREATE) ? TRUE : FALSE;
  327. +    }
  328. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  329. +
  330.      compound_add_op(&compound, OP_OPEN, &open_args, &open_res);
  331.      open_args.seqid = 0;
  332.  #ifdef DISABLE_FILE_DELEGATIONS
  333. @@ -549,6 +591,27 @@ int nfs41_open(
  334.              parent->fh.superblock, &createattrs->attrmask);
  335.      }
  336.      open_args.claim = claim;
  337. +
  338. +#ifdef NFS41_WINSTREAMS_SUPPORT
  339. +    if (is_stream &&
  340. +        ((claim->claim == CLAIM_NULL) ||
  341. +            (claim->claim == CLAIM_DELEGATE_CUR))) {
  342. +        if (claim->claim == CLAIM_NULL) {
  343. +            if (stream_comp.len > 0)
  344. +                claim->u.null.filename = &stream_comp;
  345. +            else
  346. +                claim->u.null.filename = &base_comp;
  347. +        }
  348. +        else if (claim->claim == CLAIM_DELEGATE_CUR) {
  349. +            claim->u.deleg_cur.name = &stream_comp;
  350. +            if (stream_comp.len > 0)
  351. +                claim->u.deleg_cur.name = &stream_comp;
  352. +            else
  353. +                claim->u.deleg_cur.name = &base_comp;
  354. +        }
  355. +    }
  356. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  357. +
  358.      open_res.resok4.stateid = stateid;
  359.      open_res.resok4.delegation = delegation;
  360.  
  361. @@ -582,6 +645,7 @@ int nfs41_open(
  362.      if (compound_error(status = compound.res.status))
  363.          goto out;
  364.  
  365. +    /* This can happen if |nfs41_open()| is called by the EA code */
  366.      if (dir_info.type == NF4ATTRDIR) {
  367.          file->fh.superblock = parent->fh.superblock;
  368.          goto out;
  369. @@ -614,8 +678,8 @@ int nfs41_create(
  370.  {
  371.      int status;
  372.      nfs41_compound compound;
  373. -    nfs_argop4 argops[8];
  374. -    nfs_resop4 resops[8];
  375. +    nfs_argop4 argops[12];
  376. +    nfs_resop4 resops[12];
  377.      nfs41_sequence_args sequence_args;
  378.      nfs41_sequence_res sequence_res;
  379.      nfs41_putfh_args putfh_args;
  380. @@ -629,6 +693,35 @@ int nfs41_create(
  381.      nfs41_file_info dir_info;
  382.      nfs41_savefh_res savefh_res;
  383.      nfs41_restorefh_res restorefh_res;
  384. +#ifdef NFS41_WINSTREAMS_SUPPORT
  385. +    char base_buf[NFS41_MAX_PATH_LEN+1];
  386. +    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
  387. +    bool is_stream = false;
  388. +    nfs41_component base_comp = {0};
  389. +    nfs41_component stream_comp = {0};
  390. +    nfs41_lookup_args lookup_args;
  391. +    nfs41_lookup_res lookup_res;
  392. +    nfs41_openattr_args openattr_args;
  393. +    nfs41_openattr_res openattr_res;
  394. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  395. +
  396. +#ifdef NFS41_WINSTREAMS_SUPPORT
  397. +    if (file->name.name) {
  398. +        if ((memchr(file->name.name, ':', file->name.len) != NULL)) {
  399. +            status = parse_win32stream_name(file->name.name,
  400. +                &is_stream, base_buf, stream_buf);
  401. +            if (status)
  402. +                goto out;
  403. +
  404. +            if (is_stream) {
  405. +                base_comp.name = base_buf;
  406. +                base_comp.len = (unsigned short)strlen(base_buf);
  407. +                stream_comp.name = stream_buf;
  408. +                stream_comp.len = (unsigned short)strlen(stream_buf);
  409. +            }
  410. +        }
  411. +    }
  412. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  413.  
  414.      nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
  415.  
  416. @@ -644,13 +737,34 @@ int nfs41_create(
  417.  
  418.      compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
  419.  
  420. +#ifdef NFS41_WINSTREAMS_SUPPORT
  421. +    if (is_stream && (stream_comp.len > 0)) {
  422. +        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  423. +        lookup_args.name = &base_comp;
  424. +        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  425. +        openattr_args.createdir = TRUE;
  426. +    }
  427. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  428. +
  429.      compound_add_op(&compound, OP_CREATE, &create_args, &create_res);
  430.      create_args.objtype.type = type;
  431.      if (type == NF4LNK) {
  432.          create_args.objtype.u.lnk.linkdata = symlink;
  433.          create_args.objtype.u.lnk.linkdata_len = (uint32_t)strlen(symlink);
  434.      }
  435. -    create_args.name = &file->name;
  436. +
  437. +#ifdef NFS41_WINSTREAMS_SUPPORT
  438. +    if (is_stream) {
  439. +        if (stream_comp.len > 0)
  440. +            create_args.name = &stream_comp;
  441. +        else
  442. +            create_args.name = &base_comp;
  443. +    }
  444. +    else
  445. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  446. +    {
  447. +        create_args.name = &file->name;
  448. +    }
  449.      create_args.createattrs = createattrs;
  450.      nfs41_superblock_supported_attrs(
  451.                  parent->fh.superblock, &createattrs->attrmask);
  452. @@ -1283,8 +1397,8 @@ int nfs41_remove(
  453.  {
  454.      int status;
  455.      nfs41_compound compound;
  456. -    nfs_argop4 argops[4];
  457. -    nfs_resop4 resops[4];
  458. +    nfs_argop4 argops[8];
  459. +    nfs_resop4 resops[8];
  460.      nfs41_sequence_args sequence_args;
  461.      nfs41_sequence_res sequence_res;
  462.      nfs41_putfh_args putfh_args;
  463. @@ -1295,6 +1409,33 @@ int nfs41_remove(
  464.      nfs41_getattr_res getattr_res NDSH(= { 0 });
  465.      bitmap4 attr_request;
  466.      nfs41_file_info info;
  467. +#ifdef NFS41_WINSTREAMS_SUPPORT
  468. +    char base_buf[NFS41_MAX_PATH_LEN+1];
  469. +    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
  470. +    bool is_stream = false;
  471. +    nfs41_component base_comp = {0};
  472. +    nfs41_component stream_comp = {0};
  473. +    nfs41_lookup_args lookup_args;
  474. +    nfs41_lookup_res lookup_res;
  475. +    nfs41_openattr_args openattr_args;
  476. +    nfs41_openattr_res openattr_res;
  477. +
  478. +    if (target->name) {
  479. +        if ((memchr(target->name, ':', target->len) != NULL)) {
  480. +            status = parse_win32stream_name(target->name,
  481. +                &is_stream, base_buf, stream_buf);
  482. +            if (status)
  483. +                goto out;
  484. +
  485. +            if (is_stream) {
  486. +                base_comp.name = base_buf;
  487. +                base_comp.len = (unsigned short)strlen(base_buf);
  488. +                stream_comp.name = stream_buf;
  489. +                stream_comp.len = (unsigned short)strlen(stream_buf);
  490. +            }
  491. +        }
  492. +    }
  493. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  494.  
  495.      nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
  496.  
  497. @@ -1308,8 +1449,28 @@ int nfs41_remove(
  498.      putfh_args.file = parent;
  499.      putfh_args.in_recovery = 0;
  500.  
  501. +#ifdef NFS41_WINSTREAMS_SUPPORT
  502. +    if (is_stream && (stream_comp.len > 0)) {
  503. +        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
  504. +        lookup_args.name = &base_comp;
  505. +        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
  506. +        openattr_args.createdir = FALSE;
  507. +    }
  508. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  509. +
  510.      compound_add_op(&compound, OP_REMOVE, &remove_args, &remove_res);
  511. -    remove_args.target = target;
  512. +#ifdef NFS41_WINSTREAMS_SUPPORT
  513. +    if (is_stream) {
  514. +        if (stream_comp.len > 0)
  515. +            remove_args.target = &stream_comp;
  516. +        else
  517. +            remove_args.target = &base_comp;
  518. +    }
  519. +    else
  520. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  521. +    {
  522. +        remove_args.target = target;
  523. +    }
  524.  
  525.      compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
  526.      getattr_args.attr_request = &attr_request;
  527. diff --git a/daemon/nfs41_superblock.c b/daemon/nfs41_superblock.c
  528. index 5ffb171..7878df1 100644
  529. --- a/daemon/nfs41_superblock.c
  530. +++ b/daemon/nfs41_superblock.c
  531. @@ -303,8 +303,12 @@ void nfs41_superblock_fs_attributes(
  532.          FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_HARD_LINKS;
  533.      if (superblock->symlink_support)
  534.          FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_REPARSE_POINTS;
  535. -    if (superblock->ea_support)
  536. +    if (superblock->ea_support) {
  537.          FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES;
  538. +#ifdef NFS41_WINSTREAMS_SUPPORT
  539. +        FsAttrs->FileSystemAttributes |= FILE_NAMED_STREAMS;
  540. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  541. +    }
  542.      if (superblock->case_preserving)
  543.          FsAttrs->FileSystemAttributes |= FILE_CASE_PRESERVED_NAMES;
  544.      if (!superblock->case_insensitive)
  545. diff --git a/daemon/open.c b/daemon/open.c
  546. index b5e72c7..4928bf7 100644
  547. --- a/daemon/open.c
  548. +++ b/daemon/open.c
  549. @@ -880,8 +880,12 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
  550.      // first check if windows told us it's a directory
  551.      if (args->create_opts & FILE_DIRECTORY_FILE)
  552.          state->type = NF4DIR;
  553. -    else
  554. -        state->type = NF4REG;
  555. +    else {
  556. +        if (strstr(args->path, ":"))
  557. +            state->type = NF4NAMEDATTR;
  558. +        else
  559. +            state->type = NF4REG;
  560. +    }
  561.  
  562.  #ifdef NFS41_DRIVER_ALLOW_CREATEFILE_ACLS
  563.      if (args->sec_desc_len) {
  564. @@ -1007,20 +1011,26 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
  565.                      goto out_free_state;
  566.                  }
  567.              }
  568. -        } else if (info.type == NF4REG) {
  569. +        } else if (info.type == NF4REG || info.type == NF4NAMEDATTR) {
  570.              DPRINTF(2, ("handle nfs41_open: FILE\n"));
  571.              if (args->create_opts & FILE_DIRECTORY_FILE) {
  572. -                DPRINTF(1, ("trying to open file '%s' as a directory\n",
  573. -                    state->path.path));
  574. +                DPRINTF(1,
  575. +                    ("trying to open file state->path.path='%s',"
  576. +                    "info.type=%d as a directory\n",
  577. +                    state->path.path, (int)info.type));
  578.                  /*
  579.                   * Notes:
  580.                   * - SMB returns |STATUS_OBJECT_TYPE_MISMATCH|
  581.                   * while NTFS returns |STATUS_NOT_A_DIRECTORY|
  582.                   * - See |map_open_errors()| for the mapping to
  583.                   * |STATUS_*|
  584. +                 * - We do not return |ERROR_DIRECTORY| when opening a Win32
  585. +                 * stream
  586.                   */
  587. -                status = ERROR_DIRECTORY;
  588. -                goto out_free_state;
  589. +                if (memchr(state->path.path, ':', state->path.len) == NULL) {
  590. +                    status = ERROR_DIRECTORY;
  591. +                    goto out_free_state;
  592. +                }
  593.              }
  594.          } else if (info.type == NF4LNK) {
  595.              DPRINTF(2, ("handle nfs41_open: SYMLINK\n"));
  596. @@ -1324,7 +1334,7 @@ supersede_retry:
  597.  
  598.  #ifdef DEBUG_OPEN_SPARSE_FILES
  599.      if ((status == 0) &&
  600. -        (info.type == NF4REG) &&
  601. +        (info.type == NF4REG | info.type == NF4NAMEDATTR) &&
  602.          (state->session->client->root->supports_nfs42_seek)) {
  603.          //debug_list_sparsefile_holes(state);
  604.          debug_list_sparsefile_datasections(state);
  605. @@ -1524,7 +1534,7 @@ static int handle_close(void *deamon_context, nfs41_upcall *upcall)
  606.      nfs41_open_state *state = upcall->state_ref;
  607.  
  608.      /* return associated file layouts if necessary */
  609. -    if (state->type == NF4REG)
  610. +    if (state->type == NF4REG || state->type == NF4NAMEDATTR)
  611.          pnfs_layout_state_close(state->session, state, args->remove);
  612.  
  613.      if (state->srv_open == args->srv_open)
  614. diff --git a/daemon/winstreams.c b/daemon/winstreams.c
  615. new file mode 100644
  616. index 0000000..6e09ccf
  617. --- /dev/null
  618. +++ b/daemon/winstreams.c
  619. @@ -0,0 +1,200 @@
  620. +/* NFSv4.1 client for Windows
  621. + * Copyright (C) 2025 Roland Mainz <roland.mainz@nrubsig.org>
  622. + *
  623. + * Roland Mainz <roland.mainz@nrubsig.org>
  624. + *
  625. + * This library is free software; you can redistribute it and/or modify it
  626. + * under the terms of the GNU Lesser General Public License as published by
  627. + * the Free Software Foundation; either version 2.1 of the License, or (at
  628. + * your option) any later version.
  629. + *
  630. + * This library is distributed in the hope that it will be useful, but
  631. + * without any warranty; without even the implied warranty of merchantability
  632. + * or fitness for a particular purpose.  See the GNU Lesser General Public
  633. + * License for more details.
  634. + *
  635. + * You should have received a copy of the GNU Lesser General Public License
  636. + * along with this library; if not, write to the Free Software Foundation,
  637. + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  638. + */
  639. +
  640. +#include <stdlib.h>
  641. +#include <stdbool.h>
  642. +
  643. +#include "nfs41_build_features.h"
  644. +#include "winstreams.h"
  645. +#include "daemon_debug.h"
  646. +#include "util.h"
  647. +
  648. +#ifdef NFS41_WINSTREAMS_SUPPORT
  649. +/*
  650. + * |WIN_NFS4_STREAMS_NAME_PREFIX| - Prefix for Windows streams in NFSv4
  651. + * XATTR (extended attributes) namespace
  652. + *
  653. + * We need such a prefix to avoid colliding with other users
  654. + * in the NFSv4 XATTR namespace - for example SUN Microsystrems
  655. + * (Solaris, Illumos, ...) uses "SUNWattr_" as prefix, and setting
  656. + * such attributes can cause data corruption (or in case of
  657. + * "SUNWattr_ro" will fail, because the attribute file is
  658. + * read-only).
  659. + */
  660. +#define WIN_NFS4_STREAMS_NAME_PREFIX "win32.streams."
  661. +#define WIN_NFS4_STREAMS_NAME_PREFIX_LEN (14)
  662. +
  663. +static
  664. +int parse_stream_filename_streamname_streamtype(
  665. +    const char *restrict path,
  666. +    char *restrict filename,
  667. +    char *restrict streamname,
  668. +    char *restrict streamtype)
  669. +{
  670. +    const char *sep;
  671. +    const char *base;
  672. +    int colon_count;
  673. +    const char *c1;
  674. +    const char *c2;
  675. +    const char *p;
  676. +    size_t len_prefix;
  677. +    size_t len_sn;
  678. +
  679. +    filename[0]   = '\0';
  680. +    streamname[0] = '\0';
  681. +    streamtype[0] = '\0';
  682. +
  683. +    /* Take the last component after the last backslash */
  684. +    sep  = strrchr(path, '\\');
  685. +    base = sep ? (sep + 1) : path;
  686. +
  687. +    /* Count colons and error out if >= 3 in the final component */
  688. +    colon_count = 0;
  689. +    for (p = base ; *p ; p++) {
  690. +        if (*p == ':') {
  691. +            colon_count++;
  692. +            if (colon_count >= 3)
  693. +                return ERROR_INVALID_NAME;
  694. +        }
  695. +    }
  696. +
  697. +    /* Find first colon (if any) */
  698. +    c1 = strchr(base, ':');
  699. +    if (c1 == NULL) {
  700. +        /* No colon: filename is the entire path */
  701. +        (void)strcpy(filename, path);
  702. +        return NO_ERROR;
  703. +    }
  704. +
  705. +    /* First colon exists: filename before ':' must be non-empty */
  706. +    if (c1 == base)
  707. +        return ERROR_INVALID_NAME;
  708. +
  709. +    /*
  710. +     * One colon case
  711. +     */
  712. +    if (colon_count == 1) {
  713. +        if (*(c1 + 1) == '\0')
  714. +            return ERROR_INVALID_NAME; /* "filename:" => invalid */
  715. +
  716. +        /* filename = full path up to first colon in base */
  717. +        len_prefix = (size_t)(c1 - path);
  718. +        (void)memcpy(filename, path, len_prefix);
  719. +        filename[len_prefix] = '\0';
  720. +
  721. +        /* streamname after first colon */
  722. +        (void)strcpy(streamname, c1 + 1);
  723. +        /* streamtype stays empty */
  724. +        return NO_ERROR;
  725. +    }
  726. +
  727. +    /*
  728. +     * Two colons case
  729. +     */
  730. +    c2 = strchr(c1 + 1, ':');
  731. +    if (c2 == NULL) {
  732. +        /* Should not happen when colon_count == 2 */
  733. +        return ERROR_INVALID_NAME;
  734. +    }
  735. +
  736. +    if (*(c2 + 1) == '\0')
  737. +        return ERROR_INVALID_NAME; /* type must be non-empty */
  738. +
  739. +    /* filename = full path up to first colon */
  740. +    len_prefix = (size_t)(c1 - path);
  741. +    (void)memcpy(filename, path, len_prefix);
  742. +    filename[len_prefix] = '\0';
  743. +
  744. +    /* streamname = [c1+1, c2] (may be empty, e.g., filename::$DATA) */
  745. +    len_sn = (size_t)(c2 - (c1 + 1));
  746. +    (void)memcpy(streamname, c1 + 1, len_sn);
  747. +    streamname[len_sn] = '\0';
  748. +
  749. +    /* streamtype = (c2+1 .. end) */
  750. +    (void)strcpy(streamtype, c2 + 1);
  751. +
  752. +    return NO_ERROR;
  753. +}
  754. +
  755. +int parse_win32stream_name(
  756. +    IN const char *restrict path,
  757. +    OUT bool *restrict is_stream,
  758. +    OUT char *restrict base_name,
  759. +    OUT char *restrict stream_name)
  760. +{
  761. +    int status;
  762. +    char filenamebuff[NFS41_MAX_PATH_LEN+1];
  763. +    char streamnamebuff[NFS41_MAX_COMPONENT_LEN+1];
  764. +    /* |streamtypebuff| must include space for prefix+suffix */
  765. +    char streamtypebuff[NFS41_MAX_COMPONENT_LEN+1+128];
  766. +    char *p;
  767. +
  768. +    status = parse_stream_filename_streamname_streamtype(path,
  769. +        filenamebuff, streamnamebuff, streamtypebuff);
  770. +    if (status) {
  771. +        eprintf("parse_win32stream_name: "
  772. +            "parsing for path='%s' failed, status=%d\n",
  773. +            path, status);
  774. +        return status;
  775. +    }
  776. +
  777. +    DPRINTF(2,
  778. +        ("parse_win32stream_name: "
  779. +        "parse_stream_filename_streamname_streamtype(path='%s') returned "
  780. +        "filenamebuff='%s', streamnamebuff='%s', streamtypebuff='%s'\n",
  781. +        path, filenamebuff, streamnamebuff, streamtypebuff));
  782. +
  783. +    if ((streamnamebuff[0] == '\0') && (streamtypebuff[0] == '\0')) {
  784. +        return ERROR_INVALID_NAME;
  785. +    }
  786. +
  787. +    /* We do not support any stream types except "$DATA" (yet) */
  788. +    if ((streamtypebuff[0] != '\0') &&
  789. +        ((_stricmp(streamtypebuff, "$DATA") != 0))) {
  790. +        eprintf("parse_win32stream_name: "
  791. +            "Unsupported stream type, path='%s', "
  792. +            "stream='%s', streamtype='%s'\n",
  793. +            path,
  794. +            streamnamebuff,
  795. +            streamtypebuff);
  796. +        return ERROR_INVALID_NAME;
  797. +    }
  798. +
  799. +    /* "foo::$DATA" refers to "foo" */
  800. +    if (streamnamebuff[0] == '\0') {
  801. +        *is_stream = true;
  802. +        (void)strcpy(base_name, filenamebuff);
  803. +        stream_name[0] = '\0';
  804. +        return NO_ERROR;
  805. +    }
  806. +
  807. +    /*
  808. +     * If we have a stream name, then add our NFS attr prefix for Windows
  809. +     * streams, and ":$DATA" as suffix
  810. +     */
  811. +    *is_stream = true;
  812. +    (void)strcpy(base_name, filenamebuff);
  813. +    p = stpcpy(stream_name, WIN_NFS4_STREAMS_NAME_PREFIX);
  814. +    p = stpcpy(p, streamnamebuff);
  815. +    (void)stpcpy(p, ":$DATA");
  816. +
  817. +    return NO_ERROR;
  818. +}
  819. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  820. diff --git a/daemon/winstreams.h b/daemon/winstreams.h
  821. new file mode 100644
  822. index 0000000..7d80036
  823. --- /dev/null
  824. +++ b/daemon/winstreams.h
  825. @@ -0,0 +1,41 @@
  826. +/* NFSv4.1 client for Windows
  827. + * Copyright (C) 2025 Roland Mainz <roland.mainz@nrubsig.org>
  828. + *
  829. + * Roland Mainz <roland.mainz@nrubsig.org>
  830. + *
  831. + * This library is free software; you can redistribute it and/or modify it
  832. + * under the terms of the GNU Lesser General Public License as published by
  833. + * the Free Software Foundation; either version 2.1 of the License, or (at
  834. + * your option) any later version.
  835. + *
  836. + * This library is distributed in the hope that it will be useful, but
  837. + * without any warranty; without even the implied warranty of merchantability
  838. + * or fitness for a particular purpose.  See the GNU Lesser General Public
  839. + * License for more details.
  840. + *
  841. + * You should have received a copy of the GNU Lesser General Public License
  842. + * along with this library; if not, write to the Free Software Foundation,
  843. + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  844. + */
  845. +
  846. +#ifndef __NFS41_DAEMON_WINSTREAMS_H__
  847. +#define __NFS41_DAEMON_WINSTREAMS_H__ 1
  848. +
  849. +#include <stdlib.h>
  850. +#include <stdbool.h>
  851. +
  852. +#include "nfs41_build_features.h"
  853. +#include "nfs41_types.h"
  854. +#include "from_kernel.h"
  855. +
  856. +#ifdef NFS41_WINSTREAMS_SUPPORT
  857. +
  858. +int parse_win32stream_name(
  859. +    IN const char *restrict path,
  860. +    OUT bool *restrict is_stream,
  861. +    OUT char *restrict base_name,
  862. +    OUT char *restrict stream_name);
  863. +
  864. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  865. +
  866. +#endif /* !__NFS41_DAEMON_WINSTREAMS_H__ */
  867. diff --git a/nfs41_build_features.h b/nfs41_build_features.h
  868. index fb744f5..3ee191a 100644
  869. --- a/nfs41_build_features.h
  870. +++ b/nfs41_build_features.h
  871. @@ -273,4 +273,10 @@
  872.   */
  873.  #define NFS41_DRIVER_ALLOW_CREATEFILE_ACLS 1
  874.  
  875. +/*
  876. + * |NFS41_WINSTREAMS_SUPPORT| - Enable Win32 named streams support using
  877. + * NFSv4.1 named attributes
  878. + */
  879. +#define NFS41_WINSTREAMS_SUPPORT 1
  880. +
  881.  #endif /* !_NFS41_DRIVER_BUILDFEATURES_ */
  882. diff --git a/sys/nfs41sys_fileinfo.c b/sys/nfs41sys_fileinfo.c
  883. index f871915..f5dd208 100644
  884. --- a/sys/nfs41sys_fileinfo.c
  885. +++ b/sys/nfs41sys_fileinfo.c
  886. @@ -398,6 +398,9 @@ NTSTATUS nfs41_QueryFileInformation(
  887.      case FileStatInformation:
  888.      case FileStatLxInformation:
  889.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  890. +#ifdef NFS41_WINSTREAMS_SUPPORT
  891. +    case FileStreamInformation:
  892. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  893.          break;
  894.      default:
  895.          print_error("nfs41_QueryFileInformation: "
  896. @@ -500,6 +503,9 @@ NTSTATUS nfs41_QueryFileInformation(
  897.          case FileStatInformation:
  898.          case FileStatLxInformation:
  899.  #endif /* NFS41_DRIVER_WSL_SUPPORT */
  900. +#ifdef NFS41_WINSTREAMS_SUPPORT
  901. +        case FileStreamInformation:
  902. +#endif /* NFS41_WINSTREAMS_SUPPORT */
  903.              break;
  904.          default:
  905.              print_error("nfs41_QueryFileInformation: "
  906. diff --git a/sys/nfs41sys_openclose.c b/sys/nfs41sys_openclose.c
  907. index 93c95b1..d9cc74f 100644
  908. --- a/sys/nfs41sys_openclose.c
  909. +++ b/sys/nfs41sys_openclose.c
  910. @@ -574,11 +574,13 @@ NTSTATUS check_nfs41_create_args(
  911.          goto out;
  912.      }
  913.  
  914. +#ifndef NFS41_WINSTREAMS_SUPPORT
  915.      if (isStream(SrvOpen->pAlreadyPrefixedName)) {
  916.          DbgP("nfs41_Create: Streams not supported (yet)\n");
  917.          status = STATUS_NOT_SUPPORTED;
  918.          goto out;
  919.      }
  920. +#endif /* !NFS41_WINSTREAMS_SUPPORT */
  921.  
  922.      if (pVNetRootContext->read_only &&
  923.              (params->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
  924. diff --git a/tests/manual_testing.txt b/tests/manual_testing.txt
  925. index a520bfb..a653a07 100644
  926. --- a/tests/manual_testing.txt
  927. +++ b/tests/manual_testing.txt
  928. @@ -1,5 +1,5 @@
  929.  #
  930. -# ms-nfs41-client manual testing sequence, 2025-12-12
  931. +# ms-nfs41-client manual testing sequence, 2025-12-31
  932.  #
  933.  # Draft version, needs to be turned into automated tests
  934.  # if possible
  935. @@ -218,6 +218,20 @@ $ rm -f d1 d2 d1.diff ; printf '1\n2\n' >d1 ; cp d1 d2 ; printf '3\n' >> d2 ; di
  936.  The test should print "# test OK"
  937.  
  938.  
  939. +#
  940. +# Tests for Win32 streams
  941. +#
  942. +
  943. +# this test should print $'dir_line1\ndir_line2\n'
  944. +rm -Rf dir1 && mkdir dir1 && cmd /c 'echo dir_line1 >>dir1:dirstr1 & echo dir_line2 >>dir1:dirstr1 & cat <dir1:dirstr1'
  945. +# these three tests should print $'file1data\nfile1_line1\nfile1_line2\n'
  946. +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'
  947. +rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:filestr1 & echo file1_line2 >>file1:filestr1:$DATA & type file1 & type file1:filestr1'
  948. +rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:filestr1 & echo file1_line2 >>file1:filestr1:$DATA & type file1::$DATA & type file1:filestr1:$DATA'
  949. +
  950. +# FIXME: tests for stream removal and enumeration
  951. +
  952. +
  953.  #
  954.  # Tests for Cycgwin/UWIN/SFU Nfs3Attr EA-based local uid/gid
  955.  #

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