pastebin - collaborative debugging tool
nrubsig.kpaste.net RSS

Difference between
modified post 7821a by Anonymous on Wed 31st Dec 2025 00:10
original post a2b00a3 by Anonymous on Sat 27th Dec 2025 18:55
Download diff
Show old version | new version | both versions

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

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