pastebin - collaborative debugging tool
nrubsig.kpaste.net RSS

Difference between
modified post 02a1 by Anonymous on Fri 2nd Jan 2026 18:22
original post 7821a by Anonymous on Wed 31st Dec 2025 00:10
Download diff
Show old version | new version | both versions

    
11
diff --git a/build.vc19/nfsd/nfsd.vcxproj b/build.vc19/nfsd/nfsd.vcxproj
22
index 8589a2a..face630 100644
33
--- a/build.vc19/nfsd/nfsd.vcxproj
44
+++ b/build.vc19/nfsd/nfsd.vcxproj
55
@@ -331,6 +331,7 @@
66
     <ClCompile Include="..\..\daemon\upcall.c" />
77
     <ClCompile Include="..\..\daemon\util.c" />
88
     <ClCompile Include="..\..\daemon\volume.c" />
99
+    <ClCompile Include="..\..\daemon\winstreams.c" />
1010
   </ItemGroup>
1111
   <ItemGroup>
1212
     <ClInclude Include="..\..\daemon\accesstoken.h" />
1313
@@ -356,6 +357,7 @@
1414
     <ClInclude Include="..\..\daemon\tree.h" />
1515
     <ClInclude Include="..\..\daemon\upcall.h" />
1616
     <ClInclude Include="..\..\daemon\util.h" />
1717
+    <ClInclude Include="..\..\daemon\winstreams.h" />
1818
     <ClInclude Include="..\..\include\from_kernel.h" />
1919
     <ClInclude Include="..\..\include\nfs_ea.h" />
2020
   </ItemGroup>
2121
diff --git a/build.vc19/nfsd/nfsd.vcxproj.filters b/build.vc19/nfsd/nfsd.vcxproj.filters
2222
index 134404f..8e7c552 100644
2323
--- a/build.vc19/nfsd/nfsd.vcxproj.filters
2424
+++ b/build.vc19/nfsd/nfsd.vcxproj.filters
2525
@@ -147,6 +147,9 @@
2626
     <ClCompile Include="..\..\daemon\volume.c">
2727
       <Filter>Source Files</Filter>
2828
     </ClCompile>
2929
+    <ClCompile Include="..\..\daemon\winstreams.c">
3030
+      <Filter>Source Files</Filter>
3131
+    </ClCompile>
3232
     <ClCompile Include="..\..\daemon\accesstoken.c">
3333
       <Filter>Source Files</Filter>
3434
     </ClCompile>
3535
@@ -227,5 +230,8 @@
3636
     <ClInclude Include="..\..\daemon\accesstoken.h">
3737
       <Filter>Header Files</Filter>
3838
     </ClInclude>
3939
+    <ClInclude Include="..\..\daemon\winstreams.h">
4040
+      <Filter>Header Files</Filter>
4141
+    </ClInclude>
4242
   </ItemGroup>
4343
 </Project>
4444
\ No newline at end of file
4545
diff --git a/daemon/fileinfoutil.c b/daemon/fileinfoutil.c
46-
index 0ed40c8..bbd6d28 100644
46+
index 0ed40c8..d745fd2 100644
4747
--- a/daemon/fileinfoutil.c
4848
+++ b/daemon/fileinfoutil.c
49+
@@ -1,6 +1,6 @@
4957
@@ -59,7 +59,7 @@ ULONG nfs_file_info_to_attributes(
5058
         if (info->symlink_dir)
5159
             attrs |= FILE_ATTRIBUTE_DIRECTORY;
5260
     }
5361
-    else if (info->type == NF4REG) {
5462
+    else if ((info->type == NF4REG) || (info->type == NF4NAMEDATTR)) {
5563
         if (superblock->sparse_file_support) {
5664
             /* FIXME: What about pNFS ? */
5765
             attrs |= FILE_ATTRIBUTE_SPARSE_FILE;
5866
@@ -383,6 +383,7 @@ void nfs_to_stat_lx_info(
5967
     stat_lx_out->LxMode = 0UL;
6068
     switch(info->type) {
6169
         case NF4REG:
6270
+        case NF4NAMEDATTR:
6371
             stat_lx_out->LxMode |= LX_MODE_S_IFREG;
6472
             break;
6573
         case NF4DIR:
6674
diff --git a/daemon/getattr.c b/daemon/getattr.c
67-
index 4744285..1069a8d 100644
75+
index 4744285..63b985e 100644
6876
--- a/daemon/getattr.c
6977
+++ b/daemon/getattr.c
70-
@@ -204,6 +204,9 @@ static int handle_getattr(void *daemon_context, nfs41_upcall *upcall)
78+
@@ -1,6 +1,6 @@
71-
             &args->stat_lx_info);
79+
 /* NFSv4.1 client for Windows
72-
         break;
80+
  * Copyright (C) 2012 The Regents of the University of Michigan
73-
 #endif /* NFS41_DRIVER_WSL_SUPPORT */
81+
- * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
82+
+ * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
7490
+#ifdef NFS41_WINSTREAMS_SUPPORT
75-
+    case FileStreamInformation:
91+
+#include "winstreams.h"
7692
+#endif /* NFS41_WINSTREAMS_SUPPORT */
77-
     default:
93+
78-
         eprintf("handle_getattr(state->path.path='%s'): "
94+
79-
             "unhandled file query class %d\n",
95+
 int nfs41_cached_getattr(
80-
diff --git a/daemon/lookup.c b/daemon/lookup.c
96+
@@ -204,6 +207,31 @@ static int handle_getattr(void *daemon_context, nfs41_upcall *upcall)
81-
index 3e9e591..a7fb7e7 100644
97+
             &args->stat_lx_info);
82-
--- a/daemon/lookup.c
98+
         break;
83-
+++ b/daemon/lookup.c
99+
 #endif /* NFS41_DRIVER_WSL_SUPPORT */
88100
+#ifdef NFS41_WINSTREAMS_SUPPORT
89-
+#include "winstreams.h"
101+
+    case FileStreamInformation:
90-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
102+
+        args->stream_info_list = NULL;
91-
103+
+
92-
104+
+        nfs41_path_fh parent = { 0 };
93-
 #define LULVL 2 /* dprintf level for lookup logging */
105+
+
94-
@@ -502,6 +505,156 @@ int nfs41_lookup(
106+
+        status = nfs41_rpc_openattr(state->session, &state->file, FALSE, &parent.fh);
95-
107+
+        /* No named attribute directory ? */
96-
     DPRINTF(LULVL, ("--> nfs41_lookup('%s')\n", path.path));
108+
+        if (status == NFS4ERR_NOENT) {
97-
109+
+            DPRINTF(0, ("FileStreamInformation: no named attribute directory for '%s'\n", state->file.name.name));
98-
+#ifdef NFS41_WINSTREAMS_SUPPORT
110+
+            /* FIXME: We should return a default "file::$DATA" entry */
99-
+    /* Fast check for potential stream: Look for a colon */
111+
+        } else if (status) {
100-
+    if (memchr(path_inout->path, ':', path_inout->len)) {
112+
+            eprintf("FileStreamInformation: "
101-
+        char base_name[NFS41_MAX_PATH_LEN+1];
113+
+                "nfs41_rpc_openattr() failed with '%s'\n",
102-
+        char stream_name[NFS41_MAX_COMPONENT_LEN+1];
114+
+                nfs_error_string(status));
103-
+        bool is_stream = false;
115+
+            status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
116+
+            goto out;
104118
+
105-
+        /* Parse the stream syntax */
119+
+        status = get_stream_list(state,
106-
+        status = parse_win32stream_name(path.path, &is_stream, base_name, stream_name);
120+
+            &parent,
107-
+        if (status)
121+
+            &args->stream_info_list,
108-
+            goto out;
122+
+            &args->stream_info_list_size);
109-
+
123+
+        break;
110-
+        if (is_stream) {
124+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
111-
+            if (stream_name[0] == '\0') {
125+
     default:
112-
+                /* "foo::$DATA" case -> treat as "foo" */
126+
         eprintf("handle_getattr(state->path.path='%s'): "
113-
+                DPRINTF(2, ("nfs41_lookup: 'foo::$DATA' case -> treat as 'foo'\n"));
127+
             "unhandled file query class %d\n",
114-
+                size_t base_len = strlen(base_name);
128+
@@ -222,7 +250,7 @@ static int marshall_getattr(
115-
+
129+
     nfs41_upcall *restrict upcall)
116-
+                (void)memcpy(path.path, base_name, base_len);
130+
 {
117-
+                path.path[base_len] = '\0';
131+
     int status;
118-
+                path.len = (unsigned short)base_len;
132+
-    const getattr_upcall_args *args = &upcall->args.getattr;
119-
+                path_pos = path.path;
133+
+    getattr_upcall_args *args = &upcall->args.getattr;
120-
+                path_end = path.path + path.len;
134+
     uint32_t info_len;
121-
+
135+
122-
+                /* Continue to normal lookup with stripped name */
136+
     switch (args->query_class) {
123-
+            } else {
137+
@@ -292,6 +320,15 @@ static int marshall_getattr(
124-
+                /* "foo:bar" case */
138+
         if (status) goto out;
125-
+                DPRINTF(2, ("nfs41_lookup: base_name='%s', stream_name='%s'\n",
139+
         break;
126-
+                    base_name, stream_name));
140+
 #endif /* NFS41_DRIVER_WSL_SUPPORT */
127-
+
141+
+#ifdef NFS41_WINSTREAMS_SUPPORT
128-
+                /*
142+
+    case FileStreamInformation:
129-
+                 * Recursively lookup the base file ...
143+
+        info_len = args->stream_info_list_size;
130-
+                 */
144+
+        status = safe_write(&buffer, length, &info_len, sizeof(info_len));
131-
+                nfs41_abs_path base_path = path;
145+
+        if (status) goto out;
132-
+                size_t base_len = strlen(base_name);
146+
+        status = safe_write(&buffer, length, args->stream_info_list, info_len);
133-
+
147+
+        if (status) goto out;
134-
+                (void)memcpy(base_path.path, base_name, base_len);
148+
+        break;
135-
+                base_path.path[base_len] = '\0';
149+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
136-
+                base_path.len = (unsigned short)base_len;
150+
     default:
137-
+
151+
         eprintf("marshall_getattr: unknown file query class %d\n",
138-
+                nfs41_path_fh attr_target;
152+
             args->query_class);
139-
+                if (target_out == NULL) {
153+
@@ -302,6 +339,13 @@ static int marshall_getattr(
140-
+                    target_out = &attr_target;
154+
     if (status) goto out;
141-
+                }
155+
     DPRINTF(1, ("NFS41_SYSOP_FILE_QUERY: downcall changattr=%llu\n", args->ctime));
156+
 out:
142163
+
143-
+                status = nfs41_lookup(root, session, casesensitive, &base_path,
164+
     return status;
144-
+                    parent_out, target_out, info_out, session_out);
165+
 }
145-
+                if (status)
166+
146-
+                    goto out;
167+
diff --git a/daemon/lookup.c b/daemon/lookup.c
147-
+
168+
index 3e9e591..39074e2 100644
148-
+                /*
169+
--- a/daemon/lookup.c
149-
+                 * ... and then lookup the NFSv4 named attribute
170+
+++ b/daemon/lookup.c
150-
+                 */
171+
@@ -1,6 +1,6 @@
151-
+                nfs41_session *lookup_session;
172+
 /* NFSv4.1 client for Windows
152-
+                nfs41_compound compound;
173+
  * Copyright (C) 2012 The Regents of the University of Michigan
153-
+                nfs_argop4 argops[8];
174+
- * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
154-
+                nfs_resop4 resops[8];
175+
+ * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
155-
+                nfs41_sequence_args sequence_args;
176+
  *
156-
+                nfs41_sequence_res sequence_res;
177+
  * Olga Kornievskaia <aglo@umich.edu>
157-
+                nfs41_openattr_args openattr_args;
178+
  * Casey Bodley <cbodley@umich.edu>
158-
+                nfs41_openattr_res openattr_res;
179+
@@ -25,12 +25,16 @@
159-
+                nfs41_putfh_args putfh_args = { 0 };
180+
 #include <strsafe.h>
160-
+                nfs41_putfh_res putfh_res;
181+
 #include <time.h>
161-
+                nfs41_lookup_args lookup_args = { 0 };
182+
162-
+                nfs41_lookup_res lookup_res;
183+
+#include "nfs41_build_features.h"
163-
+                nfs41_getfh_res getfh_res;
184+
 #include "nfs41_compound.h"
164-
+                nfs41_getattr_args getattr_args = { 0 };
185+
 #include "nfs41_ops.h"
165-
+                nfs41_getattr_res getattr_res = { 0 };
186+
 #include "name_cache.h"
166-
+                nfs41_component stream_comp = {
187+
 #include "fileinfoutil.h"
167-
+                    .name = stream_name,
188+
 #include "util.h"
168-
+                    .len = (unsigned short)strlen(stream_name)
189+
 #include "daemon_debug.h"
169-
+                };
190+
+#ifdef NFS41_WINSTREAMS_SUPPORT
170-
+                /* Minimal attributes for stream */
191+
+#include "winstreams.h"
171-
+                bitmap4 attr_request = {
192+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
172-
+                    .count = 2,
193+
173-
+                    .arr = {
194+
174-
+                        [0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE |
195+
 #define LULVL 2 /* dprintf level for lookup logging */
175-
+                            FATTR4_WORD0_SIZE | FATTR4_WORD0_FSID |
196+
@@ -502,6 +506,156 @@ int nfs41_lookup(
176-
+                            FATTR4_WORD0_FILEID,
197+
177-
+                        [1] = FATTR4_WORD1_MODE | FATTR4_WORD1_SPACE_USED |
198+
     DPRINTF(LULVL, ("--> nfs41_lookup('%s')\n", path.path));
178-
+                            FATTR4_WORD1_TIME_ACCESS |
199+
179-
+                            FATTR4_WORD1_TIME_CREATE |
200+
+#ifdef NFS41_WINSTREAMS_SUPPORT
180-
+                            FATTR4_WORD1_TIME_MODIFY |
201+
+    /* Fast check for potential stream: Look for a colon */
181-
+                            FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP,
202+
+    if (is_stream_path(path_inout)) {
182-
+                        [2] = 0
203+
+        char base_name[NFS41_MAX_PATH_LEN+1];
183-
+                    }
204+
+        char stream_name[NFS41_MAX_COMPONENT_LEN+1];
184-
+                };
205+
+        bool is_stream = false;
185206
+
186-
+                DPRINTF(1,
207+
+        /* Parse the stream syntax */
187-
+                    ("nfs41_lookup: "
208+
+        status = parse_win32stream_name(path.path, &is_stream, base_name, stream_name);
188-
+                    "Looking up base_name='%s'/stream_name='%s'\n",
209+
+        if (status)
189-
+                    base_name, stream_name));
210+
+            goto out;
190211
+
191-
+                /* Use the session returned by the base lookup if applicable */
212+
+        if (is_stream) {
192-
+                if ((session_out != NULL) && (*session_out != NULL))
213+
+            if (stream_name[0] == '\0') {
193-
+                    lookup_session = *session_out;
214+
+                /* "foo::$DATA" case -> treat as "foo" */
194-
+                else
215+
+                DPRINTF(2, ("nfs41_lookup: 'foo::$DATA' case -> treat as 'foo'\n"));
195-
+                    lookup_session = session;
216+
+                size_t base_len = strlen(base_name);
196217
+
197-
+                compound_init(&compound,
218+
+                (void)memcpy(path.path, base_name, base_len);
198-
+                    lookup_session->client->root->nfsminorvers,
219+
+                path.path[base_len] = '\0';
199-
+                    argops, resops, "lookup_win32stream");
220+
+                path.len = (unsigned short)base_len;
221+
+                path_pos = path.path;
200223
+
201-
+                compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
224+
+                /* Continue to normal lookup with stripped name */
202-
+                nfs41_session_sequence(&sequence_args, lookup_session, 0);
225+
+            } else {
226+
+                /* "foo:bar" case */
203229
+
204-
+                compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
230+
+                /*
205-
+                putfh_args.file = target_out;
231+
+                 * Recursively lookup the base file ...
206-
+                putfh_args.in_recovery = FALSE;
232+
+                 */
233+
+                nfs41_abs_path base_path = path;
207235
+
208-
+                compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
236+
+                (void)memcpy(base_path.path, base_name, base_len);
209-
+                openattr_args.createdir = FALSE;
237+
+                base_path.path[base_len] = '\0';
238+
+                base_path.len = (unsigned short)base_len;
210239
+
211-
+                compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
240+
+                nfs41_path_fh attr_target;
212-
+                lookup_args.name = &stream_comp;
241+
+                if (target_out == NULL) {
242+
+                    target_out = &attr_target;
213244
+
214-
+                compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
245+
+                status = nfs41_lookup(root, session, casesensitive, &base_path,
215-
+                getfh_res.fh = &target_out->fh;
246+
+                    parent_out, target_out, info_out, session_out);
247+
+                if (status)
216249
+
217-
+                compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
250+
+                /*
218-
+                getattr_args.attr_request = &attr_request;
251+
+                 * ... and then lookup the NFSv4 named attribute
219-
+                /* If caller wanted info, fill it; else dummy */
252+
+                 */
220-
+                nfs41_file_info dummy_info;
253+
+                nfs41_session *lookup_session;
221-
+                if (info_out)
254+
+                nfs41_compound compound;
222-
+                    getattr_res.info = info_out;
255+
+                nfs_argop4 argops[8];
223-
+                else
256+
+                nfs_resop4 resops[8];
224-
+                    getattr_res.info = &dummy_info;
257+
+                nfs41_sequence_args sequence_args;
225-
+                getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT_ATTR;
258+
+                nfs41_sequence_res sequence_res;
226-
+
259+
+                nfs41_openattr_args openattr_args;
227-
+                status = compound_encode_send_decode(lookup_session, &compound, TRUE);
260+
+                nfs41_openattr_res openattr_res;
228-
+
261+
+                nfs41_putfh_args putfh_args = { 0 };
229-
+                if (status == 0)
262+
+                nfs41_putfh_res putfh_res;
230-
+                    status = compound.res.status;
263+
+                nfs41_lookup_args lookup_args = { 0 };
231-
+
264+
+                nfs41_lookup_res lookup_res;
232-
+                if (status == 0) {
265+
+                nfs41_getfh_res getfh_res;
233-
+                    /* We MUST return a |NF4NAMEDATTR|, and never a |NF4ATTRDIR| */
266+
+                nfs41_getattr_args getattr_args = { 0 };
234-
+                    EASSERT(getattr_res.info->type != NF4ATTRDIR);
267+
+                nfs41_getattr_res getattr_res = { 0 };
235-
+                    EASSERT(getattr_res.info->type == NF4NAMEDATTR);
268+
+                nfs41_component stream_comp = {
236-
+                }
269+
+                    .name = stream_name,
237-
+                else {
270+
+                    .len = (unsigned short)strlen(stream_name)
238-
+                    DPRINTF(1, ("nfs41_lookup: failed for attr, status=%d\n",
271+
+                };
239-
+                        (int)status));
272+
+                /* Minimal attributes for stream */
240-
+                }
273+
+                bitmap4 attr_request = {
241-
+
274+
+                    .count = 2,
242-
+                goto out;
275+
+                    .arr = {
243-
+            }
276+
+                        [0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE |
244-
+        }
277+
+                            FATTR4_WORD0_SIZE | FATTR4_WORD0_FSID |
245-
+    }
278+
+                            FATTR4_WORD0_FILEID,
246-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
279+
+                        [1] = FATTR4_WORD1_MODE | FATTR4_WORD1_SPACE_USED |
280+
+                            FATTR4_WORD1_TIME_ACCESS |
247287
+
248-
     if (parent_out == NULL) parent_out = &parent;
288+
+                DPRINTF(1,
249-
     if (target_out == NULL) target_out = &target;
289+
+                    ("nfs41_lookup: "
250-
     parent_out->fh.len = target_out->fh.len = 0;
290+
+                    "Looking up base_name='%s'/stream_name='%s'\n",
251-
diff --git a/daemon/nfs41_ops.c b/daemon/nfs41_ops.c
291+
+                    base_name, stream_name));
252-
index 9012db4..ebe0bd5 100644
292+
+
253-
--- a/daemon/nfs41_ops.c
293+
+                /* Use the session returned by the base lookup if applicable */
254-
+++ b/daemon/nfs41_ops.c
294+
+                if ((session_out != NULL) && (*session_out != NULL))
255-
@@ -36,6 +36,9 @@
295+
+                    lookup_session = *session_out;
256-
 #include "delegation.h"
296+
+                else
257-
 #include "daemon_debug.h"
297+
+                    lookup_session = session;
258-
 #include "util.h"
298+
+
259-
+#ifdef NFS41_WINSTREAMS_SUPPORT
299+
+                compound_init(&compound,
260-
+#include "winstreams.h"
300+
+                    lookup_session->client->root->nfsminorvers,
261-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
301+
+                    argops, resops, "lookup_win32stream");
262-
302+
+
263-
 #ifdef NFS41_DRIVER_STABILITY_HACKS
303+
+                compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
264-
 /*
304+
+                nfs41_session_sequence(&sequence_args, lookup_session, 0);
265-
@@ -453,8 +456,8 @@ int nfs41_open(
305+
+
266-
 {
306+
+                compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
267-
     int status;
307+
+                putfh_args.file = target_out;
268-
     nfs41_compound compound;
308+
+                putfh_args.in_recovery = FALSE;
269-
-    nfs_argop4 argops[8];
309+
+
270-
-    nfs_resop4 resops[8];
310+
+                compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
271-
+    nfs_argop4 argops[12];
311+
+                openattr_args.createdir = FALSE;
272-
+    nfs_resop4 resops[12];
312+
+
273-
     nfs41_sequence_args sequence_args;
313+
+                compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
274-
     nfs41_sequence_res sequence_res;
314+
+                lookup_args.name = &stream_comp;
275-
     nfs41_putfh_args putfh_args[2];
315+
+
276-
@@ -471,10 +474,39 @@ int nfs41_open(
316+
+                compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
277-
     bool_t current_fh_is_dir;
317+
+                getfh_res.fh = &target_out->fh;
278-
     bool_t already_delegated = delegation->type == OPEN_DELEGATE_READ
318+
+
279-
         || delegation->type == OPEN_DELEGATE_WRITE;
319+
+                compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
280-
+#ifdef NFS41_WINSTREAMS_SUPPORT
320+
+                getattr_args.attr_request = &attr_request;
281-
+    char base_buf[NFS41_MAX_PATH_LEN+1];
321+
+                /* If caller wanted info, fill it; else dummy */
282-
+    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
322+
+                nfs41_file_info dummy_info;
283-
+    bool is_stream = false;
323+
+                if (info_out)
284-
+    nfs41_component base_comp = {0};
324+
+                    getattr_res.info = info_out;
285-
+    nfs41_component stream_comp = {0};
325+
+                else
286-
+    nfs41_lookup_args lookup_args;
326+
+                    getattr_res.info = &dummy_info;
287-
+    nfs41_lookup_res lookup_res;
327+
+                getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT_ATTR;
288-
+    nfs41_openattr_args openattr_args;
328+
+
289-
+    nfs41_openattr_res openattr_res;
329+
+                status = compound_encode_send_decode(lookup_session, &compound, TRUE);
290-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
330+
+
291-
331+
+                if (status == 0)
292-
     EASSERT_IS_VALID_NON_NULL_PTR(parent);
332+
+                    status = compound.res.status;
302333
+
303-
+            if (is_stream) {
334+
+                if (status == 0) {
304-
+                base_comp.name = base_buf;
335+
+                    /* We MUST return a |NF4NAMEDATTR|, and never a |NF4ATTRDIR| */
305-
+                base_comp.len = (unsigned short)strlen(base_buf);
336+
+                    EASSERT(getattr_res.info->type != NF4ATTRDIR);
306-
+                stream_comp.name = stream_buf;
337+
+                    EASSERT(getattr_res.info->type == NF4NAMEDATTR);
307-
+                stream_comp.len = (unsigned short)strlen(stream_buf);
338+
+                }
339+
+                else {
308345
+            }
309346
+        }
310347
+    }
311348
+#endif /* NFS41_WINSTREAMS_SUPPORT */
312349
+
313-
     /* depending on the claim type, OPEN expects CURRENT_FH set
350+
     if (parent_out == NULL) parent_out = &parent;
314-
      * to either the parent directory, or to the file itself */
351+
     if (target_out == NULL) target_out = &target;
315-
     switch (claim->claim) {
352+
     parent_out->fh.len = target_out->fh.len = 0;
316-
@@ -524,6 +556,16 @@ int nfs41_open(
353+
diff --git a/daemon/nfs41_ops.c b/daemon/nfs41_ops.c
317-
         putfh_args[0].in_recovery = 0;
354+
index 9012db4..6a1a395 100644
318-
     }
355+
--- a/daemon/nfs41_ops.c
319-
356+
+++ b/daemon/nfs41_ops.c
320-
+#ifdef NFS41_WINSTREAMS_SUPPORT
357+
@@ -1,6 +1,6 @@
321-
+    if (is_stream && (stream_comp.len > 0)) {
358+
 /* NFSv4.1 client for Windows
322-
+        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
359+
  * Copyright (C) 2012 The Regents of the University of Michigan
323-
+        lookup_args.name = &base_comp;
360+
- * Copyright (C) 2024-2025 Roland Mainz <roland.mainz@nrubsig.org>
324-
+        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
361+
+ * Copyright (C) 2024-2026 Roland Mainz <roland.mainz@nrubsig.org>
325-
+        /* If we are creating the stream, we should allow creating the attr dir */
362+
  *
326-
+        openattr_args.createdir = (create == OPEN4_CREATE) ? TRUE : FALSE;
363+
  * Olga Kornievskaia <aglo@umich.edu>
327-
+    }
364+
  * Casey Bodley <cbodley@umich.edu>
365+
@@ -36,6 +36,9 @@
328371
+#endif /* NFS41_WINSTREAMS_SUPPORT */
329-
+
372+
330-
     compound_add_op(&compound, OP_OPEN, &open_args, &open_res);
373+
 #ifdef NFS41_DRIVER_STABILITY_HACKS
331-
     open_args.seqid = 0;
374+
 /*
332-
 #ifdef DISABLE_FILE_DELEGATIONS
375+
@@ -453,8 +456,8 @@ int nfs41_open(
333-
@@ -549,6 +591,27 @@ int nfs41_open(
376+
 {
334-
             parent->fh.superblock, &createattrs->attrmask);
377+
     int status;
335-
     }
378+
     nfs41_compound compound;
336-
     open_args.claim = claim;
379+
-    nfs_argop4 argops[8];
337-
+
380+
-    nfs_resop4 resops[8];
381+
+    nfs_argop4 argops[12];
338390
+#ifdef NFS41_WINSTREAMS_SUPPORT
339-
+    if (is_stream &&
391+
+    char base_buf[NFS41_MAX_PATH_LEN+1];
340-
+        ((claim->claim == CLAIM_NULL) ||
392+
+    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
341-
+            (claim->claim == CLAIM_DELEGATE_CUR))) {
393+
+    bool is_stream = false;
342-
+        if (claim->claim == CLAIM_NULL) {
394+
+    nfs41_component base_comp = {0};
343-
+            if (stream_comp.len > 0)
395+
+    nfs41_component stream_comp = {0};
344-
+                claim->u.null.filename = &stream_comp;
396+
+    nfs41_lookup_args lookup_args;
345-
+            else
397+
+    nfs41_lookup_res lookup_res;
346-
+                claim->u.null.filename = &base_comp;
398+
+    nfs41_openattr_args openattr_args;
347-
+        }
399+
+    nfs41_openattr_res openattr_res;
356400
+#endif /* NFS41_WINSTREAMS_SUPPORT */
357-
+
358-
     open_res.resok4.stateid = stateid;
359-
     open_res.resok4.delegation = delegation;
360401
361-
@@ -582,6 +645,7 @@ int nfs41_open(
402+
     EASSERT_IS_VALID_NON_NULL_PTR(parent);
362-
     if (compound_error(status = compound.res.status))
403+
     EASSERT_IS_VALID_NON_NULL_PTR(parent->fh.superblock);
364404
365-
+    /* This can happen if |nfs41_open()| is called by the EA code */
405+
+#ifdef NFS41_WINSTREAMS_SUPPORT
366-
     if (dir_info.type == NF4ATTRDIR) {
406+
+    if (file->name.name) {
367-
         file->fh.superblock = parent->fh.superblock;
407+
+        if (is_stream_path_fh(file)) {
368-
         goto out;
408+
+            status = parse_win32stream_name(file->name.name,
369-
@@ -614,8 +678,8 @@ int nfs41_create(
409+
+                &is_stream, base_buf, stream_buf);
370-
 {
410+
+            if (status)
371-
     int status;
411+
+                goto out;
372-
     nfs41_compound compound;
412+
+
373-
-    nfs_argop4 argops[8];
413+
+            if (is_stream) {
374-
-    nfs_resop4 resops[8];
414+
+                base_comp.name = base_buf;
375-
+    nfs_argop4 argops[12];
415+
+                base_comp.len = (unsigned short)strlen(base_buf);
376-
+    nfs_resop4 resops[12];
416+
+                stream_comp.name = stream_buf;
377-
     nfs41_sequence_args sequence_args;
417+
+                stream_comp.len = (unsigned short)strlen(stream_buf);
378-
     nfs41_sequence_res sequence_res;
418+
+            }
379-
     nfs41_putfh_args putfh_args;
419+
+        }
380-
@@ -629,6 +693,35 @@ int nfs41_create(
420+
+    }
381-
     nfs41_file_info dir_info;
421+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
382-
     nfs41_savefh_res savefh_res;
422+
+
383-
     nfs41_restorefh_res restorefh_res;
423+
     /* depending on the claim type, OPEN expects CURRENT_FH set
424+
      * to either the parent directory, or to the file itself */
384430
+#ifdef NFS41_WINSTREAMS_SUPPORT
385-
+    char base_buf[NFS41_MAX_PATH_LEN+1];
431+
+    if (is_stream && (stream_comp.len > 0)) {
386-
+    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
432+
+        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
387-
+    bool is_stream = false;
433+
+        lookup_args.name = &base_comp;
388-
+    nfs41_component base_comp = {0};
434+
+        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
389-
+    nfs41_component stream_comp = {0};
435+
+        /* If we are creating the stream, we should allow creating the attr dir */
390-
+    nfs41_lookup_args lookup_args;
436+
+        openattr_args.createdir = (create == OPEN4_CREATE) ? TRUE : FALSE;
391-
+    nfs41_lookup_res lookup_res;
437+
+    }
394438
+#endif /* NFS41_WINSTREAMS_SUPPORT */
395439
+
440+
     compound_add_op(&compound, OP_OPEN, &open_args, &open_res);
396448
+#ifdef NFS41_WINSTREAMS_SUPPORT
397-
+    if (file->name.name) {
449+
+    if (is_stream &&
398-
+        if ((memchr(file->name.name, ':', file->name.len) != NULL)) {
450+
+        ((claim->claim == CLAIM_NULL) ||
399-
+            status = parse_win32stream_name(file->name.name,
451+
+            (claim->claim == CLAIM_DELEGATE_CUR))) {
400-
+                &is_stream, base_buf, stream_buf);
452+
+        if (claim->claim == CLAIM_NULL) {
401-
+            if (status)
453+
+            if (stream_comp.len > 0)
402-
+                goto out;
454+
+                claim->u.null.filename = &stream_comp;
403-
+
455+
+            else
404-
+            if (is_stream) {
456+
+                claim->u.null.filename = &base_comp;
410457
+        }
458+
+        else if (claim->claim == CLAIM_DELEGATE_CUR) {
411465
+    }
412466
+#endif /* NFS41_WINSTREAMS_SUPPORT */
467+
+
413470
414-
     nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
471+
@@ -582,6 +645,7 @@ int nfs41_open(
472+
     if (compound_error(status = compound.res.status))
415474
416-
@@ -644,13 +737,34 @@ int nfs41_create(
475+
+    /* This can happen if |nfs41_open()| is called by the EA code */
417-
476+
     if (dir_info.type == NF4ATTRDIR) {
418-
     compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
477+
         file->fh.superblock = parent->fh.superblock;
419-
478+
         goto out;
420-
+#ifdef NFS41_WINSTREAMS_SUPPORT
479+
@@ -614,8 +678,8 @@ int nfs41_create(
421-
+    if (is_stream && (stream_comp.len > 0)) {
480+
 {
422-
+        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
481+
     int status;
423-
+        lookup_args.name = &base_comp;
482+
     nfs41_compound compound;
424-
+        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
483+
-    nfs_argop4 argops[8];
425-
+        openattr_args.createdir = TRUE;
484+
-    nfs_resop4 resops[8];
426-
+    }
485+
+    nfs_argop4 argops[12];
427-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
486+
+    nfs_resop4 resops[12];
428-
+
487+
     nfs41_sequence_args sequence_args;
429-
     compound_add_op(&compound, OP_CREATE, &create_args, &create_res);
488+
     nfs41_sequence_res sequence_res;
430-
     create_args.objtype.type = type;
489+
     nfs41_putfh_args putfh_args;
431-
     if (type == NF4LNK) {
490+
@@ -629,6 +693,35 @@ int nfs41_create(
432-
         create_args.objtype.u.lnk.linkdata = symlink;
491+
     nfs41_file_info dir_info;
433-
         create_args.objtype.u.lnk.linkdata_len = (uint32_t)strlen(symlink);
492+
     nfs41_savefh_res savefh_res;
434-
     }
493+
     nfs41_restorefh_res restorefh_res;
437494
+#ifdef NFS41_WINSTREAMS_SUPPORT
438-
+    if (is_stream) {
495+
+    char base_buf[NFS41_MAX_PATH_LEN+1];
439-
+        if (stream_comp.len > 0)
496+
+    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
440-
+            create_args.name = &stream_comp;
497+
+    bool is_stream = false;
441-
+        else
498+
+    nfs41_component base_comp = {0};
442-
+            create_args.name = &base_comp;
499+
+    nfs41_component stream_comp = {0};
443-
+    }
500+
+    nfs41_lookup_args lookup_args;
444-
+    else
501+
+    nfs41_lookup_res lookup_res;
502+
+    nfs41_openattr_args openattr_args;
445504
+#endif /* NFS41_WINSTREAMS_SUPPORT */
446-
+    {
505+
+
447-
+        create_args.name = &file->name;
506+
+#ifdef NFS41_WINSTREAMS_SUPPORT
448-
+    }
507+
+    if (file->name.name) {
449-
     create_args.createattrs = createattrs;
508+
+        if (is_stream_path_fh(file)) {
450-
     nfs41_superblock_supported_attrs(
509+
+            status = parse_win32stream_name(file->name.name,
451-
                 parent->fh.superblock, &createattrs->attrmask);
510+
+                &is_stream, base_buf, stream_buf);
452-
@@ -1283,8 +1397,8 @@ int nfs41_remove(
511+
+            if (status)
453-
 {
512+
+                goto out;
454-
     int status;
513+
+
455-
     nfs41_compound compound;
514+
+            if (is_stream) {
456-
-    nfs_argop4 argops[4];
515+
+                base_comp.name = base_buf;
457-
-    nfs_resop4 resops[4];
516+
+                base_comp.len = (unsigned short)strlen(base_buf);
458-
+    nfs_argop4 argops[8];
517+
+                stream_comp.name = stream_buf;
459-
+    nfs_resop4 resops[8];
518+
+                stream_comp.len = (unsigned short)strlen(stream_buf);
460-
     nfs41_sequence_args sequence_args;
519+
+            }
461-
     nfs41_sequence_res sequence_res;
520+
+        }
462-
     nfs41_putfh_args putfh_args;
521+
+    }
463-
@@ -1295,6 +1409,33 @@ int nfs41_remove(
522+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
464-
     nfs41_getattr_res getattr_res NDSH(= { 0 });
523+
465-
     bitmap4 attr_request;
524+
     nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
466-
     nfs41_file_info info;
525+
526+
@@ -644,13 +737,34 @@ int nfs41_create(
467530
+#ifdef NFS41_WINSTREAMS_SUPPORT
468-
+    char base_buf[NFS41_MAX_PATH_LEN+1];
531+
+    if (is_stream && (stream_comp.len > 0)) {
469-
+    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
532+
+        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
470-
+    bool is_stream = false;
533+
+        lookup_args.name = &base_comp;
471-
+    nfs41_component base_comp = {0};
534+
+        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
472-
+    nfs41_component stream_comp = {0};
535+
+        openattr_args.createdir = TRUE;
473-
+    nfs41_lookup_args lookup_args;
536+
+    }
474-
+    nfs41_lookup_res lookup_res;
537+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
477538
+
478-
+    if (target->name) {
539+
     compound_add_op(&compound, OP_CREATE, &create_args, &create_res);
479-
+        if ((memchr(target->name, ':', target->len) != NULL)) {
540+
     create_args.objtype.type = type;
480-
+            status = parse_win32stream_name(target->name,
541+
     if (type == NF4LNK) {
481-
+                &is_stream, base_buf, stream_buf);
542+
         create_args.objtype.u.lnk.linkdata = symlink;
482-
+            if (status)
543+
         create_args.objtype.u.lnk.linkdata_len = (uint32_t)strlen(symlink);
483-
+                goto out;
544+
     }
545+
-    create_args.name = &file->name;
484546
+
485-
+            if (is_stream) {
547+
+#ifdef NFS41_WINSTREAMS_SUPPORT
486-
+                base_comp.name = base_buf;
548+
+    if (is_stream) {
487-
+                base_comp.len = (unsigned short)strlen(base_buf);
549+
+        if (stream_comp.len > 0)
488-
+                stream_comp.name = stream_buf;
550+
+            create_args.name = &stream_comp;
489-
+                stream_comp.len = (unsigned short)strlen(stream_buf);
551+
+        else
490-
+            }
552+
+            create_args.name = &base_comp;
492553
+    }
554+
+    else
493555
+#endif /* NFS41_WINSTREAMS_SUPPORT */
494-
556+
+    {
495-
     nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
557+
+        create_args.name = &file->name;
496-
558+
+    }
497-
@@ -1308,8 +1449,28 @@ int nfs41_remove(
559+
     create_args.createattrs = createattrs;
498-
     putfh_args.file = parent;
560+
     nfs41_superblock_supported_attrs(
499-
     putfh_args.in_recovery = 0;
561+
                 parent->fh.superblock, &createattrs->attrmask);
500-
562+
@@ -1283,8 +1397,8 @@ int nfs41_remove(
501-
+#ifdef NFS41_WINSTREAMS_SUPPORT
563+
 {
502-
+    if (is_stream && (stream_comp.len > 0)) {
564+
     int status;
503-
+        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
565+
     nfs41_compound compound;
504-
+        lookup_args.name = &base_comp;
566+
-    nfs_argop4 argops[4];
505-
+        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
567+
-    nfs_resop4 resops[4];
506-
+        openattr_args.createdir = FALSE;
568+
+    nfs_argop4 argops[8];
507-
+    }
569+
+    nfs_resop4 resops[8];
508-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
570+
     nfs41_sequence_args sequence_args;
509-
+
571+
     nfs41_sequence_res sequence_res;
510-
     compound_add_op(&compound, OP_REMOVE, &remove_args, &remove_res);
572+
     nfs41_putfh_args putfh_args;
511-
-    remove_args.target = target;
573+
@@ -1295,6 +1409,33 @@ int nfs41_remove(
574+
     nfs41_getattr_res getattr_res NDSH(= { 0 });
512577
+#ifdef NFS41_WINSTREAMS_SUPPORT
513-
+    if (is_stream) {
578+
+    char base_buf[NFS41_MAX_PATH_LEN+1];
514-
+        if (stream_comp.len > 0)
579+
+    char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
515-
+            remove_args.target = &stream_comp;
580+
+    bool is_stream = false;
516-
+        else
581+
+    nfs41_component base_comp = {0};
517-
+            remove_args.target = &base_comp;
582+
+    nfs41_component stream_comp = {0};
518-
+    }
583+
+    nfs41_lookup_args lookup_args;
519-
+    else
584+
+    nfs41_lookup_res lookup_res;
520-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
585+
+    nfs41_openattr_args openattr_args;
521-
+    {
586+
+    nfs41_openattr_res openattr_res;
522-
+        remove_args.target = target;
587+
+
523-
+    }
588+
+    if (target->name) {
524-
589+
+        if (is_stream_component(target)) {
525-
     compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
590+
+            status = parse_win32stream_name(target->name,
526-
     getattr_args.attr_request = &attr_request;
591+
+                &is_stream, base_buf, stream_buf);
527-
diff --git a/daemon/nfs41_superblock.c b/daemon/nfs41_superblock.c
592+
+            if (status)
528-
index 5ffb171..7878df1 100644
593+
+                goto out;
529-
--- a/daemon/nfs41_superblock.c
594+
+
530-
+++ b/daemon/nfs41_superblock.c
595+
+            if (is_stream) {
531-
@@ -303,8 +303,12 @@ void nfs41_superblock_fs_attributes(
596+
+                base_comp.name = base_buf;
532-
         FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_HARD_LINKS;
597+
+                base_comp.len = (unsigned short)strlen(base_buf);
533-
     if (superblock->symlink_support)
598+
+                stream_comp.name = stream_buf;
534-
         FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_REPARSE_POINTS;
599+
+                stream_comp.len = (unsigned short)strlen(stream_buf);
535-
-    if (superblock->ea_support)
600+
+            }
536-
+    if (superblock->ea_support) {
601+
+        }
541602
+    }
542-
     if (superblock->case_preserving)
603+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
543-
         FsAttrs->FileSystemAttributes |= FILE_CASE_PRESERVED_NAMES;
604+
544-
     if (!superblock->case_insensitive)
605+
     nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
545-
diff --git a/daemon/open.c b/daemon/open.c
606+
546-
index b5e72c7..4928bf7 100644
607+
@@ -1308,8 +1449,28 @@ int nfs41_remove(
547-
--- a/daemon/open.c
608+
     putfh_args.file = parent;
548-
+++ b/daemon/open.c
609+
     putfh_args.in_recovery = 0;
549-
@@ -880,8 +880,12 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
610+
550-
     // first check if windows told us it's a directory
611+
+#ifdef NFS41_WINSTREAMS_SUPPORT
551-
     if (args->create_opts & FILE_DIRECTORY_FILE)
612+
+    if (is_stream && (stream_comp.len > 0)) {
552-
         state->type = NF4DIR;
613+
+        compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
553-
-    else
614+
+        lookup_args.name = &base_comp;
554-
-        state->type = NF4REG;
615+
+        compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
555-
+    else {
616+
+        openattr_args.createdir = FALSE;
560617
+    }
561-
618+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
562-
 #ifdef NFS41_DRIVER_ALLOW_CREATEFILE_ACLS
619+
+
563-
     if (args->sec_desc_len) {
620+
     compound_add_op(&compound, OP_REMOVE, &remove_args, &remove_res);
564-
@@ -1007,20 +1011,26 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
621+
-    remove_args.target = target;
565-
                     goto out_free_state;
622+
+#ifdef NFS41_WINSTREAMS_SUPPORT
566-
                 }
623+
+    if (is_stream) {
567-
             }
624+
+        if (stream_comp.len > 0)
568-
-        } else if (info.type == NF4REG) {
625+
+            remove_args.target = &stream_comp;
569-
+        } else if (info.type == NF4REG || info.type == NF4NAMEDATTR) {
626+
+        else
570-
             DPRINTF(2, ("handle nfs41_open: FILE\n"));
627+
+            remove_args.target = &base_comp;
571-
             if (args->create_opts & FILE_DIRECTORY_FILE) {
628+
+    }
572-
-                DPRINTF(1, ("trying to open file '%s' as a directory\n",
629+
+    else
573-
-                    state->path.path));
630+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
574-
+                DPRINTF(1,
631+
+    {
575-
+                    ("trying to open file state->path.path='%s',"
632+
+        remove_args.target = target;
576-
+                    "info.type=%d as a directory\n",
633+
+    }
577-
+                    state->path.path, (int)info.type));
634+
578-
                 /*
635+
     compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
579-
                  * Notes:
636+
     getattr_args.attr_request = &attr_request;
580-
                  * - SMB returns |STATUS_OBJECT_TYPE_MISMATCH|
637+
@@ -1369,6 +1530,17 @@ int nfs41_rename(
581-
                  * while NTFS returns |STATUS_NOT_A_DIRECTORY|
638+
     bitmap4 attr_request;
582-
                  * - See |map_open_errors()| for the mapping to
639+
     nfs41_restorefh_res restorefh_res;
583-
                  * |STATUS_*|
640+
584-
+                 * - We do not return |ERROR_DIRECTORY| when opening a Win32
641+
+#ifdef NFS41_WINSTREAMS_SUPPORT
585-
+                 * stream
642+
+    if (src_name->name) {
586-
                  */
643+
+        EASSERT_MSG(is_stream_component(src_name) == false,
587-
-                status = ERROR_DIRECTORY;
644+
+            ("nfs41_rename: Streams not implemented yet"));
588-
-                goto out_free_state;
645+
+    }
589-
+                if (memchr(state->path.path, ':', state->path.len) == NULL) {
646+
+    if (dst_name->name) {
590-
+                    status = ERROR_DIRECTORY;
647+
+        EASSERT_MSG(is_stream_component(dst_name) == false,
591-
+                    goto out_free_state;
648+
+            ("nfs41_rename: Streams not implemented yet"));
592-
+                }
649+
+    }
593-
             }
650+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
594-
         } else if (info.type == NF4LNK) {
651+
+
595-
             DPRINTF(2, ("handle nfs41_open: SYMLINK\n"));
652+
     nfs41_superblock_getattr_mask(src_dir->fh.superblock, &attr_request);
597653
598-
 #ifdef DEBUG_OPEN_SPARSE_FILES
654+
     compound_init(&compound, session->client->root->nfsminorvers,
599-
     if ((status == 0) &&
655+
@@ -1555,6 +1727,21 @@ int nfs41_link(
600-
-        (info.type == NF4REG) &&
656+
     nfs41_file_info info = { 0 };
601-
+        (info.type == NF4REG | info.type == NF4NAMEDATTR) &&
657+
     nfs41_path_fh file;
607658
608-
     /* return associated file layouts if necessary */
659+
+#ifdef NFS41_WINSTREAMS_SUPPORT
609-
-    if (state->type == NF4REG)
660+
+    if (src->name.name) {
610-
+    if (state->type == NF4REG || state->type == NF4NAMEDATTR)
661+
+        EASSERT_MSG(is_stream_path_fh(src) == false,
611-
         pnfs_layout_state_close(state->session, state, args->remove);
662+
+            ("nfs41_link: Streams not implemented yet"));
612-
663+
+    }
613-
     if (state->srv_open == args->srv_open)
664+
+    if (dst_dir->name.name) {
614-
diff --git a/daemon/winstreams.c b/daemon/winstreams.c
665+
+        EASSERT_MSG(is_stream_path_fh(dst_dir) == false,
615-
new file mode 100644
666+
+            ("nfs41_link: Streams not implemented yet"));
616-
index 0000000..6e09ccf
667+
+    }
617-
--- /dev/null
668+
+    if (target->name) {
618-
+++ b/daemon/winstreams.c
669+
+        EASSERT_MSG(is_stream_component(target) == false,
619-
@@ -0,0 +1,200 @@
670+
+            ("nfs41_link: Streams not implemented yet"));
620-
+/* NFSv4.1 client for Windows
671+
+    }
621-
+ * Copyright (C) 2025 Roland Mainz <roland.mainz@nrubsig.org>
672+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
622-
+ *
673+
+
623-
+ * Roland Mainz <roland.mainz@nrubsig.org>
674+
 #ifdef NFS41_DRIVER_STABILITY_HACKS
624-
+ *
675+
     /* gisburn: fixme, see comment about |NDSH| above */
625-
+ * This library is free software; you can redistribute it and/or modify it
676+
     (void)memset(getattr_res, 0, sizeof(getattr_res));
626-
+ * under the terms of the GNU Lesser General Public License as published by
677+
@@ -1655,6 +1842,13 @@ int nfs41_readlink(
627-
+ * the Free Software Foundation; either version 2.1 of the License, or (at
678+
     nfs41_putfh_res putfh_res;
628-
+ * your option) any later version.
679+
     nfs41_readlink_res readlink_res;
629-
+ *
680+
630-
+ * This library is distributed in the hope that it will be useful, but
681+
+#ifdef NFS41_WINSTREAMS_SUPPORT
631-
+ * without any warranty; without even the implied warranty of merchantability
682+
+    if (file->name.name) {
632-
+ * or fitness for a particular purpose.  See the GNU Lesser General Public
683+
+        EASSERT_MSG(is_stream_path_fh(file) == false,
633-
+ * License for more details.
684+
+            ("nfs41_readlink: Streams not implemented yet"));
634-
+ *
685+
+    }
635-
+ * You should have received a copy of the GNU Lesser General Public License
686+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
639687
+
640-
+#include <stdlib.h>
688+
     compound_init(&compound, session->client->root->nfsminorvers,
641-
+#include <stdbool.h>
689+
         argops, resops, "readlink");
642-
+
690+
643-
+#include "nfs41_build_features.h"
691+
@@ -1700,6 +1894,13 @@ int nfs41_access(
644-
+#include "winstreams.h"
692+
     nfs41_access_args access_args;
645-
+#include "daemon_debug.h"
693+
     nfs41_access_res access_res;
646-
+#include "util.h"
694+
648695
+#ifdef NFS41_WINSTREAMS_SUPPORT
649-
+/*
696+
+    if (file->name.name) {
650-
+ * |WIN_NFS4_STREAMS_NAME_PREFIX| - Prefix for Windows streams in NFSv4
697+
+        EASSERT_MSG(is_stream_path_fh(file) == false,
651-
+ * XATTR (extended attributes) namespace
698+
+            ("nfs41_access: Streams not implemented yet"));
652-
+ *
699+
+    }
653-
+ * We need such a prefix to avoid colliding with other users
700+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
662701
+
663-
+static
702+
     compound_init(&compound, session->client->root->nfsminorvers,
664-
+int parse_stream_filename_streamname_streamtype(
703+
         argops, resops, "access");
665-
+    const char *restrict path,
704+
666-
+    char *restrict filename,
705+
@@ -1937,6 +2138,13 @@ int nfs41_secinfo(
667-
+    char *restrict streamname,
706+
     nfs41_secinfo_args secinfo_args;
668-
+    char *restrict streamtype)
707+
     nfs41_secinfo_no_name_res secinfo_res;
669-
+{
708+
670-
+    const char *sep;
709+
+#ifdef NFS41_WINSTREAMS_SUPPORT
671-
+    const char *base;
710+
+    if (name->name) {
672-
+    int colon_count;
711+
+        EASSERT_MSG(is_stream_component(name) == false,
673-
+    const char *c1;
712+
+            ("nfs41_secinfo: Streams not implemented yet"));
674-
+    const char *c2;
713+
+    }
675-
+    const char *p;
714+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
678715
+
679-
+    filename[0]   = '\0';
716+
     compound_init(&compound, session->client->root->nfsminorvers,
680-
+    streamname[0] = '\0';
717+
         argops, resops, "secinfo");
681-
+    streamtype[0] = '\0';
718+
682-
+
719+
diff --git a/daemon/nfs41_superblock.c b/daemon/nfs41_superblock.c
683-
+    /* Take the last component after the last backslash */
720+
index 5ffb171..1a767d3 100644
684-
+    sep  = strrchr(path, '\\');
721+
--- a/daemon/nfs41_superblock.c
685-
+    base = sep ? (sep + 1) : path;
722+
+++ b/daemon/nfs41_superblock.c
686-
+
723+
@@ -1,6 +1,6 @@
687-
+    /* Count colons and error out if >= 3 in the final component */
724+
 /* NFSv4.1 client for Windows
688-
+    colon_count = 0;
725+
  * Copyright (C) 2012 The Regents of the University of Michigan
689-
+    for (p = base ; *p ; p++) {
726+
- * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
690-
+        if (*p == ':') {
727+
+ * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
691-
+            colon_count++;
728+
  *
692-
+            if (colon_count >= 3)
729+
  * Olga Kornievskaia <aglo@umich.edu>
693-
+                return ERROR_INVALID_NAME;
730+
  * Casey Bodley <cbodley@umich.edu>
694-
+        }
731+
@@ -303,8 +303,12 @@ void nfs41_superblock_fs_attributes(
732+
         FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_HARD_LINKS;
695741
+    }
696-
+
742+
     if (superblock->case_preserving)
697-
+    /* Find first colon (if any) */
743+
         FsAttrs->FileSystemAttributes |= FILE_CASE_PRESERVED_NAMES;
698-
+    c1 = strchr(base, ':');
744+
     if (!superblock->case_insensitive)
699-
+    if (c1 == NULL) {
745+
diff --git a/daemon/open.c b/daemon/open.c
700-
+        /* No colon: filename is the entire path */
746+
index b5e72c7..d9163bc 100644
701-
+        (void)strcpy(filename, path);
747+
--- a/daemon/open.c
702-
+        return NO_ERROR;
748+
+++ b/daemon/open.c
703-
+    }
749+
@@ -1,6 +1,6 @@
704-
+
750+
 /* NFSv4.1 client for Windows
705-
+    /* First colon exists: filename before ':' must be non-empty */
751+
  * Copyright (C) 2012 The Regents of the University of Michigan
706-
+    if (c1 == base)
752+
- * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
707-
+        return ERROR_INVALID_NAME;
753+
+ * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
708-
+
754+
  *
709-
+    /*
755+
  * Olga Kornievskaia <aglo@umich.edu>
710-
+     * One colon case
756+
  * Casey Bodley <cbodley@umich.edu>
711-
+     */
757+
@@ -37,6 +37,7 @@
712-
+    if (colon_count == 1) {
758+
 #include "fileinfoutil.h"
713-
+        if (*(c1 + 1) == '\0')
759+
 #include "util.h"
714-
+            return ERROR_INVALID_NAME; /* "filename:" => invalid */
760+
 #include "idmap.h"
715-
+
761+
+#include "winstreams.h"
716-
+        /* filename = full path up to first colon in base */
762+
717-
+        len_prefix = (size_t)(c1 - path);
763+
 static int create_open_state(
718-
+        (void)memcpy(filename, path, len_prefix);
764+
     IN const char *path,
719-
+        filename[len_prefix] = '\0';
765+
@@ -880,8 +881,12 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
720-
+
766+
     // first check if windows told us it's a directory
721-
+        /* streamname after first colon */
767+
     if (args->create_opts & FILE_DIRECTORY_FILE)
722-
+        (void)strcpy(streamname, c1 + 1);
768+
         state->type = NF4DIR;
723-
+        /* streamtype stays empty */
769+
-    else
724-
+        return NO_ERROR;
770+
-        state->type = NF4REG;
771+
+    else {
725776
+    }
726-
+
777+
727-
+    /*
778+
 #ifdef NFS41_DRIVER_ALLOW_CREATEFILE_ACLS
728-
+     * Two colons case
779+
     if (args->sec_desc_len) {
729-
+     */
780+
@@ -1007,20 +1012,26 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
730-
+    c2 = strchr(c1 + 1, ':');
781+
                     goto out_free_state;
731-
+    if (c2 == NULL) {
782+
                 }
732-
+        /* Should not happen when colon_count == 2 */
783+
             }
733-
+        return ERROR_INVALID_NAME;
784+
-        } else if (info.type == NF4REG) {
734-
+    }
785+
+        } else if ((info.type == NF4REG) || (info.type == NF4NAMEDATTR)) {
735-
+
786+
             DPRINTF(2, ("handle nfs41_open: FILE\n"));
736-
+    if (*(c2 + 1) == '\0')
787+
             if (args->create_opts & FILE_DIRECTORY_FILE) {
737-
+        return ERROR_INVALID_NAME; /* type must be non-empty */
788+
-                DPRINTF(1, ("trying to open file '%s' as a directory\n",
738-
+
789+
-                    state->path.path));
739-
+    /* filename = full path up to first colon */
790+
+                DPRINTF(1,
740-
+    len_prefix = (size_t)(c1 - path);
791+
+                    ("trying to open file state->path.path='%s',"
741-
+    (void)memcpy(filename, path, len_prefix);
792+
+                    "info.type=%d as a directory\n",
742-
+    filename[len_prefix] = '\0';
793+
+                    state->path.path, (int)info.type));
743-
+
794+
                 /*
744-
+    /* streamname = [c1+1, c2] (may be empty, e.g., filename::$DATA) */
795+
                  * Notes:
745-
+    len_sn = (size_t)(c2 - (c1 + 1));
796+
                  * - SMB returns |STATUS_OBJECT_TYPE_MISMATCH|
746-
+    (void)memcpy(streamname, c1 + 1, len_sn);
797+
                  * while NTFS returns |STATUS_NOT_A_DIRECTORY|
747-
+    streamname[len_sn] = '\0';
798+
                  * - See |map_open_errors()| for the mapping to
748-
+
799+
                  * |STATUS_*|
749-
+    /* streamtype = (c2+1 .. end) */
800+
+                 * - We do not return |ERROR_DIRECTORY| when opening a Win32
750-
+    (void)strcpy(streamtype, c2 + 1);
801+
+                 * stream
751-
+
802+
                  */
752-
+    return NO_ERROR;
803+
-                status = ERROR_DIRECTORY;
753-
+}
804+
-                goto out_free_state;
754-
+
805+
+                if (is_stream_path(&state->path) == false) {
755-
+int parse_win32stream_name(
806+
+                    status = ERROR_DIRECTORY;
756-
+    IN const char *restrict path,
807+
+                    goto out_free_state;
757-
+    OUT bool *restrict is_stream,
808+
+                }
758-
+    OUT char *restrict base_name,
809+
             }
759-
+    OUT char *restrict stream_name)
810+
         } else if (info.type == NF4LNK) {
760-
+{
811+
             DPRINTF(2, ("handle nfs41_open: SYMLINK\n"));
761-
+    int status;
812+
@@ -1324,7 +1335,7 @@ supersede_retry:
762-
+    char filenamebuff[NFS41_MAX_PATH_LEN+1];
813+
763-
+    char streamnamebuff[NFS41_MAX_COMPONENT_LEN+1];
814+
 #ifdef DEBUG_OPEN_SPARSE_FILES
764-
+    /* |streamtypebuff| must include space for prefix+suffix */
815+
     if ((status == 0) &&
765-
+    char streamtypebuff[NFS41_MAX_COMPONENT_LEN+1+128];
816+
-        (info.type == NF4REG) &&
766-
+    char *p;
817+
+        (info.type == NF4REG | info.type == NF4NAMEDATTR) &&
767-
+
818+
         (state->session->client->root->supports_nfs42_seek)) {
768-
+    status = parse_stream_filename_streamname_streamtype(path,
819+
         //debug_list_sparsefile_holes(state);
769-
+        filenamebuff, streamnamebuff, streamtypebuff);
820+
         debug_list_sparsefile_datasections(state);
770-
+    if (status) {
821+
@@ -1524,7 +1535,7 @@ static int handle_close(void *deamon_context, nfs41_upcall *upcall)
771-
+        eprintf("parse_win32stream_name: "
822+
     nfs41_open_state *state = upcall->state_ref;
772-
+            "parsing for path='%s' failed, status=%d\n",
823+
773-
+            path, status);
824+
     /* return associated file layouts if necessary */
774-
+        return status;
825+
-    if (state->type == NF4REG)
775-
+    }
826+
+    if (state->type == NF4REG || state->type == NF4NAMEDATTR)
776-
+
827+
         pnfs_layout_state_close(state->session, state, args->remove);
777-
+    DPRINTF(2,
828+
778-
+        ("parse_win32stream_name: "
829+
     if (state->srv_open == args->srv_open)
779-
+        "parse_stream_filename_streamname_streamtype(path='%s') returned "
830+
diff --git a/daemon/upcall.h b/daemon/upcall.h
780-
+        "filenamebuff='%s', streamnamebuff='%s', streamtypebuff='%s'\n",
831+
index 1563c25..01a84ed 100644
781-
+        path, filenamebuff, streamnamebuff, streamtypebuff));
832+
--- a/daemon/upcall.h
782-
+
833+
+++ b/daemon/upcall.h
783-
+    if ((streamnamebuff[0] == '\0') && (streamtypebuff[0] == '\0')) {
834+
@@ -1,6 +1,6 @@
784-
+        return ERROR_INVALID_NAME;
835+
 /* NFSv4.1 client for Windows
785-
+    }
836+
  * Copyright (C) 2012 The Regents of the University of Michigan
786-
+
837+
- * Copyright (C) 2023-2025 Roland Mainz <roland.mainz@nrubsig.org>
787-
+    /* We do not support any stream types except "$DATA" (yet) */
838+
+ * Copyright (C) 2023-2026 Roland Mainz <roland.mainz@nrubsig.org>
788-
+    if ((streamtypebuff[0] != '\0') &&
839+
  *
789-
+        ((_stricmp(streamtypebuff, "$DATA") != 0))) {
840+
  * Olga Kornievskaia <aglo@umich.edu>
790-
+        eprintf("parse_win32stream_name: "
841+
  * Casey Bodley <cbodley@umich.edu>
791-
+            "Unsupported stream type, path='%s', "
842+
@@ -122,6 +122,10 @@ typedef struct __getattr_upcall_args {
792-
+            "stream='%s', streamtype='%s'\n",
843+
     FILE_STAT_INFORMATION stat_info;
793-
+            path,
844+
     FILE_STAT_LX_INFORMATION stat_lx_info;
794-
+            streamnamebuff,
845+
 #endif /* NFS41_DRIVER_WSL_SUPPORT */
795-
+            streamtypebuff);
846+
+#ifdef NFS41_WINSTREAMS_SUPPORT
796-
+        return ERROR_INVALID_NAME;
847+
+    PFILE_STREAM_INFORMATION stream_info_list;
797-
+    }
848+
+    ULONG stream_info_list_size;
798-
+
849+
+#endif /* NFS41_WINSTREAMS_SUPPORT */
799-
+    /* "foo::$DATA" refers to "foo" */
850+
     int query_class;
800-
+    if (streamnamebuff[0] == '\0') {
851+
     int buf_len;
801-
+        *is_stream = true;
852+
     int query_reply_len;
802-
+        (void)strcpy(base_name, filenamebuff);
853+
diff --git a/daemon/winstreams.c b/daemon/winstreams.c
803-
+        stream_name[0] = '\0';
854+
new file mode 100644
804-
+        return NO_ERROR;
855+
index 0000000..be0af14
805-
+    }
856+
--- /dev/null
806-
+
857+
+++ b/daemon/winstreams.c
807-
+    /*
858+
@@ -0,0 +1,455 @@
808-
+     * If we have a stream name, then add our NFS attr prefix for Windows
859+
+/* NFSv4.1 client for Windows
809-
+     * streams, and ":$DATA" as suffix
860+
+ * Copyright (C) 2025-2026 Roland Mainz <roland.mainz@nrubsig.org>
810-
+     */
861+
+ *
811-
+    *is_stream = true;
862+
+ * Roland Mainz <roland.mainz@nrubsig.org>
812-
+    (void)strcpy(base_name, filenamebuff);
863+
+ *
813-
+    p = stpcpy(stream_name, WIN_NFS4_STREAMS_NAME_PREFIX);
864+
+ * This library is free software; you can redistribute it and/or modify it
814-
+    p = stpcpy(p, streamnamebuff);
865+
+ * under the terms of the GNU Lesser General Public License as published by
815-
+    (void)stpcpy(p, ":$DATA");
866+
+ * the Free Software Foundation; either version 2.1 of the License, or (at
816-
+
867+
+ * your option) any later version.
817-
+    return NO_ERROR;
868+
+ *
818-
+}
869+
+ * This library is distributed in the hope that it will be useful, but
819-
+#endif /* NFS41_WINSTREAMS_SUPPORT */
870+
+ * without any warranty; without even the implied warranty of merchantability
820-
diff --git a/daemon/winstreams.h b/daemon/winstreams.h
871+
+ * or fitness for a particular purpose.  See the GNU Lesser General Public
821-
new file mode 100644
872+
+ * License for more details.
828873
+ *
829-
+ * Roland Mainz <roland.mainz@nrubsig.org>
874+
+ * You should have received a copy of the GNU Lesser General Public License
830-
+ *
875+
+ * along with this library; if not, write to the Free Software Foundation,
831-
+ * This library is free software; you can redistribute it and/or modify it
876+
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
832-
+ * under the terms of the GNU Lesser General Public License as published by
877+
+ */
833-
+ * the Free Software Foundation; either version 2.1 of the License, or (at
878+
+
834-
+ * your option) any later version.
879+
+#include <stdlib.h>
835-
+ *
880+
+#include <stdbool.h>
845881
+
846-
+#ifndef __NFS41_DAEMON_WINSTREAMS_H__
847-
+#define __NFS41_DAEMON_WINSTREAMS_H__ 1
848-
+
849-
+#include <stdlib.h>
850-
+#include <stdbool.h>
851-
+
882+
+#include "nfs41_build_features.h"

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