- diff --git a/build.vc19/nfsd/nfsd.vcxproj b/build.vc19/nfsd/nfsd.vcxproj
- index 8589a2a..face630 100644
- --- a/build.vc19/nfsd/nfsd.vcxproj
- +++ b/build.vc19/nfsd/nfsd.vcxproj
- @@ -331,6 +331,7 @@
- <ClCompile Include="..\..\daemon\upcall.c" />
- <ClCompile Include="..\..\daemon\util.c" />
- <ClCompile Include="..\..\daemon\volume.c" />
- + <ClCompile Include="..\..\daemon\winstreams.c" />
- </ItemGroup>
- <ItemGroup>
- <ClInclude Include="..\..\daemon\accesstoken.h" />
- @@ -356,6 +357,7 @@
- <ClInclude Include="..\..\daemon\tree.h" />
- <ClInclude Include="..\..\daemon\upcall.h" />
- <ClInclude Include="..\..\daemon\util.h" />
- + <ClInclude Include="..\..\daemon\winstreams.h" />
- <ClInclude Include="..\..\include\from_kernel.h" />
- <ClInclude Include="..\..\include\nfs_ea.h" />
- </ItemGroup>
- diff --git a/build.vc19/nfsd/nfsd.vcxproj.filters b/build.vc19/nfsd/nfsd.vcxproj.filters
- index 134404f..8e7c552 100644
- --- a/build.vc19/nfsd/nfsd.vcxproj.filters
- +++ b/build.vc19/nfsd/nfsd.vcxproj.filters
- @@ -147,6 +147,9 @@
- <ClCompile Include="..\..\daemon\volume.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- + <ClCompile Include="..\..\daemon\winstreams.c">
- + <Filter>Source Files</Filter>
- + </ClCompile>
- <ClCompile Include="..\..\daemon\accesstoken.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- @@ -227,5 +230,8 @@
- <ClInclude Include="..\..\daemon\accesstoken.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- + <ClInclude Include="..\..\daemon\winstreams.h">
- + <Filter>Header Files</Filter>
- + </ClInclude>
- </ItemGroup>
- </Project>
- \ No newline at end of file
- diff --git a/daemon/fileinfoutil.c b/daemon/fileinfoutil.c
- index 0ed40c8..bbd6d28 100644
- --- a/daemon/fileinfoutil.c
- +++ b/daemon/fileinfoutil.c
- @@ -59,7 +59,7 @@ ULONG nfs_file_info_to_attributes(
- if (info->symlink_dir)
- attrs |= FILE_ATTRIBUTE_DIRECTORY;
- }
- - else if (info->type == NF4REG) {
- + else if ((info->type == NF4REG) || (info->type == NF4NAMEDATTR)) {
- if (superblock->sparse_file_support) {
- /* FIXME: What about pNFS ? */
- attrs |= FILE_ATTRIBUTE_SPARSE_FILE;
- @@ -383,6 +383,7 @@ void nfs_to_stat_lx_info(
- stat_lx_out->LxMode = 0UL;
- switch(info->type) {
- case NF4REG:
- + case NF4NAMEDATTR:
- stat_lx_out->LxMode |= LX_MODE_S_IFREG;
- break;
- case NF4DIR:
- diff --git a/daemon/getattr.c b/daemon/getattr.c
- index 4744285..1069a8d 100644
- --- a/daemon/getattr.c
- +++ b/daemon/getattr.c
- @@ -204,6 +204,9 @@ static int handle_getattr(void *daemon_context, nfs41_upcall *upcall)
- &args->stat_lx_info);
- break;
- #endif /* NFS41_DRIVER_WSL_SUPPORT */
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + case FileStreamInformation:
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- default:
- eprintf("handle_getattr(state->path.path='%s'): "
- "unhandled file query class %d\n",
- diff --git a/daemon/lookup.c b/daemon/lookup.c
- index 3e9e591..a7fb7e7 100644
- --- a/daemon/lookup.c
- +++ b/daemon/lookup.c
- @@ -31,6 +31,9 @@
- #include "fileinfoutil.h"
- #include "util.h"
- #include "daemon_debug.h"
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- +#include "winstreams.h"
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- #define LULVL 2 /* dprintf level for lookup logging */
- @@ -502,6 +505,156 @@ int nfs41_lookup(
- DPRINTF(LULVL, ("--> nfs41_lookup('%s')\n", path.path));
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + /* Fast check for potential stream: Look for a colon */
- + if (memchr(path_inout->path, ':', path_inout->len)) {
- + char base_name[NFS41_MAX_PATH_LEN+1];
- + char stream_name[NFS41_MAX_COMPONENT_LEN+1];
- + bool is_stream = false;
- +
- + /* Parse the stream syntax */
- + status = parse_win32stream_name(path.path, &is_stream, base_name, stream_name);
- + if (status)
- + goto out;
- +
- + if (is_stream) {
- + if (stream_name[0] == '\0') {
- + /* "foo::$DATA" case -> treat as "foo" */
- + DPRINTF(2, ("nfs41_lookup: 'foo::$DATA' case -> treat as 'foo'\n"));
- + size_t base_len = strlen(base_name);
- +
- + (void)memcpy(path.path, base_name, base_len);
- + path.path[base_len] = '\0';
- + path.len = (unsigned short)base_len;
- + path_pos = path.path;
- + path_end = path.path + path.len;
- +
- + /* Continue to normal lookup with stripped name */
- + } else {
- + /* "foo:bar" case */
- + DPRINTF(2, ("nfs41_lookup: base_name='%s', stream_name='%s'\n",
- + base_name, stream_name));
- +
- + /*
- + * Recursively lookup the base file ...
- + */
- + nfs41_abs_path base_path = path;
- + size_t base_len = strlen(base_name);
- +
- + (void)memcpy(base_path.path, base_name, base_len);
- + base_path.path[base_len] = '\0';
- + base_path.len = (unsigned short)base_len;
- +
- + nfs41_path_fh attr_target;
- + if (target_out == NULL) {
- + target_out = &attr_target;
- + }
- +
- + status = nfs41_lookup(root, session, casesensitive, &base_path,
- + parent_out, target_out, info_out, session_out);
- + if (status)
- + goto out;
- +
- + /*
- + * ... and then lookup the NFSv4 named attribute
- + */
- + nfs41_session *lookup_session;
- + nfs41_compound compound;
- + nfs_argop4 argops[8];
- + nfs_resop4 resops[8];
- + nfs41_sequence_args sequence_args;
- + nfs41_sequence_res sequence_res;
- + nfs41_openattr_args openattr_args;
- + nfs41_openattr_res openattr_res;
- + nfs41_putfh_args putfh_args = { 0 };
- + nfs41_putfh_res putfh_res;
- + nfs41_lookup_args lookup_args = { 0 };
- + nfs41_lookup_res lookup_res;
- + nfs41_getfh_res getfh_res;
- + nfs41_getattr_args getattr_args = { 0 };
- + nfs41_getattr_res getattr_res = { 0 };
- + nfs41_component stream_comp = {
- + .name = stream_name,
- + .len = (unsigned short)strlen(stream_name)
- + };
- + /* Minimal attributes for stream */
- + bitmap4 attr_request = {
- + .count = 2,
- + .arr = {
- + [0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE |
- + FATTR4_WORD0_SIZE | FATTR4_WORD0_FSID |
- + FATTR4_WORD0_FILEID,
- + [1] = FATTR4_WORD1_MODE | FATTR4_WORD1_SPACE_USED |
- + FATTR4_WORD1_TIME_ACCESS |
- + FATTR4_WORD1_TIME_CREATE |
- + FATTR4_WORD1_TIME_MODIFY |
- + FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP,
- + [2] = 0
- + }
- + };
- +
- + DPRINTF(1,
- + ("nfs41_lookup: "
- + "Looking up base_name='%s'/stream_name='%s'\n",
- + base_name, stream_name));
- +
- + /* Use the session returned by the base lookup if applicable */
- + if ((session_out != NULL) && (*session_out != NULL))
- + lookup_session = *session_out;
- + else
- + lookup_session = session;
- +
- + compound_init(&compound,
- + lookup_session->client->root->nfsminorvers,
- + argops, resops, "lookup_win32stream");
- +
- + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
- + nfs41_session_sequence(&sequence_args, lookup_session, 0);
- +
- + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
- + putfh_args.file = target_out;
- + putfh_args.in_recovery = FALSE;
- +
- + compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
- + openattr_args.createdir = FALSE;
- +
- + compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
- + lookup_args.name = &stream_comp;
- +
- + compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
- + getfh_res.fh = &target_out->fh;
- +
- + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
- + getattr_args.attr_request = &attr_request;
- + /* If caller wanted info, fill it; else dummy */
- + nfs41_file_info dummy_info;
- + if (info_out)
- + getattr_res.info = info_out;
- + else
- + getattr_res.info = &dummy_info;
- + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT_ATTR;
- +
- + status = compound_encode_send_decode(lookup_session, &compound, TRUE);
- +
- + if (status == 0)
- + status = compound.res.status;
- +
- + if (status == 0) {
- + /* We MUST return a |NF4NAMEDATTR|, and never a |NF4ATTRDIR| */
- + EASSERT(getattr_res.info->type != NF4ATTRDIR);
- + EASSERT(getattr_res.info->type == NF4NAMEDATTR);
- + }
- + else {
- + DPRINTF(1, ("nfs41_lookup: failed for attr, status=%d\n",
- + (int)status));
- + }
- +
- + goto out;
- + }
- + }
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- if (parent_out == NULL) parent_out = &parent;
- if (target_out == NULL) target_out = ⌖
- parent_out->fh.len = target_out->fh.len = 0;
- diff --git a/daemon/nfs41_ops.c b/daemon/nfs41_ops.c
- index 9012db4..ebe0bd5 100644
- --- a/daemon/nfs41_ops.c
- +++ b/daemon/nfs41_ops.c
- @@ -36,6 +36,9 @@
- #include "delegation.h"
- #include "daemon_debug.h"
- #include "util.h"
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- +#include "winstreams.h"
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- #ifdef NFS41_DRIVER_STABILITY_HACKS
- /*
- @@ -453,8 +456,8 @@ int nfs41_open(
- {
- int status;
- nfs41_compound compound;
- - nfs_argop4 argops[8];
- - nfs_resop4 resops[8];
- + nfs_argop4 argops[12];
- + nfs_resop4 resops[12];
- nfs41_sequence_args sequence_args;
- nfs41_sequence_res sequence_res;
- nfs41_putfh_args putfh_args[2];
- @@ -471,10 +474,39 @@ int nfs41_open(
- bool_t current_fh_is_dir;
- bool_t already_delegated = delegation->type == OPEN_DELEGATE_READ
- || delegation->type == OPEN_DELEGATE_WRITE;
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + char base_buf[NFS41_MAX_PATH_LEN+1];
- + char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
- + bool is_stream = false;
- + nfs41_component base_comp = {0};
- + nfs41_component stream_comp = {0};
- + nfs41_lookup_args lookup_args;
- + nfs41_lookup_res lookup_res;
- + nfs41_openattr_args openattr_args;
- + nfs41_openattr_res openattr_res;
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- EASSERT_IS_VALID_NON_NULL_PTR(parent);
- EASSERT_IS_VALID_NON_NULL_PTR(parent->fh.superblock);
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (file->name.name) {
- + if ((memchr(file->name.name, ':', file->name.len) != NULL)) {
- + status = parse_win32stream_name(file->name.name,
- + &is_stream, base_buf, stream_buf);
- + if (status)
- + goto out;
- +
- + if (is_stream) {
- + base_comp.name = base_buf;
- + base_comp.len = (unsigned short)strlen(base_buf);
- + stream_comp.name = stream_buf;
- + stream_comp.len = (unsigned short)strlen(stream_buf);
- + }
- + }
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- /* depending on the claim type, OPEN expects CURRENT_FH set
- * to either the parent directory, or to the file itself */
- switch (claim->claim) {
- @@ -524,6 +556,16 @@ int nfs41_open(
- putfh_args[0].in_recovery = 0;
- }
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (is_stream && (stream_comp.len > 0)) {
- + compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
- + lookup_args.name = &base_comp;
- + compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
- + /* If we are creating the stream, we should allow creating the attr dir */
- + openattr_args.createdir = (create == OPEN4_CREATE) ? TRUE : FALSE;
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- compound_add_op(&compound, OP_OPEN, &open_args, &open_res);
- open_args.seqid = 0;
- #ifdef DISABLE_FILE_DELEGATIONS
- @@ -549,6 +591,27 @@ int nfs41_open(
- parent->fh.superblock, &createattrs->attrmask);
- }
- open_args.claim = claim;
- +
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (is_stream &&
- + ((claim->claim == CLAIM_NULL) ||
- + (claim->claim == CLAIM_DELEGATE_CUR))) {
- + if (claim->claim == CLAIM_NULL) {
- + if (stream_comp.len > 0)
- + claim->u.null.filename = &stream_comp;
- + else
- + claim->u.null.filename = &base_comp;
- + }
- + else if (claim->claim == CLAIM_DELEGATE_CUR) {
- + claim->u.deleg_cur.name = &stream_comp;
- + if (stream_comp.len > 0)
- + claim->u.deleg_cur.name = &stream_comp;
- + else
- + claim->u.deleg_cur.name = &base_comp;
- + }
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- open_res.resok4.stateid = stateid;
- open_res.resok4.delegation = delegation;
- @@ -582,6 +645,7 @@ int nfs41_open(
- if (compound_error(status = compound.res.status))
- goto out;
- + /* This can happen if |nfs41_open()| is called by the EA code */
- if (dir_info.type == NF4ATTRDIR) {
- file->fh.superblock = parent->fh.superblock;
- goto out;
- @@ -614,8 +678,8 @@ int nfs41_create(
- {
- int status;
- nfs41_compound compound;
- - nfs_argop4 argops[8];
- - nfs_resop4 resops[8];
- + nfs_argop4 argops[12];
- + nfs_resop4 resops[12];
- nfs41_sequence_args sequence_args;
- nfs41_sequence_res sequence_res;
- nfs41_putfh_args putfh_args;
- @@ -629,6 +693,35 @@ int nfs41_create(
- nfs41_file_info dir_info;
- nfs41_savefh_res savefh_res;
- nfs41_restorefh_res restorefh_res;
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + char base_buf[NFS41_MAX_PATH_LEN+1];
- + char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
- + bool is_stream = false;
- + nfs41_component base_comp = {0};
- + nfs41_component stream_comp = {0};
- + nfs41_lookup_args lookup_args;
- + nfs41_lookup_res lookup_res;
- + nfs41_openattr_args openattr_args;
- + nfs41_openattr_res openattr_res;
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (file->name.name) {
- + if ((memchr(file->name.name, ':', file->name.len) != NULL)) {
- + status = parse_win32stream_name(file->name.name,
- + &is_stream, base_buf, stream_buf);
- + if (status)
- + goto out;
- +
- + if (is_stream) {
- + base_comp.name = base_buf;
- + base_comp.len = (unsigned short)strlen(base_buf);
- + stream_comp.name = stream_buf;
- + stream_comp.len = (unsigned short)strlen(stream_buf);
- + }
- + }
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
- @@ -644,13 +737,34 @@ int nfs41_create(
- compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (is_stream && (stream_comp.len > 0)) {
- + compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
- + lookup_args.name = &base_comp;
- + compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
- + openattr_args.createdir = TRUE;
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- compound_add_op(&compound, OP_CREATE, &create_args, &create_res);
- create_args.objtype.type = type;
- if (type == NF4LNK) {
- create_args.objtype.u.lnk.linkdata = symlink;
- create_args.objtype.u.lnk.linkdata_len = (uint32_t)strlen(symlink);
- }
- - create_args.name = &file->name;
- +
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (is_stream) {
- + if (stream_comp.len > 0)
- + create_args.name = &stream_comp;
- + else
- + create_args.name = &base_comp;
- + }
- + else
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- + {
- + create_args.name = &file->name;
- + }
- create_args.createattrs = createattrs;
- nfs41_superblock_supported_attrs(
- parent->fh.superblock, &createattrs->attrmask);
- @@ -1283,8 +1397,8 @@ int nfs41_remove(
- {
- int status;
- nfs41_compound compound;
- - nfs_argop4 argops[4];
- - nfs_resop4 resops[4];
- + nfs_argop4 argops[8];
- + nfs_resop4 resops[8];
- nfs41_sequence_args sequence_args;
- nfs41_sequence_res sequence_res;
- nfs41_putfh_args putfh_args;
- @@ -1295,6 +1409,33 @@ int nfs41_remove(
- nfs41_getattr_res getattr_res NDSH(= { 0 });
- bitmap4 attr_request;
- nfs41_file_info info;
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + char base_buf[NFS41_MAX_PATH_LEN+1];
- + char stream_buf[NFS41_MAX_COMPONENT_LEN+1];
- + bool is_stream = false;
- + nfs41_component base_comp = {0};
- + nfs41_component stream_comp = {0};
- + nfs41_lookup_args lookup_args;
- + nfs41_lookup_res lookup_res;
- + nfs41_openattr_args openattr_args;
- + nfs41_openattr_res openattr_res;
- +
- + if (target->name) {
- + if ((memchr(target->name, ':', target->len) != NULL)) {
- + status = parse_win32stream_name(target->name,
- + &is_stream, base_buf, stream_buf);
- + if (status)
- + goto out;
- +
- + if (is_stream) {
- + base_comp.name = base_buf;
- + base_comp.len = (unsigned short)strlen(base_buf);
- + stream_comp.name = stream_buf;
- + stream_comp.len = (unsigned short)strlen(stream_buf);
- + }
- + }
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
- @@ -1308,8 +1449,28 @@ int nfs41_remove(
- putfh_args.file = parent;
- putfh_args.in_recovery = 0;
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (is_stream && (stream_comp.len > 0)) {
- + compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
- + lookup_args.name = &base_comp;
- + compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
- + openattr_args.createdir = FALSE;
- + }
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- compound_add_op(&compound, OP_REMOVE, &remove_args, &remove_res);
- - remove_args.target = target;
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + if (is_stream) {
- + if (stream_comp.len > 0)
- + remove_args.target = &stream_comp;
- + else
- + remove_args.target = &base_comp;
- + }
- + else
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- + {
- + remove_args.target = target;
- + }
- compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
- getattr_args.attr_request = &attr_request;
- diff --git a/daemon/nfs41_superblock.c b/daemon/nfs41_superblock.c
- index 5ffb171..7878df1 100644
- --- a/daemon/nfs41_superblock.c
- +++ b/daemon/nfs41_superblock.c
- @@ -303,8 +303,12 @@ void nfs41_superblock_fs_attributes(
- FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_HARD_LINKS;
- if (superblock->symlink_support)
- FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_REPARSE_POINTS;
- - if (superblock->ea_support)
- + if (superblock->ea_support) {
- FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES;
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + FsAttrs->FileSystemAttributes |= FILE_NAMED_STREAMS;
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- + }
- if (superblock->case_preserving)
- FsAttrs->FileSystemAttributes |= FILE_CASE_PRESERVED_NAMES;
- if (!superblock->case_insensitive)
- diff --git a/daemon/open.c b/daemon/open.c
- index b5e72c7..4928bf7 100644
- --- a/daemon/open.c
- +++ b/daemon/open.c
- @@ -880,8 +880,12 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
- // first check if windows told us it's a directory
- if (args->create_opts & FILE_DIRECTORY_FILE)
- state->type = NF4DIR;
- - else
- - state->type = NF4REG;
- + else {
- + if (strstr(args->path, ":"))
- + state->type = NF4NAMEDATTR;
- + else
- + state->type = NF4REG;
- + }
- #ifdef NFS41_DRIVER_ALLOW_CREATEFILE_ACLS
- if (args->sec_desc_len) {
- @@ -1007,20 +1011,26 @@ static int handle_open(void *daemon_context, nfs41_upcall *upcall)
- goto out_free_state;
- }
- }
- - } else if (info.type == NF4REG) {
- + } else if (info.type == NF4REG || info.type == NF4NAMEDATTR) {
- DPRINTF(2, ("handle nfs41_open: FILE\n"));
- if (args->create_opts & FILE_DIRECTORY_FILE) {
- - DPRINTF(1, ("trying to open file '%s' as a directory\n",
- - state->path.path));
- + DPRINTF(1,
- + ("trying to open file state->path.path='%s',"
- + "info.type=%d as a directory\n",
- + state->path.path, (int)info.type));
- /*
- * Notes:
- * - SMB returns |STATUS_OBJECT_TYPE_MISMATCH|
- * while NTFS returns |STATUS_NOT_A_DIRECTORY|
- * - See |map_open_errors()| for the mapping to
- * |STATUS_*|
- + * - We do not return |ERROR_DIRECTORY| when opening a Win32
- + * stream
- */
- - status = ERROR_DIRECTORY;
- - goto out_free_state;
- + if (memchr(state->path.path, ':', state->path.len) == NULL) {
- + status = ERROR_DIRECTORY;
- + goto out_free_state;
- + }
- }
- } else if (info.type == NF4LNK) {
- DPRINTF(2, ("handle nfs41_open: SYMLINK\n"));
- @@ -1324,7 +1334,7 @@ supersede_retry:
- #ifdef DEBUG_OPEN_SPARSE_FILES
- if ((status == 0) &&
- - (info.type == NF4REG) &&
- + (info.type == NF4REG | info.type == NF4NAMEDATTR) &&
- (state->session->client->root->supports_nfs42_seek)) {
- //debug_list_sparsefile_holes(state);
- debug_list_sparsefile_datasections(state);
- @@ -1524,7 +1534,7 @@ static int handle_close(void *deamon_context, nfs41_upcall *upcall)
- nfs41_open_state *state = upcall->state_ref;
- /* return associated file layouts if necessary */
- - if (state->type == NF4REG)
- + if (state->type == NF4REG || state->type == NF4NAMEDATTR)
- pnfs_layout_state_close(state->session, state, args->remove);
- if (state->srv_open == args->srv_open)
- diff --git a/daemon/winstreams.c b/daemon/winstreams.c
- new file mode 100644
- index 0000000..6e09ccf
- --- /dev/null
- +++ b/daemon/winstreams.c
- @@ -0,0 +1,200 @@
- +/* NFSv4.1 client for Windows
- + * Copyright (C) 2025 Roland Mainz <roland.mainz@nrubsig.org>
- + *
- + * Roland Mainz <roland.mainz@nrubsig.org>
- + *
- + * This library is free software; you can redistribute it and/or modify it
- + * under the terms of the GNU Lesser General Public License as published by
- + * the Free Software Foundation; either version 2.1 of the License, or (at
- + * your option) any later version.
- + *
- + * This library is distributed in the hope that it will be useful, but
- + * without any warranty; without even the implied warranty of merchantability
- + * or fitness for a particular purpose. See the GNU Lesser General Public
- + * License for more details.
- + *
- + * You should have received a copy of the GNU Lesser General Public License
- + * along with this library; if not, write to the Free Software Foundation,
- + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- + */
- +
- +#include <stdlib.h>
- +#include <stdbool.h>
- +
- +#include "nfs41_build_features.h"
- +#include "winstreams.h"
- +#include "daemon_debug.h"
- +#include "util.h"
- +
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- +/*
- + * |WIN_NFS4_STREAMS_NAME_PREFIX| - Prefix for Windows streams in NFSv4
- + * XATTR (extended attributes) namespace
- + *
- + * We need such a prefix to avoid colliding with other users
- + * in the NFSv4 XATTR namespace - for example SUN Microsystrems
- + * (Solaris, Illumos, ...) uses "SUNWattr_" as prefix, and setting
- + * such attributes can cause data corruption (or in case of
- + * "SUNWattr_ro" will fail, because the attribute file is
- + * read-only).
- + */
- +#define WIN_NFS4_STREAMS_NAME_PREFIX "win32.streams."
- +#define WIN_NFS4_STREAMS_NAME_PREFIX_LEN (14)
- +
- +static
- +int parse_stream_filename_streamname_streamtype(
- + const char *restrict path,
- + char *restrict filename,
- + char *restrict streamname,
- + char *restrict streamtype)
- +{
- + const char *sep;
- + const char *base;
- + int colon_count;
- + const char *c1;
- + const char *c2;
- + const char *p;
- + size_t len_prefix;
- + size_t len_sn;
- +
- + filename[0] = '\0';
- + streamname[0] = '\0';
- + streamtype[0] = '\0';
- +
- + /* Take the last component after the last backslash */
- + sep = strrchr(path, '\\');
- + base = sep ? (sep + 1) : path;
- +
- + /* Count colons and error out if >= 3 in the final component */
- + colon_count = 0;
- + for (p = base ; *p ; p++) {
- + if (*p == ':') {
- + colon_count++;
- + if (colon_count >= 3)
- + return ERROR_INVALID_NAME;
- + }
- + }
- +
- + /* Find first colon (if any) */
- + c1 = strchr(base, ':');
- + if (c1 == NULL) {
- + /* No colon: filename is the entire path */
- + (void)strcpy(filename, path);
- + return NO_ERROR;
- + }
- +
- + /* First colon exists: filename before ':' must be non-empty */
- + if (c1 == base)
- + return ERROR_INVALID_NAME;
- +
- + /*
- + * One colon case
- + */
- + if (colon_count == 1) {
- + if (*(c1 + 1) == '\0')
- + return ERROR_INVALID_NAME; /* "filename:" => invalid */
- +
- + /* filename = full path up to first colon in base */
- + len_prefix = (size_t)(c1 - path);
- + (void)memcpy(filename, path, len_prefix);
- + filename[len_prefix] = '\0';
- +
- + /* streamname after first colon */
- + (void)strcpy(streamname, c1 + 1);
- + /* streamtype stays empty */
- + return NO_ERROR;
- + }
- +
- + /*
- + * Two colons case
- + */
- + c2 = strchr(c1 + 1, ':');
- + if (c2 == NULL) {
- + /* Should not happen when colon_count == 2 */
- + return ERROR_INVALID_NAME;
- + }
- +
- + if (*(c2 + 1) == '\0')
- + return ERROR_INVALID_NAME; /* type must be non-empty */
- +
- + /* filename = full path up to first colon */
- + len_prefix = (size_t)(c1 - path);
- + (void)memcpy(filename, path, len_prefix);
- + filename[len_prefix] = '\0';
- +
- + /* streamname = [c1+1, c2] (may be empty, e.g., filename::$DATA) */
- + len_sn = (size_t)(c2 - (c1 + 1));
- + (void)memcpy(streamname, c1 + 1, len_sn);
- + streamname[len_sn] = '\0';
- +
- + /* streamtype = (c2+1 .. end) */
- + (void)strcpy(streamtype, c2 + 1);
- +
- + return NO_ERROR;
- +}
- +
- +int parse_win32stream_name(
- + IN const char *restrict path,
- + OUT bool *restrict is_stream,
- + OUT char *restrict base_name,
- + OUT char *restrict stream_name)
- +{
- + int status;
- + char filenamebuff[NFS41_MAX_PATH_LEN+1];
- + char streamnamebuff[NFS41_MAX_COMPONENT_LEN+1];
- + /* |streamtypebuff| must include space for prefix+suffix */
- + char streamtypebuff[NFS41_MAX_COMPONENT_LEN+1+128];
- + char *p;
- +
- + status = parse_stream_filename_streamname_streamtype(path,
- + filenamebuff, streamnamebuff, streamtypebuff);
- + if (status) {
- + eprintf("parse_win32stream_name: "
- + "parsing for path='%s' failed, status=%d\n",
- + path, status);
- + return status;
- + }
- +
- + DPRINTF(2,
- + ("parse_win32stream_name: "
- + "parse_stream_filename_streamname_streamtype(path='%s') returned "
- + "filenamebuff='%s', streamnamebuff='%s', streamtypebuff='%s'\n",
- + path, filenamebuff, streamnamebuff, streamtypebuff));
- +
- + if ((streamnamebuff[0] == '\0') && (streamtypebuff[0] == '\0')) {
- + return ERROR_INVALID_NAME;
- + }
- +
- + /* We do not support any stream types except "$DATA" (yet) */
- + if ((streamtypebuff[0] != '\0') &&
- + ((_stricmp(streamtypebuff, "$DATA") != 0))) {
- + eprintf("parse_win32stream_name: "
- + "Unsupported stream type, path='%s', "
- + "stream='%s', streamtype='%s'\n",
- + path,
- + streamnamebuff,
- + streamtypebuff);
- + return ERROR_INVALID_NAME;
- + }
- +
- + /* "foo::$DATA" refers to "foo" */
- + if (streamnamebuff[0] == '\0') {
- + *is_stream = true;
- + (void)strcpy(base_name, filenamebuff);
- + stream_name[0] = '\0';
- + return NO_ERROR;
- + }
- +
- + /*
- + * If we have a stream name, then add our NFS attr prefix for Windows
- + * streams, and ":$DATA" as suffix
- + */
- + *is_stream = true;
- + (void)strcpy(base_name, filenamebuff);
- + p = stpcpy(stream_name, WIN_NFS4_STREAMS_NAME_PREFIX);
- + p = stpcpy(p, streamnamebuff);
- + (void)stpcpy(p, ":$DATA");
- +
- + return NO_ERROR;
- +}
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- diff --git a/daemon/winstreams.h b/daemon/winstreams.h
- new file mode 100644
- index 0000000..7d80036
- --- /dev/null
- +++ b/daemon/winstreams.h
- @@ -0,0 +1,41 @@
- +/* NFSv4.1 client for Windows
- + * Copyright (C) 2025 Roland Mainz <roland.mainz@nrubsig.org>
- + *
- + * Roland Mainz <roland.mainz@nrubsig.org>
- + *
- + * This library is free software; you can redistribute it and/or modify it
- + * under the terms of the GNU Lesser General Public License as published by
- + * the Free Software Foundation; either version 2.1 of the License, or (at
- + * your option) any later version.
- + *
- + * This library is distributed in the hope that it will be useful, but
- + * without any warranty; without even the implied warranty of merchantability
- + * or fitness for a particular purpose. See the GNU Lesser General Public
- + * License for more details.
- + *
- + * You should have received a copy of the GNU Lesser General Public License
- + * along with this library; if not, write to the Free Software Foundation,
- + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- + */
- +
- +#ifndef __NFS41_DAEMON_WINSTREAMS_H__
- +#define __NFS41_DAEMON_WINSTREAMS_H__ 1
- +
- +#include <stdlib.h>
- +#include <stdbool.h>
- +
- +#include "nfs41_build_features.h"
- +#include "nfs41_types.h"
- +#include "from_kernel.h"
- +
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- +
- +int parse_win32stream_name(
- + IN const char *restrict path,
- + OUT bool *restrict is_stream,
- + OUT char *restrict base_name,
- + OUT char *restrict stream_name);
- +
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- +
- +#endif /* !__NFS41_DAEMON_WINSTREAMS_H__ */
- diff --git a/nfs41_build_features.h b/nfs41_build_features.h
- index fb744f5..3ee191a 100644
- --- a/nfs41_build_features.h
- +++ b/nfs41_build_features.h
- @@ -273,4 +273,10 @@
- */
- #define NFS41_DRIVER_ALLOW_CREATEFILE_ACLS 1
- +/*
- + * |NFS41_WINSTREAMS_SUPPORT| - Enable Win32 named streams support using
- + * NFSv4.1 named attributes
- + */
- +#define NFS41_WINSTREAMS_SUPPORT 1
- +
- #endif /* !_NFS41_DRIVER_BUILDFEATURES_ */
- diff --git a/sys/nfs41sys_fileinfo.c b/sys/nfs41sys_fileinfo.c
- index f871915..f5dd208 100644
- --- a/sys/nfs41sys_fileinfo.c
- +++ b/sys/nfs41sys_fileinfo.c
- @@ -398,6 +398,9 @@ NTSTATUS nfs41_QueryFileInformation(
- case FileStatInformation:
- case FileStatLxInformation:
- #endif /* NFS41_DRIVER_WSL_SUPPORT */
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + case FileStreamInformation:
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- break;
- default:
- print_error("nfs41_QueryFileInformation: "
- @@ -500,6 +503,9 @@ NTSTATUS nfs41_QueryFileInformation(
- case FileStatInformation:
- case FileStatLxInformation:
- #endif /* NFS41_DRIVER_WSL_SUPPORT */
- +#ifdef NFS41_WINSTREAMS_SUPPORT
- + case FileStreamInformation:
- +#endif /* NFS41_WINSTREAMS_SUPPORT */
- break;
- default:
- print_error("nfs41_QueryFileInformation: "
- diff --git a/sys/nfs41sys_openclose.c b/sys/nfs41sys_openclose.c
- index 93c95b1..d9cc74f 100644
- --- a/sys/nfs41sys_openclose.c
- +++ b/sys/nfs41sys_openclose.c
- @@ -574,11 +574,13 @@ NTSTATUS check_nfs41_create_args(
- goto out;
- }
- +#ifndef NFS41_WINSTREAMS_SUPPORT
- if (isStream(SrvOpen->pAlreadyPrefixedName)) {
- DbgP("nfs41_Create: Streams not supported (yet)\n");
- status = STATUS_NOT_SUPPORTED;
- goto out;
- }
- +#endif /* !NFS41_WINSTREAMS_SUPPORT */
- if (pVNetRootContext->read_only &&
- (params->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
- diff --git a/tests/manual_testing.txt b/tests/manual_testing.txt
- index a520bfb..a653a07 100644
- --- a/tests/manual_testing.txt
- +++ b/tests/manual_testing.txt
- @@ -1,5 +1,5 @@
- #
- -# ms-nfs41-client manual testing sequence, 2025-12-12
- +# ms-nfs41-client manual testing sequence, 2025-12-31
- #
- # Draft version, needs to be turned into automated tests
- # if possible
- @@ -218,6 +218,20 @@ $ rm -f d1 d2 d1.diff ; printf '1\n2\n' >d1 ; cp d1 d2 ; printf '3\n' >> d2 ; di
- The test should print "# test OK"
- +#
- +# Tests for Win32 streams
- +#
- +
- +# this test should print $'dir_line1\ndir_line2\n'
- +rm -Rf dir1 && mkdir dir1 && cmd /c 'echo dir_line1 >>dir1:dirstr1 & echo dir_line2 >>dir1:dirstr1 & cat <dir1:dirstr1'
- +# these three tests should print $'file1data\nfile1_line1\nfile1_line2\n'
- +rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:filestr1 & echo file1_line2 >>file1:filestr1:$DATA & C:\cygwin64\bin\cat.exe <file1 & C:\cygwin64\bin\cat.exe <file1:filestr1'
- +rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:filestr1 & echo file1_line2 >>file1:filestr1:$DATA & type file1 & type file1:filestr1'
- +rm -f file1 && echo "file1data" >file1 && cmd /c 'echo file1_line1 >>file1:filestr1 & echo file1_line2 >>file1:filestr1:$DATA & type file1::$DATA & type file1:filestr1:$DATA'
- +
- +# FIXME: tests for stream removal and enumeration
- +
- +
- #
- # Tests for Cycgwin/UWIN/SFU Nfs3Attr EA-based local uid/gid
- #
Win32 streams support
Posted by Anonymous on Wed 31st Dec 2025 00:10
raw | new post
view followups (newest first): Win32 streams support by Anonymous
modification of post by Anonymous (view diff)
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.
nrubsig.kpaste.net RSS