[NFSD]
authorPierre Schweitzer <pierre@reactos.org>
Mon, 19 Jun 2017 07:57:04 +0000 (07:57 +0000)
committerPierre Schweitzer <pierre@reactos.org>
Mon, 19 Jun 2017 07:57:04 +0000 (07:57 +0000)
Import the nfsd deamon from the nfs41 project.

CORE-8204

svn path=/trunk/; revision=75114

62 files changed:
reactos/base/services/CMakeLists.txt
reactos/base/services/nfsd/CMakeLists.txt [new file with mode: 0644]
reactos/base/services/nfsd/acl.c [new file with mode: 0644]
reactos/base/services/nfsd/callback_server.c [new file with mode: 0644]
reactos/base/services/nfsd/callback_xdr.c [new file with mode: 0644]
reactos/base/services/nfsd/daemon_debug.c [new file with mode: 0644]
reactos/base/services/nfsd/daemon_debug.h [new file with mode: 0644]
reactos/base/services/nfsd/delegation.c [new file with mode: 0644]
reactos/base/services/nfsd/delegation.h [new file with mode: 0644]
reactos/base/services/nfsd/ea.c [new file with mode: 0644]
reactos/base/services/nfsd/from_kernel.h [new file with mode: 0644]
reactos/base/services/nfsd/getattr.c [new file with mode: 0644]
reactos/base/services/nfsd/idmap.c [new file with mode: 0644]
reactos/base/services/nfsd/idmap.h [new file with mode: 0644]
reactos/base/services/nfsd/list.h [new file with mode: 0644]
reactos/base/services/nfsd/lock.c [new file with mode: 0644]
reactos/base/services/nfsd/lookup.c [new file with mode: 0644]
reactos/base/services/nfsd/makefile [new file with mode: 0644]
reactos/base/services/nfsd/mount.c [new file with mode: 0644]
reactos/base/services/nfsd/ms-nfs41-idmap.conf [new file with mode: 0644]
reactos/base/services/nfsd/name_cache.c [new file with mode: 0644]
reactos/base/services/nfsd/name_cache.h [new file with mode: 0644]
reactos/base/services/nfsd/namespace.c [new file with mode: 0644]
reactos/base/services/nfsd/netconfig [new file with mode: 0644]
reactos/base/services/nfsd/nfs41.h [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_callback.h [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_client.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_compound.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_compound.h [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_const.h [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_daemon.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_ops.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_ops.h [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_rpc.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_server.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_session.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_superblock.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_types.h [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_xdr.c [new file with mode: 0644]
reactos/base/services/nfsd/nfs41_xdr.h [new file with mode: 0644]
reactos/base/services/nfsd/nfsd.rc [new file with mode: 0644]
reactos/base/services/nfsd/open.c [new file with mode: 0644]
reactos/base/services/nfsd/pnfs.h [new file with mode: 0644]
reactos/base/services/nfsd/pnfs_debug.c [new file with mode: 0644]
reactos/base/services/nfsd/pnfs_device.c [new file with mode: 0644]
reactos/base/services/nfsd/pnfs_io.c [new file with mode: 0644]
reactos/base/services/nfsd/pnfs_layout.c [new file with mode: 0644]
reactos/base/services/nfsd/readdir.c [new file with mode: 0644]
reactos/base/services/nfsd/readwrite.c [new file with mode: 0644]
reactos/base/services/nfsd/recovery.c [new file with mode: 0644]
reactos/base/services/nfsd/recovery.h [new file with mode: 0644]
reactos/base/services/nfsd/service.c [new file with mode: 0644]
reactos/base/services/nfsd/service.h [new file with mode: 0644]
reactos/base/services/nfsd/setattr.c [new file with mode: 0644]
reactos/base/services/nfsd/sources [new file with mode: 0644]
reactos/base/services/nfsd/symlink.c [new file with mode: 0644]
reactos/base/services/nfsd/tree.h [new file with mode: 0644]
reactos/base/services/nfsd/upcall.c [new file with mode: 0644]
reactos/base/services/nfsd/upcall.h [new file with mode: 0644]
reactos/base/services/nfsd/util.c [new file with mode: 0644]
reactos/base/services/nfsd/util.h [new file with mode: 0644]
reactos/base/services/nfsd/volume.c [new file with mode: 0644]

index 41557e9..e49a340 100644 (file)
@@ -2,6 +2,7 @@
 add_subdirectory(audiosrv)
 add_subdirectory(dhcpcsvc)
 add_subdirectory(eventlog)
 add_subdirectory(audiosrv)
 add_subdirectory(dhcpcsvc)
 add_subdirectory(eventlog)
+add_subdirectory(nfsd)
 add_subdirectory(rpcss)
 add_subdirectory(schedsvc)
 add_subdirectory(shsvcs)
 add_subdirectory(rpcss)
 add_subdirectory(schedsvc)
 add_subdirectory(shsvcs)
diff --git a/reactos/base/services/nfsd/CMakeLists.txt b/reactos/base/services/nfsd/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d7f3df3
--- /dev/null
@@ -0,0 +1,61 @@
+remove_definitions(-D_WIN32_WINNT=0x502)
+add_definitions(-D_WIN32_WINNT=0x600)
+add_definitions(-DNTDDI_VERSION=0x06010000)
+
+include_directories(
+    ${REACTOS_SOURCE_DIR}/dll/3rdparty/libtirpc/tirpc
+    ${REACTOS_SOURCE_DIR}/drivers/filesystems/nfs
+    ${REACTOS_SOURCE_DIR}/dll/np/nfs)
+
+list(APPEND SOURCE 
+    nfs41_daemon.c
+    daemon_debug.c
+    nfs41_ops.c
+    nfs41_compound.c
+    nfs41_xdr.c
+       nfs41_server.c
+    nfs41_client.c
+    nfs41_superblock.c
+    nfs41_session.c
+    lookup.c
+       mount.c
+    open.c
+    readwrite.c
+    lock.c
+    readdir.c
+    getattr.c
+    setattr.c
+    upcall.c
+       nfs41_rpc.c
+    util.c
+    pnfs_layout.c
+    pnfs_device.c
+    pnfs_debug.c
+    pnfs_io.c
+       name_cache.c
+    namespace.c
+    volume.c
+    callback_server.c
+    callback_xdr.c
+       service.c
+    symlink.c
+    idmap.c
+    delegation.c
+    recovery.c
+    acl.c
+    ea.c)
+
+add_executable(nfsd ${SOURCE} nfsd.rc)
+
+if(MSVC)
+else()
+    # FIXME: Tons of warnings.
+    replace_compile_flags("-Werror" " ")
+endif()
+
+set_module_type(nfsd win32cui)
+target_link_libraries(nfsd wldap32)
+add_importlibs(nfsd advapi32 iphlpapi kernel32 kernel32_vista tirpc ntdll msvcrt shell32 ws2_32)
+add_cd_file(TARGET nfsd DESTINATION reactos/system32 FOR all)
+add_cd_file(FILE "${CMAKE_CURRENT_SOURCE_DIR}/netconfig" DESTINATION reactos/system32/drivers/etc FOR all)
+add_cd_file(FILE "${CMAKE_CURRENT_SOURCE_DIR}/ms-nfs41-idmap.conf" DESTINATION reactos/system32/drivers/etc FOR all)
diff --git a/reactos/base/services/nfsd/acl.c b/reactos/base/services/nfsd/acl.c
new file mode 100644 (file)
index 0000000..1d3774a
--- /dev/null
@@ -0,0 +1,801 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <sddl.h>
+
+#include "nfs41_ops.h"
+#include "delegation.h"
+#include "daemon_debug.h"
+#include "util.h"
+#include "upcall.h"
+#include "nfs41_xdr.h"
+
+//#define DEBUG_ACLS
+#define ACLLVL 2 /* dprintf level for acl logging */
+
+extern char localdomain_name[NFS41_HOSTNAME_LEN];
+
+static int parse_getacl(unsigned char *buffer, uint32_t length, 
+                        nfs41_upcall *upcall)
+{
+    int status;
+    getacl_upcall_args *args = &upcall->args.getacl;
+
+    status = safe_read(&buffer, &length, &args->query, sizeof(args->query));
+    if (status) goto out;
+
+    dprintf(1, "parsing NFS41_ACL_QUERY: info_class=%d\n", args->query);
+out:
+    return status;
+}
+
+static int create_unknownsid(WELL_KNOWN_SID_TYPE type, PSID *sid, 
+                             DWORD *sid_len)
+{
+    int status;
+    *sid_len = 0;
+    *sid = NULL;
+
+    status = CreateWellKnownSid(type, NULL, *sid, sid_len);
+    dprintf(ACLLVL, "create_unknownsid: CreateWellKnownSid type %d returned %d "
+            "GetLastError %d sid len %d needed\n", type, status, 
+            GetLastError(), *sid_len); 
+    if (status) 
+        return ERROR_INTERNAL_ERROR;
+    status = GetLastError();
+    if (status != ERROR_INSUFFICIENT_BUFFER) 
+        return status;
+    *sid = malloc(*sid_len);
+    if (*sid == NULL) 
+        return ERROR_INSUFFICIENT_BUFFER;
+    status = CreateWellKnownSid(type, NULL, *sid, sid_len);
+    if (status) 
+        return ERROR_SUCCESS;
+    free(*sid);
+    status = GetLastError();
+    eprintf("create_unknownsid: CreateWellKnownSid failed with %d\n", status);
+    return status;
+}
+
+static void convert_nfs4name_2_user_domain(LPSTR nfs4name, 
+                                           LPSTR *domain)
+{
+    LPSTR p = nfs4name;
+    for(; p[0] != '\0'; p++) {
+        if (p[0] == '@') { 
+            p[0] = '\0';
+            *domain = &p[1];
+            break;
+        }
+    }
+}
+
+static int map_name_2_sid(DWORD *sid_len, PSID *sid, LPCSTR name)
+{
+    int status = ERROR_INTERNAL_ERROR;
+    SID_NAME_USE sid_type;
+    LPSTR tmp_buf = NULL;
+    DWORD tmp = 0;
+
+    status = LookupAccountName(NULL, name, NULL, sid_len, NULL, &tmp, &sid_type);
+    dprintf(ACLLVL, "map_name_2_sid: LookupAccountName for %s returned %d "
+            "GetLastError %d name len %d domain len %d\n", name, status, 
+            GetLastError(), *sid_len, tmp); 
+    if (status)
+        return ERROR_INTERNAL_ERROR;
+
+    status = GetLastError();
+    switch(status) {
+    case ERROR_INSUFFICIENT_BUFFER:
+        *sid = malloc(*sid_len);
+        if (*sid == NULL) {
+            status = GetLastError();
+            goto out;
+        }
+        tmp_buf = (LPSTR) malloc(tmp);
+        if (tmp_buf == NULL)
+            goto out_free_sid;
+        status = LookupAccountName(NULL, name, *sid, sid_len, tmp_buf, 
+                                    &tmp, &sid_type);
+        free(tmp_buf);
+        if (!status) {
+            eprintf("map_name_2_sid: LookupAccountName for %s failed "
+                    "with %d\n", name, GetLastError());
+            goto out_free_sid;
+        } else {
+#ifdef DEBUG_ACLS
+            LPSTR ssid = NULL;
+            if (IsValidSid(*sid))
+                if (ConvertSidToStringSidA(*sid, &ssid))
+                    dprintf(1, "map_name_2_sid: sid_type = %d SID %s\n", 
+                            sid_type, ssid);
+                else
+                    dprintf(1, "map_name_2_sid: ConvertSidToStringSidA failed "
+                            "with %d\n", GetLastError());
+            else
+                dprintf(1, "map_name_2_sid: Invalid Sid ?\n");
+            if (ssid) LocalFree(ssid);
+#endif
+        }
+        status = ERROR_SUCCESS;
+        break;
+    case ERROR_NONE_MAPPED:
+        status = create_unknownsid(WinNullSid, sid, sid_len);
+        if (status)
+            goto out_free_sid;
+    }
+out:
+    return status;
+out_free_sid:
+    status = GetLastError();
+    free(*sid);
+    goto out;
+}
+
+static void free_sids(PSID *sids, int count)
+{
+    int i;
+    for(i = 0; i < count; i++)
+        free(sids[i]);
+    free(sids);
+}
+
+static int check_4_special_identifiers(char *who, PSID *sid, DWORD *sid_len, 
+                                       BOOLEAN *flag)
+{
+    int status = ERROR_SUCCESS;
+    WELL_KNOWN_SID_TYPE type = 0;
+    *flag = TRUE;
+    if (!strncmp(who, ACE4_OWNER, strlen(ACE4_OWNER)-1))
+        type = WinCreatorOwnerSid;
+    else if (!strncmp(who, ACE4_GROUP, strlen(ACE4_GROUP)-1))
+        type = WinCreatorGroupSid;
+    else if (!strncmp(who, ACE4_EVERYONE, strlen(ACE4_EVERYONE)-1))
+        type = WinWorldSid;
+    else if (!strncmp(who, ACE4_NOBODY, strlen(ACE4_NOBODY)))
+        type = WinNullSid;
+    else 
+        *flag = FALSE;
+    if (*flag) 
+        status = create_unknownsid(type, sid, sid_len);
+    return status;
+}
+
+static int convert_nfs4acl_2_dacl(nfsacl41 *acl, int file_type, 
+                                  PACL *dacl_out, PSID **sids_out)
+{
+    int status = ERROR_NOT_SUPPORTED, size = 0;
+    uint32_t i;
+    DWORD sid_len;
+    PSID *sids;
+    PACL dacl;
+    LPSTR domain = NULL;
+    BOOLEAN flag;
+
+    sids = malloc(acl->count * sizeof(PSID));
+    if (sids == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+    for (i = 0; i < acl->count; i++) {
+        convert_nfs4name_2_user_domain(acl->aces[i].who, &domain);
+        dprintf(ACLLVL, "handle_getacl: for user=%s domain=%s\n", 
+                acl->aces[i].who, domain?domain:"<null>");
+        status = check_4_special_identifiers(acl->aces[i].who, &sids[i], 
+                                             &sid_len, &flag);
+        if (status) {
+            free_sids(sids, i);
+            goto out;
+        }
+        if (!flag) {
+            status = map_name_2_sid(&sid_len, &sids[i], acl->aces[i].who);
+            if (status) {
+                free_sids(sids, i);
+                goto out;
+            }
+        } 
+        size += sid_len - sizeof(DWORD);
+    }
+    size += sizeof(ACL) + (sizeof(ACCESS_ALLOWED_ACE)*acl->count);
+    size = (size + sizeof(DWORD) - 1) & 0xfffffffc; //align size on word boundry
+    dacl = malloc(size);
+    if (dacl == NULL)
+        goto out_free_sids;
+    
+    if (InitializeAcl(dacl, size, ACL_REVISION)) {
+        ACCESS_MASK mask;
+        for (i = 0; i < acl->count; i++) {
+            // nfs4 acemask should be exactly the same as file access mask
+            mask = acl->aces[i].acemask;
+            dprintf(ACLLVL, "access mask %x ace type %s\n", mask, 
+                acl->aces[i].acetype?"DENIED ACE":"ALLOWED ACE");
+            if (acl->aces[i].acetype == ACE4_ACCESS_ALLOWED_ACE_TYPE) {
+                status = AddAccessAllowedAce(dacl, ACL_REVISION, mask, sids[i]);
+                if (!status) {
+                    eprintf("convert_nfs4acl_2_dacl: AddAccessAllowedAce failed "
+                            "with %d\n", status);
+                    goto out_free_dacl;
+                }
+                else status = ERROR_SUCCESS;
+            } else if (acl->aces[i].acetype == ACE4_ACCESS_DENIED_ACE_TYPE) {
+                status = AddAccessDeniedAce(dacl, ACL_REVISION, mask, sids[i]);
+                if (!status) {
+                    eprintf("convert_nfs4acl_2_dacl: AddAccessDeniedAce failed "
+                            "with %d\n", status);
+                    goto out_free_dacl;
+                }
+                else status = ERROR_SUCCESS;
+            } else {
+                eprintf("convert_nfs4acl_2_dacl: unknown acetype %d\n", 
+                        acl->aces[i].acetype);
+                status = ERROR_INTERNAL_ERROR;
+                free(dacl);
+                free_sids(sids, acl->count);
+                goto out;
+            }
+        }
+    } else {
+        eprintf("convert_nfs4acl_2_dacl: InitializeAcl failed with %d\n", status);
+        goto out_free_dacl;
+    }
+    status = ERROR_SUCCESS;
+    *sids_out = sids;
+    *dacl_out = dacl;
+out:
+    return status;
+out_free_dacl:
+    free(dacl);
+out_free_sids:
+    free_sids(sids, acl->count);
+    status = GetLastError();
+    goto out;
+}
+
+static int handle_getacl(nfs41_upcall *upcall)
+{
+    int status = ERROR_NOT_SUPPORTED;
+    getacl_upcall_args *args = &upcall->args.getacl;
+    nfs41_open_state *state = upcall->state_ref;
+    nfs41_file_info info = { 0 };
+    bitmap4 attr_request = { 0 };
+    LPSTR domain = NULL;
+    SECURITY_DESCRIPTOR sec_desc;
+    PACL dacl = NULL;
+    PSID *sids = NULL;
+    PSID osid = NULL, gsid = NULL;
+    DWORD sid_len;
+    char owner[NFS4_OPAQUE_LIMIT], group[NFS4_OPAQUE_LIMIT];
+    nfsacl41 acl = { 0 };
+
+    // need to cache owner/group information XX
+    attr_request.count = 2;
+    attr_request.arr[1] = FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP;
+    if (args->query & DACL_SECURITY_INFORMATION) {
+        info.acl = &acl;
+        attr_request.arr[0] |= FATTR4_WORD0_ACL;
+    }
+    info.owner = owner;
+    info.owner_group = group;
+    status = nfs41_getattr(state->session, &state->file, &attr_request, &info);
+    if (status) {
+        eprintf("handle_getacl: nfs41_cached_getattr() failed with %d\n", 
+                status);
+        goto out;
+    }
+
+    status = InitializeSecurityDescriptor(&sec_desc, 
+                                          SECURITY_DESCRIPTOR_REVISION);
+    if (!status) {
+        status = GetLastError();
+        eprintf("handle_getacl: InitializeSecurityDescriptor failed with %d\n", 
+                status);
+        goto out;
+    }
+     /* can't (re)use the same sid variable for both owner and group sids 
+      * because security descriptor is created in absolute-form and it just
+      * stores pointers to the sids. thus each owner and group needs its own
+      * memory. free them after creating self-relative security descriptor. 
+      */
+    if (args->query & OWNER_SECURITY_INFORMATION) {
+        // parse user@domain. currently ignoring domain part XX
+        convert_nfs4name_2_user_domain(info.owner, &domain);
+        dprintf(ACLLVL, "handle_getacl: OWNER_SECURITY_INFORMATION: for user=%s "
+                "domain=%s\n", info.owner, domain?domain:"<null>");
+        sid_len = 0;
+        status = map_name_2_sid(&sid_len, &osid, info.owner);
+        if (status)
+            goto out;
+        status = SetSecurityDescriptorOwner(&sec_desc, osid, TRUE);
+        if (!status) {
+            status = GetLastError();
+            eprintf("handle_getacl: SetSecurityDescriptorOwner failed with "
+                    "%d\n", status);
+            goto out;
+        }
+    }
+    if (args->query & GROUP_SECURITY_INFORMATION) {
+        convert_nfs4name_2_user_domain(info.owner_group, &domain);
+        dprintf(ACLLVL, "handle_getacl: GROUP_SECURITY_INFORMATION: for %s "
+                "domain=%s\n", info.owner_group, domain?domain:"<null>");
+        sid_len = 0;
+        status = map_name_2_sid(&sid_len, &gsid, info.owner_group);
+        if (status)
+            goto out;
+        status = SetSecurityDescriptorGroup(&sec_desc, gsid, TRUE);
+        if (!status) {
+            status = GetLastError();
+            eprintf("handle_getacl: SetSecurityDescriptorGroup failed with "
+                    "%d\n", status);
+            goto out;
+        }
+    }
+    if (args->query & DACL_SECURITY_INFORMATION) {
+        dprintf(ACLLVL, "handle_getacl: DACL_SECURITY_INFORMATION\n");
+        status = convert_nfs4acl_2_dacl(info.acl, state->type, &dacl, &sids);
+        if (status)
+            goto out;
+        status = SetSecurityDescriptorDacl(&sec_desc, TRUE, dacl, TRUE);
+        if (!status) {
+            status = GetLastError();
+            eprintf("handle_getacl: SetSecurityDescriptorDacl failed with "
+                    "%d\n", status);
+            goto out;
+        }
+    }
+
+    args->sec_desc_len = 0;
+    status = MakeSelfRelativeSD(&sec_desc, args->sec_desc, &args->sec_desc_len);
+    if (status) {
+        status = ERROR_INTERNAL_ERROR;
+        goto out;
+    }
+    status = GetLastError();
+    if (status != ERROR_INSUFFICIENT_BUFFER) {
+        eprintf("handle_getacl: MakeSelfRelativeSD failes with %d\n", status);
+        goto out;
+    }
+    args->sec_desc = malloc(args->sec_desc_len);
+    if (args->sec_desc == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+    status = MakeSelfRelativeSD(&sec_desc, args->sec_desc, &args->sec_desc_len);
+    if (!status) {
+        status = GetLastError();
+        eprintf("handle_getacl: MakeSelfRelativeSD failes with %d\n", status);
+        free(args->sec_desc);
+        goto out;
+    } else status = ERROR_SUCCESS;
+
+out:
+    if (args->query & OWNER_SECURITY_INFORMATION) {
+        if (osid) free(osid);
+    }
+    if (args->query & GROUP_SECURITY_INFORMATION) {
+        if (gsid) free(gsid);
+    }
+    if (args->query & DACL_SECURITY_INFORMATION) {
+        if (sids) free_sids(sids, info.acl->count);
+        free(dacl);
+        nfsacl41_free(info.acl);
+    }
+    return status;
+}
+
+static int marshall_getacl(unsigned char *buffer, uint32_t *length, 
+                           nfs41_upcall *upcall)
+{
+    int status = ERROR_NOT_SUPPORTED;
+    getacl_upcall_args *args = &upcall->args.getacl;
+
+    status = safe_write(&buffer, length, &args->sec_desc_len, sizeof(DWORD));
+    if (status) goto out;
+    status = safe_write(&buffer, length, args->sec_desc, args->sec_desc_len);
+    free(args->sec_desc);
+    if (status) goto out;
+out:
+    return status;
+}
+
+const nfs41_upcall_op nfs41_op_getacl = {
+    parse_getacl,
+    handle_getacl,
+    marshall_getacl
+};
+
+static int parse_setacl(unsigned char *buffer, uint32_t length, 
+                        nfs41_upcall *upcall)
+{
+    int status;
+    setacl_upcall_args *args = &upcall->args.setacl;
+    ULONG sec_desc_len;
+
+    status = safe_read(&buffer, &length, &args->query, sizeof(args->query));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &sec_desc_len, sizeof(ULONG));
+    if (status) goto out;
+    args->sec_desc = (PSECURITY_DESCRIPTOR)buffer;
+
+    dprintf(1, "parsing NFS41_ACL_SET: info_class=%d sec_desc_len=%d\n", 
+            args->query, sec_desc_len);
+out:
+    return status;
+}
+
+static int is_well_known_sid(PSID sid, char *who) 
+{
+    int status, i;
+    for (i = 0; i < 78; i++) {
+        status = IsWellKnownSid(sid, (WELL_KNOWN_SID_TYPE)i);
+        if (!status) continue;
+        else {
+            dprintf(ACLLVL, "WELL_KNOWN_SID_TYPE %d\n", i);
+            switch((WELL_KNOWN_SID_TYPE)i) {
+            case WinCreatorOwnerSid:
+                memcpy(who, ACE4_OWNER, strlen(ACE4_OWNER)+1); 
+                return TRUE;
+            case WinNullSid:
+                memcpy(who, ACE4_NOBODY, strlen(ACE4_NOBODY)+1); 
+                return TRUE;
+            case WinAnonymousSid:
+                memcpy(who, ACE4_ANONYMOUS, strlen(ACE4_ANONYMOUS)+1); 
+                return TRUE;
+            case WinWorldSid:
+                memcpy(who, ACE4_EVERYONE, strlen(ACE4_EVERYONE)+1); 
+                return TRUE;
+            case WinCreatorGroupSid:
+            case WinBuiltinUsersSid:
+                memcpy(who, ACE4_GROUP, strlen(ACE4_GROUP)+1); 
+                return TRUE;
+            case WinAuthenticatedUserSid:
+                memcpy(who, ACE4_AUTHENTICATED, strlen(ACE4_AUTHENTICATED)+1); 
+                return TRUE;
+            case WinDialupSid:
+                memcpy(who, ACE4_DIALUP, strlen(ACE4_DIALUP)+1); 
+                return TRUE;
+            case WinNetworkSid:
+                memcpy(who, ACE4_NETWORK, strlen(ACE4_NETWORK)+1); 
+                return TRUE;
+            case WinBatchSid:
+                memcpy(who, ACE4_BATCH, strlen(ACE4_BATCH)+1); 
+                return TRUE;
+            case WinInteractiveSid:
+                memcpy(who, ACE4_INTERACTIVE, strlen(ACE4_INTERACTIVE)+1); 
+                return TRUE;
+            case WinNetworkServiceSid:
+            case WinLocalServiceSid:
+            case WinServiceSid:
+                memcpy(who, ACE4_SERVICE, strlen(ACE4_SERVICE)+1); 
+                return TRUE;
+            default: return FALSE;
+            }
+        }
+    }
+    return FALSE;
+}
+
+static void map_aceflags(BYTE win_aceflags, uint32_t *nfs4_aceflags)
+{
+    if (win_aceflags & OBJECT_INHERIT_ACE)
+        *nfs4_aceflags |= ACE4_FILE_INHERIT_ACE;
+    if (win_aceflags & CONTAINER_INHERIT_ACE)
+        *nfs4_aceflags |= ACE4_DIRECTORY_INHERIT_ACE;
+    if (win_aceflags & NO_PROPAGATE_INHERIT_ACE)
+        *nfs4_aceflags |= ACE4_NO_PROPAGATE_INHERIT_ACE;
+    if (win_aceflags & INHERIT_ONLY_ACE)
+        *nfs4_aceflags |= ACE4_INHERIT_ONLY_ACE;
+    if (win_aceflags & INHERITED_ACE)
+        *nfs4_aceflags |= ACE4_INHERITED_ACE;
+    dprintf(ACLLVL, "ACE FLAGS: %x nfs4 aceflags %x\n", 
+            win_aceflags, *nfs4_aceflags);
+}
+
+static void map_acemask(ACCESS_MASK mask, int file_type, uint32_t *nfs4_mask)
+{
+    dprintf(ACLLVL, "ACE MASK: %x\n", mask);
+    print_windows_access_mask(0, mask);
+    /* check if any GENERIC bits set */
+    if (mask & 0xf000000) {
+        if (mask & GENERIC_ALL) {
+            if (file_type == NF4DIR)
+                *nfs4_mask |= ACE4_ALL_DIR;
+            else
+                *nfs4_mask |= ACE4_ALL_FILE;
+        } else {
+            if (mask & GENERIC_READ)
+                *nfs4_mask |= ACE4_GENERIC_READ;
+            if (mask & GENERIC_WRITE)
+                *nfs4_mask |= ACE4_GENERIC_WRITE;
+            if (mask & GENERIC_EXECUTE)
+                *nfs4_mask |= ACE4_GENERIC_EXECUTE;
+        }
+    }
+    else /* ignoring generic and reserved bits */
+        *nfs4_mask = mask & 0x00ffffff;
+    print_nfs_access_mask(0, *nfs4_mask);
+}
+
+static int map_nfs4ace_who(PSID sid, PSID owner_sid, PSID group_sid, char *who_out, char *domain)
+{
+    int status = ERROR_INTERNAL_ERROR;
+    DWORD size = 0, tmp_size = 0;
+    SID_NAME_USE sid_type;
+    LPSTR tmp_buf = NULL, who = NULL;
+
+    /* for ace mapping, we want to map owner's sid into "owner@" 
+     * but for set_owner attribute we want to map owner into a user name
+     * same applies to group
+     */
+    status = 0;
+    if (owner_sid) {
+        if (EqualSid(sid, owner_sid)) {
+            dprintf(ACLLVL, "map_nfs4ace_who: this is owner's sid\n");
+            memcpy(who_out, ACE4_OWNER, strlen(ACE4_OWNER)+1); 
+            return ERROR_SUCCESS;
+        }
+    }
+    if (group_sid) {
+        if (EqualSid(sid, group_sid)) {
+            dprintf(ACLLVL, "map_nfs4ace_who: this is group's sid\n");
+            memcpy(who_out, ACE4_GROUP, strlen(ACE4_GROUP)+1); 
+            return ERROR_SUCCESS;
+        }
+    }
+    status = is_well_known_sid(sid, who_out);
+    if (status) {
+        if (!strncmp(who_out, ACE4_NOBODY, strlen(ACE4_NOBODY))) {
+            size = (DWORD)strlen(ACE4_NOBODY);
+            goto add_domain;
+        }
+        else
+            return ERROR_SUCCESS;
+    }
+
+    status = LookupAccountSid(NULL, sid, who, &size, tmp_buf, 
+        &tmp_size, &sid_type);
+    dprintf(ACLLVL, "map_nfs4ace_who: LookupAccountSid returned %d GetLastError "
+            "%d name len %d domain len %d\n", status, GetLastError(), 
+            size, tmp_size); 
+    if (status)
+        return ERROR_INTERNAL_ERROR;
+    status = GetLastError();
+    if (status != ERROR_INSUFFICIENT_BUFFER)
+        return ERROR_INTERNAL_ERROR;
+    who = malloc(size);
+    if (who == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+    tmp_buf = malloc(tmp_size);
+    if (tmp_buf == NULL)
+        goto out_free_who;
+    status = LookupAccountSid(NULL, sid, who, &size, tmp_buf, 
+                                &tmp_size, &sid_type);
+    free(tmp_buf);
+    if (!status) {
+        eprintf("map_nfs4ace_who: LookupAccountSid failed with %d\n", 
+                GetLastError());
+        goto out_free_who;
+    }
+    memcpy(who_out, who, size);
+add_domain:
+    memcpy(who_out+size, "@", sizeof(char));
+    memcpy(who_out+size+1, domain, strlen(domain)+1);
+    dprintf(ACLLVL, "map_nfs4ace_who: who=%s\n", who_out);
+    if (who) free(who);
+    status = ERROR_SUCCESS;
+out:
+    return status;
+out_free_who:
+    free(who);
+    status = GetLastError();
+    goto out;
+}
+static int map_dacl_2_nfs4acl(PACL acl, PSID sid, PSID gsid, nfsacl41 *nfs4_acl, 
+                                int file_type, char *domain)
+{
+    int status;
+    if (acl == NULL) {
+        dprintf(ACLLVL, "this is a NULL dacl: all access to an object\n");
+        nfs4_acl->count = 1;
+        nfs4_acl->aces = calloc(1, sizeof(nfsace4));
+        if (nfs4_acl->aces == NULL) {
+            status = GetLastError();
+            goto out;
+        }
+        nfs4_acl->flag = 0;
+        memcpy(nfs4_acl->aces->who, ACE4_EVERYONE, strlen(ACE4_EVERYONE)+1);
+        nfs4_acl->aces->acetype = ACE4_ACCESS_ALLOWED_ACE_TYPE;
+        if (file_type == NF4DIR)
+            nfs4_acl->aces->acemask = ACE4_ALL_DIR;
+        else
+            nfs4_acl->aces->acemask = ACE4_ALL_FILE;
+        nfs4_acl->aces->aceflag = 0;
+    } else {
+        int i;
+        PACE_HEADER ace;
+        PBYTE tmp_pointer;
+
+        dprintf(ACLLVL, "NON-NULL dacl with %d ACEs\n", acl->AceCount);
+        print_hexbuf_no_asci(3, (unsigned char *)"ACL\n", 
+                            (unsigned char *)acl, acl->AclSize);
+        nfs4_acl->count = acl->AceCount;
+        nfs4_acl->aces = calloc(nfs4_acl->count, sizeof(nfsace4));
+        if (nfs4_acl->aces == NULL) {
+            status = GetLastError();
+            goto out;
+        }
+        nfs4_acl->flag = 0;
+        for (i = 0; i < acl->AceCount; i++) {
+            status = GetAce(acl, i, &ace);
+            if (!status) {
+                status = GetLastError();
+                eprintf("map_dacl_2_nfs4acl: GetAce failed with %d\n", status);
+                goto out_free;
+            }
+            tmp_pointer = (PBYTE)ace;
+            print_hexbuf_no_asci(3, (unsigned char *)"ACE\n", 
+                                    (unsigned char *)ace, ace->AceSize); 
+            dprintf(ACLLVL, "ACE TYPE: %x\n", ace->AceType);
+            if (ace->AceType == ACCESS_ALLOWED_ACE_TYPE)
+                nfs4_acl->aces[i].acetype = ACE4_ACCESS_ALLOWED_ACE_TYPE;
+            else if (ace->AceType == ACCESS_DENIED_ACE_TYPE)
+                nfs4_acl->aces[i].acetype = ACE4_ACCESS_DENIED_ACE_TYPE;
+            else {
+                eprintf("map_dacl_2_nfs4acl: unsupported ACE type %d\n",
+                    ace->AceType);
+                status = ERROR_NOT_SUPPORTED;
+                goto out_free;
+            }
+
+            map_aceflags(ace->AceFlags, &nfs4_acl->aces[i].aceflag);            
+            map_acemask(*(PACCESS_MASK)(ace + 1), file_type, 
+                        &nfs4_acl->aces[i].acemask);
+
+            tmp_pointer += sizeof(ACCESS_MASK) + sizeof(ACE_HEADER);
+            status = map_nfs4ace_who(tmp_pointer, sid, gsid, nfs4_acl->aces[i].who, 
+                                     domain);
+            if (status)
+                goto out_free;
+        }
+    }
+    status = ERROR_SUCCESS;
+out:
+    return status;
+out_free:
+    free(nfs4_acl->aces);
+    goto out;
+}
+
+static int handle_setacl(nfs41_upcall *upcall)
+{
+    int status = ERROR_NOT_SUPPORTED;
+    setacl_upcall_args *args = &upcall->args.setacl;
+    nfs41_open_state *state = upcall->state_ref;
+    nfs41_file_info info = { 0 };
+    stateid_arg stateid;
+    nfsacl41 nfs4_acl = { 0 };
+    PSID sid = NULL, gsid = NULL;
+    BOOL sid_default, gsid_default;
+
+    if (args->query & OWNER_SECURITY_INFORMATION) {
+        char owner[NFS4_OPAQUE_LIMIT];
+        dprintf(ACLLVL, "handle_setacl: OWNER_SECURITY_INFORMATION\n");
+        status = GetSecurityDescriptorOwner(args->sec_desc, &sid, &sid_default);
+        if (!status) {
+            status = GetLastError();
+            eprintf("GetSecurityDescriptorOwner failed with %d\n", status);
+            goto out;
+        }
+        info.owner = owner;
+        status = map_nfs4ace_who(sid, NULL, NULL, info.owner, localdomain_name);
+        if (status)
+            goto out;
+        else {
+            info.attrmask.arr[1] |= FATTR4_WORD1_OWNER;
+            info.attrmask.count = 2;
+        }
+    }
+    if (args->query & GROUP_SECURITY_INFORMATION) {
+        char group[NFS4_OPAQUE_LIMIT];
+        dprintf(ACLLVL, "handle_setacl: GROUP_SECURITY_INFORMATION\n");
+        status = GetSecurityDescriptorGroup(args->sec_desc, &sid, &sid_default);
+        if (!status) {
+            status = GetLastError();
+            eprintf("GetSecurityDescriptorOwner failed with %d\n", status);
+            goto out;
+        }
+        info.owner_group = group;
+        status = map_nfs4ace_who(sid, NULL, NULL, info.owner_group, 
+                                 localdomain_name);
+        if (status)
+            goto out;
+        else {
+            info.attrmask.arr[1] |= FATTR4_WORD1_OWNER_GROUP;
+            info.attrmask.count = 2;
+        }
+    }
+    if (args->query & DACL_SECURITY_INFORMATION) {
+        BOOL dacl_present, dacl_default;
+        PACL acl;
+        dprintf(ACLLVL, "handle_setacl: DACL_SECURITY_INFORMATION\n");
+        status = GetSecurityDescriptorDacl(args->sec_desc, &dacl_present,
+                                            &acl, &dacl_default);
+        if (!status) {
+            status = GetLastError();
+            eprintf("GetSecurityDescriptorDacl failed with %d\n", status);
+            goto out;
+        }
+        status = GetSecurityDescriptorOwner(args->sec_desc, &sid, &sid_default);
+        if (!status) {
+            status = GetLastError();
+            eprintf("GetSecurityDescriptorOwner failed with %d\n", status);
+            goto out;
+        }
+        status = GetSecurityDescriptorGroup(args->sec_desc, &gsid, &gsid_default);
+        if (!status) {
+            status = GetLastError();
+            eprintf("GetSecurityDescriptorOwner failed with %d\n", status);
+            goto out;
+        }
+        status = map_dacl_2_nfs4acl(acl, sid, gsid, &nfs4_acl, state->type, 
+                                    localdomain_name);
+        if (status)
+            goto out;
+        else {
+            info.acl = &nfs4_acl;
+            info.attrmask.arr[0] |= FATTR4_WORD0_ACL;
+            if (!info.attrmask.count)
+                info.attrmask.count = 1;
+        }
+    }
+
+    /* break read delegations before SETATTR */
+    nfs41_delegation_return(state->session, &state->file,
+        OPEN_DELEGATE_WRITE, FALSE);
+
+    nfs41_open_stateid_arg(state, &stateid);
+    status = nfs41_setattr(state->session, &state->file, &stateid, &info);
+    if (status) {
+        dprintf(ACLLVL, "handle_setacl: nfs41_setattr() failed with error %s.\n",
+                nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_NOT_SUPPORTED);
+    }
+    args->ctime = info.change;
+    if (args->query & DACL_SECURITY_INFORMATION)
+        free(nfs4_acl.aces);
+out:
+    return status;
+}
+
+static int marshall_setacl(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    setacl_upcall_args *args = &upcall->args.setacl;
+    return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime));
+}
+
+const nfs41_upcall_op nfs41_op_setacl = {
+    parse_setacl,
+    handle_setacl,
+    marshall_setacl
+};
\ No newline at end of file
diff --git a/reactos/base/services/nfsd/callback_server.c b/reactos/base/services/nfsd/callback_server.c
new file mode 100644 (file)
index 0000000..dc569b6
--- /dev/null
@@ -0,0 +1,547 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+
+#include "nfs41_ops.h"
+#include "delegation.h"
+#include "nfs41_callback.h"
+#include "daemon_debug.h"
+
+
+#define CBSLVL 2 /* dprintf level for callback server logging */
+
+
+static const char g_server_tag[] = "ms-nfs41-callback";
+
+
+/* callback session */
+static void replay_cache_write(
+    IN nfs41_cb_session *session,
+    IN struct cb_compound_args *args,
+    IN struct cb_compound_res *res,
+    IN bool_t cachethis);
+
+void nfs41_callback_session_init(
+    IN nfs41_session *session)
+{
+    /* initialize the replay cache with status NFS4ERR_SEQ_MISORDERED */
+    struct cb_compound_res res = { 0 };
+    StringCchCopyA(res.tag.str, CB_COMPOUND_MAX_TAG, g_server_tag);
+    res.tag.len = sizeof(g_server_tag);
+    res.status = NFS4ERR_SEQ_MISORDERED;
+
+    session->cb_session.cb_sessionid = session->session_id;
+
+    replay_cache_write(&session->cb_session, NULL, &res, FALSE);
+}
+
+
+/* OP_CB_LAYOUTRECALL */
+static enum_t handle_cb_layoutrecall(
+    IN nfs41_rpc_clnt *rpc_clnt,
+    IN struct cb_layoutrecall_args *args,
+    OUT struct cb_layoutrecall_res *res)
+{
+    enum pnfs_status status;
+
+    status = pnfs_file_layout_recall(rpc_clnt->client, args);
+    switch (status) {
+    case PNFS_PENDING:
+        /* not enough information to process the recall yet */
+        res->status = NFS4ERR_DELAY;
+        break;
+    default:
+        /* forgetful model for layout recalls */
+        res->status = NFS4ERR_NOMATCHING_LAYOUT;
+        break;
+    }
+
+    dprintf(CBSLVL, "  OP_CB_LAYOUTRECALL { %s, %s, recall %u } %s\n",
+        pnfs_layout_type_string(args->type),
+        pnfs_iomode_string(args->iomode), args->recall.type,
+        nfs_error_string(res->status));
+    return res->status;
+}
+
+/* OP_CB_RECALL_SLOT */
+static enum_t handle_cb_recall_slot(
+    IN nfs41_rpc_clnt *rpc_clnt,
+    IN struct cb_recall_slot_args *args,
+    OUT struct cb_recall_slot_res *res)
+{
+    res->status = nfs41_session_recall_slot(rpc_clnt->client->session,
+        args->target_highest_slotid);
+
+    dprintf(CBSLVL, "  OP_CB_RECALL_SLOT { %u } %s\n",
+        args->target_highest_slotid, nfs_error_string(res->status));
+    return res->status;
+}
+
+/* OP_CB_SEQUENCE */
+static enum_t handle_cb_sequence(
+    IN nfs41_rpc_clnt *rpc_clnt,
+    IN struct cb_sequence_args *args,
+    OUT struct cb_sequence_res *res,
+    OUT nfs41_cb_session **session_out,
+    OUT bool_t *cachethis)
+{
+    nfs41_cb_session *cb_session = &rpc_clnt->client->session->cb_session;
+    uint32_t status = NFS4_OK;
+    res->status = NFS4_OK;
+
+    *session_out = cb_session;
+
+    /* validate the sessionid */
+    if (memcmp(cb_session->cb_sessionid, args->sessionid,
+            NFS4_SESSIONID_SIZE)) {
+        eprintf("[cb] received sessionid doesn't match session\n");
+        res->status = NFS4ERR_BADSESSION;
+        goto out;
+    }
+
+    /* we only support 1 slot for the back channel so slotid MUST be 0 */
+    if (args->slotid != 0) {
+        eprintf("[cb] received unexpected slotid=%d\n", args->slotid);
+        res->status = NFS4ERR_BADSLOT;
+        goto out;
+    }
+    if (args->highest_slotid != 0) {
+        eprintf("[cb] received unexpected highest_slotid=%d\n", 
+            args->highest_slotid);
+        res->status = NFS4ERR_BAD_HIGH_SLOT;
+        goto out;
+    }
+
+    /* check for a retry with the same seqid */
+    if (args->sequenceid == cb_session->cb_seqnum) {
+        if (!cb_session->replay.res.length) {
+            /* return success for sequence, but fail the next operation */
+            res->status = NFS4_OK;
+            status = NFS4ERR_RETRY_UNCACHED_REP;
+        } else {
+            /* return NFS4ERR_SEQ_FALSE_RETRY for all replays; if the retry
+             * turns out to be valid, this response will be replaced anyway */
+            status = res->status = NFS4ERR_SEQ_FALSE_RETRY;
+        }
+        goto out;
+    }
+
+    /* error on any unexpected seqids */
+    if (args->sequenceid != cb_session->cb_seqnum+1) {
+        eprintf("[cb] bad received seq#=%d, expected=%d\n", 
+            args->sequenceid, cb_session->cb_seqnum+1);
+        res->status = NFS4ERR_SEQ_MISORDERED;
+        goto out;
+    }
+
+    cb_session->cb_seqnum = args->sequenceid;
+    *cachethis = args->cachethis;
+
+    memcpy(res->ok.sessionid, args->sessionid, NFS4_SESSIONID_SIZE);
+    res->ok.sequenceid = args->sequenceid;
+    res->ok.slotid = args->slotid;
+    res->ok.highest_slotid = args->highest_slotid;
+    res->ok.target_highest_slotid = args->highest_slotid;
+
+out:
+    dprintf(CBSLVL, "  OP_CB_SEQUENCE { seqid %u, slot %u, cachethis %d } "
+        "%s\n", args->sequenceid, args->slotid, args->cachethis, 
+        nfs_error_string(res->status));
+    return status;
+}
+
+/* OP_CB_GETATTR */
+static enum_t handle_cb_getattr(
+    IN nfs41_rpc_clnt *rpc_clnt,
+    IN struct cb_getattr_args *args,
+    OUT struct cb_getattr_res *res)
+{
+    /* look up cached attributes for the given filehandle */
+    res->status = nfs41_delegation_getattr(rpc_clnt->client,
+        &args->fh, &args->attr_request, &res->info);
+    return res->status;
+}
+
+/* OP_CB_RECALL */
+static enum_t handle_cb_recall(
+    IN nfs41_rpc_clnt *rpc_clnt,
+    IN struct cb_recall_args *args,
+    OUT struct cb_recall_res *res)
+{
+    /* return the delegation asynchronously */
+    res->status = nfs41_delegation_recall(rpc_clnt->client,
+        &args->fh, &args->stateid, args->truncate);
+    return res->status;
+}
+
+/* OP_CB_NOTIFY_DEVICEID */
+static enum_t handle_cb_notify_deviceid(
+    IN nfs41_rpc_clnt *rpc_clnt,
+    IN struct cb_notify_deviceid_args *args,
+    OUT struct cb_notify_deviceid_res *res)
+{
+    uint32_t i;
+    for (i = 0; i < args->change_count; i++) {
+        pnfs_file_device_notify(rpc_clnt->client->devices,
+            &args->change_list[i]);
+    }
+    res->status = NFS4_OK;
+    return res->status;
+}
+
+static void replay_cache_write(
+    IN nfs41_cb_session *session,
+    IN OPTIONAL struct cb_compound_args *args,
+    IN struct cb_compound_res *res,
+    IN bool_t cachethis)
+{
+    XDR xdr;
+    uint32_t i;
+
+    session->replay.arg.length = 0;
+    session->replay.res.length = 0;
+
+    /* encode the reply directly into the replay cache */
+    xdrmem_create(&xdr, (char*)session->replay.res.buffer,
+        NFS41_MAX_SERVER_CACHE, XDR_ENCODE);
+
+    /* always try to cache the result */
+    if (proc_cb_compound_res(&xdr, res)) {
+        session->replay.res.length = XDR_GETPOS(&xdr);
+
+        if (args) {
+            /* encode the arguments into the request cache */
+            xdrmem_create(&xdr, (char*)session->replay.arg.buffer,
+                NFS41_MAX_SERVER_CACHE, XDR_ENCODE);
+
+            if (proc_cb_compound_args(&xdr, args))
+                session->replay.arg.length = XDR_GETPOS(&xdr);
+        }
+    } else if (cachethis) {
+        /* on failure, only return errors if caching was requested */
+        res->status = NFS4ERR_REP_TOO_BIG_TO_CACHE;
+
+        /* find the first operation that failed to encode */
+        for (i = 0; i < res->resarray_count; i++) {
+            if (!res->resarray[i].xdr_ok) {
+                res->resarray[i].res.status = NFS4ERR_REP_TOO_BIG_TO_CACHE;
+                res->resarray_count = i + 1;
+                break;
+            }
+        }
+    }
+}
+
+static bool_t replay_validate_args(
+    IN struct cb_compound_args *args,
+    IN const struct replay_cache *cache)
+{
+    char buffer[NFS41_MAX_SERVER_CACHE];
+    XDR xdr;
+
+    /* encode the current arguments into a temporary buffer */
+    xdrmem_create(&xdr, buffer, NFS41_MAX_SERVER_CACHE, XDR_ENCODE);
+
+    if (!proc_cb_compound_args(&xdr, args))
+        return FALSE;
+
+    /* must match the cached length */
+    if (XDR_GETPOS(&xdr) != cache->length)
+        return FALSE;
+
+    /* must match the cached buffer contents */
+    return memcmp(cache->buffer, buffer, cache->length) == 0;
+}
+
+static bool_t replay_validate_ops(
+    IN const struct cb_compound_args *args,
+    IN const struct cb_compound_res *res)
+{
+    uint32_t i;
+    for (i = 0; i < res->resarray_count; i++) {
+        /* can't have more operations than the request */
+        if (i >= args->argarray_count)
+            return FALSE;
+
+        /* each opnum must match the request */
+        if (args->argarray[i].opnum != res->resarray[i].opnum)
+            return FALSE;
+
+        if (res->resarray[i].res.status)
+            break;
+    }
+    return TRUE;
+}
+
+static int replay_cache_read(
+    IN nfs41_cb_session *session,
+    IN struct cb_compound_args *args,
+    OUT struct cb_compound_res **res_out)
+{
+    XDR xdr;
+    struct cb_compound_res *replay;
+    struct cb_compound_res *res = *res_out;
+    uint32_t status = NFS4_OK;
+
+    replay = calloc(1, sizeof(struct cb_compound_res));
+    if (replay == NULL) {
+        eprintf("[cb] failed to allocate replay buffer\n");
+        status = NFS4ERR_SERVERFAULT;
+        goto out;
+    }
+
+    /* decode the response from the replay cache */
+    xdrmem_create(&xdr, (char*)session->replay.res.buffer,
+        NFS41_MAX_SERVER_CACHE, XDR_DECODE);
+    if (!proc_cb_compound_res(&xdr, replay)) {
+        eprintf("[cb] failed to decode replay buffer\n");
+        status = NFS4ERR_SEQ_FALSE_RETRY;
+        goto out_free_replay;
+    }
+
+    /* if we cached the arguments, use them to validate the retry */
+    if (session->replay.arg.length) {
+        if (!replay_validate_args(args, &session->replay.arg)) {
+            eprintf("[cb] retry attempt with different arguments\n");
+            status = NFS4ERR_SEQ_FALSE_RETRY;
+            goto out_free_replay;
+        }
+    } else { /* otherwise, comparing opnums is the best we can do */
+        if (!replay_validate_ops(args, replay)) {
+            eprintf("[cb] retry attempt with different operations\n");
+            status = NFS4ERR_SEQ_FALSE_RETRY;
+            goto out_free_replay;
+        }
+    }
+
+    /* free previous response and replace it with the replay */
+    xdr.x_op = XDR_FREE;
+    proc_cb_compound_res(&xdr, res);
+
+    dprintf(2, "[cb] retry: returning cached response\n");
+
+    *res_out = replay;
+out:
+    return status;
+
+out_free_replay:
+    xdr.x_op = XDR_FREE;
+    proc_cb_compound_res(&xdr, replay);
+    goto out;
+}
+
+/* CB_COMPOUND */
+static void handle_cb_compound(nfs41_rpc_clnt *rpc_clnt, cb_req *req, struct cb_compound_res **reply)
+{
+    struct cb_compound_args args = { 0 };
+    struct cb_compound_res *res = NULL;
+    struct cb_argop *argop;
+    struct cb_resop *resop;
+    XDR *xdr = (XDR*)req->xdr;
+    nfs41_cb_session *session = NULL;
+    bool_t cachethis = FALSE;
+    uint32_t i, status = NFS4_OK;
+
+    dprintf(CBSLVL, "--> handle_cb_compound()\n");
+
+    /* decode the arguments */
+    if (!proc_cb_compound_args(xdr, &args)) {
+        status = NFS4ERR_BADXDR;
+        eprintf("failed to decode compound arguments\n");
+    }
+
+    /* allocate the compound results */
+    res = calloc(1, sizeof(struct cb_compound_res));
+    if (res == NULL) {
+        status = NFS4ERR_SERVERFAULT;
+        goto out;
+    }
+    res->status = status;
+    StringCchCopyA(res->tag.str, CB_COMPOUND_MAX_TAG, g_server_tag);
+    res->tag.str[CB_COMPOUND_MAX_TAG-1] = 0;
+    res->tag.len = (uint32_t)strlen(res->tag.str);
+    res->resarray = calloc(args.argarray_count, sizeof(struct cb_resop));
+    if (res->resarray == NULL) {
+        res->status = NFS4ERR_SERVERFAULT;
+        goto out;
+    }
+
+    dprintf(CBSLVL, "CB_COMPOUND('%s', %u)\n", args.tag.str, args.argarray_count);
+    if (args.minorversion != 1) {
+        res->status = NFS4ERR_MINOR_VERS_MISMATCH; //XXXXX
+        eprintf("args.minorversion %u != 1\n", args.minorversion);
+        goto out;
+    }
+
+    /* handle each operation in the compound */
+    for (i = 0; i < args.argarray_count && res->status == NFS4_OK; i++) {
+        argop = &args.argarray[i];
+        resop = &res->resarray[i];
+        resop->opnum = argop->opnum;
+        res->resarray_count++;
+
+        /* 20.9.3: The error NFS4ERR_SEQUENCE_POS MUST be returned
+         * when CB_SEQUENCE is found in any position in a CB_COMPOUND 
+         * beyond the first.  If any other operation is in the first 
+         * position of CB_COMPOUND, NFS4ERR_OP_NOT_IN_SESSION MUST 
+         * be returned.
+         */
+        if (i == 0 && argop->opnum != OP_CB_SEQUENCE) {
+            res->status = resop->res.status = NFS4ERR_OP_NOT_IN_SESSION;
+            break;
+        }
+        if (i != 0 && argop->opnum == OP_CB_SEQUENCE) {
+            res->status = resop->res.status = NFS4ERR_SEQUENCE_POS;
+            break;
+        }
+        if (status == NFS4ERR_RETRY_UNCACHED_REP) {
+            res->status = resop->res.status = status;
+            break;
+        }
+
+        switch (argop->opnum) {
+        case OP_CB_LAYOUTRECALL:
+            dprintf(1, "OP_CB_LAYOUTRECALL\n");
+            res->status = handle_cb_layoutrecall(rpc_clnt,
+                &argop->args.layoutrecall, &resop->res.layoutrecall);
+            break;
+        case OP_CB_RECALL_SLOT:
+            dprintf(1, "OP_CB_RECALL_SLOT\n");
+            res->status = handle_cb_recall_slot(rpc_clnt,
+                &argop->args.recall_slot, &resop->res.recall_slot);
+            break;
+        case OP_CB_SEQUENCE:
+            dprintf(1, "OP_CB_SEQUENCE\n");
+            status = handle_cb_sequence(rpc_clnt, &argop->args.sequence,
+                &resop->res.sequence, &session, &cachethis);
+
+            if (status == NFS4ERR_SEQ_FALSE_RETRY) {
+                /* replace the current results with the cached response */
+                status = replay_cache_read(session, &args, &res);
+                if (status) res->status = status;
+                goto out;
+            }
+
+            if (status == NFS4_OK)
+                res->status = resop->res.sequence.status;
+            break;
+        case OP_CB_GETATTR:
+            dprintf(1, "OP_CB_GETATTR\n");
+            res->status = handle_cb_getattr(rpc_clnt, 
+                &argop->args.getattr, &resop->res.getattr);
+            break;
+        case OP_CB_RECALL:
+            dprintf(1, "OP_CB_RECALL\n");
+            res->status = handle_cb_recall(rpc_clnt,
+                &argop->args.recall, &resop->res.recall);
+            break;
+        case OP_CB_NOTIFY:
+            dprintf(1, "OP_CB_NOTIFY\n");
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        case OP_CB_PUSH_DELEG:
+            dprintf(1, "OP_CB_PUSH_DELEG\n");
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        case OP_CB_RECALL_ANY:
+            dprintf(1, "OP_CB_RECALL_ANY\n");
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        case OP_CB_RECALLABLE_OBJ_AVAIL:
+            dprintf(1, "OP_CB_RECALLABLE_OBJ_AVAIL\n");
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        case OP_CB_WANTS_CANCELLED:
+            dprintf(1, "OP_CB_WANTS_CANCELLED\n");
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        case OP_CB_NOTIFY_LOCK:
+            dprintf(1, "OP_CB_NOTIFY_LOCK\n");
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        case OP_CB_NOTIFY_DEVICEID:
+            dprintf(1, "OP_CB_NOTIFY_DEVICEID\n");
+            res->status = NFS4_OK;
+            break;
+        case OP_CB_ILLEGAL:
+            dprintf(1, "OP_CB_ILLEGAL\n");
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        default:
+            eprintf("operation %u not supported\n", argop->opnum);
+            res->status = NFS4ERR_NOTSUPP;
+            break;
+        }
+    }
+
+    /* always attempt to cache the reply */
+    if (session)
+        replay_cache_write(session, &args, res, cachethis);
+out:
+    /* free the arguments */
+    xdr->x_op = XDR_FREE;
+    proc_cb_compound_args(xdr, &args);
+
+    *reply = res;
+    dprintf(CBSLVL, "<-- handle_cb_compound() returning %s (%u results)\n",
+        nfs_error_string(res ? res->status : status),
+        res ? res->resarray_count : 0);
+}
+
+#ifdef __REACTOS__
+int nfs41_handle_callback(void *rpc_clnt, void *cb, void * dummy)
+{
+    struct cb_compound_res **reply = dummy;
+#else
+int nfs41_handle_callback(void *rpc_clnt, void *cb, struct cb_compound_res **reply)
+{
+#endif
+    nfs41_rpc_clnt *rpc = (nfs41_rpc_clnt *)rpc_clnt;
+    cb_req *request = (cb_req *)cb;
+    uint32_t status = 0;
+
+    dprintf(1, "nfs41_handle_callback: received call\n");
+    if (request->rq_prog != NFS41_RPC_CBPROGRAM) {
+        eprintf("invalid rpc program %u\n", request->rq_prog);
+        status = 2;
+        goto out;
+    }
+
+    switch (request->rq_proc) {
+    case CB_NULL:
+        dprintf(1, "CB_NULL\n");
+        break;
+
+    case CB_COMPOUND:
+        dprintf(1, "CB_COMPOUND\n");
+        handle_cb_compound(rpc, request, reply);
+        break;
+
+    default:
+        dprintf(1, "invalid rpc procedure %u\n", request->rq_proc);
+        status = 3;
+        goto out;
+    }
+out:
+    return status;
+}
diff --git a/reactos/base/services/nfsd/callback_xdr.c b/reactos/base/services/nfsd/callback_xdr.c
new file mode 100644 (file)
index 0000000..945aa0b
--- /dev/null
@@ -0,0 +1,659 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 "nfs41_callback.h"
+#include "nfs41_ops.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+#define CBXLVL 2 /* dprintf level for callback xdr logging */
+#ifdef __REACTOS__
+#define CBX_ERR(msg) dprintf((CBXLVL), "%s: failed at %s\n", __FUNCTION__, msg)
+#else
+#define CBX_ERR(msg) dprintf((CBXLVL), __FUNCTION__ ": failed at " msg "\n")
+#endif
+
+/* common types */
+bool_t xdr_bitmap4(XDR *xdr, bitmap4 *bitmap);
+bool_t xdr_fattr4(XDR *xdr, fattr4 *fattr);
+
+static bool_t common_stateid(XDR *xdr, stateid4 *stateid)
+{
+    return xdr_u_int32_t(xdr, &stateid->seqid)
+        && xdr_opaque(xdr, (char*)stateid->other, NFS4_STATEID_OTHER);
+}
+
+static bool_t common_fh(XDR *xdr, nfs41_fh *fh)
+{
+    return xdr_u_int32_t(xdr, &fh->len)
+        && fh->len <= NFS4_FHSIZE
+        && xdr_opaque(xdr, (char*)fh->fh, fh->len);
+}
+
+static bool_t common_fsid(XDR *xdr, nfs41_fsid *fsid)
+{
+    return xdr_u_int64_t(xdr, &fsid->major)
+        && xdr_u_int64_t(xdr, &fsid->minor);
+}
+
+static bool_t common_notify4(XDR *xdr, struct notify4 *notify)
+{
+    return xdr_bitmap4(xdr, &notify->mask)
+        && xdr_bytes(xdr, &notify->list, &notify->len, NFS4_OPAQUE_LIMIT);
+}
+
+/* OP_CB_LAYOUTRECALL */
+static bool_t op_cb_layoutrecall_file(XDR *xdr, struct cb_recall_file *args)
+{
+    bool_t result;
+
+    result = common_fh(xdr, &args->fh);
+    if (!result) { CBX_ERR("layoutrecall_file.fh"); goto out; }
+
+    result = xdr_u_int64_t(xdr, &args->offset);
+    if (!result) { CBX_ERR("layoutrecall_file.offset"); goto out; }
+
+    result = xdr_u_int64_t(xdr, &args->length);
+    if (!result) { CBX_ERR("layoutrecall_file.length"); goto out; }
+
+    result = common_stateid(xdr, &args->stateid);
+    if (!result) { CBX_ERR("layoutrecall_file.stateid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_layoutrecall_fsid(XDR *xdr, union cb_recall_file_args *args)
+{
+    bool_t result;
+
+    result = common_fsid(xdr, &args->fsid);
+    if (!result) { CBX_ERR("layoutrecall_fsid.fsid"); goto out; }
+out:
+    return result;
+}
+
+static const struct xdr_discrim cb_layoutrecall_discrim[] = {
+    { PNFS_RETURN_FILE,     (xdrproc_t)op_cb_layoutrecall_file },
+    { PNFS_RETURN_FSID,     (xdrproc_t)op_cb_layoutrecall_fsid },
+    { PNFS_RETURN_ALL,      (xdrproc_t)xdr_void },
+    { 0,                    NULL_xdrproc_t }
+};
+
+static bool_t op_cb_layoutrecall_args(XDR *xdr, struct cb_layoutrecall_args *args)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, (enum_t*)&args->type);
+    if (!result) { CBX_ERR("layoutrecall_args.type"); goto out; }
+
+    result = xdr_enum(xdr, (enum_t*)&args->iomode);
+    if (!result) { CBX_ERR("layoutrecall_args.iomode"); goto out; }
+
+    result = xdr_bool(xdr, &args->changed);
+    if (!result) { CBX_ERR("layoutrecall_args.changed"); goto out; }
+
+    result = xdr_union(xdr, (enum_t*)&args->recall.type,
+        (char*)&args->recall.args, cb_layoutrecall_discrim, NULL_xdrproc_t);
+    if (!result) { CBX_ERR("layoutrecall_args.recall"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_layoutrecall_res(XDR *xdr, struct cb_layoutrecall_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("layoutrecall_res.status"); goto out; }
+out:
+    return result;
+}
+
+
+/* OP_CB_RECALL_SLOT */
+static bool_t op_cb_recall_slot_args(XDR *xdr, struct cb_recall_slot_args *res)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("recall_slot.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_recall_slot_res(XDR *xdr, struct cb_recall_slot_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("recall_slot.status"); goto out; }
+out:
+    return result;
+}
+
+
+/* OP_CB_SEQUENCE */
+static bool_t op_cb_sequence_ref(XDR *xdr, struct cb_sequence_ref *args)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &args->sequenceid);
+    if (!result) { CBX_ERR("sequence_ref.sequenceid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &args->slotid);
+    if (!result) { CBX_ERR("sequence_ref.slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_sequence_ref_list(XDR *xdr, struct cb_sequence_ref_list *args)
+{
+    bool_t result;
+
+    result = xdr_opaque(xdr, args->sessionid, NFS4_SESSIONID_SIZE);
+    if (!result) { CBX_ERR("sequence_ref_list.sessionid"); goto out; }
+
+    result = xdr_array(xdr, (char**)&args->calls, &args->call_count,
+        64, sizeof(struct cb_sequence_ref), (xdrproc_t)op_cb_sequence_ref);
+    if (!result) { CBX_ERR("sequence_ref_list.calls"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_sequence_args(XDR *xdr, struct cb_sequence_args *args)
+{
+    bool_t result;
+
+    result = xdr_opaque(xdr, args->sessionid, NFS4_SESSIONID_SIZE);
+    if (!result) { CBX_ERR("sequence_args.sessionid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &args->sequenceid);
+    if (!result) { CBX_ERR("sequence_args.sequenceid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &args->slotid);
+    if (!result) { CBX_ERR("sequence_args.slotid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &args->highest_slotid);
+    if (!result) { CBX_ERR("sequence_args.highest_slotid"); goto out; }
+
+    result = xdr_bool(xdr, &args->cachethis);
+    if (!result) { CBX_ERR("sequence_args.cachethis"); goto out; }
+
+    result = xdr_array(xdr, (char**)&args->ref_lists,
+        &args->ref_list_count, 64, sizeof(struct cb_sequence_ref_list),
+        (xdrproc_t)op_cb_sequence_ref_list);
+    if (!result) { CBX_ERR("sequence_args.ref_lists"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_sequence_res_ok(XDR *xdr, struct cb_sequence_res_ok *res)
+{
+    bool_t result;
+
+    result = xdr_opaque(xdr, res->sessionid, NFS4_SESSIONID_SIZE);
+    if (!result) { CBX_ERR("sequence_res.sessionid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &res->sequenceid);
+    if (!result) { CBX_ERR("sequence_res.sequenceid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &res->slotid);
+    if (!result) { CBX_ERR("sequence_res.slotid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &res->highest_slotid);
+    if (!result) { CBX_ERR("sequence_res.highest_slotid"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("sequence_res.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static const struct xdr_discrim cb_sequence_res_discrim[] = {
+    { NFS4_OK,              (xdrproc_t)op_cb_sequence_res_ok },
+    { 0,                    NULL_xdrproc_t }
+};
+
+static bool_t op_cb_sequence_res(XDR *xdr, struct cb_sequence_res *res)
+{
+    bool_t result;
+
+    result = xdr_union(xdr, &res->status, (char*)&res->ok,
+        cb_sequence_res_discrim, (xdrproc_t)xdr_void);
+    if (!result) { CBX_ERR("seq:argop.args"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_GETATTR */
+static bool_t op_cb_getattr_args(XDR *xdr, struct cb_getattr_args *args)
+{
+    bool_t result;
+
+    result = common_fh(xdr, &args->fh);
+    if (!result) { CBX_ERR("getattr.fh"); goto out; }
+
+    result = xdr_bitmap4(xdr, &args->attr_request);
+    if (!result) { CBX_ERR("getattr.attr_request"); goto out; }
+out:
+    return result;
+}
+
+static bool_t info_to_fattr4(nfs41_file_info *info, fattr4 *fattr)
+{
+    XDR fattr_xdr;
+    bool_t result = TRUE;
+
+    /* encode nfs41_file_info into fattr4 */
+    xdrmem_create(&fattr_xdr, (char*)fattr->attr_vals,
+        NFS4_OPAQUE_LIMIT, XDR_ENCODE);
+    
+    /* The only attributes that the server can reliably
+     * query via CB_GETATTR are size and change. */
+    if (bitmap_isset(&info->attrmask, 0, FATTR4_WORD0_CHANGE)) {
+        result = xdr_u_hyper(&fattr_xdr, &info->change);
+        if (!result) { CBX_ERR("getattr.info.change"); goto out; }
+        bitmap_set(&fattr->attrmask, 0, FATTR4_WORD0_CHANGE);
+    }
+    if (bitmap_isset(&info->attrmask, 0, FATTR4_WORD0_SIZE)) {
+        result = xdr_u_hyper(&fattr_xdr, &info->size);
+        if (!result) { CBX_ERR("getattr.info.size"); goto out; }
+        bitmap_set(&fattr->attrmask, 0, FATTR4_WORD0_SIZE);
+    }
+    fattr->attr_vals_len = xdr_getpos(&fattr_xdr);
+out:
+    return result;
+}
+
+static bool_t op_cb_getattr_res(XDR *xdr, struct cb_getattr_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("getattr.status"); goto out; }
+
+    if (res->status == NFS4_OK) {
+        fattr4 fattr = { 0 };
+
+        result = info_to_fattr4(&res->info, &fattr);
+        if (!result) { goto out; }
+
+        result = xdr_fattr4(xdr, &fattr);
+        if (!result) { CBX_ERR("getattr.obj_attributes"); goto out; }
+    }
+out:
+    return result;
+}
+
+/* OP_CB_RECALL */
+static bool_t op_cb_recall_args(XDR *xdr, struct cb_recall_args *args)
+{
+    bool_t result;
+
+    result = common_stateid(xdr, &args->stateid);
+    if (!result) { CBX_ERR("recall.stateid"); goto out; }
+
+    result = xdr_bool(xdr, &args->truncate);
+    if (!result) { CBX_ERR("recall.truncate"); goto out; }
+
+    result = common_fh(xdr, &args->fh);
+    if (!result) { CBX_ERR("recall.fh"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_recall_res(XDR *xdr, struct cb_recall_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("recall.status"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_NOTIFY */
+static bool_t op_cb_notify_args(XDR *xdr, struct cb_notify_args *res)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("notify.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_notify_res(XDR *xdr, struct cb_notify_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("notify.status"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_PUSH_DELEG */
+static bool_t op_cb_push_deleg_args(XDR *xdr, struct cb_push_deleg_args *res)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("push_deleg.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_push_deleg_res(XDR *xdr, struct cb_push_deleg_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("push_deleg.status"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_RECALL_ANY */
+static bool_t op_cb_recall_any_args(XDR *xdr, struct cb_recall_any_args *res)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("recall_any.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_recall_any_res(XDR *xdr, struct cb_recall_any_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("recall_any.status"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_RECALLABLE_OBJ_AVAIL */
+static bool_t op_cb_recallable_obj_avail_args(XDR *xdr, struct cb_recallable_obj_avail_args *res)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("recallable_obj_avail.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_recallable_obj_avail_res(XDR *xdr, struct cb_recallable_obj_avail_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("recallable_obj_avail.status"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_WANTS_CANCELLED */
+static bool_t op_cb_wants_cancelled_args(XDR *xdr, struct cb_wants_cancelled_args *res)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("wants_cancelled.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_wants_cancelled_res(XDR *xdr, struct cb_wants_cancelled_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("wants_cancelled.status"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_NOTIFY_LOCK */
+static bool_t op_cb_notify_lock_args(XDR *xdr, struct cb_notify_lock_args *res)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, &res->target_highest_slotid);
+    if (!result) { CBX_ERR("notify_lock.target_highest_slotid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_notify_lock_res(XDR *xdr, struct cb_notify_lock_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("notify_lock.status"); goto out; }
+out:
+    return result;
+}
+
+/* OP_CB_NOTIFY_DEVICEID */
+static bool_t cb_notify_deviceid_change(XDR *xdr, struct notify_deviceid4 *change)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, (uint32_t*)&change->layouttype);
+    if (!result) { CBX_ERR("notify_deviceid.change.layouttype"); goto out; }
+
+    result = xdr_opaque(xdr, (char*)change->deviceid, PNFS_DEVICEID_SIZE);
+    if (!result) { CBX_ERR("notify_deviceid.change.deviceid"); goto out; }
+
+    result = xdr_bool(xdr, &change->immediate);
+    if (!result) { CBX_ERR("notify_deviceid.change.immediate"); goto out; }
+out:
+    return result;
+}
+
+static bool_t cb_notify_deviceid_delete(XDR *xdr, struct notify_deviceid4 *change)
+{
+    bool_t result;
+
+    result = xdr_u_int32_t(xdr, (uint32_t*)&change->layouttype);
+    if (!result) { CBX_ERR("notify_deviceid.delete.layouttype"); goto out; }
+
+    result = xdr_opaque(xdr, (char*)change->deviceid, PNFS_DEVICEID_SIZE);
+    if (!result) { CBX_ERR("notify_deviceid.delete.deviceid"); goto out; }
+out:
+    return result;
+}
+
+static bool_t op_cb_notify_deviceid_args(XDR *xdr, struct cb_notify_deviceid_args *args)
+{
+    XDR notify_xdr;
+    uint32_t i, j, c;
+    bool_t result;
+
+    /* decode the generic notify4 list */
+    result = xdr_array(xdr, (char**)&args->notify_list,
+        &args->notify_count, CB_COMPOUND_MAX_OPERATIONS,
+        sizeof(struct notify4), (xdrproc_t)common_notify4);
+    if (!result) { CBX_ERR("notify_deviceid.notify_list"); goto out; }
+
+    switch (xdr->x_op) {
+    case XDR_FREE:
+        free(args->change_list);
+    case XDR_ENCODE:
+        return TRUE;
+    }
+
+    /* count the number of device changes */
+    args->change_count = 0;
+    for (i = 0; i < args->notify_count; i++)
+        args->change_count += args->notify_list[i].mask.count;
+
+    args->change_list = calloc(args->change_count, sizeof(struct notify_deviceid4));
+    if (args->change_list == NULL)
+        return FALSE;
+
+    c = 0;
+    for (i = 0; i < args->notify_count; i++) {
+        struct notify4 *notify = &args->notify_list[i];
+
+        /* decode the device notifications out of the opaque buffer */
+        xdrmem_create(&notify_xdr, notify->list, notify->len, XDR_DECODE);
+
+        for (j = 0; j < notify->mask.count; j++) {
+            struct notify_deviceid4 *change = &args->change_list[c++];
+            change->type = notify->mask.arr[j];
+
+            switch (change->type) {
+            case NOTIFY_DEVICEID4_CHANGE:
+                result = cb_notify_deviceid_change(&notify_xdr, change);
+                if (!result) { CBX_ERR("notify_deviceid.change"); goto out; }
+                break;
+            case NOTIFY_DEVICEID4_DELETE:
+                result = cb_notify_deviceid_delete(&notify_xdr, change);
+                if (!result) { CBX_ERR("notify_deviceid.delete"); goto out; }
+                break;
+            }
+        }
+    }
+out:
+    return result;
+}
+
+static bool_t op_cb_notify_deviceid_res(XDR *xdr, struct cb_notify_deviceid_res *res)
+{
+    bool_t result;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("notify_deviceid.status"); goto out; }
+out:
+    return result;
+}
+
+/* CB_COMPOUND */
+static bool_t cb_compound_tag(XDR *xdr, struct cb_compound_tag *args)
+{
+    return xdr_u_int32_t(xdr, &args->len)
+        && args->len <= CB_COMPOUND_MAX_TAG
+        && xdr_opaque(xdr, args->str, args->len);
+}
+
+static const struct xdr_discrim cb_argop_discrim[] = {
+    { OP_CB_LAYOUTRECALL,   (xdrproc_t)op_cb_layoutrecall_args },
+    { OP_CB_RECALL_SLOT,    (xdrproc_t)op_cb_recall_slot_args },
+    { OP_CB_SEQUENCE,       (xdrproc_t)op_cb_sequence_args },
+    { OP_CB_GETATTR,        (xdrproc_t)op_cb_getattr_args },
+    { OP_CB_RECALL,         (xdrproc_t)op_cb_recall_args },
+    { OP_CB_NOTIFY,         (xdrproc_t)op_cb_notify_args },
+    { OP_CB_PUSH_DELEG,     (xdrproc_t)op_cb_push_deleg_args },
+    { OP_CB_RECALL_ANY,     (xdrproc_t)op_cb_recall_any_args },
+    { OP_CB_RECALLABLE_OBJ_AVAIL, (xdrproc_t)op_cb_recallable_obj_avail_args },
+    { OP_CB_WANTS_CANCELLED, (xdrproc_t)op_cb_wants_cancelled_args },
+    { OP_CB_NOTIFY_LOCK,     (xdrproc_t)op_cb_notify_lock_args },
+    { OP_CB_NOTIFY_DEVICEID, (xdrproc_t)op_cb_notify_deviceid_args },
+    { OP_CB_ILLEGAL,         NULL_xdrproc_t },
+};
+
+static bool_t cb_compound_argop(XDR *xdr, struct cb_argop *args)
+{
+    bool_t result;
+
+    result = xdr_union(xdr, &args->opnum, (char*)&args->args,
+        cb_argop_discrim, NULL_xdrproc_t);
+    if (!result) { CBX_ERR("cmb:argop.args"); goto out; }
+out:
+    return result;
+}
+
+bool_t proc_cb_compound_args(XDR *xdr, struct cb_compound_args *args)
+{
+    bool_t result;
+
+    result = cb_compound_tag(xdr, &args->tag);
+    if (!result) { CBX_ERR("compound.tag"); goto out; }
+
+    result = xdr_u_int32_t(xdr, &args->minorversion);
+    if (!result) { CBX_ERR("compound.minorversion"); goto out; }
+
+    /* "superfluous in NFSv4.1 and MUST be ignored by the client" */
+    result = xdr_u_int32_t(xdr, &args->callback_ident);
+    if (!result) { CBX_ERR("compound.callback_ident"); goto out; }
+
+    result = xdr_array(xdr, (char**)&args->argarray,
+        &args->argarray_count, CB_COMPOUND_MAX_OPERATIONS,
+        sizeof(struct cb_argop), (xdrproc_t)cb_compound_argop);
+    if (!result) { CBX_ERR("compound.argarray"); goto out; }
+out:
+    return result;
+}
+
+static const struct xdr_discrim cb_resop_discrim[] = {
+    { OP_CB_LAYOUTRECALL,   (xdrproc_t)op_cb_layoutrecall_res },
+    { OP_CB_RECALL_SLOT,    (xdrproc_t)op_cb_recall_slot_res },
+    { OP_CB_SEQUENCE,       (xdrproc_t)op_cb_sequence_res },
+    { OP_CB_GETATTR,        (xdrproc_t)op_cb_getattr_res },
+    { OP_CB_RECALL,         (xdrproc_t)op_cb_recall_res },
+    { OP_CB_NOTIFY,         (xdrproc_t)op_cb_notify_res },
+    { OP_CB_PUSH_DELEG,     (xdrproc_t)op_cb_push_deleg_res },
+    { OP_CB_RECALL_ANY,     (xdrproc_t)op_cb_recall_any_res },
+    { OP_CB_RECALLABLE_OBJ_AVAIL, (xdrproc_t)op_cb_recallable_obj_avail_res },
+    { OP_CB_WANTS_CANCELLED, (xdrproc_t)op_cb_wants_cancelled_res },
+    { OP_CB_NOTIFY_LOCK,     (xdrproc_t)op_cb_notify_lock_res },
+    { OP_CB_NOTIFY_DEVICEID, (xdrproc_t)op_cb_notify_deviceid_res },
+    { OP_CB_ILLEGAL,         NULL_xdrproc_t },
+};
+
+static bool_t cb_compound_resop(XDR *xdr, struct cb_resop *res)
+{
+    /* save xdr encode/decode status to see which operation failed */
+    res->xdr_ok = xdr_union(xdr, &res->opnum, (char*)&res->res,
+        cb_resop_discrim, NULL_xdrproc_t);
+    if (!res->xdr_ok) { CBX_ERR("resop.res"); goto out; }
+out:
+    return res->xdr_ok;
+}
+
+bool_t proc_cb_compound_res(XDR *xdr, struct cb_compound_res *res)
+{
+    bool_t result;
+
+    if (res == NULL)
+        return TRUE;
+
+    result = xdr_enum(xdr, &res->status);
+    if (!result) { CBX_ERR("compound_res.status"); goto out; }
+
+    result = cb_compound_tag(xdr, &res->tag);
+    if (!result) { CBX_ERR("compound_res.tag"); goto out; }
+
+    result = xdr_array(xdr, (char**)&res->resarray,
+        &res->resarray_count, CB_COMPOUND_MAX_OPERATIONS,
+        sizeof(struct cb_resop), (xdrproc_t)cb_compound_resop);
+    if (!result) { CBX_ERR("compound_res.resarray"); goto out; }
+out:
+    if (xdr->x_op == XDR_FREE)
+        free(res);
+    return result;
+}
diff --git a/reactos/base/services/nfsd/daemon_debug.c b/reactos/base/services/nfsd/daemon_debug.c
new file mode 100644 (file)
index 0000000..661c627
--- /dev/null
@@ -0,0 +1,678 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+
+#include "daemon_debug.h"
+#include "from_kernel.h"
+#include "nfs41_driver.h"
+#include "nfs41_ops.h"
+#include "service.h"
+#include "rpc/rpc.h"
+#include "rpc/auth_sspi.h"
+
+static int g_debug_level = DEFAULT_DEBUG_LEVEL;
+
+void set_debug_level(int level) { g_debug_level = level; }
+
+FILE *dlog_file, *elog_file;
+
+#ifndef STANDALONE_NFSD
+void open_log_files()
+{
+    const char dfile[] = "nfsddbg.log";
+    const char efile[] = "nfsderr.log";
+    const char mode[] = "w";
+    if (g_debug_level > 0) {
+        dlog_file = fopen(dfile, mode);
+        if (dlog_file == NULL) {
+            ReportStatusToSCMgr(SERVICE_STOPPED, GetLastError(), 0);
+            exit (GetLastError());
+        }
+    }
+    elog_file = fopen(efile, mode);
+    if (elog_file == NULL) {
+        ReportStatusToSCMgr(SERVICE_STOPPED, GetLastError(), 0);
+        exit (GetLastError());
+    }
+}
+
+void close_log_files()
+{
+    if (dlog_file) fclose(dlog_file);
+    if (elog_file) fclose(elog_file);
+}
+#else
+void open_log_files()
+{
+    dlog_file = stdout;
+    elog_file = stderr;
+}
+#endif
+
+void dprintf(int level, LPCSTR format, ...)
+{
+    if (level <= g_debug_level) {
+        va_list args;
+        va_start(args, format);
+        fprintf(dlog_file, "%04x: ", GetCurrentThreadId());
+        vfprintf(dlog_file, format, args);
+#ifndef STANDALONE_NFSD
+        fflush(dlog_file);
+#endif
+        va_end(args);
+    }
+}
+
+void eprintf(LPCSTR format, ...)
+{
+    va_list args;
+    va_start(args, format);
+    fprintf(elog_file, "%04x: ", GetCurrentThreadId());
+    vfprintf(elog_file, format, args);
+#ifndef STANDALONE_NFSD
+    fflush(elog_file);
+#endif
+    va_end(args);
+}
+
+void print_hexbuf(int level, unsigned char *title, unsigned char *buf, int len) 
+{
+    int j, k;
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "%s", title);
+    for(j = 0, k = 0; j < len; j++, k++) {
+        fprintf(dlog_file, "%02x '%c' ", buf[j], isascii(buf[j])? buf[j]:' ');
+        if (((k+1) % 10 == 0 && k > 0)) {
+            fprintf(dlog_file, "\n");
+        }
+    }
+    fprintf(dlog_file, "\n");
+}
+
+void print_hexbuf_no_asci(int level, unsigned char *title, unsigned char *buf, int len) 
+{
+    int j, k;
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "%s", title);
+    for(j = 0, k = 0; j < len; j++, k++) {
+        fprintf(dlog_file, "%02x ", buf[j]);
+        if (((k+1) % 10 == 0 && k > 0)) {
+            fprintf(dlog_file, "\n");
+        }
+    }
+    fprintf(dlog_file, "\n");
+}
+
+void print_create_attributes(int level, DWORD create_opts) {
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "create attributes: ");
+    if (create_opts & FILE_DIRECTORY_FILE)
+        fprintf(dlog_file, "DIRECTORY_FILE ");
+    if (create_opts & FILE_NON_DIRECTORY_FILE)
+        fprintf(dlog_file, "NON_DIRECTORY_FILE ");
+    if (create_opts & FILE_WRITE_THROUGH)
+        fprintf(dlog_file, "WRITE_THROUGH ");
+    if (create_opts & FILE_SEQUENTIAL_ONLY)
+        fprintf(dlog_file, "SEQUENTIAL_ONLY ");
+    if (create_opts & FILE_RANDOM_ACCESS)
+        fprintf(dlog_file, "RANDOM_ACCESS ");
+    if (create_opts & FILE_NO_INTERMEDIATE_BUFFERING)
+        fprintf(dlog_file, "NO_INTERMEDIATE_BUFFERING ");
+    if (create_opts & FILE_SYNCHRONOUS_IO_ALERT)
+        fprintf(dlog_file, "SYNCHRONOUS_IO_ALERT ");
+    if (create_opts & FILE_SYNCHRONOUS_IO_NONALERT)
+        fprintf(dlog_file, "SYNCHRONOUS_IO_NONALERT ");
+    if (create_opts & FILE_CREATE_TREE_CONNECTION)
+        fprintf(dlog_file, "CREATE_TREE_CONNECTION ");
+    if (create_opts & FILE_COMPLETE_IF_OPLOCKED)
+        fprintf(dlog_file, "COMPLETE_IF_OPLOCKED ");
+    if (create_opts & FILE_NO_EA_KNOWLEDGE)
+        fprintf(dlog_file, "NO_EA_KNOWLEDGE ");
+    if (create_opts & FILE_OPEN_REPARSE_POINT)
+        fprintf(dlog_file, "OPEN_REPARSE_POINT ");
+    if (create_opts & FILE_DELETE_ON_CLOSE)
+        fprintf(dlog_file, "DELETE_ON_CLOSE ");
+    if (create_opts & FILE_OPEN_BY_FILE_ID)
+        fprintf(dlog_file, "OPEN_BY_FILE_ID ");
+    if (create_opts & FILE_OPEN_FOR_BACKUP_INTENT)
+        fprintf(dlog_file, "OPEN_FOR_BACKUP_INTENT ");
+    if (create_opts & FILE_RESERVE_OPFILTER)
+        fprintf(dlog_file, "RESERVE_OPFILTER");
+    fprintf(dlog_file, "\n");
+}
+
+void print_disposition(int level, DWORD disposition) {
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "userland disposition = ");
+    if (disposition == FILE_SUPERSEDE)
+        fprintf(dlog_file, "FILE_SUPERSEDE\n");
+    else if (disposition == FILE_CREATE)
+        fprintf(dlog_file, "FILE_CREATE\n");
+    else if (disposition == FILE_OPEN)
+        fprintf(dlog_file, "FILE_OPEN\n");
+    else if (disposition == FILE_OPEN_IF)
+        fprintf(dlog_file, "FILE_OPEN_IF\n");
+    else if (disposition == FILE_OVERWRITE)
+        fprintf(dlog_file, "FILE_OVERWRITE\n");
+    else if (disposition == FILE_OVERWRITE_IF)
+        fprintf(dlog_file, "FILE_OVERWRITE_IF\n");
+}
+
+void print_access_mask(int level, DWORD access_mask) {
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "access mask: ");
+    if (access_mask & FILE_READ_DATA)
+        fprintf(dlog_file, "READ ");
+    if (access_mask & STANDARD_RIGHTS_READ)
+        fprintf(dlog_file, "READ_ACL ");
+    if (access_mask & FILE_READ_ATTRIBUTES)
+        fprintf(dlog_file, "READ_ATTR ");
+    if (access_mask & FILE_READ_EA)
+        fprintf(dlog_file, "READ_EA ");
+    if (access_mask & FILE_WRITE_DATA)
+        fprintf(dlog_file, "WRITE ");
+    if (access_mask & STANDARD_RIGHTS_WRITE)
+        fprintf(dlog_file, "WRITE_ACL ");
+    if (access_mask & FILE_WRITE_ATTRIBUTES)
+        fprintf(dlog_file, "WRITE_ATTR ");
+    if (access_mask & FILE_WRITE_EA)
+        fprintf(dlog_file, "WRITE_EA ");
+    if (access_mask & FILE_APPEND_DATA)
+        fprintf(dlog_file, "APPEND ");
+    if (access_mask & FILE_EXECUTE)
+        fprintf(dlog_file, "EXECUTE ");
+    if (access_mask & FILE_LIST_DIRECTORY)
+        fprintf(dlog_file, "LIST ");
+    if (access_mask & FILE_TRAVERSE)
+        fprintf(dlog_file, "TRAVERSE ");
+    if (access_mask & SYNCHRONIZE)
+        fprintf(dlog_file, "SYNC ");
+    if (access_mask & FILE_DELETE_CHILD)
+        fprintf(dlog_file, "DELETE_CHILD");
+    fprintf(dlog_file, "\n");
+}
+
+void print_share_mode(int level, DWORD mode)
+{
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "share mode: ");
+    if (mode & FILE_SHARE_READ)
+        fprintf(dlog_file, "READ ");
+    if (mode & FILE_SHARE_WRITE)
+        fprintf(dlog_file, "WRITE ");
+    if (mode & FILE_SHARE_DELETE)
+        fprintf(dlog_file, "DELETE");
+    fprintf(dlog_file, "\n");
+}
+
+void print_file_id_both_dir_info(int level, FILE_ID_BOTH_DIR_INFO *pboth_dir_info)
+{
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "FILE_ID_BOTH_DIR_INFO %p %d\n", 
+       pboth_dir_info, sizeof(unsigned char *));
+    fprintf(dlog_file, "\tNextEntryOffset=%ld %d %d\n", 
+        pboth_dir_info->NextEntryOffset, 
+        sizeof(pboth_dir_info->NextEntryOffset), sizeof(DWORD));
+    fprintf(dlog_file, "\tFileIndex=%ld  %d\n", pboth_dir_info->FileIndex, 
+        sizeof(pboth_dir_info->FileIndex));
+    fprintf(dlog_file, "\tCreationTime=0x%x %d\n", 
+        pboth_dir_info->CreationTime.QuadPart, 
+        sizeof(pboth_dir_info->CreationTime));
+    fprintf(dlog_file, "\tLastAccessTime=0x%x %d\n", 
+        pboth_dir_info->LastAccessTime.QuadPart, 
+        sizeof(pboth_dir_info->LastAccessTime));
+    fprintf(dlog_file, "\tLastWriteTime=0x%x %d\n", 
+        pboth_dir_info->LastWriteTime.QuadPart, 
+        sizeof(pboth_dir_info->LastWriteTime));
+    fprintf(dlog_file, "\tChangeTime=0x%x %d\n", 
+        pboth_dir_info->ChangeTime.QuadPart, 
+        sizeof(pboth_dir_info->ChangeTime));
+    fprintf(dlog_file, "\tEndOfFile=0x%x %d\n", 
+        pboth_dir_info->EndOfFile.QuadPart, 
+        sizeof(pboth_dir_info->EndOfFile));
+    fprintf(dlog_file, "\tAllocationSize=0x%x %d\n", 
+        pboth_dir_info->AllocationSize.QuadPart, 
+        sizeof(pboth_dir_info->AllocationSize));
+    fprintf(dlog_file, "\tFileAttributes=%ld %d\n", 
+        pboth_dir_info->FileAttributes, 
+        sizeof(pboth_dir_info->FileAttributes));
+    fprintf(dlog_file, "\tFileNameLength=%ld %d\n", 
+        pboth_dir_info->FileNameLength, 
+        sizeof(pboth_dir_info->FileNameLength));
+    fprintf(dlog_file, "\tEaSize=%ld %d\n", 
+        pboth_dir_info->EaSize, sizeof(pboth_dir_info->EaSize));
+    fprintf(dlog_file, "\tShortNameLength=%d %d\n", 
+        pboth_dir_info->ShortNameLength, 
+        sizeof(pboth_dir_info->ShortNameLength));
+    fprintf(dlog_file, "\tShortName='%S' %d\n", pboth_dir_info->ShortName, 
+        sizeof(pboth_dir_info->ShortName));
+    fprintf(dlog_file, "\tFileId=0x%x %d\n", pboth_dir_info->FileId.QuadPart, 
+        sizeof(pboth_dir_info->FileId));
+    fprintf(dlog_file, "\tFileName='%S' %p\n", pboth_dir_info->FileName, 
+        pboth_dir_info->FileName);
+}
+
+void print_opcode(int level, DWORD opcode) 
+{
+    dprintf(level, (LPCSTR)opcode2string(opcode));
+}
+
+const char* opcode2string(DWORD opcode)
+{
+    switch(opcode) {
+    case NFS41_SHUTDOWN:    return "NFS41_SHUTDOWN";
+    case NFS41_MOUNT:       return "NFS41_MOUNT";
+    case NFS41_UNMOUNT:     return "NFS41_UNMOUNT";
+    case NFS41_OPEN:        return "NFS41_OPEN";
+    case NFS41_CLOSE:       return "NFS41_CLOSE";
+    case NFS41_READ:        return "NFS41_READ";
+    case NFS41_WRITE:       return "NFS41_WRITE";
+    case NFS41_LOCK:        return "NFS41_LOCK";
+    case NFS41_UNLOCK:      return "NFS41_UNLOCK";
+    case NFS41_DIR_QUERY:   return "NFS41_DIR_QUERY";
+    case NFS41_FILE_QUERY:  return "NFS41_FILE_QUERY";
+    case NFS41_FILE_SET:    return "NFS41_FILE_SET";
+    case NFS41_EA_SET:      return "NFS41_EA_SET";
+    case NFS41_EA_GET:      return "NFS41_EA_GET";
+    case NFS41_SYMLINK:     return "NFS41_SYMLINK";
+    case NFS41_VOLUME_QUERY: return "NFS41_VOLUME_QUERY";
+    case NFS41_ACL_QUERY:   return "NFS41_ACL_QUERY";
+    case NFS41_ACL_SET:     return "NFS41_ACL_SET";
+    default:                return "UNKNOWN";
+    }
+}
+
+const char* nfs_opnum_to_string(int opnum)
+{
+    switch (opnum)
+    {
+    case OP_ACCESS: return "ACCESS";
+    case OP_CLOSE: return "CLOSE";
+    case OP_COMMIT: return "COMMIT";
+    case OP_CREATE: return "CREATE";
+    case OP_DELEGPURGE: return "DELEGPURGE";
+    case OP_DELEGRETURN: return "DELEGRETURN";
+    case OP_GETATTR: return "GETATTR";
+    case OP_GETFH: return "GETFH";
+    case OP_LINK: return "LINK";
+    case OP_LOCK: return "LOCK";
+    case OP_LOCKT: return "LOCKT";
+    case OP_LOCKU: return "LOCKU";
+    case OP_LOOKUP: return "LOOKUP";
+    case OP_LOOKUPP: return "LOOKUPP";
+    case OP_NVERIFY: return "NVERIFY";
+    case OP_OPEN: return "OPEN";
+    case OP_OPENATTR: return "OPENATTR";
+    case OP_OPEN_CONFIRM: return "OPEN_CONFIRM";
+    case OP_OPEN_DOWNGRADE: return "OPEN_DOWNGRADE";
+    case OP_PUTFH: return "PUTFH";
+    case OP_PUTPUBFH: return "PUTPUBFH";
+    case OP_PUTROOTFH: return "PUTROOTFH";
+    case OP_READ: return "READ";
+    case OP_READDIR: return "READDIR";
+    case OP_READLINK: return "READLINK";
+    case OP_REMOVE: return "REMOVE";
+    case OP_RENAME: return "RENAME";
+    case OP_RENEW: return "RENEW";
+    case OP_RESTOREFH: return "RESTOREFH";
+    case OP_SAVEFH: return "SAVEFH";
+    case OP_SECINFO: return "SECINFO";
+    case OP_SETATTR: return "SETATTR";
+    case OP_SETCLIENTID: return "SETCLIENTID";
+    case OP_SETCLIENTID_CONFIRM: return "SETCLIENTID_CONFIRM";
+    case OP_VERIFY: return "VERIFY";
+    case OP_WRITE: return "WRITE";
+    case OP_RELEASE_LOCKOWNER: return "RELEASE_LOCKOWNER";
+    case OP_BACKCHANNEL_CTL: return "BACKCHANNEL_CTL";
+    case OP_BIND_CONN_TO_SESSION: return "BIND_CONN_TO_SESSION";
+    case OP_EXCHANGE_ID: return "EXCHANGE_ID";
+    case OP_CREATE_SESSION: return "CREATE_SESSION";
+    case OP_DESTROY_SESSION: return "DESTROY_SESSION";
+    case OP_FREE_STATEID: return "FREE_STATEID";
+    case OP_GET_DIR_DELEGATION: return "GET_DIR_DELEGATION";
+    case OP_GETDEVICEINFO: return "GETDEVICEINFO";
+    case OP_GETDEVICELIST: return "GETDEVICELIST";
+    case OP_LAYOUTCOMMIT: return "LAYOUTCOMMIT";
+    case OP_LAYOUTGET: return "LAYOUTGET";
+    case OP_LAYOUTRETURN: return "LAYOUTRETURN";
+    case OP_SECINFO_NO_NAME: return "SECINFO_NO_NAME";
+    case OP_SEQUENCE: return "SEQUENCE";
+    case OP_SET_SSV: return "SET_SSV";
+    case OP_TEST_STATEID: return "TEST_STATEID";
+    case OP_WANT_DELEGATION: return "WANT_DELEGATION";
+    case OP_DESTROY_CLIENTID: return "DESTROY_CLIENTID";
+    case OP_RECLAIM_COMPLETE: return "RECLAIM_COMPLETE";
+    case OP_ILLEGAL: return "ILLEGAL";
+    default: return "invalid nfs opnum";
+    }
+}
+
+const char* nfs_error_string(int status)
+{
+    switch (status)
+    {
+    case NFS4_OK: return "NFS4_OK";
+    case NFS4ERR_PERM: return "NFS4ERR_PERM";
+    case NFS4ERR_NOENT: return "NFS4ERR_NOENT";
+    case NFS4ERR_IO: return "NFS4ERR_IO";
+    case NFS4ERR_NXIO: return "NFS4ERR_NXIO";
+    case NFS4ERR_ACCESS: return "NFS4ERR_ACCESS";
+    case NFS4ERR_EXIST: return "NFS4ERR_EXIST";
+    case NFS4ERR_XDEV: return "NFS4ERR_XDEV";
+    case NFS4ERR_NOTDIR: return "NFS4ERR_NOTDIR";
+    case NFS4ERR_ISDIR: return "NFS4ERR_ISDIR";
+    case NFS4ERR_INVAL: return "NFS4ERR_INVAL";
+    case NFS4ERR_FBIG: return "NFS4ERR_FBIG";
+    case NFS4ERR_NOSPC: return "NFS4ERR_NOSPC";
+    case NFS4ERR_ROFS: return "NFS4ERR_ROFS";
+    case NFS4ERR_MLINK: return "NFS4ERR_MLINK";
+    case NFS4ERR_NAMETOOLONG: return "NFS4ERR_NAMETOOLONG";
+    case NFS4ERR_NOTEMPTY: return "NFS4ERR_NOTEMPTY";
+    case NFS4ERR_DQUOT: return "NFS4ERR_DQUOT";
+    case NFS4ERR_STALE: return "NFS4ERR_STALE";
+    case NFS4ERR_BADHANDLE: return "NFS4ERR_BADHANDLE";
+    case NFS4ERR_BAD_COOKIE: return "NFS4ERR_BAD_COOKIE";
+    case NFS4ERR_NOTSUPP: return "NFS4ERR_NOTSUPP";
+    case NFS4ERR_TOOSMALL: return "NFS4ERR_TOOSMALL";
+    case NFS4ERR_SERVERFAULT: return "NFS4ERR_SERVERFAULT";
+    case NFS4ERR_BADTYPE: return "NFS4ERR_BADTYPE";
+    case NFS4ERR_DELAY: return "NFS4ERR_DELAY";
+    case NFS4ERR_SAME: return "NFS4ERR_SAME";
+    case NFS4ERR_DENIED: return "NFS4ERR_DENIED";
+    case NFS4ERR_EXPIRED: return "NFS4ERR_EXPIRED";
+    case NFS4ERR_LOCKED: return "NFS4ERR_LOCKED";
+    case NFS4ERR_GRACE: return "NFS4ERR_GRACE";
+    case NFS4ERR_FHEXPIRED: return "NFS4ERR_FHEXPIRED";
+    case NFS4ERR_SHARE_DENIED: return "NFS4ERR_SHARE_DENIED";
+    case NFS4ERR_WRONGSEC: return "NFS4ERR_WRONGSEC";
+    case NFS4ERR_CLID_INUSE: return "NFS4ERR_CLID_INUSE";
+    case NFS4ERR_RESOURCE: return "NFS4ERR_RESOURCE";
+    case NFS4ERR_MOVED: return "NFS4ERR_MOVED";
+    case NFS4ERR_NOFILEHANDLE: return "NFS4ERR_NOFILEHANDLE";
+    case NFS4ERR_MINOR_VERS_MISMATCH: return "NFS4ERR_MINOR_VERS_MISMATCH";
+    case NFS4ERR_STALE_CLIENTID: return "NFS4ERR_STALE_CLIENTID";
+    case NFS4ERR_STALE_STATEID: return "NFS4ERR_STALE_STATEID";
+    case NFS4ERR_OLD_STATEID: return "NFS4ERR_OLD_STATEID";
+    case NFS4ERR_BAD_STATEID: return "NFS4ERR_BAD_STATEID";
+    case NFS4ERR_BAD_SEQID: return "NFS4ERR_BAD_SEQID";
+    case NFS4ERR_NOT_SAME: return "NFS4ERR_NOT_SAME";
+    case NFS4ERR_LOCK_RANGE: return "NFS4ERR_LOCK_RANGE";
+    case NFS4ERR_SYMLINK: return "NFS4ERR_SYMLINK";
+    case NFS4ERR_RESTOREFH: return "NFS4ERR_RESTOREFH";
+    case NFS4ERR_LEASE_MOVED: return "NFS4ERR_LEASE_MOVED";
+    case NFS4ERR_ATTRNOTSUPP: return "NFS4ERR_ATTRNOTSUPP";
+    case NFS4ERR_NO_GRACE: return "NFS4ERR_NO_GRACE";
+    case NFS4ERR_RECLAIM_BAD: return "NFS4ERR_RECLAIM_BAD";
+    case NFS4ERR_RECLAIM_CONFLICT: return "NFS4ERR_RECLAIM_CONFLICT";
+    case NFS4ERR_BADXDR: return "NFS4ERR_BADXDR";
+    case NFS4ERR_LOCKS_HELD: return "NFS4ERR_LOCKS_HELD";
+    case NFS4ERR_OPENMODE: return "NFS4ERR_OPENMODE";
+    case NFS4ERR_BADOWNER: return "NFS4ERR_BADOWNER";
+    case NFS4ERR_BADCHAR: return "NFS4ERR_BADCHAR";
+    case NFS4ERR_BADNAME: return "NFS4ERR_BADNAME";
+    case NFS4ERR_BAD_RANGE: return "NFS4ERR_BAD_RANGE";
+    case NFS4ERR_LOCK_NOTSUPP: return "NFS4ERR_LOCK_NOTSUPP";
+    case NFS4ERR_OP_ILLEGAL: return "NFS4ERR_OP_ILLEGAL";
+    case NFS4ERR_DEADLOCK: return "NFS4ERR_DEADLOCK";
+    case NFS4ERR_FILE_OPEN: return "NFS4ERR_FILE_OPEN";
+    case NFS4ERR_ADMIN_REVOKED: return "NFS4ERR_ADMIN_REVOKED";
+    case NFS4ERR_CB_PATH_DOWN: return "NFS4ERR_CB_PATH_DOWN";
+    case NFS4ERR_BADIOMODE: return "NFS4ERR_BADIOMODE";
+    case NFS4ERR_BADLAYOUT: return "NFS4ERR_BADLAYOUT";
+    case NFS4ERR_BAD_SESSION_DIGEST: return "NFS4ERR_BAD_SESSION_DIGEST";
+    case NFS4ERR_BADSESSION: return "NFS4ERR_BADSESSION";
+    case NFS4ERR_BADSLOT: return "NFS4ERR_BADSLOT";
+    case NFS4ERR_COMPLETE_ALREADY: return "NFS4ERR_COMPLETE_ALREADY";
+    case NFS4ERR_CONN_NOT_BOUND_TO_SESSION: return "NFS4ERR_CONN_NOT_BOUND_TO_SESSION";
+    case NFS4ERR_DELEG_ALREADY_WANTED: return "NFS4ERR_DELEG_ALREADY_WANTED";
+    case NFS4ERR_BACK_CHAN_BUSY: return "NFS4ERR_BACK_CHAN_BUSY";
+    case NFS4ERR_LAYOUTTRYLATER: return "NFS4ERR_LAYOUTTRYLATER";
+    case NFS4ERR_LAYOUTUNAVAILABLE: return "NFS4ERR_LAYOUTUNAVAILABLE";
+    case NFS4ERR_NOMATCHING_LAYOUT: return "NFS4ERR_NOMATCHING_LAYOUT";
+    case NFS4ERR_RECALLCONFLICT: return "NFS4ERR_RECALLCONFLICT";
+    case NFS4ERR_UNKNOWN_LAYOUTTYPE: return "NFS4ERR_UNKNOWN_LAYOUTTYPE";
+    case NFS4ERR_SEQ_MISORDERED: return "NFS4ERR_SEQ_MISORDERED";
+    case NFS4ERR_SEQUENCE_POS: return "NFS4ERR_SEQUENCE_POS";
+    case NFS4ERR_REQ_TOO_BIG: return "NFS4ERR_REQ_TOO_BIG";
+    case NFS4ERR_REP_TOO_BIG: return "NFS4ERR_REP_TOO_BIG";
+    case NFS4ERR_REP_TOO_BIG_TO_CACHE: return "NFS4ERR_REP_TOO_BIG_TO_CACHE";
+    case NFS4ERR_RETRY_UNCACHED_REP: return "NFS4ERR_RETRY_UNCACHED_REP";
+    case NFS4ERR_UNSAFE_COMPOUND: return "NFS4ERR_UNSAFE_COMPOUND";
+    case NFS4ERR_TOO_MANY_OPS: return "NFS4ERR_TOO_MANY_OPS";
+    case NFS4ERR_OP_NOT_IN_SESSION: return "NFS4ERR_OP_NOT_IN_SESSION";
+    case NFS4ERR_HASH_ALG_UNSUPP: return "NFS4ERR_HASH_ALG_UNSUPP";
+    case NFS4ERR_CLIENTID_BUSY: return "NFS4ERR_CLIENTID_BUSY";
+    case NFS4ERR_PNFS_IO_HOLE: return "NFS4ERR_PNFS_IO_HOLE";
+    case NFS4ERR_SEQ_FALSE_RETRY: return "NFS4ERR_SEQ_FALSE_RETRY";
+    case NFS4ERR_BAD_HIGH_SLOT: return "NFS4ERR_BAD_HIGH_SLOT";
+    case NFS4ERR_DEADSESSION: return "NFS4ERR_DEADSESSION";
+    case NFS4ERR_ENCR_ALG_UNSUPP: return "NFS4ERR_ENCR_ALG_UNSUPP";
+    case NFS4ERR_PNFS_NO_LAYOUT: return "NFS4ERR_PNFS_NO_LAYOUT";
+    case NFS4ERR_NOT_ONLY_OP: return "NFS4ERR_NOT_ONLY_OP";
+    case NFS4ERR_WRONG_CRED: return "NFS4ERR_WRONG_CRED";
+    case NFS4ERR_WRONG_TYPE: return "NFS4ERR_WRONG_TYPE";
+    case NFS4ERR_DIRDELEG_UNAVAIL: return "NFS4ERR_DIRDELEG_UNAVAIL";
+    case NFS4ERR_REJECT_DELEG: return "NFS4ERR_REJECT_DELEG";
+    case NFS4ERR_RETURNCONFLICT: return "NFS4ERR_RETURNCONFLICT";
+    case NFS4ERR_DELEG_REVOKED: return "NFS4ERR_DELEG_REVOKED";
+    default: return "invalid nfs error code";
+    }
+}
+
+const char* rpc_error_string(int status)
+{
+    switch (status)
+    {
+    case RPC_CANTENCODEARGS: return "RPC_CANTENCODEARGS";
+    case RPC_CANTDECODERES: return "RPC_CANTDECODERES";
+    case RPC_CANTSEND: return "RPC_CANTSEND";
+    case RPC_CANTRECV: return "RPC_CANTRECV";
+    case RPC_TIMEDOUT: return "RPC_TIMEDOUT";
+    case RPC_INTR: return "RPC_INTR";
+    case RPC_UDERROR: return "RPC_UDERROR";
+    case RPC_VERSMISMATCH: return "RPC_VERSMISMATCH";
+    case RPC_AUTHERROR: return "RPC_AUTHERROR";
+    case RPC_PROGUNAVAIL: return "RPC_PROGUNAVAIL";
+    case RPC_PROGVERSMISMATCH: return "RPC_PROGVERSMISMATCH";
+    case RPC_PROCUNAVAIL: return "RPC_PROCUNAVAIL";
+    case RPC_CANTDECODEARGS: return "RPC_CANTDECODEARGS";
+    case RPC_SYSTEMERROR: return "RPC_SYSTEMERROR";
+    default: return "invalid rpc error code";
+    }
+}
+
+const char* gssauth_string(int type) {
+    switch(type) {
+    case RPCSEC_SSPI_SVC_NONE: return "RPCSEC_SSPI_SVC_NONE";
+    case RPCSEC_SSPI_SVC_INTEGRITY: return "RPCSEC_SSPI_SVC_INTEGRITY";
+    case RPCSEC_SSPI_SVC_PRIVACY: return "RPCSEC_SSPI_SVC_PRIVACY";
+    default: return "invalid gss auth type";
+    }
+}
+
+void print_condwait_status(int level, int status)
+{
+    if (level > g_debug_level) return;
+    switch(status) {
+        case WAIT_ABANDONED: fprintf(dlog_file, "WAIT_ABANDONED\n"); break;
+        case WAIT_OBJECT_0: fprintf(dlog_file, "WAIT_OBJECT_0\n"); break;
+        case WAIT_TIMEOUT: fprintf(dlog_file, "WAIT_TIMEOUT\n"); break;
+        case WAIT_FAILED: fprintf(dlog_file, "WAIT_FAILED %d\n", GetLastError());
+        default: fprintf(dlog_file, "unknown status =%d\n", status);
+    }
+}
+
+void print_sr_status_flags(int level, int flags)
+{
+    if (level > g_debug_level) return;
+    fprintf(dlog_file, "%04x: sr_status_flags: ", GetCurrentThreadId());
+    if (flags & SEQ4_STATUS_CB_PATH_DOWN) 
+        fprintf(dlog_file, "SEQ4_STATUS_CB_PATH_DOWN ");
+    if (flags & SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING) 
+        fprintf(dlog_file, "SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING ");
+    if (flags & SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED) 
+        fprintf(dlog_file, "SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED ");
+    if (flags & SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED) 
+        fprintf(dlog_file, "SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED ");
+    if (flags & SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED) 
+        fprintf(dlog_file, "SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED ");
+    if (flags & SEQ4_STATUS_ADMIN_STATE_REVOKED) 
+        fprintf(dlog_file, "SEQ4_STATUS_ADMIN_STATE_REVOKED ");
+    if (flags & SEQ4_STATUS_RECALLABLE_STATE_REVOKED) 
+        fprintf(dlog_file, "SEQ4_STATUS_RECALLABLE_STATE_REVOKED ");
+    if (flags & SEQ4_STATUS_LEASE_MOVED) 
+        fprintf(dlog_file, "SEQ4_STATUS_LEASE_MOVED ");
+    if (flags & SEQ4_STATUS_RESTART_RECLAIM_NEEDED) 
+        fprintf(dlog_file, "SEQ4_STATUS_RESTART_RECLAIM_NEEDED ");
+    if (flags & SEQ4_STATUS_CB_PATH_DOWN_SESSION) 
+        fprintf(dlog_file, "SEQ4_STATUS_CB_PATH_DOWN_SESSION ");
+    if (flags & SEQ4_STATUS_BACKCHANNEL_FAULT) 
+        fprintf(dlog_file, "SEQ4_STATUS_BACKCHANNEL_FAULT ");
+    if (flags & SEQ4_STATUS_DEVID_CHANGED) 
+        fprintf(dlog_file, "SEQ4_STATUS_DEVID_CHANGED ");
+    if (flags & SEQ4_STATUS_DEVID_DELETED) 
+        fprintf(dlog_file, "SEQ4_STATUS_DEVID_DELETED ");
+    fprintf(dlog_file, "\n");
+}
+
+const char* secflavorop2name(DWORD sec_flavor)
+{
+    switch(sec_flavor) {
+    case RPCSEC_AUTH_SYS:      return "AUTH_SYS";
+    case RPCSEC_AUTHGSS_KRB5:  return "AUTHGSS_KRB5";
+    case RPCSEC_AUTHGSS_KRB5I: return "AUTHGSS_KRB5I";
+    case RPCSEC_AUTHGSS_KRB5P: return "AUTHGSS_KRB5P";
+    }
+
+    return "UNKNOWN FLAVOR";
+}
+
+void print_windows_access_mask(int on, ACCESS_MASK m)
+{
+    if (!on) return;
+    dprintf(1, "--> print_windows_access_mask: %x\n", m);
+    if (m & GENERIC_READ)
+        dprintf(1, "\tGENERIC_READ\n");
+    if (m & GENERIC_WRITE)
+        dprintf(1, "\tGENERIC_WRITE\n");
+    if (m & GENERIC_EXECUTE)
+        dprintf(1, "\tGENERIC_EXECUTE\n");
+    if (m & GENERIC_ALL)
+        dprintf(1, "\tGENERIC_ALL\n");
+    if (m & MAXIMUM_ALLOWED)
+        dprintf(1, "\tMAXIMUM_ALLOWED\n");
+    if (m & ACCESS_SYSTEM_SECURITY)
+        dprintf(1, "\tACCESS_SYSTEM_SECURITY\n");
+    if ((m & SPECIFIC_RIGHTS_ALL) == SPECIFIC_RIGHTS_ALL)
+        dprintf(1, "\tSPECIFIC_RIGHTS_ALL\n");
+    if ((m & STANDARD_RIGHTS_ALL) == STANDARD_RIGHTS_ALL)
+        dprintf(1, "\tSTANDARD_RIGHTS_ALL\n");
+    if ((m & STANDARD_RIGHTS_REQUIRED) == STANDARD_RIGHTS_REQUIRED)
+        dprintf(1, "\tSTANDARD_RIGHTS_REQUIRED\n");
+    if (m & SYNCHRONIZE)
+        dprintf(1, "\tSYNCHRONIZE\n");
+    if (m & WRITE_OWNER)
+        dprintf(1, "\tWRITE_OWNER\n");
+    if (m & WRITE_DAC)
+        dprintf(1, "\tWRITE_DAC\n");
+    if (m & READ_CONTROL)
+        dprintf(1, "\tREAD_CONTROL\n");
+    if (m & DELETE)
+        dprintf(1, "\tDELETE\n");
+    if (m & FILE_READ_DATA)
+        dprintf(1, "\tFILE_READ_DATA\n");
+    if (m & FILE_LIST_DIRECTORY)
+        dprintf(1, "\tFILE_LIST_DIRECTORY\n");
+    if (m & FILE_WRITE_DATA)
+        dprintf(1, "\tFILE_WRITE_DATA\n");
+    if (m & FILE_ADD_FILE)
+        dprintf(1, "\tFILE_ADD_FILE\n");
+    if (m & FILE_APPEND_DATA)
+        dprintf(1, "\tFILE_APPEND_DATA\n");
+    if (m & FILE_ADD_SUBDIRECTORY)
+        dprintf(1, "\tFILE_ADD_SUBDIRECTORY\n");
+    if (m & FILE_CREATE_PIPE_INSTANCE)
+        dprintf(1, "\tFILE_CREATE_PIPE_INSTANCE\n");
+    if (m & FILE_READ_EA)
+        dprintf(1, "\tFILE_READ_EA\n");
+    if (m & FILE_WRITE_EA)
+        dprintf(1, "\tFILE_WRITE_EA\n");
+    if (m & FILE_EXECUTE)
+        dprintf(1, "\tFILE_EXECUTE\n");
+    if (m & FILE_TRAVERSE)
+        dprintf(1, "\tFILE_TRAVERSE\n");
+    if (m & FILE_DELETE_CHILD)
+        dprintf(1, "\tFILE_DELETE_CHILD\n");
+    if (m & FILE_READ_ATTRIBUTES)
+        dprintf(1, "\tFILE_READ_ATTRIBUTES\n");
+    if (m & FILE_WRITE_ATTRIBUTES)
+        dprintf(1, "\tFILE_WRITE_ATTRIBUTES\n");
+    if ((m & FILE_ALL_ACCESS) == FILE_ALL_ACCESS)
+        dprintf(1, "\tFILE_ALL_ACCESS\n");
+    if ((m & FILE_GENERIC_READ) == FILE_GENERIC_READ)
+        dprintf(1, "\tFILE_GENERIC_READ\n");
+    if ((m & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE)
+        dprintf(1, "\tFILE_GENERIC_WRITE\n");
+    if ((m & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE)
+        dprintf(1, "\tFILE_GENERIC_EXECUTE\n");
+}
+
+void print_nfs_access_mask(int on, int m)
+{
+    if (!on) return;
+    dprintf(1, "--> print_nfs_access_mask: %x\n", m);
+    if (m & ACE4_READ_DATA)
+        dprintf(1, "\tACE4_READ_DATA\n");
+    if (m & ACE4_LIST_DIRECTORY)
+        dprintf(1, "\tACE4_LIST_DIRECTORY\n");
+    if (m & ACE4_WRITE_DATA)
+        dprintf(1, "\tACE4_WRITE_DATA\n");
+    if (m & ACE4_ADD_FILE)
+        dprintf(1, "\tACE4_ADD_FILE\n");
+    if (m & ACE4_APPEND_DATA)
+        dprintf(1, "\tACE4_APPEND_DATA\n");
+    if (m & ACE4_ADD_SUBDIRECTORY)
+        dprintf(1, "\tACE4_ADD_SUBDIRECTORY\n");
+    if (m & ACE4_READ_NAMED_ATTRS)
+        dprintf(1, "\tACE4_READ_NAMED_ATTRS\n");
+    if (m & ACE4_WRITE_NAMED_ATTRS)
+        dprintf(1, "\tACE4_WRITE_NAMED_ATTRS\n");
+    if (m & ACE4_EXECUTE)
+        dprintf(1, "\tACE4_EXECUTE\n");
+    if (m & ACE4_DELETE_CHILD)
+        dprintf(1, "\tACE4_DELETE_CHILD\n");
+    if (m & ACE4_READ_ATTRIBUTES)
+        dprintf(1, "\tACE4_READ_ATTRIBUTES\n");
+    if (m & ACE4_WRITE_ATTRIBUTES)
+        dprintf(1, "\tACE4_WRITE_ATTRIBUTES\n");
+    if (m & ACE4_DELETE)
+        dprintf(1, "\tACE4_DELETE\n");
+    if (m & ACE4_READ_ACL)
+        dprintf(1, "\tACE4_READ_ACL\n");
+    if (m & ACE4_WRITE_ACL)
+        dprintf(1, "\tACE4_WRITE_ACL\n");
+    if (m & ACE4_WRITE_OWNER)
+        dprintf(1, "\tACE4_WRITE_OWNER\n");
+    if (m & ACE4_SYNCHRONIZE)
+        dprintf(1, "\tACE4_SYNCHRONIZE\n");
+}
diff --git a/reactos/base/services/nfsd/daemon_debug.h b/reactos/base/services/nfsd/daemon_debug.h
new file mode 100644 (file)
index 0000000..5b4f247
--- /dev/null
@@ -0,0 +1,78 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 _DAEMON_DEBUG_
+#define _DAEMON_DEBUG_
+
+#ifdef _DEBUG
+/* use visual studio's debug heap */
+# define _CRTDBG_MAP_ALLOC
+# include <stdlib.h>
+# include <crtdbg.h>
+#else
+# include <stdlib.h>
+#endif
+
+#define DEFAULT_DEBUG_LEVEL 1
+
+
+/* daemon_debug.h */
+void set_debug_level(int level);
+void dprintf(int level, LPCSTR format, ...);
+void eprintf(LPCSTR format, ...);
+
+void print_windows_access_mask(int on, ACCESS_MASK m);
+void print_nfs_access_mask(int on, int m);
+void print_hexbuf_no_asci(int on, unsigned char *title, unsigned char *buf, int len);
+void print_hexbuf(int level, unsigned char *title, unsigned char *buf, int len);
+void print_create_attributes(int level, DWORD create_opts);
+void print_disposition(int level, DWORD disposition);
+void print_access_mask(int level, DWORD access_mask);
+void print_share_mode(int level, DWORD mode);
+void print_file_id_both_dir_info(int level, FILE_ID_BOTH_DIR_INFO *p);
+void print_opcode(int level, DWORD opcode);
+const char* opcode2string(DWORD opcode);
+const char* nfs_opnum_to_string(int opnum);
+const char* nfs_error_string(int status);
+const char* rpc_error_string(int status);
+const char* gssauth_string(int type);
+void print_condwait_status(int level, int status);
+void print_sr_status_flags(int level, int flags);
+void open_log_files();
+void close_log_files();
+const char* secflavorop2name(DWORD sec_flavor);
+
+/* pnfs_debug.c */
+enum pnfs_status;
+enum pnfs_layout_type;
+enum pnfs_iomode;
+struct __pnfs_file_layout;
+struct __pnfs_file_device;
+
+const char* pnfs_error_string(enum pnfs_status status);
+const char* pnfs_layout_type_string(enum pnfs_layout_type type);
+const char* pnfs_iomode_string(enum pnfs_iomode iomode);
+
+void dprint_deviceid(int level, const char *title, const unsigned char *deviceid);
+void dprint_layout(int level, const struct __pnfs_file_layout *layout);
+void dprint_device(int level, const struct __pnfs_file_device *device);
+
+#endif
diff --git a/reactos/base/services/nfsd/delegation.c b/reactos/base/services/nfsd/delegation.c
new file mode 100644 (file)
index 0000000..994134f
--- /dev/null
@@ -0,0 +1,941 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 "delegation.h"
+#include "nfs41_ops.h"
+#include "name_cache.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+#include <devioctl.h>
+#include "nfs41_driver.h" /* for making downcall to invalidate cache */
+#include "util.h"
+
+#define DGLVL 2 /* dprintf level for delegation logging */
+
+
+/* allocation and reference counting */
+static int delegation_create(
+    IN const nfs41_path_fh *parent,
+    IN const nfs41_path_fh *file,
+    IN const open_delegation4 *delegation,
+    OUT nfs41_delegation_state **deleg_out)
+{
+    nfs41_delegation_state *state;
+    int status = NO_ERROR;
+
+    state = calloc(1, sizeof(nfs41_delegation_state));
+    if (state == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    memcpy(&state->state, delegation, sizeof(open_delegation4));
+
+    abs_path_copy(&state->path, file->path);
+    path_fh_init(&state->file, &state->path);
+    fh_copy(&state->file.fh, &file->fh);
+    path_fh_init(&state->parent, &state->path);
+    last_component(state->path.path, state->file.name.name,
+        &state->parent.name);
+    fh_copy(&state->parent.fh, &parent->fh);
+
+    list_init(&state->client_entry);
+    state->status = DELEGATION_GRANTED;
+    InitializeSRWLock(&state->lock);
+    InitializeConditionVariable(&state->cond);
+    state->ref_count = 1;
+    *deleg_out = state;
+out:
+    return status;
+}
+
+void nfs41_delegation_ref(
+    IN nfs41_delegation_state *state)
+{
+    const LONG count = InterlockedIncrement(&state->ref_count);
+    dprintf(DGLVL, "nfs41_delegation_ref(%s) count %d\n",
+        state->path.path, count);
+}
+
+void nfs41_delegation_deref(
+    IN nfs41_delegation_state *state)
+{
+    const LONG count = InterlockedDecrement(&state->ref_count);
+    dprintf(DGLVL, "nfs41_delegation_deref(%s) count %d\n",
+        state->path.path, count);
+    if (count == 0)
+        free(state);
+}
+
+#define open_entry(pos) list_container(pos, nfs41_open_state, client_entry)
+
+static void delegation_remove(
+    IN nfs41_client *client,
+    IN nfs41_delegation_state *deleg)
+{
+    struct list_entry *entry;
+
+    /* remove from the client's list */
+    EnterCriticalSection(&client->state.lock);
+    list_remove(&deleg->client_entry);
+
+    /* remove from each associated open */
+    list_for_each(entry, &client->state.opens) {
+        nfs41_open_state *open = open_entry(entry);
+        AcquireSRWLockExclusive(&open->lock);
+        if (open->delegation.state == deleg) {
+            /* drop the delegation reference */
+            nfs41_delegation_deref(open->delegation.state);
+            open->delegation.state = NULL;
+        }
+        ReleaseSRWLockExclusive(&open->lock);
+    }
+    LeaveCriticalSection(&client->state.lock);
+
+    /* signal threads waiting on delegreturn */
+    AcquireSRWLockExclusive(&deleg->lock);
+    deleg->status = DELEGATION_RETURNED;
+    WakeAllConditionVariable(&deleg->cond);
+    ReleaseSRWLockExclusive(&deleg->lock);
+
+    /* release the client's reference */
+    nfs41_delegation_deref(deleg);
+}
+
+
+/* delegation return */
+#define lock_entry(pos) list_container(pos, nfs41_lock_state, open_entry)
+
+static bool_t has_delegated_locks(
+    IN nfs41_open_state *open)
+{
+    struct list_entry *entry;
+    list_for_each(entry, &open->locks.list) {
+        if (lock_entry(entry)->delegated)
+            return TRUE;
+    }
+    return FALSE;
+}
+
+static int open_deleg_cmp(const struct list_entry *entry, const void *value)
+{
+    nfs41_open_state *open = open_entry(entry);
+    int result = -1;
+
+    /* open must match the delegation and have state to reclaim */
+    AcquireSRWLockShared(&open->lock);
+    if (open->delegation.state != value) goto out;
+    if (open->do_close && !has_delegated_locks(open)) goto out;
+    result = 0;
+out:
+    ReleaseSRWLockShared(&open->lock);
+    return result;
+}
+
+/* find the first open that needs recovery */
+static nfs41_open_state* deleg_open_find(
+    IN struct client_state *state,
+    IN const nfs41_delegation_state *deleg)
+{
+    struct list_entry *entry;
+    nfs41_open_state *open = NULL;
+
+    EnterCriticalSection(&state->lock);
+    entry = list_search(&state->opens, deleg, open_deleg_cmp);
+    if (entry) {
+        open = open_entry(entry);
+        nfs41_open_state_ref(open); /* return a reference */
+    }
+    LeaveCriticalSection(&state->lock);
+    return open;
+}
+
+/* find the first lock that needs recovery */
+static bool_t deleg_lock_find(
+    IN nfs41_open_state *open,
+    OUT nfs41_lock_state *lock_out)
+{
+    struct list_entry *entry;
+    bool_t found = FALSE;
+
+    AcquireSRWLockShared(&open->lock);
+    list_for_each(entry, &open->locks.list) {
+        nfs41_lock_state *lock = lock_entry(entry);
+        if (lock->delegated) {
+            /* copy offset, length, type */
+            lock_out->offset = lock->offset;
+            lock_out->length = lock->length;
+            lock_out->exclusive = lock->exclusive;
+            lock_out->id = lock->id;
+            found = TRUE;
+            break;
+        }
+    }
+    ReleaseSRWLockShared(&open->lock);
+    return found;
+}
+
+/* find the matching lock by id, and reset lock.delegated */
+static void deleg_lock_update(
+    IN nfs41_open_state *open,
+    IN const nfs41_lock_state *source)
+{
+    struct list_entry *entry;
+
+    AcquireSRWLockExclusive(&open->lock);
+    list_for_each(entry, &open->locks.list) {
+        nfs41_lock_state *lock = lock_entry(entry);
+        if (lock->id == source->id) {
+            lock->delegated = FALSE;
+            break;
+        }
+    }
+    ReleaseSRWLockExclusive(&open->lock);
+}
+
+static int delegation_flush_locks(
+    IN nfs41_open_state *open,
+    IN bool_t try_recovery)
+{
+    stateid_arg stateid;
+    nfs41_lock_state lock;
+    int status = NFS4_OK;
+
+    stateid.open = open;
+    stateid.delegation = NULL;
+
+    /* get the starting open/lock stateid */
+    AcquireSRWLockShared(&open->lock);
+    if (open->locks.stateid.seqid) {
+        memcpy(&stateid.stateid, &open->locks.stateid, sizeof(stateid4));
+        stateid.type = STATEID_LOCK;
+    } else {
+        memcpy(&stateid.stateid, &open->stateid, sizeof(stateid4));
+        stateid.type = STATEID_OPEN;
+    }
+    ReleaseSRWLockShared(&open->lock);
+
+    /* send LOCK requests for each delegated lock range */
+    while (deleg_lock_find(open, &lock)) {
+        status = nfs41_lock(open->session, &open->file,
+            &open->owner, lock.exclusive ? WRITE_LT : READ_LT,
+            lock.offset, lock.length, FALSE, try_recovery, &stateid);
+        if (status)
+            break;
+        deleg_lock_update(open, &lock);
+    }
+
+    /* save the updated lock stateid */
+    if (stateid.type == STATEID_LOCK) {
+        AcquireSRWLockExclusive(&open->lock);
+        if (open->locks.stateid.seqid == 0) {
+            /* if it's a new lock stateid, copy it in */
+            memcpy(&open->locks.stateid, &stateid.stateid, sizeof(stateid4));
+        } else if (stateid.stateid.seqid > open->locks.stateid.seqid) {
+            /* update the seqid if it's more recent */
+            open->locks.stateid.seqid = stateid.stateid.seqid;
+        }
+        ReleaseSRWLockExclusive(&open->lock);
+    }
+    return status;
+}
+
+#pragma warning (disable : 4706) /* assignment within conditional expression */
+
+static int delegation_return(
+    IN nfs41_client *client,
+    IN nfs41_delegation_state *deleg,
+    IN bool_t truncate,
+    IN bool_t try_recovery)
+{
+    stateid_arg stateid;
+    nfs41_open_state *open;
+    int status;
+
+    if (deleg->srv_open) {
+        /* make an upcall to the kernel: invalide data cache */
+        HANDLE pipe;
+        unsigned char inbuf[sizeof(HANDLE)], *buffer = inbuf; 
+        DWORD inbuf_len = sizeof(HANDLE), outbuf_len, dstatus;
+        uint32_t length;
+        dprintf(1, "delegation_return: making a downcall for srv_open=%x\n", 
+            deleg->srv_open);
+        pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ|GENERIC_WRITE, 
+                FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+        if (pipe == INVALID_HANDLE_VALUE) {
+            eprintf("delegation_return: Unable to open downcall pipe %d\n", 
+                GetLastError());
+            goto out_downcall;
+        }
+        length = inbuf_len;
+        safe_write(&buffer, &length, &deleg->srv_open, sizeof(HANDLE));
+
+        dstatus = DeviceIoControl(pipe, IOCTL_NFS41_INVALCACHE, inbuf, inbuf_len,
+            NULL, 0, (LPDWORD)&outbuf_len, NULL);
+        if (!dstatus)
+            eprintf("IOCTL_NFS41_INVALCACHE failed %d\n", GetLastError());
+        CloseHandle(pipe);
+    }
+out_downcall:
+
+    /* recover opens and locks associated with the delegation */
+    while (open = deleg_open_find(&client->state, deleg)) {
+        status = nfs41_delegation_to_open(open, try_recovery);
+        if (status == NFS4_OK)
+            status = delegation_flush_locks(open, try_recovery);
+        nfs41_open_state_deref(open);
+
+        if (status)
+            break;
+    }
+
+    /* return the delegation */
+    stateid.type = STATEID_DELEG_FILE;
+    stateid.open = NULL;
+    stateid.delegation = deleg;
+    AcquireSRWLockShared(&deleg->lock);
+    memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4));
+    ReleaseSRWLockShared(&deleg->lock);
+
+    status = nfs41_delegreturn(client->session,
+        &deleg->file, &stateid, try_recovery);
+    if (status == NFS4ERR_BADSESSION)
+        goto out;
+
+    delegation_remove(client, deleg);
+out:
+    return status;
+}
+
+/* open delegation */
+int nfs41_delegation_granted(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN nfs41_path_fh *file,
+    IN open_delegation4 *delegation,
+    IN bool_t try_recovery,
+    OUT nfs41_delegation_state **deleg_out)
+{
+    stateid_arg stateid;
+    nfs41_client *client = session->client;
+    nfs41_delegation_state *state;
+    int status = NO_ERROR;
+
+    if (delegation->type != OPEN_DELEGATE_READ &&
+        delegation->type != OPEN_DELEGATE_WRITE)
+        goto out;
+
+    if (delegation->recalled) {
+        status = NFS4ERR_DELEG_REVOKED;
+        goto out_return;
+    }
+
+    /* allocate the delegation state */
+    status = delegation_create(parent, file, delegation, &state);
+    if (status)
+        goto out_return;
+
+    /* register the delegation with the client */
+    EnterCriticalSection(&client->state.lock);
+    /* XXX: check for duplicates by fh and stateid? */
+    list_add_tail(&client->state.delegations, &state->client_entry);
+    LeaveCriticalSection(&client->state.lock);
+
+    nfs41_delegation_ref(state); /* return a reference */
+    *deleg_out = state;
+out:
+    return status;
+
+out_return: /* return the delegation on failure */
+    memcpy(&stateid.stateid, &delegation->stateid, sizeof(stateid4));
+    stateid.type = STATEID_DELEG_FILE;
+    stateid.open = NULL;
+    stateid.delegation = NULL;
+    nfs41_delegreturn(session, file, &stateid, try_recovery);
+    goto out;
+}
+
+#define deleg_entry(pos) list_container(pos, nfs41_delegation_state, client_entry)
+
+static int deleg_file_cmp(const struct list_entry *entry, const void *value)
+{
+    const nfs41_fh *lhs = &deleg_entry(entry)->file.fh;
+    const nfs41_fh *rhs = (const nfs41_fh*)value;
+    if (lhs->superblock != rhs->superblock) return -1;
+    if (lhs->fileid != rhs->fileid) return -1;
+    return 0;
+}
+
+static bool_t delegation_compatible(
+    IN enum open_delegation_type4 type,
+    IN uint32_t create,
+    IN uint32_t access,
+    IN uint32_t deny)
+{
+    switch (type) {
+    case OPEN_DELEGATE_WRITE:
+        /* An OPEN_DELEGATE_WRITE delegation allows the client to handle,
+         * on its own, all opens. */
+        return TRUE;
+
+    case OPEN_DELEGATE_READ:
+        /* An OPEN_DELEGATE_READ delegation allows a client to handle,
+         * on its own, requests to open a file for reading that do not
+         * deny OPEN4_SHARE_ACCESS_READ access to others. */
+        if (create == OPEN4_CREATE)
+            return FALSE;
+        if (access & OPEN4_SHARE_ACCESS_WRITE || deny & OPEN4_SHARE_DENY_READ)
+            return FALSE;
+        return TRUE;
+
+    default:
+        return FALSE;
+    }
+}
+
+static int delegation_find(
+    IN nfs41_client *client,
+    IN const void *value,
+    IN list_compare_fn cmp,
+    OUT nfs41_delegation_state **deleg_out)
+{
+    struct list_entry *entry;
+    int status = NFS4ERR_BADHANDLE;
+
+    EnterCriticalSection(&client->state.lock);
+    entry = list_search(&client->state.delegations, value, cmp);
+    if (entry) {
+        /* return a reference to the delegation */
+        *deleg_out = deleg_entry(entry);
+        nfs41_delegation_ref(*deleg_out);
+
+        /* move to the 'most recently used' end of the list */
+        list_remove(entry);
+        list_add_tail(&client->state.delegations, entry);
+        status = NFS4_OK;
+    }
+    LeaveCriticalSection(&client->state.lock);
+    return status;
+}
+
+static int delegation_truncate(
+    IN nfs41_delegation_state *deleg,
+    IN nfs41_client *client,
+    IN stateid_arg *stateid,
+    IN nfs41_file_info *info)
+{
+    nfs41_superblock *superblock = deleg->file.fh.superblock;
+
+    /* use SETATTR to truncate the file */
+    info->attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE |
+        FATTR4_WORD1_TIME_MODIFY_SET;
+
+    get_nfs_time(&info->time_create);
+    get_nfs_time(&info->time_modify);
+    info->time_delta = &superblock->time_delta;
+
+    /* mask out unsupported attributes */
+    nfs41_superblock_supported_attrs(superblock, &info->attrmask);
+
+    return nfs41_setattr(client->session, &deleg->file, stateid, info);
+}
+
+int nfs41_delegate_open(
+    IN nfs41_open_state *state,
+    IN uint32_t create,
+    IN OPTIONAL nfs41_file_info *createattrs,
+    OUT nfs41_file_info *info)
+{
+    nfs41_client *client = state->session->client;
+    nfs41_path_fh *file = &state->file;
+    uint32_t access = state->share_access;
+    uint32_t deny = state->share_deny;
+    nfs41_delegation_state *deleg;
+    stateid_arg stateid;
+    int status;
+
+    /* search for a delegation with this filehandle */
+    status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg);
+    if (status)
+        goto out;
+
+    AcquireSRWLockExclusive(&deleg->lock);
+    if (deleg->status != DELEGATION_GRANTED) {
+        /* the delegation is being returned, wait for it to finish */
+        while (deleg->status != DELEGATION_RETURNED)
+            SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0);
+        status = NFS4ERR_BADHANDLE;
+    }
+    else if (!delegation_compatible(deleg->state.type, create, access, deny)) {
+#ifdef DELEGATION_RETURN_ON_CONFLICT
+        /* this open will conflict, start the delegation return */
+        deleg->status = DELEGATION_RETURNING;
+        status = NFS4ERR_DELEG_REVOKED;
+#else
+        status = NFS4ERR_BADHANDLE;
+#endif
+    } else if (create == OPEN4_CREATE) {
+        /* copy the stateid for SETATTR */
+        stateid.open = NULL;
+        stateid.delegation = deleg;
+        stateid.type = STATEID_DELEG_FILE;
+        memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4));
+    }
+    if (!status) {
+        dprintf(1, "nfs41_delegate_open: updating srv_open from %x to %x\n", 
+            deleg->srv_open, state->srv_open);
+        deleg->srv_open = state->srv_open;
+    }
+    ReleaseSRWLockExclusive(&deleg->lock);
+
+    if (status == NFS4ERR_DELEG_REVOKED)
+        goto out_return;
+    if (status)
+        goto out_deleg;
+
+    if (create == OPEN4_CREATE) {
+        memcpy(info, createattrs, sizeof(nfs41_file_info));
+
+        /* write delegations allow us to simulate OPEN4_CREATE with SETATTR */
+        status = delegation_truncate(deleg, client, &stateid, info);
+        if (status)
+            goto out_deleg;
+    }
+
+    /* TODO: check access against deleg->state.permissions or send ACCESS */
+
+    state->delegation.state = deleg;
+    status = NFS4_OK;
+out:
+    return status;
+
+out_return:
+    delegation_return(client, deleg, create == OPEN4_CREATE, TRUE);
+
+out_deleg:
+    nfs41_delegation_deref(deleg);
+    goto out;
+}
+
+int nfs41_delegation_to_open(
+    IN nfs41_open_state *open,
+    IN bool_t try_recovery)
+{
+    open_delegation4 ignore;
+    open_claim4 claim;
+    stateid4 open_stateid = { 0 };
+    stateid_arg deleg_stateid;
+    int status = NFS4_OK;
+
+    AcquireSRWLockExclusive(&open->lock);
+    if (open->delegation.state == NULL) /* no delegation to reclaim */
+        goto out_unlock;
+
+    if (open->do_close) /* already have an open stateid */
+        goto out_unlock;
+
+    /* if another thread is reclaiming the open stateid,
+     * wait for it to finish before returning success */
+    if (open->delegation.reclaim) {
+        do {
+            SleepConditionVariableSRW(&open->delegation.cond, &open->lock,
+                INFINITE, 0);
+        } while (open->delegation.reclaim);
+        if (open->do_close)
+            goto out_unlock;
+    }
+    open->delegation.reclaim = 1;
+
+    AcquireSRWLockShared(&open->delegation.state->lock);
+    deleg_stateid.open = open;
+    deleg_stateid.delegation = NULL;
+    deleg_stateid.type = STATEID_DELEG_FILE;
+    memcpy(&deleg_stateid.stateid, &open->delegation.state->state.stateid,
+        sizeof(stateid4));
+    ReleaseSRWLockShared(&open->delegation.state->lock);
+
+    ReleaseSRWLockExclusive(&open->lock);
+
+    /* send OPEN with CLAIM_DELEGATE_CUR */
+    claim.claim = CLAIM_DELEGATE_CUR;
+    claim.u.deleg_cur.delegate_stateid = &deleg_stateid;
+    claim.u.deleg_cur.name = &open->file.name;
+
+    status = nfs41_open(open->session, &open->parent, &open->file,
+        &open->owner, &claim, open->share_access, open->share_deny,
+        OPEN4_NOCREATE, 0, NULL, try_recovery, &open_stateid, &ignore, NULL);
+
+    AcquireSRWLockExclusive(&open->lock);
+    if (status == NFS4_OK) {
+        /* save the new open stateid */
+        memcpy(&open->stateid, &open_stateid, sizeof(stateid4));
+        open->do_close = 1;
+    } else if (open->do_close && (status == NFS4ERR_BAD_STATEID ||
+        status == NFS4ERR_STALE_STATEID || status == NFS4ERR_EXPIRED)) {
+        /* something triggered client state recovery, and the open stateid
+         * has already been reclaimed; see recover_stateid_delegation() */
+        status = NFS4_OK;
+    }
+    open->delegation.reclaim = 0;
+
+    /* signal anyone waiting on the open stateid */
+    WakeAllConditionVariable(&open->delegation.cond);
+out_unlock:
+    ReleaseSRWLockExclusive(&open->lock);
+    if (status)
+        eprintf("nfs41_delegation_to_open(%p) failed with %s\n",
+            open, nfs_error_string(status));
+    return status;
+}
+
+void nfs41_delegation_remove_srvopen(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file)
+{
+    nfs41_delegation_state *deleg = NULL;
+
+    /* find a delegation for this file */
+    if (delegation_find(session->client, &file->fh, deleg_file_cmp, &deleg))
+        return;
+    dprintf(1, "nfs41_delegation_remove_srvopen: removing reference to "
+        "srv_open=%x\n", deleg->srv_open);
+    AcquireSRWLockExclusive(&deleg->lock);
+    deleg->srv_open = NULL;
+    ReleaseSRWLockExclusive(&deleg->lock);
+    nfs41_delegation_deref(deleg);
+}
+
+/* synchronous delegation return */
+#ifdef DELEGATION_RETURN_ON_CONFLICT
+int nfs41_delegation_return(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+#ifndef __REACTOS__
+    IN enum open_delegation_type4 access,
+#else
+    IN int access,
+#endif
+    IN bool_t truncate)
+{
+    nfs41_client *client = session->client;
+    nfs41_delegation_state *deleg;
+    int status;
+
+    /* find a delegation for this file */
+    status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg);
+    if (status)
+        goto out;
+
+    AcquireSRWLockExclusive(&deleg->lock);
+    if (deleg->status == DELEGATION_GRANTED) {
+        /* return unless delegation is write and access is read */
+        if (deleg->state.type != OPEN_DELEGATE_WRITE
+            || access != OPEN_DELEGATE_READ) {
+            deleg->status = DELEGATION_RETURNING;
+            status = NFS4ERR_DELEG_REVOKED;
+        }
+    } else {
+        /* the delegation is being returned, wait for it to finish */
+        while (deleg->status == DELEGATION_RETURNING)
+            SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0);
+        status = NFS4ERR_BADHANDLE;
+    }
+    ReleaseSRWLockExclusive(&deleg->lock);
+
+    if (status == NFS4ERR_DELEG_REVOKED) {
+        delegation_return(client, deleg, truncate, TRUE);
+        status = NFS4_OK;
+    }
+
+    nfs41_delegation_deref(deleg);
+out:
+    return status;
+}
+#endif
+
+
+/* asynchronous delegation recall */
+struct recall_thread_args {
+    nfs41_client            *client;
+    nfs41_delegation_state  *delegation;
+    bool_t                  truncate;
+};
+
+static unsigned int WINAPI delegation_recall_thread(void *args)
+{
+    struct recall_thread_args *recall = (struct recall_thread_args*)args;
+
+    delegation_return(recall->client, recall->delegation, recall->truncate, TRUE);
+
+    /* clean up thread arguments */
+    nfs41_delegation_deref(recall->delegation);
+    nfs41_root_deref(recall->client->root);
+    free(recall);
+    return 0;
+}
+
+static int deleg_stateid_cmp(const struct list_entry *entry, const void *value)
+{
+    const stateid4 *lhs = &deleg_entry(entry)->state.stateid;
+    const stateid4 *rhs = (const stateid4*)value;
+    return memcmp(lhs->other, rhs->other, NFS4_STATEID_OTHER);
+}
+
+int nfs41_delegation_recall(
+    IN nfs41_client *client,
+    IN nfs41_fh *fh,
+    IN const stateid4 *stateid,
+    IN bool_t truncate)
+{
+    nfs41_delegation_state *deleg;
+    struct recall_thread_args *args;
+    int status;
+
+    dprintf(2, "--> nfs41_delegation_recall()\n");
+
+    /* search for the delegation by stateid instead of filehandle;
+     * deleg_file_cmp() relies on a proper superblock and fileid,
+     * which we don't get with CB_RECALL */
+    status = delegation_find(client, stateid, deleg_stateid_cmp, &deleg);
+    if (status)
+        goto out;
+
+    AcquireSRWLockExclusive(&deleg->lock);
+    if (deleg->state.recalled) {
+        /* return BADHANDLE if we've already responded to CB_RECALL */
+        status = NFS4ERR_BADHANDLE;
+    } else {
+        deleg->state.recalled = 1;
+
+        if (deleg->status == DELEGATION_GRANTED) {
+            /* start the delegation return */
+            deleg->status = DELEGATION_RETURNING;
+            status = NFS4ERR_DELEG_REVOKED;
+        } /* else return NFS4_OK */
+    }
+    ReleaseSRWLockExclusive(&deleg->lock);
+
+    if (status != NFS4ERR_DELEG_REVOKED)
+        goto out_deleg;
+
+    /* allocate thread arguments */
+    args = calloc(1, sizeof(struct recall_thread_args));
+    if (args == NULL) {
+        status = NFS4ERR_SERVERFAULT;
+        eprintf("nfs41_delegation_recall() failed to allocate arguments\n");
+        goto out_deleg;
+    }
+
+    /* hold a reference on the root */
+    nfs41_root_ref(client->root);
+    args->client = client;
+    args->delegation = deleg;
+    args->truncate = truncate;
+
+    /* the callback thread can't make rpc calls, so spawn a separate thread */
+    if (_beginthreadex(NULL, 0, delegation_recall_thread, args, 0, NULL) == 0) {
+        status = NFS4ERR_SERVERFAULT;
+        eprintf("nfs41_delegation_recall() failed to start thread\n");
+        goto out_args;
+    }
+    status = NFS4_OK;
+out:
+    dprintf(DGLVL, "<-- nfs41_delegation_recall() returning %s\n",
+        nfs_error_string(status));
+    return status;
+
+out_args:
+    free(args);
+    nfs41_root_deref(client->root);
+out_deleg:
+    nfs41_delegation_deref(deleg);
+    goto out;
+}
+
+
+static int deleg_fh_cmp(const struct list_entry *entry, const void *value)
+{
+    const nfs41_fh *lhs = &deleg_entry(entry)->file.fh;
+    const nfs41_fh *rhs = (const nfs41_fh*)value;
+    if (lhs->len != rhs->len) return -1;
+    return memcmp(lhs->fh, rhs->fh, lhs->len);
+}
+
+int nfs41_delegation_getattr(
+    IN nfs41_client *client,
+    IN const nfs41_fh *fh,
+    IN const bitmap4 *attr_request,
+    OUT nfs41_file_info *info)
+{
+    nfs41_delegation_state *deleg;
+    uint64_t fileid;
+    int status;
+
+    dprintf(2, "--> nfs41_delegation_getattr()\n");
+
+    /* search for a delegation on this file handle */
+    status = delegation_find(client, fh, deleg_fh_cmp, &deleg);
+    if (status)
+        goto out;
+
+    AcquireSRWLockShared(&deleg->lock);
+    fileid = deleg->file.fh.fileid;
+    if (deleg->status != DELEGATION_GRANTED ||
+        deleg->state.type != OPEN_DELEGATE_WRITE) {
+        status = NFS4ERR_BADHANDLE;
+    }
+    ReleaseSRWLockShared(&deleg->lock);
+    if (status)
+        goto out_deleg;
+
+    ZeroMemory(info, sizeof(nfs41_file_info));
+
+    /* find attributes for the given fileid */
+    status = nfs41_attr_cache_lookup(
+        client_name_cache(client), fileid, info);
+    if (status) {
+        status = NFS4ERR_BADHANDLE;
+        goto out_deleg;
+    }
+out_deleg:
+    nfs41_delegation_deref(deleg);
+out:
+    dprintf(DGLVL, "<-- nfs41_delegation_getattr() returning %s\n",
+        nfs_error_string(status));
+    return status;
+}
+
+
+void nfs41_client_delegation_free(
+    IN nfs41_client *client)
+{
+    struct list_entry *entry, *tmp;
+
+    EnterCriticalSection(&client->state.lock);
+    list_for_each_tmp (entry, tmp, &client->state.delegations) {
+        list_remove(entry);
+        nfs41_delegation_deref(deleg_entry(entry));
+    }
+    LeaveCriticalSection(&client->state.lock);
+}
+
+
+static int delegation_recovery_status(
+    IN nfs41_delegation_state *deleg)
+{
+    int status = NFS4_OK;
+
+    AcquireSRWLockExclusive(&deleg->lock);
+    if (deleg->status == DELEGATION_GRANTED) {
+        if (deleg->revoked) {
+            deleg->status = DELEGATION_RETURNED;
+            status = NFS4ERR_BADHANDLE;
+        } else if (deleg->state.recalled) {
+            deleg->status = DELEGATION_RETURNING;
+            status = NFS4ERR_DELEG_REVOKED;
+        }
+    }
+    ReleaseSRWLockExclusive(&deleg->lock);
+    return status;
+}
+
+int nfs41_client_delegation_recovery(
+    IN nfs41_client *client)
+{
+    struct list_entry *entry, *tmp;
+    nfs41_delegation_state *deleg;
+    int status = NFS4_OK;
+
+    list_for_each_tmp(entry, tmp, &client->state.delegations) {
+        deleg = list_container(entry, nfs41_delegation_state, client_entry);
+
+        status = delegation_recovery_status(deleg);
+        switch (status) {
+        case NFS4ERR_DELEG_REVOKED:
+            /* the delegation was reclaimed, but flagged as recalled;
+             * return it with try_recovery=FALSE */
+            status = delegation_return(client, deleg, FALSE, FALSE);
+            break;
+
+        case NFS4ERR_BADHANDLE:
+            /* reclaim failed, so we have no delegation state on the server;
+             * 'forget' the delegation without trying to return it */
+            delegation_remove(client, deleg);
+            status = NFS4_OK;
+            break;
+        }
+
+        if (status == NFS4ERR_BADSESSION)
+            goto out;
+    }
+
+    /* use DELEGPURGE to indicate that we're done reclaiming delegations */
+    status = nfs41_delegpurge(client->session);
+
+    /* support for DELEGPURGE is optional; ignore any errors but BADSESSION */
+    if (status != NFS4ERR_BADSESSION)
+        status = NFS4_OK;
+out:
+    return status;
+}
+
+
+int nfs41_client_delegation_return_lru(
+    IN nfs41_client *client)
+{
+    struct list_entry *entry;
+    nfs41_delegation_state *state = NULL;
+    int status = NFS4ERR_BADHANDLE;
+
+    /* starting from the least recently opened, find and return
+     * the first delegation that's not 'in use' (currently open) */
+
+    /* TODO: use a more robust algorithm, taking into account:
+     *  -number of total opens
+     *  -time since last operation on an associated open, or
+     *  -number of operations/second over last n seconds */
+    EnterCriticalSection(&client->state.lock);
+    list_for_each(entry, &client->state.delegations) {
+        state = deleg_entry(entry);
+
+        /* skip if it's currently in use for an open; note that ref_count
+         * can't go from 1 to 2 without holding client->state.lock */
+        if (state->ref_count > 1)
+            continue;
+
+        AcquireSRWLockExclusive(&state->lock);
+        if (state->status == DELEGATION_GRANTED) {
+            /* start returning the delegation */
+            state->status = DELEGATION_RETURNING;
+            status = NFS4ERR_DELEG_REVOKED;
+        }
+        ReleaseSRWLockExclusive(&state->lock);
+
+        if (status == NFS4ERR_DELEG_REVOKED)
+            break;
+    }
+    LeaveCriticalSection(&client->state.lock);
+
+    if (status == NFS4ERR_DELEG_REVOKED)
+        status = delegation_return(client, state, FALSE, TRUE);
+    return status;
+}
diff --git a/reactos/base/services/nfsd/delegation.h b/reactos/base/services/nfsd/delegation.h
new file mode 100644 (file)
index 0000000..d71be17
--- /dev/null
@@ -0,0 +1,113 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 DELEGATION_H
+#define DELEGATION_H
+
+#include "nfs41.h"
+
+
+/* option to avoid conflicts by returning the delegation */
+#define DELEGATION_RETURN_ON_CONFLICT
+
+
+/* reference counting and cleanup */
+void nfs41_delegation_ref(
+    IN nfs41_delegation_state *state);
+
+void nfs41_delegation_deref(
+    IN nfs41_delegation_state *state);
+
+void nfs41_client_delegation_free(
+    IN nfs41_client *client);
+
+
+/* open delegation */
+int nfs41_delegation_granted(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN nfs41_path_fh *file,
+    IN open_delegation4 *delegation,
+    IN bool_t try_recovery,
+    OUT nfs41_delegation_state **deleg_out);
+
+int nfs41_delegate_open(
+    IN nfs41_open_state *state,
+    IN uint32_t create,
+    IN OPTIONAL nfs41_file_info *createattrs,
+    OUT nfs41_file_info *info);
+
+int nfs41_delegation_to_open(
+    IN nfs41_open_state *open,
+    IN bool_t try_recovery);
+
+void nfs41_delegation_remove_srvopen(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file);
+
+/* synchronous delegation return */
+#ifdef DELEGATION_RETURN_ON_CONFLICT
+int nfs41_delegation_return(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+#ifndef __REACTOS__
+    IN enum open_delegation_type4 access,
+#else
+    IN int access,
+#endif
+    IN bool_t truncate);
+#else
+static int nfs41_delegation_return(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN enum open_delegation_type4 access,
+    IN bool_t truncate)
+{
+    return NFS4_OK;
+}
+#endif
+
+
+/* asynchronous delegation recall */
+int nfs41_delegation_recall(
+    IN nfs41_client *client,
+    IN nfs41_fh *fh,
+    IN const stateid4 *stateid,
+    IN bool_t truncate);
+
+int nfs41_delegation_getattr(
+    IN nfs41_client *client,
+    IN const nfs41_fh *fh,
+    IN const bitmap4 *attr_request,
+    OUT nfs41_file_info *info);
+
+
+/* after client state recovery, return any 'recalled' delegations;
+ * must be called under the client's state lock */
+int nfs41_client_delegation_recovery(
+    IN nfs41_client *client);
+
+/* attempt to return the least recently used delegation;
+ * fails with NFS4ERR_BADHANDLE if all delegations are in use */
+int nfs41_client_delegation_return_lru(
+    IN nfs41_client *client);
+
+#endif /* DELEGATION_H */
diff --git a/reactos/base/services/nfsd/ea.c b/reactos/base/services/nfsd/ea.c
new file mode 100644 (file)
index 0000000..0a83e35
--- /dev/null
@@ -0,0 +1,693 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <strsafe.h>
+
+#include "from_kernel.h"
+#include "nfs41_ops.h"
+#include "delegation.h"
+#include "upcall.h"
+#include "daemon_debug.h"
+
+
+#define EALVL 2 /* dprintf level for extended attribute logging */
+
+
+static int set_ea_value(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN state_owner4 *owner,
+    IN PFILE_FULL_EA_INFORMATION ea)
+{
+    nfs41_path_fh file = { 0 };
+    nfs41_file_info createattrs;
+    open_claim4 claim;
+    stateid_arg stateid;
+    open_delegation4 delegation = { 0 };
+    nfs41_write_verf verf;
+    uint32_t bytes_written;
+    int status;
+
+    /* don't allow values larger than NFS4_EASIZE */
+    if (ea->EaValueLength > NFS4_EASIZE) {
+        eprintf("trying to write extended attribute value of size %d, "
+            "max allowed %d\n", ea->EaValueLength, NFS4_EASIZE);
+        status = NFS4ERR_FBIG;
+        goto out;
+    }
+    /* remove the file on empty value */
+    if (ea->EaValueLength == 0) {
+        nfs41_component name;
+        name.name = ea->EaName;
+        name.len = ea->EaNameLength;
+        nfs41_remove(session, parent, &name, 0);
+        status = NFS4_OK;
+        goto out;
+    }
+
+    claim.claim = CLAIM_NULL;
+    claim.u.null.filename = &file.name;
+    file.name.name = ea->EaName;
+    file.name.len = ea->EaNameLength; 
+
+    createattrs.attrmask.count = 2;
+    createattrs.attrmask.arr[0] = FATTR4_WORD0_SIZE;
+    createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE;
+    createattrs.size = 0;
+    createattrs.mode = 0664;
+
+    status = nfs41_open(session, parent, &file, owner, &claim,
+        OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+        OPEN4_SHARE_DENY_BOTH, OPEN4_CREATE, UNCHECKED4,
+        &createattrs, TRUE, &stateid.stateid, &delegation, NULL);
+    if (status) {
+        eprintf("nfs41_open() failed with %s\n", nfs_error_string(status));
+        goto out;
+    }
+
+    status = nfs41_write(session, &file, &stateid,
+        (unsigned char*)ea->EaName + ea->EaNameLength + 1,
+        ea->EaValueLength, 0, FILE_SYNC4, &bytes_written,
+        &verf, NULL);
+    if (status) {
+        eprintf("nfs41_write() failed with %s\n", nfs_error_string(status));
+        goto out_close;
+    }
+
+out_close:
+    nfs41_close(session, &file, &stateid);
+out:
+    return status;
+}
+
+static int is_cygwin_ea(
+    PFILE_FULL_EA_INFORMATION ea)
+{
+    return (strncmp("NfsV3Attributes", ea->EaName, ea->EaNameLength) == 0
+            && sizeof("NfsV3Attributes")-1 == ea->EaNameLength)
+        || (strncmp("NfsActOnLink", ea->EaName, ea->EaNameLength) == 0
+            && sizeof("NfsActOnLink")-1 == ea->EaNameLength)
+        || (strncmp("NfsSymlinkTargetName", ea->EaName, ea->EaNameLength) == 0
+            && sizeof("NfsSymlinkTargetName")-1 == ea->EaNameLength);
+}
+
+#define NEXT_ENTRY(ea) ((PBYTE)(ea) + (ea)->NextEntryOffset)
+
+int nfs41_ea_set(
+    IN nfs41_open_state *state,
+    IN PFILE_FULL_EA_INFORMATION ea)
+{
+    nfs41_path_fh attrdir = { 0 };
+    int status;
+
+    status = nfs41_rpc_openattr(state->session, &state->file, TRUE, &attrdir.fh);
+    if (status) {
+        eprintf("nfs41_rpc_openattr() failed with error %s\n",
+            nfs_error_string(status));
+        goto out;
+    }
+
+    while (status == NFS4_OK) {
+        if (!is_cygwin_ea(ea))
+            status = set_ea_value(state->session, &attrdir, &state->owner, ea);
+
+        if (ea->NextEntryOffset == 0)
+            break;
+        ea = (PFILE_FULL_EA_INFORMATION)NEXT_ENTRY(ea);
+    }
+out:
+    return status;
+}
+
+
+/* NFS41_EA_SET */
+static int parse_setexattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    setexattr_upcall_args *args = &upcall->args.setexattr;
+
+    status = get_name(&buffer, &length, &args->path);
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->mode, sizeof(args->mode));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
+    if (status) goto out;
+    args->buf = buffer;
+
+    dprintf(1, "parsing NFS41_EA_SET: mode=%o\n", args->mode);
+out:
+    return status;
+}
+
+static int handle_setexattr(nfs41_upcall *upcall)
+{
+    int status;
+    setexattr_upcall_args *args = &upcall->args.setexattr;
+    nfs41_open_state *state = upcall->state_ref;
+    PFILE_FULL_EA_INFORMATION ea = 
+        (PFILE_FULL_EA_INFORMATION)args->buf;
+
+    /* break read delegations before SETATTR */
+    nfs41_delegation_return(state->session, &state->file,
+        OPEN_DELEGATE_READ, FALSE);
+
+    if (strncmp("NfsV3Attributes", ea->EaName, ea->EaNameLength) == 0
+            && sizeof("NfsV3Attributes")-1 == ea->EaNameLength) {
+        nfs41_file_info info;
+        stateid_arg stateid;
+
+        nfs41_open_stateid_arg(state, &stateid);
+
+        info.mode = args->mode;
+        info.attrmask.arr[0] = 0;
+        info.attrmask.arr[1] = FATTR4_WORD1_MODE;
+        info.attrmask.count = 2;
+
+        status = nfs41_setattr(state->session, &state->file, &stateid, &info);
+        if (status) {
+            dprintf(1, "nfs41_setattr() failed with error %s.\n",
+                nfs_error_string(status));
+            goto out;
+        }
+
+        args->ctime = info.change;
+        goto out;
+    }
+
+    status = nfs41_ea_set(state, ea);
+out:
+    return nfs_to_windows_error(status, ERROR_NOT_SUPPORTED);
+}
+
+static int marshall_setexattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    setexattr_upcall_args *args = &upcall->args.setexattr;
+    return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime));
+}
+
+
+/* NFS41_EA_GET */
+static int parse_getexattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    getexattr_upcall_args *args = &upcall->args.getexattr;
+
+    status = get_name(&buffer, &length, &args->path);
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->eaindex, sizeof(args->eaindex));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->restart, sizeof(args->restart));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->single, sizeof(args->single));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->ealist_len, sizeof(args->ealist_len));
+    if (status) goto out;
+    args->ealist = args->ealist_len ? buffer : NULL;
+
+    dprintf(1, "parsing NFS41_EA_GET: buf_len=%d Index %d Restart %d "
+        "Single %d\n", args->buf_len,args->eaindex, args->restart, args->single);
+out:
+    return status;
+}
+
+#define READDIR_LEN_INITIAL 8192
+#define READDIR_LEN_MIN 2048
+
+/* call readdir repeatedly to get a complete list of entries */
+static int read_entire_dir(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *eadir,
+    OUT unsigned char **buffer_out,
+    OUT uint32_t *length_out)
+{
+    nfs41_readdir_cookie cookie = { 0 };
+    bitmap4 attr_request;
+    nfs41_readdir_entry *last_entry;
+    unsigned char *buffer;
+    uint32_t buffer_len, len, total_len;
+    bool_t eof;
+    int status = NO_ERROR;
+
+    attr_request.count = 0; /* don't request attributes */
+
+    /* allocate the buffer for readdir entries */
+    buffer_len = READDIR_LEN_INITIAL;
+    buffer = calloc(1, buffer_len);
+    if (buffer == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    last_entry = NULL;
+    total_len = 0;
+    eof = FALSE;
+
+    while (!eof) {
+        len = buffer_len - total_len;
+        if (len < READDIR_LEN_MIN) {
+            const ptrdiff_t diff = (unsigned char*)last_entry - buffer;
+            /* realloc the buffer to fit more entries */
+            unsigned char *tmp = realloc(buffer, buffer_len * 2);
+            if (tmp == NULL) {
+                status = GetLastError();
+                goto out_free;
+            }
+
+            if (last_entry) /* fix last_entry pointer */
+                last_entry = (nfs41_readdir_entry*)(tmp + diff);
+            buffer = tmp;
+            buffer_len *= 2;
+            len = buffer_len - total_len;
+        }
+
+        /* fetch the next group of entries */
+        status = nfs41_readdir(session, eadir, &attr_request,
+            &cookie, buffer + total_len, &len, &eof);
+        if (status)
+            goto out_free;
+
+        if (last_entry == NULL) {
+            /* initialize last_entry to the front of the list */
+            last_entry = (nfs41_readdir_entry*)(buffer + total_len);
+        } else if (len) {
+            /* link the previous list to the new one */
+            last_entry->next_entry_offset = (uint32_t)FIELD_OFFSET(
+                nfs41_readdir_entry, name) + last_entry->name_len;
+        }
+
+        /* find the new last entry */
+        while (last_entry->next_entry_offset) {
+            last_entry = (nfs41_readdir_entry*)((char*)last_entry +
+                last_entry->next_entry_offset);
+        }
+
+        cookie.cookie = last_entry->cookie;
+        total_len += len;
+    }
+
+    *buffer_out = buffer;
+    *length_out = total_len;
+out:
+    return status;
+
+out_free:
+    free(buffer);
+    goto out;
+}
+
+#define ALIGNED_EASIZE(len) (align4(sizeof(FILE_GET_EA_INFORMATION) + len))
+
+static uint32_t calculate_ea_list_length(
+    IN const unsigned char *position,
+    IN uint32_t remaining)
+{
+    const nfs41_readdir_entry *entry;
+    uint32_t length = 0;
+
+    while (remaining) {
+        entry = (const nfs41_readdir_entry*)position;
+        length += ALIGNED_EASIZE(entry->name_len);
+
+        if (!entry->next_entry_offset)
+            break;
+
+        position += entry->next_entry_offset;
+        remaining -= entry->next_entry_offset;
+    }
+    return length;
+}
+
+static void populate_ea_list(
+    IN const unsigned char *position,
+    OUT PFILE_GET_EA_INFORMATION ea_list)
+{
+    const nfs41_readdir_entry *entry;
+    PFILE_GET_EA_INFORMATION ea = ea_list, prev = NULL;
+
+    for (;;) {
+        entry = (const nfs41_readdir_entry*)position;
+        StringCchCopyA(ea->EaName, entry->name_len, entry->name);
+        ea->EaNameLength = (UCHAR)entry->name_len - 1;
+
+        if (!entry->next_entry_offset) {
+            ea->NextEntryOffset = 0;
+            break;
+        }
+
+        prev = ea;
+        ea->NextEntryOffset = ALIGNED_EASIZE(ea->EaNameLength);
+        ea = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(ea);
+        position += entry->next_entry_offset;
+    }
+}
+
+static int get_ea_list(
+    IN OUT nfs41_open_state *state,
+    IN nfs41_path_fh *eadir,
+    OUT PFILE_GET_EA_INFORMATION *ealist_out,
+    OUT uint32_t *eaindex_out)
+{
+    unsigned char *entry_list;
+    PFILE_GET_EA_INFORMATION ea_list;
+    uint32_t entry_len, ea_size;
+    int status = NO_ERROR;
+
+    EnterCriticalSection(&state->ea.lock);
+
+    if (state->ea.list != INVALID_HANDLE_VALUE) {
+        /* use cached ea names */
+        *ealist_out = state->ea.list;
+        *eaindex_out = state->ea.index;
+        goto out;
+    }
+
+    /* read the entire directory into a nfs41_readdir_entry buffer */
+    status = read_entire_dir(state->session, eadir, &entry_list, &entry_len);
+    if (status)
+        goto out;
+
+    ea_size = calculate_ea_list_length(entry_list, entry_len);
+    if (ea_size == 0) {
+        *ealist_out = state->ea.list = NULL;
+        goto out_free;
+    }
+    ea_list = calloc(1, ea_size);
+    if (ea_list == NULL) {
+        status = GetLastError();
+        goto out_free;
+    }
+
+    populate_ea_list(entry_list, ea_list);
+
+    *ealist_out = state->ea.list = ea_list;
+    *eaindex_out = state->ea.index;
+out_free:
+    free(entry_list); /* allocated by read_entire_dir() */
+out:
+    LeaveCriticalSection(&state->ea.lock);
+    return status;
+}
+
+static int get_ea_value(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN state_owner4 *owner,
+    OUT PFILE_FULL_EA_INFORMATION ea,
+    IN uint32_t length,
+    OUT uint32_t *needed)
+{
+    nfs41_path_fh file = { 0 };
+    open_claim4 claim;
+    stateid_arg stateid;
+    open_delegation4 delegation = { 0 };
+    nfs41_file_info info;
+    unsigned char *buffer;
+    uint32_t diff, bytes_read;
+    bool_t eof;
+    int status;
+
+    if (parent->fh.len == 0) /* no named attribute directory */
+        goto out_empty;
+
+    claim.claim = CLAIM_NULL;
+    claim.u.null.filename = &file.name;
+    file.name.name = ea->EaName;
+    file.name.len = ea->EaNameLength;
+
+    status = nfs41_open(session, parent, &file, owner, &claim,
+        OPEN4_SHARE_ACCESS_READ | OPEN4_SHARE_ACCESS_WANT_NO_DELEG,
+        OPEN4_SHARE_DENY_WRITE, OPEN4_NOCREATE, UNCHECKED4, NULL, TRUE,
+        &stateid.stateid, &delegation, &info);
+    if (status) {
+        eprintf("nfs41_open() failed with %s\n", nfs_error_string(status));
+        if (status == NFS4ERR_NOENT)
+            goto out_empty;
+        goto out;
+    }
+
+    if (info.size > NFS4_EASIZE) {
+        status = NFS4ERR_FBIG;
+        eprintf("EA value for '%s' longer than maximum %u "
+            "(%llu bytes), returning %s\n", ea->EaName, NFS4_EASIZE,
+            info.size, nfs_error_string(status));
+        goto out_close;
+    }
+
+    buffer = (unsigned char*)ea->EaName + ea->EaNameLength + 1;
+    diff = (uint32_t)(buffer - (unsigned char*)ea);
+
+    /* make sure we have room for the value */
+    if (length < diff + info.size) {
+        *needed = (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION) +
+            ea->EaNameLength + info.size);
+        status = NFS4ERR_TOOSMALL;
+        goto out_close;
+    }
+
+    /* read directly into the ea buffer */
+    status = nfs41_read(session, &file, &stateid,
+        0, length - diff, buffer, &bytes_read, &eof);
+    if (status) {
+        eprintf("nfs41_read() failed with %s\n", nfs_error_string(status));
+        goto out_close;
+    }
+    if (!eof) {
+        *needed = (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION) +
+            ea->EaNameLength + NFS4_EASIZE);
+        status = NFS4ERR_TOOSMALL;
+        goto out_close;
+    }
+
+    ea->EaValueLength = (USHORT)bytes_read;
+
+out_close:
+    nfs41_close(session, &file, &stateid);
+out:
+    return status;
+
+out_empty: /* return an empty value */
+    ea->EaValueLength = 0;
+    status = NFS4_OK;
+    goto out;
+}
+
+static int empty_ea_error(
+    IN uint32_t index,
+    IN BOOLEAN restart)
+{
+    /* choose an error value depending on the arguments */
+    if (index)
+        return ERROR_INVALID_EA_HANDLE;
+
+    if (!restart)
+        return ERROR_NO_MORE_FILES;  /* -> STATUS_NO_MORE_EAS */
+
+    return ERROR_FILE_NOT_FOUND; /* -> STATUS_NO_EAS_ON_FILE */
+}
+
+static int overflow_error(
+    IN OUT getexattr_upcall_args *args,
+    IN PFILE_FULL_EA_INFORMATION prev,
+    IN uint32_t needed)
+{
+    if (prev) {
+        /* unlink the overflowing entry, but copy the entries that fit */
+        prev->NextEntryOffset = 0;
+        args->overflow = ERROR_BUFFER_OVERFLOW;
+    } else {
+        /* no entries fit; return only the length needed */
+        args->buf_len = needed;
+        args->overflow = ERROR_INSUFFICIENT_BUFFER;
+    }
+
+    /* in either case, the upcall must return NO_ERROR so we
+     * can copy this information down to the driver */
+    return NO_ERROR;
+}
+
+static int handle_getexattr(nfs41_upcall *upcall)
+{
+    getexattr_upcall_args *args = &upcall->args.getexattr;
+    PFILE_GET_EA_INFORMATION query = (PFILE_GET_EA_INFORMATION)args->ealist;
+    PFILE_FULL_EA_INFORMATION ea, prev = NULL;
+    nfs41_open_state *state = upcall->state_ref;
+    nfs41_path_fh parent = { 0 };
+    uint32_t remaining, needed, index = 0;
+    int status;
+
+    status = nfs41_rpc_openattr(state->session, &state->file, FALSE, &parent.fh);
+    if (status == NFS4ERR_NOENT) { /* no named attribute directory */
+        dprintf(EALVL, "no named attribute directory for '%s'\n", args->path);
+        if (query == NULL) {
+            status = empty_ea_error(args->eaindex, args->restart);
+            goto out;
+        }
+    } else if (status) {
+        eprintf("nfs41_rpc_openattr() failed with %s\n",
+            nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_EAS_NOT_SUPPORTED);
+        goto out;
+    }
+
+    if (query == NULL) {
+        /* if no names are queried, use READDIR to list them all */
+        uint32_t i;
+        status = get_ea_list(state, &parent, &query, &index);
+        if (status)
+            goto out;
+
+        if (query == NULL) { /* the file has no EAs */
+            dprintf(EALVL, "empty named attribute directory for '%s'\n",
+                args->path);
+            status = empty_ea_error(args->eaindex, args->restart);
+            goto out;
+        }
+
+        if (args->eaindex)
+            index = args->eaindex - 1; /* convert to zero-based index */
+        else if (args->restart)
+            index = 0;
+
+        /* advance the list to the specified index */
+        for (i = 0; i < index; i++) {
+            if (query->NextEntryOffset == 0) {
+                if (args->eaindex)
+                    status = ERROR_INVALID_EA_HANDLE;
+                else
+                    status = ERROR_NO_MORE_FILES; /* STATUS_NO_MORE_EAS */
+                goto out;
+            }
+            query = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(query);
+        }
+    }
+
+    /* returned ea information can't exceed the downcall buffer size */
+    if (args->buf_len > UPCALL_BUF_SIZE - 2 * sizeof(uint32_t))
+        args->buf_len = UPCALL_BUF_SIZE - 2 * sizeof(uint32_t);
+
+    args->buf = malloc(args->buf_len);
+    if (args->buf == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    ea = (PFILE_FULL_EA_INFORMATION)args->buf;
+    remaining = args->buf_len;
+
+    for (;;) {
+        /* make sure we have room for at least the name */
+        needed = sizeof(FILE_FULL_EA_INFORMATION) + query->EaNameLength;
+        if (needed > remaining) {
+            status = overflow_error(args, prev, needed + NFS4_EASIZE);
+            goto out;
+        }
+
+        ea->EaNameLength = query->EaNameLength;
+        StringCchCopy(ea->EaName, ea->EaNameLength + 1, query->EaName);
+        ea->Flags = 0;
+
+        /* read the value from file */
+        status = get_ea_value(state->session, &parent,
+            &state->owner, ea, remaining, &needed);
+        if (status == NFS4ERR_TOOSMALL) {
+            status = overflow_error(args, prev, needed);
+            goto out;
+        }
+        if (status) {
+            status = nfs_to_windows_error(status, ERROR_EA_FILE_CORRUPT);
+            goto out_free;
+        }
+
+        needed = align4(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
+            ea->EaNameLength + 1 + ea->EaValueLength);
+
+        if (remaining < needed) {
+            /* align4 may push NextEntryOffset past our buffer, but we
+             * were still able to fit the ea value.  set remaining = 0
+             * so we'll fail on the next ea (if any) */
+            remaining = 0;
+        } else
+            remaining -= needed;
+
+        index++;
+        if (query->NextEntryOffset == 0 || args->single)
+            break;
+
+        prev = ea;
+        ea->NextEntryOffset = needed;
+        ea = (PFILE_FULL_EA_INFORMATION)NEXT_ENTRY(ea);
+        query = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(query);
+    }
+
+    ea->NextEntryOffset = 0;
+    args->buf_len -= remaining;
+out:
+    if (args->ealist == NULL) { /* update the ea index */
+        EnterCriticalSection(&state->ea.lock);
+        state->ea.index = index;
+        if (status == NO_ERROR && !args->overflow && !args->single) {
+            /* listing was completed, free the cache */
+            free(state->ea.list);
+            state->ea.list = INVALID_HANDLE_VALUE;
+        }
+        LeaveCriticalSection(&state->ea.lock);
+    }
+    return status;
+
+out_free:
+    free(args->buf);
+    goto out;
+}
+
+static int marshall_getexattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    int status = NO_ERROR;
+    getexattr_upcall_args *args = &upcall->args.getexattr;
+
+    status = safe_write(&buffer, length, &args->overflow, sizeof(args->overflow));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->buf_len, sizeof(args->buf_len));
+    if (status) goto out;
+    if (args->overflow == ERROR_INSUFFICIENT_BUFFER)
+        goto out;
+    status = safe_write(&buffer, length, args->buf, args->buf_len);
+    if (status) goto out;
+out:
+    free(args->buf);
+    return status;
+}
+
+
+const nfs41_upcall_op nfs41_op_setexattr = {
+    parse_setexattr,
+    handle_setexattr,
+    marshall_setexattr
+};
+
+const nfs41_upcall_op nfs41_op_getexattr = {
+    parse_getexattr,
+    handle_getexattr,
+    marshall_getexattr
+};
diff --git a/reactos/base/services/nfsd/from_kernel.h b/reactos/base/services/nfsd/from_kernel.h
new file mode 100644 (file)
index 0000000..8525647
--- /dev/null
@@ -0,0 +1,280 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_
+#define _NFS41_DAEMON_
+
+#define FILE_DIRECTORY_FILE                     0x00000001
+#define FILE_WRITE_THROUGH                      0x00000002
+#define FILE_SEQUENTIAL_ONLY                    0x00000004
+#define FILE_NO_INTERMEDIATE_BUFFERING          0x00000008
+
+#define FILE_SYNCHRONOUS_IO_ALERT               0x00000010
+#define FILE_SYNCHRONOUS_IO_NONALERT            0x00000020
+#define FILE_NON_DIRECTORY_FILE                 0x00000040
+#define FILE_CREATE_TREE_CONNECTION             0x00000080
+
+#define FILE_COMPLETE_IF_OPLOCKED               0x00000100
+#define FILE_NO_EA_KNOWLEDGE                    0x00000200
+#define FILE_OPEN_REMOTE_INSTANCE               0x00000400
+#define FILE_RANDOM_ACCESS                      0x00000800
+
+#define FILE_DELETE_ON_CLOSE                    0x00001000
+#define FILE_OPEN_BY_FILE_ID                    0x00002000
+#define FILE_OPEN_FOR_BACKUP_INTENT             0x00004000
+#define FILE_NO_COMPRESSION                     0x00008000
+
+#define FILE_RESERVE_OPFILTER                   0x00100000
+#define FILE_OPEN_REPARSE_POINT                 0x00200000
+#define FILE_OPEN_NO_RECALL                     0x00400000
+#define FILE_OPEN_FOR_FREE_SPACE_QUERY          0x00800000
+
+#define FILE_COPY_STRUCTURED_STORAGE            0x00000041
+#define FILE_STRUCTURED_STORAGE                 0x00000441
+
+#define FILE_SUPERSEDE                  0x00000000
+#define FILE_OPEN                       0x00000001
+#define FILE_CREATE                     0x00000002
+#define FILE_OPEN_IF                    0x00000003
+#define FILE_OVERWRITE                  0x00000004
+#define FILE_OVERWRITE_IF               0x00000005
+#define FILE_MAXIMUM_DISPOSITION        0x00000005
+
+typedef enum _FILE_INFORMATION_CLASS {
+    FileDirectoryInformation         = 1,
+    FileFullDirectoryInformation,   // 2
+    FileBothDirectoryInformation,   // 3
+    FileBasicInformation,           // 4
+    FileStandardInformation,        // 5
+    FileInternalInformation,        // 6
+    FileEaInformation,              // 7
+    FileAccessInformation,          // 8
+    FileNameInformation,            // 9
+    FileRenameInformation,          // 10
+    FileLinkInformation,            // 11
+    FileNamesInformation,           // 12
+    FileDispositionInformation,     // 13
+    FilePositionInformation,        // 14
+    FileFullEaInformation,          // 15
+    FileModeInformation,            // 16
+    FileAlignmentInformation,       // 17
+    FileAllInformation,             // 18
+    FileAllocationInformation,      // 19
+    FileEndOfFileInformation,       // 20
+    FileAlternateNameInformation,   // 21
+    FileStreamInformation,          // 22
+    FilePipeInformation,            // 23
+    FilePipeLocalInformation,       // 24
+    FilePipeRemoteInformation,      // 25
+    FileMailslotQueryInformation,   // 26
+    FileMailslotSetInformation,     // 27
+    FileCompressionInformation,     // 28
+    FileObjectIdInformation,        // 29
+    FileCompletionInformation,      // 30
+    FileMoveClusterInformation,     // 31
+    FileQuotaInformation,           // 32
+    FileReparsePointInformation,    // 33
+    FileNetworkOpenInformation,     // 34
+    FileAttributeTagInformation,    // 35
+    FileTrackingInformation,        // 36
+    FileIdBothDirectoryInformation, // 37
+    FileIdFullDirectoryInformation, // 38
+    FileValidDataLengthInformation, // 39
+    FileShortNameInformation,       // 40
+    FileIoCompletionNotificationInformation, // 41
+    FileIoStatusBlockRangeInformation,       // 42
+    FileIoPriorityHintInformation,           // 43
+    FileSfioReserveInformation,              // 44
+    FileSfioVolumeInformation,               // 45
+    FileHardLinkInformation,                 // 46
+    FileProcessIdsUsingFileInformation,      // 47
+    FileNormalizedNameInformation,           // 48
+    FileNetworkPhysicalNameInformation,      // 49
+    FileIdGlobalTxDirectoryInformation,      // 50
+    FileMaximumInformation
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+
+/* kernel structures for QueryDirectory results */
+typedef struct _FILE_NAMES_INFORMATION {
+    ULONG NextEntryOffset;
+    ULONG FileIndex;
+    ULONG FileNameLength;
+    WCHAR FileName[1];
+} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;
+
+typedef struct _FILE_DIRECTORY_INFO {
+    ULONG NextEntryOffset;
+    ULONG FileIndex;
+    LARGE_INTEGER CreationTime;
+    LARGE_INTEGER LastAccessTime;
+    LARGE_INTEGER LastWriteTime;
+    LARGE_INTEGER ChangeTime;
+    LARGE_INTEGER EndOfFile;
+    LARGE_INTEGER AllocationSize;
+    ULONG FileAttributes;
+    ULONG FileNameLength;
+    WCHAR FileName[1];
+} FILE_DIRECTORY_INFO, *PFILE_DIRECTORY_INFO;
+
+typedef struct _FILE_BOTH_DIR_INFORMATION {
+    ULONG NextEntryOffset;
+    ULONG FileIndex;
+    LARGE_INTEGER CreationTime;
+    LARGE_INTEGER LastAccessTime;
+    LARGE_INTEGER LastWriteTime;
+    LARGE_INTEGER ChangeTime;
+    LARGE_INTEGER EndOfFile;
+    LARGE_INTEGER AllocationSize;
+    ULONG FileAttributes;
+    ULONG FileNameLength;
+    ULONG EaSize;
+    CCHAR ShortNameLength;
+    WCHAR ShortName[12];
+    WCHAR FileName[1];
+} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
+
+typedef struct _FILE_FULL_DIR_INFO {
+    ULONG NextEntryOffset;
+    ULONG FileIndex;
+    LARGE_INTEGER CreationTime;
+    LARGE_INTEGER LastAccessTime;
+    LARGE_INTEGER LastWriteTime;
+    LARGE_INTEGER ChangeTime;
+    LARGE_INTEGER EndOfFile;
+    LARGE_INTEGER AllocationSize;
+    ULONG FileAttributes;
+    ULONG FileNameLength;
+    ULONG EaSize;
+    WCHAR FileName[1];
+} FILE_FULL_DIR_INFO, *PFILE_FULL_DIR_INFO;
+
+typedef struct _FILE_ID_FULL_DIR_INFO {
+    ULONG NextEntryOffset;
+    ULONG FileIndex;
+    LARGE_INTEGER CreationTime;
+    LARGE_INTEGER LastAccessTime;
+    LARGE_INTEGER LastWriteTime;
+    LARGE_INTEGER ChangeTime;
+    LARGE_INTEGER EndOfFile;
+    LARGE_INTEGER AllocationSize;
+    ULONG FileAttributes;
+    ULONG FileNameLength;
+    ULONG EaSize;
+    LARGE_INTEGER FileId;
+    WCHAR FileName[1];
+} FILE_ID_FULL_DIR_INFO, *PFILE_ID_FULL_DIR_INFO;
+
+typedef struct _FILE_LINK_INFORMATION {
+    BOOLEAN ReplaceIfExists;
+    HANDLE RootDirectory;
+    ULONG FileNameLength;
+    WCHAR FileName[1];
+} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION;
+
+typedef struct _FILE_FULL_EA_INFORMATION {
+    ULONG NextEntryOffset;
+    UCHAR Flags;
+    UCHAR EaNameLength;
+    USHORT EaValueLength;
+    CHAR EaName[1];
+} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
+
+typedef struct _FILE_GET_EA_INFORMATION {
+    ULONG NextEntryOffset;
+    UCHAR EaNameLength;
+    CHAR  EaName[1];
+} FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION;
+
+typedef struct _FILE_NETWORK_OPEN_INFORMATION {
+    LARGE_INTEGER CreationTime;
+    LARGE_INTEGER LastAccessTime;
+    LARGE_INTEGER LastWriteTime;
+    LARGE_INTEGER ChangeTime;
+    LARGE_INTEGER AllocationSize;
+    LARGE_INTEGER EndOfFile;
+    ULONG FileAttributes;
+} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION;
+
+/* wdm.h */
+typedef enum _FSINFOCLASS {
+    FileFsVolumeInformation       = 1,
+    FileFsLabelInformation,      // 2
+    FileFsSizeInformation,       // 3
+    FileFsDeviceInformation,     // 4
+    FileFsAttributeInformation,  // 5
+    FileFsControlInformation,    // 6
+    FileFsFullSizeInformation,   // 7
+    FileFsObjectIdInformation,   // 8
+    FileFsDriverPathInformation, // 9
+    FileFsVolumeFlagsInformation,// 10
+    FileFsMaximumInformation
+} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS;
+
+/* ntifs.h */
+#define FILE_CASE_SENSITIVE_SEARCH          0x00000001
+#define FILE_CASE_PRESERVED_NAMES           0x00000002
+#define FILE_UNICODE_ON_DISK                0x00000004
+#define FILE_PERSISTENT_ACLS                0x00000008
+#define FILE_FILE_COMPRESSION               0x00000010
+#define FILE_VOLUME_QUOTAS                  0x00000020
+#define FILE_SUPPORTS_SPARSE_FILES          0x00000040
+#define FILE_SUPPORTS_REPARSE_POINTS        0x00000080
+#define FILE_SUPPORTS_REMOTE_STORAGE        0x00000100
+#define FILE_VOLUME_IS_COMPRESSED           0x00008000
+#define FILE_SUPPORTS_OBJECT_IDS            0x00010000
+#define FILE_SUPPORTS_ENCRYPTION            0x00020000
+#define FILE_NAMED_STREAMS                  0x00040000
+#define FILE_READ_ONLY_VOLUME               0x00080000
+#define FILE_SEQUENTIAL_WRITE_ONCE          0x00100000
+#define FILE_SUPPORTS_TRANSACTIONS          0x00200000
+#define FILE_SUPPORTS_HARD_LINKS            0x00400000
+#define FILE_SUPPORTS_EXTENDED_ATTRIBUTES   0x00800000
+#define FILE_SUPPORTS_OPEN_BY_FILE_ID       0x01000000
+#define FILE_SUPPORTS_USN_JOURNAL           0x02000000
+
+typedef struct _FILE_FS_ATTRIBUTE_INFORMATION {
+    ULONG FileSystemAttributes;
+    LONG MaximumComponentNameLength;
+    ULONG FileSystemNameLength;
+    WCHAR FileSystemName[1];
+} FILE_FS_ATTRIBUTE_INFORMATION, *PFILE_FS_ATTRIBUTE_INFORMATION;
+
+/* ntddk.h */
+typedef struct _FILE_FS_SIZE_INFORMATION {
+    LARGE_INTEGER TotalAllocationUnits;
+    LARGE_INTEGER AvailableAllocationUnits;
+    ULONG SectorsPerAllocationUnit;
+    ULONG BytesPerSector;
+} FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION;
+
+typedef struct _FILE_FS_FULL_SIZE_INFORMATION {
+    LARGE_INTEGER TotalAllocationUnits;
+    LARGE_INTEGER CallerAvailableAllocationUnits;
+    LARGE_INTEGER ActualAvailableAllocationUnits;
+    ULONG SectorsPerAllocationUnit;
+    ULONG BytesPerSector;
+} FILE_FS_FULL_SIZE_INFORMATION, *PFILE_FS_FULL_SIZE_INFORMATION;
+
+typedef struct _FILE_INTERNAL_INFORMATION {
+    LARGE_INTEGER IndexNumber;
+} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION;
+#endif
diff --git a/reactos/base/services/nfsd/getattr.c b/reactos/base/services/nfsd/getattr.c
new file mode 100644 (file)
index 0000000..6559d40
--- /dev/null
@@ -0,0 +1,184 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <strsafe.h>
+
+#include "nfs41_ops.h"
+#include "name_cache.h"
+#include "upcall.h"
+#include "daemon_debug.h"
+
+
+int nfs41_cached_getattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    OUT nfs41_file_info *info)
+{
+    int status;
+
+    /* first look for cached attributes */
+    status = nfs41_attr_cache_lookup(session_name_cache(session),
+        file->fh.fileid, info);
+
+    if (status) {
+        /* fetch attributes from the server */
+        bitmap4 attr_request;
+        nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+
+        status = nfs41_getattr(session, file, &attr_request, info);
+        if (status) {
+            eprintf("nfs41_getattr() failed with %s\n",
+                nfs_error_string(status));
+            status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+        }
+    }
+    return status;
+}
+
+/* NFS41_FILE_QUERY */
+static int parse_getattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    getattr_upcall_args *args = &upcall->args.getattr;
+
+    status = safe_read(&buffer, &length, &args->query_class, sizeof(args->query_class));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
+    if (status) goto out;
+
+    dprintf(1, "parsing NFS41_FILE_QUERY: info_class=%d buf_len=%d file=%.*s\n",
+        args->query_class, args->buf_len, upcall->state_ref->path.len, 
+        upcall->state_ref->path.path);
+out:
+    return status;
+}
+
+static int handle_getattr(nfs41_upcall *upcall)
+{
+    int status;
+    getattr_upcall_args *args = &upcall->args.getattr;
+    nfs41_open_state *state = upcall->state_ref;
+    nfs41_file_info info = { 0 };
+
+    status = nfs41_cached_getattr(state->session, &state->file, &info);
+    if (status) {
+        eprintf("nfs41_cached_getattr() failed with %d\n", status);
+        goto out;
+    }
+
+    if (info.type == NF4LNK) {
+        nfs41_file_info target_info;
+        int target_status = nfs41_symlink_follow(upcall->root_ref,
+            state->session, &state->file, &target_info);
+        if (target_status == NO_ERROR && target_info.type == NF4DIR)
+            info.symlink_dir = TRUE;
+    }
+
+    switch (args->query_class) {
+    case FileBasicInformation:
+        nfs_to_basic_info(&info, &args->basic_info);
+        args->ctime = info.change;
+        break;
+    case FileStandardInformation:
+        nfs_to_standard_info(&info, &args->std_info);
+        break;
+    case FileAttributeTagInformation:
+        args->tag_info.FileAttributes = nfs_file_info_to_attributes(&info);
+        args->tag_info.ReparseTag = info.type == NF4LNK ?
+            IO_REPARSE_TAG_SYMLINK : 0;
+        break;
+    case FileInternalInformation:
+        args->intr_info.IndexNumber.QuadPart = info.fileid;
+        break;
+    case FileNetworkOpenInformation:
+        nfs_to_network_openinfo(&info, &args->network_info);
+        break;
+    default:
+        eprintf("unhandled file query class %d\n", args->query_class);
+        status = ERROR_INVALID_PARAMETER;
+        break;
+    }
+out:
+    return status;
+}
+
+static int marshall_getattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    int status;
+    getattr_upcall_args *args = &upcall->args.getattr;
+    uint32_t info_len;
+
+    switch (args->query_class) {
+    case FileBasicInformation:
+        info_len = sizeof(args->basic_info);
+        status = safe_write(&buffer, length, &info_len, sizeof(info_len));
+        if (status) goto out;
+        status = safe_write(&buffer, length, &args->basic_info, info_len);
+        if (status) goto out;
+        break;
+    case FileStandardInformation:
+        info_len = sizeof(args->std_info);
+        status = safe_write(&buffer, length, &info_len, sizeof(info_len));
+        if (status) goto out;
+        status = safe_write(&buffer, length, &args->std_info, info_len);
+        if (status) goto out;
+        break;
+    case FileAttributeTagInformation:
+        info_len = sizeof(args->tag_info);
+        status = safe_write(&buffer, length, &info_len, sizeof(info_len));
+        if (status) goto out;
+        status = safe_write(&buffer, length, &args->tag_info, info_len);
+        if (status) goto out;
+        break;
+    case FileInternalInformation:
+        info_len = sizeof(args->intr_info);
+        status = safe_write(&buffer, length, &info_len, sizeof(info_len));
+        if (status) goto out;
+        status = safe_write(&buffer, length, &args->intr_info, info_len);
+        if (status) goto out;
+        break;
+    case FileNetworkOpenInformation:
+        info_len = sizeof(args->network_info);
+        status = safe_write(&buffer, length, &info_len, sizeof(info_len));
+        if (status) goto out;
+        status = safe_write(&buffer, length, &args->network_info, info_len);
+        if (status) goto out;
+        break;
+    default:
+        eprintf("unknown file query class %d\n", args->query_class);
+        status = 103;
+        goto out;
+    }
+    status = safe_write(&buffer, length, &args->ctime, sizeof(args->ctime));
+    if (status) goto out;
+    dprintf(1, "NFS41_FILE_QUERY: downcall changattr=%llu\n", args->ctime);
+out:
+    return status;
+}
+
+
+const nfs41_upcall_op nfs41_op_getattr = {
+    parse_getattr,
+    handle_getattr,
+    marshall_getattr
+};
diff --git a/reactos/base/services/nfsd/idmap.c b/reactos/base/services/nfsd/idmap.c
new file mode 100644 (file)
index 0000000..328f887
--- /dev/null
@@ -0,0 +1,1063 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <winldap.h>
+#include <stdlib.h> /* for strtoul() */
+#include <errno.h>
+#include <time.h>
+
+#include "idmap.h"
+#include "nfs41_const.h"
+#include "list.h"
+#include "daemon_debug.h"
+
+
+#define IDLVL 2 /* dprintf level for idmap logging */
+
+#define FILTER_LEN 1024
+#define NAME_LEN 32
+#define VAL_LEN 257
+
+
+enum ldap_class {
+    CLASS_USER,
+    CLASS_GROUP,
+
+    NUM_CLASSES
+};
+
+enum ldap_attr {
+    ATTR_USER_NAME,
+    ATTR_GROUP_NAME,
+    ATTR_PRINCIPAL,
+    ATTR_UID,
+    ATTR_GID,
+
+    NUM_ATTRIBUTES
+};
+
+#define ATTR_FLAG(attr) (1 << (attr))
+#define ATTR_ISSET(mask, attr) (((mask) & ATTR_FLAG(attr)) != 0)
+
+
+/* ldap/cache lookups */
+struct idmap_lookup {
+    enum ldap_attr attr;
+    enum ldap_class klass;
+#ifdef __REACTOS__
+    uint32_t type;
+#else
+    enum config_type type;
+#endif
+    list_compare_fn compare;
+    const void *value;
+};
+
+
+/* configuration */
+static const char CONFIG_FILENAME[] = "C:\\ReactOS\\System32\\drivers\\etc\\ms-nfs41-idmap.conf";
+
+struct idmap_config {
+    /* ldap server information */
+    char hostname[NFS41_HOSTNAME_LEN+1];
+    UINT port;
+    UINT version;
+    UINT timeout;
+
+    /* ldap schema information */
+    char classes[NUM_CLASSES][NAME_LEN];
+    char attributes[NUM_ATTRIBUTES][NAME_LEN];
+    char base[VAL_LEN];
+
+    /* caching configuration */
+    INT cache_ttl;
+};
+
+
+enum config_type {
+    TYPE_STR,
+    TYPE_INT
+};
+
+struct config_option {
+    const char *key;
+    const char *def;
+    enum config_type type;
+    size_t offset;
+    size_t max_len;
+};
+
+/* helper macros for declaring config_options */
+#define OPT_INT(key,def,field) \
+    { key, def, TYPE_INT, FIELD_OFFSET(struct idmap_config, field), 0 }
+#define OPT_STR(key,def,field,len) \
+    { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, field), len }
+#define OPT_CLASS(key,def,index) \
+    { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, classes[index]), NAME_LEN }
+#define OPT_ATTR(key,def,index) \
+    { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, attributes[index]), NAME_LEN }
+
+/* table of recognized config options, including type and default value */
+static const struct config_option g_options[] = {
+    /* server information */
+    OPT_STR("ldap_hostname", "localhost", hostname, NFS41_HOSTNAME_LEN+1),
+    OPT_INT("ldap_port", "389", port),
+    OPT_INT("ldap_version", "3", version),
+    OPT_INT("ldap_timeout", "0", timeout),
+
+    /* schema information */
+    OPT_STR("ldap_base", "cn=localhost", base, VAL_LEN),
+    OPT_CLASS("ldap_class_users", "user", CLASS_USER),
+    OPT_CLASS("ldap_class_groups", "group", CLASS_GROUP),
+    OPT_ATTR("ldap_attr_username", "cn", ATTR_USER_NAME),
+    OPT_ATTR("ldap_attr_groupname", "cn", ATTR_GROUP_NAME),
+    OPT_ATTR("ldap_attr_gssAuthName", "gssAuthName", ATTR_PRINCIPAL),
+    OPT_ATTR("ldap_attr_uidNumber", "uidNumber", ATTR_UID),
+    OPT_ATTR("ldap_attr_gidNumber", "gidNumber", ATTR_GID),
+
+    /* caching configuration */
+    OPT_INT("cache_ttl", "60", cache_ttl),
+};
+
+
+/* parse each line into key-value pairs
+ * accepts 'key = value' or 'key = "value"',
+ * ignores whitespace anywhere outside the ""s */
+struct config_pair {
+    const char *key, *value;
+    size_t key_len, value_len;
+};
+
+static int config_parse_pair(
+    char *line,
+    struct config_pair *pair)
+{
+    char *pos = line;
+    int status = NO_ERROR;
+
+    /* terminate at comment */
+    pos = strchr(line, '#');
+    if (pos) *pos = 0;
+
+    /* skip whitespace before key */
+    pos = line;
+    while (isspace(*pos)) pos++;
+    pair->key = pos;
+
+    pos = strchr(pos, '=');
+    if (pos == NULL) {
+        eprintf("missing '='\n");
+        status = ERROR_INVALID_PARAMETER;
+        goto out;
+    }
+
+    /* skip whitespace after key */
+    pair->key_len = pos - pair->key;
+    while (pair->key_len && isspace(pair->key[pair->key_len-1]))
+        pair->key_len--;
+
+    if (pair->key_len <= 0) {
+        eprintf("empty key\n");
+        status = ERROR_INVALID_PARAMETER;
+        goto out;
+    }
+
+    /* skip whitespace after = */
+    pos++;
+    while (isspace(*pos)) pos++;
+
+    if (*pos == 0) {
+        eprintf("end of line looking for value\n");
+        status = ERROR_INVALID_PARAMETER;
+        goto out;
+    }
+
+    if (*pos == '\"') {
+        /* value is between the "s */
+        pair->value = pos + 1;
+        pos = strchr(pair->value, '\"');
+        if (pos == NULL) {
+            eprintf("no matching '\"'\n");
+            status = ERROR_INVALID_PARAMETER;
+            goto out;
+        }
+        pair->value_len = pos - pair->value;
+    } else {
+        pair->value = pos;
+        pair->value_len = strlen(pair->value);
+
+        /* skip whitespace after value */
+        while (pair->value_len && isspace(pair->value[pair->value_len-1]))
+            pair->value_len--;
+    }
+
+    /* on success, null terminate the key and value */
+    ((char*)pair->key)[pair->key_len] = 0;
+    ((char*)pair->value)[pair->value_len] = 0;
+out:
+    return status;
+}
+
+static BOOL parse_uint(
+    const char *str,
+    UINT *id_out)
+{
+    PCHAR endp;
+    const UINT id = strtoul(str, &endp, 10);
+
+    /* must convert the whole string */
+    if ((endp - str) < (ptrdiff_t)strlen(str))
+        return FALSE;
+
+    /* result must fit in 32 bits */
+    if (id == ULONG_MAX && errno == ERANGE)
+        return FALSE;
+
+    *id_out = id;
+    return TRUE;
+}
+
+/* parse default values from g_options[] into idmap_config */
+static int config_defaults(
+    struct idmap_config *config)
+{
+    const struct config_option *option;
+    const int count = ARRAYSIZE(g_options);
+    char *dst;
+    int i, status = NO_ERROR;
+
+    for (i = 0; i < count; i++) {
+        option = &g_options[i];
+        dst = (char*)config + option->offset;
+
+        if (option->type == TYPE_INT) {
+            if (!parse_uint(option->def, (UINT*)dst)) {
+                status = ERROR_INVALID_PARAMETER;
+                eprintf("failed to parse default value of %s=\"%s\": "
+                    "expected a number\n", option->key, option->def);
+                break;
+            }
+        } else {
+            if (FAILED(StringCchCopyA(dst, option->max_len, option->def))) {
+                status = ERROR_BUFFER_OVERFLOW;
+                eprintf("failed to parse default value of %s=\"%s\": "
+                    "buffer overflow > %u\n", option->key, option->def,
+                    option->max_len);
+                break;
+            }
+        }
+    }
+    return status;
+}
+
+static int config_find_option(
+    const struct config_pair *pair,
+    const struct config_option **option)
+{
+    int i, count = ARRAYSIZE(g_options);
+    int status = ERROR_NOT_FOUND;
+
+    /* find the config_option by key */
+    for (i = 0; i < count; i++) {
+        if (stricmp(pair->key, g_options[i].key) == 0) {
+            *option = &g_options[i];
+            status = NO_ERROR;
+            break;
+        }
+    }
+    return status;
+}
+
+static int config_load(
+    struct idmap_config *config,
+    const char *filename)
+{
+    char buffer[1024], *pos;
+    FILE *file;
+    struct config_pair pair;
+    const struct config_option *option;
+    int line = 0;
+    int status = NO_ERROR;
+
+    /* open the file */
+    file = fopen(filename, "r");
+    if (file == NULL) {
+        eprintf("config_load() failed to open file '%s'\n", filename);
+        goto out;
+    }
+
+    /* read each line */
+    while (fgets(buffer, sizeof(buffer), file)) {
+        line++;
+
+        /* skip whitespace */
+        pos = buffer;
+        while (isspace(*pos)) pos++;
+
+        /* skip comments and empty lines */
+        if (*pos == '#' || *pos == 0)
+            continue;
+
+        /* parse line into a key=value pair */
+        status = config_parse_pair(buffer, &pair);
+        if (status) {
+            eprintf("error on line %d: %s\n", line, buffer);
+            break;
+        }
+
+        /* find the config_option by key */
+        status = config_find_option(&pair, &option);
+        if (status) {
+            eprintf("unrecognized option '%s' on line %d: %s\n",
+                pair.key, line, buffer);
+            status = ERROR_INVALID_PARAMETER;
+            break;
+        }
+
+        if (option->type == TYPE_INT) {
+            if (!parse_uint(pair.value, (UINT*)((char*)config + option->offset))) {
+                status = ERROR_INVALID_PARAMETER;
+                eprintf("expected a number on line %d: %s=\"%s\"\n",
+                    line, pair.key, pair.value);
+                break;
+            }
+        } else {
+            if (FAILED(StringCchCopyNA((char*)config + option->offset,
+                    option->max_len, pair.value, pair.value_len))) {
+                status = ERROR_BUFFER_OVERFLOW;
+                eprintf("overflow on line %d: %s=\"%s\"\n",
+                    line, pair.key, pair.value);
+                break;
+            }
+        }
+    }
+
+    fclose(file);
+out:
+    return status;
+}
+
+static int config_init(
+    struct idmap_config *config)
+{
+    int status;
+
+    /* load default values */
+    status = config_defaults(config);
+    if (status) {
+        eprintf("config_defaults() failed with %d\n", status);
+        goto out;
+    }
+
+    /* load configuration from file */
+    status = config_load(config, CONFIG_FILENAME);
+    if (status) {
+        eprintf("config_load('%s') failed with %d\n", CONFIG_FILENAME, status);
+        goto out;
+    }
+out:
+    return status;
+}
+
+
+/* generic cache */
+typedef struct list_entry* (*entry_alloc_fn)();
+typedef void (*entry_free_fn)(struct list_entry*);
+typedef void (*entry_copy_fn)(struct list_entry*, const struct list_entry*);
+
+struct cache_ops {
+    entry_alloc_fn entry_alloc;
+    entry_free_fn entry_free;
+    entry_copy_fn entry_copy;
+};
+
+struct idmap_cache {
+    struct list_entry head;
+    const struct cache_ops *ops;
+    SRWLOCK lock;
+};
+
+
+static void cache_init(
+    struct idmap_cache *cache,
+    const struct cache_ops *ops)
+{
+    list_init(&cache->head);
+    cache->ops = ops;
+    InitializeSRWLock(&cache->lock);
+}
+
+static void cache_cleanup(
+    struct idmap_cache *cache)
+{
+    struct list_entry *entry, *tmp;
+    list_for_each_tmp(entry, tmp, &cache->head)
+        cache->ops->entry_free(entry);
+    list_init(&cache->head);
+}
+
+static int cache_insert(
+    struct idmap_cache *cache,
+    const struct idmap_lookup *lookup,
+    const struct list_entry *src)
+{
+    struct list_entry *entry;
+    int status = NO_ERROR;
+
+    AcquireSRWLockExclusive(&cache->lock);
+
+    /* search for an existing match */
+    entry = list_search(&cache->head, lookup->value, lookup->compare);
+    if (entry) {
+        /* overwrite the existing entry with the new results */
+        cache->ops->entry_copy(entry, src);
+        goto out;
+    }
+
+    /* initialize a new entry and add it to the list */
+    entry = cache->ops->entry_alloc();
+    if (entry == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+    cache->ops->entry_copy(entry, src);
+    list_add_head(&cache->head, entry);
+out:
+    ReleaseSRWLockExclusive(&cache->lock);
+    return status;
+}
+
+static int cache_lookup(
+    struct idmap_cache *cache,
+    const struct idmap_lookup *lookup,
+    struct list_entry *entry_out)
+{
+    struct list_entry *entry;
+    int status = ERROR_NOT_FOUND;
+
+    AcquireSRWLockShared(&cache->lock);
+
+    entry = list_search(&cache->head, lookup->value, lookup->compare);
+    if (entry) {
+        /* make a copy for use outside of the lock */
+        cache->ops->entry_copy(entry_out, entry);
+        status = NO_ERROR;
+    }
+
+    ReleaseSRWLockShared(&cache->lock);
+    return status;
+}
+
+
+/* user cache */
+struct idmap_user {
+    struct list_entry entry;
+    char username[VAL_LEN];
+    char principal[VAL_LEN];
+    uid_t uid;
+    gid_t gid;
+    time_t last_updated;
+};
+
+static struct list_entry* user_cache_alloc()
+{
+    struct idmap_user *user = calloc(1, sizeof(struct idmap_user));
+    return user == NULL ? NULL : &user->entry;
+}
+static void user_cache_free(struct list_entry *entry)
+{
+    free(list_container(entry, struct idmap_user, entry));
+}
+static void user_cache_copy(
+    struct list_entry *lhs,
+    const struct list_entry *rhs)
+{
+    struct idmap_user *dst = list_container(lhs, struct idmap_user, entry);
+    const struct idmap_user *src = list_container(rhs, const struct idmap_user, entry);
+    StringCchCopyA(dst->username, VAL_LEN, src->username);
+    StringCchCopyA(dst->principal, VAL_LEN, src->principal);
+    dst->uid = src->uid;
+    dst->gid = src->gid;
+    dst->last_updated = src->last_updated;
+}
+static const struct cache_ops user_cache_ops = {
+    user_cache_alloc,
+    user_cache_free,
+    user_cache_copy
+};
+
+
+/* group cache */
+struct idmap_group {
+    struct list_entry entry;
+    char name[VAL_LEN];
+    gid_t gid;
+    time_t last_updated;
+};
+
+static struct list_entry* group_cache_alloc()
+{
+    struct idmap_group *group = calloc(1, sizeof(struct idmap_group));
+    return group == NULL ? NULL : &group->entry;
+}
+static void group_cache_free(struct list_entry *entry)
+{
+    free(list_container(entry, struct idmap_group, entry));
+}
+static void group_cache_copy(
+    struct list_entry *lhs,
+    const struct list_entry *rhs)
+{
+    struct idmap_group *dst = list_container(lhs, struct idmap_group, entry);
+    const struct idmap_group *src = list_container(rhs, const struct idmap_group, entry);
+    StringCchCopyA(dst->name, VAL_LEN, src->name);
+    dst->gid = src->gid;
+    dst->last_updated = src->last_updated;
+}
+static const struct cache_ops group_cache_ops = {
+    group_cache_alloc,
+    group_cache_free,
+    group_cache_copy
+};
+
+
+/* ldap context */
+struct idmap_context {
+    struct idmap_config config;
+    struct idmap_cache users;
+    struct idmap_cache groups;
+    LDAP *ldap;
+};
+
+
+static int idmap_filter(
+    struct idmap_config *config,
+    const struct idmap_lookup *lookup,
+    char *filter,
+    size_t filter_len)
+{
+    UINT_PTR i;
+    int status = NO_ERROR;
+
+    switch (lookup->type) {
+    case TYPE_INT:
+        i = (UINT_PTR)lookup->value;
+        if (FAILED(StringCchPrintfA(filter, filter_len,
+                "(&(objectClass=%s)(%s=%u))",
+                config->classes[lookup->klass],
+                config->attributes[lookup->attr], (UINT)i))) {
+            status = ERROR_BUFFER_OVERFLOW;
+            eprintf("ldap filter buffer overflow: '%s=%u'\n",
+                config->attributes[lookup->attr], (UINT)i);
+        }
+        break;
+
+    case TYPE_STR:
+        if (FAILED(StringCchPrintfA(filter, filter_len,
+                "(&(objectClass=%s)(%s=%s))",
+                config->classes[lookup->klass],
+                config->attributes[lookup->attr], lookup->value))) {
+            status = ERROR_BUFFER_OVERFLOW;
+            eprintf("ldap filter buffer overflow: '%s=%s'\n",
+                config->attributes[lookup->attr], lookup->value);
+        }
+        break;
+
+    default:
+        status = ERROR_INVALID_PARAMETER;
+        break;
+    }
+    return status;
+}
+
+static int idmap_query_attrs(
+    struct idmap_context *context,
+    const struct idmap_lookup *lookup,
+    const unsigned attributes,
+    const unsigned optional,
+    PCHAR *values[],
+    const int len)
+{
+    char filter[FILTER_LEN];
+    struct idmap_config *config = &context->config;
+    LDAPMessage *res = NULL, *entry;
+    int i, status;
+
+    /* format the ldap filter */
+    status = idmap_filter(config, lookup, filter, FILTER_LEN);
+    if (status)
+        goto out;
+
+    /* send the ldap query */
+    status = ldap_search_st(context->ldap, config->base,
+        LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, &res);
+    if (status) {
+        eprintf("ldap search for '%s' failed with %d: %s\n",
+            filter, status, ldap_err2stringA(status));
+        status = LdapMapErrorToWin32(status);
+        goto out;
+    }
+
+    entry = ldap_first_entry(context->ldap, res);
+    if (entry == NULL) {
+        status = LDAP_NO_RESULTS_RETURNED;
+        eprintf("ldap search for '%s' failed with %d: %s\n",
+            filter, status, ldap_err2stringA(status));
+        status = LdapMapErrorToWin32(status);
+        goto out;
+    }
+
+    /* fetch the attributes */
+    for (i = 0; i < len; i++) {
+        if (ATTR_ISSET(attributes, i)) {
+            values[i] = ldap_get_values(context->ldap,
+                entry, config->attributes[i]);
+
+            /* fail if required attributes are missing */
+            if (values[i] == NULL && !ATTR_ISSET(optional, i)) {
+                status = LDAP_NO_SUCH_ATTRIBUTE;
+                eprintf("ldap entry for '%s' missing required "
+                    "attribute '%s', returning %d: %s\n",
+                    filter, config->attributes[i],
+                    status, ldap_err2stringA(status));
+                status = LdapMapErrorToWin32(status);
+                goto out;
+            }
+        }
+    }
+out:
+    if (res) ldap_msgfree(res);
+    return status;
+}
+
+static int idmap_lookup_user(
+    struct idmap_context *context,
+    const struct idmap_lookup *lookup,
+    struct idmap_user *user)
+{
+    PCHAR* values[NUM_ATTRIBUTES] = { NULL };
+    const unsigned attributes = ATTR_FLAG(ATTR_USER_NAME)
+        | ATTR_FLAG(ATTR_PRINCIPAL)
+        | ATTR_FLAG(ATTR_UID)
+        | ATTR_FLAG(ATTR_GID);
+    /* principal is optional; we'll cache it if we have it */
+    const unsigned optional = ATTR_FLAG(ATTR_PRINCIPAL);
+    int i, status;
+
+    /* check the user cache for an existing entry */
+    status = cache_lookup(&context->users, lookup, &user->entry);
+    if (status == NO_ERROR) {
+        /* don't return expired entries; query new attributes
+         * and overwrite the entry with cache_insert() */
+        if (time(NULL) - user->last_updated < context->config.cache_ttl)
+            goto out;
+    }
+
+    /* send the query to the ldap server */
+    status = idmap_query_attrs(context, lookup,
+        attributes, optional, values, NUM_ATTRIBUTES);
+    if (status)
+        goto out_free_values;
+
+    /* parse attributes */
+    if (FAILED(StringCchCopyA(user->username, VAL_LEN,
+            *values[ATTR_USER_NAME]))) {
+        eprintf("ldap attribute %s='%s' longer than %u characters\n",
+            context->config.attributes[ATTR_USER_NAME],
+            *values[ATTR_USER_NAME], VAL_LEN);
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out_free_values;
+    }
+    if (FAILED(StringCchCopyA(user->principal, VAL_LEN,
+            values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : ""))) {
+        eprintf("ldap attribute %s='%s' longer than %u characters\n",
+            context->config.attributes[ATTR_PRINCIPAL],
+            values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : "", VAL_LEN);
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out_free_values;
+    }
+    if (!parse_uint(*values[ATTR_UID], &user->uid)) {
+        eprintf("failed to parse ldap attribute %s='%s'\n",
+            context->config.attributes[ATTR_UID], *values[ATTR_UID]);
+        status = ERROR_INVALID_PARAMETER;
+        goto out_free_values;
+    }
+    if (!parse_uint(*values[ATTR_GID], &user->gid)) {
+        eprintf("failed to parse ldap attribute %s='%s'\n",
+            context->config.attributes[ATTR_GID], *values[ATTR_GID]);
+        status = ERROR_INVALID_PARAMETER;
+        goto out_free_values;
+    }
+    user->last_updated = time(NULL);
+
+    if (context->config.cache_ttl) {
+        /* insert the entry into the cache */
+        cache_insert(&context->users, lookup, &user->entry);
+    }
+out_free_values:
+    for (i = 0; i < NUM_ATTRIBUTES; i++)
+        ldap_value_free(values[i]);
+out:
+    return status;
+}
+
+static int idmap_lookup_group(
+    struct idmap_context *context,
+    const struct idmap_lookup *lookup,
+    struct idmap_group *group)
+{
+    PCHAR* values[NUM_ATTRIBUTES] = { NULL };
+    const unsigned attributes = ATTR_FLAG(ATTR_GROUP_NAME)
+        | ATTR_FLAG(ATTR_GID);
+    int i, status;
+
+    /* check the group cache for an existing entry */
+    status = cache_lookup(&context->groups, lookup, &group->entry);
+    if (status == NO_ERROR) {
+        /* don't return expired entries; query new attributes
+         * and overwrite the entry with cache_insert() */
+        if (time(NULL) - group->last_updated < context->config.cache_ttl)
+            goto out;
+    }
+
+    /* send the query to the ldap server */
+    status = idmap_query_attrs(context, lookup,
+        attributes, 0, values, NUM_ATTRIBUTES);
+    if (status)
+        goto out_free_values;
+
+    /* parse attributes */
+    if (FAILED(StringCchCopyA(group->name, VAL_LEN,
+            *values[ATTR_GROUP_NAME]))) {
+        eprintf("ldap attribute %s='%s' longer than %u characters\n",
+            context->config.attributes[ATTR_GROUP_NAME],
+            *values[ATTR_GROUP_NAME], VAL_LEN);
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out_free_values;
+    }
+    if (!parse_uint(*values[ATTR_GID], &group->gid)) {
+        eprintf("failed to parse ldap attribute %s='%s'\n",
+            context->config.attributes[ATTR_GID], *values[ATTR_GID]);
+        status = ERROR_INVALID_PARAMETER;
+        goto out_free_values;
+    }
+    group->last_updated = time(NULL);
+
+    if (context->config.cache_ttl) {
+        /* insert the entry into the cache */
+        cache_insert(&context->groups, lookup, &group->entry);
+    }
+out_free_values:
+    for (i = 0; i < NUM_ATTRIBUTES; i++)
+        ldap_value_free(values[i]);
+out:
+    return status;
+}
+
+
+/* public idmap interface */
+int nfs41_idmap_create(
+    struct idmap_context **context_out)
+{
+    struct idmap_context *context;
+    int status = NO_ERROR;
+
+    context = calloc(1, sizeof(struct idmap_context));
+    if (context == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    /* initialize the caches */
+    cache_init(&context->users, &user_cache_ops);
+    cache_init(&context->groups, &group_cache_ops);
+
+    /* load ldap configuration from file */
+    status = config_init(&context->config);
+    if (status) {
+        eprintf("config_init() failed with %d\n", status);
+        goto out_err_free;
+    }
+
+    /* initialize ldap and configure options */
+    context->ldap = ldap_init(context->config.hostname, context->config.port);
+    if (context->ldap == NULL) {
+        status = LdapGetLastError();
+        eprintf("ldap_init(%s) failed with %d: %s\n",
+            context->config.hostname, status, ldap_err2stringA(status));
+        status = LdapMapErrorToWin32(status);
+        goto out_err_free;
+    }
+
+    status = ldap_set_option(context->ldap, LDAP_OPT_PROTOCOL_VERSION,
+        (void *)&context->config.version);
+    if (status != LDAP_SUCCESS) {
+        eprintf("ldap_set_option(version=%d) failed with %d\n",
+            context->config.version, status);
+        status = LdapMapErrorToWin32(status);
+        goto out_err_free;
+    }
+
+    if (context->config.timeout) {
+        status = ldap_set_option(context->ldap, LDAP_OPT_TIMELIMIT,
+            (void *)&context->config.timeout);
+        if (status != LDAP_SUCCESS) {
+            eprintf("ldap_set_option(timeout=%d) failed with %d\n",
+                context->config.timeout, status);
+            status = LdapMapErrorToWin32(status);
+            goto out_err_free;
+        }
+    }
+
+    *context_out = context;
+out:
+    return status;
+
+out_err_free:
+    nfs41_idmap_free(context);
+    goto out;
+}
+
+void nfs41_idmap_free(
+    struct idmap_context *context)
+{
+    /* clean up the connection */
+    if (context->ldap)
+        ldap_unbind(context->ldap);
+
+    cache_cleanup(&context->users);
+    cache_cleanup(&context->groups);
+    free(context);
+}
+
+
+/* username -> uid, gid */
+static int username_cmp(const struct list_entry *list, const void *value)
+{
+    const struct idmap_user *entry = list_container(list,
+        const struct idmap_user, entry);
+    const char *username = (const char*)value;
+    return strcmp(entry->username, username);
+}
+
+int nfs41_idmap_name_to_ids(
+    struct idmap_context *context,
+    const char *username,
+    uid_t *uid_out,
+    gid_t *gid_out)
+{
+    struct idmap_lookup lookup = { ATTR_USER_NAME,
+        CLASS_USER, TYPE_STR, username_cmp };
+    struct idmap_user user;
+    int status;
+
+    if (context == NULL)
+        return ERROR_FILE_NOT_FOUND;
+
+    dprintf(IDLVL, "--> nfs41_idmap_name_to_ids('%s')\n", username);
+
+    lookup.value = username;
+
+    /* look up the user entry */
+    status = idmap_lookup_user(context, &lookup, &user);
+    if (status) {
+        dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
+            "failed with %d\n", username, status);
+        goto out;
+    }
+
+    *uid_out = user.uid;
+    *gid_out = user.gid;
+    dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
+        "returning uid=%u, gid=%u\n", username, user.uid, user.gid);
+out:
+    return status;
+}
+
+/* uid -> username */
+static int uid_cmp(const struct list_entry *list, const void *value)
+{
+    const struct idmap_user *entry = list_container(list,
+        const struct idmap_user, entry);
+    const UINT_PTR uid = (const UINT_PTR)value;
+    return (UINT)uid - entry->uid;
+}
+
+int nfs41_idmap_uid_to_name(
+    struct idmap_context *context,
+    uid_t uid,
+    char *name,
+    size_t len)
+{
+    UINT_PTR uidp = uid; /* convert to pointer size to pass as void* */
+    struct idmap_lookup lookup = { ATTR_UID, CLASS_USER, TYPE_INT, uid_cmp };
+    struct idmap_user user;
+    int status;
+
+    dprintf(IDLVL, "--> nfs41_idmap_uid_to_name(%u)\n", uid);
+
+    lookup.value = (const void*)uidp;
+
+    /* look up the user entry */
+    status = idmap_lookup_user(context, &lookup, &user);
+    if (status) {
+        dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
+            "failed with %d\n", uid, status);
+        goto out;
+    }
+
+    if (FAILED(StringCchCopyA(name, len, user.username))) {
+        status = ERROR_BUFFER_OVERFLOW;
+        eprintf("username buffer overflow: '%s' > %u\n",
+            user.username, len);
+        goto out;
+    }
+
+    dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
+        "returning '%s'\n", uid, name);
+out:
+    return status;
+}
+
+/* principal -> uid, gid */
+static int principal_cmp(const struct list_entry *list, const void *value)
+{
+    const struct idmap_user *entry = list_container(list,
+        const struct idmap_user, entry);
+    const char *principal = (const char*)value;
+    return strcmp(entry->principal, principal);
+}
+
+int nfs41_idmap_principal_to_ids(
+    struct idmap_context *context,
+    const char *principal,
+    uid_t *uid_out,
+    gid_t *gid_out)
+{
+    struct idmap_lookup lookup = { ATTR_PRINCIPAL,
+        CLASS_USER, TYPE_STR, principal_cmp };
+    struct idmap_user user;
+    int status;
+
+    dprintf(IDLVL, "--> nfs41_idmap_principal_to_ids('%s')\n", principal);
+
+    lookup.value = principal;
+
+    /* look up the user entry */
+    status = idmap_lookup_user(context, &lookup, &user);
+    if (status) {
+        dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
+            "failed with %d\n", principal, status);
+        goto out;
+    }
+
+    *uid_out = user.uid;
+    *gid_out = user.gid;
+    dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
+        "returning uid=%u, gid=%u\n", principal, user.uid, user.gid);
+out:
+    return status;
+}
+
+/* group -> gid */
+static int group_cmp(const struct list_entry *list, const void *value)
+{
+    const struct idmap_group *entry = list_container(list,
+        const struct idmap_group, entry);
+    const char *group = (const char*)value;
+    return strcmp(entry->name, group);
+}
+
+int nfs41_idmap_group_to_gid(
+    struct idmap_context *context,
+    const char *name,
+    gid_t *gid_out)
+{
+    struct idmap_lookup lookup = { ATTR_GROUP_NAME,
+        CLASS_GROUP, TYPE_STR, group_cmp };
+    struct idmap_group group;
+    int status;
+
+    dprintf(IDLVL, "--> nfs41_idmap_group_to_gid('%s')\n", name);
+
+    lookup.value = name;
+
+    /* look up the group entry */
+    status = idmap_lookup_group(context, &lookup, &group);
+    if (status) {
+        dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
+            "failed with %d\n", name, status);
+        goto out;
+    }
+
+    *gid_out = group.gid;
+    dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
+        "returning %u\n", name, group.gid);
+out:
+    return status;
+}
+
+/* gid -> group */
+static int gid_cmp(const struct list_entry *list, const void *value)
+{
+    const struct idmap_group *entry = list_container(list,
+        const struct idmap_group, entry);
+    const UINT_PTR gid = (const UINT_PTR)value;
+    return (UINT)gid - entry->gid;
+}
+
+int nfs41_idmap_gid_to_group(
+    struct idmap_context *context,
+    gid_t gid,
+    char *name,
+    size_t len)
+{
+    UINT_PTR gidp = gid; /* convert to pointer size to pass as void* */
+    struct idmap_lookup lookup = { ATTR_GID, CLASS_GROUP, TYPE_INT, gid_cmp };
+    struct idmap_group group;
+    int status;
+
+    dprintf(IDLVL, "--> nfs41_idmap_gid_to_group(%u)\n", gid);
+
+    lookup.value = (const void*)gidp;
+
+    /* look up the group entry */
+    status = idmap_lookup_group(context, &lookup, &group);
+    if (status) {
+        dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
+            "failed with %d\n", gid, status);
+        goto out;
+    }
+
+    if (FAILED(StringCchCopyA(name, len, group.name))) {
+        status = ERROR_BUFFER_OVERFLOW;
+        eprintf("group name buffer overflow: '%s' > %u\n",
+            group.name, len);
+        goto out;
+    }
+
+    dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
+        "returning '%s'\n", gid, name);
+out:
+    return status;
+}
diff --git a/reactos/base/services/nfsd/idmap.h b/reactos/base/services/nfsd/idmap.h
new file mode 100644 (file)
index 0000000..6703bec
--- /dev/null
@@ -0,0 +1,67 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 IDMAP_H
+#define IDMAP_H
+
+#include "nfs41_types.h"
+
+
+/* idmap.c */
+typedef struct idmap_context nfs41_idmapper;
+
+int nfs41_idmap_create(
+    nfs41_idmapper **context_out);
+
+void nfs41_idmap_free(
+    nfs41_idmapper *context);
+
+
+int nfs41_idmap_name_to_ids(
+    nfs41_idmapper *context,
+    const char *username,
+    uid_t *uid_out,
+    gid_t *gid_out);
+
+int nfs41_idmap_uid_to_name(
+    nfs41_idmapper *context,
+    uid_t uid,
+    char *name_out,
+    size_t len);
+
+int nfs41_idmap_principal_to_ids(
+    nfs41_idmapper *context,
+    const char *principal,
+    uid_t *uid_out,
+    gid_t *gid_out);
+
+int nfs41_idmap_group_to_gid(
+    nfs41_idmapper *context,
+    const char *name,
+    gid_t *gid_out);
+
+int nfs41_idmap_gid_to_group(
+    nfs41_idmapper *context,
+    gid_t gid,
+    char *name_out,
+    size_t len);
+
+#endif /* !IDMAP_H */
diff --git a/reactos/base/services/nfsd/list.h b/reactos/base/services/nfsd/list.h
new file mode 100644 (file)
index 0000000..221daf9
--- /dev/null
@@ -0,0 +1,114 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_LIST_H
+#define NFS41_LIST_H
+
+
+/* doubly-linked list */
+struct list_entry {
+    struct list_entry *prev;
+    struct list_entry *next;
+};
+
+
+#define list_container(entry, type, field) \
+    ((type*)((const char*)(entry) - (const char*)(&((type*)0)->field)))
+
+#define list_for_each(entry, head) \
+    for (entry = (head)->next; entry != (head); entry = entry->next)
+
+#define list_for_each_tmp(entry, tmp, head) \
+    for (entry = (head)->next, tmp = entry->next; entry != (head); \
+        entry = tmp, tmp = entry->next)
+
+#define list_for_each_reverse(entry, head) \
+    for (entry = (head)->prev; entry != (head); entry = entry->prev)
+
+#define list_for_each_reverse_tmp(entry, tmp, head) \
+    for (entry = (head)->next, tmp = entry->next; entry != (head); \
+        entry = tmp, tmp = entry->next)
+
+
+static void list_init(
+    struct list_entry *head)
+{
+    head->prev = head;
+    head->next = head;
+}
+
+static int list_empty(
+    struct list_entry *head)
+{
+    return head->next == head;
+}
+
+static void list_add(
+    struct list_entry *entry,
+    struct list_entry *prev,
+    struct list_entry *next)
+{
+    /* assert(prev->next == next && next->prev == prev); */
+    entry->prev = prev;
+    entry->next = next;
+    prev->next = entry;
+    next->prev = entry;
+}
+
+static void list_add_head(
+    struct list_entry *head,
+    struct list_entry *entry)
+{
+    list_add(entry, head, head->next);
+}
+
+static void list_add_tail(
+    struct list_entry *head,
+    struct list_entry *entry)
+{
+    list_add(entry, head->prev, head);
+}
+
+static void list_remove(
+    struct list_entry *entry)
+{
+    if (!list_empty(entry)) {
+        entry->next->prev = entry->prev;
+        entry->prev->next = entry->next;
+        list_init(entry);
+    }
+}
+
+typedef int (*list_compare_fn)(const struct list_entry*, const void*);
+
+static struct list_entry* list_search(
+    const struct list_entry *head,
+    const void *value,
+    list_compare_fn compare)
+{
+    struct list_entry *entry;
+    list_for_each(entry, head)
+        if (compare(entry, value) == 0)
+            return entry;
+    return NULL;
+}
+
+#endif /* !NFS41_LIST_H */
diff --git a/reactos/base/services/nfsd/lock.c b/reactos/base/services/nfsd/lock.c
new file mode 100644 (file)
index 0000000..9d0c46b
--- /dev/null
@@ -0,0 +1,369 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+
+#include "daemon_debug.h"
+#include "delegation.h"
+#include "nfs41_ops.h"
+#include "upcall.h"
+#include "util.h"
+
+
+#define LKLVL 2 /* dprintf level for lock logging */
+
+
+static void lock_stateid_arg(
+    IN nfs41_open_state *state,
+    OUT stateid_arg *arg)
+{
+    arg->open = state;
+    arg->delegation = NULL;
+
+    AcquireSRWLockShared(&state->lock);
+    if (state->locks.stateid.seqid) {
+        memcpy(&arg->stateid, &state->locks.stateid, sizeof(stateid4));
+        arg->type = STATEID_LOCK;
+    } else if (state->do_close) {
+        memcpy(&arg->stateid, &state->stateid, sizeof(stateid4));
+        arg->type = STATEID_OPEN;
+    } else {
+        memset(&arg->stateid, 0, sizeof(stateid4));
+        arg->type = STATEID_SPECIAL;
+    }
+    ReleaseSRWLockShared(&state->lock);
+}
+
+/* expects the caller to hold an exclusive lock on nfs41_open_state.lock */
+static void lock_stateid_update(
+    OUT nfs41_open_state *state,
+    IN const stateid4 *stateid)
+{
+    if (state->locks.stateid.seqid == 0) {
+        /* if it's a new lock stateid, copy it in */
+        memcpy(&state->locks.stateid, stateid, sizeof(stateid4));
+    } else if (stateid->seqid > state->locks.stateid.seqid) {
+        /* update the seqid if it's more recent */
+        state->locks.stateid.seqid = stateid->seqid;
+    }
+}
+
+static void open_lock_add(
+    IN nfs41_open_state *open,
+    IN const stateid_arg *stateid,
+    IN nfs41_lock_state *lock)
+{
+    AcquireSRWLockExclusive(&open->lock);
+
+    if (stateid->type == STATEID_LOCK)
+        lock_stateid_update(open, &stateid->stateid);
+
+    lock->id = open->locks.counter++;
+    list_add_tail(&open->locks.list, &lock->open_entry);
+
+    ReleaseSRWLockExclusive(&open->lock);
+}
+
+static bool_t open_lock_delegate(
+    IN nfs41_open_state *open,
+    IN nfs41_lock_state *lock)
+{
+    bool_t delegated = FALSE;
+
+    AcquireSRWLockExclusive(&open->lock);
+    if (open->delegation.state) {
+        nfs41_delegation_state *deleg = open->delegation.state;
+        AcquireSRWLockShared(&deleg->lock);
+        if (deleg->state.type == OPEN_DELEGATE_WRITE
+            && deleg->status == DELEGATION_GRANTED) {
+            lock->delegated = 1;
+            lock->id = open->locks.counter++;
+            list_add_tail(&open->locks.list, &lock->open_entry);
+            delegated = TRUE;
+        }
+        ReleaseSRWLockShared(&deleg->lock);
+    }
+    ReleaseSRWLockExclusive(&open->lock);
+
+    return delegated;
+}
+
+#define lock_entry(pos) list_container(pos, nfs41_lock_state, open_entry)
+
+static int lock_range_cmp(const struct list_entry *entry, const void *value)
+{
+    const nfs41_lock_state *lhs = lock_entry(entry);
+    const nfs41_lock_state *rhs = (const nfs41_lock_state*)value;
+    if (lhs->offset != rhs->offset) return -1;
+    if (lhs->length != rhs->length) return -1;
+    return 0;
+}
+
+static int open_unlock_delegate(
+    IN nfs41_open_state *open,
+    IN const nfs41_lock_state *input)
+{
+    struct list_entry *entry;
+    int status = ERROR_NOT_LOCKED;
+
+    AcquireSRWLockExclusive(&open->lock);
+
+    /* find lock state that matches this range */
+    entry = list_search(&open->locks.list, input, lock_range_cmp);
+    if (entry) {
+        nfs41_lock_state *lock = lock_entry(entry);
+        if (lock->delegated) {
+            /* if the lock was delegated, remove/free it and return success */
+            list_remove(entry);
+            free(lock);
+            status = NO_ERROR;
+        } else
+            status = ERROR_LOCKED;
+    }
+
+    ReleaseSRWLockExclusive(&open->lock);
+    return status;
+}
+
+static void open_unlock_remove(
+    IN nfs41_open_state *open,
+    IN const stateid_arg *stateid,
+    IN const nfs41_lock_state *input)
+{
+    struct list_entry *entry;
+
+    AcquireSRWLockExclusive(&open->lock);
+    if (stateid->type == STATEID_LOCK)
+        lock_stateid_update(open, &stateid->stateid);
+
+    /* find and remove the unlocked range */
+    entry = list_search(&open->locks.list, input, lock_range_cmp);
+    if (entry) {
+        list_remove(entry);
+        free(lock_entry(entry));
+    }
+    ReleaseSRWLockExclusive(&open->lock);
+}
+
+
+/* NFS41_LOCK */
+static int parse_lock(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    lock_upcall_args *args = &upcall->args.lock;
+
+    status = safe_read(&buffer, &length, &args->offset, sizeof(LONGLONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->length, sizeof(LONGLONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->exclusive, sizeof(BOOLEAN));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->blocking, sizeof(BOOLEAN));
+    if (status) goto out;
+
+    dprintf(1, "parsing NFS41_LOCK: offset=0x%llx length=0x%llx exclusive=%u "
+            "blocking=%u\n", args->offset, args->length, args->exclusive, 
+            args->blocking);
+out:
+    return status;
+}
+
+static __inline uint32_t get_lock_type(BOOLEAN exclusive, BOOLEAN blocking)
+{
+    return blocking == 0
+        ? ( exclusive == 0 ? READ_LT : WRITE_LT )
+        : ( exclusive == 0 ? READW_LT : WRITEW_LT );
+}
+
+static int handle_lock(nfs41_upcall *upcall)
+{
+    stateid_arg stateid;
+    lock_upcall_args *args = &upcall->args.lock;
+    nfs41_open_state *state = upcall->state_ref;
+    nfs41_lock_state *lock;
+    const uint32_t type = get_lock_type(args->exclusive, args->blocking);
+    int status = NO_ERROR;
+
+    /* 18.10.3. Operation 12: LOCK - Create Lock
+     * "To lock the file from a specific offset through the end-of-file
+     * (no matter how long the file actually is) use a length field equal
+     * to NFS4_UINT64_MAX." */
+    if (args->length >= NFS4_UINT64_MAX - args->offset)
+        args->length = NFS4_UINT64_MAX;
+
+    /* allocate the lock state */
+    lock = calloc(1, sizeof(nfs41_lock_state));
+    if (lock == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+    lock->offset = args->offset;
+    lock->length = args->length;
+    lock->exclusive = args->exclusive;
+
+    /* if we hold a write delegation, handle the lock locally */
+    if (open_lock_delegate(state, lock)) {
+        dprintf(LKLVL, "delegated lock { %llu, %llu }\n",
+            lock->offset, lock->length);
+        args->acquired = TRUE; /* for cancel_lock() */
+        goto out;
+    }
+
+    /* open_to_lock_owner4 requires an open stateid; if we
+     * have a delegation, convert it to an open stateid */
+    status = nfs41_delegation_to_open(state, TRUE);
+    if (status) {
+        status = ERROR_FILE_INVALID;
+        goto out_free;
+    }
+
+    EnterCriticalSection(&state->locks.lock);
+
+    lock_stateid_arg(state, &stateid);
+
+    status = nfs41_lock(state->session, &state->file, &state->owner,
+        type, lock->offset, lock->length, FALSE, TRUE, &stateid);
+    if (status) {
+        dprintf(LKLVL, "nfs41_lock failed with %s\n",
+            nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+        LeaveCriticalSection(&state->locks.lock);
+        goto out_free;
+    }
+
+    /* save lock state with the open */
+    open_lock_add(state, &stateid, lock);
+    LeaveCriticalSection(&state->locks.lock);
+
+    args->acquired = TRUE; /* for cancel_lock() */
+out:
+    return status;
+
+out_free:
+    free(lock);
+    goto out;
+}
+
+static void cancel_lock(IN nfs41_upcall *upcall)
+{
+    stateid_arg stateid;
+    nfs41_lock_state input;
+    lock_upcall_args *args = &upcall->args.lock;
+    nfs41_open_state *state = upcall->state_ref;
+    int status = NO_ERROR;
+
+    dprintf(1, "--> cancel_lock()\n");
+
+    /* can't do 'if (upcall->status)' here, because a handle_lock() success
+     * could be overwritten by upcall_marshall() or allocation failure */
+    if (!args->acquired)
+        goto out;
+
+    input.offset = args->offset;
+    input.length = args->length;
+
+    /* search for the range to unlock, and remove if delegated */
+    status = open_unlock_delegate(state, &input);
+    if (status != ERROR_LOCKED)
+        goto out;
+
+    EnterCriticalSection(&state->locks.lock);
+    lock_stateid_arg(state, &stateid);
+
+    status = nfs41_unlock(state->session, &state->file,
+        args->offset, args->length, &stateid);
+
+    open_unlock_remove(state, &stateid, &input);
+    LeaveCriticalSection(&state->locks.lock);
+
+    status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+out:
+    dprintf(1, "<-- cancel_lock() returning %d\n", status);
+}
+
+
+/* NFS41_UNLOCK */
+static int parse_unlock(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    unlock_upcall_args *args = &upcall->args.unlock;
+
+    status = safe_read(&buffer, &length, &args->count, sizeof(ULONG));
+    if (status) goto out;
+
+    args->buf = buffer;
+    args->buf_len = length;
+
+    dprintf(1, "parsing NFS41_UNLOCK: count=%u\n", args->count);
+out:
+    return status;
+}
+
+static int handle_unlock(nfs41_upcall *upcall)
+{
+    nfs41_lock_state input;
+    stateid_arg stateid;
+    unlock_upcall_args *args = &upcall->args.unlock;
+    nfs41_open_state *state = upcall->state_ref;
+    unsigned char *buf = args->buf;
+    uint32_t buf_len = args->buf_len;
+    uint32_t i;
+    int status = NO_ERROR;
+
+    for (i = 0; i < args->count; i++) {
+        if (safe_read(&buf, &buf_len, &input.offset, sizeof(LONGLONG))) break;
+        if (safe_read(&buf, &buf_len, &input.length, sizeof(LONGLONG))) break;
+
+        /* do the same translation as LOCK, or the ranges won't match */
+        if (input.length >= NFS4_UINT64_MAX - input.offset)
+            input.length = NFS4_UINT64_MAX;
+
+        /* search for the range to unlock, and remove if delegated */
+        status = open_unlock_delegate(state, &input);
+        if (status != ERROR_LOCKED)
+            continue;
+
+        EnterCriticalSection(&state->locks.lock);
+        lock_stateid_arg(state, &stateid);
+
+        status = nfs41_unlock(state->session, &state->file,
+            input.offset, input.length, &stateid);
+
+        open_unlock_remove(state, &stateid, &input);
+        LeaveCriticalSection(&state->locks.lock);
+
+        status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+    }
+    return status;
+}
+
+
+const nfs41_upcall_op nfs41_op_lock = {
+    parse_lock,
+    handle_lock,
+    NULL,
+    cancel_lock
+};
+const nfs41_upcall_op nfs41_op_unlock = {
+    parse_unlock,
+    handle_unlock
+};
diff --git a/reactos/base/services/nfsd/lookup.c b/reactos/base/services/nfsd/lookup.c
new file mode 100644 (file)
index 0000000..1b84bdc
--- /dev/null
@@ -0,0 +1,508 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <time.h>
+
+#include "nfs41_compound.h"
+#include "nfs41_ops.h"
+#include "name_cache.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+#define LULVL 2 /* dprintf level for lookup logging */
+
+
+#define MAX_LOOKUP_COMPONENTS 8
+
+/* map NFS4ERR_MOVED to an arbitrary windows error */
+#define ERROR_FILESYSTEM_ABSENT ERROR_DEVICE_REMOVED
+
+struct lookup_referral {
+    nfs41_path_fh           parent;
+    nfs41_component         name;
+};
+
+
+typedef struct __nfs41_lookup_component_args {
+    nfs41_sequence_args     sequence;
+    nfs41_putfh_args        putfh;
+    nfs41_lookup_args       lookup[MAX_LOOKUP_COMPONENTS];
+    nfs41_getattr_args      getrootattr;
+    nfs41_getattr_args      getattr[MAX_LOOKUP_COMPONENTS];
+    bitmap4                 attr_request;
+} nfs41_lookup_component_args;
+
+typedef struct __nfs41_lookup_component_res {
+    nfs41_sequence_res      sequence;
+    nfs41_putfh_res         putfh;
+    nfs41_lookup_res        lookup[MAX_LOOKUP_COMPONENTS];
+    nfs41_getfh_res         getrootfh;
+    nfs41_getfh_res         getfh[MAX_LOOKUP_COMPONENTS];
+    nfs41_path_fh           root;
+    nfs41_path_fh           file[MAX_LOOKUP_COMPONENTS];
+    nfs41_getattr_res       getrootattr;
+    nfs41_getattr_res       getattr[MAX_LOOKUP_COMPONENTS];
+    nfs41_file_info         rootinfo;
+    nfs41_file_info         info[MAX_LOOKUP_COMPONENTS];
+    struct lookup_referral  *referral;
+} nfs41_lookup_component_res;
+
+
+static void init_component_args(
+    IN nfs41_lookup_component_args *args,
+    IN nfs41_lookup_component_res *res,
+    IN nfs41_abs_path *path,
+    IN struct lookup_referral *referral)
+{
+    uint32_t i;
+
+    args->attr_request.count = 2;
+    args->attr_request.arr[0] = FATTR4_WORD0_TYPE
+        | FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE
+        | FATTR4_WORD0_FSID | FATTR4_WORD0_FILEID
+        | FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE;
+    args->attr_request.arr[1] = FATTR4_WORD1_MODE
+        | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_SYSTEM
+        | FATTR4_WORD1_TIME_ACCESS | FATTR4_WORD1_TIME_CREATE
+        | FATTR4_WORD1_TIME_MODIFY;
+
+    args->getrootattr.attr_request = &args->attr_request;
+    res->root.path = path;
+    res->getrootfh.fh = &res->root.fh;
+    res->getrootattr.info = &res->rootinfo;
+    res->getrootattr.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    res->referral = referral;
+
+    for (i = 0; i < MAX_LOOKUP_COMPONENTS; i++) {
+        args->getattr[i].attr_request = &args->attr_request;
+        res->file[i].path = path;
+        args->lookup[i].name = &res->file[i].name;
+        res->getfh[i].fh = &res->file[i].fh;
+        res->getattr[i].info = &res->info[i];
+        res->getattr[i].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    }
+}
+
+static int lookup_rpc(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *dir,
+    IN uint32_t component_count,
+    IN nfs41_lookup_component_args *args,
+    OUT nfs41_lookup_component_res *res)
+{
+    int status;
+    uint32_t i;
+    nfs41_compound compound;
+    nfs_argop4 argops[4+MAX_LOOKUP_COMPONENTS*3];
+    nfs_resop4 resops[4+MAX_LOOKUP_COMPONENTS*3];
+
+    compound_init(&compound, argops, resops, "lookup");
+
+    compound_add_op(&compound, OP_SEQUENCE, &args->sequence, &res->sequence);
+    nfs41_session_sequence(&args->sequence, session, 0);
+
+    if (dir == &res->root) {
+        compound_add_op(&compound, OP_PUTROOTFH, NULL, &res->putfh);
+        compound_add_op(&compound, OP_GETFH, NULL, &res->getrootfh);
+        compound_add_op(&compound, OP_GETATTR, &args->getrootattr, 
+            &res->getrootattr);
+    } else {
+        args->putfh.file = dir;
+        compound_add_op(&compound, OP_PUTFH, &args->putfh, &res->putfh);
+    }
+
+    for (i = 0; i < component_count; i++) {
+        compound_add_op(&compound, OP_LOOKUP, &args->lookup[i], &res->lookup[i]);
+        compound_add_op(&compound, OP_GETFH, NULL, &res->getfh[i]);
+        compound_add_op(&compound, OP_GETATTR, &args->getattr[i], 
+            &res->getattr[i]);
+    }
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+static int map_lookup_error(int status, bool_t last_component)
+{
+    switch (status) {
+    case NFS4ERR_NOENT:
+        if (last_component)     return ERROR_FILE_NOT_FOUND;
+        else                    return ERROR_PATH_NOT_FOUND;
+    case NFS4ERR_SYMLINK:       return ERROR_REPARSE;
+    case NFS4ERR_MOVED:         return ERROR_FILESYSTEM_ABSENT;
+    default: return nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND);
+    }
+}
+
+static int server_lookup(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *dir,
+    IN const char *path,
+    IN const char *path_end,
+    IN uint32_t count,
+    IN nfs41_lookup_component_args *args,
+    IN nfs41_lookup_component_res *res,
+    OUT OPTIONAL nfs41_path_fh **parent_out,
+    OUT OPTIONAL nfs41_path_fh **target_out,
+    OUT OPTIONAL nfs41_file_info *info_out)
+{
+    nfs41_path_fh *file, *parent;
+    uint32_t i = 0;
+    int status;
+
+    if (parent_out) *parent_out = NULL;
+    if (target_out) *target_out = NULL;
+
+    lookup_rpc(session, dir, count, args, res);
+
+    status = res->sequence.sr_status;       if (status) goto out;
+    status = res->putfh.status;             if (status) goto out;
+    status = res->getrootfh.status;         if (status) goto out;
+    status = res->getrootattr.status;       if (status) goto out;
+
+    if (dir == &res->root) {
+        nfs41_component name = { 0 };
+
+        /* fill in the file handle's fileid and superblock */
+        dir->fh.fileid = res->getrootattr.info->fileid;
+        status = nfs41_superblock_for_fh(session,
+            &res->getrootattr.info->fsid, NULL, dir);
+        if (status)
+            goto out;
+
+        /* get the name of the parent (empty if its the root) */
+        last_component(path, count ? args->lookup[0].name->name : path_end, &name);
+
+        /* add the file handle and attributes to the name cache */
+        memcpy(&res->getrootattr.info->attrmask,
+            &res->getrootattr.obj_attributes.attrmask, sizeof(bitmap4));
+        nfs41_name_cache_insert(session_name_cache(session), path, &name,
+            &dir->fh, res->getrootattr.info, NULL, OPEN_DELEGATE_NONE);
+    }
+    file = dir;
+
+    if (count == 0) {
+        if (target_out)
+            *target_out = dir;
+        if (info_out)
+            memcpy(info_out, res->getrootattr.info, sizeof(nfs41_file_info));
+    } else if (count == 1) {
+        if (parent_out)
+            *parent_out = dir;
+    }
+
+    for (i = 0; i < count; i++) {
+        if (res->lookup[i].status == NFS4ERR_SYMLINK) {
+            /* return the symlink as the parent file */
+            last_component(path, args->lookup[i].name->name, &file->name);
+            if (parent_out) *parent_out = file;
+        } else if (res->lookup[i].status == NFS4ERR_NOENT) {
+            /* insert a negative lookup entry */
+            nfs41_name_cache_insert(session_name_cache(session), path,
+                args->lookup[i].name, NULL, NULL, NULL, OPEN_DELEGATE_NONE);
+        }
+        status = res->lookup[i].status;     if (status) break;
+
+        if (res->getfh[i].status == NFS4ERR_MOVED) {
+            /* save enough information to follow the referral */
+            path_fh_copy(&res->referral->parent, file);
+            res->referral->name.name = args->lookup[i].name->name;
+            res->referral->name.len = args->lookup[i].name->len;
+        }
+        status = res->getfh[i].status;      if (status) break;
+        status = res->getattr[i].status;    if (status) break;
+
+        parent = file;
+        file = &res->file[i];
+
+        /* fill in the file handle's fileid and superblock */
+        file->fh.fileid = res->getattr[i].info->fileid;
+        status = nfs41_superblock_for_fh(session,
+            &res->getattr[i].info->fsid, &parent->fh, file);
+        if (status)
+            break;
+
+        /* add the file handle and attributes to the name cache */
+        memcpy(&res->getattr[i].info->attrmask,
+            &res->getattr[i].obj_attributes.attrmask, sizeof(bitmap4));
+        nfs41_name_cache_insert(session_name_cache(session),
+            path, args->lookup[i].name, &res->file[i].fh,
+            res->getattr[i].info, NULL, OPEN_DELEGATE_NONE);
+
+        if (i == count-1) {
+            if (target_out)
+                *target_out = file;
+            if (info_out)
+                memcpy(info_out, res->getattr[i].info, sizeof(nfs41_file_info));
+        } else if (i == count-2) {
+            if (parent_out)
+                *parent_out = file;
+        }
+    }
+out:
+    return map_lookup_error(status, i == count-1);
+}
+
+static uint32_t max_lookup_components(
+    IN const nfs41_session *session)
+{
+    const uint32_t comps = (session->fore_chan_attrs.ca_maxoperations - 4) / 3;
+    return min(comps, MAX_LOOKUP_COMPONENTS);
+}
+
+static uint32_t get_component_array(
+    IN OUT const char **path_pos,
+    IN const char *path_end,
+    IN uint32_t max_components,
+    OUT nfs41_path_fh *components,
+    OUT uint32_t *component_count)
+{
+    uint32_t i;
+
+    for (i = 0; i < max_components; i++) {
+        if (!next_component(*path_pos, path_end, &components[i].name))
+            break;
+        *path_pos = components[i].name.name + components[i].name.len;
+    }
+
+    *component_count = i;
+    return i;
+}
+
+static int server_lookup_loop(
+    IN nfs41_session *session,
+    IN OPTIONAL nfs41_path_fh *parent_in,
+    IN nfs41_abs_path *path,
+    IN const char *path_pos,
+    IN struct lookup_referral *referral,
+    OUT OPTIONAL nfs41_path_fh *parent_out,
+    OUT OPTIONAL nfs41_path_fh *target_out,
+    OUT OPTIONAL nfs41_file_info *info_out)
+{
+    nfs41_lookup_component_args args = { 0 };
+    nfs41_lookup_component_res res = { 0 };
+    nfs41_path_fh *dir, *parent, *target;
+    const char *path_end;
+    const uint32_t max_components = max_lookup_components(session);
+    uint32_t count;
+    int status = NO_ERROR;
+
+    init_component_args(&args, &res, path, referral);
+    parent = NULL;
+    target = NULL;
+
+    path_end = path->path + path->len;
+    dir = parent_in ? parent_in : &res.root;
+
+    while (get_component_array(&path_pos, path_end,
+        max_components, res.file, &count)) {
+
+        status = server_lookup(session, dir, path->path, path_end, count,
+            &args, &res, &parent, &target, info_out);
+
+        if (status == ERROR_REPARSE) {
+            /* copy the component name of the symlink */
+            if (parent_out && parent) {
+                const ptrdiff_t offset = parent->name.name - path->path;
+                parent_out->name.name = parent_out->path->path + offset;
+                parent_out->name.len = parent->name.len;
+            }
+            goto out_parent;
+        }
+        if (status == ERROR_FILE_NOT_FOUND && is_last_component(path_pos, path_end))
+            goto out_parent;
+        if (status)
+            goto out;
+
+        dir = target;
+    }
+
+    if (dir == &res.root && (target_out || info_out)) {
+        /* didn't get any components, so we just need the root */
+        status = server_lookup(session, dir, path->path, path_end,
+            0, &args, &res, &parent, &target, info_out);
+        if (status)
+            goto out;
+    }
+
+    if (target_out && target) fh_copy(&target_out->fh, &target->fh);
+out_parent:
+    if (parent_out && parent) fh_copy(&parent_out->fh, &parent->fh);
+out:
+    return status;
+}
+
+
+static void referral_locations_free(
+    IN fs_locations4 *locations)
+{
+    uint32_t i;
+    if (locations->locations) {
+        for (i = 0; i < locations->location_count; i++)
+            free(locations->locations[i].servers);
+        free(locations->locations);
+    }
+}
+
+static int referral_resolve(
+    IN nfs41_root *root,
+    IN nfs41_session *session_in,
+    IN struct lookup_referral *referral,
+    OUT nfs41_abs_path *path_out,
+    OUT nfs41_session **session_out)
+{
+    char rest_of_path[NFS41_MAX_PATH_LEN];
+    fs_locations4 locations = { 0 };
+    const fs_location4 *location;
+    nfs41_client *client;
+    int status;
+
+    /* get fs_locations */
+    status = nfs41_fs_locations(session_in, &referral->parent,
+        &referral->name, &locations);
+    if (status) {
+        eprintf("nfs41_fs_locations() failed with %s\n",
+            nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_PATH_NOT_FOUND);
+        goto out;
+    }
+
+    /* mount the first location available */
+    status = nfs41_root_mount_referral(root, &locations, &location, &client);
+    if (status) {
+        eprintf("nfs41_root_mount_referral() failed with %d\n",
+            status);
+        goto out;
+    }
+
+    /* format a new path from that location's root */
+    if (FAILED(StringCchCopyA(rest_of_path, NFS41_MAX_PATH_LEN,
+            referral->name.name + referral->name.len))) {
+        status = ERROR_FILENAME_EXCED_RANGE;
+        goto out;
+    }
+
+    AcquireSRWLockExclusive(&path_out->lock);
+    abs_path_copy(path_out, &location->path);
+    if (FAILED(StringCchCatA(path_out->path, NFS41_MAX_PATH_LEN, rest_of_path)))
+        status = ERROR_FILENAME_EXCED_RANGE;
+    path_out->len = path_out->len + (unsigned short)strlen(rest_of_path);
+    ReleaseSRWLockExclusive(&path_out->lock);
+
+    if (session_out) *session_out = client->session;
+out:
+    referral_locations_free(&locations);
+    return status;
+}
+
+int nfs41_lookup(
+    IN nfs41_root *root,
+    IN nfs41_session *session,
+    IN OUT nfs41_abs_path *path_inout,
+    OUT OPTIONAL nfs41_path_fh *parent_out,
+    OUT OPTIONAL nfs41_path_fh *target_out,
+    OUT OPTIONAL nfs41_file_info *info_out,
+    OUT nfs41_session **session_out)
+{
+    nfs41_abs_path path;
+    struct nfs41_name_cache *cache = session_name_cache(session);
+    nfs41_path_fh parent, target, *server_start;
+    const char *path_pos, *path_end;
+    struct lookup_referral referral;
+    bool_t negative = 0;
+    int status;
+
+    if (session_out) *session_out = session;
+
+    InitializeSRWLock(&path.lock);
+
+    /* to avoid holding this lock over multiple rpcs,
+     * make a copy of the path and use that instead */
+    AcquireSRWLockShared(&path_inout->lock);
+    abs_path_copy(&path, path_inout);
+    ReleaseSRWLockShared(&path_inout->lock);
+
+    path_pos = path.path;
+    path_end = path.path + path.len;
+
+    dprintf(LULVL, "--> nfs41_lookup('%s')\n", path.path);
+
+    if (parent_out == NULL) parent_out = &parent;
+    if (target_out == NULL) target_out = &target;
+    parent_out->fh.len = target_out->fh.len = 0;
+
+    status = nfs41_name_cache_lookup(cache, path_pos, path_end, &path_pos,
+        &parent_out->fh, &target_out->fh, info_out, &negative);
+    if (status == NO_ERROR || negative)
+        goto out;
+
+    if (parent_out->fh.len) {
+        /* start where the name cache left off */
+        if (&parent != parent_out) {
+            /* must make a copy for server_start, because
+             * server_lookup_loop() will overwrite parent_out */
+            path_fh_copy(&parent, parent_out);
+        }
+        server_start = &parent;
+    } else {
+        /* start with PUTROOTFH */
+        server_start = NULL;
+    }
+
+    status = server_lookup_loop(session, server_start,
+        &path, path_pos, &referral, parent_out, target_out, info_out);
+
+    if (status == ERROR_FILESYSTEM_ABSENT) {
+        nfs41_session *new_session;
+
+        /* create a session to the referred server and
+         * reformat the path relative to that server's root */
+        status = referral_resolve(root, session,
+            &referral, path_inout, &new_session);
+        if (status) {
+            eprintf("referral_resolve() failed with %d\n", status);
+            goto out;
+        }
+
+        /* update the positions of the parent and target components */
+        last_component(path_inout->path, path_inout->path + path_inout->len,
+            &target_out->name);
+        last_component(path_inout->path, target_out->name.name,
+            &parent_out->name);
+
+        if (session_out) *session_out = new_session;
+
+        /* look up the new path */
+        status = nfs41_lookup(root, new_session, path_inout,
+            parent_out, target_out, info_out, session_out);
+    }
+out:
+    dprintf(LULVL, "<-- nfs41_lookup() returning %d\n", status);
+    return status;
+}
diff --git a/reactos/base/services/nfsd/makefile b/reactos/base/services/nfsd/makefile
new file mode 100644 (file)
index 0000000..d5bedee
--- /dev/null
@@ -0,0 +1,8 @@
+#
+# DO NOT EDIT THIS FILE!!!  Edit .\sources. if you want to add a new source
+# file to this component.  This file merely indirects to the real make file
+# that is shared by all the driver components of the Windows NT DDK
+#
+
+!INCLUDE $(NTMAKEENV)\makefile.def
+
diff --git a/reactos/base/services/nfsd/mount.c b/reactos/base/services/nfsd/mount.c
new file mode 100644 (file)
index 0000000..ea059c4
--- /dev/null
@@ -0,0 +1,176 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+
+#include "daemon_debug.h"
+#include "nfs41_ops.h"
+#include "upcall.h"
+#include "util.h"
+
+
+/* NFS41_MOUNT */
+static int parse_mount(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) 
+{
+    int status;
+    mount_upcall_args *args = &upcall->args.mount;
+
+    status = get_name(&buffer, &length, &args->hostname);
+    if(status) goto out;
+    status = get_name(&buffer, &length, &args->path);
+    if(status) goto out;
+    status = safe_read(&buffer, &length, &args->sec_flavor, sizeof(DWORD));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->rsize, sizeof(DWORD));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->wsize, sizeof(DWORD));
+    if (status) goto out;
+
+    dprintf(1, "parsing NFS14_MOUNT: srv_name=%s root=%s sec_flavor=%s "
+        "rsize=%d wsize=%d\n", args->hostname, args->path, 
+        secflavorop2name(args->sec_flavor), args->rsize, args->wsize);
+out:
+    return status;
+}
+
+static int handle_mount(nfs41_upcall *upcall)
+{
+    int status;
+    mount_upcall_args *args = &upcall->args.mount;
+    nfs41_abs_path path;
+    multi_addr4 addrs;
+    nfs41_root *root;
+    nfs41_client *client;
+    nfs41_path_fh file;
+
+    // resolve hostname,port
+    status = nfs41_server_resolve(args->hostname, 2049, &addrs);
+    if (status) {
+        eprintf("nfs41_server_resolve() failed with %d\n", status);
+        goto out;
+    }
+
+    if (upcall->root_ref != INVALID_HANDLE_VALUE) {
+        /* use an existing root from a previous mount, but don't take an
+         * extra reference; we'll only get one UNMOUNT upcall for each root */
+        root = upcall->root_ref;
+    } else {
+        // create root
+        status = nfs41_root_create(args->hostname, args->sec_flavor,
+            args->wsize + WRITE_OVERHEAD, args->rsize + READ_OVERHEAD, &root);
+        if (status) {
+            eprintf("nfs41_root_create() failed %d\n", status);
+            goto out;
+        }
+        root->uid = upcall->uid;
+        root->gid = upcall->gid;
+    }
+
+    // find or create the client/session
+    status = nfs41_root_mount_addrs(root, &addrs, 0, 0, &client);
+    if (status) {
+        eprintf("nfs41_root_mount_addrs() failed with %d\n", status);
+        goto out_err;
+    }
+
+    // make a copy of the path for nfs41_lookup()
+    InitializeSRWLock(&path.lock);
+    if (FAILED(StringCchCopyA(path.path, NFS41_MAX_PATH_LEN, args->path))) {
+        status = ERROR_FILENAME_EXCED_RANGE;
+        goto out_err;
+    }
+    path.len = (unsigned short)strlen(path.path);
+
+    // look up the mount path, and fail if it doesn't exist
+    status = nfs41_lookup(root, client->session,
+        &path, NULL, &file, NULL, NULL);
+    if (status) {
+        eprintf("nfs41_lookup('%s') failed with %d\n", path.path, status);
+        status = ERROR_BAD_NETPATH;
+        goto out_err;
+    }
+
+    nfs41_superblock_fs_attributes(file.fh.superblock, &args->FsAttrs);
+
+    if (upcall->root_ref == INVALID_HANDLE_VALUE)
+        nfs41_root_ref(root);
+    upcall->root_ref = root;
+    args->lease_time = client->session->lease_time;
+out:
+    return status;
+
+out_err:
+    if (upcall->root_ref == INVALID_HANDLE_VALUE)
+        nfs41_root_deref(root);
+    goto out;
+}
+
+static int marshall_mount(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    mount_upcall_args *args = &upcall->args.mount;
+    int status;
+    dprintf(2, "NFS41_MOUNT: writing pointer to nfs41_root %p, version %d, "
+        "lease_time %d\n", upcall->root_ref, NFS41D_VERSION, args->lease_time);
+    status = safe_write(&buffer, length, &upcall->root_ref, sizeof(HANDLE));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &NFS41D_VERSION, sizeof(DWORD));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->lease_time, sizeof(DWORD));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->FsAttrs, sizeof(args->FsAttrs));
+out:
+    return status;
+}
+
+static void cancel_mount(IN nfs41_upcall *upcall)
+{
+    if (upcall->root_ref != INVALID_HANDLE_VALUE)
+        nfs41_root_deref(upcall->root_ref);
+}
+
+const nfs41_upcall_op nfs41_op_mount = {
+    parse_mount,
+    handle_mount,
+    marshall_mount,
+    cancel_mount
+};
+
+
+/* NFS41_UNMOUNT */
+static int parse_unmount(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) 
+{
+    dprintf(1, "parsing NFS41_UNMOUNT: root=%p\n", upcall->root_ref);
+    return ERROR_SUCCESS;
+}
+
+static int handle_unmount(nfs41_upcall *upcall)
+{
+    /* release the original reference from nfs41_root_create() */
+    nfs41_root_deref(upcall->root_ref);
+    return ERROR_SUCCESS;
+}
+
+const nfs41_upcall_op nfs41_op_unmount = {
+    parse_unmount,
+    handle_unmount
+};
diff --git a/reactos/base/services/nfsd/ms-nfs41-idmap.conf b/reactos/base/services/nfsd/ms-nfs41-idmap.conf
new file mode 100644 (file)
index 0000000..111a34b
--- /dev/null
@@ -0,0 +1,20 @@
+# ldap server information
+#ldap_hostname="localhost"
+#ldap_port="389"
+#ldap_version="3"
+#ldap_timeout="5"
+
+# ldap schema information
+#ldap_base="cn=localhost"
+
+#ldap_class_users="user"
+#ldap_class_groups="group"
+
+#ldap_attr_username="cn"
+#ldap_attr_groupname="cn"
+#ldap_attr_gssAuthName="gssAuthName"
+#ldap_attr_uidNumber="uidNumber"
+#ldap_attr_gidNumber="gidNumber"
+
+# caching configuration
+#cache_ttl="60"
diff --git a/reactos/base/services/nfsd/name_cache.c b/reactos/base/services/nfsd/name_cache.c
new file mode 100644 (file)
index 0000000..f8c7302
--- /dev/null
@@ -0,0 +1,1399 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <time.h>
+#include <assert.h>
+
+#include "nfs41_ops.h"
+#include "nfs41_compound.h"
+#include "name_cache.h"
+#include "util.h"
+#include "tree.h"
+#include "daemon_debug.h"
+
+
+/* dprintf levels for name cache logging */
+enum {
+    NCLVL1 = 2,
+    NCLVL2
+};
+
+
+#define NAME_CACHE_EXPIRATION 20 /* TODO: get from configuration */
+
+/* allow up to 256K of memory for name and attribute cache entries */
+#define NAME_CACHE_MAX_SIZE 262144
+
+/* negative lookup caching
+ *
+ * by caching lookups that result in NOENT, we can avoid sending subsequent
+ * lookups over the wire.  a name cache entry is negative when its attributes
+ * pointer is NULL.  negative entries are created by three functions:
+ * nfs41_name_cache_remove(), _insert() when called with NULL for the fh and
+ * attributes, and _rename() for the source entry */
+
+/* delegations and cache feedback
+ *
+ *   delegations provide a guarantee that no links or attributes will change
+ * without notice.  the name cache takes advantage of this by preventing
+ * delegated entries from being removed on NAME_CACHE_EXPIRATION, though
+ * they're still removed when a parent is invalidated.  the attribute cache
+ * holds an extra reference on delegated entries to prevent their removal
+ * entirely, until the delegation is returned.
+ *   this extra reference presents a problem when the number of delegations
+ * approaches the maximum number of attribute cache entries.  when there are
+ * not enough available entries to store the parent directories, every lookup
+ * results in a name cache miss, and cache performance degrades significantly.
+ *   the solution is to provide feedback via nfs41_name_cache_insert() when
+ * delegations reach a certain percent of the cache capacity.  the error code
+ * ERROR_TOO_MANY_OPEN_FILES, chosen arbitrarily for this case, instructs the
+ * caller to return an outstanding delegation before caching a new one.
+ */
+static __inline bool_t is_delegation(
+    IN enum open_delegation_type4 type)
+{
+    return type == OPEN_DELEGATE_READ || type == OPEN_DELEGATE_WRITE;
+}
+
+
+/* attribute cache */
+struct attr_cache_entry {
+    RB_ENTRY(attr_cache_entry) rbnode;
+    struct list_entry       free_entry;
+    uint64_t                change;
+    uint64_t                size;
+    uint64_t                fileid;
+    int64_t                 time_access_s;
+    int64_t                 time_create_s;
+    int64_t                 time_modify_s;
+    uint32_t                time_access_ns;
+    uint32_t                time_create_ns;
+    uint32_t                time_modify_ns;
+    uint32_t                numlinks;
+    unsigned                mode : 30;
+    unsigned                hidden : 1;
+    unsigned                system : 1;
+    unsigned                archive : 1;
+    time_t                  expiration;
+    unsigned                ref_count : 26;
+    unsigned                type : 4;
+    unsigned                invalidated : 1;
+    unsigned                delegated : 1;
+};
+#define ATTR_ENTRY_SIZE sizeof(struct attr_cache_entry)
+
+RB_HEAD(attr_tree, attr_cache_entry);
+
+struct attr_cache {
+    struct attr_tree        head;
+    struct attr_cache_entry *pool;
+    struct list_entry       free_entries;
+};
+
+int attr_cmp(struct attr_cache_entry *lhs, struct attr_cache_entry *rhs)
+{
+    return lhs->fileid < rhs->fileid ? -1 : lhs->fileid > rhs->fileid;
+}
+RB_GENERATE(attr_tree, attr_cache_entry, rbnode, attr_cmp)
+
+
+/* attr_cache_entry */
+#define attr_entry(pos) list_container(pos, struct attr_cache_entry, free_entry)
+
+static int attr_cache_entry_create(
+    IN struct attr_cache *cache,
+    IN uint64_t fileid,
+    OUT struct attr_cache_entry **entry_out)
+{
+    struct attr_cache_entry *entry;
+    int status = NO_ERROR;
+
+    /* get the next entry from free_entries and remove it */
+    if (list_empty(&cache->free_entries)) {
+        status = ERROR_OUTOFMEMORY;
+        goto out;
+    }
+    entry = attr_entry(cache->free_entries.next);
+    list_remove(&entry->free_entry);
+
+    entry->fileid = fileid;
+    entry->invalidated = FALSE;
+    entry->delegated = FALSE;
+    *entry_out = entry;
+out:
+    return status;
+}
+
+static __inline void attr_cache_entry_free(
+    IN struct attr_cache *cache,
+    IN struct attr_cache_entry *entry)
+{
+    dprintf(NCLVL1, "attr_cache_entry_free(%llu)\n", entry->fileid);
+    RB_REMOVE(attr_tree, &cache->head, entry);
+    /* add it back to free_entries */
+    list_add_tail(&cache->free_entries, &entry->free_entry);
+}
+
+static __inline void attr_cache_entry_ref(
+    IN struct attr_cache *cache,
+    IN struct attr_cache_entry *entry)
+{
+    const uint32_t previous = entry->ref_count++;
+    dprintf(NCLVL2, "attr_cache_entry_ref(%llu) %u -> %u\n",
+        entry->fileid, previous, entry->ref_count);
+}
+
+static __inline void attr_cache_entry_deref(
+    IN struct attr_cache *cache,
+    IN struct attr_cache_entry *entry)
+{
+    const uint32_t previous = entry->ref_count--;
+    dprintf(NCLVL2, "attr_cache_entry_deref(%llu) %u -> %u\n",
+        entry->fileid, previous, entry->ref_count);
+
+    if (entry->ref_count == 0)
+        attr_cache_entry_free(cache, entry);
+}
+
+static __inline int attr_cache_entry_expired(
+    IN const struct attr_cache_entry *entry)
+{
+    return entry->invalidated ||
+        (!entry->delegated && time(NULL) > entry->expiration);
+}
+
+/* attr_cache */
+static int attr_cache_init(
+    IN struct attr_cache *cache,
+    IN uint32_t max_entries)
+{
+    uint32_t i;
+    int status = NO_ERROR;
+
+    /* allocate a pool of entries */
+    cache->pool = calloc(max_entries, ATTR_ENTRY_SIZE);
+    if (cache->pool == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    /* initialize the list of free entries */
+    list_init(&cache->free_entries);
+    for (i = 0; i < max_entries; i++) {
+        list_init(&cache->pool[i].free_entry);
+        list_add_tail(&cache->free_entries, &cache->pool[i].free_entry);
+    }
+out:
+    return status;
+}
+
+static void attr_cache_free(
+    IN struct attr_cache *cache)
+{
+    /* free the pool */
+    free(cache->pool);
+    cache->pool = NULL;
+    list_init(&cache->free_entries);
+}
+
+static struct attr_cache_entry* attr_cache_search(
+    IN struct attr_cache *cache,
+    IN uint64_t fileid)
+{
+    /* find an entry that matches fileid */
+    struct attr_cache_entry tmp;
+    tmp.fileid = fileid;
+    return RB_FIND(attr_tree, &cache->head, &tmp);
+}
+
+static int attr_cache_insert(
+    IN struct attr_cache *cache,
+    IN struct attr_cache_entry *entry)
+{
+    int status = NO_ERROR;
+
+    dprintf(NCLVL2, "--> attr_cache_insert(%llu)\n", entry->fileid);
+
+    if (RB_INSERT(attr_tree, &cache->head, entry))
+        status = ERROR_FILE_EXISTS;
+
+    dprintf(NCLVL2, "<-- attr_cache_insert() returning %d\n", status);
+    return status;
+}
+
+static int attr_cache_find_or_create(
+    IN struct attr_cache *cache,
+    IN uint64_t fileid,
+    OUT struct attr_cache_entry **entry_out)
+{
+    struct attr_cache_entry *entry;
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "--> attr_cache_find_or_create(%llu)\n", fileid);
+
+    /* look for an existing entry */
+    entry = attr_cache_search(cache, fileid);
+    if (entry == NULL) {
+        /* create and insert */
+        status = attr_cache_entry_create(cache, fileid, &entry);
+        if (status)
+            goto out;
+
+        status = attr_cache_insert(cache, entry);
+        if (status)
+            goto out_err_free;
+    }
+
+    /* take a reference on success */
+    attr_cache_entry_ref(cache, entry);
+
+out:
+    *entry_out = entry;
+    dprintf(NCLVL1, "<-- attr_cache_find_or_create() returning %d\n",
+        status);
+    return status;
+
+out_err_free:
+    attr_cache_entry_free(cache, entry);
+    entry = NULL;
+    goto out;
+}
+
+static void attr_cache_update(
+    IN struct attr_cache_entry *entry,
+    IN const nfs41_file_info *info,
+    IN enum open_delegation_type4 delegation)
+{
+    /* update the attributes present in mask */
+    if (info->attrmask.count >= 1) {
+        if (info->attrmask.arr[0] & FATTR4_WORD0_TYPE)
+            entry->type = (unsigned char)(info->type & NFS_FTYPE_MASK);
+        if (info->attrmask.arr[0] & FATTR4_WORD0_CHANGE) {
+            entry->change = info->change;
+            /* revalidate whenever we get a change attribute */
+            entry->invalidated = 0;
+            entry->expiration = time(NULL) + NAME_CACHE_EXPIRATION;
+        }
+        if (info->attrmask.arr[0] & FATTR4_WORD0_SIZE)
+            entry->size = info->size;
+        if (info->attrmask.arr[0] & FATTR4_WORD0_HIDDEN)
+            entry->hidden = info->hidden;
+        if (info->attrmask.arr[0] & FATTR4_WORD0_ARCHIVE)
+            entry->archive = info->archive;
+    }
+    if (info->attrmask.count >= 2) {
+        if (info->attrmask.arr[1] & FATTR4_WORD1_MODE)
+            entry->mode = info->mode;
+        if (info->attrmask.arr[1] & FATTR4_WORD1_NUMLINKS)
+            entry->numlinks = info->numlinks;
+        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS) {
+            entry->time_access_s = info->time_access.seconds;
+            entry->time_access_ns = info->time_access.nseconds;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) {
+            entry->time_create_s = info->time_create.seconds;
+            entry->time_create_ns = info->time_create.nseconds;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY) {
+            entry->time_modify_s = info->time_modify.seconds;
+            entry->time_modify_ns = info->time_modify.nseconds;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_SYSTEM)
+            entry->system = info->system;
+    }
+
+    if (is_delegation(delegation))
+        entry->delegated = TRUE;
+}
+
+static void copy_attrs(
+    OUT nfs41_file_info *dst,
+    IN const struct attr_cache_entry *src)
+{
+    dst->change = src->change;
+    dst->size = src->size;
+    dst->time_access.seconds = src->time_access_s;
+    dst->time_access.nseconds = src->time_access_ns;
+    dst->time_create.seconds = src->time_create_s;
+    dst->time_create.nseconds = src->time_create_ns;
+    dst->time_modify.seconds = src->time_modify_s;
+    dst->time_modify.nseconds = src->time_modify_ns;
+    dst->type = src->type;
+    dst->numlinks = src->numlinks;
+    dst->mode = src->mode;
+    dst->fileid = src->fileid;
+    dst->hidden = src->hidden;
+    dst->system = src->system;
+    dst->archive = src->archive;
+
+    dst->attrmask.count = 2;
+    dst->attrmask.arr[0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE
+        | FATTR4_WORD0_SIZE | FATTR4_WORD0_FILEID 
+        | FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE;
+    dst->attrmask.arr[1] = FATTR4_WORD1_MODE
+        | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_TIME_ACCESS
+        | FATTR4_WORD1_TIME_CREATE | FATTR4_WORD1_TIME_MODIFY 
+        | FATTR4_WORD1_SYSTEM;
+}
+
+
+/* name cache */
+RB_HEAD(name_tree, name_cache_entry);
+struct name_cache_entry {
+    char                    component[NFS41_MAX_COMPONENT_LEN];
+    nfs41_fh                fh;
+    RB_ENTRY(name_cache_entry) rbnode;
+    struct name_tree        rbchildren;
+    struct attr_cache_entry *attributes;
+    struct name_cache_entry *parent;
+    struct list_entry       exp_entry;
+    time_t                  expiration;
+    unsigned short          component_len;
+};
+#define NAME_ENTRY_SIZE sizeof(struct name_cache_entry)
+
+int name_cmp(struct name_cache_entry *lhs, struct name_cache_entry *rhs)
+{
+    const int diff = rhs->component_len - lhs->component_len;
+    return diff ? diff : strncmp(lhs->component, rhs->component, lhs->component_len);
+}
+RB_GENERATE(name_tree, name_cache_entry, rbnode, name_cmp)
+
+struct nfs41_name_cache {
+    struct name_cache_entry *root;
+    struct name_cache_entry *pool;
+    struct attr_cache       attributes;
+    struct list_entry       exp_entries; /* list of entries by expiry */
+    uint32_t                expiration;
+    uint32_t                entries;
+    uint32_t                max_entries;
+    uint32_t                delegations;
+    uint32_t                max_delegations;
+    SRWLOCK                 lock;
+};
+
+
+/* internal name cache functions used by the public name cache interface;
+ * these functions expect the caller to hold a lock on the cache */
+
+#define name_entry(pos) list_container(pos, struct name_cache_entry, exp_entry)
+
+static __inline bool_t name_cache_enabled(
+    IN struct nfs41_name_cache *cache)
+{
+    return cache->expiration > 0;
+}
+
+static __inline void name_cache_entry_rename(
+    OUT struct name_cache_entry *entry,
+    IN const nfs41_component *component)
+{
+    StringCchCopyNA(entry->component, NFS41_MAX_COMPONENT_LEN,
+        component->name, component->len);
+    entry->component_len = component->len;
+}
+
+static __inline void name_cache_remove(
+    IN struct name_cache_entry *entry,
+    IN struct name_cache_entry *parent)
+{
+    RB_REMOVE(name_tree, &parent->rbchildren, entry);
+    entry->parent = NULL;
+}
+
+static void name_cache_unlink_children_recursive(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *parent);
+
+static __inline void name_cache_unlink(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *entry)
+{
+    /* remove the entry from the tree */
+    if (entry->parent)
+        name_cache_remove(entry, entry->parent);
+    else if (entry == cache->root)
+        cache->root = NULL;
+
+    /* unlink all of its children */
+    name_cache_unlink_children_recursive(cache, entry);
+    /* release the cached attributes */
+    if (entry->attributes) {
+        attr_cache_entry_deref(&cache->attributes, entry->attributes);
+        entry->attributes = NULL;
+    }
+    /* move it to the end of exp_entries for scavenging */
+    list_remove(&entry->exp_entry);
+    list_add_tail(&cache->exp_entries, &entry->exp_entry);
+}
+
+static void name_cache_unlink_children_recursive(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *parent)
+{
+    struct name_cache_entry *entry, *tmp;
+    RB_FOREACH_SAFE(entry, name_tree, &parent->rbchildren, tmp)
+        name_cache_unlink(cache, entry);
+}
+
+static int name_cache_entry_create(
+    IN struct nfs41_name_cache *cache,
+    IN const nfs41_component *component,
+    OUT struct name_cache_entry **entry_out)
+{
+    int status = NO_ERROR;
+    struct name_cache_entry *entry;
+
+    if (cache->entries >= cache->max_entries) {
+        /* scavenge the oldest entry */
+        if (list_empty(&cache->exp_entries)) {
+            status = ERROR_OUTOFMEMORY;
+            goto out;
+        }
+        entry = name_entry(cache->exp_entries.prev);
+        name_cache_unlink(cache, entry);
+
+        dprintf(NCLVL2, "name_cache_entry_create('%s') scavenged 0x%p\n",
+            component->name, entry);
+    } else {
+        /* take the next entry in the pool and add it to exp_entries */
+        entry = &cache->pool[cache->entries++];
+        list_init(&entry->exp_entry);
+        list_add_tail(&cache->exp_entries, &entry->exp_entry);
+    }
+
+    name_cache_entry_rename(entry, component);
+
+    *entry_out = entry;
+out:
+    return status;
+}
+
+static void name_cache_entry_accessed(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *entry)
+{
+    /* move the entry to the front of cache->exp_entries, then do
+     * the same for its parents, which are more costly to evict */
+    while (entry) {
+        /* if entry is delegated, it won't be in the list */
+        if (!list_empty(&entry->exp_entry)) {
+            list_remove(&entry->exp_entry);
+            list_add_head(&cache->exp_entries, &entry->exp_entry);
+        }
+        if (entry == entry->parent)
+            break;
+        entry = entry->parent;
+    }
+}
+
+static void name_cache_entry_updated(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *entry)
+{
+    /* update the expiration timer */
+    entry->expiration = time(NULL) + cache->expiration;
+    name_cache_entry_accessed(cache, entry);
+}
+
+static int name_cache_entry_update(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *entry,
+    IN OPTIONAL const nfs41_fh *fh,
+    IN OPTIONAL const nfs41_file_info *info,
+    IN enum open_delegation_type4 delegation)
+{
+    int status = NO_ERROR;
+
+    if (fh)
+        fh_copy(&entry->fh, fh);
+    else
+        entry->fh.len = 0;
+
+    if (info) {
+        if (entry->attributes == NULL) {
+            /* negative -> positive entry, create the attributes */
+            status = attr_cache_find_or_create(&cache->attributes,
+                info->fileid, &entry->attributes);
+            if (status)
+                goto out;
+        }
+
+        attr_cache_update(entry->attributes, info, delegation);
+
+        /* hold a reference as long as we have the delegation */
+        if (is_delegation(delegation)) {
+            attr_cache_entry_ref(&cache->attributes, entry->attributes);
+            cache->delegations++;
+        }
+
+        /* keep the entry from expiring */
+        if (entry->attributes->delegated)
+            list_remove(&entry->exp_entry);
+    } else if (entry->attributes) {
+        /* positive -> negative entry, deref the attributes */
+        attr_cache_entry_deref(&cache->attributes, entry->attributes);
+        entry->attributes = NULL;
+    }
+    name_cache_entry_updated(cache, entry);
+out:
+    return status;
+}
+
+static int name_cache_entry_changed(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *entry,
+    IN const change_info4 *cinfo)
+{
+    if (entry->attributes == NULL)
+        return FALSE;
+
+    if (cinfo->after == entry->attributes->change ||
+            (cinfo->atomic && cinfo->before == entry->attributes->change)) {
+        entry->attributes->change = cinfo->after;
+        name_cache_entry_updated(cache, entry);
+        dprintf(NCLVL1, "name_cache_entry_changed('%s') has not changed. "
+            "updated change=%llu\n", entry->component,
+            entry->attributes->change);
+        return FALSE;
+    } else {
+        dprintf(NCLVL1, "name_cache_entry_changed('%s') has changed: was %llu, "
+            "got before=%llu\n", entry->component,
+            entry->attributes->change, cinfo->before);
+        return TRUE;
+    }
+}
+
+static void name_cache_entry_invalidate(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *entry)
+{
+    dprintf(NCLVL1, "name_cache_entry_invalidate('%s')\n", entry->component);
+
+    if (entry->attributes) {
+        /* flag attributes so that entry_invis() will return true
+         * if another entry attempts to use them */
+        entry->attributes->invalidated = 1;
+    }
+    name_cache_unlink(cache, entry);
+}
+
+static struct name_cache_entry* name_cache_search(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *parent,
+    IN const nfs41_component *component)
+{
+    struct name_cache_entry tmp, *entry;
+
+    dprintf(NCLVL2, "--> name_cache_search('%.*s' under '%s')\n",
+        component->len, component->name, parent->component);
+
+    StringCchCopyNA(tmp.component, NFS41_MAX_COMPONENT_LEN,
+        component->name, component->len);
+    tmp.component_len = component->len;
+
+    entry = RB_FIND(name_tree, &parent->rbchildren, &tmp);
+    if (entry)
+        dprintf(NCLVL2, "<-- name_cache_search() "
+            "found existing entry 0x%p\n", entry);
+    else
+        dprintf(NCLVL2, "<-- name_cache_search() returning NULL\n");
+    return entry;
+}
+
+static int entry_invis(
+    IN struct name_cache_entry *entry,
+    OUT OPTIONAL bool_t *is_negative)
+{
+    /* name entry timer expired? */
+    if (!list_empty(&entry->exp_entry) && time(NULL) > entry->expiration) {
+        dprintf(NCLVL2, "name_entry_expired('%s')\n", entry->component);
+        return 1;
+    }
+    /* negative lookup entry? */
+    if (entry->attributes == NULL) {
+        if (is_negative) *is_negative = 1;
+        dprintf(NCLVL2, "name_entry_negative('%s')\n", entry->component);
+        return 1;
+    }
+    /* attribute entry expired? */
+    if (attr_cache_entry_expired(entry->attributes)) {
+        dprintf(NCLVL2, "attr_entry_expired(%llu)\n",
+            entry->attributes->fileid);
+        return 1;
+    }
+    return 0;
+}
+
+static int name_cache_lookup(
+    IN struct nfs41_name_cache *cache,
+    IN bool_t skip_invis,
+    IN const char *path,
+    IN const char *path_end,
+    OUT OPTIONAL const char **remaining_path_out,
+    OUT OPTIONAL struct name_cache_entry **parent_out,
+    OUT OPTIONAL struct name_cache_entry **target_out,
+    OUT OPTIONAL bool_t *is_negative)
+{
+    struct name_cache_entry *parent, *target;
+    nfs41_component component;
+    const char *path_pos;
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "--> name_cache_lookup('%s')\n", path);
+
+    parent = NULL;
+    target = cache->root;
+    component.name = path_pos = path;
+
+    if (target == NULL || (skip_invis && entry_invis(target, is_negative))) {
+        target = NULL;
+        status = ERROR_PATH_NOT_FOUND;
+        goto out;
+    }
+
+    while (next_component(path_pos, path_end, &component)) {
+        parent = target;
+        target = name_cache_search(cache, parent, &component);
+        path_pos = component.name + component.len;
+        if (target == NULL || (skip_invis && entry_invis(target, is_negative))) {
+            target = NULL;
+            if (is_last_component(component.name, path_end))
+                status = ERROR_FILE_NOT_FOUND;
+            else
+                status = ERROR_PATH_NOT_FOUND;
+            break;
+        }
+    }
+out:
+    if (remaining_path_out) *remaining_path_out = component.name;
+    if (parent_out) *parent_out = parent;
+    if (target_out) *target_out = target;
+    dprintf(NCLVL1, "<-- name_cache_lookup() returning %d\n", status);
+    return status;
+}
+
+static int name_cache_insert(
+    IN struct name_cache_entry *entry,
+    IN struct name_cache_entry *parent)
+{
+    int status = NO_ERROR;
+
+    dprintf(NCLVL2, "--> name_cache_insert('%s')\n", entry->component);
+
+    if (RB_INSERT(name_tree, &parent->rbchildren, entry))
+        status = ERROR_FILE_EXISTS;
+    entry->parent = parent;
+
+    dprintf(NCLVL2, "<-- name_cache_insert() returning %u\n", status);
+    return status;
+}
+
+static int name_cache_find_or_create(
+    IN struct nfs41_name_cache *cache,
+    IN struct name_cache_entry *parent,
+    IN const nfs41_component *component,
+    OUT struct name_cache_entry **target_out)
+{
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "--> name_cache_find_or_create('%.*s' under '%s')\n",
+        component->len, component->name, parent->component);
+
+    *target_out = name_cache_search(cache, parent, component);
+    if (*target_out)
+        goto out;
+
+    status = name_cache_entry_create(cache, component, target_out);
+    if (status)
+        goto out;
+
+    status = name_cache_insert(*target_out, parent);
+    if (status)
+        goto out_err;
+
+out:
+    dprintf(NCLVL1, "<-- name_cache_find_or_create() returning %d\n",
+        status);
+    return status;
+
+out_err:
+    *target_out = NULL;
+    goto out;
+}
+
+
+/* public name cache interface, declared in name_cache.h */
+
+/* assuming no hard links, calculate how many entries will fit in the cache */
+#define SIZE_PER_ENTRY (ATTR_ENTRY_SIZE + NAME_ENTRY_SIZE)
+#define NAME_CACHE_MAX_ENTRIES (NAME_CACHE_MAX_SIZE / SIZE_PER_ENTRY)
+
+int nfs41_name_cache_create(
+    OUT struct nfs41_name_cache **cache_out)
+{
+    struct nfs41_name_cache *cache;
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "nfs41_name_cache_create()\n");
+
+    /* allocate the cache */
+    cache = calloc(1, sizeof(struct nfs41_name_cache));
+    if (cache == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    list_init(&cache->exp_entries);
+    cache->expiration = NAME_CACHE_EXPIRATION;
+    cache->max_entries = NAME_CACHE_MAX_ENTRIES;
+    cache->max_delegations = NAME_CACHE_MAX_ENTRIES / 2;
+    InitializeSRWLock(&cache->lock);
+
+    /* allocate a pool of entries */
+    cache->pool = calloc(cache->max_entries, NAME_ENTRY_SIZE);
+    if (cache->pool == NULL) {
+        status = GetLastError();
+        goto out_err_cache;
+    }
+
+    /* initialize the attribute cache */
+    status = attr_cache_init(&cache->attributes, cache->max_entries);
+    if (status)
+        goto out_err_pool;
+
+    *cache_out = cache;
+out:
+    return status;
+
+out_err_pool:
+    free(cache->pool);
+out_err_cache:
+    free(cache);
+    goto out;
+}
+
+int nfs41_name_cache_free(
+    IN struct nfs41_name_cache **cache_out)
+{
+    struct nfs41_name_cache *cache = *cache_out;
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "nfs41_name_cache_free()\n");
+
+    /* free the attribute cache */
+    attr_cache_free(&cache->attributes);
+
+    /* free the name entry pool */
+    free(cache->pool);
+    free(cache);
+    *cache_out = NULL;
+    return status;
+}
+
+static __inline void copy_fh(
+    OUT nfs41_fh *dst,
+    IN OPTIONAL const struct name_cache_entry *src)
+{
+    if (src)
+        fh_copy(dst, &src->fh);
+    else
+        dst->len = 0;
+}
+
+int nfs41_name_cache_lookup(
+    IN struct nfs41_name_cache *cache,
+    IN const char *path,
+    IN const char *path_end,
+    OUT OPTIONAL const char **remaining_path_out,
+    OUT OPTIONAL nfs41_fh *parent_out,
+    OUT OPTIONAL nfs41_fh *target_out,
+    OUT OPTIONAL nfs41_file_info *info_out,
+    OUT OPTIONAL bool_t *is_negative)
+{
+    struct name_cache_entry *parent, *target;
+    const char *path_pos = path;
+    int status;
+
+    AcquireSRWLockShared(&cache->lock);
+
+    if (!name_cache_enabled(cache)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out_unlock;
+    }
+
+    status = name_cache_lookup(cache, 1, path, path_end,
+        &path_pos, &parent, &target, is_negative);
+
+    if (parent_out) copy_fh(parent_out, parent);
+    if (target_out) copy_fh(target_out, target);
+    if (info_out && target && target->attributes)
+        copy_attrs(info_out, target->attributes);
+
+out_unlock:
+    ReleaseSRWLockShared(&cache->lock);
+    if (remaining_path_out) *remaining_path_out = path_pos;
+    return status;
+}
+
+int nfs41_attr_cache_lookup(
+    IN struct nfs41_name_cache *cache,
+    IN uint64_t fileid,
+    OUT nfs41_file_info *info_out)
+{
+    struct attr_cache_entry *entry;
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "--> nfs41_attr_cache_lookup(%llu)\n", fileid);
+
+    AcquireSRWLockShared(&cache->lock);
+
+    if (!name_cache_enabled(cache)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out_unlock;
+    }
+
+    entry = attr_cache_search(&cache->attributes, fileid);
+    if (entry == NULL || attr_cache_entry_expired(entry)) {
+        status = ERROR_FILE_NOT_FOUND;
+        goto out_unlock;
+    }
+
+    copy_attrs(info_out, entry);
+
+out_unlock:
+    ReleaseSRWLockShared(&cache->lock);
+
+    dprintf(NCLVL1, "<-- nfs41_attr_cache_lookup() returning %d\n", status);
+    return status;
+}
+
+int nfs41_attr_cache_update(
+    IN struct nfs41_name_cache *cache,
+    IN uint64_t fileid,
+    IN const nfs41_file_info *info)
+{
+    struct attr_cache_entry *entry;
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "--> nfs41_attr_cache_update(%llu)\n", fileid);
+
+    AcquireSRWLockExclusive(&cache->lock);
+
+    if (!name_cache_enabled(cache)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out_unlock;
+    }
+
+    entry = attr_cache_search(&cache->attributes, fileid);
+    if (entry == NULL) {
+        status = ERROR_FILE_NOT_FOUND;
+        goto out_unlock;
+    }
+
+    attr_cache_update(entry, info, OPEN_DELEGATE_NONE);
+
+out_unlock:
+    ReleaseSRWLockExclusive(&cache->lock);
+
+    dprintf(NCLVL1, "<-- nfs41_attr_cache_update() returning %d\n", status);
+    return status;
+}
+
+int nfs41_name_cache_insert(
+    IN struct nfs41_name_cache *cache,
+    IN const char *path,
+    IN const nfs41_component *name,
+    IN OPTIONAL const nfs41_fh *fh,
+    IN OPTIONAL const nfs41_file_info *info,
+    IN OPTIONAL const change_info4 *cinfo,
+    IN enum open_delegation_type4 delegation)
+{
+    struct name_cache_entry *parent, *target;
+    int status;
+
+    dprintf(NCLVL1, "--> nfs41_name_cache_insert('%.*s')\n",
+        name->name + name->len - path, path);
+
+    AcquireSRWLockExclusive(&cache->lock);
+
+    if (!name_cache_enabled(cache)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out_unlock;
+    }
+
+    /* limit the number of delegations to prevent attr cache starvation */
+    if (is_delegation(delegation) &&
+        cache->delegations >= cache->max_delegations) {
+        status = ERROR_TOO_MANY_OPEN_FILES;
+        goto out_unlock;
+    }
+
+    /* an empty path or component implies the root entry */
+    if (path == NULL || name == NULL || name->len == 0) {
+        /* create the root entry if it doesn't exist */
+        if (cache->root == NULL) {
+            const nfs41_component name = { "ROOT", 4 };
+            status = name_cache_entry_create(cache, &name, &cache->root);
+            if (status)
+                goto out_err_deleg;
+        }
+        target = cache->root;
+    } else {
+        /* find/create an entry under its parent */
+        status = name_cache_lookup(cache, 0, path,
+            name->name, NULL, NULL, &parent, NULL);
+        if (status)
+            goto out_err_deleg;
+
+        if (cinfo && name_cache_entry_changed(cache, parent, cinfo)) {
+            name_cache_entry_invalidate(cache, parent);
+            goto out_err_deleg;
+        }
+
+        status = name_cache_find_or_create(cache, parent, name, &target);
+        if (status)
+            goto out_err_deleg;
+    }
+
+    /* pass in the new fh/attributes */
+    status = name_cache_entry_update(cache, target, fh, info, delegation);
+    if (status)
+        goto out_err_update;
+
+out_unlock:
+    ReleaseSRWLockExclusive(&cache->lock);
+
+    dprintf(NCLVL1, "<-- nfs41_name_cache_insert() returning %d\n",
+        status);
+    return status;
+
+out_err_update:
+    /* a failure in name_cache_entry_update() leaves a negative entry
+     * where there shouldn't be one; remove it from the cache */
+    name_cache_entry_invalidate(cache, target);
+
+out_err_deleg:
+    if (is_delegation(delegation)) {
+        /* we still need a reference to the attributes for the delegation */
+        struct attr_cache_entry *attributes;
+        status = attr_cache_find_or_create(&cache->attributes,
+            info->fileid, &attributes);
+        if (status == NO_ERROR) {
+            attr_cache_update(attributes, info, delegation);
+            cache->delegations++;
+        }
+        else
+            status = ERROR_TOO_MANY_OPEN_FILES;
+    }
+    goto out_unlock;
+}
+
+int nfs41_name_cache_delegreturn(
+    IN struct nfs41_name_cache *cache,
+    IN uint64_t fileid,
+    IN const char *path,
+    IN const nfs41_component *name)
+{
+    struct name_cache_entry *parent, *target;
+    struct attr_cache_entry *attributes;
+    int status;
+
+    dprintf(NCLVL1, "--> nfs41_name_cache_delegreturn(%llu, '%s')\n",
+        fileid, path);
+
+    AcquireSRWLockExclusive(&cache->lock);
+
+    if (!name_cache_enabled(cache)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out_unlock;
+    }
+
+    status = name_cache_lookup(cache, 0, path,
+        name->name + name->len, NULL, &parent, &target, NULL);
+    if (status == NO_ERROR) {
+        /* put the name cache entry back on the exp_entries list */
+        list_add_head(&cache->exp_entries, &target->exp_entry);
+        name_cache_entry_updated(cache, target);
+
+        attributes = target->attributes;
+    } else {
+        /* should still have an attr cache entry */
+        attributes = attr_cache_search(&cache->attributes, fileid);
+    }
+
+    if (attributes == NULL) {
+        status = ERROR_FILE_NOT_FOUND;
+        goto out_unlock;
+    }
+
+    /* release the reference from name_cache_entry_update() */
+    if (attributes->delegated) {
+        attributes->delegated = FALSE;
+        attr_cache_entry_deref(&cache->attributes, attributes);
+        assert(cache->delegations > 0);
+        cache->delegations--;
+    }
+    status = NO_ERROR;
+
+out_unlock:
+    ReleaseSRWLockExclusive(&cache->lock);
+
+    dprintf(NCLVL1, "<-- nfs41_name_cache_delegreturn() returning %d\n", status);
+    return status;
+}
+
+int nfs41_name_cache_remove(
+    IN struct nfs41_name_cache *cache,
+    IN const char *path,
+    IN const nfs41_component *name,
+    IN uint64_t fileid,
+    IN const change_info4 *cinfo)
+{
+    struct name_cache_entry *parent, *target;
+    struct attr_cache_entry *attributes = NULL;
+    int status;
+
+    dprintf(NCLVL1, "--> nfs41_name_cache_remove('%s')\n", path);
+
+    AcquireSRWLockExclusive(&cache->lock);
+
+    if (!name_cache_enabled(cache)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out_unlock;
+    }
+
+    status = name_cache_lookup(cache, 0, path,
+        name->name + name->len, NULL, &parent, &target, NULL);
+    if (status == ERROR_PATH_NOT_FOUND)
+        goto out_attributes;
+
+    if (cinfo && name_cache_entry_changed(cache, parent, cinfo)) {
+        name_cache_entry_invalidate(cache, parent);
+        goto out_attributes;
+    }
+
+    if (status == ERROR_FILE_NOT_FOUND)
+        goto out_attributes;
+
+    if (target->attributes)
+        target->attributes->numlinks--;
+
+    /* make this a negative entry and unlink children */
+    name_cache_entry_update(cache, target, NULL, NULL, OPEN_DELEGATE_NONE);
+    name_cache_unlink_children_recursive(cache, target);
+
+out_unlock:
+    ReleaseSRWLockExclusive(&cache->lock);
+
+    dprintf(NCLVL1, "<-- nfs41_name_cache_remove() returning %d\n", status);
+    return status;
+
+out_attributes:
+    /* in the presence of other links, we need to update numlinks
+     * regardless of a failure to find the target entry */
+    dprintf(NCLVL1, "nfs41_name_cache_remove: need to find attributes for %s\n", path);
+    attributes = attr_cache_search(&cache->attributes, fileid);
+    if (attributes)
+        attributes->numlinks--;
+    goto out_unlock;
+}
+
+int nfs41_name_cache_rename(
+    IN struct nfs41_name_cache *cache,
+    IN const char *src_path,
+    IN const nfs41_component *src_name,
+    IN const change_info4 *src_cinfo,
+    IN const char *dst_path,
+    IN const nfs41_component *dst_name,
+    IN const change_info4 *dst_cinfo)
+{
+    struct name_cache_entry *src_parent, *src;
+    struct name_cache_entry *dst_parent;
+    int status = NO_ERROR;
+
+    dprintf(NCLVL1, "--> nfs41_name_cache_rename('%s' to '%s')\n",
+        src_path, dst_path);
+
+    AcquireSRWLockExclusive(&cache->lock);
+
+    if (!name_cache_enabled(cache)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out_unlock;
+    }
+
+    /* look up dst_parent */
+    status = name_cache_lookup(cache, 0, dst_path,
+        dst_name->name, NULL, NULL, &dst_parent, NULL);
+    /* we can't create the dst entry without a parent */
+    if (status || dst_parent->attributes == NULL) {
+        /* if src exists, make it negative */
+        dprintf(NCLVL1, "nfs41_name_cache_rename: adding negative cache "
+            "entry for %.*s\n", src_name->len, src_name->name);
+        status = name_cache_lookup(cache, 0, src_path,
+            src_name->name + src_name->len, NULL, NULL, &src, NULL);
+        if (status == NO_ERROR) {
+            name_cache_entry_update(cache, src, NULL, NULL, OPEN_DELEGATE_NONE);
+            name_cache_unlink_children_recursive(cache, src);
+        }
+        status = ERROR_PATH_NOT_FOUND;
+        goto out_unlock;
+    }
+
+    /* look up src_parent and src */
+    status = name_cache_lookup(cache, 0, src_path,
+        src_name->name + src_name->len, NULL, &src_parent, &src, NULL);
+    /* we can't create the dst entry without valid attributes */
+    if (status || src->attributes == NULL) {
+        /* remove dst if it exists */
+        struct name_cache_entry *dst;
+        dprintf(NCLVL1, "nfs41_name_cache_rename: removing negative cache "
+            "entry for %.*s\n", dst_name->len, dst_name->name);
+        dst = name_cache_search(cache, dst_parent, dst_name);
+        if (dst) name_cache_unlink(cache, dst);
+        goto out_unlock;
+    }
+
+    if (name_cache_entry_changed(cache, dst_parent, dst_cinfo)) {
+        name_cache_entry_invalidate(cache, dst_parent);
+        /* if dst_parent and src_parent are both gone,
+         * we no longer have an entry to rename */
+        if (dst_parent == src_parent)
+            goto out_unlock;
+    } else {
+        struct name_cache_entry *existing;
+        existing = name_cache_search(cache, dst_parent, dst_name);
+        if (existing) {
+            if (existing == src)
+                goto out_unlock;
+            /* remove the existing entry, but don't unlink it yet;
+             * we may reuse it for a negative entry */
+            name_cache_remove(existing, dst_parent);
+        }
+
+        /* move the src entry under dst_parent */
+        name_cache_remove(src, src_parent);
+        name_cache_entry_rename(src, dst_name);
+        name_cache_insert(src, dst_parent);
+
+        if (existing) {
+            /* recycle 'existing' as the negative entry 'src' */
+            name_cache_entry_rename(existing, src_name);
+            name_cache_insert(existing, src_parent);
+        }
+        src = existing;
+    }
+
+    if (name_cache_entry_changed(cache, src_parent, src_cinfo)) {
+        name_cache_entry_invalidate(cache, src_parent);
+        goto out_unlock;
+    }
+
+    /* leave a negative entry where the file used to be */
+    if (src == NULL) {
+        /* src was moved, create a new entry in its place */
+        status = name_cache_find_or_create(cache, src_parent, src_name, &src);
+        if (status)
+            goto out_unlock;
+    }
+    name_cache_entry_update(cache, src, NULL, NULL, OPEN_DELEGATE_NONE);
+    name_cache_unlink_children_recursive(cache, src);
+
+out_unlock:
+    ReleaseSRWLockExclusive(&cache->lock);
+
+    dprintf(NCLVL1, "<-- nfs41_name_cache_rename() returning %d\n", status);
+    return status;
+}
+
+/* nfs41_name_cache_resolve_fh() */
+
+#define MAX_PUTFH_PER_COMPOUND 16
+
+static bool_t get_path_fhs(
+    IN struct nfs41_name_cache *cache,
+    IN nfs41_abs_path *path,
+    IN OUT const char **path_pos,
+    IN uint32_t max_components,
+    OUT nfs41_path_fh *files,
+    OUT uint32_t *count)
+{
+    struct name_cache_entry *target;
+    const char *path_end = path->path + path->len;
+    nfs41_component *name;
+    uint32_t i;
+    int status;
+
+    *count = 0;
+
+    AcquireSRWLockShared(&cache->lock);
+
+    /* look up the parent of the first component */
+    status = name_cache_lookup(cache, 1, path->path,
+        *path_pos, NULL, NULL, &target, NULL);
+    if (status)
+        goto out_unlock;
+
+    for (i = 0; i < max_components; i++) {
+        files[i].path = path;
+        name = &files[i].name;
+
+        if (!next_component(*path_pos, path_end, name))
+            break;
+        *path_pos = name->name + name->len;
+
+        target = name_cache_search(cache, target, name);
+        if (target == NULL || entry_invis(target, NULL)) {
+            if (is_last_component(name->name, path_end))
+                status = ERROR_FILE_NOT_FOUND;
+            else
+                status = ERROR_PATH_NOT_FOUND;
+            goto out_unlock;
+        }
+        /* make copies for use outside of cache->lock */
+        fh_copy(&files[i].fh, &target->fh);
+        (*count)++;
+    }
+
+out_unlock:
+    ReleaseSRWLockShared(&cache->lock);
+    return *count && status == 0;
+}
+
+static int rpc_array_putfh(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *files,
+    IN uint32_t count,
+    OUT uint32_t *valid_out)
+{
+    nfs41_compound compound;
+    nfs_argop4 argops[1+MAX_PUTFH_PER_COMPOUND];
+    nfs_resop4 resops[1+MAX_PUTFH_PER_COMPOUND];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res = { 0 };
+    nfs41_putfh_args putfh_args[MAX_PUTFH_PER_COMPOUND];
+    nfs41_putfh_res putfh_res[MAX_PUTFH_PER_COMPOUND] = { 0 };
+    uint32_t i;
+    int status;
+
+    *valid_out = 0;
+
+    compound_init(&compound, argops, resops, "array_putfh");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    for (i = 0; i < count; i++){
+        compound_add_op(&compound, OP_PUTFH, &putfh_args[i], &putfh_res[i]);
+        putfh_args[i].file = &files[i];
+        putfh_args[i].in_recovery = 1;
+    }
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status) goto out;
+
+    status = sequence_res.sr_status;
+    if (status) goto out;
+
+    for (i = 0; i < count; i++) {
+        status = putfh_res[i].status;
+        if (status) break;
+    }
+    *valid_out = i;
+out:
+    return status;
+}
+
+static int delete_stale_component(
+    IN struct nfs41_name_cache *cache,
+    IN nfs41_session *session,
+    IN const nfs41_abs_path *path,
+    IN const nfs41_component *component)
+{
+    struct name_cache_entry *target;
+    int status;
+
+    dprintf(NCLVL1, "--> delete_stale_component('%s')\n",
+        component->name);
+
+    AcquireSRWLockExclusive(&cache->lock);
+
+    status = name_cache_lookup(cache, 0, path->path,
+        component->name + component->len, NULL, NULL, &target, NULL);
+    if (status == NO_ERROR)
+        name_cache_unlink(cache, target);
+
+    ReleaseSRWLockExclusive(&cache->lock);
+
+    dprintf(NCLVL1, "<-- delete_stale_component() returning %d\n", status);
+    return status;
+}
+
+static __inline uint32_t max_putfh_components(
+    IN const nfs41_session *session)
+{
+    const uint32_t comps = session->fore_chan_attrs.ca_maxoperations - 1;
+    return min(comps, MAX_PUTFH_PER_COMPOUND);
+}
+
+int nfs41_name_cache_remove_stale(
+    IN struct nfs41_name_cache *cache,
+    IN nfs41_session *session,
+    IN nfs41_abs_path *path)
+{
+    nfs41_path_fh files[MAX_PUTFH_PER_COMPOUND];
+    const char *path_pos = path->path;
+    const char* const path_end = path->path + path->len;
+    const uint32_t max_components = max_putfh_components(session);
+    uint32_t count, index;
+    int status = NO_ERROR;
+
+    AcquireSRWLockShared(&cache->lock);
+
+    /* if there's no cache, don't check any components */
+    if (!name_cache_enabled(cache))
+        path_pos = path_end;
+
+    ReleaseSRWLockShared(&cache->lock);
+
+    /* hold a lock on the path to protect against rename */
+    AcquireSRWLockShared(&path->lock);
+
+    while (get_path_fhs(cache, path, &path_pos, max_components, files, &count)) {
+        status = rpc_array_putfh(session, files, count, &index);
+
+        if (status == NFS4ERR_STALE || status == NFS4ERR_FHEXPIRED) {
+            status = delete_stale_component(cache,
+                session, path, &files[index].name);
+            break;
+        }
+        if (status) {
+            status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND);
+            break;
+        }
+    }
+
+    ReleaseSRWLockShared(&path->lock);
+
+    return status;
+}
diff --git a/reactos/base/services/nfsd/name_cache.h b/reactos/base/services/nfsd/name_cache.h
new file mode 100644 (file)
index 0000000..ffab173
--- /dev/null
@@ -0,0 +1,106 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_NAME_CACHE_H__
+#define __NFS41_DAEMON_NAME_CACHE_H__
+
+#include "nfs41.h"
+
+
+static __inline struct nfs41_name_cache* client_name_cache(
+    IN nfs41_client *client)
+{
+    return client_server(client)->name_cache;
+}
+
+static __inline struct nfs41_name_cache* session_name_cache(
+    IN nfs41_session *session)
+{
+    return client_name_cache(session->client);
+}
+
+
+/* attribute cache */
+int nfs41_attr_cache_lookup(
+    IN struct nfs41_name_cache *cache,
+    IN uint64_t fileid,
+    OUT nfs41_file_info *info_out);
+
+int nfs41_attr_cache_update(
+    IN struct nfs41_name_cache *cache,
+    IN uint64_t fileid,
+    IN const nfs41_file_info *info);
+
+
+/* name cache */
+int nfs41_name_cache_create(
+    OUT struct nfs41_name_cache **cache_out);
+
+int nfs41_name_cache_free(
+    IN OUT struct nfs41_name_cache **cache_out);
+
+int nfs41_name_cache_lookup(
+    IN struct nfs41_name_cache *cache,
+    IN const char *path,
+    IN const char *path_end,
+    OUT OPTIONAL const char **remaining_path_out,
+    OUT OPTIONAL nfs41_fh *parent_out,
+    OUT OPTIONAL nfs41_fh *target_out,
+    OUT OPTIONAL nfs41_file_info *info_out,
+    OUT OPTIONAL bool_t *is_negative);
+
+int nfs41_name_cache_insert(
+    IN struct nfs41_name_cache *cache,
+    IN const char *path,
+    IN const nfs41_component *name,
+    IN OPTIONAL const nfs41_fh *fh,
+    IN OPTIONAL const nfs41_file_info *info,
+    IN OPTIONAL const change_info4 *cinfo,
+    IN enum open_delegation_type4 delegation);
+
+int nfs41_name_cache_delegreturn(
+    IN struct nfs41_name_cache *cache,
+    IN uint64_t fileid,
+    IN const char *path,
+    IN const nfs41_component *name);
+
+int nfs41_name_cache_remove(
+    IN struct nfs41_name_cache *cache,
+    IN const char *path,
+    IN const nfs41_component *name,
+    IN uint64_t fileid,
+    IN const change_info4 *cinfo);
+
+int nfs41_name_cache_rename(
+    IN struct nfs41_name_cache *cache,
+    IN const char *src_path,
+    IN const nfs41_component *src_name,
+    IN const change_info4 *src_cinfo,
+    IN const char *dst_path,
+    IN const nfs41_component *dst_name,
+    IN const change_info4 *dst_cinfo);
+
+int nfs41_name_cache_remove_stale(
+    IN struct nfs41_name_cache *cache,
+    IN nfs41_session *session,
+    IN nfs41_abs_path *path);
+
+#endif /* !__NFS41_DAEMON_NAME_CACHE_H__ */
diff --git a/reactos/base/services/nfsd/namespace.c b/reactos/base/services/nfsd/namespace.c
new file mode 100644 (file)
index 0000000..c593b41
--- /dev/null
@@ -0,0 +1,478 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+
+#include "nfs41_ops.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+#define NSLVL 2 /* dprintf level for namespace logging */
+
+
+#define client_entry(pos) list_container(pos, nfs41_client, root_entry)
+
+
+/* nfs41_root */
+int nfs41_root_create(
+    IN const char *name,
+    IN uint32_t sec_flavor,
+    IN uint32_t wsize,
+    IN uint32_t rsize,
+    OUT nfs41_root **root_out)
+{
+    int status = NO_ERROR;
+    nfs41_root *root;
+
+    dprintf(NSLVL, "--> nfs41_root_create()\n");
+
+    root = calloc(1, sizeof(nfs41_root));
+    if (root == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    list_init(&root->clients);
+    root->wsize = wsize;
+    root->rsize = rsize;
+    InitializeCriticalSection(&root->lock);
+    root->ref_count = 1;
+    root->sec_flavor = sec_flavor;
+
+    /* generate a unique client_owner */
+    status = nfs41_client_owner(name, sec_flavor, &root->client_owner);
+    if (status) {
+        eprintf("nfs41_client_owner() failed with %d\n", status);
+        free(root);
+        goto out;
+    }
+
+    *root_out = root;
+out:
+    dprintf(NSLVL, "<-- nfs41_root_create() returning %d\n", status);
+    return status;
+}
+
+static void root_free(
+    IN nfs41_root *root)
+{
+    struct list_entry *entry, *tmp;
+
+    dprintf(NSLVL, "--> nfs41_root_free()\n");
+
+    /* free clients */
+    list_for_each_tmp(entry, tmp, &root->clients)
+        nfs41_client_free(client_entry(entry));
+    DeleteCriticalSection(&root->lock);
+    free(root);
+
+    dprintf(NSLVL, "<-- nfs41_root_free()\n");
+}
+
+void nfs41_root_ref(
+    IN nfs41_root *root)
+{
+    const LONG count = InterlockedIncrement(&root->ref_count);
+
+    dprintf(NSLVL, "nfs41_root_ref() count %d\n", count);
+}
+
+void nfs41_root_deref(
+    IN nfs41_root *root)
+{
+    const LONG count = InterlockedDecrement(&root->ref_count);
+
+    dprintf(NSLVL, "nfs41_root_deref() count %d\n", count);
+    if (count == 0)
+        root_free(root);
+}
+
+
+/* root_client_find_addrs() */
+struct cl_addr_info {
+    const multi_addr4       *addrs;
+    uint32_t                roles;
+};
+
+static int cl_addr_compare(
+    IN const struct list_entry *entry,
+    IN const void *value)
+{
+    nfs41_client *client = client_entry(entry);
+    const struct cl_addr_info *info = (const struct cl_addr_info*)value;
+    uint32_t i, roles;
+
+    /* match any of the desired roles */
+    AcquireSRWLockShared(&client->exid_lock);
+    roles = info->roles & client->roles;
+    ReleaseSRWLockShared(&client->exid_lock);
+
+    if (roles == 0)
+        return ERROR_FILE_NOT_FOUND;
+
+    /* match any address in 'addrs' with any address in client->rpc->addrs */
+    for (i = 0; i < info->addrs->count; i++)
+        if (multi_addr_find(&client->rpc->addrs, &info->addrs->arr[i], NULL))
+            return NO_ERROR;
+
+    return ERROR_FILE_NOT_FOUND;
+}
+
+static int root_client_find_addrs(
+    IN nfs41_root *root,
+    IN const multi_addr4 *addrs,
+    IN bool_t is_data,
+    OUT nfs41_client **client_out)
+{
+    struct cl_addr_info info;
+    struct list_entry *entry;
+    int status;
+
+    dprintf(NSLVL, "--> root_client_find_addrs()\n");
+
+    info.addrs = addrs;
+    info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS;
+
+    entry = list_search(&root->clients, &info, cl_addr_compare);
+    if (entry) {
+        *client_out = client_entry(entry);
+        status = NO_ERROR;
+        dprintf(NSLVL, "<-- root_client_find_addrs() returning 0x%p\n",
+            *client_out);
+    } else {
+        status = ERROR_FILE_NOT_FOUND;
+        dprintf(NSLVL, "<-- root_client_find_addrs() failed with %d\n",
+            status);
+    }
+    return status;
+}
+
+/* root_client_find() */
+struct cl_exid_info {
+    const nfs41_exchange_id_res *exchangeid;
+    uint32_t                roles;
+};
+
+static int cl_exid_compare(
+    IN const struct list_entry *entry,
+    IN const void *value)
+{
+    nfs41_client *client = client_entry(entry);
+    const struct cl_exid_info *info = (const struct cl_exid_info*)value;
+    int status = ERROR_FILE_NOT_FOUND;
+
+    AcquireSRWLockShared(&client->exid_lock);
+
+    /* match any of the desired roles */
+    if ((info->roles & client->roles) == 0)
+        goto out;
+    /* match server_owner.major_id */
+    if (strncmp(info->exchangeid->server_owner.so_major_id,
+        client->server->owner, NFS4_OPAQUE_LIMIT) != 0)
+        goto out;
+    /* match server_scope */
+    if (strncmp(info->exchangeid->server_scope,
+        client->server->scope, NFS4_OPAQUE_LIMIT) != 0)
+        goto out;
+    /* match clientid */
+    if (info->exchangeid->clientid != client->clnt_id)
+        goto out;
+
+    status = NO_ERROR;
+out:
+    ReleaseSRWLockShared(&client->exid_lock);
+    return status;
+}
+
+static int root_client_find(
+    IN nfs41_root *root,
+    IN const nfs41_exchange_id_res *exchangeid,
+    IN bool_t is_data,
+    OUT nfs41_client **client_out)
+{
+    struct cl_exid_info info;
+    struct list_entry *entry;
+    int status;
+
+    dprintf(NSLVL, "--> root_client_find()\n");
+
+    info.exchangeid = exchangeid;
+    info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS;
+
+    entry = list_search(&root->clients, &info, cl_exid_compare);
+    if (entry) {
+        *client_out = client_entry(entry);
+        status = NO_ERROR;
+        dprintf(NSLVL, "<-- root_client_find() returning 0x%p\n",
+            *client_out);
+    } else {
+        status = ERROR_FILE_NOT_FOUND;
+        dprintf(NSLVL, "<-- root_client_find() failed with %d\n",
+            status);
+    }
+    return status;
+}
+
+static int session_get_lease(
+    IN nfs41_session *session,
+    IN OPTIONAL uint32_t lease_time)
+{
+    bool_t use_mds_lease;
+    int status;
+
+    /* http://tools.ietf.org/html/rfc5661#section-13.1.1
+     * 13.1.1. Sessions Considerations for Data Servers:
+     * If the reply to EXCHANGE_ID has just the EXCHGID4_FLAG_USE_PNFS_DS role
+     * set, then (as noted in Section 13.6) the client will not be able to
+     * determine the data server's lease_time attribute because GETATTR will
+     * not be permitted.  Instead, the rule is that any time a client
+     * receives a layout referring it to a data server that returns just the
+     * EXCHGID4_FLAG_USE_PNFS_DS role, the client MAY assume that the
+     * lease_time attribute from the metadata server that returned the
+     * layout applies to the data server. */
+    AcquireSRWLockShared(&session->client->exid_lock);
+    use_mds_lease = session->client->roles == EXCHGID4_FLAG_USE_PNFS_DS;
+    ReleaseSRWLockShared(&session->client->exid_lock);
+
+    if (!use_mds_lease) {
+        /* the client is allowed to GETATTR, so query the lease_time */
+        nfs41_file_info info = { 0 };
+        bitmap4 attr_request = { 1, { FATTR4_WORD0_LEASE_TIME, 0, 0 } };
+
+        status = nfs41_getattr(session, NULL, &attr_request, &info);
+        if (status) {
+            eprintf("nfs41_getattr() failed with %s\n",
+                nfs_error_string(status));
+            status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+            goto out;
+        }
+        lease_time = info.lease_time;
+    }
+
+    status = nfs41_session_set_lease(session, lease_time);
+    if (status) {
+        eprintf("nfs41_session_set_lease() failed %d\n", status);
+        goto out;
+    }
+out:
+    return status;
+}
+
+static int root_client_create(
+    IN nfs41_root *root,
+    IN nfs41_rpc_clnt *rpc,
+    IN bool_t is_data,
+    IN OPTIONAL uint32_t lease_time,
+    IN const nfs41_exchange_id_res *exchangeid,
+    OUT nfs41_client **client_out)
+{
+    nfs41_client *client;
+    nfs41_session *session;
+    int status;
+
+    /* create client (transfers ownership of rpc to client) */
+    status = nfs41_client_create(rpc, &root->client_owner,
+        is_data, exchangeid, &client);
+    if (status) {
+        eprintf("nfs41_client_create() failed with %d\n", status);
+        goto out;
+    }
+    client->root = root;
+    rpc->client = client;
+
+    /* create session (and client takes ownership) */
+    status = nfs41_session_create(client, &session);
+    if (status) {
+        eprintf("nfs41_session_create failed %d\n", status);
+        goto out_err;
+    }
+
+    if (!is_data) {
+        /* send RECLAIM_COMPLETE, but don't fail on ERR_NOTSUPP */
+        status = nfs41_reclaim_complete(session);
+        if (status && status != NFS4ERR_NOTSUPP) {
+            eprintf("nfs41_reclaim_complete() failed with %s\n",
+                nfs_error_string(status));
+            status = ERROR_BAD_NETPATH;
+            goto out_err;
+        }
+    }
+
+    /* get least time and start session renewal thread */
+    status = session_get_lease(session, lease_time);
+    if (status)
+        goto out_err;
+
+    *client_out = client;
+out:
+    return status;
+
+out_err:
+    nfs41_client_free(client);
+    goto out;
+}
+
+int nfs41_root_mount_addrs(
+    IN nfs41_root *root,
+    IN const multi_addr4 *addrs,
+    IN bool_t is_data,
+    IN OPTIONAL uint32_t lease_time,
+    OUT nfs41_client **client_out)
+{
+    nfs41_exchange_id_res exchangeid = { 0 };
+    nfs41_rpc_clnt *rpc;
+    nfs41_client *client, *existing;
+    int status;
+
+    dprintf(NSLVL, "--> nfs41_root_mount_addrs()\n");
+
+    /* look for an existing client that matches the address and role */
+    EnterCriticalSection(&root->lock);
+    status = root_client_find_addrs(root, addrs, is_data, &client);
+    LeaveCriticalSection(&root->lock);
+
+    if (status == NO_ERROR)
+        goto out;
+
+    /* create an rpc client */
+    status = nfs41_rpc_clnt_create(addrs, root->wsize, root->rsize,
+        root->uid, root->gid, root->sec_flavor, &rpc);
+    if (status) {
+        eprintf("nfs41_rpc_clnt_create() failed %d\n", status);
+        goto out;
+    }
+
+    /* get a clientid with exchangeid */
+    status = nfs41_exchange_id(rpc, &root->client_owner,
+        nfs41_exchange_id_flags(is_data), &exchangeid);
+    if (status) {
+        eprintf("nfs41_exchange_id() failed %s\n", nfs_error_string(status));
+        status = ERROR_BAD_NET_RESP;
+        goto out_free_rpc;
+    }
+
+    /* attempt to match existing clients by the exchangeid response */
+    EnterCriticalSection(&root->lock);
+    status = root_client_find(root, &exchangeid, is_data, &client);
+    LeaveCriticalSection(&root->lock);
+
+    if (status == NO_ERROR)
+        goto out_free_rpc;
+
+    /* create a client for this clientid */
+    status = root_client_create(root, rpc, is_data,
+        lease_time, &exchangeid, &client);
+    if (status) {
+        eprintf("nfs41_client_create() failed %d\n", status);
+        /* root_client_create takes care of cleaning up 
+         * thus don't go to out_free_rpc */
+        goto out;
+    }
+
+    /* because we don't hold the root's lock over session creation,
+     * we could end up creating multiple clients with the same
+     * server and roles */
+    EnterCriticalSection(&root->lock);
+    status = root_client_find(root, &exchangeid, is_data, &existing);
+
+    if (status) {
+        dprintf(NSLVL, "caching new client 0x%p\n", client);
+
+        /* the client is not a duplicate, so add it to the list */
+        list_add_tail(&root->clients, &client->root_entry);
+        status = NO_ERROR;
+    } else {
+        dprintf(NSLVL, "created a duplicate client 0x%p! using "
+            "existing client 0x%p instead\n", client, existing);
+
+        /* a matching client has been created in parallel, so free
+         * the one we created and use the existing client instead */
+        nfs41_client_free(client);
+        client = existing;
+    }
+    LeaveCriticalSection(&root->lock);
+
+out:
+    if (status == NO_ERROR)
+        *client_out = client;
+    dprintf(NSLVL, "<-- nfs41_root_mount_addrs() returning %d\n", status);
+    return status;
+
+out_free_rpc:
+    nfs41_rpc_clnt_free(rpc);
+    goto out;
+}
+
+
+/* http://tools.ietf.org/html/rfc5661#section-11.9
+ * 11.9. The Attribute fs_locations
+ * An entry in the server array is a UTF-8 string and represents one of a
+ * traditional DNS host name, IPv4 address, IPv6 address, or a zero-length
+ * string.  An IPv4 or IPv6 address is represented as a universal address
+ * (see Section 3.3.9 and [15]), minus the netid, and either with or without
+ * the trailing ".p1.p2" suffix that represents the port number.  If the
+ * suffix is omitted, then the default port, 2049, SHOULD be assumed.  A
+ * zero-length string SHOULD be used to indicate the current address being
+ * used for the RPC call. */
+static int referral_mount_location(
+    IN nfs41_root *root,
+    IN const fs_location4 *loc,
+    OUT nfs41_client **client_out)
+{
+    multi_addr4 addrs;
+    int status = ERROR_BAD_NET_NAME;
+    uint32_t i;
+
+    /* create a client and session for the first available server */
+    for (i = 0; i < loc->server_count; i++) {
+        /* XXX: only deals with 'address' as a hostname with default port */
+        status = nfs41_server_resolve(loc->servers[i].address, 2049, &addrs);
+        if (status) continue;
+
+        status = nfs41_root_mount_addrs(root, &addrs, 0, 0, client_out);
+        if (status == NO_ERROR)
+            break;
+    }
+    return status;
+}
+
+int nfs41_root_mount_referral(
+    IN nfs41_root *root,
+    IN const fs_locations4 *locations,
+    OUT const fs_location4 **loc_out,
+    OUT nfs41_client **client_out)
+{
+    int status = ERROR_BAD_NET_NAME;
+    uint32_t i;
+
+    /* establish a mount to the first available location */
+    for (i = 0; i < locations->location_count; i++) {
+        status = referral_mount_location(root,
+            &locations->locations[i], client_out);
+        if (status == NO_ERROR) {
+            *loc_out = &locations->locations[i];
+            break;
+        }
+    }
+    return status;
+}
diff --git a/reactos/base/services/nfsd/netconfig b/reactos/base/services/nfsd/netconfig
new file mode 100644 (file)
index 0000000..162ae83
--- /dev/null
@@ -0,0 +1,19 @@
+#
+# The network configuration file. This file is currently only used in
+# conjunction with the TI-RPC code in the libtirpc library.
+#
+# Entries consist of:
+#
+#       <network_id> <semantics> <flags> <protofamily> <protoname> \
+#               <device> <nametoaddr_libs>
+#
+# The <device> and <nametoaddr_libs> fields are always empty in this
+# implementation.
+#
+udp        tpi_clts      v     inet     udp     -       -
+tcp        tpi_cots_ord  v     inet     tcp     -       -
+udp6       tpi_clts      v     inet6    udp     -       -
+tcp6       tpi_cots_ord  v     inet6    tcp     -       -
+rawip      tpi_raw       -     inet      -      -       -
+local      tpi_cots_ord  -     loopback  -      -       -
+unix tpi_cots_ord - loopback - - -
diff --git a/reactos/base/services/nfsd/nfs41.h b/reactos/base/services/nfsd/nfs41.h
new file mode 100644 (file)
index 0000000..438bb75
--- /dev/null
@@ -0,0 +1,532 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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__
+#define __NFS41__
+
+#include "util.h"
+#include "list.h"
+
+
+struct __nfs41_session;
+struct __nfs41_client;
+struct __rpc_client;
+struct __nfs41_root;
+
+struct _FILE_GET_EA_INFORMATION;
+struct _FILE_FULL_EA_INFORMATION;
+
+typedef struct __nfs41_superblock {
+    nfs41_fsid fsid;
+    struct list_entry entry; /* position in nfs41_server.superblocks */
+
+    bitmap4 supported_attrs;
+    bitmap4 suppattr_exclcreat;
+    bitmap4 default_getattr;
+
+    nfstime4 time_delta;
+    uint64_t maxread;
+    uint64_t maxwrite;
+
+    /* constant filesystem attributes */
+    unsigned int layout_types : 3;
+    unsigned int aclsupport : 3;
+    unsigned int cansettime : 1;
+    unsigned int link_support : 1;
+    unsigned int symlink_support : 1;
+    unsigned int ea_support : 1;
+    unsigned int case_preserving : 1;
+    unsigned int case_insensitive : 1;
+
+    /* variable filesystem attributes */
+    uint64_t space_avail;
+    uint64_t space_free;
+    uint64_t space_total;
+    time_t cache_expiration; /* applies to space_ attributes */
+
+    SRWLOCK lock;
+} nfs41_superblock;
+
+typedef struct __nfs41_superblock_list {
+    struct list_entry head;
+    SRWLOCK lock;
+} nfs41_superblock_list;
+
+struct server_addrs {
+    multi_addr4 addrs; /* list of addrs we've used with this server */
+    uint32_t next_index;
+    SRWLOCK lock;
+};
+
+typedef struct __nfs41_server {
+    char scope[NFS4_OPAQUE_LIMIT]; /* server_scope from exchangeid */
+    char owner[NFS4_OPAQUE_LIMIT]; /* server_owner.major_id from exchangeid */
+    struct server_addrs addrs;
+    nfs41_superblock_list superblocks;
+    struct nfs41_name_cache *name_cache;
+    struct list_entry entry; /* position in global server list */
+    LONG ref_count;
+} nfs41_server;
+
+enum delegation_status {
+    DELEGATION_GRANTED,
+    DELEGATION_RETURNING,
+    DELEGATION_RETURNED,
+};
+
+typedef struct __nfs41_delegation_state {
+    open_delegation4 state;
+    nfs41_abs_path path;
+    nfs41_path_fh parent;
+    nfs41_path_fh file;
+    struct list_entry client_entry; /* entry in nfs41_client.delegations */
+    LONG ref_count;
+
+    enum delegation_status status;
+    SRWLOCK lock;
+    CONDITION_VARIABLE cond;
+
+    bool_t revoked; /* for recovery, accessed under client.state.lock */
+
+    HANDLE srv_open; /* for rdbss cache invalidation */
+} nfs41_delegation_state;
+
+typedef struct __nfs41_lock_state {
+    struct list_entry open_entry; /* entry in nfs41_open_state.locks */
+    uint64_t offset;
+    uint64_t length;
+    uint32_t exclusive : 1;
+    uint32_t delegated : 1; /* whether or not there is state on the server */
+    uint32_t id : 30;
+} nfs41_lock_state;
+
+/* nfs41_open_state reference counting:
+ * one reference is held implicitly by the driver (initialized to 1 on
+ * OPEN and released on CLOSE). other references must be held during
+ * upcalls to prevent a parallel CLOSE from freeing it prematurely. by
+ * calling upcall_open_state_ref() when parsing the upcall, you are
+ * guaranteed a matching dereference on upcall_cleanup() */
+typedef struct __nfs41_open_state {
+    nfs41_abs_path path;
+    nfs41_path_fh parent;
+    nfs41_path_fh file;
+    nfs41_readdir_cookie cookie;
+    struct __nfs41_session *session;
+    uint32_t type;
+    bool_t do_close;
+    stateid4 stateid;
+    state_owner4 owner;
+    struct __pnfs_layout_state *layout;
+    struct list_entry client_entry; /* entry in nfs41_client.opens */
+    SRWLOCK lock;
+    LONG ref_count;
+    uint32_t share_access;
+    uint32_t share_deny;
+    uint64_t pnfs_last_offset; /* for layoutcommit */
+
+    struct {
+        nfs41_delegation_state *state;
+        bool_t reclaim;
+        CONDITION_VARIABLE cond;
+    } delegation;
+
+    struct { /* list of open lock state for recovery */
+        stateid4 stateid;
+        struct list_entry list;
+        uint32_t counter;
+        CRITICAL_SECTION lock;
+    } locks;
+
+    struct {
+        struct _FILE_GET_EA_INFORMATION *list;
+        uint32_t index;
+        CRITICAL_SECTION lock;
+    } ea;
+
+    HANDLE srv_open; /* for data cache invalidation */
+} nfs41_open_state;
+
+typedef struct __nfs41_rpc_clnt {
+    struct __rpc_client *rpc;
+    SRWLOCK lock;
+    HANDLE cond;
+    struct __nfs41_client *client;
+    multi_addr4 addrs;
+    uint32_t addr_index; /* index of addr we're using */
+    uint32_t wsize;
+    uint32_t rsize;
+    uint32_t version;
+    uint32_t sec_flavor;
+    uint32_t uid;
+    uint32_t gid;
+    char server_name[NI_MAXHOST];
+    bool_t is_valid_session;
+    bool_t in_recovery;
+    bool_t needcb;
+} nfs41_rpc_clnt;
+
+struct client_state {
+    struct list_entry opens; /* list of associated nfs41_open_state */
+    struct list_entry delegations; /* list of associated delegations */
+    CRITICAL_SECTION lock;
+};
+
+typedef struct __nfs41_client {
+    nfs41_server *server;
+    client_owner4 owner;
+    uint64_t clnt_id;
+    uint32_t seq_id;
+    uint32_t roles;
+    SRWLOCK exid_lock;
+    struct __nfs41_session *session;
+    SRWLOCK session_lock;
+    nfs41_rpc_clnt *rpc;
+    bool_t is_data;
+    struct pnfs_layout_list *layouts;
+    struct pnfs_file_device_list *devices;
+    struct list_entry root_entry; /* position in nfs41_root.clients */
+    struct __nfs41_root *root;
+
+    struct {
+        CONDITION_VARIABLE cond;
+        CRITICAL_SECTION lock;
+        bool_t in_recovery;
+    } recovery;
+
+    /* for state recovery on server reboot */
+    struct client_state state;
+} nfs41_client;
+
+#define NFS41_MAX_NUM_SLOTS NFS41_MAX_RPC_REQS
+typedef struct __nfs41_slot_table {
+    uint32_t seq_nums[NFS41_MAX_NUM_SLOTS];
+    uint32_t used_slots[NFS41_MAX_NUM_SLOTS];
+    uint32_t max_slots;
+    uint32_t highest_used;
+    uint32_t num_used;
+    ULONGLONG target_delay;
+    CRITICAL_SECTION lock;
+    CONDITION_VARIABLE cond;
+} nfs41_slot_table;
+
+typedef struct __nfs41_channel_attrs {
+    uint32_t                ca_headerpadsize;
+    uint32_t                ca_maxrequestsize;
+    uint32_t                ca_maxresponsesize;
+    uint32_t                ca_maxresponsesize_cached;
+    uint32_t                ca_maxoperations;
+    uint32_t                ca_maxrequests;
+    uint32_t                *ca_rdma_ird;
+} nfs41_channel_attrs;
+
+struct replay_cache {
+    unsigned char buffer[NFS41_MAX_SERVER_CACHE];
+    uint32_t length;
+};
+
+typedef struct __nfs41_cb_session {
+    struct {
+        struct replay_cache arg;
+        struct replay_cache res;
+    } replay;
+    const unsigned char *cb_sessionid; /* -> nfs41_session.session_id */
+    uint32_t cb_seqnum;
+    uint32_t cb_slotid;
+} nfs41_cb_session;
+
+typedef struct __nfs41_session {
+    nfs41_client *client;
+    unsigned char session_id[NFS4_SESSIONID_SIZE];
+    nfs41_channel_attrs fore_chan_attrs;
+    nfs41_channel_attrs back_chan_attrs;
+    uint32_t lease_time;
+    nfs41_slot_table table;
+    // array of slots
+    HANDLE renew_thread;
+    bool_t isValidState;
+    uint32_t flags;
+    nfs41_cb_session cb_session;
+} nfs41_session;
+
+/* nfs41_root reference counting:
+ * similar to nfs41_open_state, the driver holds an implicit reference
+ * between MOUNT and UNMOUNT. all other upcalls use upcall_root_ref() on
+ * upcall_parse(), which prevents the root/clients from being freed and
+ * guarantees a matching deref on upcall_cleanup() */
+typedef struct __nfs41_root {
+    client_owner4 client_owner;
+    CRITICAL_SECTION lock;
+    struct list_entry clients;
+    uint32_t wsize;
+    uint32_t rsize;
+    LONG ref_count;
+    uint32_t uid;
+    uint32_t gid;
+    DWORD sec_flavor;
+} nfs41_root;
+
+
+/* nfs41_namespace.c */
+int nfs41_root_create(
+    IN const char *name,
+    IN uint32_t sec_flavor,
+    IN uint32_t wsize,
+    IN uint32_t rsize,
+    OUT nfs41_root **root_out);
+
+void nfs41_root_ref(
+    IN nfs41_root *root);
+
+void nfs41_root_deref(
+    IN nfs41_root *root);
+
+int nfs41_root_mount_addrs(
+    IN nfs41_root *root,
+    IN const multi_addr4 *addrs,
+    IN bool_t is_data,
+    IN OPTIONAL uint32_t lease_time,
+    OUT nfs41_client **client_out);
+
+int nfs41_root_mount_server(
+    IN nfs41_root *root,
+    IN nfs41_server *server,
+    IN bool_t is_data,
+    IN OPTIONAL uint32_t lease_time,
+    OUT nfs41_client **client_out);
+
+int nfs41_root_mount_referral(
+    IN nfs41_root *root,
+    IN const fs_locations4 *locations,
+    OUT const fs_location4 **loc_out,
+    OUT nfs41_client **client_out);
+
+static __inline nfs41_session* nfs41_root_session(
+    IN nfs41_root *root)
+{
+    nfs41_client *client;
+    /* return a session for the server at the root of the namespace.
+     * because we created it on mount, it's the first one in the list */
+    EnterCriticalSection(&root->lock);
+    client = list_container(root->clients.next, nfs41_client, root_entry);
+    LeaveCriticalSection(&root->lock);
+    return client->session;
+}
+
+
+/* nfs41_session.c */
+int nfs41_session_create(
+    IN nfs41_client *client,
+    IN nfs41_session **session_out);
+
+int nfs41_session_renew(
+    IN nfs41_session *session);
+
+int nfs41_session_set_lease(
+    IN nfs41_session *session,
+    IN uint32_t lease_time);
+
+void nfs41_session_free(
+    IN nfs41_session *session);
+
+void nfs41_session_bump_seq(
+    IN nfs41_session *session,
+    IN uint32_t slotid,
+    IN uint32_t target_highest_slotid);
+
+void nfs41_session_free_slot(
+    IN nfs41_session *session,
+    IN uint32_t slotid);
+
+void nfs41_session_get_slot(
+    IN nfs41_session *session, 
+    OUT uint32_t *slot, 
+    OUT uint32_t *seq, 
+    OUT uint32_t *highest);
+
+int nfs41_session_recall_slot(
+    IN nfs41_session *session,
+    IN OUT uint32_t target_highest_slotid);
+
+struct __nfs41_sequence_args;
+void nfs41_session_sequence(
+    struct __nfs41_sequence_args *args,
+    nfs41_session *session,
+    bool_t cachethis);
+
+int nfs41_session_bad_slot(
+    IN nfs41_session *session,
+    IN OUT struct __nfs41_sequence_args *args);
+
+
+/* nfs41_server.c */
+void nfs41_server_list_init();
+
+int nfs41_server_resolve(
+    IN const char *hostname,
+    IN unsigned short port,
+    OUT multi_addr4 *addrs);
+
+int nfs41_server_find_or_create(
+    IN const char *server_owner_major_id,
+    IN const char *server_scope,
+    IN const netaddr4 *addr,
+    OUT nfs41_server **server_out);
+
+void nfs41_server_ref(
+    IN nfs41_server *server);
+
+void nfs41_server_deref(
+    IN nfs41_server *server);
+
+void nfs41_server_addrs(
+    IN nfs41_server *server,
+    OUT multi_addr4 *addrs);
+
+
+/* nfs41_client.c */
+int nfs41_client_owner(
+    IN const char *name,
+    IN uint32_t sec_flavor,
+    OUT client_owner4 *owner);
+
+uint32_t nfs41_exchange_id_flags(
+    IN bool_t is_data);
+
+struct __nfs41_exchange_id_res;
+
+int nfs41_client_create(
+    IN nfs41_rpc_clnt *rpc,
+    IN const client_owner4 *owner,
+    IN bool_t is_data,
+    IN const struct __nfs41_exchange_id_res *exchangeid,
+    OUT nfs41_client **client_out);
+
+int nfs41_client_renew(
+    IN nfs41_client *client);
+
+void nfs41_client_free(
+    IN nfs41_client *client);
+
+static __inline nfs41_server* client_server(
+    IN nfs41_client *client)
+{
+    /* the client's server could change during nfs41_client_renew(),
+     * so access to client->server must be protected */
+    nfs41_server *server;
+    AcquireSRWLockShared(&client->exid_lock);
+    server = client->server;
+    ReleaseSRWLockShared(&client->exid_lock);
+    return server;
+}
+
+
+/* nfs41_superblock.c */
+int nfs41_superblock_for_fh(
+    IN nfs41_session *session,
+    IN const nfs41_fsid *fsid,
+    IN const nfs41_fh *parent OPTIONAL,
+    OUT nfs41_path_fh *file);
+
+static __inline void nfs41_superblock_getattr_mask(
+    IN const nfs41_superblock *superblock,
+    OUT bitmap4 *attrs)
+{
+    memcpy(attrs, &superblock->default_getattr, sizeof(bitmap4));
+}
+static __inline void nfs41_superblock_supported_attrs(
+    IN const nfs41_superblock *superblock,
+    IN OUT bitmap4 *attrs)
+{
+    bitmap_intersect(attrs, &superblock->supported_attrs);
+}
+static __inline void nfs41_superblock_supported_attrs_exclcreat(
+    IN const nfs41_superblock *superblock,
+    IN OUT bitmap4 *attrs)
+{
+    bitmap_intersect(attrs, &superblock->suppattr_exclcreat);
+}
+
+struct _FILE_FS_ATTRIBUTE_INFORMATION;
+void nfs41_superblock_fs_attributes(
+    IN const nfs41_superblock *superblock,
+    OUT struct _FILE_FS_ATTRIBUTE_INFORMATION *FsAttrs);
+
+void nfs41_superblock_space_changed(
+    IN nfs41_superblock *superblock);
+
+void nfs41_superblock_list_init(
+    IN nfs41_superblock_list *superblocks);
+
+void nfs41_superblock_list_free(
+    IN nfs41_superblock_list *superblocks);
+
+
+/* nfs41_rpc.c */
+int nfs41_rpc_clnt_create(
+    IN const multi_addr4 *addrs,
+    IN uint32_t wsize,
+    IN uint32_t rsize,
+    IN uint32_t uid,
+    IN uint32_t gid,
+    IN uint32_t sec_flavor,
+    OUT nfs41_rpc_clnt **rpc_out);
+
+void nfs41_rpc_clnt_free(
+    IN nfs41_rpc_clnt *rpc);
+
+int nfs41_send_compound(
+    IN nfs41_rpc_clnt *rpc,
+    IN char *inbuf,
+    OUT char *outbuf);
+
+static __inline netaddr4* nfs41_rpc_netaddr(
+    IN nfs41_rpc_clnt *rpc)
+{
+    uint32_t id;
+    AcquireSRWLockShared(&rpc->lock);
+    /* only addr_index needs to be protected, as rpc->addrs is write-once */
+    id = rpc->addr_index;
+    ReleaseSRWLockShared(&rpc->lock);
+
+    /* return the netaddr used to create the rpc client */
+    return &rpc->addrs.arr[id];
+}
+
+
+/* open.c */
+void nfs41_open_state_ref(
+    IN nfs41_open_state *state);
+
+void nfs41_open_state_deref(
+    IN nfs41_open_state *state);
+
+struct __stateid_arg;
+void nfs41_open_stateid_arg(
+    IN nfs41_open_state *state,
+    OUT struct __stateid_arg *arg);
+
+
+/* ea.c */
+int nfs41_ea_set(
+    IN nfs41_open_state *state,
+    IN struct _FILE_FULL_EA_INFORMATION *ea);
+
+#endif /* __NFS41__ */
diff --git a/reactos/base/services/nfsd/nfs41_callback.h b/reactos/base/services/nfsd/nfs41_callback.h
new file mode 100644 (file)
index 0000000..18c8cb6
--- /dev/null
@@ -0,0 +1,295 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_CALLBACK_H__
+#define __NFS41_CALLBACK_H__
+
+#include "wintirpc.h"
+#include "rpc/rpc.h"
+#include "nfs41_types.h"
+
+
+enum nfs41_callback_proc {
+    CB_NULL                 = 0,
+    CB_COMPOUND             = 1,
+};
+
+enum nfs41_callback_op {
+    OP_CB_GETATTR           = 3,
+    OP_CB_RECALL            = 4,
+    OP_CB_LAYOUTRECALL      = 5,
+    OP_CB_NOTIFY            = 6,
+    OP_CB_PUSH_DELEG        = 7,
+    OP_CB_RECALL_ANY        = 8,
+    OP_CB_RECALLABLE_OBJ_AVAIL = 9,
+    OP_CB_RECALL_SLOT       = 10,
+    OP_CB_SEQUENCE          = 11,
+    OP_CB_WANTS_CANCELLED   = 12,
+    OP_CB_NOTIFY_LOCK       = 13,
+    OP_CB_NOTIFY_DEVICEID   = 14,
+    OP_CB_ILLEGAL           = 10044
+};
+
+int nfs41_handle_callback(void *, void *, void *);
+
+/* OP_CB_LAYOUTRECALL */
+struct cb_recall_file {
+    nfs41_fh                fh;
+    uint64_t                offset;
+    uint64_t                length;
+    stateid4                stateid;
+};
+union cb_recall_file_args {
+    struct cb_recall_file   file;
+    nfs41_fsid              fsid;
+};
+struct cb_recall {
+#ifdef __REACTOS__
+    uint32_t type;
+#else
+    enum pnfs_return_type   type;
+#endif
+    union cb_recall_file_args args;
+};
+struct cb_layoutrecall_args {
+#ifdef __REACTOS__
+    uint32_t type;
+    uint32_t iomode;
+#else
+    enum pnfs_return_type   type;
+    enum pnfs_iomode        iomode;
+#endif
+    bool_t                  changed;
+    struct cb_recall        recall;
+};
+
+struct cb_layoutrecall_res {
+    enum_t                  status;
+};
+
+/* OP_CB_RECALL_SLOT */
+struct cb_recall_slot_args {
+    uint32_t                target_highest_slotid;
+};
+
+struct cb_recall_slot_res {
+    enum_t                  status;
+};
+
+/* OP_CB_SEQUENCE */
+struct cb_sequence_ref {
+    uint32_t                sequenceid;
+    uint32_t                slotid;
+};
+struct cb_sequence_ref_list {
+    char                    sessionid[NFS4_SESSIONID_SIZE];
+    struct cb_sequence_ref  *calls;
+    uint32_t                call_count;
+};
+struct cb_sequence_args {
+    char                    sessionid[NFS4_SESSIONID_SIZE];
+    uint32_t                sequenceid;
+    uint32_t                slotid;
+    uint32_t                highest_slotid;
+    bool_t                  cachethis;
+    struct cb_sequence_ref_list *ref_lists;
+    uint32_t                ref_list_count;
+};
+
+struct cb_sequence_res_ok {
+    char                    sessionid[NFS4_SESSIONID_SIZE];
+    uint32_t                sequenceid;
+    uint32_t                slotid;
+    uint32_t                highest_slotid;
+    uint32_t                target_highest_slotid;
+};
+struct cb_sequence_res {
+    enum_t                  status;
+    struct cb_sequence_res_ok ok;
+};
+
+/* OP_CB_GETATTR */
+struct cb_getattr_args {
+    nfs41_fh                fh;
+    bitmap4                 attr_request;
+};
+
+struct cb_getattr_res {
+    enum_t                  status;
+    nfs41_file_info         info;
+};
+
+/* OP_CB_RECALL */
+struct cb_recall_args {
+    stateid4                stateid;
+    bool_t                  truncate;
+    nfs41_fh                fh;
+};
+
+struct cb_recall_res {
+    enum_t                  status;
+};
+
+/* OP_CB_NOTIFY */
+struct cb_notify_args {
+    uint32_t                target_highest_slotid;
+};
+
+struct cb_notify_res {
+    enum_t                  status;
+};
+
+/* OP_CB_PUSH_DELEG */
+struct cb_push_deleg_args {
+    uint32_t                target_highest_slotid;
+};
+
+struct cb_push_deleg_res {
+    enum_t                  status;
+};
+
+/* OP_CB_RECALL_ANY */
+struct cb_recall_any_args {
+    uint32_t                target_highest_slotid;
+};
+
+struct cb_recall_any_res {
+    enum_t                  status;
+};
+
+/* OP_CB_RECALLABLE_OBJ_AVAIL */
+struct cb_recallable_obj_avail_args {
+    uint32_t                target_highest_slotid;
+};
+
+struct cb_recallable_obj_avail_res {
+    enum_t                  status;
+};
+
+/* OP_CB_WANTS_CANCELLED */
+struct cb_wants_cancelled_args {
+    uint32_t                target_highest_slotid;
+};
+
+struct cb_wants_cancelled_res {
+    enum_t                  status;
+};
+
+/* OP_CB_NOTIFY_LOCK */
+struct cb_notify_lock_args {
+    uint32_t                target_highest_slotid;
+};
+
+struct cb_notify_lock_res {
+    enum_t                  status;
+};
+
+/* OP_CB_NOTIFY_DEVICEID */
+enum notify_deviceid_type4 {
+    NOTIFY_DEVICEID4_CHANGE = 1,
+    NOTIFY_DEVICEID4_DELETE = 2
+};
+struct notify_deviceid4 {
+    unsigned char           deviceid[16];
+    enum notify_deviceid_type4 type;
+#ifdef __REACTOS__
+    uint32_t layouttype;
+#else
+    enum pnfs_layout_type   layouttype;
+#endif
+    bool_t                  immediate;
+};
+struct notify4 {
+    bitmap4                 mask;
+    char                    *list;
+    uint32_t                len;
+};
+struct cb_notify_deviceid_args {
+    struct notify4          *notify_list;
+    uint32_t                notify_count;
+    struct notify_deviceid4 *change_list;
+    uint32_t                change_count;
+};
+
+struct cb_notify_deviceid_res {
+    enum_t                  status;
+};
+
+/* CB_COMPOUND */
+#define CB_COMPOUND_MAX_TAG         64
+#define CB_COMPOUND_MAX_OPERATIONS  16
+
+union cb_op_args {
+    struct cb_layoutrecall_args layoutrecall;
+    struct cb_recall_slot_args recall_slot;
+    struct cb_sequence_args sequence;
+    struct cb_getattr_args  getattr;
+    struct cb_recall_args   recall;
+    struct cb_notify_deviceid_args notify_deviceid;
+};
+struct cb_argop {
+    enum_t                  opnum;
+    union cb_op_args        args;
+};
+struct cb_compound_tag {
+    char                    str[CB_COMPOUND_MAX_TAG];
+    uint32_t                len;
+};
+struct cb_compound_args {
+    struct cb_compound_tag  tag;
+    uint32_t                minorversion;
+    uint32_t                callback_ident; /* client MUST ignore */
+    struct cb_argop         *argarray;
+    uint32_t                argarray_count; /* <= CB_COMPOUND_MAX_OPERATIONS */
+};
+
+union cb_op_res {
+    enum_t                  status; /* all results start with status */ 
+    struct cb_layoutrecall_res layoutrecall;
+    struct cb_recall_slot_res recall_slot;
+    struct cb_sequence_res  sequence;
+    struct cb_getattr_res   getattr;
+    struct cb_recall_res    recall;
+    struct cb_notify_deviceid_res notify_deviceid;
+};
+struct cb_resop {
+    enum_t                  opnum;
+    union cb_op_res         res;
+    bool_t                  xdr_ok;
+};
+struct cb_compound_res {
+    enum_t                  status;
+    struct cb_compound_tag  tag;
+    struct cb_resop         *resarray;
+    uint32_t                resarray_count; /* <= CB_COMPOUND_MAX_OPERATIONS */
+};
+
+
+/* callback_xdr.c */
+bool_t proc_cb_compound_args(XDR *xdr, struct cb_compound_args *args);
+bool_t proc_cb_compound_res(XDR *xdr, struct cb_compound_res *res);
+
+/* callback_server.c */
+struct __nfs41_session;
+void nfs41_callback_session_init(
+    IN struct __nfs41_session *session);
+
+#endif /* !__NFS41_CALLBACK_H__ */
diff --git a/reactos/base/services/nfsd/nfs41_client.c b/reactos/base/services/nfsd/nfs41_client.c
new file mode 100644 (file)
index 0000000..566f17e
--- /dev/null
@@ -0,0 +1,442 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <time.h>
+#include <winsock2.h>
+#include <iphlpapi.h> /* for GetAdaptersAddresses() */
+#include <wincrypt.h> /* for Crypt*() functions */
+#include <winsock2.h> /* for hostent struct */
+
+#include "tree.h"
+#include "delegation.h"
+#include "daemon_debug.h"
+#include "nfs41_ops.h"
+
+
+uint32_t nfs41_exchange_id_flags(
+    IN bool_t is_data)
+{
+    uint32_t flags = EXCHGID4_FLAG_SUPP_MOVED_REFER;
+    if (is_data)
+        flags |= EXCHGID4_FLAG_USE_PNFS_DS;
+    else
+        flags |= EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS;
+    return flags;
+}
+
+static int pnfs_client_init(
+    IN nfs41_client *client)
+{
+    enum pnfs_status pnfsstat;
+    int status = NO_ERROR;
+
+    /* initialize the pnfs layout and device lists for metadata clients */
+    pnfsstat = pnfs_layout_list_create(&client->layouts);
+    if (pnfsstat) {
+        status = ERROR_NOT_ENOUGH_MEMORY;
+        goto out;
+    }
+    pnfsstat = pnfs_file_device_list_create(&client->devices);
+    if (pnfsstat) {
+        status = ERROR_NOT_ENOUGH_MEMORY;
+        goto out_err_layouts;
+    }
+out:
+    return status;
+
+out_err_layouts:
+    pnfs_layout_list_free(client->layouts);
+    client->layouts = NULL;
+    goto out;
+}
+
+static int update_server(
+    IN nfs41_client *client,
+    IN const char *server_scope,
+    IN const server_owner4 *owner)
+{
+    nfs41_server *server;
+    int status;
+
+    /* find a server matching the owner.major_id and scope */
+    status = nfs41_server_find_or_create(owner->so_major_id,
+        server_scope, nfs41_rpc_netaddr(client->rpc), &server);
+    if (status)
+        goto out;
+
+    /* if the server is the same, we now have an extra reference. if
+     * the servers are different, we still need to deref the old server.
+     * so both cases can be treated the same */
+    if (client->server)
+        nfs41_server_deref(client->server);
+    client->server = server;
+out:
+    return status;
+}
+
+static int update_exchangeid_res(
+    IN nfs41_client *client,
+    IN const nfs41_exchange_id_res *exchangeid)
+{
+    client->clnt_id = exchangeid->clientid;
+    client->seq_id = exchangeid->sequenceid;
+    client->roles = exchangeid->flags & EXCHGID4_FLAG_MASK_PNFS;
+    return update_server(client, exchangeid->server_scope,
+        &exchangeid->server_owner);
+}
+
+int nfs41_client_create(
+    IN nfs41_rpc_clnt *rpc,
+    IN const client_owner4 *owner,
+    IN bool_t is_data,
+    IN const nfs41_exchange_id_res *exchangeid,
+    OUT nfs41_client **client_out)
+{
+    int status;
+    nfs41_client *client;
+
+    client = calloc(1, sizeof(nfs41_client));
+    if (client == NULL) {
+        status = GetLastError();
+        goto out_err_rpc;
+    }
+
+    memcpy(&client->owner, owner, sizeof(client_owner4));
+    client->rpc = rpc;
+    client->is_data = is_data;
+
+    status = update_exchangeid_res(client, exchangeid);
+    if (status)
+        goto out_err_client;
+
+    list_init(&client->state.opens);
+    list_init(&client->state.delegations);
+    InitializeCriticalSection(&client->state.lock);
+
+    //initialize a lock used to protect access to client id and client id seq#
+    InitializeSRWLock(&client->exid_lock);
+
+    InitializeConditionVariable(&client->recovery.cond);
+    InitializeCriticalSection(&client->recovery.lock);
+
+    status = pnfs_client_init(client);
+    if (status) {
+        eprintf("pnfs_client_init() failed with %d\n", status);
+        goto out_err_client;
+    }
+    *client_out = client;
+out:
+    return status;
+out_err_client:
+    nfs41_client_free(client); /* also calls nfs41_rpc_clnt_free() */
+    goto out;
+out_err_rpc:
+    nfs41_rpc_clnt_free(rpc);
+    goto out;
+}
+
+static void dprint_roles(
+    IN int level,
+    IN uint32_t roles)
+{
+    dprintf(level, "roles: %s%s%s\n",
+        (roles & EXCHGID4_FLAG_USE_NON_PNFS) ? "USE_NON_PNFS " : "",
+        (roles & EXCHGID4_FLAG_USE_PNFS_MDS) ? "USE_PNFS_MDS " : "",
+        (roles & EXCHGID4_FLAG_USE_PNFS_DS) ? "USE_PNFS_DS" : "");
+}
+
+int nfs41_client_renew(
+    IN nfs41_client *client)
+{
+    nfs41_exchange_id_res exchangeid = { 0 };
+    int status;
+
+    status = nfs41_exchange_id(client->rpc, &client->owner,
+        nfs41_exchange_id_flags(client->is_data), &exchangeid);
+    if (status) {
+        eprintf("nfs41_exchange_id() failed with %d\n", status);
+        status = ERROR_BAD_NET_RESP;
+        goto out;
+    }
+
+    if (client->is_data) { /* require USE_PNFS_DS */
+        if ((exchangeid.flags & EXCHGID4_FLAG_USE_PNFS_DS) == 0) {
+            eprintf("client expected USE_PNFS_DS\n");
+            status = ERROR_BAD_NET_RESP;
+            goto out;
+        }
+    } else { /* require USE_NON_PNFS or USE_PNFS_MDS */
+        if ((exchangeid.flags & EXCHGID4_FLAG_USE_NON_PNFS) == 0 &&
+            (exchangeid.flags & EXCHGID4_FLAG_USE_PNFS_MDS) == 0) {
+            eprintf("client expected USE_NON_PNFS OR USE_PNFS_MDS\n");
+            status = ERROR_BAD_NET_RESP;
+            goto out;
+        }
+    }
+
+    dprint_roles(2, exchangeid.flags);
+
+    AcquireSRWLockExclusive(&client->exid_lock);
+    status = update_exchangeid_res(client, &exchangeid);
+    ReleaseSRWLockExclusive(&client->exid_lock);
+out:
+    return status;
+}
+
+void nfs41_client_free(
+    IN nfs41_client *client)
+{
+    dprintf(2, "nfs41_client_free(%llu)\n", client->clnt_id);
+    nfs41_client_delegation_free(client);
+    if (client->session) nfs41_session_free(client->session);
+    nfs41_destroy_clientid(client->rpc, client->clnt_id);
+    if (client->server) nfs41_server_deref(client->server);
+    nfs41_rpc_clnt_free(client->rpc);
+    if (client->layouts) pnfs_layout_list_free(client->layouts);
+    if (client->devices) pnfs_file_device_list_free(client->devices);
+    DeleteCriticalSection(&client->state.lock);
+    DeleteCriticalSection(&client->recovery.lock);
+    free(client);
+}
+
+
+/* client_owner generation
+ * we choose to use MAC addresses to generate a client_owner value that
+ * is unique to a machine and persists over restarts.  because the client
+ * can have multiple network adapters/addresses, we take each adapter into
+ * account.  the specification suggests that "for privacy reasons, it is
+ * best to perform some one-way function," so we apply an md5 hash to the
+ * sorted list of MAC addresses */
+
+/* References:
+ * RFC 5661: 2.4. Client Identifiers and Client Owners
+ * http://tools.ietf.org/html/rfc5661#section-2.4
+ *
+ * MSDN: GetAdaptersAddresses Function
+ * http://msdn.microsoft.com/en-us/library/aa365915%28VS.85%29.aspx
+ *
+ * MSDN: Example C Program: Creating an MD5 Hash from File Content
+ * http://msdn.microsoft.com/en-us/library/aa382380%28VS.85%29.aspx */
+
+
+/* use an rbtree to sort mac address entries */
+struct mac_entry {
+    RB_ENTRY(mac_entry)     rbnode;
+    PBYTE                   address;
+    ULONG                   length;
+};
+
+int mac_cmp(struct mac_entry *lhs, struct mac_entry *rhs)
+{
+    const int diff = rhs->length - lhs->length;
+    return diff ? diff : strncmp((const char*)lhs->address,
+        (const char*)rhs->address, lhs->length);
+}
+RB_HEAD(mac_tree, mac_entry);
+RB_GENERATE(mac_tree, mac_entry, rbnode, mac_cmp)
+
+static void mac_entry_insert(
+    IN struct mac_tree *root,
+    IN PBYTE address,
+    IN ULONG length)
+{
+    struct mac_entry *entry;
+
+    entry = calloc(1, sizeof(struct mac_entry));
+    if (entry == NULL)
+        return;
+
+    entry->address = address;
+    entry->length = length;
+
+    if (RB_INSERT(mac_tree, root, entry))
+        free(entry);
+}
+
+static int adapter_valid(
+    IN const IP_ADAPTER_ADDRESSES *addr)
+{
+    /* ignore generic interfaces whose address is not unique */
+    switch (addr->IfType) {
+    case IF_TYPE_SOFTWARE_LOOPBACK:
+    case IF_TYPE_TUNNEL:
+        return 0;
+    }
+    /* must have an address */
+    if (addr->PhysicalAddressLength == 0)
+        return 0;
+#ifndef __REACTOS__
+    /* must support ip */
+    return addr->Ipv4Enabled || addr->Ipv6Enabled;
+#else
+    return 1;
+#endif
+}
+
+static DWORD hash_mac_addrs(
+    IN HCRYPTHASH hash)
+{
+    PIP_ADAPTER_ADDRESSES addr, addrs = NULL;
+    struct mac_tree rbtree = RB_INITIALIZER(rbtree);
+    struct mac_entry *entry, *node;
+    ULONG len;
+    DWORD status;
+
+    /* start with enough room for DEFAULT_MINIMUM_ENTITIES */
+    len = DEFAULT_MINIMUM_ENTITIES * sizeof(IP_ADAPTER_ADDRESSES);
+
+    do {
+        PIP_ADAPTER_ADDRESSES tmp;
+        /* reallocate the buffer until we can fit all of it */
+        tmp = realloc(addrs, len);
+        if (tmp == NULL) {
+            status = GetLastError();
+            goto out;
+        }
+        addrs = tmp;
+        status = GetAdaptersAddresses(AF_UNSPEC,
+            GAA_FLAG_INCLUDE_ALL_INTERFACES | GAA_FLAG_SKIP_ANYCAST |
+            GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME |
+            GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_UNICAST,
+            NULL, addrs, &len);
+    } while (status == ERROR_BUFFER_OVERFLOW);
+
+    if (status) {
+        eprintf("GetAdaptersAddresses() failed with %d\n", status);
+        goto out;
+    }
+
+    /* get the mac address of each adapter */
+    for (addr = addrs; addr; addr = addr->Next)
+        if (adapter_valid(addr))
+            mac_entry_insert(&rbtree, addr->PhysicalAddress,
+                addr->PhysicalAddressLength);
+
+    /* require at least one valid address */
+    if (RB_EMPTY(&rbtree)) {
+        status = ERROR_FILE_NOT_FOUND;
+        eprintf("GetAdaptersAddresses() did not return "
+            "any valid mac addresses, failing with %d.\n", status);
+        goto out;
+    }
+
+    RB_FOREACH_SAFE(entry, mac_tree, &rbtree, node) {
+        RB_REMOVE(mac_tree, &rbtree, entry);
+
+        if (!CryptHashData(hash, entry->address, entry->length, 0)) {
+            status = GetLastError();
+            eprintf("CryptHashData() failed with %d\n", status);
+            /* don't break here, we need to free the rest */
+        }
+        free(entry);
+    }
+out:
+    free(addrs);
+    return status;
+}
+
+int nfs41_client_owner(
+    IN const char *name,
+    IN uint32_t sec_flavor,
+    OUT client_owner4 *owner)
+{
+    HCRYPTPROV context;
+    HCRYPTHASH hash;
+    PBYTE buffer;
+    DWORD length;
+    const ULONGLONG time_created = GetTickCount64();
+    int status;
+    char username[UNLEN + 1];
+    DWORD len = UNLEN + 1;
+
+    if (!GetUserNameA(username, &len)) {
+        status = GetLastError();
+        eprintf("GetUserName() failed with %d\n", status);
+        goto out;
+    }
+
+    /* owner.verifier = "time created" */
+    memcpy(owner->co_verifier, &time_created, sizeof(time_created));
+
+    /* set up the md5 hash generator */
+    if (!CryptAcquireContext(&context, NULL, NULL,
+        PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
+        status = GetLastError();
+        eprintf("CryptAcquireContext() failed with %d\n", status);
+        goto out;
+    }
+    if (!CryptCreateHash(context, CALG_MD5, 0, 0, &hash)) {
+        status = GetLastError();
+        eprintf("CryptCreateHash() failed with %d\n", status);
+        goto out_context;
+    }
+
+    if (!CryptHashData(hash, (const BYTE*)&sec_flavor, (DWORD)sizeof(sec_flavor), 0)) {
+        status = GetLastError();
+        eprintf("CryptHashData() failed with %d\n", status);
+        goto out_hash;
+    }
+
+    if (!CryptHashData(hash, (const BYTE*)username, (DWORD)strlen(username), 0)) {
+        status = GetLastError();
+        eprintf("CryptHashData() failed with %d\n", status);
+        goto out_hash;
+    }
+
+    if (!CryptHashData(hash, (const BYTE*)name, (DWORD)strlen(name), 0)) {
+        status = GetLastError();
+        eprintf("CryptHashData() failed with %d\n", status);
+        goto out_hash;
+    }
+
+    /* add the mac address from each applicable adapter to the hash */
+    status = hash_mac_addrs(hash);
+    if (status) {
+        eprintf("hash_mac_addrs() failed with %d\n", status);
+        goto out_hash;
+    }
+
+    /* extract the hash size (should always be 16 for md5) */
+    buffer = (PBYTE)&owner->co_ownerid_len;
+    length = (DWORD)sizeof(DWORD);
+    if (!CryptGetHashParam(hash, HP_HASHSIZE, buffer, &length, 0)) {
+        status = GetLastError();
+        eprintf("CryptGetHashParam(size) failed with %d\n", status);
+        goto out_hash;
+    }
+    /* extract the hash buffer */
+    buffer = owner->co_ownerid;
+    length = owner->co_ownerid_len;
+    if (!CryptGetHashParam(hash, HP_HASHVAL, buffer, &length, 0)) {
+        status = GetLastError();
+        eprintf("CryptGetHashParam(val) failed with %d\n", status);
+        goto out_hash;
+    }
+
+out_hash:
+    CryptDestroyHash(hash);
+out_context:
+    CryptReleaseContext(context, 0);
+out:
+    return status;
+}
diff --git a/reactos/base/services/nfsd/nfs41_compound.c b/reactos/base/services/nfsd/nfs41_compound.c
new file mode 100644 (file)
index 0000000..7017dda
--- /dev/null
@@ -0,0 +1,457 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+
+#include "nfs41_compound.h"
+#include "nfs41_xdr.h"
+#include "nfs41_ops.h"
+#include "recovery.h"
+#include "name_cache.h"
+#include "daemon_debug.h"
+#include "rpc/rpc.h"
+#include "rpc/auth_sspi.h"
+
+int compound_error(int status)
+{
+    if (status != NFS4_OK)
+        dprintf(1, "COMPOUND failed with status %d.\n", status);
+    return status;
+}
+
+void compound_init(
+    nfs41_compound *compound,
+    nfs_argop4 *argops,
+    nfs_resop4 *resops,
+    const char *tag)
+{
+    /* initialize args */
+    compound->args.tag_len = (uint32_t)strlen(tag);
+    memcpy(compound->args.tag, tag, compound->args.tag_len);
+    compound->args.minorversion = 1;
+    compound->args.argarray_count = 0;
+    compound->args.argarray = argops;
+
+    /* initialize results */
+    ZeroMemory(&compound->res, sizeof(nfs41_compound_res));
+    compound->res.tag_len = NFS4_OPAQUE_LIMIT;
+    compound->res.resarray_count = 0;
+    compound->res.resarray = resops;
+}
+
+void compound_add_op(
+    nfs41_compound *compound,
+    uint32_t opnum,
+    void *arg,
+    void *res)
+{
+    const uint32_t i = compound->args.argarray_count++;
+    const uint32_t j = compound->res.resarray_count++;
+    compound->args.argarray[i].op = opnum;
+    compound->args.argarray[i].arg = arg;
+    compound->res.resarray[j].op = opnum;
+    compound->res.resarray[j].res = res;
+}
+
+/* Due to the possibility of replays, we might get a response to a different
+ * call than the one we're expecting.  If we don't have a way to check for
+ * this, we'll likely crash trying to decode into the wrong structures.
+ * This function copies the number of operations and all of the operation
+ * numbers from the compound arguments into the response, so we can verify
+ * them on decode and fail before doing any damage. */
+static void set_expected_res(
+    nfs41_compound *compound)
+{
+    uint32_t i;
+    compound->res.resarray_count = compound->args.argarray_count;
+    for (i = 0; i < compound->res.resarray_count; i++)
+        compound->res.resarray[i].op = compound->args.argarray[i].op;
+}
+
+
+static int create_new_rpc_auth(nfs41_session *session, uint32_t op,
+                               nfs41_secinfo_info *secinfo)
+{
+    AUTH *auth = NULL;
+    int status = ERROR_NETWORK_UNREACHABLE, i;
+    uint32_t sec_flavor;
+
+    for (i = 0; i < MAX_SECINFOS; i++) { 
+        if (!secinfo[i].sec_flavor && !secinfo[i].type)
+            goto out;
+        if (secinfo[i].sec_flavor == RPCSEC_GSS) {
+            auth = authsspi_create_default(session->client->rpc->rpc, 
+                        session->client->rpc->server_name, secinfo[i].type);
+            if (auth == NULL) {
+                eprintf("handle_wrongsecinfo_noname: authsspi_create_default for "
+                        "gsstype %s failed\n", gssauth_string(secinfo[i].type));
+                continue;
+            }
+            sec_flavor = secinfo[i].type;
+        } else {
+            char machname[MAXHOSTNAMELEN + 1];
+            gid_t gids[1];
+            if (gethostname(machname, sizeof(machname)) == -1) {
+                eprintf("nfs41_rpc_clnt_create: gethostname failed\n");
+                continue;
+            }
+            machname[sizeof(machname) - 1] = '\0';
+            auth = authsys_create(machname, session->client->rpc->uid, 
+                        session->client->rpc->gid, 0, gids);
+            if (auth == NULL) {
+                eprintf("handle_wrongsecinfo_noname: authsys_create failed\n");
+                continue;
+            }
+            sec_flavor = AUTH_SYS;
+        }
+        AcquireSRWLockExclusive(&session->client->rpc->lock);
+        session->client->rpc->sec_flavor = sec_flavor;
+        session->client->rpc->rpc->cl_auth = auth;
+        ReleaseSRWLockExclusive(&session->client->rpc->lock);
+        status = 0;
+        break;
+    }
+out:
+    return status;
+}
+
+int compound_encode_send_decode(
+    nfs41_session *session,
+    nfs41_compound *compound,
+    bool_t try_recovery)
+{
+    int status, retry_count = 0, delayby = 0, secinfo_status;
+    nfs41_sequence_args *args = (nfs41_sequence_args *)
+        compound->args.argarray[0].arg;
+    uint32_t saved_sec_flavor;
+    AUTH *saved_auth;
+    int op1 = compound->args.argarray[0].op;
+
+retry:
+    /* send compound */
+    retry_count++;
+    set_expected_res(compound);
+    status = nfs41_send_compound(session->client->rpc,
+        (char *)&compound->args, (char *)&compound->res);
+    // bump sequence number if sequence op succeeded.
+    if (compound->res.resarray_count > 0 && 
+            compound->res.resarray[0].op == OP_SEQUENCE) {
+        nfs41_sequence_res *seq = 
+            (nfs41_sequence_res *)compound->res.resarray[0].res;
+        if (seq->sr_status == NFS4_OK) {
+            // returned slotid must be the same we sent
+            if (seq->sr_resok4.sr_slotid != args->sa_slotid) {
+                eprintf("[session] sr_slotid=%d != sa_slotid=%d\n",
+                    seq->sr_resok4.sr_slotid, args->sa_slotid);
+                status = NFS4ERR_IO;
+                goto out_free_slot;
+            }
+            // returned sessionid must be the same we sent
+            if (memcmp(seq->sr_resok4.sr_sessionid, args->sa_sessionid, 
+                    NFS4_SESSIONID_SIZE)) {
+                eprintf("[session] sr_sessionid != sa_sessionid\n");
+                print_hexbuf(1, (unsigned char *)"sr_sessionid", 
+                    seq->sr_resok4.sr_sessionid, NFS4_SESSIONID_SIZE);
+                print_hexbuf(1, (unsigned char *)"sa_sessionid", 
+                    args->sa_sessionid, NFS4_SESSIONID_SIZE);
+                status = NFS4ERR_IO;
+                goto out_free_slot;
+            }
+            if (seq->sr_resok4.sr_status_flags) 
+                print_sr_status_flags(1, seq->sr_resok4.sr_status_flags);
+
+            nfs41_session_bump_seq(session, args->sa_slotid,
+                seq->sr_resok4.sr_target_highest_slotid);
+
+            /* check sequence status flags for state revocation */
+            if (try_recovery && seq->sr_resok4.sr_status_flags)
+                nfs41_recover_sequence_flags(session,
+                    seq->sr_resok4.sr_status_flags);
+        }
+    }
+
+    if (status) {
+        eprintf("nfs41_send_compound failed %d for seqid=%d, slotid=%d\n", 
+            status, args->sa_sequenceid, args->sa_slotid);
+        status = NFS4ERR_IO;
+        goto out_free_slot;
+    }
+
+    if (compound->res.status != NFS4_OK)
+        dprintf(1, "\n################ %s ################\n\n",
+            nfs_error_string(compound->res.status));
+
+    switch (compound->res.status) {
+    case NFS4_OK:
+        break;
+
+    case NFS4ERR_STALE_CLIENTID:
+        if (!try_recovery)
+            goto out;
+        if (!nfs41_recovery_start_or_wait(session->client))
+            goto do_retry;
+        // try to create a new client
+        status = nfs41_client_renew(session->client);
+
+        nfs41_recovery_finish(session->client);
+        if (status) {
+            eprintf("nfs41_client_renew() failed with %d\n", status);
+            status = ERROR_BAD_NET_RESP;
+            goto out;
+        }
+        if (op1 == OP_CREATE_SESSION) {
+            nfs41_create_session_args *csa = (nfs41_create_session_args*)
+                compound->args.argarray[0].arg;
+            AcquireSRWLockShared(&session->client->exid_lock);
+            csa->csa_clientid = session->client->clnt_id;
+            csa->csa_sequence = session->client->seq_id;
+            AcquireSRWLockShared(&session->client->exid_lock);
+        }
+        goto do_retry;
+
+    case NFS4ERR_BADSESSION:
+        if (!try_recovery)
+            goto out;
+        if (!nfs41_recovery_start_or_wait(session->client))
+            goto do_retry;
+        // try to create a new session
+        status = nfs41_recover_session(session, FALSE);
+
+        nfs41_recovery_finish(session->client);
+        if (status) {
+            eprintf("nfs41_recover_session() failed with %d\n", status);
+            status = ERROR_BAD_NET_RESP;
+            goto out;
+        }
+        goto do_retry;
+
+    case NFS4ERR_EXPIRED: /* revoked by lease expiration */
+    case NFS4ERR_BAD_STATEID:
+    case NFS4ERR_STALE_STATEID: /* server reboot */
+        if (op1 == OP_SEQUENCE)
+            nfs41_session_free_slot(session, args->sa_slotid);
+        if (try_recovery && nfs41_recover_stateid(session,
+                &compound->args.argarray[compound->res.resarray_count-1]))
+            goto do_retry;
+        goto out;
+
+    case NFS4ERR_BADSLOT:
+        /* free the slot and retry with a new one */
+        if (op1 != OP_SEQUENCE || nfs41_session_bad_slot(session, args))
+            goto out;
+        goto retry;
+
+    case NFS4ERR_GRACE:
+    case NFS4ERR_DELAY:
+#define RETRY_INDEFINITELY
+#ifndef RETRY_INDEFINITELY
+#define NUMBER_2_RETRY 19
+#endif
+
+#ifndef RETRY_INDEFINITELY
+        if (retry_count < NUMBER_2_RETRY) {
+#endif
+            if (op1 == OP_SEQUENCE)
+                nfs41_session_free_slot(session, args->sa_slotid);
+            if (compound->res.status == NFS4ERR_GRACE)
+                delayby = 5000;
+            else
+                delayby = 500*retry_count;
+            dprintf(1, "Compound returned %s: sleeping for %ums..\n", 
+                (compound->res.status==NFS4ERR_GRACE)?"NFS4ERR_GRACE":"NFS4ERR_DELAY",
+                delayby);
+            Sleep(delayby);
+            dprintf(1, "Attempting to resend compound.\n");
+            goto do_retry;
+#ifndef RETRY_INDEFINITELY
+        }
+#endif
+        break;
+
+    case NFS4ERR_FHEXPIRED: /* TODO: recover expired volatile filehandles */
+        status = NFS4ERR_STALE; /* for now, treat them as ERR_STALE */
+        /* no break */
+    case NFS4ERR_STALE:
+        {
+            nfs_argop4 *argarray = compound->args.argarray;
+            struct nfs41_name_cache *name_cache =
+                session_name_cache(session);
+            nfs41_putfh_args *putfh;
+            uint32_t i, start = 0;
+
+            /* NFS4ERR_STALE generally comes from a PUTFH operation. in
+             * this case, remove its filehandle from the name cache. but
+             * because COMPOUNDs are not atomic, a file can be removed
+             * between PUTFH and the operation that uses it. in this
+             * case, we can't tell which PUTFH operation is to blame, so
+             * we must invalidate filehandles of all PUTFH operations in
+             * the COMPOUND */
+
+            if (argarray[compound->res.resarray_count-1].op == OP_PUTFH)
+                start = compound->res.resarray_count-1;
+
+            for (i = start; i < compound->res.resarray_count; i++) {
+                if (argarray[i].op == OP_PUTFH) {
+                    putfh = (nfs41_putfh_args*)argarray[i].arg;
+
+                    if (!putfh->in_recovery && putfh->file->path)
+                        nfs41_name_cache_remove_stale(name_cache,
+                            session, putfh->file->path);
+                }
+            }
+        }
+        break;
+    case NFS4ERR_WRONGSEC:
+        {
+            nfs41_secinfo_info secinfo[MAX_SECINFOS] = { 0 };
+            uint32_t rcount = compound->res.resarray_count;
+            nfs_argop4 *argarray = compound->args.argarray;
+            uint32_t op = argarray[rcount-1].op;
+            nfs41_putfh_args *putfh;
+            nfs41_path_fh *file = NULL;
+            switch(op) {
+            case OP_PUTFH:
+            case OP_RESTOREFH:
+            case OP_LINK:
+            case OP_RENAME:
+            case OP_PUTROOTFH:
+            case OP_LOOKUP:
+            case OP_OPEN:
+            case OP_SECINFO_NO_NAME:
+            case OP_SECINFO:
+                if (op1 == OP_SEQUENCE)
+                    nfs41_session_free_slot(session, args->sa_slotid);
+                /* from: 2.6.3.1.1.5.  Put Filehandle Operation + SECINFO/SECINFO_NO_NAME
+                 * The NFSv4.1 server MUST NOT return NFS4ERR_WRONGSEC to a put
+                 * filehandle operation that is immediately followed by SECINFO or
+                 * SECINFO_NO_NAME.  The NFSv4.1 server MUST NOT return NFS4ERR_WRONGSEC
+                 * from SECINFO or SECINFO_NO_NAME.
+                 */
+                if (op1 == OP_SEQUENCE &&
+                        (argarray[1].op == OP_PUTFH || 
+                        argarray[1].op == OP_PUTROOTFH) &&
+                        (argarray[2].op == OP_SECINFO_NO_NAME ||
+                        argarray[2].op == OP_SECINFO)) {
+                    dprintf(1, "SECINFO: BROKEN SERVER\n");
+                    goto out;
+                }
+                if (!try_recovery)
+                    goto out;
+                if (!nfs41_recovery_start_or_wait(session->client))
+                    goto do_retry;
+
+                saved_sec_flavor = session->client->rpc->sec_flavor;
+                saved_auth = session->client->rpc->rpc->cl_auth;
+                if (op == OP_LOOKUP || op == OP_OPEN) {
+                    const nfs41_component *name;
+                    nfs41_path_fh tmp = { 0 };                   
+                    nfs41_getfh_res *getfh;
+                    nfs41_lookup_args *largs;
+                    nfs41_op_open_args *oargs;
+                    if (argarray[rcount-2].op == OP_PUTFH) {
+                        putfh = (nfs41_putfh_args *)argarray[rcount-2].arg;
+                        file = putfh->file;
+                    } else if (argarray[rcount-2].op == OP_GETATTR &&
+                               argarray[rcount-3].op == OP_GETFH) {
+                        getfh = (nfs41_getfh_res *)compound->res.resarray[rcount-3].res;
+                        memcpy(&tmp.fh, getfh->fh, sizeof(nfs41_fh));
+                        file = &tmp;
+                    }
+                    else {
+                        nfs41_recovery_finish(session->client);
+                        goto out;
+                    }
+
+                    if (op == OP_LOOKUP) {
+                        largs = (nfs41_lookup_args *)argarray[rcount-1].arg;
+                        name = largs->name;
+                    } else if (op == OP_OPEN) {
+                        oargs = (nfs41_op_open_args *)argarray[rcount-1].arg;
+                        name = oargs->claim->u.null.filename;
+                    }
+                    secinfo_status = nfs41_secinfo(session, file, name, secinfo);
+                    if (secinfo_status) {
+                        eprintf("nfs41_secinfo failed with %d\n", secinfo_status);
+                        nfs41_recovery_finish(session->client);
+                        if (secinfo_status == NFS4ERR_BADSESSION) {
+                            if (op1 == OP_SEQUENCE)
+                                nfs41_session_free_slot(session, args->sa_slotid);
+                            goto do_retry;
+                        }
+                        goto out_free_slot;
+                    }
+                }
+                else {                    
+                    if (op == OP_PUTFH) {
+                        putfh = (nfs41_putfh_args *)argarray[rcount-1].arg;
+                        file = putfh->file;
+                    } 
+                    secinfo_status = nfs41_secinfo_noname(session, file, secinfo);
+                    if (secinfo_status) {
+                        eprintf("nfs41_secinfo_noname failed with %d\n", 
+                            secinfo_status);
+                        nfs41_recovery_finish(session->client);
+                        if (op1 == OP_SEQUENCE)
+                            nfs41_session_free_slot(session, args->sa_slotid);
+                        goto out_free_slot;
+                    }
+                }
+                secinfo_status = create_new_rpc_auth(session, op, secinfo);
+                if (!secinfo_status) {
+                    auth_destroy(saved_auth);
+                    nfs41_recovery_finish(session->client);
+                    // Need to retry only 
+                    goto do_retry;
+                } else {
+                    AcquireSRWLockExclusive(&session->client->rpc->lock);
+                    session->client->rpc->sec_flavor = saved_sec_flavor;
+                    session->client->rpc->rpc->cl_auth = saved_auth;
+                    ReleaseSRWLockExclusive(&session->client->rpc->lock);
+                    nfs41_recovery_finish(session->client);
+                }                
+                break;
+            }
+        }
+    }
+    if (compound->res.resarray[0].op == OP_SEQUENCE) {
+        nfs41_sequence_res *seq = 
+            (nfs41_sequence_res *)compound->res.resarray[0].res;
+        if (seq->sr_status == NFS4_OK && session->client->rpc->needcb &&
+                (seq->sr_resok4.sr_status_flags & SEQ4_STATUS_CB_PATH_DOWN)) {
+            nfs41_session_free_slot(session, args->sa_slotid);
+            nfs41_bind_conn_to_session(session->client->rpc,
+                session->session_id, CDFC4_BACK_OR_BOTH);
+            goto out;
+        }
+    }
+out_free_slot:
+    if (op1 == OP_SEQUENCE)
+        nfs41_session_free_slot(session, args->sa_slotid);
+out:
+    return status;
+
+do_retry:
+    if (compound->res.resarray[0].op == OP_SEQUENCE)
+        nfs41_session_get_slot(session, &args->sa_slotid,
+            &args->sa_sequenceid, &args->sa_highest_slotid);
+    goto retry;
+}
diff --git a/reactos/base/services/nfsd/nfs41_compound.h b/reactos/base/services/nfsd/nfs41_compound.h
new file mode 100644 (file)
index 0000000..bd4e3c3
--- /dev/null
@@ -0,0 +1,80 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_COMPOUND_H__
+#define __NFS41_DAEMON_COMPOUND_H__
+
+#include "nfs41.h"
+
+
+/* COMPOUND */
+typedef struct __nfs_argop4 {
+    uint32_t                op;
+    void                    *arg;
+} nfs_argop4;
+
+typedef struct __nfs41_compound_args {
+    uint32_t                tag_len;
+    unsigned char           tag[NFS4_OPAQUE_LIMIT];
+    uint32_t                minorversion;
+    uint32_t                argarray_count;
+    nfs_argop4              *argarray; /* <> */
+} nfs41_compound_args;
+
+typedef struct __nfs_resop4 {
+    uint32_t                op;
+    void                    *res;
+} nfs_resop4;
+
+typedef struct __nfs41_compound_res {
+    uint32_t                status;
+    uint32_t                tag_len;
+    unsigned char           tag[NFS4_OPAQUE_LIMIT];
+    uint32_t                resarray_count;
+    nfs_resop4              *resarray; /* <> */
+} nfs41_compound_res;
+
+typedef struct __nfs41_compound {
+    nfs41_compound_args     args;
+    nfs41_compound_res      res;
+} nfs41_compound;
+
+
+int compound_error(int status);
+
+void compound_init(
+    nfs41_compound *compound,
+    nfs_argop4 *argops,
+    nfs_resop4 *resops,
+    const char *tag);
+
+void compound_add_op(
+    nfs41_compound *compound,
+    uint32_t opnum,
+    void *arg,
+    void *res);
+
+int compound_encode_send_decode(
+    nfs41_session *session,
+    nfs41_compound *compound,
+    bool_t try_recovery);
+
+#endif /* __NFS41_DAEMON_COMPOUND_H__ */
diff --git a/reactos/base/services/nfsd/nfs41_const.h b/reactos/base/services/nfsd/nfs41_const.h
new file mode 100644 (file)
index 0000000..c47b833
--- /dev/null
@@ -0,0 +1,399 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_NFS_CONST_H__
+#define __NFS41_NFS_CONST_H__
+
+
+/*
+ * Sizes
+ */
+#define NFS4_FHSIZE             128
+#define NFS4_VERIFIER_SIZE      8
+#define NFS4_OPAQUE_LIMIT       1024
+#define NFS4_SESSIONID_SIZE     16
+#define NFS4_STATEID_OTHER      12
+#define NFS4_EASIZE             256
+#define NFS4_EANAME_SIZE        128
+
+
+#define NFS41_MAX_FILEIO_SIZE   (1024 * 1024)
+#define NFS41_MAX_SERVER_CACHE  1024
+#define NFS41_MAX_RPC_REQS      128
+
+#define UPCALL_BUF_SIZE         2048
+
+/* MaximumComponentNameLength reported for FileFsAttributeInformation */
+#define NFS41_MAX_COMPONENT_LEN     256
+#define NFS41_MAX_PATH_LEN          1280
+
+#define NFS41_HOSTNAME_LEN          64
+#define NFS41_ADDRS_PER_SERVER      4
+
+/* max length of ipv6 address       48
+ * sizeof(".255.255")              + 8 */
+#define NFS41_UNIVERSAL_ADDR_LEN    56
+
+/* "udp" "tcp" "udp6" "tcp6" */
+#define NFS41_NETWORK_ID_LEN        4
+
+/* msdn: There is a maximum of 31 reparse points (and
+ * therefore symbolic links) allowed in a particular path. */
+#define NFS41_MAX_SYMLINK_DEPTH     31
+
+
+/* 424 bytes: max rpc header for reply with data */
+/* 32 bytes: max COMPOUND response */
+/* 40 bytes: max SEQUENCE response */
+/* 4 bytes: max PUTFH response */
+/* 12 bytes: max READ response */
+#define READ_OVERHEAD       512
+
+/* 840 bytes: max rpc header for call */
+/* 32 bytes: max COMPOUND request */
+/* 32 bytes: max SEQUENCE request */
+/* 132 bytes: max PUTFH request */
+/* 32 bytes: max WRITE request */
+#define WRITE_OVERHEAD      1068
+
+
+#define NFS41_RPC_PROGRAM   100003
+#define NFS41_RPC_VERSION   4
+#define NFS41_RPC_CBPROGRAM 0x2358
+
+
+/*
+ * Error status
+ */
+enum nfsstat4 {
+    NFS4_OK                     = 0,        /* everything is okay      */
+    NFS4ERR_PERM                = 1,        /* caller not privileged   */
+    NFS4ERR_NOENT               = 2,        /* no such file/directory  */
+    NFS4ERR_IO                  = 5,        /* hard I/O error          */
+    NFS4ERR_NXIO                = 6,        /* no such device          */
+    NFS4ERR_ACCESS              = 13,       /* access denied           */
+    NFS4ERR_EXIST               = 17,       /* file already exists     */
+    NFS4ERR_XDEV                = 18,       /* different filesystems   */
+
+    NFS4ERR_NOTDIR              = 20,       /* should be a directory   */
+    NFS4ERR_ISDIR               = 21,       /* should not be directory */
+    NFS4ERR_INVAL               = 22,       /* invalid argument        */
+    NFS4ERR_FBIG                = 27,       /* file exceeds server max */
+    NFS4ERR_NOSPC               = 28,       /* no space on filesystem  */
+    NFS4ERR_ROFS                = 30,       /* read-only filesystem    */
+    NFS4ERR_MLINK               = 31,       /* too many hard links     */
+    NFS4ERR_NAMETOOLONG         = 63,       /* name exceeds server max */
+    NFS4ERR_NOTEMPTY            = 66,       /* directory not empty     */
+    NFS4ERR_DQUOT               = 69,       /* hard quota limit reached*/
+    NFS4ERR_STALE               = 70,       /* file no longer exists   */
+    NFS4ERR_BADHANDLE           = 10001,    /* Illegal filehandle      */
+    NFS4ERR_BAD_COOKIE          = 10003,    /* READDIR cookie is stale */
+    NFS4ERR_NOTSUPP             = 10004,    /* operation not supported */
+    NFS4ERR_TOOSMALL            = 10005,    /* response limit exceeded */
+    NFS4ERR_SERVERFAULT         = 10006,    /* undefined server error  */
+    NFS4ERR_BADTYPE             = 10007,    /* type invalid for CREATE */
+    NFS4ERR_DELAY               = 10008,    /* file "busy" - retry     */
+    NFS4ERR_SAME                = 10009,    /* nverify says attrs same */
+    NFS4ERR_DENIED              = 10010,    /* lock unavailable        */
+    NFS4ERR_EXPIRED             = 10011,    /* lock lease expired      */
+    NFS4ERR_LOCKED              = 10012,    /* I/O failed due to lock  */
+    NFS4ERR_GRACE               = 10013,    /* in grace period         */
+    NFS4ERR_FHEXPIRED           = 10014,    /* filehandle expired      */
+    NFS4ERR_SHARE_DENIED        = 10015,    /* share reserve denied    */
+    NFS4ERR_WRONGSEC            = 10016,    /* wrong security flavor   */
+    NFS4ERR_CLID_INUSE          = 10017,    /* clientid in use         */
+
+    /* NFS4ERR_RESOURCE is not a valid error in NFSv4.1 */
+    NFS4ERR_RESOURCE            = 10018,    /* resource exhaustion     */
+    NFS4ERR_MOVED               = 10019,    /* filesystem relocated    */
+    NFS4ERR_NOFILEHANDLE        = 10020,    /* current FH is not set   */
+    NFS4ERR_MINOR_VERS_MISMATCH = 10021,    /* minor vers not supp     */
+    NFS4ERR_STALE_CLIENTID      = 10022,    /* server has rebooted     */
+    NFS4ERR_STALE_STATEID       = 10023,    /* server has rebooted     */
+    NFS4ERR_OLD_STATEID         = 10024,    /* state is out of sync    */
+    NFS4ERR_BAD_STATEID         = 10025,    /* incorrect stateid       */
+    NFS4ERR_BAD_SEQID           = 10026,    /* request is out of seq.  */
+    NFS4ERR_NOT_SAME            = 10027,    /* verify - attrs not same */
+    NFS4ERR_LOCK_RANGE          = 10028,    /* overlapping lock range  */
+    NFS4ERR_SYMLINK             = 10029,    /* should be file/directory*/
+    NFS4ERR_RESTOREFH           = 10030,    /* no saved filehandle     */
+    NFS4ERR_LEASE_MOVED         = 10031,    /* some filesystem moved   */
+    NFS4ERR_ATTRNOTSUPP         = 10032,    /* recommended attr not sup*/
+    NFS4ERR_NO_GRACE            = 10033,    /* reclaim outside of grace*/
+    NFS4ERR_RECLAIM_BAD         = 10034,    /* reclaim error at server */
+    NFS4ERR_RECLAIM_CONFLICT    = 10035,    /* conflict on reclaim     */
+    NFS4ERR_BADXDR              = 10036,    /* XDR decode failed       */
+    NFS4ERR_LOCKS_HELD          = 10037,    /* file locks held at CLOSE*/
+    NFS4ERR_OPENMODE            = 10038,    /* conflict in OPEN and I/O*/
+    NFS4ERR_BADOWNER            = 10039,    /* owner translation bad   */
+    NFS4ERR_BADCHAR             = 10040,    /* utf-8 char not supported*/
+    NFS4ERR_BADNAME             = 10041,    /* name not supported      */
+    NFS4ERR_BAD_RANGE           = 10042,    /* lock range not supported*/
+    NFS4ERR_LOCK_NOTSUPP        = 10043,    /* no atomic up/downgrade  */
+    NFS4ERR_OP_ILLEGAL          = 10044,    /* undefined operation     */
+    NFS4ERR_DEADLOCK            = 10045,    /* file locking deadlock   */
+    NFS4ERR_FILE_OPEN           = 10046,    /* open file blocks op.    */
+    NFS4ERR_ADMIN_REVOKED       = 10047,    /* lockowner state revoked */
+    NFS4ERR_CB_PATH_DOWN        = 10048,    /* callback path down      */
+
+    /* NFSv4.1 errors start here. */
+    NFS4ERR_BADIOMODE           = 10049,
+    NFS4ERR_BADLAYOUT           = 10050,
+    NFS4ERR_BAD_SESSION_DIGEST  = 10051,
+    NFS4ERR_BADSESSION          = 10052,
+    NFS4ERR_BADSLOT             = 10053,
+    NFS4ERR_COMPLETE_ALREADY    = 10054,
+    NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055,
+    NFS4ERR_DELEG_ALREADY_WANTED = 10056,
+    NFS4ERR_BACK_CHAN_BUSY      = 10057,    /*backchan reqs outstanding*/
+    NFS4ERR_LAYOUTTRYLATER      = 10058,
+    NFS4ERR_LAYOUTUNAVAILABLE   = 10059,
+    NFS4ERR_NOMATCHING_LAYOUT   = 10060,
+    NFS4ERR_RECALLCONFLICT      = 10061,
+    NFS4ERR_UNKNOWN_LAYOUTTYPE  = 10062,
+    NFS4ERR_SEQ_MISORDERED      = 10063,    /* unexpected seq.ID in req*/
+    NFS4ERR_SEQUENCE_POS        = 10064,    /* [CB_]SEQ. op not 1st op */
+    NFS4ERR_REQ_TOO_BIG         = 10065,    /* request too big         */
+    NFS4ERR_REP_TOO_BIG         = 10066,    /* reply too big           */
+    NFS4ERR_REP_TOO_BIG_TO_CACHE = 10067,   /* rep. not all cached     */
+    NFS4ERR_RETRY_UNCACHED_REP  = 10068,    /* retry & rep. uncached   */
+    NFS4ERR_UNSAFE_COMPOUND     = 10069,    /* retry/recovery too hard */
+    NFS4ERR_TOO_MANY_OPS        = 10070,    /*too many ops in [CB_]COMP*/
+    NFS4ERR_OP_NOT_IN_SESSION   = 10071,    /* op needs [CB_]SEQ. op   */
+    NFS4ERR_HASH_ALG_UNSUPP     = 10072,    /* hash alg. not supp.     */
+                                            /* Error 10073 is unused.  */
+    NFS4ERR_CLIENTID_BUSY       = 10074,    /* clientid has state      */
+    NFS4ERR_PNFS_IO_HOLE        = 10075,    /* IO to _SPARSE file hole */
+    NFS4ERR_SEQ_FALSE_RETRY     = 10076,    /* Retry != original req.  */
+    NFS4ERR_BAD_HIGH_SLOT       = 10077,    /* req has bad highest_slot*/
+    NFS4ERR_DEADSESSION         = 10078,    /*new req sent to dead sess*/
+    NFS4ERR_ENCR_ALG_UNSUPP     = 10079,    /* encr alg. not supp.     */
+    NFS4ERR_PNFS_NO_LAYOUT      = 10080,    /* I/O without a layout    */
+    NFS4ERR_NOT_ONLY_OP         = 10081,    /* addl ops not allowed    */
+    NFS4ERR_WRONG_CRED          = 10082,    /* op done by wrong cred   */
+    NFS4ERR_WRONG_TYPE          = 10083,    /* op on wrong type object */
+    NFS4ERR_DIRDELEG_UNAVAIL    = 10084,    /* delegation not avail.   */
+    NFS4ERR_REJECT_DELEG        = 10085,    /* cb rejected delegation  */
+    NFS4ERR_RETURNCONFLICT      = 10086,    /* layout get before return*/
+    NFS4ERR_DELEG_REVOKED       = 10087     /* deleg./layout revoked   */
+};
+
+#define MAKE_WORD0(XXX) (1 << XXX)
+#define MAKE_WORD1(XXX) (1 << (XXX-32))
+#define MAKE_WORD2(XXX) (1 << (XXX-64))
+
+enum {
+/*
+ * Mandatory Attributes
+ */
+    FATTR4_WORD0_SUPPORTED_ATTRS    = MAKE_WORD0(0),
+    FATTR4_WORD0_TYPE               = MAKE_WORD0(1),
+    FATTR4_WORD0_FH_EXPIRE_TYPE     = MAKE_WORD0(2),
+    FATTR4_WORD0_CHANGE             = MAKE_WORD0(3),
+    FATTR4_WORD0_SIZE               = MAKE_WORD0(4),
+    FATTR4_WORD0_LINK_SUPPORT       = MAKE_WORD0(5),
+    FATTR4_WORD0_SYMLINK_SUPPORT    = MAKE_WORD0(6),
+    FATTR4_WORD0_NAMED_ATTR         = MAKE_WORD0(7),
+    FATTR4_WORD0_FSID               = MAKE_WORD0(8),
+    FATTR4_WORD0_UNIQUE_HANDLES     = MAKE_WORD0(9),
+    FATTR4_WORD0_LEASE_TIME         = MAKE_WORD0(10),
+    FATTR4_WORD0_RDATTR_ERROR       = MAKE_WORD0(11),
+    FATTR4_WORD0_FILEHANDLE         = MAKE_WORD0(19),
+    FATTR4_WORD2_SUPPATTR_EXCLCREAT = MAKE_WORD2(75),
+
+/*
+ * Recommended Attributes
+ */
+    FATTR4_WORD0_ACL                = MAKE_WORD0(12),
+    FATTR4_WORD0_ACLSUPPORT         = MAKE_WORD0(13),
+    FATTR4_WORD0_ARCHIVE            = MAKE_WORD0(14),
+    FATTR4_WORD0_CANSETTIME         = MAKE_WORD0(15),
+    FATTR4_WORD0_CASE_INSENSITIVE   = MAKE_WORD0(16),
+    FATTR4_WORD0_CASE_PRESERVING    = MAKE_WORD0(17),
+    FATTR4_WORD0_CHOWN_RESTRICTED   = MAKE_WORD0(18),
+    FATTR4_WORD0_FILEID             = MAKE_WORD0(20),
+    FATTR4_WORD0_FILES_AVAIL        = MAKE_WORD0(21),
+    FATTR4_WORD0_FILES_FREE         = MAKE_WORD0(22),
+    FATTR4_WORD0_FILES_TOTAL        = MAKE_WORD0(23),
+    FATTR4_WORD0_FS_LOCATIONS       = MAKE_WORD0(24),
+    FATTR4_WORD0_HIDDEN             = MAKE_WORD0(25),
+    FATTR4_WORD0_HOMOGENEOUS        = MAKE_WORD0(26),
+    FATTR4_WORD0_MAXFILESIZE        = MAKE_WORD0(27),
+    FATTR4_WORD0_MAXLINK            = MAKE_WORD0(28),
+    FATTR4_WORD0_MAXNAME            = MAKE_WORD0(29),
+    FATTR4_WORD0_MAXREAD            = MAKE_WORD0(30),
+    FATTR4_WORD0_MAXWRITE           = MAKE_WORD0(31),
+    FATTR4_WORD1_MIMETYPE           = MAKE_WORD1(32),
+    FATTR4_WORD1_MODE               = MAKE_WORD1(33),
+    FATTR4_WORD1_NO_TRUNC           = MAKE_WORD1(34),
+    FATTR4_WORD1_NUMLINKS           = MAKE_WORD1(35),
+    FATTR4_WORD1_OWNER              = MAKE_WORD1(36),
+    FATTR4_WORD1_OWNER_GROUP        = MAKE_WORD1(37),
+    FATTR4_WORD1_QUOTA_AVAIL_HARD   = MAKE_WORD1(38),
+    FATTR4_WORD1_QUOTA_AVAIL_SOFT   = MAKE_WORD1(39),
+    FATTR4_WORD1_QUOTA_USED         = MAKE_WORD1(40),
+    FATTR4_WORD1_RAWDEV             = MAKE_WORD1(41),
+    FATTR4_WORD1_SPACE_AVAIL        = MAKE_WORD1(42),
+    FATTR4_WORD1_SPACE_FREE         = MAKE_WORD1(43),
+    FATTR4_WORD1_SPACE_TOTAL        = MAKE_WORD1(44),
+    FATTR4_WORD1_SPACE_USED         = MAKE_WORD1(45),
+    FATTR4_WORD1_SYSTEM             = MAKE_WORD1(46),
+    FATTR4_WORD1_TIME_ACCESS        = MAKE_WORD1(47),
+    FATTR4_WORD1_TIME_ACCESS_SET    = MAKE_WORD1(48),
+    FATTR4_WORD1_TIME_BACKUP        = MAKE_WORD1(49),
+    FATTR4_WORD1_TIME_CREATE        = MAKE_WORD1(50),
+    FATTR4_WORD1_TIME_DELTA         = MAKE_WORD1(51),
+    FATTR4_WORD1_TIME_METADATA      = MAKE_WORD1(52),
+    FATTR4_WORD1_TIME_MODIFY        = MAKE_WORD1(53),
+    FATTR4_WORD1_TIME_MODIFY_SET    = MAKE_WORD1(54),
+    FATTR4_WORD1_MOUNTED_ON_FILEID  = MAKE_WORD1(55),
+    FATTR4_WORD1_DIR_NOTIF_DELAY    = MAKE_WORD1(56),
+    FATTR4_WORD1_DIRENT_NOTIF_DELAY = MAKE_WORD1(57),
+    FATTR4_WORD1_DACL               = MAKE_WORD1(58),
+    FATTR4_WORD1_SACL               = MAKE_WORD1(59),
+    FATTR4_WORD1_CHANGE_POLICY      = MAKE_WORD1(60),
+    FATTR4_WORD1_FS_STATUS          = MAKE_WORD1(61),
+    FATTR4_WORD1_FS_LAYOUT_TYPE     = MAKE_WORD1(62),
+    FATTR4_WORD1_LAYOUT_HINT        = MAKE_WORD1(63),
+    FATTR4_WORD2_LAYOUT_TYPE        = MAKE_WORD2(64),
+    FATTR4_WORD2_LAYOUT_BLKSIZE     = MAKE_WORD2(65),
+    FATTR4_WORD2_LAYOUT_ALIGNMENT   = MAKE_WORD2(66),
+    FATTR4_WORD2_FS_LOCATIONS_INFO  = MAKE_WORD2(67),
+    FATTR4_WORD2_MDSTHRESHOLD       = MAKE_WORD2(68),
+    FATTR4_WORD2_RETENTION_GET      = MAKE_WORD2(69),
+    FATTR4_WORD2_RETENTION_SET      = MAKE_WORD2(70),
+    FATTR4_WORD2_RETENTEVT_GET      = MAKE_WORD2(71),
+    FATTR4_WORD2_RETENTEVT_SET      = MAKE_WORD2(72),
+    FATTR4_WORD2_RETENTION_HOLD     = MAKE_WORD2(73),
+    FATTR4_WORD2_MODE_SET_MASKED    = MAKE_WORD2(74),
+    FATTR4_WORD2_FS_CHARSET_CAP     = MAKE_WORD2(76),
+};
+
+/*
+ * File types
+ */
+enum nfs_ftype4 {
+    NF4REG          = 1,    /* Regular File */
+    NF4DIR          = 2,    /* Directory */
+    NF4BLK          = 3,    /* Special File - block device */
+    NF4CHR          = 4,    /* Special File - character device */
+    NF4LNK          = 5,    /* Symbolic Link */
+    NF4SOCK         = 6,    /* Special File - socket */
+    NF4FIFO         = 7,    /* Special File - fifo */
+    NF4ATTRDIR      = 8,    /* Attribute Directory */
+    NF4NAMEDATTR    = 9,    /* Named Attribute */
+
+    NFS_FTYPE_MASK  = 0xF
+};
+
+#define CREATE_SESSION4_FLAG_PERSIST        0x00000001
+#define CREATE_SESSION4_FLAG_CONN_BACK_CHAN 0x00000002
+#define CREATE_SESSION4_FLAG_CONN_RDMA      0x00000004
+
+/* ACLS aclsupport attribute values */
+#define ACL4_SUPPORT_ALLOW_ACL  0x00000001
+#define ACL4_SUPPORT_DENY_ACL   0x00000002
+#define ACL4_SUPPORT_AUDIT_ACL  0x00000004
+#define ACL4_SUPPORT_ALARM_ACL  0x00000008
+
+/* ACLS acetype4 field constants */
+#define ACE4_ACCESS_ALLOWED_ACE_TYPE      0x00000000
+#define ACE4_ACCESS_DENIED_ACE_TYPE       0x00000001
+#define ACE4_SYSTEM_AUDIT_ACE_TYPE        0x00000002
+#define ACE4_SYSTEM_ALARM_ACE_TYPE        0x00000003
+
+/* ACLS acemask4 field constants */
+#define ACE4_READ_DATA            0x00000001
+#define ACE4_LIST_DIRECTORY       0x00000001
+#define ACE4_WRITE_DATA           0x00000002
+#define ACE4_ADD_FILE             0x00000002
+#define ACE4_APPEND_DATA          0x00000004
+#define ACE4_ADD_SUBDIRECTORY     0x00000004
+#define ACE4_READ_NAMED_ATTRS     0x00000008
+#define ACE4_WRITE_NAMED_ATTRS    0x00000010
+#define ACE4_EXECUTE              0x00000020
+#define ACE4_DELETE_CHILD         0x00000040
+#define ACE4_READ_ATTRIBUTES      0x00000080
+#define ACE4_WRITE_ATTRIBUTES     0x00000100
+#define ACE4_WRITE_RETENTION      0x00000200
+#define ACE4_WRITE_RETENTION_HOLD 0x00000400
+
+#define ACE4_DELETE               0x00010000
+#define ACE4_READ_ACL             0x00020000
+#define ACE4_WRITE_ACL            0x00040000
+#define ACE4_WRITE_OWNER          0x00080000
+#define ACE4_SYNCHRONIZE          0x00100000
+
+#define ACE4_ALL_FILE ACE4_READ_DATA|ACE4_WRITE_DATA|ACE4_APPEND_DATA| \
+        ACE4_READ_NAMED_ATTRS|ACE4_WRITE_NAMED_ATTRS|ACE4_EXECUTE| \
+        ACE4_READ_ATTRIBUTES|ACE4_WRITE_ATTRIBUTES| \
+        ACE4_DELETE|ACE4_READ_ACL|ACE4_WRITE_ACL|ACE4_WRITE_OWNER| \
+        ACE4_SYNCHRONIZE
+#define ACE4_ALL_DIR ACE4_READ_DATA|ACE4_WRITE_DATA|ACE4_APPEND_DATA| \
+        ACE4_READ_NAMED_ATTRS|ACE4_WRITE_NAMED_ATTRS|ACE4_EXECUTE| \
+        ACE4_DELETE_CHILD|ACE4_READ_ATTRIBUTES|ACE4_WRITE_ATTRIBUTES| \
+        ACE4_DELETE|ACE4_READ_ACL|ACE4_WRITE_ACL|ACE4_WRITE_OWNER| \
+        ACE4_SYNCHRONIZE
+
+#define ACE4_GENERIC_READ ACE4_READ_DATA|ACE4_READ_NAMED_ATTRS| \
+        ACE4_READ_ATTRIBUTES|ACE4_READ_ACL|ACE4_SYNCHRONIZE
+#define ACE4_GENERIC_WRITE ACE4_WRITE_DATA|ACE4_WRITE_NAMED_ATTRS| \
+        ACE4_WRITE_ATTRIBUTES|ACE4_READ_ACL|ACE4_SYNCHRONIZE
+#define ACE4_GENERIC_EXECUTE ACE4_EXECUTE|ACE4_READ_ATTRIBUTES| \
+        ACE4_READ_ACL|ACE4_SYNCHRONIZE
+        
+
+
+#define ACE4_FILE_ALL_ACCESS ACE4_READ_DATA|ACE4_LIST_DIRECTORY| \
+        ACE4_WRITE_DATA|ACE4_ADD_FILE|ACE4_APPEND_DATA|ACE4_ADD_SUBDIRECTORY| \
+        ACE4_READ_NAMED_ATTRS|ACE4_WRITE_NAMED_ATTRS|ACE4_EXECUTE| \
+        ACE4_READ_ATTRIBUTES|ACE4_WRITE_ATTRIBUTES
+
+/* ACLS aceflag4 field constants */
+#define ACE4_FILE_INHERIT_ACE             0x00000001
+#define ACE4_DIRECTORY_INHERIT_ACE        0x00000002
+#define ACE4_NO_PROPAGATE_INHERIT_ACE     0x00000004
+#define ACE4_INHERIT_ONLY_ACE             0x00000008
+#define ACE4_SUCCESSFUL_ACCESS_ACE_FLAG   0x00000010
+#define ACE4_FAILED_ACCESS_ACE_FLAG       0x00000020
+#define ACE4_IDENTIFIER_GROUP             0x00000040
+#define ACE4_INHERITED_ACE                0x00000080
+
+/* ACLS well-defined WHOs */
+#define ACE4_OWNER "OWNER@"
+#define ACE4_GROUP "GROUP@"
+#define ACE4_EVERYONE "EVERYONE@"
+#define ACE4_INTERACTIVE "INTERACTIVE@"
+#define ACE4_NETWORK "NETWORK@"
+#define ACE4_DIALUP "DIALUP@"
+#define ACE4_BATCH "BATCH@"
+#define ACE4_ANONYMOUS "ANONYMOUS@"
+#define ACE4_AUTHENTICATED "AUTHENTICATED@"
+#define ACE4_SERVICE "SERVICE@"
+#define ACE4_NOBODY "nobody"
+
+/* ACLE nfsacl41 aclflag4 constants */
+#define ACL4_AUTO_INHERIT         0x00000001
+#define ACL4_PROTECTED            0x00000002
+#define ACL4_DEFAULTED            0x00000004
+
+
+#endif /* !__NFS41_NFS_CONST_H__ */
diff --git a/reactos/base/services/nfsd/nfs41_daemon.c b/reactos/base/services/nfsd/nfs41_daemon.c
new file mode 100644 (file)
index 0000000..7ad4ad1
--- /dev/null
@@ -0,0 +1,501 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <process.h>
+#include <tchar.h>
+#include <stdio.h>
+
+#include <devioctl.h>
+#include <lmcons.h> /* UNLEN for GetUserName() */
+#include <iphlpapi.h> /* for GetNetworkParam() */
+#include "nfs41_driver.h" /* for NFS41_USER_DEVICE_NAME_A */
+#include "nfs41_np.h" /* for NFS41NP_SHARED_MEMORY */
+
+#include "idmap.h"
+#include "daemon_debug.h"
+#include "upcall.h"
+#include "util.h"
+
+#define MAX_NUM_THREADS 128
+DWORD NFS41D_VERSION = 0;
+
+static const char FILE_NETCONFIG[] = "C:\\ReactOS\\System32\\drivers\\etc\\netconfig";
+
+/* Globals */
+char localdomain_name[NFS41_HOSTNAME_LEN];
+int default_uid = 666;
+int default_gid = 777;
+
+#ifndef STANDALONE_NFSD //make sure to define it in "sources" not here
+#include "service.h"
+HANDLE  stop_event = NULL;
+#endif
+typedef struct _nfs41_process_thread {
+    HANDLE handle;
+    uint32_t tid;
+} nfs41_process_thread;
+
+static int map_user_to_ids(nfs41_idmapper *idmapper, uid_t *uid, gid_t *gid)
+{
+    char username[UNLEN + 1];
+    DWORD len = UNLEN + 1;
+    int status = NO_ERROR;
+
+    if (!GetUserNameA(username, &len)) {
+        status = GetLastError();
+        eprintf("GetUserName() failed with %d\n", status);
+        goto out;
+    }
+    dprintf(1, "map_user_to_ids: mapping user %s\n", username);
+
+    if (nfs41_idmap_name_to_ids(idmapper, username, uid, gid)) {
+        /* instead of failing for auth_sys, fall back to 'nobody' uid/gid */
+        *uid = default_uid;
+        *gid = default_gid;
+    }
+out:
+    return status;
+}
+
+static unsigned int WINAPI thread_main(void *args) 
+{
+    nfs41_idmapper *idmapper = (nfs41_idmapper*)args;
+    DWORD status = 0;
+    HANDLE pipe;
+    // buffer used to process upcall, assumed to be fixed size. 
+    // if we ever need to handle non-cached IO, need to make it dynamic
+    unsigned char outbuf[UPCALL_BUF_SIZE], inbuf[UPCALL_BUF_SIZE]; 
+    DWORD inbuf_len = UPCALL_BUF_SIZE, outbuf_len;
+    nfs41_upcall upcall;
+
+    pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ | GENERIC_WRITE,
+        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+        0, NULL);
+    if (pipe == INVALID_HANDLE_VALUE)
+    {
+        eprintf("Unable to open upcall pipe %d\n", GetLastError());
+        return GetLastError();
+    }
+
+    while(1) {
+        status = DeviceIoControl(pipe, IOCTL_NFS41_READ, NULL, 0,
+            outbuf, UPCALL_BUF_SIZE, (LPDWORD)&outbuf_len, NULL);
+        if (!status) {
+            eprintf("IOCTL_NFS41_READ failed %d\n", GetLastError());
+            continue;
+        }
+
+        status = upcall_parse(outbuf, (uint32_t)outbuf_len, &upcall);
+        if (status) {
+            upcall.status = status;
+            goto write_downcall;
+        }
+
+        /* map username to uid/gid */
+        status = map_user_to_ids(idmapper, &upcall.uid, &upcall.gid);
+        if (status) {
+            upcall.status = status;
+            goto write_downcall;
+        }
+
+        if (upcall.opcode == NFS41_SHUTDOWN) {
+            printf("Shutting down..\n");
+            exit(0);
+        }
+
+        status = upcall_handle(&upcall);
+
+write_downcall:
+        dprintf(1, "writing downcall: xid=%lld opcode=%s status=%d "
+            "get_last_error=%d\n", upcall.xid, opcode2string(upcall.opcode),
+            upcall.status, upcall.last_error);
+
+        upcall_marshall(&upcall, inbuf, (uint32_t)inbuf_len, (uint32_t*)&outbuf_len);
+
+        dprintf(2, "making a downcall: outbuf_len %ld\n\n", outbuf_len);
+        status = DeviceIoControl(pipe, IOCTL_NFS41_WRITE,
+            inbuf, inbuf_len, NULL, 0, (LPDWORD)&outbuf_len, NULL);
+        if (!status) {
+            eprintf("IOCTL_NFS41_WRITE failed with %d xid=%lld opcode=%s\n", 
+                GetLastError(), upcall.xid, opcode2string(upcall.opcode));
+            upcall_cancel(&upcall);
+        }
+        if (upcall.status != NFSD_VERSION_MISMATCH)
+            upcall_cleanup(&upcall);
+    }
+    CloseHandle(pipe);
+
+    return GetLastError();
+}
+
+#ifndef STANDALONE_NFSD
+VOID ServiceStop()
+{
+   if (stop_event)
+      SetEvent(stop_event);
+}
+#endif
+
+typedef struct _nfsd_args {
+    bool_t ldap_enable;
+    int debug_level;
+} nfsd_args;
+
+static bool_t check_for_files()
+{
+    FILE *fd;
+     
+    fd = fopen(FILE_NETCONFIG, "r");
+    if (fd == NULL) {
+        fprintf(stderr,"nfsd() failed to open file '%s'\n", FILE_NETCONFIG);
+        return FALSE;
+    }
+    fclose(fd);
+    return TRUE;
+}
+
+static void PrintUsage()
+{
+    fprintf(stderr, "Usage: nfsd.exe -d <debug_level> --noldap "
+        "--uid <non-zero value> --gid\n");
+}
+static bool_t parse_cmdlineargs(int argc, TCHAR *argv[], nfsd_args *out)
+{
+    int i;
+
+    /* set defaults. */
+    out->debug_level = 1;
+    out->ldap_enable = TRUE;
+
+    /* parse command line */
+    for (i = 1; i < argc; i++) {
+        if (argv[i][0] == TEXT('-')) {
+            if (_tcscmp(argv[i], TEXT("-h")) == 0) { /* help */
+                PrintUsage();
+                return FALSE;
+            }
+            else if (_tcscmp(argv[i], TEXT("-d")) == 0) { /* debug level */
+                ++i;
+                if (i >= argc) {
+                    fprintf(stderr, "Missing debug level value\n");
+                    PrintUsage();
+                    return FALSE;
+                } 
+                out->debug_level = _ttoi(argv[i]);
+            }
+            else if (_tcscmp(argv[i], TEXT("--noldap")) == 0) { /* no LDAP */
+                out->ldap_enable = FALSE;
+            }
+            else if (_tcscmp(argv[i], TEXT("--uid")) == 0) { /* no LDAP, setting default uid */
+                ++i;
+                if (i >= argc) {
+                    fprintf(stderr, "Missing uid value\n");
+                    PrintUsage();
+                    return FALSE;
+                }
+                default_uid = _ttoi(argv[i]);
+                if (!default_uid) {
+                    fprintf(stderr, "Invalid (or missing) anonymous uid value of %d\n", 
+                        default_uid);
+                    return FALSE;
+                }
+            }
+            else if (_tcscmp(argv[i], TEXT("--gid")) == 0) { /* no LDAP, setting default gid */
+                ++i;
+                if (i >= argc) {
+                    fprintf(stderr, "Missing gid value\n");
+                    PrintUsage();
+                    return FALSE;
+                }
+                default_gid = _ttoi(argv[i]);
+            }
+            else
+                fprintf(stderr, "Unrecognized option '%s', disregarding.\n", argv[i]);
+        }
+    }
+    fprintf(stdout, "parse_cmdlineargs: debug_level %d ldap is %d\n", 
+        out->debug_level, out->ldap_enable);
+    return TRUE;
+}
+
+static void print_getaddrinfo(struct addrinfo *ptr)
+{
+    char ipstringbuffer[46];
+    DWORD ipbufferlength = 46;
+
+    dprintf(1, "getaddrinfo response flags: 0x%x\n", ptr->ai_flags);
+    switch (ptr->ai_family) {
+    case AF_UNSPEC: dprintf(1, "Family: Unspecified\n"); break;
+    case AF_INET:
+        dprintf(1, "Family: AF_INET IPv4 address %s\n",
+            inet_ntoa(((struct sockaddr_in *)ptr->ai_addr)->sin_addr));
+        break;
+    case AF_INET6:
+        if (WSAAddressToString((LPSOCKADDR)ptr->ai_addr, (DWORD)ptr->ai_addrlen, 
+                NULL, ipstringbuffer, &ipbufferlength))
+            dprintf(1, "WSAAddressToString failed with %u\n", WSAGetLastError());
+        else    
+            dprintf(1, "Family: AF_INET6 IPv6 address %s\n", ipstringbuffer);
+        break;
+    case AF_NETBIOS: dprintf(1, "AF_NETBIOS (NetBIOS)\n"); break;
+    default: dprintf(1, "Other %ld\n", ptr->ai_family); break;
+    }
+    dprintf(1, "Canonical name: %s\n", ptr->ai_canonname);
+}
+
+static int getdomainname()
+{
+    int status = 0;
+    PFIXED_INFO net_info = NULL;
+    DWORD size = 0;
+    BOOLEAN flag = FALSE;
+
+    status = GetNetworkParams(net_info, &size);
+    if (status != ERROR_BUFFER_OVERFLOW) {
+        eprintf("getdomainname: GetNetworkParams returned %d\n", status);
+        goto out;
+    }
+    net_info = calloc(1, size);
+    if (net_info == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+    status = GetNetworkParams(net_info, &size);
+    if (status) {
+        eprintf("getdomainname: GetNetworkParams returned %d\n", status);
+        goto out_free;
+    }
+
+    if (net_info->DomainName[0] == '\0') {
+        struct addrinfo *result = NULL, *ptr = NULL, hints = { 0 };
+        char hostname[NI_MAXHOST], servInfo[NI_MAXSERV];
+
+        hints.ai_socktype = SOCK_STREAM;
+        hints.ai_protocol = IPPROTO_TCP;
+
+        status = getaddrinfo(net_info->HostName, NULL, &hints, &result);
+        if (status) {
+            status = WSAGetLastError();
+            eprintf("getdomainname: getaddrinfo failed with %d\n", status);
+            goto out_free;
+        } 
+
+        for (ptr=result; ptr != NULL; ptr=ptr->ai_next) {
+            print_getaddrinfo(ptr);
+
+            switch (ptr->ai_family) {
+            case AF_INET6:
+            case AF_INET:
+                status = getnameinfo((struct sockaddr *)ptr->ai_addr,
+                            (socklen_t)ptr->ai_addrlen, hostname, NI_MAXHOST, 
+                            servInfo, NI_MAXSERV, NI_NAMEREQD);
+                if (status)
+                    dprintf(1, "getnameinfo failed %d\n", WSAGetLastError());
+                else {
+                    size_t i, len = strlen(hostname);
+                    char *p = hostname;
+                    dprintf(1, "getdomainname: hostname %s %d\n", hostname, len);
+                    for (i = 0; i < len; i++)
+                        if (p[i] == '.')
+                            break;
+                    if (i == len)
+                        break;
+                    flag = TRUE;
+                    memcpy(localdomain_name, &hostname[i+1], len-i);
+                    dprintf(1, "getdomainname: domainname %s %d\n", 
+                            localdomain_name, strlen(localdomain_name));
+                    goto out_loop;
+                }
+                break;
+            default:
+                break;
+            }
+        }
+out_loop:
+        if (!flag) {
+            status = ERROR_INTERNAL_ERROR;
+            eprintf("getdomainname: unable to get a domain name. "
+                "Set this machine's domain name:\n"
+                "System > ComputerName > Change > More > mydomain\n");
+        }
+        freeaddrinfo(result);
+    } else {
+        dprintf(1, "domain name is %s\n", net_info->DomainName);
+        memcpy(localdomain_name, net_info->DomainName, 
+                strlen(net_info->DomainName));
+        localdomain_name[strlen(net_info->DomainName)] = '\0';
+    }
+out_free:
+    free(net_info);
+out:
+    return status;
+}
+
+#ifdef STANDALONE_NFSD
+void __cdecl _tmain(int argc, TCHAR *argv[])
+#else
+VOID ServiceStart(DWORD argc, LPTSTR *argv)
+#endif
+{
+    DWORD status = 0, len;
+    // handle to our drivers
+    HANDLE pipe;
+    nfs41_process_thread tids[MAX_NUM_THREADS];
+    nfs41_idmapper *idmapper = NULL;
+    int i;
+    nfsd_args cmd_args;
+
+    if (!check_for_files())
+        exit(0);
+    if (!parse_cmdlineargs(argc, argv, &cmd_args)) 
+        exit(0);
+    set_debug_level(cmd_args.debug_level);
+    open_log_files();
+
+#ifdef __REACTOS__
+    /* Start the kernel part */
+    {
+        HANDLE hSvcMan = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+        if (hSvcMan)
+        {
+            HANDLE hSvc = OpenService(hSvcMan, "nfs41_driver", SERVICE_ALL_ACCESS);
+            if (hSvc)
+            {
+                SERVICE_STATUS SvcSt;
+                QueryServiceStatus(hSvc, &SvcSt);
+                if (SvcSt.dwCurrentState != SERVICE_RUNNING)
+                {
+                    if (StartService(hSvc, 0, NULL))
+                    {
+                        dprintf(1, "NFS41 driver started\n");
+                    }
+                    else
+                    {
+                        eprintf("Driver failed to start: %d\n", GetLastError());
+                    }
+                }
+                else
+                {
+                    eprintf("Driver in state: %x\n", SvcSt.dwCurrentState);
+                }
+
+                CloseServiceHandle(hSvc);
+            }
+            else
+            {
+                eprintf("Failed to open service: %d\n", GetLastError());
+            }
+
+            CloseServiceHandle(hSvcMan);
+        }
+        else
+        {
+            eprintf("Failed to open service manager: %d\n", GetLastError());
+        }
+    }
+#endif
+
+#ifdef _DEBUG
+    /* dump memory leaks to stderr on exit; this requires the debug heap,
+    /* available only when built in debug mode under visual studio -cbodley */
+    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
+#pragma warning (push)
+#pragma warning (disable : 4306) /* conversion from 'int' to '_HFILE' of greater size */
+    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
+#pragma warning (pop)
+    dprintf(1, "debug mode. dumping memory leaks to stderr on exit.\n");
+#endif
+    /* acquire and store in global memory current dns domain name.
+     * needed for acls */
+    if (getdomainname())
+        exit(0);
+
+    nfs41_server_list_init();
+
+    if (cmd_args.ldap_enable) {
+        status = nfs41_idmap_create(&idmapper);
+        if (status) {
+            eprintf("id mapping initialization failed with %d\n", status);
+            goto out_logs;
+        }
+    }
+
+    NFS41D_VERSION = GetTickCount();
+    dprintf(1, "NFS41 Daemon starting: version %d\n", NFS41D_VERSION);
+
+    pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ | GENERIC_WRITE,
+        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+        0, NULL);
+    if (pipe == INVALID_HANDLE_VALUE)
+    {
+        eprintf("Unable to open upcall pipe %d\n", GetLastError());
+        goto out_idmap;
+    }
+
+    dprintf(1, "starting nfs41 mini redirector\n");
+    status = DeviceIoControl(pipe, IOCTL_NFS41_START,
+        &NFS41D_VERSION, sizeof(DWORD), NULL, 0, (LPDWORD)&len, NULL);
+    if (!status) {
+        eprintf("IOCTL_NFS41_START failed with %d\n", 
+                GetLastError());
+        goto out_pipe;
+    }
+
+#ifndef STANDALONE_NFSD
+    stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (stop_event == NULL)
+      goto out_pipe;
+#endif
+
+    for (i = 0; i < MAX_NUM_THREADS; i++) {
+        tids[i].handle = (HANDLE)_beginthreadex(NULL, 0, thread_main, 
+                idmapper, 0, &tids[i].tid);
+        if (tids[i].handle == INVALID_HANDLE_VALUE) {
+            status = GetLastError();
+            eprintf("_beginthreadex failed %d\n", status);
+            goto out_pipe;
+        }
+    }
+#ifndef STANDALONE_NFSD
+    // report the status to the service control manager.
+    if (!ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0))
+        goto out_pipe;
+    WaitForSingleObject(stop_event, INFINITE);
+#else
+    //This can be changed to waiting on an array of handles and using waitformultipleobjects
+    dprintf(1, "Parent waiting for children threads\n");
+    for (i = 0; i < MAX_NUM_THREADS; i++)
+        WaitForSingleObject(tids[i].handle, INFINITE );
+#endif
+    dprintf(1, "Parent woke up!!!!\n");
+
+out_pipe:
+    CloseHandle(pipe);
+out_idmap:
+    if (idmapper) nfs41_idmap_free(idmapper);
+out_logs:
+#ifndef STANDALONE_NFSD
+    close_log_files();
+#endif
+    return;
+}
diff --git a/reactos/base/services/nfsd/nfs41_ops.c b/reactos/base/services/nfsd/nfs41_ops.c
new file mode 100644 (file)
index 0000000..34545dc
--- /dev/null
@@ -0,0 +1,2187 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "nfs41_ops.h"
+#include "nfs41_compound.h"
+#include "nfs41_xdr.h"
+#include "name_cache.h"
+#include "delegation.h"
+#include "daemon_debug.h"
+#include "util.h"
+
+int nfs41_exchange_id(
+    IN nfs41_rpc_clnt *rpc,
+    IN client_owner4 *owner,
+    IN uint32_t flags_in,
+    OUT nfs41_exchange_id_res *res_out)
+{
+    int status = 0;
+    nfs41_compound compound;
+    nfs_argop4 argop;
+    nfs_resop4 resop;
+    nfs41_exchange_id_args ex_id;
+
+    compound_init(&compound, &argop, &resop, "exchange_id");
+
+    compound_add_op(&compound, OP_EXCHANGE_ID, &ex_id, res_out);
+    ex_id.eia_clientowner = owner;
+    ex_id.eia_flags = flags_in;
+    ex_id.eia_state_protect.spa_how = SP4_NONE;
+    ex_id.eia_client_impl_id = NULL;
+
+    res_out->server_owner.so_major_id_len = NFS4_OPAQUE_LIMIT;
+    res_out->server_scope_len = NFS4_OPAQUE_LIMIT;
+
+    status = nfs41_send_compound(rpc, (char *)&compound.args,
+        (char *)&compound.res);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+// AGLO: 10/07/2009 we might want lookup these values from the registry
+static int set_fore_channel_attrs(
+    IN nfs41_rpc_clnt *rpc,
+    IN uint32_t max_req,
+    OUT nfs41_channel_attrs *attrs)
+{
+    attrs->ca_headerpadsize = 0;
+    attrs->ca_maxrequestsize = rpc->wsize;
+    attrs->ca_maxresponsesize = rpc->rsize;
+    attrs->ca_maxresponsesize_cached = NFS41_MAX_SERVER_CACHE;
+    attrs->ca_maxoperations = 0xffffffff;
+    attrs->ca_maxrequests = max_req;
+    attrs->ca_rdma_ird = NULL;
+    return 0;
+}
+
+// AGLO: 10/07/2009 we might want lookup these values from the registry
+static int set_back_channel_attrs(
+    IN nfs41_rpc_clnt *rpc,
+    IN uint32_t max_req,
+    OUT nfs41_channel_attrs *attrs)
+{
+    attrs->ca_headerpadsize = 0;
+    attrs->ca_maxrequestsize = rpc->wsize;
+    attrs->ca_maxresponsesize = rpc->rsize;
+    attrs->ca_maxresponsesize_cached = NFS41_MAX_SERVER_CACHE;
+    attrs->ca_maxoperations = 0xffffffff;
+    attrs->ca_maxrequests = max_req;
+    attrs->ca_rdma_ird = NULL;
+    return 0;
+}
+
+int nfs41_create_session(nfs41_client *clnt, nfs41_session *session, bool_t try_recovery)
+{
+    int status = 0;
+    nfs41_compound compound;
+    nfs_argop4 argop;
+    nfs_resop4 resop;
+    nfs41_create_session_args req = { 0 };
+    nfs41_create_session_res reply = { 0 };
+
+    compound_init(&compound, &argop, &resop, "create_session");
+
+    compound_add_op(&compound, OP_CREATE_SESSION, &req, &reply);
+
+    AcquireSRWLockShared(&clnt->exid_lock);
+    req.csa_clientid = clnt->clnt_id;
+    req.csa_sequence = clnt->seq_id;
+    ReleaseSRWLockShared(&clnt->exid_lock);
+    req.csa_flags = session->flags;
+    req.csa_cb_program = NFS41_RPC_CBPROGRAM;
+    req.csa_cb_secparams[0].type = 0; /* AUTH_NONE */
+    req.csa_cb_secparams[1].type = 1; /* AUTH_SYS */
+    req.csa_cb_secparams[1].u.auth_sys.machinename = clnt->rpc->server_name;
+    req.csa_cb_secparams[1].u.auth_sys.stamp = (uint32_t)time(NULL);
+
+    // ca_maxrequests should be gotten from the rpc layer
+    set_fore_channel_attrs(clnt->rpc,
+        NFS41_MAX_RPC_REQS, &req.csa_fore_chan_attrs);
+    set_back_channel_attrs(clnt->rpc,
+        1, &req.csa_back_chan_attrs);
+    
+    reply.csr_sessionid = session->session_id;
+    reply.csr_fore_chan_attrs = &session->fore_chan_attrs;
+    reply.csr_back_chan_attrs = &session->back_chan_attrs;
+
+    status = compound_encode_send_decode(session, &compound, try_recovery);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    print_hexbuf(1, (unsigned char *)"session id: ", session->session_id, NFS4_SESSIONID_SIZE);
+    // check that csa_sequence is same as csr_sequence
+    if (reply.csr_sequence != clnt->seq_id) {
+        eprintf("ERROR: CREATE_SESSION: csa_sequence %d != "
+            "csr_sequence %d\n", clnt->seq_id, reply.csr_sequence);
+        status = NFS4ERR_SEQ_MISORDERED;
+        goto out;
+    } else clnt->seq_id++;
+
+    if (reply.csr_flags != req.csa_flags) {
+        eprintf("WARNING: requested session flags %x received %x\n",
+            req.csa_flags, reply.csr_flags);
+        if ((session->flags & CREATE_SESSION4_FLAG_CONN_BACK_CHAN) &&
+                !(reply.csr_flags & CREATE_SESSION4_FLAG_CONN_BACK_CHAN))
+            eprintf("WARNING: we asked to use this session for callbacks but "
+                    "server refused\n");
+        if ((session->flags & CREATE_SESSION4_FLAG_PERSIST) &&
+            !(reply.csr_flags & CREATE_SESSION4_FLAG_PERSIST))
+            eprintf("WARNING: we asked for persistent session but "
+                    "server refused\n");
+        session->flags = reply.csr_flags;
+    }
+    else
+        dprintf(1, "session flags %x\n", reply.csr_flags);
+
+    dprintf(1, "session fore_chan_attrs:\n"
+        "  %-32s%d\n  %-32s%d\n  %-32s%d\n  %-32s%d\n  %-32s%d\n  %-32s%d\n",
+        "headerpadsize", session->fore_chan_attrs.ca_headerpadsize,
+        "maxrequestsize", session->fore_chan_attrs.ca_maxrequestsize,
+        "maxresponsesize", session->fore_chan_attrs.ca_maxresponsesize,
+        "maxresponsesize_cached", session->fore_chan_attrs.ca_maxresponsesize_cached,
+        "maxoperations", session->fore_chan_attrs.ca_maxoperations,
+        "maxrequests", session->fore_chan_attrs.ca_maxrequests);
+    dprintf(1, "client supports %d max rpc slots, but server has %d\n", 
+        session->table.max_slots, session->fore_chan_attrs.ca_maxrequests);
+    /* use the server's ca_maxrequests unless it's bigger than our array */
+    session->table.max_slots = min(session->table.max_slots,
+        session->fore_chan_attrs.ca_maxrequests);
+    status = 0;
+out:
+    return status;
+}
+
+enum nfsstat4 nfs41_bind_conn_to_session(
+    IN nfs41_rpc_clnt *rpc,
+    IN const unsigned char *sessionid,
+    IN enum channel_dir_from_client4 dir)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argop;
+    nfs_resop4 resop;
+    nfs41_bind_conn_to_session_args bind_args = { 0 };
+    nfs41_bind_conn_to_session_res bind_res = { 0 };
+
+    compound_init(&compound, &argop, &resop, "bind_conn_to_session");
+
+    compound_add_op(&compound, OP_BIND_CONN_TO_SESSION, &bind_args, &bind_res);
+    bind_args.sessionid = (unsigned char *)sessionid;
+    bind_args.dir = dir;
+
+    status = nfs41_send_compound(rpc,
+        (char*)&compound.args, (char*)&compound.res);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+
+out:
+    return status;
+}
+
+int nfs41_destroy_session(
+    IN nfs41_session *session)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argop;
+    nfs_resop4 resop;
+    nfs41_destroy_session_args ds_args;
+    nfs41_destroy_session_res ds_res;
+
+    compound_init(&compound, &argop, &resop, "destroy_session");
+
+    compound_add_op(&compound, OP_DESTROY_SESSION, &ds_args, &ds_res);
+    ds_args.dsa_sessionid = session->session_id;
+
+    /* don't attempt to recover from BADSESSION/STALE_CLIENTID */
+    status = compound_encode_send_decode(session, &compound, FALSE);
+    if (status)
+        goto out;
+
+    status = compound.res.status;
+    if (status)
+        eprintf("%s failed with status %d.\n",
+            nfs_opnum_to_string(OP_DESTROY_SESSION), status);
+out:
+    return status;
+}
+
+int nfs41_destroy_clientid(
+    IN nfs41_rpc_clnt *rpc,
+    IN uint64_t clientid)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops;
+    nfs_resop4 resops;
+    nfs41_destroy_clientid_args dc_args;
+    nfs41_destroy_clientid_res dc_res;
+
+    compound_init(&compound, &argops, &resops, "destroy_clientid");
+
+    compound_add_op(&compound, OP_DESTROY_CLIENTID, &dc_args, &dc_res);
+    dc_args.dca_clientid = clientid;
+
+    status = nfs41_send_compound(rpc, (char *)&compound.args,
+        (char *)&compound.res);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+enum nfsstat4 nfs41_reclaim_complete(
+    IN nfs41_session *session)
+{
+    enum nfsstat4 status = NFS4_OK;
+    nfs41_compound compound;
+    nfs_argop4 argops[2];
+    nfs_resop4 resops[2];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_reclaim_complete_res reclaim_res;
+
+    compound_init(&compound, argops, resops, "reclaim_complete");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_RECLAIM_COMPLETE, NULL, &reclaim_res);
+
+    /* don't attempt to recover from BADSESSION */
+    status = compound_encode_send_decode(session, &compound, FALSE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+static void open_delegation_return(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN open_delegation4 *delegation,
+    IN bool_t try_recovery)
+{
+    stateid_arg stateid;
+    int status;
+
+    if (delegation->type == OPEN_DELEGATE_NONE ||
+        delegation->type == OPEN_DELEGATE_NONE_EXT)
+        return;
+
+    /* return the delegation */
+    stateid.open = NULL;
+    stateid.delegation = NULL;
+    stateid.type = STATEID_DELEG_FILE;
+    memcpy(&stateid.stateid, &delegation->stateid, sizeof(stateid4));
+
+    status = nfs41_delegreturn(session, file, &stateid, try_recovery);
+
+    /* clear the delegation type returned by nfs41_open() */
+    delegation->type = OPEN_DELEGATE_NONE;
+}
+
+static void open_update_cache(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN nfs41_path_fh *file,
+    IN bool_t try_recovery,
+    IN open_delegation4 *delegation,
+    IN bool_t already_delegated,
+    IN change_info4 *changeinfo,
+    IN nfs41_getattr_res *dir_attrs,
+    IN nfs41_getattr_res *file_attrs)
+{
+    struct nfs41_name_cache *cache = session_name_cache(session);
+    uint32_t status;
+
+    /* update the attributes of the parent directory */
+    memcpy(&dir_attrs->info->attrmask, &dir_attrs->obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(cache, parent->fh.fileid, dir_attrs->info);
+
+    /* add the file handle and attributes to the name cache */
+    memcpy(&file_attrs->info->attrmask, &file_attrs->obj_attributes.attrmask,
+        sizeof(bitmap4));
+retry_cache_insert:
+    AcquireSRWLockShared(&file->path->lock);
+    status = nfs41_name_cache_insert(cache, file->path->path, &file->name,
+        &file->fh, file_attrs->info, changeinfo,
+        already_delegated ? OPEN_DELEGATE_NONE : delegation->type);
+    ReleaseSRWLockShared(&file->path->lock);
+
+    if (status == ERROR_TOO_MANY_OPEN_FILES) {
+        /* the cache won't accept any more delegations; ask the client to
+         * return a delegation to free up a slot in the attribute cache */
+        status = nfs41_client_delegation_return_lru(session->client);
+        if (status == NFS4_OK)
+            goto retry_cache_insert;
+    }
+
+    if (status && delegation->type != OPEN_DELEGATE_NONE) {
+        /* if we can't make room in the cache, return this
+         * delegation immediately to free resources on the server */
+        open_delegation_return(session, file, delegation, try_recovery);
+        goto retry_cache_insert;
+    }
+}
+
+int nfs41_open(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN nfs41_path_fh *file,
+    IN state_owner4 *owner,
+    IN open_claim4 *claim,
+    IN uint32_t allow,
+    IN uint32_t deny,
+    IN uint32_t create,
+    IN uint32_t how_mode,
+    IN OPTIONAL nfs41_file_info *createattrs,
+    IN bool_t try_recovery,
+    OUT stateid4 *stateid,
+    OUT open_delegation4 *delegation,
+    OUT OPTIONAL nfs41_file_info *info)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[8];
+    nfs_resop4 resops[8];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args[2];
+    nfs41_putfh_res putfh_res[2];
+    nfs41_op_open_args open_args;
+    nfs41_op_open_res open_res;
+    nfs41_getfh_res getfh_res;
+    bitmap4 attr_request;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res, pgetattr_res;
+    nfs41_savefh_res savefh_res;
+    nfs41_restorefh_res restorefh_res;
+    nfs41_file_info tmp_info, dir_info;
+    bool_t current_fh_is_dir;
+    bool_t already_delegated = delegation->type == OPEN_DELEGATE_READ
+        || delegation->type == OPEN_DELEGATE_WRITE;
+
+    /* depending on the claim type, OPEN expects CURRENT_FH set
+     * to either the parent directory, or to the file itself */
+    switch (claim->claim) {
+    case CLAIM_NULL:
+    case CLAIM_DELEGATE_CUR:
+    case CLAIM_DELEGATE_PREV:
+        /* CURRENT_FH: directory */
+        current_fh_is_dir = TRUE;
+        /* SEQUENCE; PUTFH(dir); SAVEFH; OPEN;
+         * GETFH(file); GETATTR(file); RESTOREFH(dir); GETATTR */
+        nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
+        break;
+    case CLAIM_PREVIOUS:
+    case CLAIM_FH:
+    case CLAIM_DELEG_CUR_FH:
+    case CLAIM_DELEG_PREV_FH:
+    default:
+        /* CURRENT_FH: file being opened */
+        current_fh_is_dir = FALSE;
+        /* SEQUENCE; PUTFH(file); OPEN; GETATTR(file); PUTFH(dir); GETATTR */
+        nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+        break;
+    }
+
+    if (info == NULL)
+        info = &tmp_info;
+
+    attr_request.arr[0] |= FATTR4_WORD0_FSID;
+
+    compound_init(&compound, argops, resops, "open");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 1);
+
+    if (current_fh_is_dir) {
+        /* CURRENT_FH: directory */
+        compound_add_op(&compound, OP_PUTFH, &putfh_args[0], &putfh_res[0]);
+        putfh_args[0].file = parent;
+        putfh_args[0].in_recovery = 0;
+
+        compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
+    } else {
+        /* CURRENT_FH: file being opened */
+        compound_add_op(&compound, OP_PUTFH, &putfh_args[0], &putfh_res[0]);
+        putfh_args[0].file = file;
+        putfh_args[0].in_recovery = 0;
+    }
+
+    compound_add_op(&compound, OP_OPEN, &open_args, &open_res);
+    open_args.seqid = 0;
+#ifdef DISABLE_FILE_DELEGATIONS
+    open_args.share_access = allow | OPEN4_SHARE_ACCESS_WANT_NO_DELEG;
+#else
+    open_args.share_access = allow;
+#endif
+    open_args.share_deny = deny; 
+    open_args.owner = owner;
+    open_args.openhow.opentype = create;
+    open_args.openhow.how.mode = how_mode;
+    open_args.openhow.how.createattrs = createattrs;
+    if (how_mode == EXCLUSIVE4_1) {
+        DWORD tid = GetCurrentThreadId();
+        time((time_t*)open_args.openhow.how.createverf);
+        memcpy(open_args.openhow.how.createverf+4, &tid, sizeof(tid));
+        /* mask unsupported attributes */
+        nfs41_superblock_supported_attrs_exclcreat(
+            parent->fh.superblock, &createattrs->attrmask);
+    } else if (createattrs) {
+        /* mask unsupported attributes */
+        nfs41_superblock_supported_attrs(
+            parent->fh.superblock, &createattrs->attrmask);
+    }
+    open_args.claim = claim;
+    open_res.resok4.stateid = stateid;
+    open_res.resok4.delegation = delegation;
+
+    if (current_fh_is_dir) {
+        compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
+        getfh_res.fh = &file->fh;
+    }
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = &attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = info;
+
+    if (current_fh_is_dir) {
+        compound_add_op(&compound, OP_RESTOREFH, NULL, &restorefh_res);
+    } else {
+        compound_add_op(&compound, OP_PUTFH, &putfh_args[1], &putfh_res[1]);
+        putfh_args[1].file = parent;
+        putfh_args[1].in_recovery = 0;
+    }
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &pgetattr_res);
+    getattr_args.attr_request = &attr_request;
+    pgetattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    pgetattr_res.info = &dir_info;
+
+    status = compound_encode_send_decode(session, &compound, try_recovery);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    if (dir_info.type == NF4ATTRDIR) {
+        file->fh.superblock = parent->fh.superblock;
+        goto out;
+    }
+
+    /* fill in the file handle's fileid and superblock */
+    file->fh.fileid = info->fileid;
+    status = nfs41_superblock_for_fh(session, &info->fsid, &parent->fh, file);
+    if (status)
+        goto out;
+
+    if (create == OPEN4_CREATE)
+        nfs41_superblock_space_changed(file->fh.superblock);
+
+    /* update the name/attr cache with the results */
+    open_update_cache(session, parent, file, try_recovery, delegation,
+        already_delegated, &open_res.resok4.cinfo, &pgetattr_res, &getattr_res);
+out:
+    return status;
+}
+
+int nfs41_create(
+    IN nfs41_session *session,
+    IN uint32_t type,
+    IN nfs41_file_info *createattrs,
+    IN OPTIONAL const char *symlink,
+    IN nfs41_path_fh *parent,
+    OUT nfs41_path_fh *file,
+    OUT nfs41_file_info *info)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[8];
+    nfs_resop4 resops[8];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_create_args create_args;
+    nfs41_create_res create_res;
+    nfs41_getfh_res getfh_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res, pgetattr_res;
+    bitmap4 attr_request;
+    nfs41_file_info dir_info;
+    nfs41_savefh_res savefh_res;
+    nfs41_restorefh_res restorefh_res;
+
+    nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
+
+    compound_init(&compound, argops, resops, "create");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 1);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = parent;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
+
+    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;
+    create_args.createattrs = createattrs;
+    nfs41_superblock_supported_attrs(
+                parent->fh.superblock, &createattrs->attrmask);
+
+    compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
+    getfh_res.fh = &file->fh;
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = &attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = info;
+
+    compound_add_op(&compound, OP_RESTOREFH, NULL, &restorefh_res);
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &pgetattr_res);
+    getattr_args.attr_request = &attr_request;
+    pgetattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    pgetattr_res.info = &dir_info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    /* fill in the file handle's fileid and superblock */
+    file->fh.fileid = info->fileid;
+    file->fh.superblock = parent->fh.superblock;
+
+    /* update the attributes of the parent directory */
+    memcpy(&dir_info.attrmask, &pgetattr_res.obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        parent->fh.fileid, &dir_info);
+
+    /* add the new file handle and attributes to the name cache */
+    memcpy(&info->attrmask, &getattr_res.obj_attributes.attrmask,
+        sizeof(bitmap4));
+    AcquireSRWLockShared(&file->path->lock);
+    nfs41_name_cache_insert(session_name_cache(session),
+        file->path->path, &file->name, &file->fh,
+        info, &create_res.cinfo, OPEN_DELEGATE_NONE);
+    ReleaseSRWLockShared(&file->path->lock);
+
+    nfs41_superblock_space_changed(file->fh.superblock);
+out:
+    return status;
+}
+
+int nfs41_close(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_op_close_args close_args;
+    nfs41_op_close_res close_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res;
+    bitmap4 attr_request;
+    nfs41_file_info info;
+
+    nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+
+    compound_init(&compound, argops, resops, "close");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 1);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_CLOSE, &close_args, &close_res);
+    close_args.stateid = stateid;
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = &attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = &info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    if (info.type == NF4NAMEDATTR)
+        goto out;
+
+    /* update the attributes of the parent directory */
+    memcpy(&info.attrmask, &getattr_res.obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        file->fh.fileid, &info);
+out:
+    return status;
+}
+
+int nfs41_write(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN unsigned char *data,
+    IN uint32_t data_len,
+    IN uint64_t offset,
+    IN enum stable_how4 stable,
+    OUT uint32_t *bytes_written,
+    OUT nfs41_write_verf *verf,
+    OUT nfs41_file_info *cinfo)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_write_args write_args;
+    nfs41_write_res write_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res = {0};
+    bitmap4 attr_request;
+    nfs41_file_info info = { 0 }, *pinfo;
+
+    nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+
+    compound_init(&compound, argops, resops,
+        stateid->stateid.seqid == 0 ? "ds write" : "write");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_WRITE, &write_args, &write_res);
+    write_args.stateid = stateid;
+    write_args.offset = offset;
+    write_args.stable = stable;
+    write_args.data_len = data_len;
+    write_args.data = data;
+    write_res.resok4.verf = verf;
+
+    if (cinfo) pinfo = cinfo;
+    else pinfo = &info;
+
+    if (stable != UNSTABLE4) {
+        /* if the write is stable, we can't rely on COMMIT to update
+         * the attribute cache, so we do the GETATTR here */
+        compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+        getattr_args.attr_request = &attr_request;
+        getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+        getattr_res.info = pinfo;
+    }
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    if (stable != UNSTABLE4 && pinfo->type != NF4NAMEDATTR) {
+        /* update the attribute cache */
+        memcpy(&pinfo->attrmask, &getattr_res.obj_attributes.attrmask,
+            sizeof(bitmap4));
+        nfs41_attr_cache_update(session_name_cache(session),
+            file->fh.fileid, pinfo);
+    }
+
+    *bytes_written = write_res.resok4.count;
+
+    /* we shouldn't ever see this, but a buggy server could
+     * send us into an infinite loop. return NFS4ERR_IO */
+    if (!write_res.resok4.count) {
+        status = NFS4ERR_IO;
+        eprintf("WRITE succeeded with count=0; returning %s\n",
+            nfs_error_string(status));
+    }
+
+    nfs41_superblock_space_changed(file->fh.superblock);
+out:
+    return status;
+}
+
+int nfs41_read(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN uint64_t offset,
+    IN uint32_t count,
+    OUT unsigned char *data_out,
+    OUT uint32_t *data_len_out,
+    OUT bool_t *eof_out)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_read_args read_args;
+    nfs41_read_res read_res;
+
+    compound_init(&compound, argops, resops,
+        stateid->stateid.seqid == 0 ? "ds read" : "read");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_READ, &read_args, &read_res);
+    read_args.stateid = stateid;
+    read_args.offset = offset;
+    read_args.count = count;
+    read_res.resok4.data_len = count;
+    read_res.resok4.data = data_out;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    *data_len_out = read_res.resok4.data_len;
+    *eof_out = read_res.resok4.eof;
+
+    /* we shouldn't ever see this, but a buggy server could
+     * send us into an infinite loop. return NFS4ERR_IO */
+    if (!read_res.resok4.data_len && !read_res.resok4.eof) {
+        status = NFS4ERR_IO;
+        eprintf("READ succeeded with len=0 and eof=0; returning %s\n",
+            nfs_error_string(status));
+    }
+out:
+    return status;
+}
+
+int nfs41_commit(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint64_t offset,
+    IN uint32_t count,
+    IN bool_t do_getattr,
+    OUT nfs41_write_verf *verf,
+    OUT nfs41_file_info *cinfo)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_commit_args commit_args;
+    nfs41_commit_res commit_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res = {0};
+    bitmap4 attr_request;
+    nfs41_file_info info, *pinfo;
+
+    compound_init(&compound, argops, resops,
+        do_getattr ? "commit" : "ds commit");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 1);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_COMMIT, &commit_args, &commit_res);
+    commit_args.offset = offset;
+    commit_args.count = count;
+    commit_res.verf = verf;
+
+    /* send a GETATTR request to update the attribute cache,
+     * but not if we're talking to a data server! */
+    if (cinfo) pinfo = cinfo;
+    else pinfo = &info;
+    if (do_getattr) {
+        nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+
+        compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+        getattr_args.attr_request = &attr_request;
+        getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+        getattr_res.info = pinfo;
+    }
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    if (do_getattr) {
+        /* update the attribute cache */
+        memcpy(&pinfo->attrmask, &getattr_res.obj_attributes.attrmask,
+            sizeof(bitmap4));
+        nfs41_attr_cache_update(session_name_cache(session),
+            file->fh.fileid, pinfo);
+    }
+    nfs41_superblock_space_changed(file->fh.superblock);
+out:
+    return status;
+}
+
+int nfs41_lock(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN state_owner4 *owner,
+    IN uint32_t type,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN bool_t reclaim,
+    IN bool_t try_recovery,
+    IN OUT stateid_arg *stateid)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_lock_args lock_args;
+    nfs41_lock_res lock_res;
+
+    compound_init(&compound, argops, resops, "lock");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_LOCK, &lock_args, &lock_res);
+    lock_args.locktype = type;
+    lock_args.reclaim = reclaim;
+    lock_args.offset = offset;
+    lock_args.length = length;
+    if (stateid->type == STATEID_LOCK) {
+        lock_args.locker.new_lock_owner = 0;
+        lock_args.locker.u.lock_owner.lock_stateid = stateid;
+        lock_args.locker.u.lock_owner.lock_seqid = 0; /* ignored */
+    } else {
+        lock_args.locker.new_lock_owner = 1;
+        lock_args.locker.u.open_owner.open_seqid = 0; /* ignored */
+        lock_args.locker.u.open_owner.open_stateid = stateid;
+        lock_args.locker.u.open_owner.lock_seqid = 0; /* ignored */
+        lock_args.locker.u.open_owner.lock_owner = owner;
+    }
+    lock_res.u.resok4.lock_stateid = &stateid->stateid;
+    lock_res.u.denied.owner.owner_len = NFS4_OPAQUE_LIMIT;
+
+    status = compound_encode_send_decode(session, &compound, try_recovery);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    stateid->type = STATEID_LOCK; /* returning a lock stateid */
+out:
+    return status;
+}
+
+int nfs41_unlock(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN OUT stateid_arg *stateid)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_locku_args locku_args;
+    nfs41_locku_res locku_res;
+
+    compound_init(&compound, argops, resops, "unlock");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_LOCKU, &locku_args, &locku_res);
+    /* 18.12.3: the server MUST accept any legal value for locktype */
+    locku_args.locktype = READ_LT;
+    locku_args.offset = offset;
+    locku_args.length = length;
+    locku_args.lock_stateid = stateid;
+    locku_res.lock_stateid = &stateid->stateid;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+int nfs41_readdir(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN bitmap4 *attr_request,
+    IN nfs41_readdir_cookie *cookie,
+    OUT unsigned char *entries,
+    IN OUT uint32_t *entries_len,
+    OUT bool_t *eof_out)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_readdir_args readdir_args;
+    nfs41_readdir_res readdir_res;
+
+    compound_init(&compound, argops, resops, "readdir");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_READDIR, &readdir_args, &readdir_res);
+    readdir_args.cookie.cookie = cookie->cookie;
+    memcpy(readdir_args.cookie.verf, cookie->verf, NFS4_VERIFIER_SIZE);
+    readdir_args.dircount = *entries_len;
+    readdir_args.maxcount = *entries_len + sizeof(nfs41_readdir_res);
+    readdir_args.attr_request = attr_request;
+    readdir_res.reply.entries_len = *entries_len;
+    readdir_res.reply.entries = entries;
+    ZeroMemory(entries, readdir_args.dircount);
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    *entries_len = readdir_res.reply.entries_len;
+    *eof_out = readdir_res.reply.eof;
+    memcpy(cookie->verf, readdir_res.cookieverf, NFS4_VERIFIER_SIZE);
+out:
+    return status;
+}
+
+int nfs41_getattr(
+    IN nfs41_session *session,
+    IN OPTIONAL nfs41_path_fh *file,
+    IN bitmap4 *attr_request,
+    OUT nfs41_file_info *info)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res;
+
+    compound_init(&compound, argops, resops, "getattr");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    if (file) {
+        compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+        putfh_args.file = file;
+        putfh_args.in_recovery = 0;
+    } else {
+        compound_add_op(&compound, OP_PUTROOTFH, NULL, &putfh_res);
+    }
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    if (file) {
+        /* update the name cache with whatever attributes we got */
+        memcpy(&info->attrmask, &getattr_res.obj_attributes.attrmask,
+            sizeof(bitmap4));
+        nfs41_attr_cache_update(session_name_cache(session),
+            file->fh.fileid, info);
+    }
+out:
+    return status;
+}
+
+int nfs41_superblock_getattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN bitmap4 *attr_request,
+    OUT nfs41_file_info *info,
+    OUT bool_t *supports_named_attrs)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res;
+    nfs41_openattr_args openattr_args;
+    nfs41_openattr_res openattr_res;
+
+    compound_init(&compound, argops, resops, "getfsattr");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = info;
+
+    compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
+    openattr_args.createdir = 0;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    status = sequence_res.sr_status;
+    if (status) goto out;
+    status = putfh_res.status;
+    if (status) goto out;
+    status = getattr_res.status;
+    if (status) goto out;
+
+    switch (status = openattr_res.status) {
+    case NFS4ERR_NOTSUPP:
+        *supports_named_attrs = 0;
+        status = NFS4_OK;
+        break;
+
+    case NFS4ERR_NOENT:
+    case NFS4_OK:
+        *supports_named_attrs = 1;
+        status = NFS4_OK;
+        break;
+    }
+out:
+    return status;
+}
+
+int nfs41_remove(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN const nfs41_component *target,
+    IN uint64_t fileid)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_remove_args remove_args;
+    nfs41_remove_res remove_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res;
+    bitmap4 attr_request;
+    nfs41_file_info info;
+
+    nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request);
+
+    compound_init(&compound, argops, resops, "remove");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 1);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = parent;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_REMOVE, &remove_args, &remove_res);
+    remove_args.target = target;
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = &attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = &info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    if (info.type == NF4ATTRDIR)
+        goto out;
+
+    /* update the attributes of the parent directory */
+    memcpy(&info.attrmask, &getattr_res.obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        parent->fh.fileid, &info);
+
+    /* remove the target file from the cache */
+    AcquireSRWLockShared(&parent->path->lock);
+    nfs41_name_cache_remove(session_name_cache(session),
+        parent->path->path, target, fileid, &remove_res.cinfo);
+    ReleaseSRWLockShared(&parent->path->lock);
+
+    nfs41_superblock_space_changed(parent->fh.superblock);
+out:
+    return status;
+}
+
+int nfs41_rename(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *src_dir,
+    IN const nfs41_component *src_name,
+    IN nfs41_path_fh *dst_dir,
+    IN const nfs41_component *dst_name)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[8];
+    nfs_resop4 resops[8];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args src_putfh_args;
+    nfs41_putfh_res src_putfh_res;
+    nfs41_savefh_res savefh_res;
+    nfs41_putfh_args dst_putfh_args;
+    nfs41_putfh_res dst_putfh_res;
+    nfs41_rename_args rename_args;
+    nfs41_rename_res rename_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res src_getattr_res, dst_getattr_res;
+    nfs41_file_info src_info, dst_info;
+    bitmap4 attr_request;
+    nfs41_restorefh_res restorefh_res;
+
+    nfs41_superblock_getattr_mask(src_dir->fh.superblock, &attr_request);
+
+    compound_init(&compound, argops, resops, "rename");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 1);
+
+    compound_add_op(&compound, OP_PUTFH, &src_putfh_args, &src_putfh_res);
+    src_putfh_args.file = src_dir;
+    src_putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
+
+    compound_add_op(&compound, OP_PUTFH, &dst_putfh_args, &dst_putfh_res);
+    dst_putfh_args.file = dst_dir;
+    dst_putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_RENAME, &rename_args, &rename_res);
+    rename_args.oldname = src_name;
+    rename_args.newname = dst_name;
+    
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &dst_getattr_res);
+    getattr_args.attr_request = &attr_request;
+    dst_getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    dst_getattr_res.info = &dst_info;
+
+    compound_add_op(&compound, OP_RESTOREFH, NULL, &restorefh_res);
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &src_getattr_res);
+    src_getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    src_getattr_res.info = &src_info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    /* update the attributes of the source directory */
+    memcpy(&src_info.attrmask, &src_getattr_res.obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        src_dir->fh.fileid, &src_info);
+
+    /* update the attributes of the destination directory */
+    memcpy(&dst_info.attrmask, &dst_getattr_res.obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        dst_dir->fh.fileid, &dst_info);
+
+    if (src_dir->path == dst_dir->path) {
+        /* source and destination are the same, only lock it once */
+        AcquireSRWLockShared(&src_dir->path->lock);
+    } else if (src_dir->path < dst_dir->path) {
+        /* lock the lowest memory address first */
+        AcquireSRWLockShared(&src_dir->path->lock);
+        AcquireSRWLockShared(&dst_dir->path->lock);
+    } else {
+        AcquireSRWLockShared(&dst_dir->path->lock);
+        AcquireSRWLockShared(&src_dir->path->lock);
+    }
+
+    /* move/rename the target file's name cache entry */
+    nfs41_name_cache_rename(session_name_cache(session),
+        src_dir->path->path, src_name, &rename_res.source_cinfo,
+        dst_dir->path->path, dst_name, &rename_res.target_cinfo);
+
+    if (src_dir->path == dst_dir->path) {
+        ReleaseSRWLockShared(&src_dir->path->lock);
+    } else {
+        ReleaseSRWLockShared(&src_dir->path->lock);
+        ReleaseSRWLockShared(&dst_dir->path->lock);
+    }
+out:
+    return status;
+}
+
+int nfs41_setattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN nfs41_file_info *info)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_setattr_args setattr_args;
+    nfs41_setattr_res setattr_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res;
+    bitmap4 attr_request;
+
+    compound_init(&compound, argops, resops, "setattr");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_SETATTR, &setattr_args, &setattr_res);
+    setattr_args.stateid = stateid;
+    setattr_args.info = info;
+
+    nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = &attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    memcpy(&info->attrmask, &attr_request, sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        file->fh.fileid, info);
+
+    if (setattr_res.attrsset.arr[0] & FATTR4_WORD0_SIZE)
+        nfs41_superblock_space_changed(file->fh.superblock);
+out:
+    return status;
+}
+
+int nfs41_link(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *src,
+    IN nfs41_path_fh *dst_dir,
+    IN const nfs41_component *target,
+    OUT nfs41_file_info *cinfo)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[9];
+    nfs_resop4 resops[9];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args[2];
+    nfs41_putfh_res putfh_res[2];
+    nfs41_savefh_res savefh_res;
+    nfs41_link_args link_args;
+    nfs41_link_res link_res;
+    nfs41_lookup_args lookup_args;
+    nfs41_lookup_res lookup_res;
+    nfs41_getfh_res getfh_res;
+    nfs41_getattr_args getattr_args[2];
+    nfs41_getattr_res getattr_res[2];
+    nfs41_file_info info = { 0 };
+    nfs41_path_fh file;
+
+    nfs41_superblock_getattr_mask(src->fh.superblock, &info.attrmask);
+    nfs41_superblock_getattr_mask(dst_dir->fh.superblock, &cinfo->attrmask);
+    cinfo->attrmask.arr[0] |= FATTR4_WORD0_FSID;
+
+    compound_init(&compound, argops, resops, "link");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 1);
+
+    /* PUTFH(src) */
+    compound_add_op(&compound, OP_PUTFH, &putfh_args[0], &putfh_res[0]);
+    putfh_args[0].file = src;
+    putfh_args[0].in_recovery = 0;
+
+    compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res);
+
+    /* PUTFH(dst_dir) */
+    compound_add_op(&compound, OP_PUTFH, &putfh_args[1], &putfh_res[1]);
+    putfh_args[1].file = dst_dir;
+    putfh_args[1].in_recovery = 0;
+
+    compound_add_op(&compound, OP_LINK, &link_args, &link_res);
+    link_args.newname = target;
+
+    /* GETATTR(dst_dir) */
+    compound_add_op(&compound, OP_GETATTR, &getattr_args[0], &getattr_res[0]);
+    getattr_args[0].attr_request = &info.attrmask;
+    getattr_res[0].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res[0].info = &info;
+
+    /* LOOKUP(target) */
+    compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
+    lookup_args.name = target;
+
+    /* GETATTR(target) */
+    compound_add_op(&compound, OP_GETATTR, &getattr_args[1], &getattr_res[1]);
+    getattr_args[1].attr_request = &cinfo->attrmask;
+    getattr_res[1].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res[1].info = cinfo;
+
+    /* GETFH(target) */
+    compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
+    getfh_res.fh = &file.fh;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    /* fill in the file handle's fileid and superblock */
+    file.fh.fileid = cinfo->fileid;
+    status = nfs41_superblock_for_fh(session,
+        &cinfo->fsid, &dst_dir->fh, &file);
+    if (status)
+        goto out;
+
+    /* update the attributes of the destination directory */
+    memcpy(&info.attrmask, &getattr_res[0].obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        info.fileid, &info);
+
+    /* add the new file handle and attributes to the name cache */
+    memcpy(&cinfo->attrmask, &getattr_res[1].obj_attributes.attrmask,
+        sizeof(bitmap4));
+    AcquireSRWLockShared(&dst_dir->path->lock);
+    nfs41_name_cache_insert(session_name_cache(session),
+        dst_dir->path->path, target, &file.fh,
+        cinfo, &link_res.cinfo, OPEN_DELEGATE_NONE);
+    ReleaseSRWLockShared(&dst_dir->path->lock);
+
+    nfs41_superblock_space_changed(dst_dir->fh.superblock);
+out:
+    return status;
+}
+
+int nfs41_readlink(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint32_t max_len,
+    OUT char *link_out,
+    OUT uint32_t *len_out)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_readlink_res readlink_res;
+
+    compound_init(&compound, argops, resops, "readlink");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_READLINK, NULL, &readlink_res);
+    readlink_res.link_len = max_len - 1;
+    readlink_res.link = link_out;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    link_out[readlink_res.link_len] = '\0';
+    *len_out = readlink_res.link_len;
+out:
+    return status;
+}
+
+int nfs41_access(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint32_t requested,
+    OUT uint32_t *supported OPTIONAL,
+    OUT uint32_t *access OPTIONAL)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_access_args access_args;
+    nfs41_access_res access_res;
+
+    compound_init(&compound, argops, resops, "access");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_ACCESS, &access_args, &access_res);
+    access_args.access = requested;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    if (supported)
+        *supported = access_res.supported;
+    if (access)
+        *access = access_res.access;
+out:
+    return status;
+}
+
+int nfs41_send_sequence(
+    IN nfs41_session *session)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[1];
+    nfs_resop4 resops[1];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+
+    compound_init(&compound, argops, resops, "sequence");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+out:
+    return status;
+}
+
+enum nfsstat4 nfs41_want_delegation(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN deleg_claim4 *claim,
+    IN uint32_t want,
+    IN bool_t try_recovery,
+    OUT open_delegation4 *delegation)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_want_delegation_args wd_args;
+    nfs41_want_delegation_res wd_res;
+
+    compound_init(&compound, argops, resops, "want_delegation");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_WANT_DELEGATION, &wd_args, &wd_res);
+    wd_args.claim = claim;
+    wd_args.want = want;
+    wd_res.delegation = delegation;
+
+    status = compound_encode_send_decode(session, &compound, try_recovery);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+int nfs41_delegpurge(
+    IN nfs41_session *session)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[2];
+    nfs_resop4 resops[2];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_delegpurge_res dp_res;
+
+    compound_init(&compound, argops, resops, "delegpurge");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_DELEGPURGE, NULL, &dp_res);
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+int nfs41_delegreturn(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN bool_t try_recovery)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_delegreturn_args dr_args;
+    nfs41_delegreturn_res dr_res;
+
+    compound_init(&compound, argops, resops, "delegreturn");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_DELEGRETURN, &dr_args, &dr_res);
+    dr_args.stateid = stateid;
+
+    status = compound_encode_send_decode(session, &compound, try_recovery);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    AcquireSRWLockShared(&file->path->lock);
+    nfs41_name_cache_delegreturn(session_name_cache(session),
+        file->fh.fileid, file->path->path, &file->name);
+    ReleaseSRWLockShared(&file->path->lock);
+out:
+    return status;
+}
+
+enum nfsstat4 nfs41_fs_locations(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN const nfs41_component *name,
+    OUT fs_locations4 *locations)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_lookup_args lookup_args;
+    nfs41_lookup_res lookup_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res;
+    bitmap4 attr_request = { 1, { FATTR4_WORD0_FS_LOCATIONS } };
+    nfs41_file_info info;
+
+    compound_init(&compound, argops, resops, "fs_locations");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = parent;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res);
+    lookup_args.name = name;
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = &attr_request;
+    info.fs_locations = locations;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = &info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+int nfs41_secinfo(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN const nfs41_component *name,
+    OUT nfs41_secinfo_info *secinfo)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_secinfo_args secinfo_args;
+    nfs41_secinfo_noname_res secinfo_res;
+
+    compound_init(&compound, argops, resops, "secinfo");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    if (file == NULL) 
+        compound_add_op(&compound, OP_PUTROOTFH, NULL, &putfh_res);
+    else {
+        compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+        putfh_args.file = file;
+        putfh_args.in_recovery = 0;
+    }
+
+    compound_add_op(&compound, OP_SECINFO, &secinfo_args, &secinfo_res);
+    secinfo_args.name = name;
+    secinfo_res.secinfo = secinfo;
+
+    status = compound_encode_send_decode(session, &compound, FALSE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+int nfs41_secinfo_noname(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    OUT nfs41_secinfo_info *secinfo)
+{
+    int status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_secinfo_noname_args noname_args;
+    nfs41_secinfo_noname_res noname_res;
+
+    compound_init(&compound, argops, resops, "secinfo_no_name");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    if (file == NULL) 
+        compound_add_op(&compound, OP_PUTROOTFH, NULL, &putfh_res);
+    else {
+        compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+        putfh_args.file = file;
+        putfh_args.in_recovery = 0;
+    }
+
+    compound_add_op(&compound, OP_SECINFO_NO_NAME, &noname_args, &noname_res);
+    noname_args.type = SECINFO_STYLE4_CURRENT_FH;
+    noname_res.secinfo = secinfo;
+
+    status = compound_encode_send_decode(session, &compound, FALSE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+enum nfsstat4 nfs41_free_stateid(
+    IN nfs41_session *session,
+    IN stateid4 *stateid)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[2];
+    nfs_resop4 resops[2];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_free_stateid_args freestateid_args;
+    nfs41_free_stateid_res freestateid_res;
+
+    compound_init(&compound, argops, resops, "free_stateid");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_FREE_STATEID, &freestateid_args, &freestateid_res);
+    freestateid_args.stateid = stateid;
+
+    status = compound_encode_send_decode(session, &compound, FALSE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+enum nfsstat4 nfs41_test_stateid(
+    IN nfs41_session *session,
+    IN stateid_arg *stateid_array,
+    IN uint32_t count,
+    OUT uint32_t *status_array)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[2];
+    nfs_resop4 resops[2];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_test_stateid_args teststateid_args;
+    nfs41_test_stateid_res teststateid_res;
+
+    compound_init(&compound, argops, resops, "test_stateid");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_TEST_STATEID, &teststateid_args, &teststateid_res);
+    teststateid_args.stateids = stateid_array;
+    teststateid_args.count = count;
+    teststateid_res.resok.status = status_array;
+    teststateid_res.resok.count = count;
+
+    status = compound_encode_send_decode(session, &compound, FALSE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+enum nfsstat4 pnfs_rpc_layoutget(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t minlength,
+    IN uint64_t length,
+    OUT pnfs_layoutget_res_ok *layoutget_res_ok)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    pnfs_layoutget_args layoutget_args;
+    pnfs_layoutget_res layoutget_res = { 0 };
+    uint32_t i;
+    struct list_entry *entry;
+
+    compound_init(&compound, argops, resops, "layoutget");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_LAYOUTGET, &layoutget_args, &layoutget_res);
+    layoutget_args.signal_layout_avail = 0;
+    layoutget_args.layout_type = PNFS_LAYOUTTYPE_FILE;
+    layoutget_args.iomode = iomode;
+    layoutget_args.offset = offset;
+    layoutget_args.minlength = minlength;
+    layoutget_args.length = length;
+    layoutget_args.stateid = stateid;
+    layoutget_args.maxcount = session->fore_chan_attrs.ca_maxresponsesize - READ_OVERHEAD;
+
+    layoutget_res.u.res_ok = layoutget_res_ok;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    /* point each file handle to the meta server's superblock */
+    list_for_each(entry, &layoutget_res_ok->layouts) {
+        pnfs_layout *base = list_container(entry, pnfs_layout, entry);
+        if (base->type == PNFS_LAYOUTTYPE_FILE) {
+            pnfs_file_layout *layout = (pnfs_file_layout*)base;
+            for (i = 0; i < layout->filehandles.count; i++)
+                layout->filehandles.arr[i].fh.superblock = file->fh.superblock;
+        }
+    }
+out:
+    return status;
+}
+
+enum nfsstat4 pnfs_rpc_layoutcommit(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid4 *stateid,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN OPTIONAL uint64_t *new_last_offset,
+    IN OPTIONAL nfstime4 *new_time_modify,
+    OUT nfs41_file_info *info)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    pnfs_layoutcommit_args lc_args;
+    pnfs_layoutcommit_res lc_res;
+    nfs41_getattr_args getattr_args;
+    nfs41_getattr_res getattr_res;
+    bitmap4 attr_request;
+
+    nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+
+    compound_init(&compound, argops, resops, "layoutcommit");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_LAYOUTCOMMIT, &lc_args, &lc_res);
+    lc_args.offset = offset;
+    lc_args.length = length;
+    lc_args.stateid = stateid;
+    lc_args.new_time = new_time_modify;
+    lc_args.new_offset = new_last_offset;
+
+    compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res);
+    getattr_args.attr_request = &attr_request;
+    getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    getattr_res.info = info;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    if (compound_error(status = compound.res.status))
+        goto out;
+
+    /* update the attribute cache */
+    memcpy(&info->attrmask, &getattr_res.obj_attributes.attrmask,
+        sizeof(bitmap4));
+    nfs41_attr_cache_update(session_name_cache(session),
+        file->fh.fileid, info);
+out:
+    return status;
+}
+
+enum nfsstat4 pnfs_rpc_layoutreturn(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN enum pnfs_layout_type type,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN stateid4 *stateid,
+    OUT pnfs_layoutreturn_res *layoutreturn_res)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[3];
+    nfs_resop4 resops[3];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    pnfs_layoutreturn_args layoutreturn_args;
+
+    compound_init(&compound, argops, resops, "layoutreturn");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = 0;
+
+    compound_add_op(&compound, OP_LAYOUTRETURN, &layoutreturn_args, layoutreturn_res);
+    layoutreturn_args.reclaim = 0;
+    layoutreturn_args.type = type;
+    layoutreturn_args.iomode = iomode;
+    layoutreturn_args.return_type = PNFS_RETURN_FILE;
+    layoutreturn_args.offset = offset;
+    layoutreturn_args.length = length;
+    layoutreturn_args.stateid = stateid;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+enum nfsstat4 pnfs_rpc_getdeviceinfo(
+    IN nfs41_session *session,
+    IN unsigned char *deviceid,
+    OUT pnfs_file_device *device)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[2];
+    nfs_resop4 resops[2];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    pnfs_getdeviceinfo_args getdeviceinfo_args;
+    pnfs_getdeviceinfo_res getdeviceinfo_res;
+
+    compound_init(&compound, argops, resops, "get_deviceinfo");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_GETDEVICEINFO,
+        &getdeviceinfo_args, &getdeviceinfo_res);
+    getdeviceinfo_args.deviceid = deviceid;
+    getdeviceinfo_args.layout_type = PNFS_LAYOUTTYPE_FILE;
+    getdeviceinfo_args.maxcount = NFS41_MAX_SERVER_CACHE; /* XXX */
+    getdeviceinfo_args.notify_types.count = 0;
+    getdeviceinfo_res.u.res_ok.device = device;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+out:
+    return status;
+}
+
+enum nfsstat4 nfs41_rpc_openattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN bool_t createdir,
+    OUT nfs41_fh *fh_out)
+{
+    enum nfsstat4 status;
+    nfs41_compound compound;
+    nfs_argop4 argops[4];
+    nfs_resop4 resops[4];
+    nfs41_sequence_args sequence_args;
+    nfs41_sequence_res sequence_res;
+    nfs41_putfh_args putfh_args;
+    nfs41_putfh_res putfh_res;
+    nfs41_openattr_args openattr_args;
+    nfs41_openattr_res openattr_res;
+    nfs41_getfh_res getfh_res;
+
+    compound_init(&compound, argops, resops, "openattr");
+
+    compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
+    nfs41_session_sequence(&sequence_args, session, 0);
+
+    compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res);
+    putfh_args.file = file;
+    putfh_args.in_recovery = FALSE;
+
+    compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res);
+    openattr_args.createdir = createdir;
+
+    compound_add_op(&compound, OP_GETFH, NULL, &getfh_res);
+    getfh_res.fh = fh_out;
+
+    status = compound_encode_send_decode(session, &compound, TRUE);
+    if (status)
+        goto out;
+
+    compound_error(status = compound.res.status);
+
+    fh_out->superblock = file->fh.superblock;
+out:
+    return status;
+}
diff --git a/reactos/base/services/nfsd/nfs41_ops.h b/reactos/base/services/nfsd/nfs41_ops.h
new file mode 100644 (file)
index 0000000..3b04a91
--- /dev/null
@@ -0,0 +1,1286 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_NFS_OPS_H__
+#define __NFS41_NFS_OPS_H__
+
+#include "nfs41.h"
+#include "pnfs.h"
+
+
+/* Operation arrays */
+enum nfs_opnum4 {
+    OP_ACCESS               = 3,
+    OP_CLOSE                = 4,
+    OP_COMMIT               = 5,
+    OP_CREATE               = 6,
+    OP_DELEGPURGE           = 7,
+    OP_DELEGRETURN          = 8,
+    OP_GETATTR              = 9,
+    OP_GETFH                = 10,
+    OP_LINK                 = 11,
+    OP_LOCK                 = 12,
+    OP_LOCKT                = 13,
+    OP_LOCKU                = 14,
+    OP_LOOKUP               = 15,
+    OP_LOOKUPP              = 16,
+    OP_NVERIFY              = 17,
+    OP_OPEN                 = 18,
+    OP_OPENATTR             = 19,
+    OP_OPEN_CONFIRM         = 20, /* Mandatory not-to-implement */
+    OP_OPEN_DOWNGRADE       = 21,
+    OP_PUTFH                = 22,
+    OP_PUTPUBFH             = 23,
+    OP_PUTROOTFH            = 24,
+    OP_READ                 = 25,
+    OP_READDIR              = 26,
+    OP_READLINK             = 27,
+    OP_REMOVE               = 28,
+    OP_RENAME               = 29,
+    OP_RENEW                = 30, /* Mandatory not-to-implement */
+    OP_RESTOREFH            = 31,
+    OP_SAVEFH               = 32,
+    OP_SECINFO              = 33,
+    OP_SETATTR              = 34,
+    OP_SETCLIENTID          = 35, /* Mandatory not-to-implement */
+    OP_SETCLIENTID_CONFIRM  = 36, /* Mandatory not-to-implement */
+    OP_VERIFY               = 37,
+    OP_WRITE                = 38,
+    OP_RELEASE_LOCKOWNER    = 39, /* Mandatory not-to-implement */
+
+    /* new operations for NFSv4.1 */
+    OP_BACKCHANNEL_CTL      = 40,
+    OP_BIND_CONN_TO_SESSION = 41,
+    OP_EXCHANGE_ID          = 42,
+    OP_CREATE_SESSION       = 43,
+    OP_DESTROY_SESSION      = 44,
+    OP_FREE_STATEID         = 45,
+    OP_GET_DIR_DELEGATION   = 46,
+    OP_GETDEVICEINFO        = 47,
+    OP_GETDEVICELIST        = 48,
+    OP_LAYOUTCOMMIT         = 49,
+    OP_LAYOUTGET            = 50,
+    OP_LAYOUTRETURN         = 51,
+    OP_SECINFO_NO_NAME      = 52,
+    OP_SEQUENCE             = 53,
+    OP_SET_SSV              = 54,
+    OP_TEST_STATEID         = 55,
+    OP_WANT_DELEGATION      = 56,
+    OP_DESTROY_CLIENTID     = 57,
+    OP_RECLAIM_COMPLETE     = 58,
+    OP_ILLEGAL              = 10044
+};
+
+
+/* OP_EXCHANGE_ID */
+enum {
+    EXCHGID4_FLAG_SUPP_MOVED_REFER      = 0x00000001,
+    EXCHGID4_FLAG_SUPP_MOVED_MIGR       = 0x00000002,
+
+    EXCHGID4_FLAG_BIND_PRINC_STATEID    = 0x00000100,
+
+    EXCHGID4_FLAG_USE_NON_PNFS          = 0x00010000,
+    EXCHGID4_FLAG_USE_PNFS_MDS          = 0x00020000,
+    EXCHGID4_FLAG_USE_PNFS_DS           = 0x00040000,
+
+    EXCHGID4_FLAG_MASK_PNFS             = 0x00070000,
+
+    EXCHGID4_FLAG_UPD_CONFIRMED_REC_A   = 0x40000000,
+    EXCHGID4_FLAG_CONFIRMED_R           = 0x80000000
+};
+
+typedef enum {
+    SP4_NONE        = 0,
+    SP4_MACH_CRED   = 1,
+    SP4_SSV         = 2
+} state_protect_how4;
+
+typedef struct __state_protect4_a {
+    state_protect_how4      spa_how;
+} state_protect4_a;
+
+typedef struct __nfs41_exchange_id_args {
+    client_owner4           *eia_clientowner;
+    uint32_t                eia_flags;
+    state_protect4_a        eia_state_protect;
+    nfs_impl_id4            *eia_client_impl_id; /* <1> */
+} nfs41_exchange_id_args;
+
+typedef struct __state_protect4_r {
+    state_protect_how4      spr_how;
+} state_protect4_r;
+
+typedef struct __nfs41_exchange_id_res {
+    uint32_t                status;
+    uint64_t                clientid;
+    uint32_t                sequenceid;
+    uint32_t                flags;
+    state_protect4_r        state_protect;
+    server_owner4           server_owner;
+    uint32_t                server_scope_len;
+    char                    server_scope[NFS4_OPAQUE_LIMIT];
+} nfs41_exchange_id_res;
+
+typedef struct __nfs41_callback_sec_parms {
+    uint32_t type;
+    union {
+        /* case AUTH_SYS */
+        struct __authsys_parms {
+            uint32_t        stamp;
+            char            *machinename;
+        } auth_sys;
+        /* case RPCSEC_GSS */
+        struct __rpcsec_gss_parms {
+            uint32_t        gss_srv_type;
+            char            *srv_gssctx_handle;
+            uint32_t         srv_gssctx_hdle_len;
+            char            *clnt_gssctx_handle;
+            uint32_t        clnt_gssctx_hdle_len;
+        } rpcsec_gss;
+    } u;
+} nfs41_callback_secparms;
+
+/* OP_CREATE_SESSION */
+typedef struct __nfs41_create_session_args {
+    uint64_t                csa_clientid;
+    uint32_t                csa_sequence;
+    uint32_t                csa_flags;
+    nfs41_channel_attrs     csa_fore_chan_attrs;
+    nfs41_channel_attrs     csa_back_chan_attrs;
+    uint32_t                csa_cb_program;
+    nfs41_callback_secparms csa_cb_secparams[2];
+} nfs41_create_session_args;
+
+typedef struct __nfs41_create_session_res {
+    unsigned char           *csr_sessionid;
+    uint32_t                csr_sequence;
+    uint32_t                csr_flags;
+    nfs41_channel_attrs     *csr_fore_chan_attrs;
+    nfs41_channel_attrs     *csr_back_chan_attrs;
+} nfs41_create_session_res;
+
+
+/* OP_BIND_CONN_TO_SESSION */
+enum channel_dir_from_client4 {
+    CDFC4_FORE              = 0x1,
+    CDFC4_BACK              = 0x2,
+    CDFC4_FORE_OR_BOTH      = 0x3,
+    CDFC4_BACK_OR_BOTH      = 0x7
+};
+
+enum channel_dir_from_server4 {
+    CDFS4_FORE              = 0x1,
+    CDFS4_BACK              = 0x2,
+    CDFS4_BOTH              = 0x3
+};
+
+typedef struct __nfs41_bind_conn_to_session_args {
+    unsigned char           *sessionid;
+    enum channel_dir_from_client4 dir;
+} nfs41_bind_conn_to_session_args;
+
+typedef struct __nfs41_bind_conn_to_session_res {
+    enum nfsstat4           status;
+    /* case NFS4_OK: */
+    enum channel_dir_from_server4 dir;
+} nfs41_bind_conn_to_session_res;
+
+
+/* OP_DESTROY_SESSION */
+typedef struct __nfs41_destroy_session_args {
+    unsigned char           *dsa_sessionid;
+} nfs41_destroy_session_args;
+
+typedef struct __nfs41_destroy_session_res {
+    uint32_t                dsr_status;
+} nfs41_destroy_session_res;
+
+
+/* OP_DESTROY_CLIENTID */
+typedef struct __nfs41_destroy_clientid_args {
+    uint64_t        dca_clientid;
+} nfs41_destroy_clientid_args;
+
+typedef struct __nfs41_destroy_clientid_res {
+    uint32_t        dcr_status;
+} nfs41_destroy_clientid_res;
+
+
+/* OP_SEQUENCE */
+typedef struct __nfs41_sequence_args {
+    unsigned char           *sa_sessionid;
+    uint32_t                sa_sequenceid;
+    uint32_t                sa_slotid;
+    uint32_t                sa_highest_slotid;
+    bool_t                  sa_cachethis;
+} nfs41_sequence_args;
+
+enum {
+    SEQ4_STATUS_CB_PATH_DOWN                = 0x00000001,
+    SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING    = 0x00000002,
+    SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED     = 0x00000004,
+    SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED   = 0x00000008,
+    SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED  = 0x00000010,
+    SEQ4_STATUS_ADMIN_STATE_REVOKED         = 0x00000020,
+    SEQ4_STATUS_RECALLABLE_STATE_REVOKED    = 0x00000040,
+    SEQ4_STATUS_LEASE_MOVED                 = 0x00000080,
+    SEQ4_STATUS_RESTART_RECLAIM_NEEDED      = 0x00000100,
+    SEQ4_STATUS_CB_PATH_DOWN_SESSION        = 0x00000200,
+    SEQ4_STATUS_BACKCHANNEL_FAULT           = 0x00000400,
+    SEQ4_STATUS_DEVID_CHANGED               = 0x00000800,
+    SEQ4_STATUS_DEVID_DELETED               = 0x00001000
+};
+
+typedef struct __nfs41_sequence_res_ok {
+    unsigned char           sr_sessionid[NFS4_SESSIONID_SIZE];
+    uint32_t                sr_sequenceid;
+    uint32_t                sr_slotid;
+    uint32_t                sr_highest_slotid;
+    uint32_t                sr_target_highest_slotid;
+    uint32_t                sr_status_flags;
+} nfs41_sequence_res_ok;
+
+typedef struct __nfs41_sequence_res {
+    uint32_t                sr_status;
+    /* case NFS4_OK: */
+    nfs41_sequence_res_ok   sr_resok4;
+} nfs41_sequence_res;
+
+
+/* OP_RECLAIM_COMPLETE */
+typedef struct __nfs41_reclaim_complete_res {
+    enum nfsstat4           status;
+} nfs41_reclaim_complete_res;
+
+
+/* recoverable stateid argument */
+enum stateid_type {
+    STATEID_OPEN,
+    STATEID_LOCK,
+    STATEID_DELEG_FILE,
+    STATEID_DELEG_DIR,
+    STATEID_LAYOUT,
+    STATEID_SPECIAL
+};
+typedef struct __stateid_arg {
+    stateid4                stateid;
+    enum stateid_type       type;
+    nfs41_open_state        *open;
+    nfs41_delegation_state  *delegation;
+} stateid_arg;
+
+
+/* OP_ACCESS */
+enum {
+    ACCESS4_READ            = 0x00000001,
+    ACCESS4_LOOKUP          = 0x00000002,
+    ACCESS4_MODIFY          = 0x00000004,
+    ACCESS4_EXTEND          = 0x00000008,
+    ACCESS4_DELETE          = 0x00000010,
+    ACCESS4_EXECUTE         = 0x00000020
+};
+
+typedef struct __nfs41_access_args {
+    uint32_t                access;
+} nfs41_access_args;
+
+typedef struct __nfs41_access_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    uint32_t                supported;
+    uint32_t                access;
+} nfs41_access_res;
+
+
+/* OP_CLOSE */
+typedef struct __nfs41_op_close_args {
+//  uint32_t                seqid; // not used, always 0
+    stateid_arg             *stateid;
+} nfs41_op_close_args;
+
+typedef struct __nfs41_op_close_res {
+    uint32_t                status;
+} nfs41_op_close_res;
+
+
+/* OP_COMMIT */
+typedef struct __nfs41_commit_args {
+    uint64_t                offset;
+    uint32_t                count;
+} nfs41_commit_args;
+
+typedef struct __nfs41_commit_res {
+    uint32_t                status;
+    nfs41_write_verf        *verf;
+} nfs41_commit_res;
+
+
+/* OP_CREATE */
+typedef struct __specdata4 {
+    uint32_t                specdata1;
+    uint32_t                specdata2;
+} specdata4;
+
+typedef struct __createtype4 {
+    uint32_t                type;
+    union {
+    /* case NF4LNK: */
+        struct __create_type_lnk {
+            uint32_t        linkdata_len;
+            const char      *linkdata;
+        } lnk;
+    /* case NF4BLK, NF4CHR: */
+        specdata4           devdata;
+    } u;
+} createtype4;
+
+typedef struct __nfs41_create_args {
+    createtype4             objtype;
+    const nfs41_component   *name;
+    nfs41_file_info         *createattrs;
+} nfs41_create_args;
+
+typedef struct __nfs41_create_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    change_info4            cinfo;
+    bitmap4                 attrset;
+} nfs41_create_res;
+
+
+/* OP_DELEGPURGE */
+typedef struct __nfs41_delegpurge_res {
+    uint32_t                status;
+} nfs41_delegpurge_res;
+
+
+/* OP_DELEGRETURN */
+typedef struct __nfs41_delegreturn_args {
+    stateid_arg             *stateid;
+} nfs41_delegreturn_args;
+
+typedef struct __nfs41_delegreturn_res {
+    uint32_t                status;
+} nfs41_delegreturn_res;
+
+
+/* OP_LINK */
+typedef struct __nfs41_link_args {
+    const nfs41_component   *newname;
+} nfs41_link_args;
+
+typedef struct __nfs41_link_res {
+    uint32_t                status;
+    /* case NFS4_OK */
+    change_info4            cinfo;
+} nfs41_link_res;
+
+
+/* OP_LOCK */
+enum {
+    READ_LT                 = 1,
+    WRITE_LT                = 2,
+    READW_LT                = 3,    /* blocking read */
+    WRITEW_LT               = 4     /* blocking write */
+};
+
+typedef struct __open_to_lock_owner4 {
+    uint32_t                open_seqid;
+    stateid_arg             *open_stateid;
+    uint32_t                lock_seqid;
+    state_owner4            *lock_owner;
+} open_to_lock_owner4;
+
+typedef struct __exist_lock_owner4 {
+    stateid_arg             *lock_stateid;
+    uint32_t                lock_seqid;
+} exist_lock_owner4;
+
+typedef struct __locker4 {
+    bool_t                  new_lock_owner;
+    union {
+        /* case TRUE: */
+        open_to_lock_owner4 open_owner;
+        /* case FALSE: */
+        exist_lock_owner4   lock_owner;
+    } u;
+} locker4;
+
+typedef struct __nfs41_lock_args {
+    uint32_t                locktype;
+    bool_t                  reclaim;
+    uint64_t                offset;
+    uint64_t                length;
+    locker4                 locker;
+} nfs41_lock_args;
+
+typedef struct __lock_res_denied {
+    uint64_t                offset;
+    uint64_t                length;
+    uint32_t                locktype;
+    state_owner4            owner;
+} lock_res_denied;
+
+typedef struct __lock_res_ok {
+    stateid4                *lock_stateid;
+} lock_res_ok;
+
+typedef struct __nfs41_lock_res {
+    uint32_t                status;
+    union {
+    /* case NFS4_OK: */
+        lock_res_ok         resok4;
+    /* case NFS4ERR_DENIED: */
+        lock_res_denied     denied;
+    /* default: void; */
+    } u;
+} nfs41_lock_res;
+
+
+/* OP_LOCKT */
+typedef struct __nfs41_lockt_args {
+    uint32_t                locktype;
+    uint64_t                offset;
+    uint64_t                length;
+    state_owner4            *owner;
+} nfs41_lockt_args;
+
+typedef struct __nfs41_lockt_res {
+    uint32_t                status;
+    /* case NFS4ERR_DENIED: */
+    lock_res_denied         denied;
+    /* default: void; */
+} nfs41_lockt_res;
+
+
+/* OP_LOCKU */
+typedef struct __nfs41_locku_args {
+    uint32_t                locktype;
+    uint32_t                seqid;
+    stateid_arg             *lock_stateid;
+    uint64_t                offset;
+    uint64_t                length;
+} nfs41_locku_args;
+
+typedef struct __nfs41_locku_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    stateid4                *lock_stateid;
+} nfs41_locku_res;
+
+
+/* OP_LOOKUP */
+typedef struct __nfs41_lookup_args {
+    const nfs41_component   *name;
+} nfs41_lookup_args;
+
+typedef struct __nfs41_lookup_res {
+    uint32_t                status;
+} nfs41_lookup_res;
+
+
+/* OP_GETFH */
+typedef struct __nfs41_getfh_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    nfs41_fh                *fh;
+} nfs41_getfh_res;
+
+
+/* OP_PUTFH */
+typedef struct __nfs41_putfh_args {
+    nfs41_path_fh           *file;
+    bool_t                  in_recovery;
+} nfs41_putfh_args;
+
+typedef struct __nfs41_putfh_res {
+    uint32_t                status;
+} nfs41_putfh_res;
+
+
+/* OP_PUTROOTFH */
+typedef struct __nfs41_putrootfh_res {
+    uint32_t                status;
+} nfs41_putrootfh_res;
+
+
+/* OP_GETATTR */
+typedef struct __nfs41_getattr_args {
+    bitmap4                 *attr_request;
+} nfs41_getattr_args;
+
+typedef struct __nfs41_getattr_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    fattr4                  obj_attributes;
+    nfs41_file_info         *info;
+} nfs41_getattr_res;
+
+
+/* OP_OPEN */
+enum createmode4 {
+    UNCHECKED4      = 0,
+    GUARDED4        = 1,
+    EXCLUSIVE4      = 2,
+    EXCLUSIVE4_1    = 3
+};
+
+typedef struct __createhow4 {
+    uint32_t            mode;
+    nfs41_file_info     *createattrs;
+    unsigned char       createverf[NFS4_VERIFIER_SIZE];
+} createhow4;
+
+enum opentype4 {
+    OPEN4_NOCREATE  = 0,
+    OPEN4_CREATE    = 1
+};
+
+typedef struct __openflag4 {
+    uint32_t                opentype;
+    /* case OPEN4_CREATE: */
+    createhow4              how;
+} openflag4;
+
+enum {
+    OPEN4_SHARE_ACCESS_READ     = 0x00000001,
+    OPEN4_SHARE_ACCESS_WRITE    = 0x00000002,
+    OPEN4_SHARE_ACCESS_BOTH     = 0x00000003,
+
+    OPEN4_SHARE_DENY_NONE       = 0x00000000,
+    OPEN4_SHARE_DENY_READ       = 0x00000001,
+    OPEN4_SHARE_DENY_WRITE      = 0x00000002,
+    OPEN4_SHARE_DENY_BOTH       = 0x00000003,
+
+    OPEN4_SHARE_ACCESS_WANT_DELEG_MASK      = 0xFF00,
+    OPEN4_SHARE_ACCESS_WANT_NO_PREFERENCE   = 0x0000,
+    OPEN4_SHARE_ACCESS_WANT_READ_DELEG      = 0x0100,
+    OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG     = 0x0200,
+    OPEN4_SHARE_ACCESS_WANT_ANY_DELEG       = 0x0300,
+    OPEN4_SHARE_ACCESS_WANT_NO_DELEG        = 0x0400,
+    OPEN4_SHARE_ACCESS_WANT_CANCEL          = 0x0500,
+
+    OPEN4_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL = 0x10000,
+    OPEN4_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED = 0x20000
+};
+
+enum open_delegation_type4 {
+    OPEN_DELEGATE_NONE      = 0,
+    OPEN_DELEGATE_READ      = 1,
+    OPEN_DELEGATE_WRITE     = 2,
+    OPEN_DELEGATE_NONE_EXT  = 3
+};
+
+enum open_claim_type4 {
+    CLAIM_NULL              = 0,
+    CLAIM_PREVIOUS          = 1,
+    CLAIM_DELEGATE_CUR      = 2,
+    CLAIM_DELEGATE_PREV     = 3,
+    CLAIM_FH                = 4,
+    CLAIM_DELEG_CUR_FH      = 5,
+    CLAIM_DELEG_PREV_FH     = 6
+};
+
+enum why_no_delegation4 {
+    WND4_NOT_WANTED         = 0,
+    WND4_CONTENTION         = 1,
+    WND4_RESOURCE           = 2,
+    WND4_NOT_SUPP_FTYPE     = 3,
+    WND4_WRITE_DELEG_NOT_SUPP_FTYPE = 4,
+    WND4_NOT_SUPP_UPGRADE   = 5,
+    WND4_NOT_SUPP_DOWNGRADE = 6,
+    WND4_CANCELED           = 7,
+    WND4_IS_DIR             = 8
+};
+
+typedef struct __open_claim4 {
+    uint32_t                claim;
+    union {
+    /* case CLAIM_NULL: */
+        struct __open_claim_null {
+            const nfs41_component *filename;
+        } null;
+    /* case CLAIM_PREVIOUS: */
+        struct __open_claim_prev {
+            uint32_t        delegate_type;
+        } prev;
+    /* case CLAIM_DELEGATE_CUR: */
+        struct __open_claim_deleg_cur {
+            stateid_arg     *delegate_stateid;
+            nfs41_component *name;
+        } deleg_cur;
+    /* case CLAIM_DELEG_CUR_FH: */
+        struct __open_claim_deleg_cur_fh {
+            stateid_arg     *delegate_stateid;
+        } deleg_cur_fh;
+    /* case CLAIM_DELEGATE_PREV: */
+        struct __open_claim_deleg_prev {
+            const nfs41_component *filename;
+        } deleg_prev;
+    } u;
+} open_claim4;
+
+typedef struct __nfs41_op_open_args {
+    uint32_t                seqid;
+    uint32_t                share_access;
+    uint32_t                share_deny;
+    state_owner4            *owner;
+    openflag4               openhow;
+    open_claim4             *claim;
+} nfs41_op_open_args;
+
+enum {
+    OPEN4_RESULT_CONFIRM            = 0x00000002,
+    OPEN4_RESULT_LOCKTYPE_POSIX     = 0x00000004,
+    OPEN4_RESULT_PRESERVE_UNLINKED  = 0x00000008,
+    OPEN4_RESULT_MAY_NOTIFY_LOCK    = 0x00000020
+};
+
+typedef struct __nfs41_op_open_res_ok {
+    stateid4                *stateid;
+    change_info4            cinfo;
+    uint32_t                rflags;
+    bitmap4                 attrset;
+    open_delegation4        *delegation;
+} nfs41_op_open_res_ok;
+
+typedef struct __nfs41_op_open_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    nfs41_op_open_res_ok    resok4;
+} nfs41_op_open_res;
+
+
+/* OP_OPENATTR */
+typedef struct __nfs41_openattr_args {
+    bool_t                  createdir;
+} nfs41_openattr_args;
+
+typedef struct __nfs41_openattr_res {
+    uint32_t                status;
+} nfs41_openattr_res;
+
+
+/* OP_READ */
+typedef struct __nfs41_read_args {
+    stateid_arg             *stateid; /* -> nfs41_op_open_res_ok.stateid */
+    uint64_t                offset;
+    uint32_t                count;
+} nfs41_read_args;
+
+typedef struct __nfs41_read_res_ok {
+    bool_t                  eof;
+    uint32_t                data_len;
+    unsigned char           *data; /* caller-allocated */
+} nfs41_read_res_ok;
+
+typedef struct __nfs41_read_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    nfs41_read_res_ok       resok4;
+} nfs41_read_res;
+
+
+/* OP_READDIR */
+typedef struct __nfs41_readdir_args {
+    nfs41_readdir_cookie    cookie;
+    uint32_t                dircount;
+    uint32_t                maxcount;
+    bitmap4                 *attr_request;
+} nfs41_readdir_args;
+
+typedef struct __nfs41_readdir_entry {
+    uint64_t                cookie;
+    uint32_t                name_len;
+    uint32_t                next_entry_offset;
+    nfs41_file_info         attr_info;
+    char                    name[1];
+} nfs41_readdir_entry;
+
+typedef struct __nfs41_readdir_list {
+    bool_t                  has_entries;
+    uint32_t                entries_len;
+    unsigned char           *entries;
+    bool_t                  eof;
+} nfs41_readdir_list;
+
+typedef struct __nfs41_readdir_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    unsigned char           cookieverf[NFS4_VERIFIER_SIZE];
+    nfs41_readdir_list      reply;
+} nfs41_readdir_res;
+
+
+/* OP_READLINK */
+typedef struct __nfs41_readlink_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    uint32_t                link_len;
+    char                    *link;
+} nfs41_readlink_res;
+
+
+/* OP_REMOVE */
+typedef struct __nfs41_remove_args {
+    const nfs41_component   *target;
+} nfs41_remove_args;
+
+typedef struct __nfs41_remove_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    change_info4            cinfo;
+} nfs41_remove_res;
+
+
+/* OP_RENAME */
+typedef struct __nfs41_rename_args {
+    const nfs41_component   *oldname;
+    const nfs41_component   *newname;
+} nfs41_rename_args;
+
+typedef struct __nfs41_rename_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    change_info4            source_cinfo;
+    change_info4            target_cinfo;
+} nfs41_rename_res;
+
+
+/* OP_RESTOREFH */
+/* OP_SAVEFH */
+typedef struct __nfs41_restorefh_savefh_res {
+    uint32_t                status;
+} nfs41_restorefh_res, nfs41_savefh_res;
+
+
+/* OP_SETATTR */
+enum time_how4 {
+    SET_TO_SERVER_TIME4 = 0,
+    SET_TO_CLIENT_TIME4 = 1
+};
+
+typedef struct __nfs41_setattr_args {
+    stateid_arg             *stateid;
+    nfs41_file_info         *info;
+} nfs41_setattr_args;
+
+typedef struct __nfs41_setattr_res {
+    uint32_t                status;
+    bitmap4                 attrsset;
+} nfs41_setattr_res;
+
+
+/* OP_WANT_DELEGATION */
+typedef struct __deleg_claim4 {
+    uint32_t                claim;
+    /* case CLAIM_PREVIOUS: */
+    uint32_t                prev_delegate_type;
+} deleg_claim4;
+
+typedef struct __nfs41_want_delegation_args {
+    uint32_t                want;
+    deleg_claim4            *claim;
+} nfs41_want_delegation_args;
+
+typedef struct __nfs41_want_delegation_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    open_delegation4        *delegation;
+} nfs41_want_delegation_res;
+/* OP_FREE_STATEID */
+typedef struct __nfs41_free_stateid_args {
+    stateid4                *stateid;
+} nfs41_free_stateid_args;
+
+typedef struct __nfs41_free_stateid_res {
+    uint32_t                status;
+} nfs41_free_stateid_res;
+
+
+/* OP_TEST_STATEID */
+typedef struct __nfs41_test_stateid_args {
+    uint32_t                count;
+    stateid_arg             *stateids; // caller-allocated array
+} nfs41_test_stateid_args;
+
+typedef struct __nfs41_test_stateid_res {
+    uint32_t                status;
+    struct {
+        uint32_t            count;
+        uint32_t            *status; // caller-allocated array
+    } resok;
+} nfs41_test_stateid_res;
+
+
+/* OP_WRITE */
+enum stable_how4 {
+    UNSTABLE4       = 0,
+    DATA_SYNC4      = 1,
+    FILE_SYNC4      = 2
+};
+
+typedef struct __nfs41_write_args {
+    stateid_arg             *stateid; /* -> nfs41_op_open_res_ok.stateid */
+    uint64_t                offset;
+    uint32_t                stable; /* stable_how4 */
+    uint32_t                data_len;
+    unsigned char           *data; /* caller-allocated */
+} nfs41_write_args;
+
+typedef struct __nfs41_write_res_ok {
+    uint32_t                count;
+    nfs41_write_verf        *verf;
+} nfs41_write_res_ok;
+
+typedef struct __nfs41_write_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    nfs41_write_res_ok      resok4;
+} nfs41_write_res;
+
+/* OP_SECINFO */
+enum sec_flavor {
+    RPC_GSS_SVC_NONE = 1,
+    RPC_GSS_SVC_INTEGRITY = 2,
+    RPC_GSS_SVC_PRIVACY = 3,
+};
+
+#define RPCSEC_GSS 6
+#define MAX_OID_LEN 128
+typedef struct __nfs41_secinfo_info {
+    char                    oid[MAX_OID_LEN];
+    uint32_t                oid_len;
+    uint32_t                sec_flavor;
+    uint32_t                qop;
+    enum sec_flavor         type;
+} nfs41_secinfo_info;
+
+typedef struct __nfs41_secinfo_args {
+    const nfs41_component   *name;
+} nfs41_secinfo_args;
+
+#define MAX_SECINFOS 6
+
+/* OP_SECINFO_NO_NAME */
+enum secinfo_no_name_type {
+    SECINFO_STYLE4_CURRENT_FH = 0,
+    SECINFO_STYLE4_PARENT = 1
+};
+
+typedef struct __nfs41_secinfo_noname_args {
+#ifdef __REACTOS__
+    uint32_t type;
+#else
+    enum secinfo_noname_type type;
+#endif
+} nfs41_secinfo_noname_args;
+
+typedef struct __nfs41_secinfo_noname_res {
+    uint32_t                status;
+    /* case NFS4_OK: */
+    nfs41_secinfo_info      *secinfo;
+    uint32_t                count;
+} nfs41_secinfo_noname_res;
+
+/* LAYOUTGET */
+typedef struct __pnfs_layoutget_args {
+    bool_t                  signal_layout_avail;
+    enum pnfs_layout_type   layout_type;
+    enum pnfs_iomode        iomode;
+    uint64_t                offset;
+    uint64_t                length;
+    uint64_t                minlength;
+    stateid_arg             *stateid;
+    uint32_t                maxcount;
+} pnfs_layoutget_args;
+
+typedef struct __pnfs_layoutget_res_ok {
+    bool_t                  return_on_close;
+    stateid4                stateid;
+    uint32_t                count;
+    struct list_entry       layouts; /* list of pnfs_layouts */
+} pnfs_layoutget_res_ok;
+
+typedef struct __pnfs_layoutget_res {
+    enum nfsstat4           status;
+    union {
+    /* case NFS4_OK: */
+        pnfs_layoutget_res_ok *res_ok;
+    /* case NFS4ERR_LAYOUTTRYLATER: */
+        bool_t              will_signal_layout_avail;
+    /* default: void; */
+    } u;
+} pnfs_layoutget_res;
+
+
+/* LAYOUTCOMMIT */
+typedef struct __pnfs_layoutcommit_args {
+    uint64_t                offset;
+    uint64_t                length;
+    stateid4                *stateid;
+    nfstime4                *new_time;
+    uint64_t                *new_offset;
+} pnfs_layoutcommit_args;
+
+typedef struct __pnfs_layoutcommit_res {
+    uint32_t                status;
+    /* case NFS4_OK */
+    bool_t                  has_new_size;
+    /* case TRUE */
+    uint64_t                new_size;
+} pnfs_layoutcommit_res;
+
+
+/* LAYOUTRETURN */
+typedef struct __pnfs_layoutreturn_args {
+    bool_t                  reclaim;
+    enum pnfs_layout_type   type;
+    enum pnfs_iomode        iomode;
+    enum pnfs_return_type   return_type;
+    /* case LAYOUTRETURN4_FILE: */
+    uint64_t                offset;
+    uint64_t                length;
+    stateid4                *stateid;
+} pnfs_layoutreturn_args;
+
+typedef struct __pnfs_layoutreturn_res {
+    enum nfsstat4           status;
+    /* case NFS4_OK: */
+    bool_t                  stateid_present;
+    /* case true: */
+    stateid4                stateid;
+} pnfs_layoutreturn_res;
+
+
+/* GETDEVICEINFO */
+typedef struct __pnfs_getdeviceinfo_args {
+    unsigned char           *deviceid;
+    enum pnfs_layout_type   layout_type;
+    uint32_t                maxcount;
+    bitmap4                 notify_types;
+} pnfs_getdeviceinfo_args;
+
+typedef struct __pnfs_getdeviceinfo_res_ok {
+    pnfs_file_device        *device;
+    bitmap4                 notification;
+} pnfs_getdeviceinfo_res_ok;
+
+typedef struct __pnfs_getdeviceinfo_res {
+    enum nfsstat4           status;
+    union {
+    /* case NFS4_OK: */
+        pnfs_getdeviceinfo_res_ok res_ok;
+    /* case NFS4ERR_TOOSMALL: */
+       uint32_t             mincount;
+    /* default: void; */
+    } u;
+} pnfs_getdeviceinfo_res;
+
+
+/* nfs41_ops.c */
+int nfs41_exchange_id(
+    IN nfs41_rpc_clnt *rpc,
+    IN client_owner4 *owner,
+    IN uint32_t flags_in,
+    OUT nfs41_exchange_id_res *res_out);
+
+int nfs41_create_session(
+    IN nfs41_client *clnt,
+    OUT nfs41_session *session,
+    IN bool_t try_recovery);
+
+enum nfsstat4 nfs41_bind_conn_to_session(
+    IN nfs41_rpc_clnt *rpc,
+    IN const unsigned char *sessionid,
+    IN enum channel_dir_from_client4 dir);
+
+int nfs41_destroy_session(
+    IN nfs41_session *session);
+
+int nfs41_destroy_clientid(
+    IN nfs41_rpc_clnt *rpc,
+    IN uint64_t clientid);
+
+int nfs41_send_sequence(
+    IN nfs41_session *session);
+
+enum nfsstat4 nfs41_reclaim_complete(
+    IN nfs41_session *session);
+
+int nfs41_lookup(
+    IN nfs41_root *root,
+    IN nfs41_session *session,
+    IN OUT nfs41_abs_path *path,
+    OUT OPTIONAL nfs41_path_fh *parent_out,
+    OUT OPTIONAL nfs41_path_fh *target_out,
+    OUT OPTIONAL nfs41_file_info *info_out,
+    OUT nfs41_session **session_out);
+
+int nfs41_open(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN nfs41_path_fh *file,
+    IN state_owner4 *owner,
+    IN open_claim4 *claim,
+    IN uint32_t allow,
+    IN uint32_t deny,
+    IN uint32_t create,
+    IN uint32_t how_mode,
+    IN OPTIONAL nfs41_file_info *createattrs,
+    IN bool_t try_recovery,
+    OUT stateid4 *stateid,
+    OUT open_delegation4 *delegation,
+    OUT OPTIONAL nfs41_file_info *info);
+
+int nfs41_create(
+    IN nfs41_session *session,
+    IN uint32_t type,
+    IN nfs41_file_info *createattrs,
+    IN OPTIONAL const char *symlink,
+    IN nfs41_path_fh *parent,
+    OUT nfs41_path_fh *file,
+    OUT nfs41_file_info *info);
+
+int nfs41_close(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid);
+
+int nfs41_write(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN unsigned char *data,
+    IN uint32_t data_len,
+    IN uint64_t offset,
+    IN enum stable_how4 stable,
+    OUT uint32_t *bytes_written,
+    OUT nfs41_write_verf *verf,
+    OUT nfs41_file_info *cinfo);
+
+int nfs41_read(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN uint64_t offset,
+    IN uint32_t count,
+    OUT unsigned char *data_out,
+    OUT uint32_t *data_len_out,
+    OUT bool_t *eof_out);
+
+int nfs41_commit(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint64_t offset,
+    IN uint32_t count,
+    IN bool_t do_getattr,
+    OUT nfs41_write_verf *verf,
+    OUT nfs41_file_info *cinfo);
+
+int nfs41_lock(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN state_owner4 *owner,
+    IN uint32_t type,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN bool_t reclaim,
+    IN bool_t try_recovery,
+    IN OUT stateid_arg *stateid);
+
+int nfs41_unlock(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN OUT stateid_arg *stateid);
+
+stateid4* nfs41_lock_stateid_copy(
+    IN nfs41_lock_state *lock_state,
+    IN OUT stateid4 *dest);
+
+int nfs41_readdir(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN bitmap4 *attr_request,
+    IN nfs41_readdir_cookie *cookie,
+    OUT unsigned char *entries,
+    IN OUT uint32_t *entries_len,
+    OUT bool_t *eof_out);
+
+int nfs41_getattr(
+    IN nfs41_session *session,
+    IN OPTIONAL nfs41_path_fh *file,
+    IN bitmap4 *attr_request,
+    OUT nfs41_file_info *info);
+
+int nfs41_superblock_getattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN bitmap4 *attr_request,
+    OUT nfs41_file_info *info,
+    OUT bool_t *supports_named_attrs);
+
+/* getattr.c */
+int nfs41_cached_getattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    OUT nfs41_file_info *info);
+
+int nfs41_remove(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN const nfs41_component *target,
+    IN uint64_t fileid);
+
+int nfs41_rename(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *src_dir,
+    IN const nfs41_component *src_name,
+    IN nfs41_path_fh *dst_dir,
+    IN const nfs41_component *dst_name);
+
+int nfs41_setattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN nfs41_file_info *info);
+
+int nfs41_link(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *src,
+    IN nfs41_path_fh *dst_dir,
+    IN const nfs41_component *target,
+    OUT nfs41_file_info *cinfo);
+
+/* symlink.c */
+int nfs41_symlink_target(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    OUT nfs41_abs_path *target);
+
+int nfs41_symlink_follow(
+    IN nfs41_root *root,
+    IN nfs41_session *session,
+    IN nfs41_path_fh *symlink,
+    OUT nfs41_file_info *info);
+
+int nfs41_readlink(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint32_t max_len,
+    OUT char *link_out,
+    OUT uint32_t *len_out);
+
+int nfs41_access(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN uint32_t requested,
+    OUT uint32_t *supported OPTIONAL,
+    OUT uint32_t *access OPTIONAL);
+
+enum nfsstat4 nfs41_want_delegation(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN deleg_claim4 *claim,
+    IN uint32_t want,
+    IN bool_t try_recovery,
+    OUT open_delegation4 *delegation);
+
+int nfs41_delegpurge(
+    IN nfs41_session *session);
+
+int nfs41_delegreturn(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN bool_t try_recovery);
+
+enum nfsstat4 nfs41_fs_locations(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN const nfs41_component *name,
+    OUT fs_locations4 *locations);
+
+int nfs41_secinfo(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN const nfs41_component *name,
+    OUT nfs41_secinfo_info *secinfo);
+
+int nfs41_secinfo_noname(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    OUT nfs41_secinfo_info *secinfo);
+
+enum nfsstat4 nfs41_free_stateid(
+    IN nfs41_session *session,
+    IN stateid4 *stateid);
+
+enum nfsstat4 nfs41_test_stateid(
+    IN nfs41_session *session,
+    IN stateid_arg *stateid_array,
+    IN uint32_t count,
+    OUT uint32_t *status_array);
+
+enum nfsstat4 pnfs_rpc_layoutget(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid_arg *stateid,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t minlength,
+    IN uint64_t length,
+    OUT pnfs_layoutget_res_ok *layoutget_res);
+
+enum nfsstat4 pnfs_rpc_layoutcommit(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN stateid4 *stateid,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN OPTIONAL uint64_t *new_last_offset,
+    IN OPTIONAL nfstime4 *new_time_modify,
+    OUT nfs41_file_info *info);
+
+enum nfsstat4 pnfs_rpc_layoutreturn(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN enum pnfs_layout_type type,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN stateid4 *stateid,
+    OUT pnfs_layoutreturn_res *layoutreturn_res);
+
+enum nfsstat4 pnfs_rpc_getdeviceinfo(
+    IN nfs41_session *session,
+    IN unsigned char *deviceid,
+    OUT pnfs_file_device *device);
+
+enum nfsstat4 nfs41_rpc_openattr(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN bool_t createdir,
+    OUT nfs41_fh *fh_out);
+
+#endif /* !__NFS41_NFS_OPS_H__ */
diff --git a/reactos/base/services/nfsd/nfs41_rpc.c b/reactos/base/services/nfsd/nfs41_rpc.c
new file mode 100644 (file)
index 0000000..a82628c
--- /dev/null
@@ -0,0 +1,409 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 "nfs41_ops.h"
+#include "daemon_debug.h"
+#include "nfs41_xdr.h"
+#include "nfs41_callback.h"
+#include "nfs41_driver.h" /* for AUTH_SYS, AUTHGSS_KRB5s defines */
+
+#include "rpc/rpc.h"
+#define SECURITY_WIN32
+#include <security.h>
+#include "rpc/auth_sspi.h"
+
+static enum clnt_stat send_null(CLIENT *client)
+{
+    struct timeval timeout = {10, 100};
+
+    return clnt_call(client, 0,
+                     (xdrproc_t)xdr_void, NULL,
+                     (xdrproc_t)xdr_void, NULL, timeout);
+}
+
+static int get_client_for_netaddr(
+    IN const netaddr4 *netaddr,
+    IN uint32_t wsize,
+    IN uint32_t rsize,
+    IN nfs41_rpc_clnt *rpc,
+    OUT OPTIONAL char *server_name,
+    OUT CLIENT **client_out)
+{
+    int status = ERROR_NETWORK_UNREACHABLE;
+    struct netconfig *nconf;
+    struct netbuf *addr;
+    CLIENT *client;
+
+    nconf = getnetconfigent(netaddr->netid);
+    if (nconf == NULL)
+        goto out;
+
+    addr = uaddr2taddr(nconf, netaddr->uaddr);
+    if (addr == NULL)
+        goto out_free_conf;
+
+    if (server_name) {
+        getnameinfo(addr->buf, addr->len, server_name, NI_MAXHOST, NULL, 0, 0);
+        dprintf(1, "servername is %s\n", server_name);
+    }
+    dprintf(1, "callback function %p args %p\n", nfs41_handle_callback, rpc);
+    client = clnt_tli_create(RPC_ANYFD, nconf, addr, NFS41_RPC_PROGRAM, 
+        NFS41_RPC_VERSION, wsize, rsize, rpc ? proc_cb_compound_res : NULL, 
+        rpc ? nfs41_handle_callback : NULL, rpc ? rpc : NULL);
+    if (client) {
+        *client_out = client;
+        status = NO_ERROR;
+    }
+
+    freenetbuf(addr);
+out_free_conf:
+    freenetconfigent(nconf);
+out:
+    return status;
+}
+
+static int get_client_for_multi_addr(
+    IN const multi_addr4 *addrs,
+    IN uint32_t wsize,
+    IN uint32_t rsize,
+    IN nfs41_rpc_clnt *rpc,
+    OUT OPTIONAL char *server_name,
+    OUT CLIENT **client_out,
+    OUT uint32_t *addr_index)
+{
+    int status = ERROR_NETWORK_UNREACHABLE;
+    uint32_t i;
+    for (i = 0; i < addrs->count; i++) {
+        status = get_client_for_netaddr(&addrs->arr[i],
+            wsize, rsize, rpc, server_name, client_out);
+        if (status == NO_ERROR) {
+            *addr_index = i;
+            break;
+        }
+    }
+    return status;
+}
+
+int create_rpcsec_auth_client(
+    IN uint32_t sec_flavor,
+    IN char *server_name,
+    CLIENT     *client
+    )
+{
+    int status = ERROR_NETWORK_UNREACHABLE;
+
+    switch (sec_flavor) {
+    case RPCSEC_AUTHGSS_KRB5:
+        client->cl_auth = authsspi_create_default(client, server_name, 
+            RPCSEC_SSPI_SVC_NONE);
+        break;
+    case RPCSEC_AUTHGSS_KRB5I:
+        client->cl_auth = authsspi_create_default(client, server_name, 
+            RPCSEC_SSPI_SVC_INTEGRITY);
+        break;
+    case RPCSEC_AUTHGSS_KRB5P:
+        client->cl_auth = authsspi_create_default(client, server_name, 
+            RPCSEC_SSPI_SVC_PRIVACY);
+        break;
+    default:
+        eprintf("create_rpc_auth_client: unknown rpcsec flavor %d\n", 
+                sec_flavor);
+        client->cl_auth = NULL;
+    }
+
+    if (client->cl_auth == NULL) {
+        eprintf("nfs41_rpc_clnt_create: failed to create %s\n", 
+                secflavorop2name(sec_flavor));
+        goto out;
+    } else 
+        dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n", 
+            secflavorop2name(sec_flavor));
+    status = 0;
+out:
+    return status;
+}
+
+/* Returns a client structure and an associated lock */
+int nfs41_rpc_clnt_create(
+    IN const multi_addr4 *addrs,
+    IN uint32_t wsize,
+    IN uint32_t rsize,
+    IN uint32_t uid,
+    IN uint32_t gid,
+    IN uint32_t sec_flavor,
+    OUT nfs41_rpc_clnt **rpc_out)
+{
+    CLIENT *client;
+    nfs41_rpc_clnt *rpc;
+    uint32_t addr_index;
+    int status;
+    char machname[MAXHOSTNAMELEN + 1];
+    gid_t gids[1];
+    bool_t needcb = 1;
+
+    rpc = calloc(1, sizeof(nfs41_rpc_clnt));
+    if (rpc == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+#ifdef NO_CB_4_KRB5P
+    if (sec_flavor == RPCSEC_AUTHGSS_KRB5P)
+        needcb = 0;
+#endif
+    rpc->needcb = needcb;
+    rpc->cond = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (rpc->cond == NULL) {
+        status = GetLastError();
+        eprintf("CreateEvent failed %d\n", status);
+        goto out_free_rpc_clnt;
+    }
+    status = get_client_for_multi_addr(addrs, wsize, rsize, needcb?rpc:NULL, 
+                rpc->server_name, &client, &addr_index);
+    if (status) {
+        clnt_pcreateerror("connecting failed");
+        goto out_free_rpc_cond;
+    }
+    if (send_null(client) != RPC_SUCCESS) {
+        // XXX Do what here?
+        eprintf("nfs41_rpc_clnt_create: send_null failed\n");
+        status = ERROR_NETWORK_UNREACHABLE;
+        goto out_err_client;
+    }
+
+    rpc->sec_flavor = sec_flavor;
+    if (sec_flavor == RPCSEC_AUTH_SYS) {
+        if (gethostname(machname, sizeof(machname)) == -1) {
+            eprintf("nfs41_rpc_clnt_create: gethostname failed\n");
+            goto out_err_client;
+        }
+        machname[sizeof(machname) - 1] = '\0';
+        client->cl_auth = authsys_create(machname, uid, gid, 0, gids);
+        if (client->cl_auth == NULL) {
+            eprintf("nfs41_rpc_clnt_create: failed to create rpc authsys\n");
+            status = ERROR_NETWORK_UNREACHABLE;
+            goto out_err_client;
+        }
+    } else {
+        status = create_rpcsec_auth_client(sec_flavor, rpc->server_name, client);
+        if (status) {
+            eprintf("nfs41_rpc_clnt_create: failed to establish security "
+                    "context with %s\n", rpc->server_name);
+            status = ERROR_NETWORK_UNREACHABLE;
+            goto out_err_client;
+        } else
+            dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n", 
+                secflavorop2name(sec_flavor));
+    }
+    rpc->rpc = client;
+
+    /* keep a copy of the address and buffer sizes for reconnect */
+    memcpy(&rpc->addrs, addrs, sizeof(multi_addr4));
+    /* save the index of the address we connected to */
+    rpc->addr_index = addr_index;
+    rpc->wsize = wsize;
+    rpc->rsize = rsize;
+    rpc->is_valid_session = TRUE;
+    rpc->uid = uid;
+    rpc->gid = gid;
+
+    //initialize rpc client lock
+    InitializeSRWLock(&rpc->lock);
+
+    *rpc_out = rpc;
+out:
+    return status;
+out_err_client:
+    clnt_destroy(client);
+out_free_rpc_cond:
+    CloseHandle(rpc->cond);
+out_free_rpc_clnt:
+    free(rpc);
+    goto out;
+}
+
+/* Frees resources allocated in clnt_create */
+void nfs41_rpc_clnt_free(
+    IN nfs41_rpc_clnt *rpc)
+{
+    auth_destroy(rpc->rpc->cl_auth);
+    clnt_destroy(rpc->rpc);
+    CloseHandle(rpc->cond);
+    free(rpc);
+}
+
+static bool_t rpc_renew_in_progress(nfs41_rpc_clnt *rpc, int *value)
+{
+    bool_t status = FALSE;
+    AcquireSRWLockExclusive(&rpc->lock);
+    if (value) {
+        dprintf(1, "nfs41_rpc_renew_in_progress: setting value %d\n", *value);
+        rpc->in_recovery = *value;
+        if (!rpc->in_recovery) 
+            SetEvent(rpc->cond);
+    } else {
+        status = rpc->in_recovery;
+        dprintf(1, "nfs41_rpc_renew_in_progress: returning value %d\n", status);
+    }
+    ReleaseSRWLockExclusive(&rpc->lock);
+    return status;
+}
+
+static bool_t rpc_should_retry(nfs41_rpc_clnt *rpc, uint32_t version)
+{
+    bool_t status = 0;
+    AcquireSRWLockExclusive(&rpc->lock);
+    if (rpc->version > version)
+        status = 1;
+    ReleaseSRWLockExclusive(&rpc->lock);
+    return status;
+}
+
+static int rpc_reconnect(
+    IN nfs41_rpc_clnt *rpc)
+{
+    CLIENT *client = NULL;
+    uint32_t addr_index;
+    int status;
+
+    AcquireSRWLockExclusive(&rpc->lock);
+
+    status = get_client_for_multi_addr(&rpc->addrs, rpc->wsize, rpc->rsize, 
+                rpc->needcb?rpc:NULL, NULL, &client, &addr_index);
+    if (status)
+        goto out_unlock;
+
+    if(rpc->sec_flavor == RPCSEC_AUTH_SYS)
+        client->cl_auth = rpc->rpc->cl_auth;
+    else {
+        auth_destroy(rpc->rpc->cl_auth);
+        status = create_rpcsec_auth_client(rpc->sec_flavor, rpc->server_name, client);
+        if (status) {
+            eprintf("Failed to reestablish security context\n");
+            status = ERROR_NETWORK_UNREACHABLE;
+            goto out_err_client;
+        }
+    }
+    if (send_null(client) != RPC_SUCCESS) {
+        eprintf("rpc_reconnect: send_null failed\n");
+        status = ERROR_NETWORK_UNREACHABLE;
+        goto out_err_client;
+    }
+
+    clnt_destroy(rpc->rpc);
+    rpc->rpc = client;
+    rpc->addr_index = addr_index;
+    rpc->version++;
+    dprintf(1, "nfs41_send_compound: reestablished RPC connection\n");
+
+out_unlock:
+    ReleaseSRWLockExclusive(&rpc->lock);
+
+    /* after releasing the rpc lock, send a BIND_CONN_TO_SESSION if
+     * we need to associate the connection with the backchannel */
+    if (status == NO_ERROR && rpc->needcb && 
+            rpc->client && rpc->client->session) {
+        status = nfs41_bind_conn_to_session(rpc,
+            rpc->client->session->session_id, CDFC4_BACK_OR_BOTH);
+        if (status)
+            eprintf("nfs41_bind_conn_to_session() failed with %s\n",
+                nfs_error_string(status));
+        status = NFS4_OK;
+    }
+    return status;
+
+out_err_client:
+    clnt_destroy(client);
+    goto out_unlock;
+}
+
+int nfs41_send_compound(
+    IN nfs41_rpc_clnt *rpc,
+    IN char *inbuf,
+    OUT char *outbuf)
+{
+    struct timeval timeout = {90, 100};
+    enum clnt_stat rpc_status;
+    int status, count = 0, one = 1, zero = 0;
+    uint32_t version;
+
+ try_again:
+    AcquireSRWLockShared(&rpc->lock);
+    version = rpc->version;
+    rpc_status = clnt_call(rpc->rpc, 1,
+                           (xdrproc_t)nfs_encode_compound, inbuf,
+                           (xdrproc_t)nfs_decode_compound, outbuf,
+                           timeout);
+    ReleaseSRWLockShared(&rpc->lock);
+
+    if (rpc_status != RPC_SUCCESS) {
+        eprintf("clnt_call returned rpc_status = %s\n", 
+            rpc_error_string(rpc_status));
+        switch(rpc_status) {
+        case RPC_CANTRECV:
+        case RPC_CANTSEND:
+        case RPC_TIMEDOUT:
+        case RPC_AUTHERROR:
+            if (++count > 3 || !rpc->is_valid_session) {
+                status = ERROR_NETWORK_UNREACHABLE;
+                break;
+            }
+            if (rpc_should_retry(rpc, version))
+                goto try_again;
+            while (rpc_renew_in_progress(rpc, NULL)) {
+                status = WaitForSingleObject(rpc->cond, INFINITE);
+                if (status != WAIT_OBJECT_0) {
+                    dprintf(1, "rpc_renew_in_progress: WaitForSingleObject failed\n");
+                    print_condwait_status(1, status);
+                    status = ERROR_LOCK_VIOLATION;
+                    goto out;
+                }
+                rpc_renew_in_progress(rpc, &zero);
+                goto try_again;
+            }
+            rpc_renew_in_progress(rpc, &one);
+            if (rpc_status == RPC_AUTHERROR && rpc->sec_flavor != RPCSEC_AUTH_SYS) {
+                AcquireSRWLockExclusive(&rpc->lock);
+                auth_destroy(rpc->rpc->cl_auth);
+                status = create_rpcsec_auth_client(rpc->sec_flavor, 
+                            rpc->server_name, rpc->rpc);
+                ReleaseSRWLockExclusive(&rpc->lock);
+                if (status) {
+                    eprintf("Failed to reestablish security context\n");
+                    status = ERROR_NETWORK_UNREACHABLE;
+                    goto out;
+                }
+            } else
+                if (rpc_reconnect(rpc))
+                    eprintf("rpc_reconnect: Failed to reconnect!\n");
+            rpc_renew_in_progress(rpc, &zero);
+            goto try_again;
+        default:
+            eprintf("UNHANDLED RPC_ERROR: %d\n", rpc_status);
+                       status = ERROR_NETWORK_UNREACHABLE;
+            goto out;
+        }
+        goto out;
+    }
+
+    status = 0;
+out:
+    return status;
+}
diff --git a/reactos/base/services/nfsd/nfs41_server.c b/reactos/base/services/nfsd/nfs41_server.c
new file mode 100644 (file)
index 0000000..844e440
--- /dev/null
@@ -0,0 +1,343 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+
+#include "wintirpc.h"
+#include "rpc/rpc.h"
+
+#include "name_cache.h"
+#include "daemon_debug.h"
+#include "nfs41.h"
+#include "util.h"
+
+
+#define SRVLVL 2 /* dprintf level for server logging */
+
+
+/* nfs41_server_list */
+struct server_list {
+    struct list_entry       head;
+    CRITICAL_SECTION        lock;
+};
+static struct server_list g_server_list;
+
+#define server_entry(pos) list_container(pos, nfs41_server, entry)
+
+
+void nfs41_server_list_init()
+{
+    list_init(&g_server_list.head);
+    InitializeCriticalSection(&g_server_list.lock);
+}
+
+/* http://tools.ietf.org/html/rfc5661#section-1.6
+ * 1.6. General Definitions: Server Owner:
+ * "When the client has two connections each to a peer with the same major
+ * identifier, the client assumes that both peers are the same server (the
+ * server namespace is the same via each connection)" */
+
+/* http://tools.ietf.org/html/rfc5661#section-2.10.4
+ * 2.10.4. Server Scope
+ * "When the server scope values are the same, server owner value may be
+ * validly compared.  In cases where the server scope values are different,
+ * server owner values are treated as different even if they contain all
+ * identical bytes." */
+
+/* given these definitions, we require that both the server_owner.major_id
+ * and server_scope are identical when matching instances of nfs41_server */
+
+struct server_info {
+    const char *scope;
+    const char *owner;
+};
+
+static int server_compare(
+    const struct list_entry *entry,
+    const void *value)
+{
+    const nfs41_server *server = server_entry(entry);
+    const struct server_info *info = (const struct server_info*)value;
+    const int diff = strncmp(server->scope, info->scope, NFS4_OPAQUE_LIMIT);
+    return diff ? diff : strncmp(server->owner, info->owner, NFS4_OPAQUE_LIMIT);
+}
+
+static int server_entry_find(
+    IN struct server_list *servers,
+    IN const struct server_info *info,
+    OUT struct list_entry **entry_out)
+{
+    *entry_out = list_search(&servers->head, info, server_compare);
+    return *entry_out ? NO_ERROR : ERROR_FILE_NOT_FOUND;
+}
+
+static int server_create(
+    IN const struct server_info *info,
+    OUT nfs41_server **server_out)
+{
+    int status = NO_ERROR;
+    nfs41_server *server;
+
+    server = calloc(1, sizeof(nfs41_server));
+    if (server == NULL) {
+        status = GetLastError();
+        eprintf("failed to allocate server %s\n", info->owner);
+        goto out;
+    }
+
+    StringCchCopyA(server->scope, NFS4_OPAQUE_LIMIT, info->scope);
+    StringCchCopyA(server->owner, NFS4_OPAQUE_LIMIT, info->owner);
+    InitializeSRWLock(&server->addrs.lock);
+    nfs41_superblock_list_init(&server->superblocks);
+
+    status = nfs41_name_cache_create(&server->name_cache);
+    if (status) {
+        eprintf("nfs41_name_cache_create() failed with %d\n", status);
+        goto out_free;
+    }
+out:
+    *server_out = server;
+    return status;
+
+out_free:
+    free(server);
+    server = NULL;
+    goto out;
+}
+
+static void server_free(
+    IN nfs41_server *server)
+{
+    dprintf(SRVLVL, "server_free(%s)\n", server->owner);
+    nfs41_superblock_list_free(&server->superblocks);
+    nfs41_name_cache_free(&server->name_cache);
+    free(server);
+}
+
+static __inline void server_ref_locked(
+    IN nfs41_server *server)
+{
+    server->ref_count++;
+    dprintf(SRVLVL, "nfs41_server_ref(%s) count %d\n",
+        server->owner, server->ref_count);
+}
+
+void nfs41_server_ref(
+    IN nfs41_server *server)
+{
+    EnterCriticalSection(&g_server_list.lock);
+
+    server_ref_locked(server);
+
+    LeaveCriticalSection(&g_server_list.lock);
+}
+
+void nfs41_server_deref(
+    IN nfs41_server *server)
+{
+    EnterCriticalSection(&g_server_list.lock);
+
+    server->ref_count--;
+    dprintf(SRVLVL, "nfs41_server_deref(%s) count %d\n",
+        server->owner, server->ref_count);
+    if (server->ref_count == 0) {
+        list_remove(&server->entry);
+        server_free(server);
+    }
+
+    LeaveCriticalSection(&g_server_list.lock);
+}
+
+static void server_addrs_add(
+    IN OUT struct server_addrs *addrs,
+    IN const netaddr4 *addr)
+{
+    /* we keep a list of addrs used to connect to each server. once it gets
+     * bigger than NFS41_ADDRS_PER_SERVER, overwrite the oldest addrs. use
+     * server_addrs.next_index to implement a circular array */
+
+    AcquireSRWLockExclusive(&addrs->lock);
+
+    if (multi_addr_find(&addrs->addrs, addr, NULL)) {
+        dprintf(SRVLVL, "server_addrs_add() found existing addr '%s'.\n",
+            addr->uaddr);
+    } else {
+        /* overwrite the address at 'next_index' */
+        StringCchCopyA(addrs->addrs.arr[addrs->next_index].netid,
+            NFS41_NETWORK_ID_LEN+1, addr->netid);
+        StringCchCopyA(addrs->addrs.arr[addrs->next_index].uaddr,
+            NFS41_UNIVERSAL_ADDR_LEN+1, addr->uaddr);
+
+        /* increment/wrap next_index */
+        addrs->next_index = (addrs->next_index + 1) % NFS41_ADDRS_PER_SERVER;
+        /* update addrs.count if necessary */
+        if (addrs->addrs.count < addrs->next_index)
+            addrs->addrs.count = addrs->next_index;
+
+        dprintf(SRVLVL, "server_addrs_add() added new addr '%s'.\n",
+            addr->uaddr);
+    }
+    ReleaseSRWLockExclusive(&addrs->lock);
+}
+
+void nfs41_server_addrs(
+    IN nfs41_server *server,
+    OUT multi_addr4 *addrs)
+{
+    struct server_addrs *saddrs = &server->addrs;
+    uint32_t i, j;
+
+    /* make a copy of the server's addrs, with most recent first */
+    AcquireSRWLockShared(&saddrs->lock);
+    j = saddrs->next_index;
+    for (i = 0; i < saddrs->addrs.count; i++) {
+        /* decrement/wrap j */
+        j = (NFS41_ADDRS_PER_SERVER + j - 1) % NFS41_ADDRS_PER_SERVER;
+        memcpy(&addrs->arr[i], &saddrs->addrs.arr[j], sizeof(netaddr4));
+    }
+    ReleaseSRWLockShared(&saddrs->lock);
+}
+
+int nfs41_server_find_or_create(
+    IN const char *server_owner_major_id,
+    IN const char *server_scope,
+    IN const netaddr4 *addr,
+    OUT nfs41_server **server_out)
+{
+    struct server_info info;
+    struct list_entry *entry;
+    nfs41_server *server;
+    int status;
+
+    info.owner = server_owner_major_id;
+    info.scope = server_scope;
+
+    dprintf(SRVLVL, "--> nfs41_server_find_or_create(%s)\n", info.owner);
+
+    EnterCriticalSection(&g_server_list.lock);
+
+    /* search for an existing server */
+    entry = list_search(&g_server_list.head, &info, server_compare);
+    if (entry == NULL) {
+        /* create a new server */
+        status = server_create(&info, &server);
+        if (status == NO_ERROR) {
+            /* add it to the list */
+            list_add_tail(&g_server_list.head, &server->entry);
+            *server_out = server;
+
+            dprintf(SRVLVL, "<-- nfs41_server_find_or_create() "
+                "returning new server %p\n", server);
+        } else {
+            dprintf(SRVLVL, "<-- nfs41_server_find_or_create() "
+                "returning %d\n", status);
+        }
+    } else {
+        server = server_entry(entry);
+        status = NO_ERROR;
+
+        dprintf(SRVLVL, "<-- nfs41_server_find_or_create() "
+            "returning existing server %p\n", server);
+    }
+
+    if (server) {
+        /* register the address used to connect */
+        server_addrs_add(&server->addrs, addr);
+
+        server_ref_locked(server);
+    }
+
+    *server_out = server;
+    LeaveCriticalSection(&g_server_list.lock);
+    return status;
+}
+
+int nfs41_server_resolve(
+    IN const char *hostname,
+    IN unsigned short port,
+    OUT multi_addr4 *addrs)
+{
+    int status = ERROR_BAD_NET_NAME;
+    char service[16];
+    struct addrinfo hints = { 0 }, *res, *info;
+    struct netconfig *nconf;
+    struct netbuf addr;
+    char *netid, *uaddr;
+
+    dprintf(SRVLVL, "--> nfs41_server_resolve(%s:%u)\n",
+        hostname, port);
+
+    addrs->count = 0;
+
+    StringCchPrintfA(service, 16, "%u", port);
+
+    /* request a list of tcp addrs for the given hostname,port */
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = IPPROTO_TCP;
+
+    if (getaddrinfo(hostname, service, &hints, &res) != 0)
+        goto out;
+
+    for (info = res; info != NULL; info = info->ai_next) {
+        /* find the appropriate entry in /etc/netconfig */
+        switch (info->ai_family) {
+        case AF_INET:  netid = "tcp";  break;
+        case AF_INET6: netid = "tcp6"; break;
+        default: continue;
+        }
+
+        nconf = getnetconfigent(netid);
+        if (nconf == NULL)
+            continue;
+
+        /* convert to a transport-independent universal address */
+        addr.buf = info->ai_addr;
+        addr.maxlen = addr.len = (unsigned int)info->ai_addrlen;
+
+        uaddr = taddr2uaddr(nconf, &addr);
+        freenetconfigent(nconf);
+
+        if (uaddr == NULL)
+            continue;
+
+        StringCchCopyA(addrs->arr[addrs->count].netid,
+            NFS41_NETWORK_ID_LEN+1, netid);
+        StringCchCopyA(addrs->arr[addrs->count].uaddr,
+            NFS41_UNIVERSAL_ADDR_LEN+1, uaddr);
+        freeuaddr(uaddr);
+
+        status = NO_ERROR;
+        if (++addrs->count >= NFS41_ADDRS_PER_SERVER)
+            break;
+    }
+    freeaddrinfo(res);
+out:
+    if (status)
+        dprintf(SRVLVL, "<-- nfs41_server_resolve(%s:%u) returning "
+            "error %d\n", hostname, port, status);
+    else
+        dprintf(SRVLVL, "<-- nfs41_server_resolve(%s:%u) returning "
+            "%s\n", hostname, port, addrs->arr[0].uaddr);
+    return status;
+}
diff --git a/reactos/base/services/nfsd/nfs41_session.c b/reactos/base/services/nfsd/nfs41_session.c
new file mode 100644 (file)
index 0000000..8b2c3a8
--- /dev/null
@@ -0,0 +1,381 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <process.h>
+#include <stdio.h>
+
+#include "nfs41_ops.h"
+#include "nfs41_callback.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+/* after a CB_RECALL_SLOT or NFS4ERR_BADSLOT, wait a short time for the
+ * SEQUENCE.target_highest_slotid to catch up before updating max_slots again */
+#define MAX_SLOTS_DELAY 2000 /* in milliseconds */
+
+
+/* predicate for nfs41_slot_table.cond */
+static int slot_table_avail(
+    IN const nfs41_slot_table *table)
+{
+    return table->num_used < table->max_slots;
+}
+
+/* session slot mechanism */
+static void init_slot_table(nfs41_slot_table *table) 
+{
+    uint32_t i;
+    EnterCriticalSection(&table->lock);
+    table->max_slots = NFS41_MAX_NUM_SLOTS;
+    for (i = 0; i < NFS41_MAX_NUM_SLOTS; i++) {
+        table->seq_nums[i] = 1;
+        table->used_slots[i] = 0;
+    }
+    table->highest_used = table->num_used = 0;
+    table->target_delay = 0;
+
+    /* wake any threads waiting on a slot */
+    if (slot_table_avail(table))
+        WakeAllConditionVariable(&table->cond);
+    LeaveCriticalSection(&table->lock);
+}
+
+static void resize_slot_table(
+    IN nfs41_slot_table *table,
+    IN uint32_t target_highest_slotid)
+{
+    if (target_highest_slotid >= NFS41_MAX_NUM_SLOTS)
+        target_highest_slotid = NFS41_MAX_NUM_SLOTS - 1;
+
+    if (table->max_slots != target_highest_slotid + 1) {
+        dprintf(2, "updated max_slots %u to %u\n",
+            table->max_slots, target_highest_slotid + 1);
+        table->max_slots = target_highest_slotid + 1;
+
+        if (slot_table_avail(table))
+            WakeAllConditionVariable(&table->cond);
+    }
+}
+
+void nfs41_session_bump_seq(
+    IN nfs41_session *session,
+    IN uint32_t slotid,
+    IN uint32_t target_highest_slotid)
+{
+    nfs41_slot_table *table = &session->table;
+
+    AcquireSRWLockShared(&session->client->session_lock);
+    EnterCriticalSection(&table->lock);
+
+    if (slotid < NFS41_MAX_NUM_SLOTS)
+        table->seq_nums[slotid]++;
+
+    /* adjust max_slots in response to changes in target_highest_slotid,
+     * but not immediately after a CB_RECALL_SLOT or NFS4ERR_BADSLOT error */
+    if (table->target_delay <= GetTickCount64())
+        resize_slot_table(table, target_highest_slotid);
+
+    LeaveCriticalSection(&table->lock);
+    ReleaseSRWLockShared(&session->client->session_lock);
+}
+
+void nfs41_session_free_slot(
+    IN nfs41_session *session,
+    IN uint32_t slotid)
+{
+    nfs41_slot_table *table = &session->table;
+
+    AcquireSRWLockShared(&session->client->session_lock);
+    EnterCriticalSection(&table->lock);
+
+    /* flag the slot as unused */
+    if (slotid < NFS41_MAX_NUM_SLOTS && table->used_slots[slotid]) {
+        table->used_slots[slotid] = 0;
+        table->num_used--;
+    }
+    /* update highest_used if necessary */
+    if (slotid == table->highest_used) {
+        while (table->highest_used && !table->used_slots[table->highest_used])
+            table->highest_used--;
+    }
+    dprintf(3, "freeing slot#=%d used=%d highest=%d\n",
+        slotid, table->num_used, table->highest_used);
+
+    /* wake any threads waiting on a slot */
+    if (slot_table_avail(table))
+        WakeAllConditionVariable(&table->cond);
+
+    LeaveCriticalSection(&table->lock);
+    ReleaseSRWLockShared(&session->client->session_lock);
+}
+
+void nfs41_session_get_slot(
+    IN nfs41_session *session,
+    OUT uint32_t *slot,
+    OUT uint32_t *seqid,
+    OUT uint32_t *highest)
+{
+    nfs41_slot_table *table = &session->table;
+    uint32_t i;
+
+    AcquireSRWLockShared(&session->client->session_lock);
+    EnterCriticalSection(&table->lock);
+
+    /* wait for an available slot */
+    while (!slot_table_avail(table))
+        SleepConditionVariableCS(&table->cond, &table->lock, INFINITE);
+
+    for (i = 0; i < table->max_slots; i++) {
+        if (table->used_slots[i])
+            continue;
+
+        table->used_slots[i] = 1;
+        table->num_used++;
+        if (i > table->highest_used)
+            table->highest_used = i;
+
+        *slot = i;
+        *seqid = table->seq_nums[i];
+        *highest = table->highest_used;
+        break;
+    }
+    LeaveCriticalSection(&table->lock);
+    ReleaseSRWLockShared(&session->client->session_lock);
+
+    dprintf(2, "session %p: using slot#=%d with seq#=%d highest=%d\n",
+        session, *slot, *seqid, *highest);
+}
+
+int nfs41_session_recall_slot(
+    IN nfs41_session *session,
+    IN OUT uint32_t target_highest_slotid)
+{
+    nfs41_slot_table *table = &session->table;
+
+    AcquireSRWLockShared(&session->client->session_lock);
+    EnterCriticalSection(&table->lock);
+    resize_slot_table(table, target_highest_slotid);
+    table->target_delay = GetTickCount64() + MAX_SLOTS_DELAY;
+    LeaveCriticalSection(&table->lock);
+    ReleaseSRWLockShared(&session->client->session_lock);
+
+    return NFS4_OK;
+}
+
+int nfs41_session_bad_slot(
+    IN nfs41_session *session,
+    IN OUT nfs41_sequence_args *args)
+{
+    nfs41_slot_table *table = &session->table;
+    int status = NFS4ERR_BADSLOT;
+
+    if (args->sa_slotid == 0) {
+        eprintf("server bug detected: NFS4ERR_BADSLOT for slotid=0\n");
+        goto out;
+    }
+
+    /* avoid using any slots >= bad_slotid */
+    EnterCriticalSection(&table->lock);
+    if (table->max_slots > args->sa_slotid) {
+        resize_slot_table(table, args->sa_slotid);
+        table->target_delay = GetTickCount64() + MAX_SLOTS_DELAY;
+    }
+    LeaveCriticalSection(&table->lock);
+
+    /* get a new slot */
+    nfs41_session_free_slot(session, args->sa_slotid);
+    nfs41_session_get_slot(session, &args->sa_slotid,
+        &args->sa_sequenceid, &args->sa_highest_slotid);
+    status = NFS4_OK;
+out:
+    return status;
+}
+
+void nfs41_session_sequence(
+    nfs41_sequence_args *args,
+    nfs41_session *session,
+    bool_t cachethis)
+{
+    nfs41_session_get_slot(session, &args->sa_slotid, 
+        &args->sa_sequenceid, &args->sa_highest_slotid);
+    args->sa_sessionid = session->session_id;
+    args->sa_cachethis = cachethis;
+}
+
+
+/* session renewal */
+static unsigned int WINAPI renew_session(void *args) 
+{
+    int status = NO_ERROR;
+    nfs41_session *session = (nfs41_session *)args;
+    /* sleep for 2/3 of lease_time */
+    const uint32_t sleep_time = (2 * session->lease_time*1000)/3;
+
+    dprintf(1, "Creating renew_session thread: %p\n", session->renew_thread);
+    while(1) {
+        dprintf(1, "Going to sleep for %dmsecs\n", sleep_time);
+        Sleep(sleep_time);
+        status = nfs41_send_sequence(session);
+        if (status)
+            dprintf(1, "renewal thread: nfs41_send_sequence failed %d\n", status);
+    }
+    return status;
+}
+
+/* session creation */
+static int session_alloc(
+    IN nfs41_client *client,
+    OUT nfs41_session **session_out)
+{
+    nfs41_session *session;
+    int status = NO_ERROR;
+
+    session = calloc(1, sizeof(nfs41_session));
+    if (session == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+    session->client = client;
+    session->renew_thread = INVALID_HANDLE_VALUE;
+    session->isValidState = FALSE;
+
+    InitializeCriticalSection(&session->table.lock);
+    InitializeConditionVariable(&session->table.cond);
+
+    init_slot_table(&session->table);
+
+    //initialize session lock
+    InitializeSRWLock(&client->session_lock);
+
+    /* initialize the back channel */
+    nfs41_callback_session_init(session);
+
+    *session_out = session;
+out:
+    return status;
+}
+
+int nfs41_session_create(
+    IN nfs41_client *client,
+    IN nfs41_session **session_out)
+{
+    nfs41_session *session;
+    int status;
+
+    status = session_alloc(client, &session);
+    if (status) {
+        eprintf("session_alloc() failed with %d\n", status);
+        goto out;
+    }
+
+    AcquireSRWLockShared(&client->exid_lock);
+    if (client->rpc->needcb)
+        session->flags |= CREATE_SESSION4_FLAG_CONN_BACK_CHAN;
+    session->flags |= CREATE_SESSION4_FLAG_PERSIST;
+    ReleaseSRWLockShared(&client->exid_lock);
+
+    status = nfs41_create_session(client, session, TRUE);
+    if (status) {
+        eprintf("nfs41_create_session failed %d\n", status);
+        status = ERROR_BAD_NET_RESP;
+        goto out_err;
+    }
+
+    AcquireSRWLockExclusive(&session->client->session_lock);
+    client->session = session;
+    session->isValidState = TRUE;
+    ReleaseSRWLockExclusive(&session->client->session_lock);
+    *session_out = session;
+out:
+    return status;
+
+out_err:
+    nfs41_session_free(session);
+    goto out;
+}
+
+/* session renewal */
+int nfs41_session_renew(
+    IN nfs41_session *session)
+{
+    int status;
+
+    AcquireSRWLockExclusive(&session->client->session_lock);
+    session->cb_session.cb_seqnum = 0;
+    init_slot_table(&session->table);
+
+    status = nfs41_create_session(session->client, session, FALSE);
+    ReleaseSRWLockExclusive(&session->client->session_lock);
+    return status;
+}
+
+int nfs41_session_set_lease(
+    IN nfs41_session *session,
+    IN uint32_t lease_time)
+{
+    int status = NO_ERROR;
+    uint32_t thread_id;
+
+    if (valid_handle(session->renew_thread)) {
+        eprintf("nfs41_session_set_lease(): session "
+            "renewal thread already started!\n");
+        goto out;
+    }
+
+    if (lease_time == 0) {
+        eprintf("nfs41_session_set_lease(): invalid lease_time=0\n");
+        status = ERROR_INVALID_PARAMETER;
+        goto out;
+    }
+
+    session->lease_time = lease_time;
+    session->renew_thread = (HANDLE)_beginthreadex(NULL,
+        0, renew_session, session, 0, &thread_id);
+    if (!valid_handle(session->renew_thread)) {
+        status = GetLastError();
+        eprintf("_beginthreadex failed %d\n", status);
+        goto out;
+    }
+out:
+    return status;
+}
+
+void nfs41_session_free(
+    IN nfs41_session *session)
+{
+    AcquireSRWLockExclusive(&session->client->session_lock);
+    if (valid_handle(session->renew_thread)) {
+        dprintf(1, "nfs41_session_free: terminating session renewal thread\n");
+        if (!TerminateThread(session->renew_thread, NO_ERROR))
+            eprintf("failed to terminate renewal thread %p\n",
+                session->renew_thread);
+    }
+
+    if (session->isValidState) {
+        session->client->rpc->is_valid_session = FALSE;
+        nfs41_destroy_session(session);
+    }
+    DeleteCriticalSection(&session->table.lock);
+    ReleaseSRWLockExclusive(&session->client->session_lock);
+    free(session);
+}
diff --git a/reactos/base/services/nfsd/nfs41_superblock.c b/reactos/base/services/nfsd/nfs41_superblock.c
new file mode 100644 (file)
index 0000000..3d8eec0
--- /dev/null
@@ -0,0 +1,302 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+
+#include "daemon_debug.h"
+#include "nfs41.h"
+#include "nfs41_ops.h"
+#include "from_kernel.h"
+#include "util.h"
+
+
+#define SBLVL 3 /* dprintf level for superblock logging */
+
+
+static __inline int compare_fsid(
+    IN const nfs41_fsid *lhs,
+    IN const nfs41_fsid *rhs)
+{
+    if (lhs->major > rhs->major) return 1;
+    if (lhs->major < rhs->major) return -1;
+    if (lhs->minor > rhs->minor) return 1;
+    if (lhs->minor < rhs->minor) return -1;
+    return 0;
+}
+
+
+/* nfs41_superblock */
+static int superblock_create(
+    IN const nfs41_fsid *fsid,
+    OUT nfs41_superblock **superblock_out)
+{
+    int status = NO_ERROR;
+    nfs41_superblock *superblock;
+
+    dprintf(SBLVL, "creating superblock for fsid(%llu,%llu)\n",
+        fsid->major, fsid->minor);
+
+    superblock = calloc(1, sizeof(nfs41_superblock));
+    if (superblock == NULL) {
+        status = GetLastError();
+        eprintf("failed to allocate superblock "
+            "for fsid(%llu,%llu)\n", fsid->major, fsid->minor);
+        goto out;
+    }
+
+    memcpy(&superblock->fsid, fsid, sizeof(nfs41_fsid));
+    InitializeSRWLock(&superblock->lock);
+
+    *superblock_out = superblock;
+out:
+    return status;
+}
+
+static int get_superblock_attrs(
+    IN nfs41_session *session,
+    IN nfs41_superblock *superblock,
+    IN nfs41_path_fh *file)
+{
+    bool_t supports_named_attrs;
+    int status;
+    bitmap4 attr_request;
+    nfs41_file_info info = { 0 };
+
+    attr_request.arr[0] = FATTR4_WORD0_SUPPORTED_ATTRS |
+        FATTR4_WORD0_LINK_SUPPORT | FATTR4_WORD0_SYMLINK_SUPPORT |
+        FATTR4_WORD0_ACLSUPPORT | FATTR4_WORD0_CANSETTIME |
+        FATTR4_WORD0_CASE_INSENSITIVE | FATTR4_WORD0_CASE_PRESERVING |
+        FATTR4_WORD0_MAXREAD | (uint32_t)(FATTR4_WORD0_MAXWRITE);
+    attr_request.arr[1] = FATTR4_WORD1_FS_LAYOUT_TYPE |
+        FATTR4_WORD1_TIME_DELTA;
+    attr_request.arr[2] = FATTR4_WORD2_SUPPATTR_EXCLCREAT;
+    attr_request.count = 3;
+
+    info.supported_attrs = &superblock->supported_attrs;
+    info.suppattr_exclcreat = &superblock->suppattr_exclcreat;
+    info.time_delta = &superblock->time_delta;
+
+    status = nfs41_superblock_getattr(session, file,
+        &attr_request, &info, &supports_named_attrs);
+    if (status) {
+        eprintf("nfs41_superblock_getattr() failed with %s when fetching "
+            "attributes for fsid(%llu,%llu)\n", nfs_error_string(status),
+            superblock->fsid.major, superblock->fsid.minor);
+        goto out;
+    }
+
+    if (info.maxread)
+        superblock->maxread = info.maxread;
+    else
+        superblock->maxread = session->fore_chan_attrs.ca_maxresponsesize;
+
+    if (info.maxwrite)
+        superblock->maxwrite = info.maxwrite;
+    else
+        superblock->maxwrite = session->fore_chan_attrs.ca_maxrequestsize;
+
+    superblock->layout_types = info.fs_layout_types;
+    superblock->aclsupport = info.aclsupport;
+    superblock->link_support = info.link_support;
+    superblock->symlink_support = info.symlink_support;
+    superblock->ea_support = supports_named_attrs;
+    superblock->case_preserving = info.case_preserving;
+    superblock->case_insensitive = info.case_insensitive;
+
+    if (bitmap_isset(&info.attrmask, 0, FATTR4_WORD0_CANSETTIME))
+        superblock->cansettime = info.cansettime;
+    else /* cansettime is not supported, try setting them anyway */
+        superblock->cansettime = 1;
+
+    /* if time_delta is not supported, default to 1s */
+    if (!bitmap_isset(&info.attrmask, 1, FATTR4_WORD1_TIME_DELTA))
+        superblock->time_delta.seconds = 1;
+
+    /* initialize the default getattr mask */
+    superblock->default_getattr.count = 2;
+    superblock->default_getattr.arr[0] = FATTR4_WORD0_TYPE
+        | FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE
+        | FATTR4_WORD0_FILEID | FATTR4_WORD0_HIDDEN
+        | FATTR4_WORD0_ARCHIVE;
+    superblock->default_getattr.arr[1] = FATTR4_WORD1_MODE
+        | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_SYSTEM
+        | FATTR4_WORD1_TIME_ACCESS | FATTR4_WORD1_TIME_CREATE
+        | FATTR4_WORD1_TIME_MODIFY;
+    superblock->default_getattr.arr[2] = 0;
+
+    nfs41_superblock_supported_attrs(superblock, &superblock->default_getattr);
+
+    dprintf(SBLVL, "attributes for fsid(%llu,%llu): "
+        "maxread=%llu, maxwrite=%llu, layout_types: 0x%X, "
+        "cansettime=%u, time_delta={%llu,%u}, aclsupport=%u, "
+        "link_support=%u, symlink_support=%u, case_preserving=%u, "
+        "case_insensitive=%u\n",
+        superblock->fsid.major, superblock->fsid.minor,
+        superblock->maxread, superblock->maxwrite,
+        superblock->layout_types, superblock->cansettime,
+        superblock->time_delta.seconds, superblock->time_delta.nseconds,
+        superblock->aclsupport, superblock->link_support,
+        superblock->symlink_support, superblock->case_preserving,
+        superblock->case_insensitive);
+out:
+    return status;
+}
+
+void nfs41_superblock_fs_attributes(
+    IN const nfs41_superblock *superblock,
+    OUT PFILE_FS_ATTRIBUTE_INFORMATION FsAttrs)
+{
+    FsAttrs->FileSystemAttributes = FILE_SUPPORTS_REMOTE_STORAGE;
+    if (superblock->link_support)
+        FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_HARD_LINKS;
+    if (superblock->symlink_support)
+        FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_REPARSE_POINTS;
+    if (superblock->ea_support)
+        FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES;
+    if (superblock->case_preserving)
+        FsAttrs->FileSystemAttributes |= FILE_CASE_PRESERVED_NAMES;
+    if (!superblock->case_insensitive)
+        FsAttrs->FileSystemAttributes |= FILE_CASE_SENSITIVE_SEARCH;
+    if (superblock->aclsupport)
+        FsAttrs->FileSystemAttributes |= FILE_PERSISTENT_ACLS;
+
+    FsAttrs->MaximumComponentNameLength = NFS41_MAX_COMPONENT_LEN;
+
+    /* let the driver fill in FileSystemName */
+    FsAttrs->FileSystemNameLength = 0;
+
+    dprintf(SBLVL, "FileFsAttributeInformation: case_preserving %u, "
+        "case_insensitive %u, max component %u\n",
+        superblock->case_preserving, superblock->case_insensitive,
+        FsAttrs->MaximumComponentNameLength);
+}
+
+
+/* nfs41_superblock_list */
+#define superblock_entry(pos) list_container(pos, nfs41_superblock, entry)
+
+static int superblock_compare(
+    const struct list_entry *entry,
+    const void *value)
+{
+    const nfs41_superblock *superblock = superblock_entry(entry);
+    return compare_fsid(&superblock->fsid, (const nfs41_fsid*)value);
+}
+
+static nfs41_superblock* find_superblock(
+    IN nfs41_superblock_list *superblocks,
+    IN const nfs41_fsid *fsid)
+{
+    struct list_entry *entry;
+    entry = list_search(&superblocks->head, fsid, superblock_compare);
+    return entry ? superblock_entry(entry) : NULL;
+}
+
+void nfs41_superblock_list_init(
+    IN nfs41_superblock_list *superblocks)
+{
+    list_init(&superblocks->head);
+    InitializeSRWLock(&superblocks->lock);
+}
+
+void nfs41_superblock_list_free(
+    IN nfs41_superblock_list *superblocks)
+{
+    struct list_entry *entry, *tmp;
+
+    dprintf(SBLVL, "nfs41_superblock_list_free()\n");
+
+    list_for_each_tmp(entry, tmp, &superblocks->head)
+        free(superblock_entry(entry));
+}
+
+
+int nfs41_superblock_for_fh(
+    IN nfs41_session *session,
+    IN const nfs41_fsid *fsid,
+    IN const nfs41_fh *parent OPTIONAL,
+    OUT nfs41_path_fh *file)
+{
+    int status = NFS4_OK;
+    nfs41_server *server = client_server(session->client);
+    nfs41_superblock_list *superblocks = &server->superblocks;
+    nfs41_superblock *superblock;
+
+    dprintf(SBLVL, "--> nfs41_superblock_for_fh(fsid(%llu,%llu))\n",
+        fsid->major, fsid->minor);
+
+    /* compare with the parent's fsid, and use that if it matches */
+    if (parent && parent->superblock &&
+            compare_fsid(fsid, &parent->superblock->fsid) == 0) {
+        file->fh.superblock = parent->superblock;
+        dprintf(SBLVL, "using superblock from parent\n");
+        goto out;
+    }
+
+    /* using a shared lock, search for an existing superblock */
+    AcquireSRWLockShared(&superblocks->lock);
+    superblock = find_superblock(superblocks, fsid);
+    ReleaseSRWLockShared(&superblocks->lock);
+
+    if (superblock) {
+        dprintf(SBLVL, "found existing superblock in server list "
+            "[shared lock]\n");
+    } else {
+        AcquireSRWLockExclusive(&superblocks->lock);
+        /* must search again under an exclusive lock, in case another thread
+         * created it after our first search */
+        superblock = find_superblock(superblocks, fsid);
+        if (superblock) {
+            dprintf(SBLVL, "found newly created superblock in server list "
+                "[exclusive lock]\n");
+        } else {
+            /* create the superblock */
+            status = superblock_create(fsid, &superblock);
+            if (status == NO_ERROR) /* add it to the list */
+                list_add_tail(&superblocks->head, &superblock->entry);
+        }
+        ReleaseSRWLockExclusive(&superblocks->lock);
+    }
+
+    if (status == NO_ERROR && superblock->supported_attrs.count == 0) {
+        /* exclusive lock on the superblock while fetching attributes */
+        AcquireSRWLockExclusive(&superblock->lock);
+        if (superblock->supported_attrs.count == 0)
+            status = get_superblock_attrs(session, superblock, file);
+        ReleaseSRWLockExclusive(&superblock->lock);
+    }
+
+    file->fh.superblock = superblock;
+out:
+    dprintf(SBLVL, "<-- nfs41_superblock_for_fh() returning %p, status %d\n",
+        file->fh.superblock, status);
+    return status;
+}
+
+void nfs41_superblock_space_changed(
+    IN nfs41_superblock *superblock)
+{
+    /* invalidate cached volume size attributes */
+    AcquireSRWLockExclusive(&superblock->lock);
+    superblock->cache_expiration = 0;
+    ReleaseSRWLockExclusive(&superblock->lock);
+}
diff --git a/reactos/base/services/nfsd/nfs41_types.h b/reactos/base/services/nfsd/nfs41_types.h
new file mode 100644 (file)
index 0000000..1b9c099
--- /dev/null
@@ -0,0 +1,249 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_TYPES_H__
+#define __NFS41_DAEMON_TYPES_H__
+
+#include "wintirpc.h"
+#include "rpc/xdr.h"
+#include "nfs41_const.h"
+
+typedef char*       caddr_t;
+
+static const int64_t    NFS4_INT64_MAX      = 0x7fffffffffffffff;
+static const uint64_t   NFS4_UINT64_MAX     = 0xffffffffffffffff;
+static const int32_t    NFS4_INT32_MAX      = 0x7fffffff;
+static const uint32_t   NFS4_UINT32_MAX     = 0xffffffff;
+
+static const uint64_t   NFS4_MAXFILELEN     = 0xffffffffffffffff;
+static const uint64_t   NFS4_MAXFILEOFF     = 0xfffffffffffffffe;
+
+
+/* common nfs types */
+typedef struct __nfs41_abs_path {
+    char            path[NFS41_MAX_PATH_LEN];
+    unsigned short  len;
+    SRWLOCK         lock;
+} nfs41_abs_path;
+
+typedef struct __nfs41_component {
+    const char      *name;
+    unsigned short  len;
+} nfs41_component;
+
+typedef struct __nfs41_fh {
+    unsigned char   fh[NFS4_FHSIZE];
+    uint32_t        len;
+    uint64_t        fileid;
+    struct __nfs41_superblock *superblock;
+} nfs41_fh;
+
+typedef struct __nfs41_path_fh {
+    nfs41_abs_path  *path;
+    nfs41_component name;
+    nfs41_fh        fh;
+} nfs41_path_fh;
+
+typedef struct __nfs41_fsid {
+    uint64_t        major;
+    uint64_t        minor;
+} nfs41_fsid;
+
+typedef struct __nfs41_readdir_cookie {
+    uint64_t        cookie;
+    unsigned char   verf[NFS4_VERIFIER_SIZE];
+} nfs41_readdir_cookie;
+
+typedef struct __nfs41_write_verf {
+    unsigned char   verf[NFS4_VERIFIER_SIZE];
+    unsigned char   expected[NFS4_VERIFIER_SIZE];
+#ifdef __REACTOS__
+    uint32_t committed;
+#else
+    enum stable_how4 committed;
+#endif
+} nfs41_write_verf;
+
+typedef struct __netaddr4 {
+    char            netid[NFS41_NETWORK_ID_LEN+1];
+    char            uaddr[NFS41_UNIVERSAL_ADDR_LEN+1];
+} netaddr4;
+
+typedef struct __multi_addr4 {
+    netaddr4        arr[NFS41_ADDRS_PER_SERVER];
+    uint32_t        count;
+} multi_addr4;
+
+typedef struct __bitmap4 {
+    uint32_t        count;
+    uint32_t        arr[3];
+} bitmap4;
+
+typedef struct __nfstime4 {
+    int64_t         seconds;
+    uint32_t        nseconds;
+} nfstime4;
+
+typedef struct __client_owner4 {
+    unsigned char   co_verifier[NFS4_VERIFIER_SIZE];
+    uint32_t        co_ownerid_len;
+    unsigned char   co_ownerid[NFS4_OPAQUE_LIMIT];
+} client_owner4;
+
+typedef struct __server_owner4 {
+    uint64_t        so_minor_id;
+    uint32_t        so_major_id_len;
+    char            so_major_id[NFS4_OPAQUE_LIMIT];
+} server_owner4;
+
+typedef struct __state_owner4 {
+    uint32_t        owner_len;
+    unsigned char   owner[NFS4_OPAQUE_LIMIT];
+} state_owner4;
+
+typedef struct __nfs_impl_id4 {
+    uint32_t        nii_domain_len;
+    unsigned char   *nii_domain;
+    uint32_t        nii_name_len;
+    unsigned char   *nii_name;
+    nfstime4        nii_date;
+} nfs_impl_id4;
+
+typedef struct __nfsace4 {
+    uint32_t        acetype;
+    uint32_t        aceflag;
+    uint32_t        acemask;
+    char            who[NFS4_OPAQUE_LIMIT];
+} nfsace4;
+
+typedef struct __nfsacl41 {
+    uint32_t        flag;
+    nfsace4         *aces;
+    uint32_t        count;
+} nfsacl41;
+
+typedef struct __stateid4 {
+    uint32_t        seqid;
+    unsigned char   other[NFS4_STATEID_OTHER];
+} stateid4;
+
+typedef struct __open_delegation4 {
+    stateid4 stateid;
+    nfsace4 permissions;
+#ifdef __REACTOS__
+    uint32_t type;
+#else
+    enum open_delegation_type4 type;
+#endif
+    bool_t recalled;
+} open_delegation4;
+
+typedef struct __fattr4 {
+    bitmap4         attrmask;
+    uint32_t        attr_vals_len;
+    unsigned char   attr_vals[NFS4_OPAQUE_LIMIT];
+} fattr4;
+
+typedef struct __change_info4 {
+    bool_t          atomic;
+    uint64_t        before;
+    uint64_t        after;
+} change_info4;
+
+typedef struct __fs_location_server {
+    /* 'address' represents one of a traditional DNS host name,
+     * IPv4 address, IPv6 address, or a zero-length string */
+    char            address[NFS41_HOSTNAME_LEN+1];
+} fs_location_server;
+
+typedef struct __fs_location4 {
+    nfs41_abs_path  path; /* path to fs from referred server's root */
+    fs_location_server *servers;
+    uint32_t        server_count;
+} fs_location4;
+
+typedef struct __fs_locations4 {
+    nfs41_abs_path  path; /* path to fs from referring server's root */
+    fs_location4    *locations;
+    uint32_t        location_count;
+} fs_locations4;
+
+enum {
+    MDSTHRESH_READ = 0,
+    MDSTHRESH_WRITE,
+    MDSTHRESH_READ_IO,
+    MDSTHRESH_WRITE_IO,
+
+    MAX_MDSTHRESH_HINTS
+};
+typedef struct __threshold_item4 {
+    uint32_t        type;
+    uint64_t        hints[MAX_MDSTHRESH_HINTS];
+} threshold_item4;
+
+#define MAX_MDSTHRESHOLD_ITEMS 1
+typedef struct __mdsthreshold4 {
+    uint32_t        count;
+    threshold_item4 items[MAX_MDSTHRESHOLD_ITEMS];
+} mdsthreshold4;
+
+typedef struct __nfs41_file_info {
+    nfs41_fsid              fsid;
+    mdsthreshold4           mdsthreshold;
+    nfstime4                time_access;
+    nfstime4                time_create;
+    nfstime4                time_modify;
+    nfsacl41                *acl;
+    nfstime4                *time_delta; /* XXX: per-fs */
+    bitmap4                 attrmask;
+    bitmap4                 *supported_attrs; /* XXX: per-fs */
+    bitmap4                 *suppattr_exclcreat; /* XXX: per-fs */
+    uint64_t                maxread; /* XXX: per-fs */
+    uint64_t                maxwrite; /* XXX: per-fs */
+    uint64_t                change;
+    uint64_t                size;
+    uint64_t                fileid;
+    uint64_t                space_avail; /* XXX: per-fs */
+    uint64_t                space_free; /* XXX: per-fs */
+    uint64_t                space_total; /* XXX: per-fs */
+    uint32_t                type;
+    uint32_t                numlinks;
+    uint32_t                rdattr_error;
+    uint32_t                mode;
+    uint32_t                mode_mask;
+    fs_locations4           *fs_locations; /* XXX: per-fs */
+    uint32_t                lease_time; /* XXX: per-server */
+    uint32_t                fs_layout_types; /* pnfs, XXX: per-fs */
+    bool_t                  hidden;
+    bool_t                  system;
+    bool_t                  archive;
+    bool_t                  cansettime; /* XXX: per-fs */
+    bool_t                  case_insensitive;
+    bool_t                  case_preserving;
+    bool_t                  symlink_dir;
+    bool_t                  symlink_support;
+    bool_t                  link_support;
+    char                    *owner;
+    char                    *owner_group;
+    uint32_t                aclsupport;
+} nfs41_file_info;
+
+#endif /* !__NFS41_DAEMON_TYPES_H__ */
diff --git a/reactos/base/services/nfsd/nfs41_xdr.c b/reactos/base/services/nfsd/nfs41_xdr.c
new file mode 100644 (file)
index 0000000..04dc962
--- /dev/null
@@ -0,0 +1,3679 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+
+#include "nfs41_compound.h"
+#include "nfs41_ops.h"
+#include "nfs41_xdr.h"
+#include "util.h"
+#include "daemon_debug.h"
+#include "rpc/rpc.h"
+
+static bool_t encode_file_attrs(
+    fattr4 *attrs,
+    nfs41_file_info *info);
+
+static __inline int unexpected_op(uint32_t op, uint32_t expected)
+{
+    if (op == expected)
+        return 0;
+
+    eprintf("Op table mismatch. Got %s (%d), expected %s (%d).\n",
+        nfs_opnum_to_string(op), op,
+        nfs_opnum_to_string(expected), expected);
+    return 1;
+}
+
+/* typedef uint32_t bitmap4<> */
+bool_t xdr_bitmap4(
+    XDR *xdr,
+    bitmap4 *bitmap)
+{
+    uint32_t i;
+
+    if (xdr->x_op == XDR_ENCODE) {
+        if (bitmap->count > 3) {
+            eprintf("encode_bitmap4: count (%d) must be <= 3\n",
+                bitmap->count);
+            return FALSE;
+        }
+        if (!xdr_u_int32_t(xdr, &bitmap->count))
+            return FALSE;
+
+        for (i = 0; i < bitmap->count; i++)
+            if (!xdr_u_int32_t(xdr, &bitmap->arr[i]))
+                return FALSE;
+
+    } else if (xdr->x_op == XDR_DECODE) {
+        if (!xdr_u_int32_t(xdr, &bitmap->count))
+            return FALSE;
+        if (bitmap->count > 3) {
+            eprintf("decode_bitmap4: count (%d) must be <= 3\n",
+                bitmap->count);
+            return FALSE;
+        }
+
+        for (i = 0; i < bitmap->count; i++)
+            if (!xdr_u_int32_t(xdr, &bitmap->arr[i]))
+                return FALSE;
+    } else 
+        return FALSE;
+
+    return TRUE;
+}
+
+/* nfstime4 */
+static bool_t xdr_nfstime4(
+    XDR *xdr,
+    nfstime4 *nt)
+{
+    if (!xdr_hyper(xdr, &nt->seconds))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &nt->nseconds);
+}
+
+
+/* settime4 */
+static uint32_t settime_how(
+    nfstime4 *newtime,
+    const nfstime4 *time_delta)
+{
+    nfstime4 current;
+    get_nfs_time(&current);
+    /* get the absolute difference between current and newtime */
+    nfstime_diff(&current, newtime, &current);
+    nfstime_abs(&current, &current);
+    /* compare the difference with time_delta */
+    nfstime_diff(time_delta, &current, &current);
+    /* use client time if diff > delta (i.e. time_delta - current < 0) */
+    return current.seconds < 0 ? SET_TO_CLIENT_TIME4 : SET_TO_SERVER_TIME4;
+}
+
+static bool_t xdr_settime4(
+    XDR *xdr,
+    nfstime4 *nt,
+    const nfstime4 *time_delta)
+{
+    uint32_t how = settime_how(nt, time_delta);
+
+    if (xdr->x_op != XDR_ENCODE) /* not used for decode */
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &how))
+        return FALSE;
+
+    if (how == SET_TO_CLIENT_TIME4)
+        return xdr_nfstime4(xdr, nt);
+
+    return TRUE;
+}
+
+/* stateid4 */
+static bool_t xdr_stateid4(
+    XDR *xdr,
+    stateid4 *si)
+{
+    if (!xdr_u_int32_t(xdr, &si->seqid))
+        return FALSE;
+
+    return xdr_opaque(xdr, (char *)si->other, NFS4_STATEID_OTHER);
+}
+
+/* fattr4 */
+bool_t xdr_fattr4(
+    XDR *xdr,
+    fattr4 *fattr)
+{
+    unsigned char *attr_vals = fattr->attr_vals;
+
+    if (!xdr_bitmap4(xdr, &fattr->attrmask))
+        return FALSE;
+
+    return xdr_bytes(xdr, (char **)&attr_vals, &fattr->attr_vals_len, NFS4_OPAQUE_LIMIT);
+}
+
+/* nfs41_fh */
+static bool_t xdr_fh(
+    XDR *xdr,
+    nfs41_fh *fh)
+{
+    unsigned char *pfh = fh->fh;
+    return xdr_bytes(xdr, (char **)&pfh, &fh->len, NFS4_FHSIZE);
+}
+
+/* nfs41_fsid */
+static bool_t xdr_fsid(
+    XDR *xdr,
+    nfs41_fsid *fsid)
+{
+    if (!xdr_u_hyper(xdr, &fsid->major))
+        return FALSE;
+
+    return xdr_u_hyper(xdr, &fsid->minor);
+}
+
+
+/* nfs41_component */
+static bool_t encode_component(
+    XDR *xdr,
+    const nfs41_component *component)
+{
+    uint32_t len = component->len;
+    return xdr_bytes(xdr, (char **)&component->name, &len, NFS4_OPAQUE_LIMIT);
+}
+
+static bool_t decode_component(
+    XDR *xdr,
+    nfs41_component *component)
+{
+    bool_t result;
+    uint32_t len;
+
+    result = xdr_bytes(xdr, (char **)&component->name, &len, NFS4_OPAQUE_LIMIT);
+    component->len = (result == FALSE) ? 0 : (unsigned short)len;
+    return result;
+}
+
+
+/* state_owner4 */
+static bool_t xdr_state_owner4(
+    XDR *xdr,
+    state_owner4 *so)
+{
+    u_quad_t clientid = 0;
+    unsigned char *owner = so->owner;
+
+    /* 18.16.3. "The client can set the clientid field to any value and
+     * the server MUST ignore it.  Instead the server MUST derive the
+     * client ID from the session ID of the SEQUENCE operation of the
+     * COMPOUND request. */
+    if (xdr->x_op == XDR_ENCODE) {
+        if (!xdr_u_hyper(xdr, &clientid)) /* clientid = 0 */
+            return FALSE;
+    } else if (xdr->x_op == XDR_DECODE) {
+        if (!xdr_u_hyper(xdr, &clientid))
+            return FALSE;
+    } else return FALSE;
+
+    return xdr_bytes(xdr, (char **)&owner, &so->owner_len, NFS4_OPAQUE_LIMIT);
+}
+
+static bool_t xdr_layout_types(
+    XDR *xdr,
+    uint32_t *layout_type)
+{
+    u_int32_t i, count, type;
+
+    if (xdr->x_op != XDR_DECODE) {
+        eprintf("xdr_layout_types: xdr->x_op is not XDR_DECODE! "
+            "x_op %d not supported.\n", xdr->x_op);
+        return FALSE;
+    }
+
+    *layout_type = 0;
+
+    if (!xdr_u_int32_t(xdr, &count))
+        return FALSE;
+
+    for (i = 0; i < count; i++) {
+        if (!xdr_u_int32_t(xdr, &type))
+            return FALSE;
+
+        *layout_type |= 1 << (type - 1);
+    }
+    return TRUE;
+}
+
+static bool_t xdr_threshold_item(
+    XDR *xdr,
+    threshold_item4 *item)
+{
+    bitmap4 bitmap;
+
+    if (!xdr_u_int32_t(xdr, &item->type))
+        return FALSE;
+
+    if (!xdr_bitmap4(xdr, &bitmap))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &bitmap.count))
+        return FALSE;
+
+    if (bitmap.count) {
+        if (bitmap.arr[0] & 0x1 && !xdr_u_hyper(xdr, &item->hints[0]))
+            return FALSE;
+        if (bitmap.arr[0] & 0x2 && !xdr_u_hyper(xdr, &item->hints[1]))
+            return FALSE;
+        if (bitmap.arr[0] & 0x4 && !xdr_u_hyper(xdr, &item->hints[2]))
+            return FALSE;
+        if (bitmap.arr[0] & 0x8 && !xdr_u_hyper(xdr, &item->hints[3]))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static bool_t xdr_mdsthreshold(
+    XDR *xdr,
+    mdsthreshold4 *mdsthreshold)
+{
+    uint32_t i;
+
+    if (!xdr_u_int32_t(xdr, &mdsthreshold->count))
+        return FALSE;
+
+    if (mdsthreshold->count > MAX_MDSTHRESHOLD_ITEMS)
+        return FALSE;
+
+    for (i = 0; i < mdsthreshold->count; i++)
+        if (!xdr_threshold_item(xdr, &mdsthreshold->items[i]))
+            return FALSE;
+    return TRUE;
+}
+
+static bool_t xdr_nfsace4(
+    XDR *xdr,
+    nfsace4 *ace)
+{
+    char *who = ace->who;
+
+    if (!xdr_u_int32_t(xdr, &ace->acetype))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &ace->aceflag))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &ace->acemask))
+        return FALSE;
+
+    /* 'who' is a static array, so don't try to free it */
+    if (xdr->x_op == XDR_FREE)
+        return TRUE;
+
+    return xdr_string(xdr, &who, NFS4_OPAQUE_LIMIT);
+}
+
+static bool_t xdr_nfsdacl41(
+    XDR *xdr,
+    nfsacl41 *acl)
+{
+    if (!xdr_u_int32_t(xdr, &acl->flag))
+        return FALSE;
+
+    return xdr_array(xdr, (char**)&acl->aces, &acl->count,
+        32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4);
+}
+
+static bool_t xdr_nfsacl41(
+    XDR *xdr,
+    nfsacl41 *acl)
+{
+    return xdr_array(xdr, (char**)&acl->aces, &acl->count,
+        32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4);
+}
+
+void nfsacl41_free(nfsacl41 *acl)
+{
+    XDR xdr = { XDR_FREE };
+    xdr_nfsacl41(&xdr, acl);
+}
+
+/* pathname4
+ * decode a variable array of components into a nfs41_abs_path */
+static bool_t decode_pathname4(
+    XDR *xdr,
+    nfs41_abs_path *path)
+{
+    char *pos;
+    u_int32_t i, count, len, remaining;
+
+    /* decode the number of components */
+    if (!xdr_u_int32_t(xdr, &count))
+        return FALSE;
+
+    pos = (char *)path->path;
+    remaining = NFS41_MAX_PATH_LEN;
+
+    /* decode each component */
+    for (i = 0; i < count; i++) {
+        len = remaining;
+        if (!xdr_bytes(xdr, (char **)&pos, &len, NFS41_MAX_PATH_LEN))
+            return FALSE;
+        remaining -= len;
+        pos += len;
+
+        if (i < count-1) { /* add a \ between components */
+            if (remaining < 1)
+                return FALSE;
+            *pos++ = '\\';
+            remaining--;
+        }
+    }
+    path->len = (unsigned short)(NFS41_MAX_PATH_LEN - remaining);
+    return TRUE;
+}
+
+/* fs_location4 */
+static bool_t decode_fs_location4(
+    XDR *xdr,
+    fs_location4 *location)
+{
+    fs_location_server *arr;
+    char *address;
+    u_int32_t i, count, len;
+
+    /* decode the number of servers */
+    if (!xdr_u_int32_t(xdr, &count))
+        return FALSE;
+
+    /* allocate the fs_location_server array */
+    if (count == 0) {
+        free(location->servers);
+        arr = NULL;
+    } else if (count != location->server_count) {
+        arr = realloc(location->servers, count * sizeof(fs_location_server));
+        if (arr == NULL)
+            return FALSE;
+        ZeroMemory(arr, count * sizeof(fs_location_server));
+    } else {
+        arr = location->servers;
+    }
+
+    location->servers = arr;
+    location->server_count = count;
+
+    for (i = 0; i < count; i++) {
+        len = NFS41_HOSTNAME_LEN;
+        address = arr[i].address;
+        if (!xdr_bytes(xdr, &address, &len, NFS41_HOSTNAME_LEN)) {
+            free(arr);
+            return FALSE;
+        }
+        arr[i].address[len] = '\0';
+    }
+
+    return decode_pathname4(xdr, &location->path);
+}
+
+/* fs_locations4 */
+static bool_t decode_fs_locations4(
+    XDR *xdr,
+    fs_locations4 *locations)
+{
+    u_int32_t i, count;
+    fs_location4 *arr;
+
+    if (!decode_pathname4(xdr, &locations->path))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &count))
+        return FALSE;
+
+    /* allocate the fs_location array */
+    if (count == 0) {
+        free(locations->locations);
+        arr = NULL;
+    } else if (count != locations->location_count) {
+        arr = realloc(locations->locations, count * sizeof(fs_location4));
+        if (arr == NULL)
+            return FALSE;
+        ZeroMemory(arr, count * sizeof(fs_location4));
+    } else {
+        arr = locations->locations;
+    }
+
+    locations->locations = arr;
+    locations->location_count = count;
+
+    for (i = 0; i < count; i++) {
+        if (!decode_fs_location4(xdr, &arr[i])) {
+            free(arr);
+            return FALSE;
+        }
+    }
+    return TRUE;
+}
+
+/*
+ * OP_EXCHANGE_ID
+ */
+static bool_t xdr_client_owner4(
+    XDR *xdr,
+    client_owner4 *co)
+{
+    unsigned char *co_ownerid = co->co_ownerid;
+    if (!xdr_opaque(xdr, (char *)&co->co_verifier[0], NFS4_VERIFIER_SIZE))
+        return FALSE;
+
+    return xdr_bytes(xdr, (char **)&co_ownerid, &co->co_ownerid_len, NFS4_OPAQUE_LIMIT);
+}
+
+#if 0
+static bool_t encode_state_protect_ops4(
+    XDR *xdr,
+    state_protect_ops4 *spo)
+{
+    if (!xdr_bitmap4(xdr, &spo->spo_must_enforce))
+        return FALSE;
+
+    return xdr_bitmap4(xdr, &spo->spo_must_allow);
+}
+
+static bool_t encode_ssv_sp_parms4(
+    XDR *xdr,
+    ssv_sp_parms4 *spp)
+{
+    if (!encode_state_protect_ops4(xdr, &spp->ssp_ops))
+        return FALSE;
+
+    if (!xdr_bytes(xdr, &spp->ssp_hash_algs,
+        &spp->ssp_hash_algs_len, NFS4_OPAQUE_LIMIT))
+        return FALSE;
+
+    if (!xdr_bytes(xdr, &spp->ssp_encr_algs,
+        &spp->ssp_encr_algs_len, NFS4_OPAQUE_LIMIT))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &spp->ssp_window))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &spp->ssp_num_gss_handles);
+}
+#endif
+
+static bool_t xdr_state_protect4_a(
+    XDR *xdr,
+    state_protect4_a *spa)
+{
+    bool_t result = TRUE;
+
+    if (!xdr_u_int32_t(xdr, (u_int32_t *)&spa->spa_how))
+        return FALSE;
+
+    switch (spa->spa_how)
+    {
+    case SP4_NONE:
+        break;
+#if 0
+    case SP4_MACH_CRED:
+        result = xdr_state_protect_ops4(xdr, &spa->u.spa_mach_ops);
+        break;
+    case SP4_SSV:
+        result = xdr_ssv_sp_parms4(xdr, &spa->u.spa_ssv_parms);
+        break;
+#endif
+    default:
+        eprintf("encode_state_protect4_a: state protect "
+            "type %d not supported.\n", spa->spa_how);
+        result = FALSE;
+        break;
+    }
+    return result;
+}
+
+static bool_t xdr_nfs_impl_id4(
+    XDR *xdr,
+    nfs_impl_id4 *nii)
+{
+    unsigned char *nii_domain = nii->nii_domain;
+    unsigned char *nii_name = nii->nii_name;
+
+    if (!xdr_bytes(xdr, (char **)&nii_domain, &nii->nii_domain_len, NFS4_OPAQUE_LIMIT))
+        return FALSE;
+
+    if (!xdr_bytes(xdr, (char **)&nii_name, &nii->nii_name_len, NFS4_OPAQUE_LIMIT))
+        return FALSE;
+
+    return xdr_nfstime4(xdr, &nii->nii_date);
+}
+
+
+static bool_t encode_op_exchange_id(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    uint32_t zero = 0;
+    uint32_t one = 1;
+
+    nfs41_exchange_id_args *args = (nfs41_exchange_id_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_EXCHANGE_ID))
+        return FALSE;
+
+    if (!xdr_client_owner4(xdr, args->eia_clientowner))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->eia_flags))
+        return FALSE;
+
+    if (!xdr_state_protect4_a(xdr, &args->eia_state_protect))
+        return FALSE;
+
+    if (args->eia_client_impl_id)
+    {
+        if (!xdr_u_int32_t(xdr, &one))
+            return FALSE;
+        return xdr_nfs_impl_id4(xdr, args->eia_client_impl_id);
+    }
+    else
+        return xdr_u_int32_t(xdr, &zero);
+}
+
+#if 0
+
+static bool_t decode_state_protect_ops4(
+    XDR *xdr,
+    state_protect_ops4 *spo)
+{
+    if (!xdr_bitmap4(xdr, &spo->spo_must_enforce))
+        return FALSE;
+
+    return xdr_bitmap4(xdr, &spo->spo_must_allow);
+}
+
+static bool_t decode_ssv_prot_info4(
+    XDR *xdr,
+    ssv_prot_info4 *spi)
+{
+/*  uint32_t i; */
+
+    if (!decode_state_protect_ops4(xdr, &spi->spi_ops))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &spi->spi_hash_alg))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &spi->spi_encr_alg))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &spi->spi_ssv_len))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &spi->spi_window))
+        return FALSE;
+
+/* TODO: spi->spi_handles */
+    return xdr_u_int32_t(xdr, 0);
+    /*
+    if (!xdr_u_int32_t(xdr, &spi->spi_handles.count))
+        return FALSE;
+
+    for (i = 0; i < spi->spi_handles.count; i++)
+        if (!xdr_opaque(xdr, &spi->spi_handles.arr[i])
+            return FALSE;
+*/
+    return TRUE;
+}
+#endif
+
+static bool_t xdr_state_protect4_r(
+    XDR *xdr,
+    state_protect4_r *spr)
+{
+    bool_t result = TRUE;
+
+    if (!xdr_u_int32_t(xdr, (uint32_t *)&spr->spr_how))
+        return FALSE;
+
+    switch (spr->spr_how)
+    {
+    case SP4_NONE:
+        break;
+#if 0
+    case SP4_MACH_CRED:
+        result = decode_state_protect_ops4(xdr, &spr->u.spr_mach_ops);
+        break;
+    case SP4_SSV:
+        result = decode_ssv_prot_info4(xdr, &spr->u.spr_ssv_info);
+        break;
+#endif
+    default:
+        eprintf("decode_state_protect4_r: state protect "
+            "type %d not supported.\n", spr->spr_how);
+        result = FALSE;
+        break;
+    }
+    return result;
+}
+
+static bool_t xdr_server_owner4(
+    XDR *xdr,
+    server_owner4 *so)
+{
+    char *so_major_id = so->so_major_id;
+
+    if (!xdr_u_hyper(xdr, &so->so_minor_id))
+        return FALSE;
+
+    return xdr_bytes(xdr, (char **)&so_major_id,
+        &so->so_major_id_len, NFS4_OPAQUE_LIMIT);
+}
+
+static bool_t decode_op_exchange_id(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_exchange_id_res *res = (nfs41_exchange_id_res*)resop->res;
+    char *server_scope = (char *)res->server_scope;
+
+    if (unexpected_op(resop->op, OP_EXCHANGE_ID))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status != NFS4_OK)
+        return TRUE;
+
+    if (!xdr_u_hyper(xdr, &res->clientid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->sequenceid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->flags))
+        return FALSE;
+
+    if (!xdr_state_protect4_r(xdr, &res->state_protect))
+        return FALSE;
+
+    if (!xdr_server_owner4(xdr, &res->server_owner))
+        return FALSE;
+
+    return xdr_bytes(xdr, &server_scope,
+        &res->server_scope_len, NFS4_OPAQUE_LIMIT);
+}
+
+/*
+ * OP_CREATE_SESSION
+ */
+static bool_t xdr_channel_attrs4(
+    XDR *xdr,
+    nfs41_channel_attrs *attrs)
+{
+    uint32_t zero = 0;
+    uint32_t one = 1;
+
+    /* count4 ca_headerpadsize */
+    if (!xdr_u_int32_t(xdr, &attrs->ca_headerpadsize))
+        return FALSE;
+
+    /* count4 ca_maxrequestsize */
+    if (!xdr_u_int32_t(xdr, &attrs->ca_maxrequestsize))
+        return FALSE;
+
+    /* count4 ca_maxresponsesize */
+    if (!xdr_u_int32_t(xdr, &attrs->ca_maxresponsesize))
+        return FALSE;
+
+    /* count4 ca_maxresponsesize_cached */
+    if (!xdr_u_int32_t(xdr, &attrs->ca_maxresponsesize_cached))
+        return FALSE;
+
+    /* count4 ca_maxoperations */
+    if (!xdr_u_int32_t(xdr, &attrs->ca_maxoperations))
+        return FALSE;
+
+    /* count4 ca_maxrequests */
+    if (!xdr_u_int32_t(xdr, &attrs->ca_maxrequests))
+        return FALSE;
+
+    if (xdr->x_op == XDR_ENCODE) {
+        /* uint32_t ca_rdma_ird<1> */
+        if (attrs->ca_rdma_ird)
+        {
+            if (!xdr_u_int32_t(xdr, &one))
+                return FALSE;
+            return xdr_u_int32_t(xdr, attrs->ca_rdma_ird);
+        }
+        else {
+            return xdr_u_int32_t(xdr, &zero);
+        }
+    }
+    else if (xdr->x_op == XDR_DECODE) {
+#if 0
+        u_int32_t count;
+        /* uint32_t ca_rdma_ird<1> */
+        if (!xdr_u_int32_t(xdr, &count))
+            return FALSE;
+        if (count > 1)
+            return FALSE;
+        if (count)
+            return xdr_u_int32_t(xdr, attrs->ca_rdma_ird);
+        else
+#endif
+            return TRUE;
+    }
+    else {
+        eprintf("%s: xdr->x_op %d not supported.\n",
+            "xdr_channel_attrs4", xdr->x_op);
+        return FALSE;
+    }
+}
+
+static bool_t encode_backchannel_sec_parms(
+    XDR *xdr,
+    nfs41_callback_secparms *args)
+{
+    uint32_t zero = 0;
+
+    if (!xdr_u_int32_t(xdr, &args->type))
+        return FALSE;
+
+    switch (args->type)  {
+    case AUTH_NONE: return TRUE;
+    case AUTH_SYS:
+        if (!xdr_u_int32_t(xdr, &args->u.auth_sys.stamp))
+            return FALSE;
+        if (!xdr_string(xdr, &args->u.auth_sys.machinename, NI_MAXHOST))
+            return FALSE;
+        return xdr_u_int32_t(xdr, &zero) && xdr_u_int32_t(xdr, &zero) && 
+                xdr_u_int32_t(xdr, &zero);
+    case RPCSEC_GSS:
+    default:
+        return FALSE;
+    }
+}
+
+static bool_t encode_op_create_session(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_create_session_args *args = (nfs41_create_session_args*)argop->arg;
+    nfs41_callback_secparms *cb_secparams = args->csa_cb_secparams;
+    uint32_t cb_count = 2;
+
+    if (unexpected_op(argop->op, OP_CREATE_SESSION))
+        return FALSE;
+
+    /* clientid4 csa_clientid */
+    if (!xdr_u_hyper(xdr, &args->csa_clientid))
+        return FALSE;
+
+    /* sequenceid4 csa_sequence */
+    if (!xdr_u_int32_t(xdr, &args->csa_sequence))
+        return FALSE;
+
+    /* TODO: uint32_t csa_flags = 0 */
+    if (!xdr_u_int32_t(xdr, &args->csa_flags))
+        return FALSE;
+
+    /* channel_attrs4 csa_fore_chan_attrs */
+    if (!xdr_channel_attrs4(xdr, &args->csa_fore_chan_attrs))
+        return FALSE;
+
+    /* channel_attrs4 csa_back_chan_attrs */
+    if (!xdr_channel_attrs4(xdr, &args->csa_back_chan_attrs))
+        return FALSE;
+
+    /* TODO: uint32_t csa_cb_program = 1234 */
+    if (!xdr_u_int32_t(xdr, &args->csa_cb_program))
+        return FALSE;
+
+    return xdr_array(xdr, (char **)&cb_secparams, &cb_count,
+        3, sizeof(nfs41_callback_secparms), (xdrproc_t) encode_backchannel_sec_parms);
+}
+
+static bool_t decode_op_create_session(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    uint32_t opstatus;
+    nfs41_create_session_res *res = (nfs41_create_session_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_CREATE_SESSION))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &opstatus))
+        return FALSE;
+
+    if (opstatus != NFS4_OK)
+        return TRUE;
+
+    if (!xdr_opaque(xdr, (char *)res->csr_sessionid, NFS4_SESSIONID_SIZE))
+        return FALSE;
+
+    /* sequenceid4 csr_sequence */
+    if (!xdr_u_int32_t(xdr, &res->csr_sequence))
+        return FALSE;
+
+    /* uint32_t csr_flags */
+    if (!xdr_u_int32_t(xdr, &res->csr_flags))
+        return FALSE;
+
+    /* channel_attrs4 csr_fore_chan_attrs */
+    if (!xdr_channel_attrs4(xdr, res->csr_fore_chan_attrs))
+        return FALSE;
+
+    /* channel_attrs4 csr_back_chan_attrs */
+    return xdr_channel_attrs4(xdr, res->csr_back_chan_attrs);
+}
+
+
+/*
+ * OP_BIND_CONN_TO_SESSION
+ */
+static bool_t encode_op_bind_conn_to_session(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    uint32_t zero = 0;
+
+    nfs41_bind_conn_to_session_args *args =
+        (nfs41_bind_conn_to_session_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_BIND_CONN_TO_SESSION))
+        return FALSE;
+
+    if (!xdr_opaque(xdr, (char *)args->sessionid, NFS4_SESSIONID_SIZE))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&args->dir))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &zero); /* bctsa_use_conn_in_rdma_mode = false */
+}
+
+static bool_t decode_op_bind_conn_to_session(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    unsigned char sessionid_ignored[NFS4_SESSIONID_SIZE];
+    nfs41_bind_conn_to_session_res *res =
+        (nfs41_bind_conn_to_session_res*)resop->res;
+    bool_t use_rdma_ignored;
+
+    if (unexpected_op(resop->op, OP_BIND_CONN_TO_SESSION))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK) {
+        if (!xdr_opaque(xdr, (char *)&sessionid_ignored, NFS4_SESSIONID_SIZE))
+            return FALSE;
+
+        if (!xdr_enum(xdr, (enum_t *)&res->dir))
+            return FALSE;
+
+        return xdr_bool(xdr, &use_rdma_ignored);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_DESTROY_SESSION
+ */
+static bool_t encode_op_destroy_session(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_destroy_session_args *args = (nfs41_destroy_session_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_DESTROY_SESSION))
+        return FALSE;
+
+    return xdr_opaque(xdr, (char *)args->dsa_sessionid, NFS4_SESSIONID_SIZE);
+}
+
+static bool_t decode_op_destroy_session(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_destroy_session_res *res = (nfs41_destroy_session_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_DESTROY_SESSION))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->dsr_status);
+}
+
+/*
+ * OP_DESTROY_CLIENTID
+ */
+static bool_t encode_op_destroy_clientid(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_destroy_clientid_args *args = (nfs41_destroy_clientid_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_DESTROY_CLIENTID))
+        return FALSE;
+
+    return xdr_u_hyper(xdr, &args->dca_clientid);
+}
+
+static bool_t decode_op_destroy_clientid(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_destroy_clientid_res *res = (nfs41_destroy_clientid_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_DESTROY_CLIENTID))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->dcr_status);
+}
+
+
+/*
+ * OP_SEQUENCE
+ */
+static bool_t encode_op_sequence(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_sequence_args *args = (nfs41_sequence_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_SEQUENCE))
+        return FALSE;
+
+    if (!xdr_opaque(xdr, (char *)args->sa_sessionid, NFS4_SESSIONID_SIZE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->sa_sequenceid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->sa_slotid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->sa_highest_slotid))
+        return FALSE;
+
+    return xdr_bool(xdr, &args->sa_cachethis);
+}
+
+static bool_t xdr_sequence_res_ok(
+    XDR *xdr,
+    nfs41_sequence_res_ok *res)
+{
+    if (!xdr_opaque(xdr, (char *)res->sr_sessionid, NFS4_SESSIONID_SIZE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->sr_sequenceid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->sr_slotid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->sr_highest_slotid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->sr_target_highest_slotid))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->sr_status_flags);
+}
+
+static bool_t decode_op_sequence(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_sequence_res *res = (nfs41_sequence_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_SEQUENCE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->sr_status))
+        return FALSE;
+
+    if (res->sr_status == NFS4_OK)
+        return xdr_sequence_res_ok(xdr, &res->sr_resok4);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_RECLAIM_COMPLETE
+ */
+static bool_t encode_op_reclaim_complete(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    bool_t zero = FALSE;
+
+    if (unexpected_op(argop->op, OP_RECLAIM_COMPLETE))
+        return FALSE;
+
+    /* rca_one_fs = 0 indicates that the reclaim applies to all filesystems */
+    return xdr_bool(xdr, &zero);
+}
+
+static bool_t decode_op_reclaim_complete(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_reclaim_complete_res *res = (nfs41_reclaim_complete_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_RECLAIM_COMPLETE))
+        return FALSE;
+
+    return xdr_enum(xdr, (enum_t *)&res->status);
+}
+
+
+/*
+ * OP_PUTFH
+ */
+static bool_t encode_op_putfh(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_putfh_args *args = (nfs41_putfh_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_PUTFH))
+        return FALSE;
+
+    return xdr_fh(xdr, &args->file->fh);
+}
+
+static bool_t decode_op_putfh(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_putfh_res *res = (nfs41_putfh_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_PUTFH))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_PUTROOTFH
+ */
+static bool_t encode_op_putrootfh(
+    XDR *xdr,
+    nfs_argop4* argop)
+{
+    if (unexpected_op(argop->op, OP_PUTROOTFH))
+        return FALSE;
+    /* void */
+    return TRUE;
+}
+
+static bool_t decode_op_putrootfh(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_putrootfh_res *res = (nfs41_putrootfh_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_PUTROOTFH))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_GETFH
+ */
+static bool_t encode_op_getfh(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    if (unexpected_op(argop->op, OP_GETFH))
+        return FALSE;
+
+    /* void */
+    return TRUE;
+}
+
+static bool_t decode_op_getfh(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_getfh_res *res = (nfs41_getfh_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_GETFH))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_fh(xdr, res->fh);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_LOOKUP
+ */
+static bool_t encode_op_lookup(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_lookup_args *args = (nfs41_lookup_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_LOOKUP))
+        return FALSE;
+
+    return encode_component(xdr, args->name);
+}
+
+static bool_t decode_op_lookup(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_lookup_res *res = (nfs41_lookup_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LOOKUP))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_ACCESS
+ */
+static bool_t encode_op_access(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_access_args *args = (nfs41_access_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_ACCESS))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &args->access);
+}
+
+static bool_t decode_op_access(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_access_res *res = (nfs41_access_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_ACCESS))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+    {
+        if (!xdr_u_int32_t(xdr, &res->supported))
+            return FALSE;
+
+        return xdr_u_int32_t(xdr, &res->access);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_CLOSE
+ */
+static bool_t encode_op_close(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_op_close_args *args = (nfs41_op_close_args*)argop->arg;
+    uint32_t zero = 0;
+
+    if (unexpected_op(argop->op, OP_CLOSE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &zero)) // This should be ignored by server
+        return FALSE;
+
+    return xdr_stateid4(xdr, &args->stateid->stateid);
+}
+
+static bool_t decode_op_close(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    stateid4 ignored;
+    nfs41_op_close_res *res = (nfs41_op_close_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_CLOSE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_stateid4(xdr, &ignored);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_COMMIT
+ */
+static bool_t encode_op_commit(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_commit_args *args = (nfs41_commit_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_COMMIT))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &args->count);
+}
+
+static bool_t decode_op_commit(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_commit_res *res = (nfs41_commit_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_COMMIT))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_opaque(xdr, (char *)res->verf->verf, NFS4_VERIFIER_SIZE);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_CREATE
+ */
+static bool_t encode_createtype4(
+    XDR *xdr,
+    createtype4 *ct)
+{
+    bool_t result = TRUE;
+    const char *linkdata;
+
+    if (!xdr_u_int32_t(xdr, &ct->type))
+        return FALSE;
+
+    switch (ct->type)
+    {
+    case NF4LNK:
+        linkdata = ct->u.lnk.linkdata;
+        result = xdr_bytes(xdr, (char**)&linkdata, &ct->u.lnk.linkdata_len,
+            NFS4_OPAQUE_LIMIT);
+        break;
+    case NF4BLK:
+    case NF4CHR:
+        result = xdr_u_int32_t(xdr, &ct->u.devdata.specdata1);
+        if (result == TRUE)
+            result = xdr_u_int32_t(xdr, &ct->u.devdata.specdata2);
+        break;
+    default:
+        // Some types need no further action
+        break;
+    }
+    return result;
+}
+
+static bool_t encode_createattrs4(
+    XDR *xdr,
+    nfs41_file_info* createattrs)
+{
+    fattr4 attrs;
+
+    /* encode attribute values from createattrs->info into attrs.attr_vals */
+    attrs.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    if (!encode_file_attrs(&attrs, createattrs))
+        return FALSE;
+
+    return xdr_fattr4(xdr, &attrs);
+}
+
+static bool_t encode_op_create(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_create_args *args = (nfs41_create_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_CREATE))
+        return FALSE;
+
+    if (!encode_createtype4(xdr, &args->objtype))
+        return FALSE;
+
+    if (!encode_component(xdr, args->name))
+        return FALSE;
+
+    return encode_createattrs4(xdr, args->createattrs);
+}
+
+static bool_t xdr_change_info4(
+    XDR *xdr,
+    change_info4 *cinfo)
+{
+    if (!xdr_bool(xdr, &cinfo->atomic))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &cinfo->before))
+        return FALSE;
+
+    return xdr_u_hyper(xdr, &cinfo->after);
+}
+
+static bool_t decode_op_create(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_create_res *res = (nfs41_create_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_CREATE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+    {
+        if (!xdr_change_info4(xdr, &res->cinfo))
+            return FALSE;
+        return xdr_bitmap4(xdr, &res->attrset);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_LINK
+ */
+static bool_t encode_op_link(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_link_args *args = (nfs41_link_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_LINK))
+        return FALSE;
+
+    return encode_component(xdr, args->newname);
+}
+
+static bool_t decode_op_link(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_link_res *res = (nfs41_link_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LINK))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_change_info4(xdr, &res->cinfo);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_LOCK
+ */
+static bool_t xdr_locker4(
+    XDR *xdr,
+    locker4 *locker)
+{
+    if (xdr->x_op != XDR_ENCODE) {
+        eprintf("%s: xdr->x_op %d is not supported!\n",
+            "xdr_locker4", xdr->x_op);
+        return FALSE;
+    }
+
+    if (!xdr_bool(xdr, &locker->new_lock_owner))
+        return FALSE;
+
+    if (locker->new_lock_owner) {
+        /* open_to_lock_owner4 open_owner */
+        if (!xdr_u_int32_t(xdr, &locker->u.open_owner.open_seqid))
+            return FALSE;
+
+        if (!xdr_stateid4(xdr, &locker->u.open_owner.open_stateid->stateid))
+            return FALSE;
+
+        if (!xdr_u_int32_t(xdr, &locker->u.open_owner.lock_seqid))
+            return FALSE;
+
+        return xdr_state_owner4(xdr, locker->u.open_owner.lock_owner);
+    } else {
+        /* exist_lock_owner4 lock_owner */
+        if (!xdr_stateid4(xdr, &locker->u.lock_owner.lock_stateid->stateid))
+            return FALSE;
+
+        return xdr_u_int32_t(xdr, &locker->u.lock_owner.lock_seqid);
+    }
+}
+
+static bool_t encode_op_lock(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_lock_args *args = (nfs41_lock_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_LOCK))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->locktype))
+        return FALSE;
+
+    if (!xdr_bool(xdr, &args->reclaim))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->length))
+        return FALSE;
+
+    return xdr_locker4(xdr, &args->locker);
+}
+
+static bool_t decode_lock_res_denied(
+    XDR *xdr,
+    lock_res_denied *denied)
+{
+    if (!xdr_u_hyper(xdr, &denied->offset))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &denied->length))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &denied->locktype))
+        return FALSE;
+
+    return xdr_state_owner4(xdr, &denied->owner);
+}
+
+static bool_t decode_op_lock(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_lock_res *res = (nfs41_lock_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LOCK))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    switch (res->status) {
+    case NFS4_OK:
+        return xdr_stateid4(xdr, res->u.resok4.lock_stateid);
+        break;
+    case NFS4ERR_DENIED:
+        return decode_lock_res_denied(xdr, &res->u.denied);
+        break;
+    default:
+        break;
+    }
+
+    return TRUE;
+}
+
+
+/*
+ * OP_LOCKT
+ */
+static bool_t encode_op_lockt(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_lockt_args *args = (nfs41_lockt_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_LOCKT))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->locktype))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->length))
+        return FALSE;
+
+    return xdr_state_owner4(xdr, args->owner);
+}
+
+static bool_t decode_op_lockt(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_lockt_res *res = (nfs41_lockt_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LOCKT))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4ERR_DENIED)
+        return decode_lock_res_denied(xdr, &res->denied);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_LOCKU
+ */
+static bool_t encode_op_locku(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_locku_args *args = (nfs41_locku_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_LOCKU))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->locktype))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->seqid))
+        return FALSE;
+
+    if (!xdr_stateid4(xdr, &args->lock_stateid->stateid))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    return xdr_u_hyper(xdr, &args->length);
+}
+
+static bool_t decode_op_locku(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_locku_res *res = (nfs41_locku_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LOCKU))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_stateid4(xdr, res->lock_stateid);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_DELEGPURGE
+ */
+static bool_t encode_op_delegpurge(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    uint64_t zero = 0;
+
+    if (unexpected_op(argop->op, OP_DELEGPURGE))
+        return FALSE;
+
+    /* The client SHOULD set the client field to zero,
+     * and the server MUST ignore the clientid field. */
+    return xdr_u_int64_t(xdr, &zero);
+}
+
+static bool_t decode_op_delegpurge(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_delegpurge_res *res = (nfs41_delegpurge_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_DELEGPURGE))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_DELEGRETURN
+ */
+static bool_t encode_op_delegreturn(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_delegreturn_args *args = (nfs41_delegreturn_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_DELEGRETURN))
+        return FALSE;
+
+    return xdr_stateid4(xdr, &args->stateid->stateid);
+}
+
+static bool_t decode_op_delegreturn(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_delegreturn_res *res = (nfs41_delegreturn_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_DELEGRETURN))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_GETATTR
+ */
+static bool_t encode_op_getattr(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_getattr_args *args = (nfs41_getattr_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_GETATTR))
+        return FALSE;
+
+    return xdr_bitmap4(xdr, args->attr_request);
+}
+
+static bool_t decode_file_attrs(
+    XDR *xdr,
+    fattr4 *attrs,
+    nfs41_file_info *info)
+{
+    if (attrs->attrmask.count >= 1) {
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_SUPPORTED_ATTRS) {
+            if (!xdr_bitmap4(xdr, info->supported_attrs))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_TYPE) {
+            if (!xdr_u_int32_t(xdr, &info->type))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CHANGE) {
+            if (!xdr_u_hyper(xdr, &info->change))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_SIZE) {
+            if (!xdr_u_hyper(xdr, &info->size))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_LINK_SUPPORT) {
+            if (!xdr_bool(xdr, &info->link_support))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_SYMLINK_SUPPORT) {
+            if (!xdr_bool(xdr, &info->symlink_support))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_FSID) {
+            if (!xdr_fsid(xdr, &info->fsid))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_LEASE_TIME) {
+            if (!xdr_u_int32_t(xdr, &info->lease_time))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_RDATTR_ERROR) {
+            if (!xdr_u_int32_t(xdr, &info->rdattr_error))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_ACL) {
+            nfsacl41 *acl = info->acl;
+            if (!xdr_array(xdr, (char**)&acl->aces, &acl->count,
+                32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_ACLSUPPORT) {
+            if (!xdr_u_int32_t(xdr, &info->aclsupport))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_ARCHIVE) {
+            if (!xdr_bool(xdr, &info->archive))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CANSETTIME) {
+            if (!xdr_bool(xdr, &info->cansettime))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CASE_INSENSITIVE) {
+            if (!xdr_bool(xdr, &info->case_insensitive))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_CASE_PRESERVING) {
+            if (!xdr_bool(xdr, &info->case_preserving))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_FILEID) {
+            if (!xdr_u_hyper(xdr, &info->fileid))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_FS_LOCATIONS) {
+            if (!decode_fs_locations4(xdr, info->fs_locations))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_HIDDEN) {
+            if (!xdr_bool(xdr, &info->hidden))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_MAXREAD) {
+            if (!xdr_u_hyper(xdr, &info->maxread))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[0] & FATTR4_WORD0_MAXWRITE) {
+            if (!xdr_u_hyper(xdr, &info->maxwrite))
+                return FALSE;
+        }
+    }
+    if (attrs->attrmask.count >= 2) {
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_MODE) {
+            if (!xdr_u_int32_t(xdr, &info->mode))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_NUMLINKS) {
+            if (!xdr_u_int32_t(xdr, &info->numlinks))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_OWNER) {
+            char *ptr = &info->owner[0];
+            uint32_t owner_len;
+            if (!xdr_bytes(xdr, &ptr, &owner_len, 
+                            NFS4_OPAQUE_LIMIT))
+                return FALSE;
+            info->owner[owner_len] = '\0';
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_OWNER_GROUP) {
+            char *ptr = &info->owner_group[0];
+            uint32_t owner_group_len;
+            if (!xdr_bytes(xdr, &ptr, &owner_group_len, 
+                            NFS4_OPAQUE_LIMIT))
+                return FALSE;
+            info->owner_group[owner_group_len] = '\0';
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_AVAIL) {
+            if (!xdr_u_hyper(xdr, &info->space_avail))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_FREE) {
+            if (!xdr_u_hyper(xdr, &info->space_free))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_TOTAL) {
+            if (!xdr_u_hyper(xdr, &info->space_total))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_SYSTEM) {
+            if (!xdr_bool(xdr, &info->system))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS) {
+            if (!xdr_nfstime4(xdr, &info->time_access))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) {
+            if (!xdr_nfstime4(xdr, &info->time_create))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_DELTA) {
+            if (!xdr_nfstime4(xdr, info->time_delta))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY) {
+            if (!xdr_nfstime4(xdr, &info->time_modify))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_DACL) {
+            if (!xdr_nfsdacl41(xdr, info->acl))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[1] & FATTR4_WORD1_FS_LAYOUT_TYPE) {
+            if (!xdr_layout_types(xdr, &info->fs_layout_types))
+                return FALSE;
+        }
+    }
+    if (attrs->attrmask.count >= 3) {
+        if (attrs->attrmask.arr[2] & FATTR4_WORD2_MDSTHRESHOLD) {
+            if (!xdr_mdsthreshold(xdr, &info->mdsthreshold))
+                return FALSE;
+        }
+        if (attrs->attrmask.arr[2] & FATTR4_WORD2_SUPPATTR_EXCLCREAT) {
+            if (!xdr_bitmap4(xdr, info->suppattr_exclcreat))
+                return FALSE;
+        }
+    }
+    return TRUE;
+}
+
+static bool_t decode_op_getattr(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_getattr_res *res = (nfs41_getattr_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_GETATTR))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+    {
+        XDR attr_xdr;
+
+        if (!xdr_fattr4(xdr, &res->obj_attributes))
+            return FALSE;
+        xdrmem_create(&attr_xdr, (char *)res->obj_attributes.attr_vals, res->obj_attributes.attr_vals_len, XDR_DECODE);
+        return  decode_file_attrs(&attr_xdr, &res->obj_attributes, res->info);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_OPEN
+ */
+static bool_t encode_createhow4(
+    XDR *xdr,
+    createhow4 *ch)
+{
+    bool_t result = TRUE;
+
+    if (!xdr_u_int32_t(xdr, &ch->mode))
+        return FALSE;
+
+    switch (ch->mode)
+    {
+    case UNCHECKED4:
+    case GUARDED4:
+        result = encode_createattrs4(xdr, ch->createattrs);
+        break;
+    case EXCLUSIVE4:
+        result = xdr_opaque(xdr, (char *)ch->createverf, NFS4_VERIFIER_SIZE);
+        break;
+    case EXCLUSIVE4_1:
+        if (!xdr_opaque(xdr, (char *)ch->createverf, NFS4_VERIFIER_SIZE))
+            return FALSE;
+        if (!encode_createattrs4(xdr, ch->createattrs))
+            return FALSE;
+        break;
+    }
+    return result;
+}
+
+static bool_t encode_openflag4(
+    XDR *xdr,
+    openflag4 *of)
+{
+    bool_t result = TRUE;
+
+    if (!xdr_u_int32_t(xdr, &of->opentype))
+        return FALSE;
+
+    switch (of->opentype)
+    {
+    case OPEN4_CREATE:
+        result = encode_createhow4(xdr, &of->how);
+        break;
+    default:
+        break;
+    }
+    return result;
+}
+
+static bool_t encode_claim_deleg_cur(
+    XDR *xdr,
+    stateid4 *stateid,
+    nfs41_component *name)
+{
+    if (!xdr_stateid4(xdr, stateid))
+        return FALSE;
+    return encode_component(xdr, name);
+}
+
+static bool_t encode_open_claim4(
+    XDR *xdr,
+    open_claim4 *oc)
+{
+    if (!xdr_u_int32_t(xdr, &oc->claim))
+        return FALSE;
+
+    switch (oc->claim)
+    {
+    case CLAIM_NULL:
+        return encode_component(xdr, oc->u.null.filename);
+    case CLAIM_PREVIOUS:
+        return xdr_u_int32_t(xdr, &oc->u.prev.delegate_type);
+    case CLAIM_FH:
+        return TRUE; /* use current file handle */
+    case CLAIM_DELEGATE_CUR:
+        return encode_claim_deleg_cur(xdr,
+            &oc->u.deleg_cur.delegate_stateid->stateid,
+            oc->u.deleg_cur.name);
+    case CLAIM_DELEG_CUR_FH:
+        return xdr_stateid4(xdr,
+            &oc->u.deleg_cur_fh.delegate_stateid->stateid);
+    case CLAIM_DELEGATE_PREV:
+        return encode_component(xdr, oc->u.deleg_prev.filename);
+    case CLAIM_DELEG_PREV_FH:
+        return TRUE; /* use current file handle */
+    default:
+        eprintf("encode_open_claim4: unsupported claim %d.\n",
+            oc->claim);
+        return FALSE;
+    }
+}
+
+static bool_t encode_op_open(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_op_open_args *args = (nfs41_op_open_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_OPEN))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->seqid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->share_access))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->share_deny))
+        return FALSE;
+
+    if (!xdr_state_owner4(xdr, args->owner))
+        return FALSE;
+
+    if (!encode_openflag4(xdr, &args->openhow))
+        return FALSE;
+
+    return encode_open_claim4(xdr, args->claim);
+}
+
+static bool_t decode_open_none_delegation4(
+    XDR *xdr,
+    open_delegation4 *delegation)
+{
+    enum_t why_no_deleg;
+    bool_t will_signal;
+
+    if (!xdr_enum(xdr, (enum_t*)&why_no_deleg))
+        return FALSE;
+
+    switch (why_no_deleg)
+    {
+    case WND4_CONTENTION:
+    case WND4_RESOURCE:
+        return xdr_bool(xdr, &will_signal);
+    default:
+        return TRUE;
+    }
+}
+
+static bool_t decode_open_read_delegation4(
+    XDR *xdr,
+    open_delegation4 *delegation)
+{
+    if (!xdr_stateid4(xdr, &delegation->stateid))
+        return FALSE;
+
+    if (!xdr_bool(xdr, &delegation->recalled))
+        return FALSE;
+
+    return xdr_nfsace4(xdr, &delegation->permissions);
+}
+
+static bool_t decode_modified_limit4(
+    XDR *xdr,
+    uint64_t *filesize)
+{
+    uint32_t blocks, bytes_per_block;
+
+    if (!xdr_u_int32_t(xdr, &blocks))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &bytes_per_block))
+        return FALSE;
+
+    *filesize = blocks * bytes_per_block;
+    return TRUE;
+}
+
+enum limit_by4 {
+    NFS_LIMIT_SIZE          = 1,
+    NFS_LIMIT_BLOCKS        = 2
+};
+
+static bool_t decode_space_limit4(
+    XDR *xdr,
+    uint64_t *filesize)
+{
+    uint32_t limitby;
+
+    if (!xdr_u_int32_t(xdr, &limitby))
+        return FALSE;
+
+    switch (limitby)
+    {
+    case NFS_LIMIT_SIZE:
+        return xdr_u_hyper(xdr, filesize);
+    case NFS_LIMIT_BLOCKS:
+        return decode_modified_limit4(xdr, filesize);
+    default:
+        eprintf("decode_space_limit4: limitby %d invalid\n", limitby);
+        return FALSE;
+    }
+}
+
+static bool_t decode_open_write_delegation4(
+    XDR *xdr,
+    open_delegation4 *delegation)
+{
+    uint64_t size_limit;
+
+    if (!xdr_stateid4(xdr, &delegation->stateid))
+        return FALSE;
+
+    if (!xdr_bool(xdr, &delegation->recalled))
+        return FALSE;
+
+    if (!decode_space_limit4(xdr, &size_limit))
+        return FALSE;
+
+    return xdr_nfsace4(xdr, &delegation->permissions);
+}
+
+static bool_t decode_open_res_ok(
+    XDR *xdr,
+    nfs41_op_open_res_ok *res)
+{
+    if (!xdr_stateid4(xdr, res->stateid))
+        return FALSE;
+
+    if (!xdr_change_info4(xdr, &res->cinfo))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->rflags))
+        return FALSE;
+
+    if (!xdr_bitmap4(xdr, &res->attrset))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t*)&res->delegation->type))
+        return FALSE;
+
+    switch (res->delegation->type)
+    {
+    case OPEN_DELEGATE_NONE:
+        return TRUE;
+    case OPEN_DELEGATE_NONE_EXT:
+        return decode_open_none_delegation4(xdr, res->delegation);
+    case OPEN_DELEGATE_READ:
+        return decode_open_read_delegation4(xdr, res->delegation);
+    case OPEN_DELEGATE_WRITE:
+        return decode_open_write_delegation4(xdr, res->delegation);
+    default:
+        eprintf("decode_open_res_ok: delegation type %d not "
+            "supported.\n", res->delegation->type);
+        return FALSE;
+    }
+}
+
+static bool_t decode_op_open(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_op_open_res *res = (nfs41_op_open_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_OPEN))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return decode_open_res_ok(xdr, &res->resok4);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_OPENATTR
+ */
+static bool_t encode_op_openattr(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_openattr_args *args = (nfs41_openattr_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_OPENATTR))
+        return FALSE;
+
+    return xdr_bool(xdr, &args->createdir);
+}
+
+static bool_t decode_op_openattr(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_openattr_res *res = (nfs41_openattr_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_OPENATTR))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_READ
+ */
+static bool_t encode_op_read(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_read_args *args = (nfs41_read_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_READ))
+        return FALSE;
+
+    if (!xdr_stateid4(xdr, &args->stateid->stateid))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &args->count);
+}
+
+static bool_t decode_read_res_ok(
+    XDR *xdr,
+    nfs41_read_res_ok *res)
+{
+    unsigned char *data = res->data;
+
+    if (!xdr_bool(xdr, &res->eof))
+        return FALSE;
+
+    return xdr_bytes(xdr, (char **)&data, &res->data_len, NFS41_MAX_FILEIO_SIZE);
+}
+
+static bool_t decode_op_read(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_read_res *res = (nfs41_read_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_READ))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return decode_read_res_ok(xdr, &res->resok4);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_READDIR
+ */
+static bool_t encode_op_readdir(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_readdir_args *args = (nfs41_readdir_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_READDIR))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->cookie.cookie))
+        return FALSE;
+
+    if (!xdr_opaque(xdr, (char *)args->cookie.verf, NFS4_VERIFIER_SIZE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->dircount))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->maxcount))
+        return FALSE;
+
+    return xdr_bitmap4(xdr, args->attr_request);
+}
+
+typedef struct __readdir_entry_iterator {
+    unsigned char   *buf_pos;
+    uint32_t        remaining_len;
+    uint32_t        *last_entry_offset;
+    bool_t          ignore_the_rest;
+    bool_t          has_next_entry;
+} readdir_entry_iterator;
+
+static bool_t decode_readdir_entry(
+    XDR *xdr,
+    readdir_entry_iterator *it)
+{
+    uint64_t cookie;
+    unsigned char name[NFS4_OPAQUE_LIMIT];
+    unsigned char *nameptr = &name[0];
+    uint32_t name_len, entry_len;
+    fattr4 attrs;
+
+    /* decode into temporaries so we can determine if there's enough
+     * room in the buffer for this entry */
+    ZeroMemory(name, NFS4_OPAQUE_LIMIT);
+    name_len = NFS4_OPAQUE_LIMIT;
+    entry_len = (uint32_t)FIELD_OFFSET(nfs41_readdir_entry, name);
+    attrs.attr_vals_len = NFS4_OPAQUE_LIMIT;
+
+    if (!xdr_u_hyper(xdr, &cookie))
+        return FALSE;
+
+    if (!xdr_bytes(xdr, (char **)&nameptr, &name_len, NFS4_OPAQUE_LIMIT))
+        return FALSE;
+
+    if (!xdr_fattr4(xdr, &attrs))
+        return FALSE;
+
+    if (!xdr_bool(xdr, &it->has_next_entry))
+        return FALSE;
+
+    if (it->ignore_the_rest)
+        return TRUE;
+
+    name_len += 1; /* account for null terminator */
+    if (entry_len + name_len <= it->remaining_len)
+    {
+        XDR fattr_xdr;
+        nfs41_readdir_entry *entry = (nfs41_readdir_entry*)it->buf_pos;
+        entry->cookie = cookie;
+        entry->name_len = name_len;
+
+        if (it->has_next_entry)
+            entry->next_entry_offset = entry_len + name_len;
+        else
+            entry->next_entry_offset = 0;
+
+        xdrmem_create(&fattr_xdr, (char *)attrs.attr_vals, attrs.attr_vals_len, XDR_DECODE);
+        if (!(decode_file_attrs(&fattr_xdr, &attrs, &entry->attr_info)))
+            entry->attr_info.rdattr_error = NFS4ERR_BADXDR;
+        StringCchCopyA(entry->name, name_len, (STRSAFE_LPCSTR)name);
+
+        it->buf_pos += entry_len + name_len;
+        it->remaining_len -= entry_len + name_len;
+        it->last_entry_offset = &entry->next_entry_offset;
+    }
+    else if (it->last_entry_offset)
+    {
+        *(it->last_entry_offset) = 0;
+        it->ignore_the_rest = 1;
+    }
+
+    return TRUE;
+}
+
+static bool_t decode_readdir_list(
+    XDR *xdr,
+    nfs41_readdir_list *dirs)
+{
+    readdir_entry_iterator iter;
+    iter.buf_pos = dirs->entries;
+    iter.remaining_len = dirs->entries_len;
+    iter.last_entry_offset = NULL;
+    iter.ignore_the_rest = 0;
+    iter.has_next_entry = 0;
+
+    if (!xdr_bool(xdr, &dirs->has_entries))
+        return FALSE;
+
+    if (dirs->has_entries)
+    {
+        do {
+            if (!decode_readdir_entry(xdr, &iter))
+                return FALSE;
+
+        } while (iter.has_next_entry);
+    }
+    dirs->entries_len -= iter.remaining_len;
+
+    if (!xdr_bool(xdr, &dirs->eof))
+        return FALSE;
+
+    /* reset eof if we couldn't fit everything in the buffer */
+    if (iter.ignore_the_rest)
+        dirs->eof = 0;
+    return TRUE;
+}
+
+static bool_t decode_op_readdir(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_readdir_res *res = (nfs41_readdir_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_READDIR))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK) {
+        if (!xdr_opaque(xdr, (char *)res->cookieverf, NFS4_VERIFIER_SIZE))
+            return FALSE;
+        return decode_readdir_list(xdr, &res->reply);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_READLINK
+ */
+static bool_t encode_op_readlink(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    if (unexpected_op(argop->op, OP_READLINK))
+        return FALSE;
+
+    /* void */
+    return TRUE;
+}
+
+static bool_t decode_op_readlink(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_readlink_res *res = (nfs41_readlink_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_READLINK))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK) {
+        char *link = res->link;
+        return xdr_bytes(xdr, &link, &res->link_len, res->link_len);
+    }
+
+    return TRUE;
+}
+
+
+/*
+ * OP_REMOVE
+ */
+static bool_t encode_op_remove(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_remove_args *args = (nfs41_remove_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_REMOVE))
+        return FALSE;
+
+    return encode_component(xdr, args->target);
+}
+
+static bool_t decode_op_remove(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_remove_res *res = (nfs41_remove_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_REMOVE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_change_info4(xdr, &res->cinfo);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_RENAME
+ */
+static bool_t encode_op_rename(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_rename_args *args = (nfs41_rename_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_RENAME))
+        return FALSE;
+
+    if (!encode_component(xdr, args->oldname))
+        return FALSE;
+
+    return encode_component(xdr, args->newname);
+}
+
+static bool_t decode_op_rename(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_rename_res *res = (nfs41_rename_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_RENAME))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+    {
+        if (!xdr_change_info4(xdr, &res->source_cinfo))
+            return FALSE;
+        return xdr_change_info4(xdr, &res->target_cinfo);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_RESTOREFH
+ */
+static bool_t encode_op_restorefh(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    if (unexpected_op(argop->op, OP_RESTOREFH))
+        return FALSE;
+
+    /* void */
+    return TRUE;
+}
+
+static bool_t decode_op_restorefh(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_restorefh_res *res = (nfs41_restorefh_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_RESTOREFH))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_SAVEFH
+ */
+static bool_t encode_op_savefh(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    if (unexpected_op(argop->op, OP_SAVEFH))
+        return FALSE;
+
+    /* void */
+    return TRUE;
+}
+
+static bool_t decode_op_savefh(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_savefh_res *res = (nfs41_savefh_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_SAVEFH))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_SETATTR
+ */
+static bool_t encode_file_attrs(
+    fattr4 *attrs,
+    nfs41_file_info *info)
+{
+    uint32_t i;
+    XDR localxdr;
+
+    xdrmem_create(&localxdr, (char *)attrs->attr_vals, NFS4_OPAQUE_LIMIT, XDR_ENCODE);
+
+    attrs->attr_vals_len = 0;
+    ZeroMemory(&attrs->attrmask, sizeof(bitmap4));
+    attrs->attrmask.count = info->attrmask.count;
+
+    if (info->attrmask.count > 0) {
+        if (info->attrmask.arr[0] & FATTR4_WORD0_SIZE) {
+            if (!xdr_u_hyper(&localxdr, &info->size))
+                return FALSE;
+            attrs->attrmask.arr[0] |= FATTR4_WORD0_SIZE;
+        }
+        if (info->attrmask.arr[0] & FATTR4_WORD0_ACL) {
+            if (!xdr_nfsacl41(&localxdr, info->acl))
+                return FALSE;
+            attrs->attrmask.arr[0] |= FATTR4_WORD0_ACL;
+        }
+        if (info->attrmask.arr[0] & FATTR4_WORD0_ARCHIVE) {
+            if (!xdr_bool(&localxdr, &info->archive))
+                return FALSE;
+            attrs->attrmask.arr[0] |= FATTR4_WORD0_ARCHIVE;
+        }
+        if (info->attrmask.arr[0] & FATTR4_WORD0_HIDDEN) {
+            if (!xdr_bool(&localxdr, &info->hidden))
+                return FALSE;
+            attrs->attrmask.arr[0] |= FATTR4_WORD0_HIDDEN;
+        }
+    }
+    if (info->attrmask.count > 1) {
+        if (info->attrmask.arr[1] & FATTR4_WORD1_MODE) {
+            if (!xdr_u_int32_t(&localxdr, &info->mode))
+                return FALSE;
+            attrs->attrmask.arr[1] |= FATTR4_WORD1_MODE;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_SYSTEM) {
+            if (!xdr_bool(&localxdr, &info->system))
+                return FALSE;
+            attrs->attrmask.arr[1] |= FATTR4_WORD1_SYSTEM;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS_SET) {
+            if (!xdr_settime4(&localxdr, &info->time_access, info->time_delta))
+                return FALSE;
+            attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_ACCESS_SET;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) {
+            if (!xdr_nfstime4(&localxdr, &info->time_create))
+                return FALSE;
+            attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY_SET) {
+            if (!xdr_settime4(&localxdr, &info->time_modify, info->time_delta))
+                return FALSE;
+            attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_MODIFY_SET;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_OWNER) {
+            char *ptr = &info->owner[0];
+            uint32_t owner_len = (uint32_t)strlen(info->owner);
+            if (!xdr_bytes(&localxdr, &ptr, &owner_len, 
+                            NFS4_OPAQUE_LIMIT))
+                return FALSE;
+            attrs->attrmask.arr[1] |= FATTR4_WORD1_OWNER;
+        }
+        if (info->attrmask.arr[1] & FATTR4_WORD1_OWNER_GROUP) {
+            char *ptr = &info->owner_group[0];
+            uint32_t owner_group_len = (uint32_t)strlen(info->owner_group);
+            if (!xdr_bytes(&localxdr, &ptr, &owner_group_len, 
+                            NFS4_OPAQUE_LIMIT))
+                return FALSE;
+            attrs->attrmask.arr[1] |= FATTR4_WORD1_OWNER_GROUP;
+        }
+    }
+    if (info->attrmask.count > 2) {
+        if (info->attrmask.arr[2] & FATTR4_WORD2_MODE_SET_MASKED) {
+            if (!xdr_u_int32_t(&localxdr, &info->mode))
+                return FALSE;
+            if (!xdr_u_int32_t(&localxdr, &info->mode_mask))
+                return FALSE;
+            attrs->attrmask.arr[2] |= FATTR4_WORD2_MODE_SET_MASKED;
+        }
+    }
+
+    /* warn if we try to set attributes that aren't handled */
+    for (i = 0; i < info->attrmask.count; i++)
+        if (attrs->attrmask.arr[i] != info->attrmask.arr[i])
+            eprintf("encode_file_attrs() attempted to encode extra "
+                "attributes in arr[%d]: encoded %d, but wanted %d.\n",
+                i, attrs->attrmask.arr[i], info->attrmask.arr[i]);
+
+    attrs->attr_vals_len = xdr_getpos(&localxdr);
+    return TRUE;
+}
+
+static bool_t encode_op_setattr(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_setattr_args *args = (nfs41_setattr_args*)argop->arg;
+    fattr4 attrs;
+
+    if (unexpected_op(argop->op, OP_SETATTR))
+        return FALSE;
+
+    if (!xdr_stateid4(xdr, &args->stateid->stateid))
+        return FALSE;
+
+    /* encode attribute values from args->info into attrs.attr_vals */
+    attrs.attr_vals_len = NFS4_OPAQUE_LIMIT;
+    if (!encode_file_attrs(&attrs, args->info))
+        return FALSE;
+
+    return xdr_fattr4(xdr, &attrs);
+}
+
+static bool_t decode_op_setattr(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_setattr_res *res = (nfs41_setattr_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_SETATTR))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_bitmap4(xdr, &res->attrsset);
+
+    return TRUE;
+}
+
+
+/*
+ * OP_WANT_DELEGATION
+ */
+static bool_t encode_op_want_delegation(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_want_delegation_args *args = (nfs41_want_delegation_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_WANT_DELEGATION))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->want))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->claim->claim))
+        return FALSE;
+
+    return args->claim->claim != CLAIM_PREVIOUS ||
+        xdr_u_int32_t(xdr, &args->claim->prev_delegate_type);
+}
+
+static bool_t decode_op_want_delegation(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_want_delegation_res *res = (nfs41_want_delegation_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_WANT_DELEGATION))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status)
+        return TRUE;
+
+    if (!xdr_enum(xdr, (enum_t*)&res->delegation->type))
+        return FALSE;
+
+    switch (res->delegation->type)
+    {
+    case OPEN_DELEGATE_NONE:
+        return TRUE;
+    case OPEN_DELEGATE_NONE_EXT:
+        return decode_open_none_delegation4(xdr, res->delegation);
+    case OPEN_DELEGATE_READ:
+        return decode_open_read_delegation4(xdr, res->delegation);
+    case OPEN_DELEGATE_WRITE:
+        return decode_open_write_delegation4(xdr, res->delegation);
+    default:
+        eprintf("decode_open_res_ok: delegation type %d not "
+            "supported.\n", res->delegation->type);
+        return FALSE;
+    }
+}
+
+
+/*
+ * OP_FREE_STATEID
+ */
+static bool_t encode_op_free_stateid(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_free_stateid_args *args = (nfs41_free_stateid_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_FREE_STATEID))
+        return FALSE;
+
+    return xdr_stateid4(xdr, args->stateid);
+}
+
+static bool_t decode_op_free_stateid(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_free_stateid_res *res = (nfs41_free_stateid_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_FREE_STATEID))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &res->status);
+}
+
+
+/*
+ * OP_TEST_STATEID
+ */
+static bool_t encode_op_test_stateid(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_test_stateid_args *args = (nfs41_test_stateid_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_TEST_STATEID))
+        return FALSE;
+
+    return xdr_array(xdr, (char**)&args->stateids, &args->count,
+        args->count, sizeof(stateid_arg), (xdrproc_t)xdr_stateid4);
+}
+
+static bool_t decode_op_test_stateid(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_test_stateid_res *res = (nfs41_test_stateid_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_TEST_STATEID))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK) {
+        return xdr_array(xdr, (char**)&res->resok.status, &res->resok.count,
+            res->resok.count, sizeof(uint32_t), (xdrproc_t)xdr_u_int32_t);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_WRITE
+ */
+static bool_t encode_op_write(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_write_args *args = (nfs41_write_args*)argop->arg;
+    unsigned char *data = args->data;
+
+    if (unexpected_op(argop->op, OP_WRITE))
+        return FALSE;
+
+    if (!xdr_stateid4(xdr, &args->stateid->stateid))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->stable))
+        return FALSE;
+
+    return xdr_bytes(xdr, (char **)&data, &args->data_len, NFS41_MAX_FILEIO_SIZE);
+}
+
+static bool_t xdr_write_verf(
+    XDR *xdr,
+    nfs41_write_verf *verf)
+{
+    if (!xdr_enum(xdr, (enum_t *)&verf->committed))
+        return FALSE;
+
+    return xdr_opaque(xdr, (char *)verf->verf, NFS4_VERIFIER_SIZE);
+}
+
+static bool_t xdr_write_res_ok(
+    XDR *xdr,
+    nfs41_write_res_ok *res)
+{
+    if (!xdr_u_int32_t(xdr, &res->count))
+        return FALSE;
+
+    return xdr_write_verf(xdr, res->verf);
+}
+
+static bool_t decode_op_write(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_write_res *res = (nfs41_write_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_WRITE))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_write_res_ok(xdr, &res->resok4);
+
+    return TRUE;
+}
+
+/* 
+ * OP_SECINFO_NO_NAME
+ */
+static bool_t xdr_secinfo(
+    XDR *xdr,
+    nfs41_secinfo_info *secinfo)
+{
+    if (!xdr_u_int32_t(xdr, &secinfo->sec_flavor))
+        return FALSE;
+    if (secinfo->sec_flavor == RPCSEC_GSS) {
+        char *p = secinfo->oid;
+        if (!xdr_bytes(xdr, (char **)&p, &secinfo->oid_len, MAX_OID_LEN))
+            return FALSE;
+        if (!xdr_u_int32_t(xdr, &secinfo->qop))
+            return FALSE;
+        if (!xdr_enum(xdr, (enum_t *)&secinfo->type))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static bool_t encode_op_secinfo_noname(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_secinfo_noname_args *args = (nfs41_secinfo_noname_args *)argop->arg;
+
+    if (unexpected_op(argop->op, OP_SECINFO_NO_NAME))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&args->type))
+        return FALSE;
+
+    return TRUE;
+}
+
+static bool_t decode_op_secinfo_noname(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_secinfo_noname_res *res = (nfs41_secinfo_noname_res *)resop->res;
+    nfs41_secinfo_info *secinfo = res->secinfo;
+    if (unexpected_op(resop->op, OP_SECINFO_NO_NAME))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_array(xdr, (char**)&secinfo, &res->count,
+            MAX_SECINFOS, sizeof(nfs41_secinfo_info), (xdrproc_t)xdr_secinfo);
+
+    return TRUE;
+}
+
+/* 
+ * OP_SECINFO
+ */
+static bool_t encode_op_secinfo(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    nfs41_secinfo_args *args = (nfs41_secinfo_args *)argop->arg;
+
+    if (unexpected_op(argop->op, OP_SECINFO))
+        return FALSE;
+
+    if (!encode_component(xdr, args->name))
+        return FALSE;
+
+    return TRUE;
+}
+
+static bool_t decode_op_secinfo(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    nfs41_secinfo_noname_res *res = (nfs41_secinfo_noname_res *)resop->res;
+    nfs41_secinfo_info *secinfo = res->secinfo;
+
+    if (unexpected_op(resop->op, OP_SECINFO))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK)
+        return xdr_array(xdr, (char**)&secinfo, &res->count,
+            MAX_SECINFOS, sizeof(nfs41_secinfo_info), (xdrproc_t)xdr_secinfo);
+
+    return TRUE;
+}
+/*
+ * OP_GETDEVICEINFO
+ */
+static bool_t encode_op_getdeviceinfo(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    pnfs_getdeviceinfo_args *args = (pnfs_getdeviceinfo_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_GETDEVICEINFO))
+        return FALSE;
+
+    if (!xdr_opaque(xdr, (char *)args->deviceid, PNFS_DEVICEID_SIZE))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&args->layout_type))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->maxcount))
+        return FALSE;
+
+    return xdr_bitmap4(xdr, &args->notify_types);
+}
+
+static bool_t xdr_stripe_indices(
+    XDR *xdr,
+    pnfs_stripe_indices *indices)
+{
+    uint32_t i, count;
+
+    if (!xdr_u_int32_t(xdr, &count))
+        return FALSE;
+
+    if (count && count != indices->count) {
+        uint32_t *tmp;
+        tmp = realloc(indices->arr, count * sizeof(uint32_t));
+        if (tmp == NULL)
+            return FALSE;
+        indices->arr = tmp;
+        ZeroMemory(indices->arr, count * sizeof(uint32_t));
+        indices->count = count;
+    }
+    
+    for (i = 0; i < indices->count; i++) {
+        if (!xdr_u_int32_t(xdr, &indices->arr[i]))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static bool_t xdr_pnfs_addr(
+    XDR *xdr,
+    netaddr4 *addr)
+{
+    uint32_t len;
+    char *netid = addr->netid;
+    char *uaddr = addr->uaddr;
+
+    if (xdr->x_op == XDR_ENCODE)
+        len = sizeof(addr->netid);
+    if (!xdr_bytes(xdr, &netid, &len, NFS41_NETWORK_ID_LEN))
+        return FALSE;
+
+    if (xdr->x_op == XDR_DECODE) {
+        if (len < NFS41_NETWORK_ID_LEN)
+            addr->netid[len] = 0;
+        else
+            addr->netid[NFS41_NETWORK_ID_LEN] = 0;
+    }
+
+    if (xdr->x_op == XDR_ENCODE)
+        len = sizeof(addr->uaddr);
+    if (!xdr_bytes(xdr, &uaddr, &len, NFS41_UNIVERSAL_ADDR_LEN))
+        return FALSE;
+
+    if (xdr->x_op == XDR_DECODE){
+        if (len < NFS41_UNIVERSAL_ADDR_LEN)
+            addr->uaddr[len] = 0;
+        else
+            addr->uaddr[NFS41_UNIVERSAL_ADDR_LEN] = 0;
+    }
+
+    return TRUE;
+}
+
+static bool_t xdr_multi_addr(
+    XDR *xdr,
+    multi_addr4 *list)
+{
+    netaddr4 dummy, *dest;
+    uint32_t i;
+
+    if (!xdr_u_int32_t(xdr, &list->count))
+        return FALSE;
+
+    for (i = 0; i < list->count; i++) {
+        /* if there are too many addrs, decode the extras into 'dummy' */
+        dest = i < NFS41_ADDRS_PER_SERVER ? &list->arr[i] : &dummy;
+
+        if (!xdr_pnfs_addr(xdr, dest))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static bool_t xdr_data_server_list(
+    XDR *xdr,
+    pnfs_data_server_list *servers)
+{
+    uint32_t i, count;
+
+    if (!xdr_u_int32_t(xdr, &count))
+        return FALSE;
+
+    if (count && count != servers->count) {
+        pnfs_data_server *tmp;
+        /* clear data server clients; they're still cached with nfs41_root,
+         * so pnfs_data_server_client() will look them up again */
+        for (i = 0; i < servers->count; i++)
+            servers->arr[i].client = NULL;
+
+        tmp = realloc(servers->arr, count * sizeof(pnfs_data_server));
+        if (tmp == NULL) 
+            return FALSE;
+        servers->arr = tmp;
+        ZeroMemory(servers->arr, count * sizeof(pnfs_data_server));
+        for (i = servers->count; i < count; i++) /* initialize new elements */
+            InitializeSRWLock(&servers->arr[i].lock);
+        servers->count = count;
+    }
+
+    for (i = 0; i < servers->count; i++) {
+        if (!xdr_multi_addr(xdr, &servers->arr[i].addrs))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static bool_t xdr_file_device(
+    XDR *xdr,
+    pnfs_file_device *device)
+{
+    if (!xdr_stripe_indices(xdr, &device->stripes))
+        return FALSE;
+
+    return xdr_data_server_list(xdr, &device->servers);
+}
+
+static bool_t decode_getdeviceinfo_ok(
+    XDR *xdr,
+    pnfs_getdeviceinfo_res_ok *res_ok)
+{
+    u_int32_t len_ignored;
+
+    if (!xdr_enum(xdr, (enum_t *)&res_ok->device->device.type))
+        return FALSE;
+
+    if (res_ok->device->device.type != PNFS_LAYOUTTYPE_FILE)
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &len_ignored))
+        return FALSE;
+
+    if (!xdr_file_device(xdr, res_ok->device))
+        return FALSE;
+
+    return xdr_bitmap4(xdr, &res_ok->notification);
+}
+
+static bool_t decode_op_getdeviceinfo(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    pnfs_getdeviceinfo_res *res = (pnfs_getdeviceinfo_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_GETDEVICEINFO))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status))
+        return FALSE;
+
+    switch (res->status) {
+    case NFS4_OK:
+        return decode_getdeviceinfo_ok(xdr, &res->u.res_ok);
+        break;
+    case NFS4ERR_TOOSMALL:
+        {
+            uint32_t ignored;
+            return xdr_u_int32_t(xdr, &ignored);
+        }
+        break;
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_LAYOUTCOMMIT
+ */
+static bool_t encode_op_layoutcommit(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    pnfs_layoutcommit_args *args = (pnfs_layoutcommit_args*)argop->arg;
+    bool_t false_bool = FALSE;
+    bool_t true_bool = TRUE;
+    enum_t pnfs_layout = PNFS_LAYOUTTYPE_FILE;
+    uint32_t zero = 0;
+
+    if (unexpected_op(argop->op, OP_LAYOUTCOMMIT))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->length))
+        return FALSE;
+
+    if (!xdr_bool(xdr, &false_bool)) /* loca_reclaim = 0 */
+        return FALSE;
+
+    if (!xdr_stateid4(xdr, args->stateid))
+        return FALSE;
+
+    /* loca_last_write_offset */
+    if (args->new_offset) {
+        if (!xdr_bool(xdr, &true_bool))
+            return FALSE;
+
+        if (!xdr_u_hyper(xdr, args->new_offset))
+            return FALSE;
+    } else {
+        if (!xdr_bool(xdr, &false_bool))
+            return FALSE;
+    }
+
+    /* loca_time_modify */
+    if (args->new_time) {
+        if (!xdr_bool(xdr, &true_bool))
+            return FALSE;
+
+        if (!xdr_nfstime4(xdr, args->new_time))
+            return FALSE;
+    } else {
+        if (!xdr_bool(xdr, &false_bool))
+            return FALSE;
+    }
+
+    /* loca_layoutupdate */
+    if (!xdr_enum(xdr, &pnfs_layout))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &zero);
+}
+
+static bool_t decode_op_layoutcommit(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    pnfs_layoutcommit_res *res = (pnfs_layoutcommit_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LAYOUTCOMMIT))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK) {
+        if (!xdr_bool(xdr, &res->has_new_size))
+            return FALSE;
+
+        if (res->has_new_size)
+            if (!xdr_u_hyper(xdr, &res->new_size))
+                return FALSE;
+    }
+    return TRUE;
+}
+
+/*
+ * OP_LAYOUTGET
+ */
+static bool_t encode_op_layoutget(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    pnfs_layoutget_args *args = (pnfs_layoutget_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_LAYOUTGET))
+        return FALSE;
+
+    if (!xdr_bool(xdr, &args->signal_layout_avail))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, (u_int32_t *)&args->layout_type))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, (u_int32_t *)&args->iomode))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->offset))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->length))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &args->minlength))
+        return FALSE;
+
+    if (!xdr_stateid4(xdr, &args->stateid->stateid))
+        return FALSE;
+
+    return xdr_u_int32_t(xdr, &args->maxcount);
+}
+
+static bool_t decode_file_layout_handles(
+    XDR *xdr,
+    pnfs_file_layout_handles *handles)
+{
+    uint32_t i, count;
+
+    if (!xdr_u_int32_t(xdr, &count))
+        return FALSE;
+
+    if (count && count != handles->count) {
+        nfs41_path_fh *tmp;
+        tmp = realloc(handles->arr, count * sizeof(nfs41_path_fh));
+        if (tmp == NULL)
+            return FALSE;
+        handles->arr = tmp;
+        ZeroMemory(handles->arr, count * sizeof(nfs41_path_fh));
+        handles->count = count;
+    }
+    
+    for (i = 0; i < handles->count; i++) {
+        if (!xdr_fh(xdr, &handles->arr[i].fh))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static bool_t decode_file_layout(
+    XDR *xdr,
+    struct list_entry *list,
+    pnfs_layout *base)
+{
+    pnfs_file_layout *layout;
+    u_int32_t len_ignored;
+
+    if (!xdr_u_int32_t(xdr, &len_ignored))
+        return FALSE;
+
+    layout = calloc(1, sizeof(pnfs_file_layout));
+    if (layout == NULL)
+        return FALSE;
+
+    layout->layout.offset = base->offset;
+    layout->layout.length = base->length;
+    layout->layout.iomode = base->iomode;
+    layout->layout.type = base->type;
+    list_init(&layout->layout.entry);
+
+    if (!xdr_opaque(xdr, (char *)layout->deviceid, PNFS_DEVICEID_SIZE))
+        goto out_error;
+
+    if (!xdr_u_int32_t(xdr, &layout->util))
+        goto out_error;
+
+    if (!xdr_u_int32_t(xdr, &layout->first_index))
+        goto out_error;
+
+    if (!xdr_u_hyper(xdr, &layout->pattern_offset))
+        goto out_error;
+
+    if (!decode_file_layout_handles(xdr, &layout->filehandles))
+        goto out_error;
+
+    list_add_tail(list, &layout->layout.entry);
+    return TRUE;
+
+out_error:
+    free(layout);
+    return FALSE;
+}
+
+static bool_t decode_layout(
+    XDR *xdr,
+    struct list_entry *list)
+{
+    pnfs_layout layout;
+
+    if (!xdr_u_hyper(xdr, &layout.offset))
+        return FALSE;
+
+    if (!xdr_u_hyper(xdr, &layout.length))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&layout.iomode))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&layout.type))
+        return FALSE;
+
+    switch (layout.type) {
+    case PNFS_LAYOUTTYPE_FILE:
+        return decode_file_layout(xdr, list, &layout);
+
+    default:
+        eprintf("%s: received non-FILE layout type, %d\n",
+            "decode_file_layout", layout.type);
+    }
+    return FALSE;
+}
+
+static bool_t decode_layout_res_ok(
+    XDR *xdr,
+    pnfs_layoutget_res_ok *res)
+{
+    uint32_t i;
+
+    if (!xdr_bool(xdr, &res->return_on_close))
+        return FALSE;
+
+    if (!xdr_stateid4(xdr, &res->stateid))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &res->count))
+        return FALSE;
+
+    for (i = 0; i < res->count; i++)
+        if (!decode_layout(xdr, &res->layouts))
+            return FALSE;
+    return TRUE;
+}
+
+static bool_t decode_op_layoutget(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    pnfs_layoutget_res *res = (pnfs_layoutget_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LAYOUTGET))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status))
+        return FALSE;
+
+    switch (res->status) {
+    case NFS4_OK:
+        return decode_layout_res_ok(xdr, res->u.res_ok);
+    case NFS4ERR_LAYOUTTRYLATER:
+        return xdr_bool(xdr, &res->u.will_signal_layout_avail);
+    }
+    return TRUE;
+}
+
+
+/*
+ * OP_LAYOUTRETURN
+ */
+static bool_t encode_op_layoutreturn(
+    XDR *xdr,
+    nfs_argop4 *argop)
+{
+    pnfs_layoutreturn_args *args = (pnfs_layoutreturn_args*)argop->arg;
+
+    if (unexpected_op(argop->op, OP_LAYOUTRETURN))
+        return FALSE;
+
+    if (!xdr_bool(xdr, &args->reclaim))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&args->type))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&args->iomode))
+        return FALSE;
+
+    if (!xdr_enum(xdr, (enum_t *)&args->return_type))
+        return FALSE;
+
+    if (args->return_type == PNFS_RETURN_FILE) {
+        u_int32_t zero = 0;
+
+        if (!xdr_u_hyper(xdr, &args->offset))
+            return FALSE;
+
+        if (!xdr_u_hyper(xdr, &args->length))
+            return FALSE;
+
+        if (!xdr_stateid4(xdr, args->stateid))
+            return FALSE;
+
+        return xdr_u_int32_t(xdr, &zero); /* size of lrf_body is 0 */
+    } else {
+        eprintf("%s: layout type (%d) is not PNFS_RETURN_FILE!\n",
+            "encode_op_layoutreturn", args->return_type);
+        return FALSE;
+    }
+}
+
+static bool_t decode_op_layoutreturn(
+    XDR *xdr,
+    nfs_resop4 *resop)
+{
+    pnfs_layoutreturn_res *res = (pnfs_layoutreturn_res*)resop->res;
+
+    if (unexpected_op(resop->op, OP_LAYOUTRETURN))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status))
+        return FALSE;
+
+    if (res->status == NFS4_OK) {
+        if (!xdr_bool(xdr, &res->stateid_present))
+            return FALSE;
+
+        if (res->stateid_present)
+            return xdr_stateid4(xdr, &res->stateid);
+    }
+    return TRUE;
+}
+
+
+/* op encode/decode table */
+typedef bool_t (*nfs_op_encode_proc)(XDR*, nfs_argop4*);
+typedef bool_t (*nfs_op_decode_proc)(XDR*, nfs_resop4*);
+
+typedef struct __op_table_entry {
+    nfs_op_encode_proc      encode;
+    nfs_op_decode_proc      decode;
+} op_table_entry;
+
+/* table of encode/decode functions, indexed by operation number */
+static const op_table_entry g_op_table[] = {
+    { NULL, NULL }, /* 0 unused */
+    { NULL, NULL }, /* 1 unused */
+    { NULL, NULL }, /* 2 unused */
+    { encode_op_access, decode_op_access }, /* OP_ACCESS = 3 */
+    { encode_op_close, decode_op_close }, /* OP_CLOSE = 4 */
+    { encode_op_commit, decode_op_commit }, /* OP_COMMIT = 5 */
+    { encode_op_create, decode_op_create }, /* OP_CREATE = 6 */
+    { encode_op_delegpurge, decode_op_delegpurge }, /* OP_DELEGPURGE = 7 */
+    { encode_op_delegreturn, decode_op_delegreturn }, /* OP_DELEGRETURN = 8 */
+    { encode_op_getattr, decode_op_getattr }, /* OP_GETATTR = 9 */
+    { encode_op_getfh, decode_op_getfh }, /* OP_GETFH = 10 */
+    { encode_op_link, decode_op_link }, /* OP_LINK = 11 */
+    { encode_op_lock, decode_op_lock }, /* OP_LOCK = 12 */
+    { encode_op_lockt, decode_op_lockt }, /* OP_LOCKT = 13 */
+    { encode_op_locku, decode_op_locku }, /* OP_LOCKU = 14 */
+    { encode_op_lookup, decode_op_lookup }, /* OP_LOOKUP = 15 */
+    { NULL, NULL }, /* OP_LOOKUPP = 16 */
+    { NULL, NULL }, /* OP_NVERIFY = 17 */
+    { encode_op_open, decode_op_open }, /* OP_OPEN = 18 */
+    { encode_op_openattr, decode_op_openattr }, /* OP_OPENATTR = 19 */
+    { NULL, NULL }, /* OP_OPEN_CONFIRM = 20 */
+    { NULL, NULL }, /* OP_OPEN_DOWNGRADE = 21 */
+    { encode_op_putfh, decode_op_putfh }, /* OP_PUTFH = 22 */
+    { NULL, NULL }, /* OP_PUTPUBFH = 23 */
+    { encode_op_putrootfh, decode_op_putrootfh }, /* OP_PUTROOTFH = 24 */
+    { encode_op_read, decode_op_read }, /* OP_READ = 25 */
+    { encode_op_readdir, decode_op_readdir }, /* OP_READDIR = 26 */
+    { encode_op_readlink, decode_op_readlink }, /* OP_READLINK = 27 */
+    { encode_op_remove, decode_op_remove }, /* OP_REMOVE = 28 */
+    { encode_op_rename, decode_op_rename }, /* OP_RENAME = 29 */
+    { NULL, NULL }, /* OP_RENEW = 30 */
+    { encode_op_restorefh, decode_op_restorefh }, /* OP_RESTOREFH = 31 */
+    { encode_op_savefh, decode_op_savefh }, /* OP_SAVEFH = 32 */
+    { encode_op_secinfo, decode_op_secinfo }, /* OP_SECINFO = 33 */
+    { encode_op_setattr, decode_op_setattr }, /* OP_SETATTR = 34 */
+    { NULL, NULL }, /* OP_SETCLIENTID = 35 */
+    { NULL, NULL }, /* OP_SETCLIENTID_CONFIRM  = 36 */
+    { NULL, NULL }, /* OP_VERIFY = 37 */
+    { encode_op_write, decode_op_write }, /* OP_WRITE = 38 */
+    { NULL, NULL }, /* OP_RELEASE_LOCKOWNER = 39 */
+    { NULL, NULL }, /* OP_BACKCHANNEL_CTL = 40 */
+    { encode_op_bind_conn_to_session, decode_op_bind_conn_to_session }, /* OP_BIND_CONN_TO_SESSION = 41 */
+    { encode_op_exchange_id, decode_op_exchange_id }, /* OP_EXCHANGE_ID = 42 */
+    { encode_op_create_session, decode_op_create_session }, /* OP_CREATE_SESSION = 43 */
+    { encode_op_destroy_session, decode_op_destroy_session }, /* OP_DESTROY_SESSION = 44 */
+    { encode_op_free_stateid, decode_op_free_stateid }, /* OP_FREE_STATEID = 45 */
+    { NULL, NULL }, /* OP_GET_DIR_DELEGATION = 46 */
+    { encode_op_getdeviceinfo, decode_op_getdeviceinfo }, /* OP_GETDEVICEINFO = 47 */
+    { NULL, NULL }, /* OP_GETDEVICELIST = 48 */
+    { encode_op_layoutcommit, decode_op_layoutcommit }, /* OP_LAYOUTCOMMIT = 49 */
+    { encode_op_layoutget, decode_op_layoutget }, /* OP_LAYOUTGET = 50 */
+    { encode_op_layoutreturn, decode_op_layoutreturn }, /* OP_LAYOUTRETURN = 51 */
+    { encode_op_secinfo_noname, decode_op_secinfo_noname }, /* OP_SECINFO_NO_NAME = 52 */
+    { encode_op_sequence, decode_op_sequence }, /* OP_SEQUENCE = 53 */
+    { NULL, NULL }, /* OP_SET_SSV = 54 */
+    { encode_op_test_stateid, decode_op_test_stateid }, /* OP_TEST_STATEID = 55 */
+    { encode_op_want_delegation, decode_op_want_delegation }, /* OP_WANT_DELEGATION = 56 */
+    { encode_op_destroy_clientid, decode_op_destroy_clientid }, /* OP_DESTROY_CLIENTID = 57 */
+    { encode_op_reclaim_complete, decode_op_reclaim_complete }, /* OP_RECLAIM_COMPLETE = 58 */
+};
+#ifdef __REACTOS__
+static const uint32_t g_op_table_size = (sizeof(g_op_table) / sizeof(g_op_table[0]));
+#else
+static const uint32_t g_op_table_size = ARRAYSIZE(g_op_table);
+#endif
+
+static const op_table_entry* op_table_find(uint32_t op)
+{
+    return op >= g_op_table_size ? NULL : &g_op_table[op];
+}
+
+
+/*
+ * COMPOUND
+ */
+bool_t nfs_encode_compound(
+    XDR *xdr,
+    caddr_t *pargs)
+{
+    unsigned char *tag;
+
+    nfs41_compound_args *args = (nfs41_compound_args*)pargs;
+    uint32_t i;
+    const op_table_entry *entry;
+
+    tag = args->tag;
+    if (!xdr_bytes(xdr, (char **)&tag, &args->tag_len, NFS4_OPAQUE_LIMIT))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->minorversion))
+        return FALSE;
+
+    if (!xdr_u_int32_t(xdr, &args->argarray_count))
+        return FALSE;
+
+    for (i = 0; i < args->argarray_count; i++)
+    {
+        entry = op_table_find(args->argarray[i].op);
+        if (entry == NULL || entry->encode == NULL)
+            return FALSE;
+
+        if (!xdr_u_int32_t(xdr, &args->argarray[i].op))
+            return FALSE;
+        if (!entry->encode(xdr, &args->argarray[i]))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+bool_t nfs_decode_compound(
+    XDR *xdr,
+    caddr_t *pres)
+{
+    nfs41_compound_res *res = (nfs41_compound_res*)pres;
+    uint32_t i, expected_count, expected_op;
+    const op_table_entry *entry;
+    unsigned char *tag = res->tag;
+
+    if (!xdr_u_int32_t(xdr, &res->status))
+        return FALSE;
+
+    if (!xdr_bytes(xdr, (char **)&tag, &res->tag_len, NFS4_OPAQUE_LIMIT))
+        return FALSE;
+
+    expected_count = res->resarray_count;
+    if (!xdr_u_int32_t(xdr, &res->resarray_count))
+        return FALSE;
+
+    /* validate the number of operations against what we sent */
+    if (res->resarray_count > expected_count) {
+        eprintf("reply with %u operations, only sent %u!\n",
+            res->resarray_count, expected_count);
+        return FALSE;
+    } else if (res->resarray_count < expected_count &&
+        res->status == NFS4_OK) {
+        /* illegal for a server to reply with less operations,
+         * unless one of them fails */
+        eprintf("successful reply with only %u operations, sent %u!\n",
+            res->resarray_count, expected_count);
+        return FALSE;
+    }
+
+    for (i = 0; i < res->resarray_count; i++)
+    {
+        expected_op = res->resarray[i].op;
+        if (!xdr_u_int32_t(xdr, &res->resarray[i].op))
+            return FALSE;
+
+        /* validate each operation number against what we sent */
+        if (res->resarray[i].op != expected_op) {
+            eprintf("reply with %s in operation %u, expected %s!\n",
+                nfs_opnum_to_string(res->resarray[i].op), i+1,
+                nfs_opnum_to_string(expected_op));
+            return FALSE;
+        }
+
+        entry = op_table_find(res->resarray[i].op);
+        if (entry == NULL || entry->decode == NULL)
+            return FALSE;
+        if (!entry->decode(xdr, &res->resarray[i]))
+            return FALSE;
+    }
+    return TRUE;
+}
diff --git a/reactos/base/services/nfsd/nfs41_xdr.h b/reactos/base/services/nfsd/nfs41_xdr.h
new file mode 100644 (file)
index 0000000..ef8b374
--- /dev/null
@@ -0,0 +1,32 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_NFS_XDR_H__
+#define __NFS41_NFS_XDR_H__
+
+#include "nfs41_types.h"
+
+bool_t nfs_encode_compound(XDR *xdr, caddr_t *args);
+bool_t nfs_decode_compound(XDR *xdr, caddr_t *res);
+
+void nfsacl41_free(nfsacl41 *acl);
+
+#endif /* !__NFS41_NFS_XDR_H__ */
diff --git a/reactos/base/services/nfsd/nfsd.rc b/reactos/base/services/nfsd/nfsd.rc
new file mode 100644 (file)
index 0000000..684a802
--- /dev/null
@@ -0,0 +1,4 @@
+#define REACTOS_STR_FILE_DESCRIPTION  "NFS daemon"
+#define REACTOS_STR_INTERNAL_NAME     "nfsd"
+#define REACTOS_STR_ORIGINAL_FILENAME "nfsd.exe"
+#include <reactos/version.rc>
diff --git a/reactos/base/services/nfsd/open.c b/reactos/base/services/nfsd/open.c
new file mode 100644 (file)
index 0000000..c43d1d0
--- /dev/null
@@ -0,0 +1,948 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <strsafe.h>
+
+#include "nfs41_ops.h"
+#include "delegation.h"
+#include "from_kernel.h"
+#include "daemon_debug.h"
+#include "upcall.h"
+#include "util.h"
+
+
+static int create_open_state(
+    IN const char *path,
+    IN uint32_t open_owner_id,
+    OUT nfs41_open_state **state_out)
+{
+    int status;
+    nfs41_open_state *state;
+
+    state = calloc(1, sizeof(nfs41_open_state));
+    if (state == NULL) {
+        status = GetLastError();
+        goto out;
+    }
+
+    InitializeSRWLock(&state->path.lock);
+    if (FAILED(StringCchCopyA(state->path.path, NFS41_MAX_PATH_LEN, path))) {
+        status = ERROR_FILENAME_EXCED_RANGE;
+        goto out_free;
+    }
+    state->path.len = (unsigned short)strlen(state->path.path);
+    path_fh_init(&state->file, &state->path);
+    path_fh_init(&state->parent, &state->path);
+    last_component(state->path.path, state->file.name.name, &state->parent.name);
+
+    StringCchPrintfA((LPSTR)state->owner.owner, NFS4_OPAQUE_LIMIT, "%u", 
+        open_owner_id);
+    state->owner.owner_len = (uint32_t)strlen((const char*)state->owner.owner);
+    state->ref_count = 1;
+    list_init(&state->locks.list);
+    list_init(&state->client_entry);
+    InitializeCriticalSection(&state->locks.lock);
+
+    state->ea.list = INVALID_HANDLE_VALUE;
+    InitializeCriticalSection(&state->ea.lock);
+
+    *state_out = state;
+    status = NO_ERROR;
+out:
+    return status;
+
+out_free:
+    free(state);
+    goto out;
+}
+
+static void open_state_free(
+    IN nfs41_open_state *state)
+{
+    struct list_entry *entry, *tmp;
+
+    /* free associated lock state */
+    list_for_each_tmp(entry, tmp, &state->locks.list)
+        free(list_container(entry, nfs41_lock_state, open_entry));
+    if (state->delegation.state)
+        nfs41_delegation_deref(state->delegation.state);
+    if (state->ea.list != INVALID_HANDLE_VALUE)
+        free(state->ea.list);
+    free(state);
+}
+
+
+/* open state reference counting */
+void nfs41_open_state_ref(
+    IN nfs41_open_state *state)
+{
+    const LONG count = InterlockedIncrement(&state->ref_count);
+
+    dprintf(2, "nfs41_open_state_ref(%s) count %d\n", state->path.path, count);
+}
+
+void nfs41_open_state_deref(
+    IN nfs41_open_state *state)
+{
+    const LONG count = InterlockedDecrement(&state->ref_count);
+
+    dprintf(2, "nfs41_open_state_deref(%s) count %d\n", state->path.path, count);
+    if (count == 0)
+        open_state_free(state);
+}
+
+/* 8.2.5. Stateid Use for I/O Operations
+ * o  If the client holds a delegation for the file in question, the
+ *    delegation stateid SHOULD be used.
+ * o  Otherwise, if the entity corresponding to the lock-owner (e.g., a
+ *    process) sending the I/O has a byte-range lock stateid for the
+ *    associated open file, then the byte-range lock stateid for that
+ *    lock-owner and open file SHOULD be used.
+ * o  If there is no byte-range lock stateid, then the OPEN stateid for
+ *    the open file in question SHOULD be used.
+ * o  Finally, if none of the above apply, then a special stateid SHOULD
+ *    be used. */
+void nfs41_open_stateid_arg(
+    IN nfs41_open_state *state,
+    OUT stateid_arg *arg)
+{
+    arg->open = state;
+    arg->delegation = NULL;
+
+    AcquireSRWLockShared(&state->lock);
+
+    if (state->delegation.state) {
+        nfs41_delegation_state *deleg = state->delegation.state;
+        AcquireSRWLockShared(&deleg->lock);
+        if (deleg->status == DELEGATION_GRANTED) {
+            arg->type = STATEID_DELEG_FILE;
+            memcpy(&arg->stateid, &deleg->state.stateid, sizeof(stateid4));
+        }
+        ReleaseSRWLockShared(&deleg->lock);
+
+        if (arg->type == STATEID_DELEG_FILE)
+            goto out;
+
+        dprintf(2, "delegation recalled, waiting for open stateid..\n");
+
+        /* wait for nfs41_delegation_to_open() to recover open stateid */
+        while (!state->do_close)
+            SleepConditionVariableSRW(&state->delegation.cond, &state->lock,
+                INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED);
+    }
+
+    if (state->locks.stateid.seqid) {
+        memcpy(&arg->stateid, &state->locks.stateid, sizeof(stateid4));
+        arg->type = STATEID_LOCK;
+    } else if (state->do_close) {
+        memcpy(&arg->stateid, &state->stateid, sizeof(stateid4));
+        arg->type = STATEID_OPEN;
+    } else {
+        memset(&arg->stateid, 0, sizeof(stateid4));
+        arg->type = STATEID_SPECIAL;
+    }
+out:
+    ReleaseSRWLockShared(&state->lock);
+}
+
+/* client list of associated open state */
+static void client_state_add(
+    IN nfs41_open_state *state)
+{
+    nfs41_client *client = state->session->client;
+
+    EnterCriticalSection(&client->state.lock);
+    list_add_tail(&client->state.opens, &state->client_entry);
+    LeaveCriticalSection(&client->state.lock);
+}
+
+static void client_state_remove(
+    IN nfs41_open_state *state)
+{
+    nfs41_client *client = state->session->client;
+
+    EnterCriticalSection(&client->state.lock);
+    list_remove(&state->client_entry);
+    LeaveCriticalSection(&client->state.lock);
+}
+
+static int do_open(
+    IN OUT nfs41_open_state *state,
+    IN uint32_t create,
+    IN uint32_t createhow,
+    IN nfs41_file_info *createattrs,
+    IN bool_t try_recovery,
+    OUT nfs41_file_info *info)
+{
+    open_claim4 claim;
+    stateid4 open_stateid;
+    open_delegation4 delegation = { 0 };
+    nfs41_delegation_state *deleg_state = NULL;
+    int status;
+
+    claim.claim = CLAIM_NULL;
+    claim.u.null.filename = &state->file.name;
+
+    status = nfs41_open(state->session, &state->parent, &state->file,
+        &state->owner, &claim, state->share_access, state->share_deny,
+        create, createhow, createattrs, TRUE, &open_stateid,
+        &delegation, info);
+    if (status)
+        goto out;
+
+    /* allocate delegation state and register it with the client */
+    nfs41_delegation_granted(state->session, &state->parent,
+        &state->file, &delegation, TRUE, &deleg_state);
+    if (deleg_state) {
+        deleg_state->srv_open = state->srv_open;
+        dprintf(1, "do_open: received delegation: saving srv_open = %x\n", 
+            state->srv_open);
+    }
+
+    AcquireSRWLockExclusive(&state->lock);
+    /* update the stateid */
+    memcpy(&state->stateid, &open_stateid, sizeof(open_stateid));
+    state->do_close = 1;
+    state->delegation.state = deleg_state;
+    ReleaseSRWLockExclusive(&state->lock);
+out:
+    return status;
+}
+
+static int open_or_delegate(
+    IN OUT nfs41_open_state *state,
+    IN uint32_t create,
+    IN uint32_t createhow,
+    IN nfs41_file_info *createattrs,
+    IN bool_t try_recovery,
+    OUT nfs41_file_info *info)
+{
+    int status;
+
+    /* check for existing delegation */
+    status = nfs41_delegate_open(state, create, createattrs, info);
+
+    /* get an open stateid if we have no delegation stateid */
+    if (status)
+        status = do_open(state, create, createhow,
+            createattrs, try_recovery, info);
+
+    state->pnfs_last_offset = info->size ? info->size - 1 : 0;
+
+    /* register the client's open state on success */
+    if (status == NFS4_OK)
+        client_state_add(state);
+    return status;
+}
+
+
+static int parse_abs_path(unsigned char **buffer, uint32_t *length, nfs41_abs_path *path)
+{
+    int status = safe_read(buffer, length, &path->len, sizeof(USHORT));
+    if (status) goto out;
+    if (path->len == 0)
+        goto out;
+    if (path->len >= NFS41_MAX_PATH_LEN) {
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out;
+    }
+    status = safe_read(buffer, length, path->path, path->len);
+    if (status) goto out;
+    path->len--; /* subtract 1 for null */
+out:
+    return status;
+}
+
+/* NFS41_OPEN */
+static int parse_open(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    open_upcall_args *args = &upcall->args.open;
+
+    status = get_name(&buffer, &length, &args->path);
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->access_mask, sizeof(ULONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->access_mode, sizeof(ULONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->file_attrs, sizeof(ULONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->create_opts, sizeof(ULONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->disposition, sizeof(ULONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->open_owner_id, sizeof(LONG));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->mode, sizeof(DWORD));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->srv_open, sizeof(HANDLE));
+    if (status) goto out;
+    status = parse_abs_path(&buffer, &length, &args->symlink);
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->ea, sizeof(HANDLE));
+    if (status) goto out;
+
+    dprintf(1, "parsing NFS41_OPEN: filename='%s' access mask=%d "
+        "access mode=%d\n\tfile attrs=0x%x create attrs=0x%x "
+        "(kernel) disposition=%d\n\topen_owner_id=%d mode=%o "
+        "srv_open=%p symlink=%s ea=%p\n", args->path, args->access_mask,
+        args->access_mode, args->file_attrs, args->create_opts,
+        args->disposition, args->open_owner_id, args->mode, args->srv_open,
+        args->symlink.path, args->ea);
+    print_disposition(2, args->disposition);
+    print_access_mask(2, args->access_mask);
+    print_share_mode(2, args->access_mode);
+    print_create_attributes(2, args->create_opts);
+out:
+    return status;
+}
+
+static BOOLEAN open_for_attributes(uint32_t type, ULONG access_mask, 
+                                   ULONG disposition, int status)
+{
+    if (type == NF4DIR) {
+        if (disposition == FILE_OPEN || disposition == FILE_OVERWRITE ||
+                (!status && (disposition == FILE_OPEN_IF || 
+                    disposition == FILE_OVERWRITE_IF || 
+                    disposition == FILE_SUPERSEDE))) {
+            dprintf(1, "Opening a directory\n");
+            return TRUE;
+        } else {
+            dprintf(1, "Creating a directory\n");
+            return FALSE;
+        }
+    }
+
+    if ((access_mask & FILE_READ_DATA) ||
+            (access_mask & FILE_WRITE_DATA) ||
+            (access_mask & FILE_APPEND_DATA) ||
+            (access_mask & FILE_EXECUTE) ||
+            disposition == FILE_CREATE ||
+            disposition == FILE_OVERWRITE_IF ||
+            disposition == FILE_SUPERSEDE ||
+            disposition == FILE_OPEN_IF ||
+            disposition == FILE_OVERWRITE)
+        return FALSE;
+    else {
+        dprintf(1, "Open call that wants to manage attributes\n");
+        return TRUE;
+    }
+}
+
+static int map_disposition_2_nfsopen(ULONG disposition, int in_status, bool_t persistent,
+                                     uint32_t *create, uint32_t *createhowmode,
+                                     uint32_t *last_error)
+{
+    int status = NO_ERROR;
+    if (disposition == FILE_SUPERSEDE) {
+        if (in_status == NFS4ERR_NOENT)           
+            *last_error = ERROR_FILE_NOT_FOUND;
+        //remove and recreate the file
+        *create = OPEN4_CREATE;
+        if (persistent) *createhowmode = GUARDED4;
+        else *createhowmode = EXCLUSIVE4_1;
+    } else if (disposition == FILE_CREATE) {
+        // if lookup succeeded which means the file exist, return an error
+        if (!in_status)
+            status = ERROR_FILE_EXISTS;
+        else {
+            *create = OPEN4_CREATE;
+            if (persistent) *createhowmode = GUARDED4;
+            else *createhowmode = EXCLUSIVE4_1;
+        }
+    } else if (disposition == FILE_OPEN) {
+        if (in_status == NFS4ERR_NOENT)
+            status = ERROR_FILE_NOT_FOUND;
+        else
+            *create = OPEN4_NOCREATE;
+    } else if (disposition == FILE_OPEN_IF) {
+        if (in_status == NFS4ERR_NOENT) {
+            dprintf(1, "creating new file\n");
+            *create = OPEN4_CREATE;
+            *last_error = ERROR_FILE_NOT_FOUND;
+        } else {
+            dprintf(1, "opening existing file\n");
+            *create = OPEN4_NOCREATE;
+        }
+    } else if (disposition == FILE_OVERWRITE) {
+        if (in_status == NFS4ERR_NOENT)
+            status = ERROR_FILE_NOT_FOUND;
+        //truncate file
+        *create = OPEN4_CREATE;
+    } else if (disposition == FILE_OVERWRITE_IF) {
+        if (in_status == NFS4ERR_NOENT)
+            *last_error = ERROR_FILE_NOT_FOUND;
+        //truncate file
+        *create = OPEN4_CREATE;
+    }
+    return status;
+}
+
+static void map_access_2_allowdeny(ULONG access_mask, ULONG access_mode,
+                                   ULONG disposition, uint32_t *allow, uint32_t *deny)
+{
+    if ((access_mask & 
+            (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES)) &&
+            (access_mask & (FILE_READ_DATA | FILE_EXECUTE)))
+        *allow = OPEN4_SHARE_ACCESS_BOTH;
+    else if (access_mask & (FILE_READ_DATA | FILE_EXECUTE))
+        *allow = OPEN4_SHARE_ACCESS_READ;
+    else if (access_mask & 
+                (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES))
+        *allow = OPEN4_SHARE_ACCESS_WRITE;
+    /* if we are creating a file and no data access is specified, then 
+     * do an open and request no delegations. example open with share access 0
+     * and share deny 0 (ie deny_both).
+     */
+    if ((disposition == FILE_CREATE || disposition == FILE_OPEN_IF || 
+            disposition == FILE_OVERWRITE_IF || disposition == FILE_SUPERSEDE ||
+            disposition == FILE_OVERWRITE) &&
+            !(access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA | 
+            FILE_WRITE_ATTRIBUTES | FILE_READ_DATA | FILE_EXECUTE)))
+        *allow = OPEN4_SHARE_ACCESS_READ | OPEN4_SHARE_ACCESS_WANT_NO_DELEG;
+
+#define FIX_ALLOW_DENY_WIN2NFS_CONVERSION
+#ifdef FIX_ALLOW_DENY_WIN2NFS_CONVERSION
+    if ((access_mode & FILE_SHARE_READ) &&
+            (access_mode & FILE_SHARE_WRITE))
+        *deny = OPEN4_SHARE_DENY_NONE;
+    else if (access_mode & FILE_SHARE_READ)
+        *deny = OPEN4_SHARE_DENY_WRITE;
+    else if (access_mode & FILE_SHARE_WRITE)
+        *deny = OPEN4_SHARE_DENY_READ;
+    else
+        *deny = OPEN4_SHARE_DENY_BOTH;
+#else
+    // AGLO: 11/13/2009.
+    // readonly file that is being opened for reading with a
+    // share read mode given above logic translates into deny
+    // write and linux server does not allow it.
+    *deny = OPEN4_SHARE_DENY_NONE;
+#endif
+}
+
+static int check_execute_access(nfs41_open_state *state)
+{
+    uint32_t supported, access;
+    int status = nfs41_access(state->session, &state->file,
+        ACCESS4_EXECUTE | ACCESS4_READ, &supported, &access);
+    if (status) {
+        eprintf("nfs41_access() failed with %s for %s\n", 
+            nfs_error_string(status), state->path.path);
+        status = ERROR_ACCESS_DENIED;
+    } else if ((supported & ACCESS4_EXECUTE) == 0) {
+        /* server can't verify execute access;
+         * for now, assume that read access is good enough */
+        if ((supported & ACCESS4_READ) == 0 || (access & ACCESS4_READ) == 0) {
+            eprintf("server can't verify execute access, and user does "
+                "not have read access to file %s\n", state->path.path);
+            status = ERROR_ACCESS_DENIED;
+        }
+    } else if ((access & ACCESS4_EXECUTE) == 0) {
+        dprintf(1, "user does not have execute access to file %s\n", 
+            state->path.path);
+        status = ERROR_ACCESS_DENIED;
+    } else
+        dprintf(2, "user has execute access to file\n");
+    return status;
+}
+
+static int create_with_ea(
+    IN uint32_t disposition,
+    IN uint32_t lookup_status)
+{
+    /* only set EAs on file creation */
+    return disposition == FILE_SUPERSEDE || disposition == FILE_CREATE
+        || disposition == FILE_OVERWRITE || disposition == FILE_OVERWRITE_IF
+        || (disposition == FILE_OPEN_IF && lookup_status == NFS4ERR_NOENT);
+}
+
+static int handle_open(nfs41_upcall *upcall)
+{
+    int status = 0;
+    open_upcall_args *args = &upcall->args.open;
+    nfs41_open_state *state;
+    nfs41_file_info info = { 0 };
+
+    status = create_open_state(args->path, args->open_owner_id, &state);
+    if (status) {
+        eprintf("create_open_state(%d) failed with %d\n",
+            args->open_owner_id, status);
+        goto out;
+    }
+    state->srv_open = args->srv_open;
+
+    // first check if windows told us it's a directory
+    if (args->create_opts & FILE_DIRECTORY_FILE)
+        state->type = NF4DIR;
+    else
+        state->type = NF4REG;
+
+    // always do a lookup
+    status = nfs41_lookup(upcall->root_ref, nfs41_root_session(upcall->root_ref),
+        &state->path, &state->parent, &state->file, &info, &state->session);
+
+    if (status == ERROR_REPARSE) {
+        uint32_t depth = 0;
+        /* one of the parent components was a symlink */
+        do {
+            if (++depth > NFS41_MAX_SYMLINK_DEPTH) {
+                status = ERROR_TOO_MANY_LINKS;
+                goto out_free_state;
+            }
+
+            /* replace the path with the symlink target's */
+            status = nfs41_symlink_target(state->session,
+                &state->parent, &state->path);
+            if (status) {
+                /* can't do the reparse if we can't get the target */
+                eprintf("nfs41_symlink_target() failed with %d\n", status);
+                goto out_free_state;
+            }
+
+            /* redo the lookup until it doesn't return REPARSE */
+            status = nfs41_lookup(upcall->root_ref, state->session,
+                &state->path, &state->parent, NULL, NULL, &state->session);
+        } while (status == ERROR_REPARSE);
+
+        if (status == NO_ERROR || status == ERROR_FILE_NOT_FOUND) {
+            abs_path_copy(&args->symlink, &state->path);
+            status = NO_ERROR;
+            upcall->last_error = ERROR_REPARSE;
+            args->symlink_embedded = TRUE;
+        }
+        goto out_free_state;
+    }
+
+    // now if file/dir exists, use type returned by lookup
+    if (status == NO_ERROR) {
+        if (info.type == NF4DIR) {
+            dprintf(2, "handle_nfs41_open: DIRECTORY\n");
+            if (args->create_opts & FILE_NON_DIRECTORY_FILE) {
+                eprintf("trying to open directory %s as a file\n", 
+                    state->path.path);
+                status = ERROR_DIRECTORY;
+                goto out_free_state;
+            }
+        } else if (info.type == NF4REG) {
+            dprintf(2, "handle nfs41_open: FILE\n");
+            if (args->create_opts & FILE_DIRECTORY_FILE) {
+                eprintf("trying to open file %s as a directory\n",
+                    state->path.path);
+                status = ERROR_BAD_FILE_TYPE;
+                goto out_free_state;
+            }
+        } else if (info.type == NF4LNK) {
+            dprintf(2, "handle nfs41_open: SYMLINK\n");
+            if (args->create_opts & FILE_OPEN_REPARSE_POINT) {
+                /* continue and open the symlink itself, but we need to
+                 * know if the target is a regular file or directory */
+                nfs41_file_info target_info;
+                int target_status = nfs41_symlink_follow(upcall->root_ref,
+                    state->session, &state->file, &target_info);
+                if (target_status == NO_ERROR && target_info.type == NF4DIR)
+                    info.symlink_dir = TRUE;
+            } else {
+                /* replace the path with the symlink target */
+                status = nfs41_symlink_target(state->session,
+                    &state->file, &args->symlink);
+                if (status) {
+                    eprintf("nfs41_symlink_target() for %s failed with %d\n",
+                        args->path, status);
+                } else {
+                    /* tell the driver to call RxPrepareToReparseSymbolicLink() */
+                    upcall->last_error = ERROR_REPARSE;
+                    args->symlink_embedded = FALSE;
+                }
+                goto out_free_state;
+            }
+        } else
+            dprintf(2, "handle_open(): unsupported type=%d\n", info.type);
+        state->type = info.type;
+    } else if (status != ERROR_FILE_NOT_FOUND)
+        goto out_free_state;
+
+    /* XXX: this is a hard-coded check for the open arguments we see from
+     * the CreateSymbolicLink() system call.  we respond to this by deferring
+     * the CREATE until we get the upcall to set the symlink.  this approach
+     * is troublesome for two reasons:
+     * -an application might use these exact arguments to create a normal
+     *   file, and we would return success without actually creating it
+     * -an application could create a symlink by sending the FSCTL to set
+     *   the reparse point manually, and their open might be different.  in
+     *   this case we'd create the file on open, and need to remove it
+     *   before creating the symlink */
+    if (args->disposition == FILE_CREATE &&
+            args->access_mask == (FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | DELETE) &&
+            args->access_mode == 0 &&
+            args->create_opts & FILE_OPEN_REPARSE_POINT) {
+        /* fail if the file already exists */
+        if (status == NO_ERROR) {
+            status = ERROR_FILE_EXISTS;
+            goto out_free_state;
+        }
+
+        /* defer the call to CREATE until we get the symlink set upcall */
+        dprintf(1, "trying to create a symlink, deferring create\n");
+
+        /* because of WRITE_ATTR access, be prepared for a setattr upcall;
+         * will crash if the superblock is null, so use the parent's */
+        state->file.fh.superblock = state->parent.fh.superblock;
+
+        status = NO_ERROR;
+    } else if (args->symlink.len) {
+        /* handle cygwin symlinks */
+        nfs41_file_info createattrs;
+        createattrs.attrmask.count = 2;
+        createattrs.attrmask.arr[0] = 0;
+        createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE;
+        createattrs.mode = 0777;
+
+        dprintf(1, "creating cygwin symlink %s -> %s\n",
+            state->file.name.name, args->symlink.path);
+
+        status = nfs41_create(state->session, NF4LNK, &createattrs,
+            args->symlink.path, &state->parent, &state->file, &info);
+        if (status) {
+            eprintf("nfs41_create() for symlink=%s failed with %s\n",
+                args->symlink.path, nfs_error_string(status));
+            status = map_symlink_errors(status);
+            goto out_free_state;
+        }
+        nfs_to_basic_info(&info, &args->basic_info);
+        nfs_to_standard_info(&info, &args->std_info);
+        args->mode = info.mode;
+        args->changeattr = info.change;
+    } else if (open_for_attributes(state->type, args->access_mask, 
+                args->disposition, status)) {
+        if (status) {
+            dprintf(1, "nfs41_lookup failed with %d\n", status);
+            goto out_free_state;
+        }
+
+        nfs_to_basic_info(&info, &args->basic_info);
+        nfs_to_standard_info(&info, &args->std_info);
+        args->mode = info.mode;
+        args->changeattr = info.change;
+    } else {
+        nfs41_file_info createattrs = { 0 };
+        uint32_t create = 0, createhowmode = 0, lookup_status = status;
+
+        if (!lookup_status && (args->disposition == FILE_OVERWRITE || 
+                args->disposition == FILE_OVERWRITE_IF || 
+                args->disposition == FILE_SUPERSEDE)) {
+            if ((info.hidden && !(args->file_attrs & FILE_ATTRIBUTE_HIDDEN)) ||
+                    (info.system && !(args->file_attrs & FILE_ATTRIBUTE_SYSTEM))) {
+                status = ERROR_ACCESS_DENIED;
+                goto out_free_state;
+            }
+            if (args->disposition != FILE_SUPERSEDE)
+                args->mode = info.mode;
+        }
+        createattrs.attrmask.count = 2;
+        createattrs.attrmask.arr[0] = FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE;
+        createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE | FATTR4_WORD1_SYSTEM;
+        createattrs.mode = args->mode;
+        createattrs.hidden = args->file_attrs & FILE_ATTRIBUTE_HIDDEN ? 1 : 0;
+        createattrs.system = args->file_attrs & FILE_ATTRIBUTE_SYSTEM ? 1 : 0;
+        createattrs.archive = args->file_attrs & FILE_ATTRIBUTE_ARCHIVE ? 1 : 0;
+
+        map_access_2_allowdeny(args->access_mask, args->access_mode,
+            args->disposition, &state->share_access, &state->share_deny);
+        status = map_disposition_2_nfsopen(args->disposition, status, 
+                    state->session->flags & CREATE_SESSION4_FLAG_PERSIST, 
+                    &create, &createhowmode, &upcall->last_error);
+        if (status)
+            goto out_free_state;
+
+        if (args->access_mask & FILE_EXECUTE && state->file.fh.len) {
+            status = check_execute_access(state);
+            if (status)
+                goto out_free_state;
+        }
+
+supersede_retry:
+        // XXX file exists and we have to remove it first
+        if (args->disposition == FILE_SUPERSEDE && lookup_status == NO_ERROR) {
+            nfs41_component *name = &state->file.name;
+            if (!(args->create_opts & FILE_DIRECTORY_FILE))
+                nfs41_delegation_return(state->session, &state->file,
+                    OPEN_DELEGATE_WRITE, TRUE);
+
+            dprintf(1, "open for FILE_SUPERSEDE removing %s first\n", name->name);
+            status = nfs41_remove(state->session, &state->parent,
+                name, state->file.fh.fileid);
+            if (status)
+                goto out_free_state;
+        }
+
+        if (create == OPEN4_CREATE && (args->create_opts & FILE_DIRECTORY_FILE)) {
+            status = nfs41_create(state->session, NF4DIR, &createattrs, NULL, 
+                &state->parent, &state->file, &info);
+            args->created = status == NFS4_OK ? TRUE : FALSE;
+        } else {
+            createattrs.attrmask.arr[0] |= FATTR4_WORD0_SIZE;
+            createattrs.size = 0;
+            dprintf(1, "creating with mod %o\n", args->mode);
+            status = open_or_delegate(state, create, createhowmode, &createattrs, 
+                TRUE, &info);
+            if (status == NFS4_OK && state->delegation.state)
+                    args->deleg_type = state->delegation.state->state.type;
+        }
+        if (status) {
+            dprintf(1, "%s failed with %s\n", (create == OPEN4_CREATE && 
+                (args->create_opts & FILE_DIRECTORY_FILE))?"nfs41_create":"nfs41_open",
+                nfs_error_string(status));
+            if (args->disposition == FILE_SUPERSEDE && status == NFS4ERR_EXIST)
+                goto supersede_retry;
+            status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND);
+            goto out_free_state;
+        } else {
+            nfs_to_basic_info(&info, &args->basic_info);
+            nfs_to_standard_info(&info, &args->std_info);
+            args->mode = info.mode;
+            args->changeattr = info.change;
+        }
+
+        /* set extended attributes on file creation */
+        if (args->ea && create_with_ea(args->disposition, lookup_status)) {
+            status = nfs41_ea_set(state, args->ea);
+            status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND);
+        }
+    }
+
+    upcall->state_ref = state;
+    nfs41_open_state_ref(upcall->state_ref);
+out:
+    return status;
+out_free_state:
+    nfs41_open_state_deref(state);
+    goto out;
+}
+
+static int marshall_open(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    int status;
+    open_upcall_args *args = &upcall->args.open;
+
+    status = safe_write(&buffer, length, &args->basic_info, sizeof(args->basic_info));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->std_info, sizeof(args->std_info));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &upcall->state_ref, sizeof(HANDLE));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->mode, sizeof(args->mode));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->changeattr, sizeof(args->changeattr));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->deleg_type, sizeof(args->deleg_type));
+    if (status) goto out;
+    if (upcall->last_error == ERROR_REPARSE) {
+        unsigned short len = (args->symlink.len + 1) * sizeof(WCHAR);
+        status = safe_write(&buffer, length, &args->symlink_embedded, sizeof(BOOLEAN));
+        if (status) goto out;
+        status = safe_write(&buffer, length, &len, sizeof(len));
+        if (status) goto out;
+        /* convert args->symlink to wchar */
+        if (*length <= len || !MultiByteToWideChar(CP_UTF8, 0,
+            args->symlink.path, args->symlink.len,
+            (LPWSTR)buffer, len / sizeof(WCHAR))) {
+            status = ERROR_BUFFER_OVERFLOW;
+            goto out;
+        }
+    }
+    dprintf(2, "NFS41_OPEN: downcall open_state=0x%p mode %o changeattr 0x%llu\n", 
+        upcall->state_ref, args->mode, args->changeattr);
+out:
+    return status;
+}
+
+static void cancel_open(IN nfs41_upcall *upcall)
+{
+    int status = NFS4_OK;
+    open_upcall_args *args = &upcall->args.open;
+    nfs41_open_state *state = upcall->state_ref;
+
+    dprintf(1, "--> cancel_open('%s')\n", args->path);
+
+    if (upcall->state_ref == NULL || 
+            upcall->state_ref == INVALID_HANDLE_VALUE)
+        goto out; /* if handle_open() failed, the state was already freed */
+
+    if (state->do_close) {
+        stateid_arg stateid;
+        stateid.open = state;
+        stateid.delegation = NULL;
+        stateid.type = STATEID_OPEN;
+        memcpy(&stateid.stateid, &state->stateid, sizeof(stateid4));
+
+        status = nfs41_close(state->session, &state->file, &stateid);
+        if (status)
+            dprintf(1, "cancel_open: nfs41_close() failed with %s\n",
+                nfs_error_string(status));
+
+    } else if (args->created) {
+        const nfs41_component *name = &state->file.name;
+        /* break any delegations and truncate before REMOVE */
+        nfs41_delegation_return(state->session, &state->file,
+            OPEN_DELEGATE_WRITE, TRUE);
+        status = nfs41_remove(state->session, &state->parent,
+            name, state->file.fh.fileid);
+        if (status)
+            dprintf(1, "cancel_open: nfs41_remove() failed with %s\n",
+                nfs_error_string(status));
+    }
+
+    /* remove from the client's list of state for recovery */
+    client_state_remove(state);
+    nfs41_open_state_deref(state);
+out:
+    status = nfs_to_windows_error(status, ERROR_INTERNAL_ERROR);
+    dprintf(1, "<-- cancel_open() returning %d\n", status);
+}
+
+
+/* NFS41_CLOSE */
+static int parse_close(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    close_upcall_args *args = &upcall->args.close;
+
+    status = safe_read(&buffer, &length, &args->remove, sizeof(BOOLEAN));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->srv_open, sizeof(HANDLE));
+    if (status) goto out;
+    if (args->remove) {
+        status = get_name(&buffer, &length, &args->path);
+        if (status) goto out;
+        status = safe_read(&buffer, &length, &args->renamed, sizeof(BOOLEAN));
+        if (status) goto out;
+    }
+
+    dprintf(1, "parsing NFS41_CLOSE: remove=%d srv_open=%x renamed=%d "
+        "filename='%s'\n", args->remove, args->srv_open, args->renamed, 
+        args->remove ? args->path : "");
+out:
+    return status;
+}
+
+static int do_nfs41_close(nfs41_open_state *state)
+{
+    int status;
+    stateid_arg stateid;
+    stateid.open = state;
+    stateid.delegation = NULL;
+    stateid.type = STATEID_OPEN;
+    memcpy(&stateid.stateid, &state->stateid, sizeof(stateid4));
+
+    status = nfs41_close(state->session, &state->file, &stateid);
+    if (status) {
+        dprintf(1, "nfs41_close() failed with error %s.\n",
+            nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_INTERNAL_ERROR);
+    }
+
+    return status;
+}
+
+static int handle_close(nfs41_upcall *upcall)
+{
+    int status = NFS4_OK, rm_status = NFS4_OK;
+    close_upcall_args *args = &upcall->args.close;
+    nfs41_open_state *state = upcall->state_ref;
+
+    /* return associated file layouts if necessary */
+    if (state->type == NF4REG)
+        pnfs_layout_state_close(state->session, state, args->remove);
+
+    if (state->srv_open == args->srv_open)
+        nfs41_delegation_remove_srvopen(state->session, &state->file);
+
+    if (args->remove) {
+        nfs41_component *name = &state->file.name;
+
+        if (args->renamed) {
+            dprintf(1, "removing a renamed file %s\n", name->name);
+            create_silly_rename(&state->path, &state->file.fh, name);
+            status = do_nfs41_close(state);
+            if (status)
+                goto out;
+            else
+                state->do_close = 0;
+        }
+
+        /* break any delegations and truncate before REMOVE */
+        nfs41_delegation_return(state->session, &state->file,
+            OPEN_DELEGATE_WRITE, TRUE);
+
+               dprintf(1, "calling nfs41_remove for %s\n", name->name);
+retry_delete:
+        rm_status = nfs41_remove(state->session, &state->parent,
+            name, state->file.fh.fileid);
+        if (rm_status) {
+                       if (rm_status == NFS4ERR_FILE_OPEN) {
+                               status = do_nfs41_close(state);
+                               if (!status) {
+                                       state->do_close = 0;
+                                       goto retry_delete;
+                               }  else goto out;
+                       }
+            dprintf(1, "nfs41_remove() failed with error %s.\n",
+                nfs_error_string(rm_status));
+            rm_status = nfs_to_windows_error(rm_status, ERROR_INTERNAL_ERROR);
+        }
+    }
+
+    if (state->do_close) {
+        status = do_nfs41_close(state);
+    }
+out:
+    /* remove from the client's list of state for recovery */
+    client_state_remove(state);
+
+    if (status || !rm_status)
+        return status;
+    else
+        return rm_status;
+}
+
+static void cleanup_close(nfs41_upcall *upcall)
+{
+    /* release the initial reference from create_open_state() */
+    nfs41_open_state_deref(upcall->state_ref);
+}
+
+
+const nfs41_upcall_op nfs41_op_open = {
+    parse_open,
+    handle_open,
+    marshall_open,
+    cancel_open
+};
+const nfs41_upcall_op nfs41_op_close = {
+    parse_close,
+    handle_close,
+    NULL,
+    NULL,
+    cleanup_close
+};
diff --git a/reactos/base/services/nfsd/pnfs.h b/reactos/base/services/nfsd/pnfs.h
new file mode 100644 (file)
index 0000000..afa870b
--- /dev/null
@@ -0,0 +1,368 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 __PNFS_H__
+#define __PNFS_H__
+
+#include "nfs41_types.h"
+#include "list.h"
+
+
+/* preprocessor options */
+#ifndef PNFS_DISABLE
+
+# ifndef PNFS_DISABLE_READ
+#  define PNFS_ENABLE_READ
+# endif
+# ifndef PNFS_DISABLE_WRITE
+#  define PNFS_ENABLE_WRITE
+# endif
+
+# define PNFS_THREADING
+
+/* XXX: the thread-by-server model breaks down when using dense layouts,
+ * because multiple stripes could be mapped to a single data server, and
+ * the per-data-server thread would have to send a COMMIT for each stripe */
+//# define PNFS_THREAD_BY_SERVER
+
+#endif
+
+
+/* forward declarations from nfs41.h */
+struct __nfs41_client;
+struct __nfs41_session;
+struct __nfs41_open_state;
+struct __nfs41_root;
+struct __stateid_arg;
+
+
+/* pnfs error values, in order of increasing severity */
+enum pnfs_status {
+    PNFS_SUCCESS            = 0,
+    PNFS_PENDING,
+    PNFS_READ_EOF,
+    PNFSERR_NOT_SUPPORTED,
+    PNFSERR_NOT_CONNECTED,
+    PNFSERR_IO,
+    PNFSERR_NO_DEVICE,
+    PNFSERR_NO_LAYOUT,
+    PNFSERR_INVALID_FH_LIST,
+    PNFSERR_INVALID_DS_INDEX,
+    PNFSERR_RESOURCES,
+    PNFSERR_LAYOUT_RECALLED,
+    PNFSERR_LAYOUT_CHANGED,
+};
+
+enum pnfs_layout_type {
+    PNFS_LAYOUTTYPE_FILE    = 1,
+    PNFS_LAYOUTTYPE_OBJECT  = 2,
+    PNFS_LAYOUTTYPE_BLOCK   = 3
+};
+
+enum pnfs_iomode {
+    PNFS_IOMODE_READ        = 0x1,
+    PNFS_IOMODE_RW          = 0x2,
+    PNFS_IOMODE_ANY         = PNFS_IOMODE_READ | PNFS_IOMODE_RW
+};
+
+enum pnfs_layout_status {
+    /* a LAYOUTGET error indicated that this layout will never be granted */
+    PNFS_LAYOUT_UNAVAILABLE = 0x10,
+    /* LAYOUTGET returned BADIOMODE, so a RW layout will never be granted */
+    PNFS_LAYOUT_NOT_RW      = 0x20,
+};
+
+enum pnfs_device_status {
+    /* GETDEVICEINFO was successful */
+    PNFS_DEVICE_GRANTED     = 0x1,
+    /* a bulk recall or lease expiration led to device invalidation */
+    PNFS_DEVICE_REVOKED     = 0x2,
+};
+
+enum pnfs_return_type {
+    PNFS_RETURN_FILE        = 1,
+    PNFS_RETURN_FSID        = 2,
+    PNFS_RETURN_ALL         = 3
+};
+
+#define NFL4_UFLG_MASK                  0x0000003F
+#define NFL4_UFLG_DENSE                 0x00000001
+#define NFL4_UFLG_COMMIT_THRU_MDS       0x00000002
+#define NFL4_UFLG_STRIPE_UNIT_SIZE_MASK 0xFFFFFFC0
+
+#define PNFS_DEVICEID_SIZE              16
+
+
+/* device */
+typedef struct __pnfs_device {
+    unsigned char           deviceid[PNFS_DEVICEID_SIZE];
+    enum pnfs_layout_type   type;
+    enum pnfs_device_status status;
+    uint32_t                layout_count; /* layouts using this device */
+    CRITICAL_SECTION        lock;
+} pnfs_device;
+
+typedef struct __pnfs_stripe_indices {
+    uint32_t                count;
+    uint32_t                *arr;
+} pnfs_stripe_indices;
+
+typedef struct __pnfs_data_server {
+    struct __nfs41_client   *client;
+    multi_addr4             addrs;
+    SRWLOCK                 lock;
+} pnfs_data_server;
+
+typedef struct __pnfs_data_server_list {
+    uint32_t                count;
+    pnfs_data_server        *arr;
+} pnfs_data_server_list;
+
+typedef struct __pnfs_file_device {
+    pnfs_device             device;
+    pnfs_stripe_indices     stripes;
+    pnfs_data_server_list   servers;
+    struct pnfs_file_device_list *devices; /* -> nfs41_client.devices */
+    struct list_entry       entry; /* position in devices */
+} pnfs_file_device;
+
+
+/* layout */
+typedef struct __pnfs_layout_state {
+    nfs41_fh                meta_fh;
+    stateid4                stateid;
+    struct list_entry       entry; /* position in nfs41_client.layouts */
+    struct list_entry       layouts; /* list of pnfs_file_layouts */
+    struct list_entry       recalls; /* list of pnfs_layouts */
+    enum pnfs_layout_status status;
+    bool_t                  return_on_close;
+    LONG                    open_count; /* for return on last close */
+    uint32_t                io_count; /* number of pending io operations */
+    bool_t                  pending; /* pending LAYOUTGET/LAYOUTRETURN */
+    SRWLOCK                 lock;
+    CONDITION_VARIABLE      cond;
+} pnfs_layout_state;
+
+typedef struct __pnfs_layout {
+    struct list_entry       entry;
+    uint64_t                offset;
+    uint64_t                length;
+    enum pnfs_iomode        iomode;
+    enum pnfs_layout_type   type;
+} pnfs_layout;
+
+typedef struct __pnfs_file_layout_handles {
+    uint32_t                count;
+    nfs41_path_fh           *arr;
+} pnfs_file_layout_handles;
+
+typedef struct __pnfs_file_layout {
+    pnfs_layout             layout;
+    pnfs_file_layout_handles filehandles;
+    unsigned char           deviceid[PNFS_DEVICEID_SIZE];
+    pnfs_file_device        *device;
+    uint64_t                pattern_offset;
+    uint32_t                first_index;
+    uint32_t                util;
+} pnfs_file_layout;
+
+
+/* pnfs_layout.c */
+struct pnfs_layout_list;
+struct cb_layoutrecall_args;
+
+enum pnfs_status pnfs_layout_list_create(
+    OUT struct pnfs_layout_list **layouts_out);
+
+void pnfs_layout_list_free(
+    IN struct pnfs_layout_list *layouts);
+
+enum pnfs_status pnfs_layout_state_open(
+    IN struct __nfs41_open_state *state,
+    OUT pnfs_layout_state **layout_out);
+
+/* expects caller to hold an exclusive lock on pnfs_layout_state */
+enum pnfs_status pnfs_layout_state_prepare(
+    IN pnfs_layout_state *state,
+    IN struct __nfs41_session *session,
+    IN nfs41_path_fh *meta_file,
+    IN struct __stateid_arg *stateid,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length);
+
+void pnfs_layout_state_close(
+    IN struct __nfs41_session *session,
+    IN struct __nfs41_open_state *state,
+    IN bool_t remove);
+
+enum pnfs_status pnfs_file_layout_recall(
+    IN struct __nfs41_client *client,
+    IN const struct cb_layoutrecall_args *recall);
+
+/* expects caller to hold a shared lock on pnfs_layout_state */
+enum pnfs_status pnfs_layout_recall_status(
+    IN const pnfs_layout_state *state,
+    IN const pnfs_layout *layout);
+
+void pnfs_layout_recall_fenced(
+    IN pnfs_layout_state *state,
+    IN const pnfs_layout *layout);
+
+/* expects caller to hold an exclusive lock on pnfs_layout_state */
+void pnfs_layout_io_start(
+    IN pnfs_layout_state *state);
+
+void pnfs_layout_io_finished(
+    IN pnfs_layout_state *state);
+
+
+/* pnfs_device.c */
+struct pnfs_file_device_list;
+
+enum pnfs_status pnfs_file_device_list_create(
+    OUT struct pnfs_file_device_list **devices_out);
+
+void pnfs_file_device_list_free(
+    IN struct pnfs_file_device_list *devices);
+
+void pnfs_file_device_list_invalidate(
+    IN struct pnfs_file_device_list *devices);
+
+enum pnfs_status pnfs_file_device_get(
+    IN struct __nfs41_session *session,
+    IN struct pnfs_file_device_list *devices,
+    IN unsigned char *deviceid,
+    OUT pnfs_file_device **device_out);
+
+void pnfs_file_device_put(
+    IN pnfs_file_device *device);
+
+struct notify_deviceid4; /* from nfs41_callback.h */
+enum notify_deviceid_type4;
+enum pnfs_status pnfs_file_device_notify(
+    IN struct pnfs_file_device_list *devices,
+    IN const struct notify_deviceid4 *change);
+
+enum pnfs_status pnfs_data_server_client(
+    IN struct __nfs41_root *root,
+    IN pnfs_data_server *server,
+    IN uint32_t default_lease,
+    OUT struct __nfs41_client **client_out);
+
+
+/* pnfs_io.c */
+enum pnfs_status pnfs_read(
+    IN struct __nfs41_root *root,
+    IN struct __nfs41_open_state *state,
+    IN struct __stateid_arg *stateid,
+    IN pnfs_layout_state *layout,
+    IN uint64_t offset,
+    IN uint64_t length,
+    OUT unsigned char *buffer_out,
+    OUT ULONG *len_out);
+
+enum pnfs_status pnfs_write(
+    IN struct __nfs41_root *root,
+    IN struct __nfs41_open_state *state,
+    IN struct __stateid_arg *stateid,
+    IN pnfs_layout_state *layout,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN unsigned char *buffer,
+    OUT ULONG *len_out,
+    OUT nfs41_file_info *cinfo);
+
+
+/* helper functions */
+#ifndef __REACTOS__
+__inline int is_dense(
+#else
+FORCEINLINE int is_dense(
+#endif
+    IN const pnfs_file_layout *layout)
+{
+    return (layout->util & NFL4_UFLG_DENSE) != 0;
+}
+#ifndef __REACTOS__
+__inline int should_commit_to_mds(
+#else
+FORCEINLINE int should_commit_to_mds(
+#endif
+    IN const pnfs_file_layout *layout)
+{
+    return (layout->util & NFL4_UFLG_COMMIT_THRU_MDS) != 0;
+}
+#ifndef __REACTOS__
+__inline uint32_t layout_unit_size(
+#else
+FORCEINLINE uint32_t layout_unit_size(
+#endif
+    IN const pnfs_file_layout *layout)
+{
+    return layout->util & NFL4_UFLG_STRIPE_UNIT_SIZE_MASK;
+}
+#ifndef __REACTOS__
+__inline uint64_t stripe_unit_number(
+#else
+FORCEINLINE uint64_t stripe_unit_number(
+#endif
+    IN const pnfs_file_layout *layout,
+    IN uint64_t offset,
+    IN uint32_t unit_size)
+{
+    const uint64_t relative_offset = offset - layout->pattern_offset;
+    return relative_offset / unit_size;
+}
+#ifndef __REACTOS__
+__inline uint64_t stripe_unit_offset(
+#else
+FORCEINLINE uint64_t stripe_unit_offset(
+#endif
+    IN const pnfs_file_layout *layout,
+    IN uint64_t sui,
+    IN uint32_t unit_size)
+{
+    return layout->pattern_offset + unit_size * sui;
+}
+#ifndef __REACTOS__
+__inline uint32_t stripe_index(
+#else
+FORCEINLINE uint32_t stripe_index(
+#endif
+    IN const pnfs_file_layout *layout,
+    IN uint64_t sui,
+    IN uint32_t stripe_count)
+{
+    return (uint32_t)((sui + layout->first_index) % stripe_count);
+}
+#ifndef __REACTOS__
+__inline uint32_t data_server_index(
+#else
+FORCEINLINE uint32_t data_server_index(
+#endif
+    IN const pnfs_file_device *device,
+    IN uint32_t stripeid)
+{
+    return device->stripes.arr[stripeid];
+}
+
+#endif /* !__PNFS_H__ */
diff --git a/reactos/base/services/nfsd/pnfs_debug.c b/reactos/base/services/nfsd/pnfs_debug.c
new file mode 100644 (file)
index 0000000..2c07d00
--- /dev/null
@@ -0,0 +1,123 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <winsock2.h>
+#include <strsafe.h>
+#include "pnfs.h"
+#include "daemon_debug.h"
+
+
+const char* pnfs_error_string(enum pnfs_status status)
+{
+    switch (status) {
+    case PNFS_SUCCESS:          return "PNFS_SUCCESS";
+    case PNFS_PENDING:          return "PNFS_PENDING";
+    case PNFS_READ_EOF:         return "PNFS_READ_EOF";
+    case PNFSERR_NOT_SUPPORTED: return "PNFSERR_NOT_SUPPORTED";
+    case PNFSERR_NOT_CONNECTED: return "PNFSERR_NOT_CONNECTED";
+    case PNFSERR_IO:            return "PNFSERR_IO";
+    case PNFSERR_NO_DEVICE:     return "PNFSERR_NO_DEVICE";
+    case PNFSERR_NO_LAYOUT:     return "PNFSERR_NO_LAYOUT";
+    case PNFSERR_INVALID_FH_LIST: return "PNFSERR_INVALID_FH_LIST";
+    case PNFSERR_INVALID_DS_INDEX: return "PNFSERR_INVALID_DS_INDEX";
+    case PNFSERR_RESOURCES:     return "PNFSERR_RESOURCES";
+    case PNFSERR_LAYOUT_RECALLED: return "PNFSERR_LAYOUT_RECALLED";
+    case PNFSERR_LAYOUT_CHANGED: return "PNFSERR_LAYOUT_CHANGED";
+    default:                    return "Invalid pnfs status";
+    }
+}
+
+const char* pnfs_layout_type_string(enum pnfs_layout_type type)
+{
+    switch (type) {
+    case PNFS_LAYOUTTYPE_FILE:  return "PNFS_LAYOUTTYPE_FILE";
+    case PNFS_LAYOUTTYPE_OBJECT: return "PNFS_LAYOUTTYPE_OBJECT";
+    case PNFS_LAYOUTTYPE_BLOCK: return "PNFS_LAYOUTTYPE_BLOCK";
+    default:                    return "Invalid layout type";
+    }
+}
+
+const char* pnfs_iomode_string(enum pnfs_iomode iomode)
+{
+    switch (iomode) {
+    case PNFS_IOMODE_READ:      return "PNFS_IOMODE_READ";
+    case PNFS_IOMODE_RW:        return "PNFS_IOMODE_RW";
+    case PNFS_IOMODE_ANY:       return "PNFS_IOMODE_ANY";
+    default:                    return "Invalid io mode";
+    }
+}
+
+void dprint_deviceid(
+    IN int level,
+    IN const char *title,
+    IN const unsigned char *deviceid)
+{
+    /* deviceid is 16 bytes, so print it as 4 uints */
+    uint32_t *p = (uint32_t*)deviceid;
+    dprintf(level, "%s%08X.%08X.%08X.%08X\n",
+        title, htonl(p[0]), htonl(p[1]), htonl(p[2]), htonl(p[3]));
+}
+
+void dprint_layout(
+    IN int level,
+    IN const pnfs_file_layout *layout)
+{
+    dprintf(level, "  type:             %s\n", pnfs_layout_type_string(layout->layout.type));
+    dprintf(level, "  iomode:           %s\n", pnfs_iomode_string(layout->layout.iomode));
+    dprint_deviceid(level, "  deviceid:         ", layout->deviceid);
+    dprintf(level, "  offset:           %llu\n", layout->layout.offset);
+    dprintf(level, "  length:           %llu\n", layout->layout.length);
+    dprintf(level, "  pattern_offset:   %llu\n", layout->pattern_offset);
+    dprintf(level, "  first_index:      %u\n", layout->first_index);
+    dprintf(level, "  dense:            %u\n", is_dense(layout));
+    dprintf(level, "  commit_to_mds:    %u\n", should_commit_to_mds(layout));
+    dprintf(level, "  stripe_unit_size: %u\n", layout_unit_size(layout));
+    dprintf(level, "  file handles:     %u\n", layout->filehandles.count);
+}
+
+#define MULTI_ADDR_BUFFER_LEN \
+    (NFS41_ADDRS_PER_SERVER*(NFS41_UNIVERSAL_ADDR_LEN+1)+1)
+
+static void dprint_multi_addr(
+    IN int level,
+    IN uint32_t index,
+    IN const multi_addr4 *addrs)
+{
+    char buffer[MULTI_ADDR_BUFFER_LEN] = "";
+    uint32_t i;
+    for (i = 0; i < addrs->count; i++) {
+        StringCchCatA(buffer, MULTI_ADDR_BUFFER_LEN, addrs->arr[i].uaddr);
+        StringCchCatA(buffer, MULTI_ADDR_BUFFER_LEN, " ");
+    }
+    dprintf(level, "  servers[%d]:       [ %s]\n", index, buffer);
+}
+
+void dprint_device(
+    IN int level,
+    IN const pnfs_file_device *device)
+{
+    uint32_t i;
+    dprint_deviceid(level, "  deviceid:         ", device->device.deviceid);
+    dprintf(level, "  type:             %s\n", pnfs_layout_type_string(device->device.type));
+    dprintf(level, "  stripes:          %u\n", device->stripes.count);
+    for (i = 0; i < device->servers.count; i++)
+        dprint_multi_addr(level, i, &device->servers.arr[i].addrs);
+}
diff --git a/reactos/base/services/nfsd/pnfs_device.c b/reactos/base/services/nfsd/pnfs_device.c
new file mode 100644 (file)
index 0000000..425da20
--- /dev/null
@@ -0,0 +1,365 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+
+#include "nfs41_ops.h"
+#include "nfs41_callback.h"
+#include "daemon_debug.h"
+
+
+#define FDLVL 2 /* dprintf level for file device logging */
+
+
+/* pnfs_file_device_list */
+struct pnfs_file_device_list {
+    struct list_entry       head;
+    CRITICAL_SECTION        lock;
+};
+
+#define device_entry(pos) list_container(pos, pnfs_file_device, entry)
+
+
+static enum pnfs_status file_device_create(
+    IN const unsigned char *deviceid,
+    IN struct pnfs_file_device_list *devices,
+    OUT pnfs_file_device **device_out)
+{
+    enum pnfs_status status = PNFS_SUCCESS;
+    pnfs_file_device *device;
+
+    device = calloc(1, sizeof(pnfs_file_device));
+    if (device == NULL) {
+        status = PNFSERR_RESOURCES;
+        goto out;
+    }
+
+    memcpy(device->device.deviceid, deviceid, PNFS_DEVICEID_SIZE);
+    device->devices = devices;
+    InitializeCriticalSection(&device->device.lock);
+    *device_out = device;
+out:
+    return status;
+}
+
+static void file_device_free(
+    IN pnfs_file_device *device)
+{
+    free(device->servers.arr);
+    free(device->stripes.arr);
+    DeleteCriticalSection(&device->device.lock);
+    free(device);
+}
+
+static int deviceid_compare(
+    const struct list_entry *entry,
+    const void *deviceid)
+{
+    const pnfs_file_device *device = device_entry(entry);
+    return memcmp(device->device.deviceid, deviceid, PNFS_DEVICEID_SIZE);
+}
+
+static enum pnfs_status file_device_find_or_create(
+    IN const unsigned char *deviceid,
+    IN struct pnfs_file_device_list *devices,
+    OUT pnfs_file_device **device_out)
+{
+    struct list_entry *entry;
+    enum pnfs_status status;
+
+    dprintf(FDLVL, "--> pnfs_file_device_find_or_create()\n");
+
+    EnterCriticalSection(&devices->lock);
+
+    /* search for an existing device */
+    entry = list_search(&devices->head, deviceid, deviceid_compare);
+    if (entry == NULL) {
+        /* create a new device */
+        pnfs_file_device *device;
+        status = file_device_create(deviceid, devices, &device);
+        if (status == PNFS_SUCCESS) {
+            /* add it to the list */
+            list_add_tail(&devices->head, &device->entry);
+            *device_out = device;
+
+            dprintf(FDLVL, "<-- pnfs_file_device_find_or_create() "
+                "returning new device %p\n", device);
+        } else {
+            dprintf(FDLVL, "<-- pnfs_file_device_find_or_create() "
+                "returning %s\n", pnfs_error_string(status));
+        }
+    } else {
+        *device_out = device_entry(entry);
+        status = PNFS_SUCCESS;
+
+        dprintf(FDLVL, "<-- pnfs_file_device_find_or_create() "
+            "returning existing device %p\n", *device_out);
+    }
+
+    LeaveCriticalSection(&devices->lock);
+    return status;
+}
+
+
+enum pnfs_status pnfs_file_device_list_create(
+    OUT struct pnfs_file_device_list **devices_out)
+{
+    enum pnfs_status status = PNFS_SUCCESS;
+    struct pnfs_file_device_list *devices;
+
+    devices = calloc(1, sizeof(struct pnfs_file_device_list));
+    if (devices == NULL) {
+        status = PNFSERR_RESOURCES;
+        goto out;
+    }
+
+    list_init(&devices->head);
+    InitializeCriticalSection(&devices->lock);
+
+    *devices_out = devices;
+out:
+    return status;
+}
+
+void pnfs_file_device_list_free(
+    IN struct pnfs_file_device_list *devices)
+{
+    struct list_entry *entry, *tmp;
+
+    EnterCriticalSection(&devices->lock);
+
+    list_for_each_tmp(entry, tmp, &devices->head)
+        file_device_free(device_entry(entry));
+
+    LeaveCriticalSection(&devices->lock);
+    DeleteCriticalSection(&devices->lock);
+    free(devices);
+}
+
+void pnfs_file_device_list_invalidate(
+    IN struct pnfs_file_device_list *devices)
+{
+    struct list_entry *entry, *tmp;
+    pnfs_file_device *device;
+
+    dprintf(FDLVL, "--> pnfs_file_device_list_invalidate()\n");
+
+    EnterCriticalSection(&devices->lock);
+
+    list_for_each_tmp(entry, tmp, &devices->head) {
+        device = device_entry(entry);
+        EnterCriticalSection(&device->device.lock);
+        /* if there are layouts still using the device, flag it
+         * as revoked and clean up on last reference */
+        if (device->device.layout_count) {
+            device->device.status |= PNFS_DEVICE_REVOKED;
+            LeaveCriticalSection(&device->device.lock);
+        } else {
+            LeaveCriticalSection(&device->device.lock);
+            /* no layouts are using it, so it's safe to free */
+            list_remove(entry);
+            file_device_free(device);
+        }
+    }
+
+    LeaveCriticalSection(&devices->lock);
+
+    dprintf(FDLVL, "<-- pnfs_file_device_list_invalidate()\n");
+}
+
+
+/* pnfs_file_device */
+enum pnfs_status pnfs_file_device_get(
+    IN nfs41_session *session,
+    IN struct pnfs_file_device_list *devices,
+    IN unsigned char *deviceid,
+    OUT pnfs_file_device **device_out)
+{
+    pnfs_file_device *device;
+    enum pnfs_status status;
+    enum nfsstat4 nfsstat;
+
+    dprintf(FDLVL, "--> pnfs_file_device_get()\n");
+
+    status = file_device_find_or_create(deviceid, devices, &device);
+    if (status)
+        goto out;
+
+    EnterCriticalSection(&device->device.lock);
+
+    /* don't give out a device that's been revoked */
+    if (device->device.status & PNFS_DEVICE_REVOKED)
+        status = PNFSERR_NO_DEVICE;
+    else if (device->device.status & PNFS_DEVICE_GRANTED)
+        status = PNFS_SUCCESS;
+    else {
+        nfsstat = pnfs_rpc_getdeviceinfo(session, deviceid, device);
+        if (nfsstat == NFS4_OK) {
+            device->device.status = PNFS_DEVICE_GRANTED;
+            status = PNFS_SUCCESS;
+
+            dprintf(FDLVL, "Received device info:\n");
+            dprint_device(FDLVL, device);
+        } else {
+            status = PNFSERR_NO_DEVICE;
+
+            eprintf("pnfs_rpc_getdeviceinfo() failed with %s\n",
+                nfs_error_string(nfsstat));
+        }
+    }
+
+    if (status == PNFS_SUCCESS) {
+        device->device.layout_count++;
+        dprintf(FDLVL, "pnfs_file_device_get() -> %u\n",
+            device->device.layout_count);
+        *device_out = device;
+    }
+
+    LeaveCriticalSection(&device->device.lock);
+out:
+    dprintf(FDLVL, "<-- pnfs_file_device_get() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+void pnfs_file_device_put(
+    IN pnfs_file_device *device)
+{
+    uint32_t count;
+    EnterCriticalSection(&device->device.lock);
+    count = --device->device.layout_count;
+    dprintf(FDLVL, "pnfs_file_device_put() -> %u\n", count);
+
+    /* if the device was revoked, remove/free the device on last reference */
+    if (count == 0 && device->device.status & PNFS_DEVICE_REVOKED) {
+        EnterCriticalSection(&device->devices->lock);
+        list_remove(&device->entry);
+        LeaveCriticalSection(&device->devices->lock);
+
+        LeaveCriticalSection(&device->device.lock);
+
+        file_device_free(device);
+        dprintf(FDLVL, "revoked file device freed after last reference\n");
+    } else {
+        LeaveCriticalSection(&device->device.lock);
+    }
+}
+
+static enum pnfs_status data_client_status(
+    IN pnfs_data_server *server,
+    OUT nfs41_client **client_out)
+{
+    enum pnfs_status status = PNFSERR_NOT_CONNECTED;
+
+    if (server->client) {
+        dprintf(FDLVL, "pnfs_data_server_client() returning "
+            "existing client %llu\n", server->client->clnt_id);
+        *client_out = server->client;
+        status = PNFS_SUCCESS;
+    }
+    return status;
+}
+
+enum pnfs_status pnfs_data_server_client(
+    IN nfs41_root *root,
+    IN pnfs_data_server *server,
+    IN uint32_t default_lease,
+    OUT nfs41_client **client_out)
+{
+    int status;
+    enum pnfs_status pnfsstat;
+
+    dprintf(FDLVL, "--> pnfs_data_server_client('%s')\n",
+        server->addrs.arr[0].uaddr);
+
+    /* if we've already created the client, return it */
+    AcquireSRWLockShared(&server->lock);
+    pnfsstat = data_client_status(server, client_out);
+    ReleaseSRWLockShared(&server->lock);
+
+    if (!pnfsstat)
+        goto out;
+
+    AcquireSRWLockExclusive(&server->lock);
+
+    pnfsstat = data_client_status(server, client_out);
+    if (pnfsstat) {
+        status = nfs41_root_mount_addrs(root, &server->addrs, 1, default_lease, 
+            &server->client);
+        if (status) {
+            dprintf(FDLVL, "data_client_create('%s') failed with %d\n",
+                server->addrs.arr[0].uaddr, status);
+        } else {
+            *client_out = server->client;
+            pnfsstat = PNFS_SUCCESS;
+
+            dprintf(FDLVL, "pnfs_data_server_client() returning new client "
+                "%llu\n", server->client->clnt_id);
+        }
+    }
+
+    ReleaseSRWLockExclusive(&server->lock);
+out:
+    return pnfsstat;
+}
+
+
+/* CB_NOTIFY_DEVICEID */
+enum pnfs_status pnfs_file_device_notify(
+    IN struct pnfs_file_device_list *devices,
+    IN const struct notify_deviceid4 *change)
+{
+    struct list_entry *entry;
+    enum pnfs_status status = PNFSERR_NO_DEVICE;
+
+    dprintf(FDLVL, "--> pnfs_file_device_notify(%u, %0llX:%0llX)\n",
+        change->type, change->deviceid);
+
+    if (change->layouttype != PNFS_LAYOUTTYPE_FILE) {
+        status = PNFSERR_NOT_SUPPORTED;
+        goto out;
+    }
+
+    EnterCriticalSection(&devices->lock);
+
+    entry = list_search(&devices->head, change->deviceid, deviceid_compare);
+    if (entry) {
+        dprintf(FDLVL, "found file device %p\n", device_entry(entry));
+
+        if (change->type == NOTIFY_DEVICEID4_CHANGE) {
+            /* if (change->immediate) ... */
+            dprintf(FDLVL, "CHANGE (%u)\n", change->immediate);
+        } else if (change->type == NOTIFY_DEVICEID4_DELETE) {
+            /* This notification MUST NOT be sent if the client
+             * has a layout that refers to the device ID. */
+            dprintf(FDLVL, "DELETE\n");
+        }
+        status = PNFS_SUCCESS;
+    }
+
+    LeaveCriticalSection(&devices->lock);
+out:
+    dprintf(FDLVL, "<-- pnfs_file_device_notify() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
diff --git a/reactos/base/services/nfsd/pnfs_io.c b/reactos/base/services/nfsd/pnfs_io.c
new file mode 100644 (file)
index 0000000..8ca7df6
--- /dev/null
@@ -0,0 +1,871 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <stdio.h>
+#include <process.h>
+
+#include "nfs41_ops.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+#define IOLVL 2 /* dprintf level for pnfs io logging */
+
+#define file_layout_entry(pos) list_container(pos, pnfs_file_layout, layout.entry)
+
+typedef struct __pnfs_io_pattern {
+    struct __pnfs_io_thread *threads;
+    nfs41_root              *root;
+    nfs41_path_fh           *meta_file;
+    const stateid_arg       *stateid;
+    pnfs_layout_state       *state;
+    unsigned char           *buffer;
+    uint64_t                offset_start;
+    uint64_t                offset_end;
+    uint32_t                count;
+    uint32_t                default_lease;
+} pnfs_io_pattern;
+
+typedef struct __pnfs_io_thread {
+    nfs41_write_verf        verf;
+    pnfs_io_pattern         *pattern;
+    pnfs_file_layout        *layout;
+    nfs41_path_fh           *file;
+    uint64_t                offset;
+    uint32_t                id;
+    enum stable_how4        stable;
+} pnfs_io_thread;
+
+typedef struct __pnfs_io_unit {
+    unsigned char           *buffer;
+    uint64_t                offset;
+    uint64_t                length;
+    uint32_t                stripeid;
+    uint32_t                serverid;
+} pnfs_io_unit;
+
+typedef uint32_t (WINAPI *pnfs_io_thread_fn)(void*);
+
+
+static enum pnfs_status stripe_next_unit(
+    IN const pnfs_file_layout *layout,
+    IN uint32_t stripeid,
+    IN uint64_t *position,
+    IN uint64_t offset_end,
+    OUT pnfs_io_unit *io);
+
+/* 13.4.2. Interpreting the File Layout Using Sparse Packing
+ * http://tools.ietf.org/html/rfc5661#section-13.4.2 */
+
+static enum pnfs_status get_sparse_fh(
+    IN pnfs_file_layout *layout,
+    IN nfs41_path_fh *meta_file,
+    IN uint32_t stripeid,
+    OUT nfs41_path_fh **file_out)
+{
+    const uint32_t filehandle_count = layout->filehandles.count;
+    const uint32_t server_count = layout->device->servers.count;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    if (filehandle_count == server_count) {
+        const uint32_t serverid = data_server_index(layout->device, stripeid);
+        *file_out = &layout->filehandles.arr[serverid];
+    } else if (filehandle_count == 1) {
+        *file_out = &layout->filehandles.arr[0];
+    } else if (filehandle_count == 0) {
+        *file_out = meta_file;
+    } else {
+        eprintf("invalid sparse layout! has %u file handles "
+            "and %u servers\n", filehandle_count, server_count);
+        status = PNFSERR_INVALID_FH_LIST;
+    }
+    return status;
+}
+
+/* 13.4.3. Interpreting the File Layout Using Dense Packing
+* http://tools.ietf.org/html/rfc5661#section-13.4.3 */
+
+static enum pnfs_status get_dense_fh(
+    IN pnfs_file_layout *layout,
+    IN uint32_t stripeid,
+    OUT nfs41_path_fh **file_out)
+{
+    const uint32_t filehandle_count = layout->filehandles.count;
+    const uint32_t stripe_count = layout->device->stripes.count;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    if (filehandle_count == stripe_count) {
+        *file_out = &layout->filehandles.arr[stripeid];
+    } else {
+        eprintf("invalid dense layout! has %u file handles "
+            "and %u stripes\n", filehandle_count, stripe_count);
+        status = PNFSERR_INVALID_FH_LIST;
+    }
+    return status;
+}
+
+static __inline bool_t layout_compatible(
+    IN const pnfs_layout *layout,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t position)
+{
+    return layout->iomode >= iomode
+        && layout->offset <= position
+        && position < layout->offset + layout->length;
+}
+
+/* count stripes for all layout segments that intersect the range
+ * and have not been covered by previous segments */
+static uint32_t thread_count(
+    IN pnfs_layout_state *state,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length)
+{
+    uint64_t position = offset;
+    struct list_entry *entry;
+    uint32_t count = 0;
+
+    list_for_each(entry, &state->layouts) {
+        pnfs_file_layout *layout = file_layout_entry(entry);
+
+        if (!layout_compatible(&layout->layout, iomode, position))
+            continue;
+
+        position = layout->layout.offset + layout->layout.length;
+        count += layout->device->stripes.count;
+    }
+    return count;
+}
+
+static enum pnfs_status thread_init(
+    IN pnfs_io_pattern *pattern,
+    IN pnfs_io_thread *thread,
+    IN pnfs_file_layout *layout,
+    IN uint32_t stripeid,
+    IN uint64_t offset)
+{
+    thread->pattern = pattern;
+    thread->layout = layout;
+    thread->stable = FILE_SYNC4;
+    thread->offset = offset;
+    thread->id = stripeid;
+
+    return is_dense(layout) ? get_dense_fh(layout, stripeid, &thread->file)
+        : get_sparse_fh(layout, pattern->meta_file, stripeid, &thread->file);
+}
+
+static enum pnfs_status pattern_threads_init(
+    IN pnfs_io_pattern *pattern,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length)
+{
+    pnfs_io_unit io;
+    uint64_t position = offset;
+    struct list_entry *entry;
+    uint32_t s, t = 0;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    list_for_each(entry, &pattern->state->layouts) {
+        pnfs_file_layout *layout = file_layout_entry(entry);
+
+        if (!layout_compatible(&layout->layout, iomode, position))
+            continue;
+
+        for (s = 0; s < layout->device->stripes.count; s++) {
+            uint64_t off = position;
+
+            /* does the range contain this stripe? */
+            status = stripe_next_unit(layout, s, &off, offset + length, &io);
+            if (status != PNFS_PENDING)
+                continue;
+
+            if (t >= pattern->count) { /* miscounted threads needed? */
+                status = PNFSERR_NO_LAYOUT;
+                goto out;
+            }
+
+            status = thread_init(pattern, &pattern->threads[t++], layout, s, off);
+            if (status)
+                goto out;
+        }
+        position = layout->layout.offset + layout->layout.length;
+    }
+
+    if (position < offset + length) {
+        /* unable to satisfy the entire range */
+        status = PNFSERR_NO_LAYOUT;
+        goto out;
+    }
+
+    /* update the pattern with the actual number of threads used */
+    pattern->count = t;
+out:
+    return status;
+}
+
+static enum pnfs_status pattern_init(
+    IN pnfs_io_pattern *pattern,
+    IN nfs41_root *root,
+    IN nfs41_path_fh *meta_file,
+    IN const stateid_arg *stateid,
+    IN pnfs_layout_state *state,
+    IN unsigned char *buffer,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN uint32_t default_lease)
+{
+    enum pnfs_status status;
+
+    /* calculate an upper bound on the number of threads to allocate */
+    pattern->count = thread_count(state, iomode, offset, length);
+    pattern->threads = calloc(pattern->count, sizeof(pnfs_io_thread));
+    if (pattern->threads == NULL) {
+        status = PNFSERR_RESOURCES;
+        goto out;
+    }
+
+    /* information shared between threads */
+    pattern->root = root;
+    pattern->meta_file = meta_file;
+    pattern->stateid = stateid;
+    pattern->state = state;
+    pattern->buffer = buffer;
+    pattern->offset_start = offset;
+    pattern->offset_end = offset + length;
+    pattern->default_lease = default_lease;
+
+    /* initialize a thread for every stripe necessary to cover the range */
+    status = pattern_threads_init(pattern, iomode, offset, length);
+    if (status)
+        goto out_err_free;
+
+    /* take a reference on the layout so we don't return it during io */
+    pnfs_layout_io_start(state);
+out:
+    return status;
+
+out_err_free:
+    free(pattern->threads);
+    pattern->threads = NULL;
+    goto out;
+}
+
+static void pattern_free(
+    IN pnfs_io_pattern *pattern)
+{
+    /* inform the layout that our io is finished */
+    pnfs_layout_io_finished(pattern->state);
+    free(pattern->threads);
+}
+
+static __inline uint64_t positive_remainder(
+    IN uint64_t dividend,
+    IN uint32_t divisor)
+{
+    const uint64_t remainder = dividend % divisor;
+    return remainder < divisor ? remainder : remainder + divisor;
+}
+
+/* return the next unit of the given stripeid */
+static enum pnfs_status stripe_next_unit(
+    IN const pnfs_file_layout *layout,
+    IN uint32_t stripeid,
+    IN uint64_t *position,
+    IN uint64_t offset_end,
+    OUT pnfs_io_unit *io)
+{
+    const uint32_t unit_size = layout_unit_size(layout);
+    const uint32_t stripe_count = layout->device->stripes.count;
+    uint64_t sui = stripe_unit_number(layout, *position, unit_size);
+
+    /* advance to the desired stripeid */
+    sui += abs(stripeid - stripe_index(layout, sui, stripe_count));
+
+    io->offset = stripe_unit_offset(layout, sui, unit_size);
+    if (io->offset < *position) /* don't start before position */
+        io->offset = *position;
+    else
+        *position = io->offset;
+
+    io->length = stripe_unit_offset(layout, sui + 1, unit_size);
+    if (io->length > offset_end) /* don't end past offset_end */
+        io->length = offset_end;
+
+    if (io->offset >= io->length) /* nothing to do, return success */
+        return PNFS_SUCCESS;
+
+    io->length -= io->offset;
+
+    if (is_dense(layout)) {
+        const uint64_t rel_offset = io->offset - layout->pattern_offset;
+        const uint64_t remainder = positive_remainder(rel_offset, unit_size);
+        const uint32_t stride = unit_size * stripe_count;
+
+        io->offset = (rel_offset / stride) * unit_size + remainder;
+    }
+    return PNFS_PENDING;
+}
+
+static enum pnfs_status thread_next_unit(
+    IN pnfs_io_thread *thread,
+    OUT pnfs_io_unit *io)
+{
+    pnfs_io_pattern *pattern = thread->pattern;
+    pnfs_layout_state *state = pattern->state;
+    enum pnfs_status status;
+
+    AcquireSRWLockShared(&state->lock);
+
+    /* stop io if the layout is recalled */
+    status = pnfs_layout_recall_status(state, &thread->layout->layout);
+    if (status)
+        goto out_unlock;
+
+    status = stripe_next_unit(thread->layout, thread->id,
+        &thread->offset, pattern->offset_end, io);
+    if (status == PNFS_PENDING)
+        io->buffer = pattern->buffer + thread->offset - pattern->offset_start;
+
+out_unlock:
+    ReleaseSRWLockShared(&state->lock);
+    return status;
+}
+
+static enum pnfs_status thread_data_server(
+    IN pnfs_io_thread *thread,
+    OUT pnfs_data_server **server_out)
+{
+    pnfs_file_device *device = thread->layout->device;
+    const uint32_t serverid = data_server_index(device, thread->id);
+
+    if (serverid >= device->servers.count)
+        return PNFSERR_INVALID_DS_INDEX;
+
+    *server_out = &device->servers.arr[serverid];
+    return PNFS_SUCCESS;
+}
+
+static enum pnfs_status pattern_join(
+    IN HANDLE *threads,
+    IN DWORD count)
+{
+    DWORD status;
+    /* WaitForMultipleObjects() supports a maximum of 64 objects */
+    while (count) {
+        const DWORD n = min(count, MAXIMUM_WAIT_OBJECTS);
+        status = WaitForMultipleObjects(n, threads, TRUE, INFINITE);
+        if (status != WAIT_OBJECT_0)
+            return PNFSERR_RESOURCES;
+
+        count -= n;
+        threads += n;
+    }
+    return PNFS_SUCCESS;
+}
+
+static enum pnfs_status pattern_fork(
+    IN pnfs_io_pattern *pattern,
+    IN pnfs_io_thread_fn thread_fn)
+{
+    HANDLE *threads;
+    uint32_t i;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    if (pattern->count == 0)
+        goto out;
+
+    if (pattern->count == 1) {
+        /* no need to fork if there's only 1 thread */
+        status = (enum pnfs_status)thread_fn(pattern->threads);
+        goto out;
+    }
+
+    /* create a thread for each unit that has actual io */
+    threads = calloc(pattern->count, sizeof(HANDLE));
+    if (threads == NULL) {
+        status = PNFSERR_RESOURCES;
+        goto out;
+    }
+
+    for (i = 0; i < pattern->count; i++) {
+        threads[i] = (HANDLE)_beginthreadex(NULL, 0,
+            thread_fn, &pattern->threads[i], 0, NULL);
+        if (threads[i] == NULL) {
+            eprintf("_beginthreadex() failed with %d\n", GetLastError());
+            pattern->count = i; /* join any threads already started */
+            break;
+        }
+    }
+
+    /* wait on all threads to finish */
+    status = pattern_join(threads, pattern->count);
+    if (status) {
+        eprintf("pattern_join() failed with %s\n", pnfs_error_string(status));
+        goto out;
+    }
+
+    for (i = 0; i < pattern->count; i++) {
+        /* keep track of the most severe error returned by a thread */
+        DWORD exitcode;
+        if (GetExitCodeThread(threads[i], &exitcode))
+            status = max(status, (enum pnfs_status)exitcode);
+
+        CloseHandle(threads[i]);
+    }
+
+    free(threads);
+out:
+    return status;
+}
+
+static uint64_t pattern_bytes_transferred(
+    IN pnfs_io_pattern *pattern,
+    OUT OPTIONAL enum stable_how4 *stable)
+{
+    uint64_t lowest_offset = pattern->offset_end;
+    uint32_t i;
+
+    if (stable) *stable = FILE_SYNC4;
+
+    for (i = 0; i < pattern->count; i++) {
+        lowest_offset = min(lowest_offset, pattern->threads[i].offset);
+        if (stable) *stable = min(*stable, pattern->threads[i].stable);
+    }
+    return lowest_offset - pattern->offset_start;
+}
+
+
+static enum pnfs_status map_ds_error(
+    IN enum nfsstat4 nfsstat,
+    IN pnfs_layout_state *state,
+    IN const pnfs_file_layout *layout)
+{
+    switch (nfsstat) {
+    case NO_ERROR:
+        return PNFS_SUCCESS;
+
+    /* 13.11 Layout Revocation and Fencing
+     * http://tools.ietf.org/html/rfc5661#section-13.11
+     * if we've been fenced, we'll either get ERR_STALE when we PUTFH
+     * something in layout.filehandles, or ERR_PNFS_NO_LAYOUT when
+     * attempting to READ or WRITE */
+    case NFS4ERR_STALE:
+    case NFS4ERR_PNFS_NO_LAYOUT:
+        dprintf(IOLVL, "data server fencing detected!\n");
+
+        pnfs_layout_recall_fenced(state, &layout->layout);
+
+        /* return CHANGED to prevent any further use of the layout */
+        return PNFSERR_LAYOUT_CHANGED;
+
+    default:
+        return PNFSERR_IO;
+    }
+}
+
+static uint32_t WINAPI file_layout_read_thread(void *args)
+{
+    pnfs_io_unit io;
+    stateid_arg stateid;
+    pnfs_io_thread *thread = (pnfs_io_thread*)args;
+    pnfs_io_pattern *pattern = thread->pattern;
+    pnfs_data_server *server;
+    nfs41_client *client;
+    uint32_t maxreadsize, bytes_read, total_read;
+    enum pnfs_status status;
+    enum nfsstat4 nfsstat;
+    bool_t eof;
+
+    dprintf(IOLVL, "--> file_layout_read_thread(%u)\n", thread->id);
+
+    /* get the data server for this thread */
+    status = thread_data_server(thread, &server);
+    if (status) {
+        eprintf("thread_data_server() failed with %s\n",
+            pnfs_error_string(status));
+        goto out;
+    }
+    /* find or establish a client for this data server */
+    status = pnfs_data_server_client(pattern->root,
+        server, pattern->default_lease, &client);
+    if (status) {
+        eprintf("pnfs_data_server_client() failed with %s\n",
+            pnfs_error_string(status));
+        goto out;
+    }
+
+    memcpy(&stateid, pattern->stateid, sizeof(stateid));
+    stateid.stateid.seqid = 0;
+
+    total_read = 0;
+    while (thread_next_unit(thread, &io) == PNFS_PENDING) {
+        maxreadsize = max_read_size(client->session, &thread->file->fh);
+        if (io.length > maxreadsize)
+            io.length = maxreadsize;
+
+        nfsstat = nfs41_read(client->session, thread->file, &stateid,
+            io.offset, (uint32_t)io.length, io.buffer, &bytes_read, &eof);
+        if (nfsstat) {
+            eprintf("nfs41_read() failed with %s\n",
+                nfs_error_string(nfsstat));
+            status = map_ds_error(nfsstat, pattern->state, thread->layout);
+            break;
+        }
+
+        total_read += bytes_read;
+        thread->offset += bytes_read;
+
+        if (eof) {
+            dprintf(IOLVL, "read thread %u reached eof: offset %llu\n",
+                thread->id, thread->offset);
+            status = total_read ? PNFS_SUCCESS : PNFS_READ_EOF;
+            break;
+        }
+    }
+out:
+    dprintf(IOLVL, "<-- file_layout_read_thread(%u) returning %s\n",
+        thread->id, pnfs_error_string(status));
+    return status;
+}
+
+static uint32_t WINAPI file_layout_write_thread(void *args)
+{
+    pnfs_io_unit io;
+    stateid_arg stateid;
+    pnfs_io_thread *thread = (pnfs_io_thread*)args;
+    pnfs_io_pattern *pattern = thread->pattern;
+    pnfs_data_server *server;
+    nfs41_client *client;
+    const uint64_t offset_start = thread->offset;
+    uint64_t commit_min, commit_max;
+    uint32_t maxwritesize, bytes_written, total_written;
+    enum pnfs_status status;
+    enum nfsstat4 nfsstat;
+
+    dprintf(IOLVL, "--> file_layout_write_thread(%u)\n", thread->id);
+
+    /* get the data server for this thread */
+    status = thread_data_server(thread, &server);
+    if (status) {
+        eprintf("thread_data_server() failed with %s\n",
+            pnfs_error_string(status));
+        goto out;
+    }
+    /* find or establish a client for this data server */
+    status = pnfs_data_server_client(pattern->root,
+        server, pattern->default_lease, &client);
+    if (status) {
+        eprintf("pnfs_data_server_client() failed with %s\n",
+            pnfs_error_string(status));
+        goto out;
+    }
+
+    memcpy(&stateid, pattern->stateid, sizeof(stateid));
+    stateid.stateid.seqid = 0;
+
+    maxwritesize = max_write_size(client->session, &thread->file->fh);
+
+retry_write:
+    thread->offset = offset_start;
+    thread->stable = FILE_SYNC4;
+    commit_min = NFS4_UINT64_MAX;
+    commit_max = 0;
+    total_written = 0;
+
+    while (thread_next_unit(thread, &io) == PNFS_PENDING) {
+        if (io.length > maxwritesize)
+            io.length = maxwritesize;
+
+        nfsstat = nfs41_write(client->session, thread->file, &stateid,
+            io.buffer, (uint32_t)io.length, io.offset, UNSTABLE4,
+            &bytes_written, &thread->verf, NULL);
+        if (nfsstat) {
+            eprintf("nfs41_write() failed with %s\n",
+                nfs_error_string(nfsstat));
+            status = map_ds_error(nfsstat, pattern->state, thread->layout);
+            break;
+        }
+        if (!verify_write(&thread->verf, &thread->stable))
+            goto retry_write;
+
+        total_written += bytes_written;
+        thread->offset += bytes_written;
+
+        /* track the range for commit */
+        if (commit_min > io.offset)
+            commit_min = io.offset;
+        if (commit_max < io.offset + io.length)
+            commit_max = io.offset + io.length;
+    }
+
+    /* nothing to commit */
+    if (commit_max <= commit_min)
+        goto out;
+    /* layout changed; redo all io against metadata server */
+    if (status == PNFSERR_LAYOUT_CHANGED)
+        goto out;
+    /* the data is already in stable storage */
+    if (thread->stable != UNSTABLE4)
+        goto out;
+    /* the metadata server expects us to commit there instead */
+    if (should_commit_to_mds(thread->layout))
+        goto out;
+
+    dprintf(1, "sending COMMIT to data server for offset=%lld len=%lld\n",
+        commit_min, commit_max - commit_min);
+    nfsstat = nfs41_commit(client->session, thread->file,
+        commit_min, (uint32_t)(commit_max - commit_min), 0, &thread->verf, NULL);
+
+    if (nfsstat)
+        status = map_ds_error(nfsstat, pattern->state, thread->layout);
+    else if (!verify_commit(&thread->verf)) {
+        /* resend the writes unless the layout was recalled */
+        if (status != PNFSERR_LAYOUT_RECALLED)
+            goto retry_write;
+        status = PNFSERR_IO;
+    } else {
+        /* on successful commit, leave pnfs_status unchanged; if the
+         * layout was recalled, we still want to return the error */
+        thread->stable = DATA_SYNC4;
+    }
+out:
+    dprintf(IOLVL, "<-- file_layout_write_thread(%u) returning %s\n",
+        thread->id, pnfs_error_string(status));
+    return status;
+}
+
+
+enum pnfs_status pnfs_read(
+    IN nfs41_root *root,
+    IN nfs41_open_state *state,
+    IN stateid_arg *stateid,
+    IN pnfs_layout_state *layout,
+    IN uint64_t offset,
+    IN uint64_t length,
+    OUT unsigned char *buffer_out,
+    OUT ULONG *len_out)
+{
+    pnfs_io_pattern pattern;
+    enum pnfs_status status;
+
+    dprintf(IOLVL, "--> pnfs_read(%llu, %llu)\n", offset, length);
+
+    *len_out = 0;
+
+    AcquireSRWLockExclusive(&layout->lock);
+
+    /* get layouts/devices for the entire range; PNFS_PENDING means we
+     * dropped the lock to send an rpc, so repeat until it succeeds */
+    do {
+        status = pnfs_layout_state_prepare(layout, state->session,
+            &state->file, stateid, PNFS_IOMODE_READ, offset, length);
+    } while (status == PNFS_PENDING);
+
+    if (status == PNFS_SUCCESS) {
+        /* interpret the layout and set up threads for io */
+        status = pattern_init(&pattern, root, &state->file, stateid,
+            layout, buffer_out, PNFS_IOMODE_READ, offset, length,
+            state->session->lease_time);
+        if (status)
+            eprintf("pattern_init() failed with %s\n",
+                pnfs_error_string(status));
+    }
+
+    ReleaseSRWLockExclusive(&layout->lock);
+
+    if (status)
+        goto out;
+
+    status = pattern_fork(&pattern, file_layout_read_thread);
+    if (status != PNFS_SUCCESS && status != PNFS_READ_EOF)
+        goto out_free_pattern;
+
+    *len_out = (ULONG)pattern_bytes_transferred(&pattern, NULL);
+
+out_free_pattern:
+    pattern_free(&pattern);
+out:
+    dprintf(IOLVL, "<-- pnfs_read() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+static enum pnfs_status mds_commit(
+    IN nfs41_open_state *state,
+    IN uint64_t offset,
+    IN uint32_t length,
+    IN const pnfs_io_pattern *pattern,
+    OUT nfs41_file_info *info)
+{
+    nfs41_write_verf verf;
+    enum nfsstat4 nfsstat;
+    enum pnfs_status status = PNFS_SUCCESS;
+    uint32_t i;
+
+    nfsstat = nfs41_commit(state->session,
+        &state->file, offset, length, 1, &verf, info);
+    if (nfsstat) {
+        eprintf("nfs41_commit() to mds failed with %s\n",
+            nfs_error_string(nfsstat));
+        status = PNFSERR_IO;
+        goto out;
+    }
+
+    /* 13.7. COMMIT through Metadata Server:
+     * If nfl_util & NFL4_UFLG_COMMIT_THRU_MDS is TRUE, then in order to
+     * maintain the current NFSv4.1 commit and recovery model, the data
+     * servers MUST return a common writeverf verifier in all WRITE
+     * responses for a given file layout, and the metadata server's
+     * COMMIT implementation must return the same writeverf. */
+    for (i = 0; i < pattern->count; i++) {
+        const pnfs_io_thread *thread = &pattern->threads[i];
+        if (thread->stable != UNSTABLE4) /* already committed */
+            continue;
+
+        if (!should_commit_to_mds(thread->layout)) {
+            /* commit to mds is not allowed on this layout */
+            eprintf("mds commit: failed to commit to data server\n");
+            status = PNFSERR_IO;
+            break;
+        }
+        if (memcmp(verf.verf, thread->verf.verf, NFS4_VERIFIER_SIZE) != 0) {
+            eprintf("mds commit verifier doesn't match ds write verifiers\n");
+            status = PNFSERR_IO;
+            break;
+        }
+    }
+out:
+    return status;
+}
+
+static enum pnfs_status layout_commit(
+    IN nfs41_open_state *state,
+    IN pnfs_layout_state *layout,
+    IN uint64_t offset,
+    IN uint64_t length,
+    OUT nfs41_file_info *info)
+{
+    stateid4 layout_stateid;
+    uint64_t last_offset = offset + length - 1;
+    uint64_t *new_last_offset = NULL;
+    enum nfsstat4 nfsstat;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    AcquireSRWLockExclusive(&state->lock);
+    /* if this is past the current eof, update the open state's
+     * last offset, and pass a pointer to LAYOUTCOMMIT */
+    if (state->pnfs_last_offset < last_offset ||
+        (state->pnfs_last_offset == 0 && last_offset == 0)) {
+        state->pnfs_last_offset = last_offset;
+        new_last_offset = &last_offset;
+    }
+    ReleaseSRWLockExclusive(&state->lock);
+
+    AcquireSRWLockShared(&layout->lock);
+    memcpy(&layout_stateid, &layout->stateid, sizeof(layout_stateid));
+    ReleaseSRWLockShared(&layout->lock);
+
+    dprintf(1, "LAYOUTCOMMIT for offset=%lld len=%lld new_last_offset=%u\n",
+        offset, length, new_last_offset ? 1 : 0);
+    nfsstat = pnfs_rpc_layoutcommit(state->session, &state->file,
+        &layout_stateid, offset, length, new_last_offset, NULL, info);
+    if (nfsstat) {
+        dprintf(IOLVL, "pnfs_rpc_layoutcommit() failed with %s\n",
+            nfs_error_string(nfsstat));
+        status = PNFSERR_IO;
+    }
+    return status;
+}
+
+enum pnfs_status pnfs_write(
+    IN nfs41_root *root,
+    IN nfs41_open_state *state,
+    IN stateid_arg *stateid,
+    IN pnfs_layout_state *layout,
+    IN uint64_t offset,
+    IN uint64_t length,
+    IN unsigned char *buffer,
+    OUT ULONG *len_out,
+    OUT nfs41_file_info *info)
+{
+    pnfs_io_pattern pattern;
+    enum stable_how4 stable;
+    enum pnfs_status status;
+
+    dprintf(IOLVL, "--> pnfs_write(%llu, %llu)\n", offset, length);
+
+    *len_out = 0;
+
+    AcquireSRWLockExclusive(&layout->lock);
+
+    /* get layouts/devices for the entire range; PNFS_PENDING means we
+     * dropped the lock to send an rpc, so repeat until it succeeds */
+    do {
+        status = pnfs_layout_state_prepare(layout, state->session,
+            &state->file, stateid, PNFS_IOMODE_RW, offset, length);
+    } while (status == PNFS_PENDING);
+
+    if (status == PNFS_SUCCESS) {
+        /* interpret the layout and set up threads for io */
+        status = pattern_init(&pattern, root, &state->file, stateid,
+            layout, buffer, PNFS_IOMODE_RW, offset, length,
+            state->session->lease_time);
+        if (status)
+            eprintf("pattern_init() failed with %s\n",
+                pnfs_error_string(status));
+    }
+
+    ReleaseSRWLockExclusive(&layout->lock);
+
+    if (status)
+        goto out;
+
+    status = pattern_fork(&pattern, file_layout_write_thread);
+    /* on layout recall, we still attempt to commit what we wrote */
+    if (status != PNFS_SUCCESS && status != PNFSERR_LAYOUT_RECALLED)
+        goto out_free_pattern;
+
+    *len_out = (ULONG)pattern_bytes_transferred(&pattern, &stable);
+    if (*len_out == 0)
+        goto out_free_pattern;
+
+    if (stable == UNSTABLE4) {
+        /* send COMMIT to the mds and verify against all ds writes */
+        status = mds_commit(state, offset, *len_out, &pattern, info);
+    } else if (stable == DATA_SYNC4) {
+        /* send LAYOUTCOMMIT to sync the metadata */
+        status = layout_commit(state, layout, offset, *len_out, info);
+    } else {
+        /* send a GETATTR to update the cached size */
+        bitmap4 attr_request;
+        nfs41_superblock_getattr_mask(state->file.fh.superblock, &attr_request);
+        nfs41_getattr(state->session, &state->file, &attr_request, info);
+    }
+out_free_pattern:
+    pattern_free(&pattern);
+out:
+    dprintf(IOLVL, "<-- pnfs_write() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
\ No newline at end of file
diff --git a/reactos/base/services/nfsd/pnfs_layout.c b/reactos/base/services/nfsd/pnfs_layout.c
new file mode 100644 (file)
index 0000000..243106a
--- /dev/null
@@ -0,0 +1,1289 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <stdio.h>
+
+#include "nfs41_ops.h"
+#include "nfs41_callback.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+#define FLLVL 2 /* dprintf level for file layout logging */
+
+
+/* pnfs_layout_list */
+struct pnfs_layout_list {
+    struct list_entry       head;
+    CRITICAL_SECTION        lock;
+};
+
+#define state_entry(pos) list_container(pos, pnfs_layout_state, entry)
+#define layout_entry(pos) list_container(pos, pnfs_layout, entry)
+#define file_layout_entry(pos) list_container(pos, pnfs_file_layout, layout.entry)
+
+static enum pnfs_status layout_state_create(
+    IN const nfs41_fh *meta_fh,
+    OUT pnfs_layout_state **layout_out)
+{
+    pnfs_layout_state *layout;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    layout = calloc(1, sizeof(pnfs_layout_state));
+    if (layout == NULL) {
+        status = PNFSERR_RESOURCES;
+        goto out;
+    }
+
+    fh_copy(&layout->meta_fh, meta_fh);
+    list_init(&layout->layouts);
+    list_init(&layout->recalls);
+    InitializeSRWLock(&layout->lock);
+    InitializeConditionVariable(&layout->cond);
+
+    *layout_out = layout;
+out:
+    return status;
+}
+
+static void file_layout_free(
+    IN pnfs_file_layout *layout)
+{
+    if (layout->device) pnfs_file_device_put(layout->device);
+    free(layout->filehandles.arr);
+    free(layout);
+}
+
+static void layout_state_free_layouts(
+    IN pnfs_layout_state *state)
+{
+    struct list_entry *entry, *tmp;
+    list_for_each_tmp(entry, tmp, &state->layouts)
+        file_layout_free(file_layout_entry(entry));
+    list_init(&state->layouts);
+}
+
+static void layout_state_free_recalls(
+    IN pnfs_layout_state *state)
+{
+    struct list_entry *entry, *tmp;
+    list_for_each_tmp(entry, tmp, &state->recalls)
+        free(layout_entry(entry));
+    list_init(&state->recalls);
+}
+
+static void layout_state_free(
+    IN pnfs_layout_state *state)
+{
+    layout_state_free_layouts(state);
+    layout_state_free_recalls(state);
+    free(state);
+}
+
+static int layout_entry_compare(
+    IN const struct list_entry *entry,
+    IN const void *value)
+{
+    const pnfs_layout_state *layout = state_entry(entry);
+    const nfs41_fh *meta_fh = (const nfs41_fh*)value;
+    const nfs41_fh *layout_fh = (const nfs41_fh*)&layout->meta_fh;
+    const uint32_t diff = layout_fh->len - meta_fh->len;
+    return diff ? diff : memcmp(layout_fh->fh, meta_fh->fh, meta_fh->len);
+}
+
+static enum pnfs_status layout_entry_find(
+    IN struct pnfs_layout_list *layouts,
+    IN const nfs41_fh *meta_fh,
+    OUT struct list_entry **entry_out)
+{
+    *entry_out = list_search(&layouts->head, meta_fh, layout_entry_compare);
+    return *entry_out ? PNFS_SUCCESS : PNFSERR_NO_LAYOUT;
+}
+
+enum pnfs_status pnfs_layout_list_create(
+    OUT struct pnfs_layout_list **layouts_out)
+{
+    struct pnfs_layout_list *layouts;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    layouts = calloc(1, sizeof(struct pnfs_layout_list));
+    if (layouts == NULL) {
+        status = PNFSERR_RESOURCES;
+        goto out;
+    }
+    list_init(&layouts->head);
+    InitializeCriticalSection(&layouts->lock);
+    *layouts_out = layouts;
+out:
+    return status;
+}
+
+void pnfs_layout_list_free(
+    IN struct pnfs_layout_list *layouts)
+{
+    struct list_entry *entry, *tmp;
+
+    EnterCriticalSection(&layouts->lock);
+
+    list_for_each_tmp(entry, tmp, &layouts->head)
+        layout_state_free(state_entry(entry));
+
+    LeaveCriticalSection(&layouts->lock);
+    DeleteCriticalSection(&layouts->lock);
+    free(layouts);
+}
+
+static enum pnfs_status layout_state_find_or_create(
+    IN struct pnfs_layout_list *layouts,
+    IN const nfs41_fh *meta_fh,
+    OUT pnfs_layout_state **layout_out)
+{
+    struct list_entry *entry;
+    enum pnfs_status status;
+
+    dprintf(FLLVL, "--> layout_state_find_or_create()\n");
+
+    EnterCriticalSection(&layouts->lock);
+
+    /* search for an existing layout */
+    status = layout_entry_find(layouts, meta_fh, &entry);
+    if (status) {
+        /* create a new layout */
+        pnfs_layout_state *layout;
+        status = layout_state_create(meta_fh, &layout);
+        if (status == PNFS_SUCCESS) {
+            /* add it to the list */
+            list_add_head(&layouts->head, &layout->entry);
+            *layout_out = layout;
+
+            dprintf(FLLVL, "<-- layout_state_find_or_create() "
+                "returning new layout %p\n", layout);
+        } else {
+            dprintf(FLLVL, "<-- layout_state_find_or_create() "
+                "returning %s\n", pnfs_error_string(status));
+        }
+    } else {
+        *layout_out = state_entry(entry);
+
+        dprintf(FLLVL, "<-- layout_state_find_or_create() "
+            "returning existing layout %p\n", *layout_out);
+    }
+
+    LeaveCriticalSection(&layouts->lock);
+    return status;
+}
+
+static enum pnfs_status layout_state_find_and_delete(
+    IN struct pnfs_layout_list *layouts,
+    IN const nfs41_fh *meta_fh)
+{
+    struct list_entry *entry;
+    enum pnfs_status status;
+
+    dprintf(FLLVL, "--> layout_state_find_and_delete()\n");
+
+    EnterCriticalSection(&layouts->lock);
+
+    status = layout_entry_find(layouts, meta_fh, &entry);
+    if (status == PNFS_SUCCESS) {
+        list_remove(entry);
+        layout_state_free(state_entry(entry));
+    }
+
+    LeaveCriticalSection(&layouts->lock);
+
+    dprintf(FLLVL, "<-- layout_state_find_and_delete() "
+        "returning %s\n", pnfs_error_string(status));
+    return status;
+}
+
+
+/* pnfs_file_layout */
+static uint64_t range_max(
+    IN const pnfs_layout *layout)
+{
+    uint64_t result = layout->offset + layout->length;
+    return result < layout->offset ? NFS4_UINT64_MAX : result;
+}
+
+static bool_t layout_sanity_check(
+    IN pnfs_file_layout *layout)
+{
+    /* prevent div/0 */
+    if (layout->layout.length == 0 ||
+        layout->layout.iomode < PNFS_IOMODE_READ ||
+        layout->layout.iomode > PNFS_IOMODE_RW ||
+        layout_unit_size(layout) == 0)
+        return FALSE;
+
+    /* put a cap on layout.length to prevent overflow */
+    layout->layout.length = range_max(&layout->layout) - layout->layout.offset;
+    return TRUE;
+}
+
+static int layout_filehandles_cmp(
+    IN const pnfs_file_layout_handles *lhs,
+    IN const pnfs_file_layout_handles *rhs)
+{
+    const uint32_t diff = rhs->count - lhs->count;
+    return diff ? diff : memcmp(rhs->arr, lhs->arr,
+        rhs->count * sizeof(nfs41_path_fh));
+}
+
+static bool_t layout_merge_segments(
+    IN pnfs_file_layout *to,
+    IN pnfs_file_layout *from)
+{
+    const uint64_t to_max = range_max(&to->layout);
+    const uint64_t from_max = range_max(&from->layout);
+
+    /* cannot merge a segment with itself */
+    if (to == from)
+        return FALSE;
+
+    /* the ranges must meet or overlap */
+    if (to_max < from->layout.offset || from_max < to->layout.offset)
+        return FALSE;
+
+    /* the following fields must match: */
+    if (to->layout.iomode != from->layout.iomode ||
+        to->layout.type != from->layout.type ||
+        layout_filehandles_cmp(&to->filehandles, &from->filehandles) != 0 ||
+        memcmp(to->deviceid, from->deviceid, PNFS_DEVICEID_SIZE) != 0 ||
+        to->pattern_offset != from->pattern_offset ||
+        to->first_index != from->first_index ||
+        to->util != from->util)
+        return FALSE;
+
+    dprintf(FLLVL, "merging layout range {%llu, %llu} with {%llu, %llu}\n",
+        to->layout.offset, to->layout.length,
+        from->layout.offset, from->layout.length);
+
+    /* calculate the union of the two ranges */
+    to->layout.offset = min(to->layout.offset, from->layout.offset);
+    to->layout.length = max(to_max, from_max) - to->layout.offset;
+    return TRUE;
+}
+
+static enum pnfs_status layout_state_merge(
+    IN pnfs_layout_state *state,
+    IN pnfs_file_layout *from)
+{
+    struct list_entry *entry, *tmp;
+    pnfs_file_layout *to;
+    enum pnfs_status status = PNFSERR_NO_LAYOUT;
+
+    /* attempt to merge the new segment with each existing segment */
+    list_for_each_tmp(entry, tmp, &state->layouts) {
+        to = file_layout_entry(entry);
+        if (!layout_merge_segments(to, from))
+            continue;
+
+        /* on success, remove/free the new segment */
+        list_remove(&from->layout.entry);
+        file_layout_free(from);
+        status = PNFS_SUCCESS;
+
+        /* because the existing segment 'to' has grown, we may
+         * be able to merge it with later segments */
+        from = to;
+
+        /* but if there could be io threads referencing this segment,
+         * we can't free it until io is finished */
+        if (state->io_count)
+            break;
+    }
+    return status;
+}
+
+static void layout_ordered_insert(
+    IN pnfs_layout_state *state,
+    IN pnfs_layout *layout)
+{
+    struct list_entry *entry;
+    list_for_each(entry, &state->layouts) {
+        pnfs_layout *existing = layout_entry(entry);
+
+        /* maintain an order of increasing offset */
+        if (existing->offset < layout->offset)
+            continue;
+
+        /* when offsets are equal, prefer a longer segment first */
+        if (existing->offset == layout->offset &&
+            existing->length > layout->length)
+            continue;
+
+        list_add(&layout->entry, existing->entry.prev, &existing->entry);
+        return;
+    }
+
+    list_add_tail(&state->layouts, &layout->entry);
+}
+
+static enum pnfs_status layout_update_range(
+    IN OUT pnfs_layout_state *state,
+    IN const struct list_entry *layouts)
+{
+    struct list_entry *entry, *tmp;
+    pnfs_file_layout *layout;
+    enum pnfs_status status = PNFSERR_NO_LAYOUT;
+
+    list_for_each_tmp(entry, tmp, layouts) {
+        layout = file_layout_entry(entry);
+
+        /* don't know what to do with non-file layouts */
+        if (layout->layout.type != PNFS_LAYOUTTYPE_FILE)
+            continue;
+
+        if (!layout_sanity_check(layout)) {
+            file_layout_free(layout);
+            continue;
+        }
+
+        /* attempt to merge the range with existing segments */
+        status = layout_state_merge(state, layout);
+        if (status) {
+            dprintf(FLLVL, "saving new layout:\n");
+            dprint_layout(FLLVL, layout);
+
+            layout_ordered_insert(state, &layout->layout);
+            status = PNFS_SUCCESS;
+        }
+    }
+    return status;
+}
+
+static enum pnfs_status layout_update_stateid(
+    IN OUT pnfs_layout_state *state,
+    IN const stateid4 *stateid)
+{
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    if (state->stateid.seqid == 0) {
+        /* save a new layout stateid */
+        memcpy(&state->stateid, stateid, sizeof(stateid4));
+    } else if (memcmp(&state->stateid.other, stateid->other, 
+                        NFS4_STATEID_OTHER) == 0) {
+        /* update an existing layout stateid */
+        state->stateid.seqid = stateid->seqid;
+    } else {
+        status = PNFSERR_NO_LAYOUT;
+    }
+    return status;
+}
+
+static enum pnfs_status layout_update(
+    IN OUT pnfs_layout_state *state,
+    IN const pnfs_layoutget_res_ok *layoutget_res)
+{
+    enum pnfs_status status;
+
+    /* update the layout ranges held by the client */
+    status = layout_update_range(state, &layoutget_res->layouts);
+    if (status) {
+        eprintf("LAYOUTGET didn't return any file layouts\n");
+        goto out;
+    }
+    /* update the layout stateid */
+    status = layout_update_stateid(state, &layoutget_res->stateid);
+    if (status) {
+        eprintf("LAYOUTGET returned a new stateid when we already had one\n");
+        goto out;
+    }
+    /* if a previous LAYOUTGET set return_on_close, don't overwrite it */
+    if (!state->return_on_close)
+        state->return_on_close = layoutget_res->return_on_close;
+out:
+    return status;
+}
+
+static enum pnfs_status file_layout_fetch(
+    IN OUT pnfs_layout_state *state,
+    IN nfs41_session *session,
+    IN nfs41_path_fh *meta_file,
+    IN stateid_arg *stateid,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t minlength,
+    IN uint64_t length)
+{
+    pnfs_layoutget_res_ok layoutget_res = { 0 };
+    enum pnfs_status pnfsstat = PNFS_SUCCESS;
+    enum nfsstat4 nfsstat;
+
+    dprintf(FLLVL, "--> file_layout_fetch(%s, seqid=%u)\n",
+        pnfs_iomode_string(iomode), state->stateid.seqid);
+
+    list_init(&layoutget_res.layouts);
+
+    /* drop the lock during the rpc call */
+    ReleaseSRWLockExclusive(&state->lock);
+    nfsstat = pnfs_rpc_layoutget(session, meta_file, stateid,
+        iomode, offset, minlength, length, &layoutget_res);
+    AcquireSRWLockExclusive(&state->lock);
+
+    if (nfsstat) {
+        dprintf(FLLVL, "pnfs_rpc_layoutget() failed with %s\n",
+            nfs_error_string(nfsstat));
+        pnfsstat = PNFSERR_NOT_SUPPORTED;
+    }
+
+    switch (nfsstat) {
+    case NFS4_OK:
+        /* use the LAYOUTGET results to update our view of the layout */
+        pnfsstat = layout_update(state, &layoutget_res);
+        break;
+
+    case NFS4ERR_BADIOMODE:
+        /* don't try RW again */
+        if (iomode == PNFS_IOMODE_RW)
+            state->status |= PNFS_LAYOUT_NOT_RW;
+        break;
+
+    case NFS4ERR_LAYOUTUNAVAILABLE:
+    case NFS4ERR_UNKNOWN_LAYOUTTYPE:
+    case NFS4ERR_BADLAYOUT:
+        /* don't try again at all */
+        state->status |= PNFS_LAYOUT_UNAVAILABLE;
+        break;
+    }
+
+    dprintf(FLLVL, "<-- file_layout_fetch() returning %s\n",
+        pnfs_error_string(pnfsstat));
+    return pnfsstat;
+}
+
+/* returns PNFS_SUCCESS if the client holds valid layouts that cover
+ * the entire range requested.  otherwise, returns PNFS_PENDING and
+ * sets 'offset_missing' to the lowest offset that is not covered */
+static enum pnfs_status layout_coverage_status(
+    IN pnfs_layout_state *state,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length,
+    OUT uint64_t *offset_missing)
+{
+    uint64_t position = offset;
+    struct list_entry *entry;
+
+    list_for_each(entry, &state->layouts) {
+        /* if the current position intersects with a compatible
+         * layout, move the position to the end of that layout */
+        pnfs_layout *layout = layout_entry(entry);
+        if (layout->iomode >= iomode &&
+            layout->offset <= position &&
+            position < layout->offset + layout->length)
+            position = layout->offset + layout->length;
+    }
+
+    if (position >= offset + length)
+        return PNFS_SUCCESS;
+
+    *offset_missing = position;
+    return PNFS_PENDING;
+}
+
+static enum pnfs_status layout_fetch(
+    IN pnfs_layout_state *state,
+    IN nfs41_session *session,
+    IN nfs41_path_fh *meta_file,
+    IN stateid_arg *stateid,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length)
+{
+    stateid_arg layout_stateid = { 0 };
+    enum pnfs_status status = PNFS_PENDING;
+
+    /* check for previous errors from LAYOUTGET */
+    if ((state->status & PNFS_LAYOUT_UNAVAILABLE) ||
+        ((state->status & PNFS_LAYOUT_NOT_RW) && iomode == PNFS_IOMODE_RW)) {
+        status = PNFSERR_NO_LAYOUT;
+        goto out;
+    }
+
+    /* wait for any pending LAYOUTGETs/LAYOUTRETURNs */
+    while (state->pending)
+        SleepConditionVariableSRW(&state->cond, &state->lock, INFINITE, 0);
+    state->pending = TRUE;
+
+    /* if there's an existing layout stateid, use it */
+    if (state->stateid.seqid) {
+        memcpy(&layout_stateid.stateid, &state->stateid, sizeof(stateid4));
+        layout_stateid.type = STATEID_LAYOUT;
+        stateid = &layout_stateid;
+    }
+
+    if ((state->status & PNFS_LAYOUT_NOT_RW) == 0) {
+        /* try to get a RW layout first */
+        status = file_layout_fetch(state, session, meta_file,
+            stateid, PNFS_IOMODE_RW, offset, length, NFS4_UINT64_MAX);
+    }
+
+    if (status && iomode == PNFS_IOMODE_READ) {
+        /* fall back on READ if necessary */
+        status = file_layout_fetch(state, session, meta_file,
+            stateid, iomode, offset, length, NFS4_UINT64_MAX);
+    }
+
+    state->pending = FALSE;
+    WakeConditionVariable(&state->cond);
+out:
+    return status;
+}
+
+static enum pnfs_status device_status(
+    IN pnfs_layout_state *state,
+    IN uint64_t offset,
+    IN uint64_t length,
+    OUT unsigned char *deviceid)
+{
+    struct list_entry *entry;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    list_for_each(entry, &state->layouts) {
+        pnfs_file_layout *layout = file_layout_entry(entry);
+
+        if (layout->device == NULL) {
+            /* copy missing deviceid */
+            memcpy(deviceid, layout->deviceid, PNFS_DEVICEID_SIZE);
+            status = PNFS_PENDING;
+            break;
+        }
+    }
+    return status;
+}
+
+static void device_assign(
+    IN pnfs_layout_state *state,
+    IN const unsigned char *deviceid,
+    IN pnfs_file_device *device)
+{
+    struct list_entry *entry;
+    list_for_each(entry, &state->layouts) {
+        pnfs_file_layout *layout = file_layout_entry(entry);
+
+        /* assign the device to any matching layouts */
+        if (layout->device == NULL &&
+            memcmp(layout->deviceid, deviceid, PNFS_DEVICEID_SIZE) == 0) {
+            layout->device = device;
+
+            /* XXX: only assign the device to a single segment, because
+             * pnfs_file_device_get() only gives us a single reference */
+            break;
+        }
+    }
+}
+
+static enum pnfs_status device_fetch(
+    IN pnfs_layout_state *state,
+    IN nfs41_session *session,
+    IN unsigned char *deviceid)
+{
+    pnfs_file_device *device;
+    enum pnfs_status status;
+
+    /* drop the layoutstate lock for the rpc call */
+    ReleaseSRWLockExclusive(&state->lock);
+    status = pnfs_file_device_get(session,
+        session->client->devices, deviceid, &device);
+    AcquireSRWLockExclusive(&state->lock);
+
+    if (status == PNFS_SUCCESS)
+        device_assign(state, deviceid, device);
+    return status;
+}
+
+
+/* nfs41_open_state */
+static enum pnfs_status client_supports_pnfs(
+    IN nfs41_client *client)
+{
+    enum pnfs_status status;
+    AcquireSRWLockShared(&client->exid_lock);
+    status = client->roles & EXCHGID4_FLAG_USE_PNFS_MDS
+        ? PNFS_SUCCESS : PNFSERR_NOT_SUPPORTED;
+    ReleaseSRWLockShared(&client->exid_lock);
+    return status;
+}
+
+static enum pnfs_status fs_supports_layout(
+    IN const nfs41_superblock *superblock,
+    IN enum pnfs_layout_type type)
+{
+    const uint32_t flag = 1 << (type - 1);
+    return (superblock->layout_types & flag) == 0
+        ? PNFSERR_NOT_SUPPORTED : PNFS_SUCCESS;
+}
+
+static enum pnfs_status open_state_layout_cached(
+    IN nfs41_open_state *state,
+    OUT pnfs_layout_state **layout_out)
+{
+    enum pnfs_status status = PNFSERR_NO_LAYOUT;
+
+    if (state->layout) {
+        status = PNFS_SUCCESS;
+        *layout_out = state->layout;
+
+        dprintf(FLLVL, "pnfs_open_state_layout() found "
+            "cached layout %p\n", *layout_out);
+    }
+    return status;
+}
+
+enum pnfs_status pnfs_layout_state_open(
+    IN nfs41_open_state *state,
+    OUT pnfs_layout_state **layout_out)
+{
+    struct pnfs_layout_list *layouts = state->session->client->layouts;
+    nfs41_session *session = state->session;
+    pnfs_layout_state *layout;
+    enum pnfs_status status;
+
+    dprintf(FLLVL, "--> pnfs_layout_state_open()\n");
+
+    status = client_supports_pnfs(session->client);
+    if (status)
+        goto out;
+    status = fs_supports_layout(state->file.fh.superblock, PNFS_LAYOUTTYPE_FILE);
+    if (status)
+        goto out;
+
+    /* under shared lock, check open state for cached layouts */
+    AcquireSRWLockShared(&state->lock);
+    status = open_state_layout_cached(state, &layout);
+    ReleaseSRWLockShared(&state->lock);
+
+    if (status) {
+        /* under exclusive lock, find or create a layout for this file */
+        AcquireSRWLockExclusive(&state->lock);
+
+        status = open_state_layout_cached(state, &layout);
+        if (status) {
+            status = layout_state_find_or_create(layouts, &state->file.fh, &layout);
+            if (status == PNFS_SUCCESS) {
+                LONG open_count = InterlockedIncrement(&layout->open_count);
+                state->layout = layout;
+
+                dprintf(FLLVL, "pnfs_layout_state_open() caching layout %p "
+                    "(%u opens)\n", state->layout, open_count);
+            }
+        }
+
+        ReleaseSRWLockExclusive(&state->lock);
+
+        if (status)
+            goto out;
+    }
+
+    *layout_out = layout;
+out:
+    dprintf(FLLVL, "<-- pnfs_layout_state_open() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+/* expects caller to hold an exclusive lock on pnfs_layout_state */
+enum pnfs_status pnfs_layout_state_prepare(
+    IN pnfs_layout_state *state,
+    IN nfs41_session *session,
+    IN nfs41_path_fh *meta_file,
+    IN stateid_arg *stateid,
+    IN enum pnfs_iomode iomode,
+    IN uint64_t offset,
+    IN uint64_t length)
+{
+    unsigned char deviceid[PNFS_DEVICEID_SIZE];
+    struct list_entry *entry;
+    uint64_t missing;
+    enum pnfs_status status;
+
+    /* fail if the range intersects any pending recalls */
+    list_for_each(entry, &state->recalls) {
+        const pnfs_layout *recall = layout_entry(entry);
+        if (offset <= recall->offset + recall->length
+            && recall->offset <= offset + length) {
+            status = PNFSERR_LAYOUT_RECALLED;
+            goto out;
+        }
+    }
+
+    /* if part of the given range is not covered by a layout,
+     * attempt to fetch it with LAYOUTGET */
+    status = layout_coverage_status(state, iomode, offset, length, &missing);
+    if (status == PNFS_PENDING) {
+        status = layout_fetch(state, session, meta_file, stateid,
+            iomode, missing, offset + length - missing);
+
+        /* return pending because layout_fetch() dropped the lock */
+        if (status == PNFS_SUCCESS)
+            status = PNFS_PENDING;
+        goto out;
+    }
+
+    /* if any layouts in the range are missing device info,
+     * fetch them with GETDEVICEINFO */
+    status = device_status(state, offset, length, deviceid);
+    if (status == PNFS_PENDING) {
+        status = device_fetch(state, session, deviceid);
+
+        /* return pending because device_fetch() dropped the lock */
+        if (status == PNFS_SUCCESS)
+            status = PNFS_PENDING;
+        goto out;
+    }
+out:
+    return status;
+}
+
+static enum pnfs_status layout_return_status(
+    IN const pnfs_layout_state *state)
+{
+    /* return the layout if we have a stateid */
+    return state->stateid.seqid ? PNFS_SUCCESS : PNFS_PENDING;
+}
+
+static enum pnfs_status file_layout_return(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    IN pnfs_layout_state *state)
+{
+    enum pnfs_status status;
+    enum nfsstat4 nfsstat;
+
+    dprintf(FLLVL, "--> file_layout_return()\n");
+
+    /* under shared lock, determine whether we need to return the layout */
+    AcquireSRWLockShared(&state->lock);
+    status = layout_return_status(state);
+    ReleaseSRWLockShared(&state->lock);
+
+    if (status != PNFS_PENDING)
+        goto out;
+
+    /* under exclusive lock, return the layout and reset status flags */
+    AcquireSRWLockExclusive(&state->lock);
+
+    /* wait for any pending LAYOUTGETs/LAYOUTRETURNs */
+    while (state->pending)
+        SleepConditionVariableSRW(&state->cond, &state->lock, INFINITE, 0);
+    state->pending = TRUE;
+
+    status = layout_return_status(state);
+    if (status == PNFS_PENDING) {
+        pnfs_layoutreturn_res layoutreturn_res = { 0 };
+        stateid4 stateid;
+        memcpy(&stateid, &state->stateid, sizeof(stateid));
+            
+        /* drop the lock during the rpc call */
+        ReleaseSRWLockExclusive(&state->lock);
+        nfsstat = pnfs_rpc_layoutreturn(session, file, PNFS_LAYOUTTYPE_FILE, 
+            PNFS_IOMODE_ANY, 0, NFS4_UINT64_MAX, &stateid, &layoutreturn_res);
+        AcquireSRWLockExclusive(&state->lock);
+
+        if (nfsstat) {
+            eprintf("pnfs_rpc_layoutreturn() failed with %s\n", 
+                nfs_error_string(nfsstat));
+            status = PNFSERR_NO_LAYOUT;
+        } else {
+            status = PNFS_SUCCESS;
+
+            /* update the layout range held by the client */
+            layout_state_free_layouts(state);
+
+            /* 12.5.3. Layout Stateid: Once a client has no more
+             * layouts on a file, the layout stateid is no longer
+             * valid and MUST NOT be used. */
+            ZeroMemory(&state->stateid, sizeof(stateid4));
+        }
+    }
+
+    state->pending = FALSE;
+    WakeConditionVariable(&state->cond);
+    ReleaseSRWLockExclusive(&state->lock);
+
+out:
+    dprintf(FLLVL, "<-- file_layout_return() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+void pnfs_layout_state_close(
+    IN nfs41_session *session,
+    IN nfs41_open_state *state,
+    IN bool_t remove)
+{
+    pnfs_layout_state *layout;
+    bool_t return_layout;
+    enum pnfs_status status;
+
+    AcquireSRWLockExclusive(&state->lock);
+    layout = state->layout;
+    state->layout = NULL;
+    ReleaseSRWLockExclusive(&state->lock);
+
+    if (layout) {
+        LONG open_count = InterlockedDecrement(&layout->open_count);
+
+        AcquireSRWLockShared(&layout->lock);
+        /* only return on close if it's the last close */
+        return_layout = layout->return_on_close && (open_count <= 0);
+        ReleaseSRWLockShared(&layout->lock);
+
+        if (return_layout) {
+            status = file_layout_return(session, &state->file, layout);
+            if (status)
+                eprintf("file_layout_return() failed with %s\n",
+                    pnfs_error_string(status));
+        }
+    }
+
+    if (remove && session->client->layouts) {
+        /* free the layout when the file is removed */
+        layout_state_find_and_delete(session->client->layouts, &state->file.fh);
+    }
+}
+
+
+/* pnfs_layout_recall */
+struct layout_recall {
+    pnfs_layout layout;
+    bool_t changed;
+};
+#define recall_entry(pos) list_container(pos, struct layout_recall, layout.entry)
+
+static bool_t layout_recall_compatible(
+    IN const pnfs_layout *layout,
+    IN const pnfs_layout *recall)
+{
+    return layout->type == recall->type
+        && layout->offset <= (recall->offset + recall->length)
+        && recall->offset <= (layout->offset + layout->length)
+        && (recall->iomode == PNFS_IOMODE_ANY ||
+            layout->iomode == recall->iomode);
+}
+
+static pnfs_file_layout* layout_allocate_copy(
+    IN const pnfs_file_layout *existing)
+{
+    /* allocate a segment to cover the end of the range */
+    pnfs_file_layout *layout = calloc(1, sizeof(pnfs_file_layout));
+    if (layout == NULL)
+        goto out;
+
+    memcpy(layout, existing, sizeof(pnfs_file_layout));
+
+    /* XXX: don't use the device from existing layout;
+     * we need to get a reference for ourselves */
+    layout->device = NULL;
+
+    /* allocate a copy of the filehandle array */
+    layout->filehandles.arr = calloc(layout->filehandles.count,
+        sizeof(nfs41_path_fh));
+    if (layout->filehandles.arr == NULL)
+        goto out_free;
+
+    memcpy(layout->filehandles.arr, existing->filehandles.arr,
+        layout->filehandles.count * sizeof(nfs41_path_fh));
+out:
+    return layout;
+
+out_free:
+    file_layout_free(layout);
+    layout = NULL;
+    goto out;
+}
+
+static void layout_recall_range(
+    IN pnfs_layout_state *state,
+    IN const pnfs_layout *recall)
+{
+    struct list_entry *entry, *tmp;
+    list_for_each_tmp(entry, tmp, &state->layouts) {
+        pnfs_file_layout *layout = file_layout_entry(entry);
+        const uint64_t layout_end = layout->layout.offset + layout->layout.length;
+
+        if (!layout_recall_compatible(&layout->layout, recall))
+            continue;
+        
+        if (recall->offset > layout->layout.offset) {
+            /* segment starts before recall; shrink length */
+            layout->layout.length = recall->offset - layout->layout.offset;
+
+            if (layout_end > recall->offset + recall->length) {
+                /* middle chunk of the segment is recalled;
+                 * allocate a new segment to cover the end */
+                pnfs_file_layout *remainder = layout_allocate_copy(layout);
+                if (remainder == NULL) {
+                    /* silently ignore allocation errors here. behave
+                     * as if we 'forgot' this last segment */
+                } else {
+                    layout->layout.offset = recall->offset + recall->length;
+                    layout->layout.length = layout_end - layout->layout.offset;
+                    layout_ordered_insert(state, &remainder->layout);
+                }
+            }
+        } else {
+            /* segment starts after recall */
+            if (layout_end <= recall->offset + recall->length) {
+                /* entire segment is recalled */
+                list_remove(&layout->layout.entry);
+                file_layout_free(layout);
+            } else {
+                /* beginning of segment is recalled; shrink offset/length */
+                layout->layout.offset = recall->offset + recall->length;
+                layout->layout.length = layout_end - layout->layout.offset;
+            }
+        }
+    }
+}
+
+static void layout_state_deferred_recalls(
+    IN pnfs_layout_state *state)
+{
+    struct list_entry *entry, *tmp;
+    list_for_each_tmp(entry, tmp, &state->recalls) {
+        /* process each deferred layout recall */
+        pnfs_layout *recall = layout_entry(entry);
+        layout_recall_range(state, recall);
+
+        /* remove/free the recall entry */
+        list_remove(&recall->entry);
+        free(recall);
+    }
+}
+
+static void layout_recall_entry_init(
+    OUT struct layout_recall *lrc,
+    IN const struct cb_layoutrecall_args *recall)
+{
+    list_init(&lrc->layout.entry);
+    if (recall->recall.type == PNFS_RETURN_FILE) {
+        lrc->layout.offset = recall->recall.args.file.offset;
+        lrc->layout.length = recall->recall.args.file.length;
+    } else {
+        lrc->layout.offset = 0;
+        lrc->layout.length = NFS4_UINT64_MAX;
+    }
+    lrc->layout.iomode = recall->iomode;
+    lrc->layout.type = PNFS_LAYOUTTYPE_FILE;
+    lrc->changed = recall->changed;
+}
+
+static enum pnfs_status layout_recall_merge(
+    IN struct list_entry *list,
+    IN pnfs_layout *from)
+{
+    struct list_entry *entry, *tmp;
+    enum pnfs_status status = PNFSERR_NO_LAYOUT;
+
+    /* attempt to merge the new recall with each existing recall */
+    list_for_each_tmp(entry, tmp, list) {
+        pnfs_layout *to = layout_entry(entry);
+        const uint64_t to_max = to->offset + to->length;
+        const uint64_t from_max = from->offset + from->length;
+
+        /* the ranges must meet or overlap */
+        if (to_max < from->offset || from_max < to->offset)
+            continue;
+
+        /* the following fields must match: */
+        if (to->iomode != from->iomode || to->type != from->type)
+            continue;
+
+        dprintf(FLLVL, "merging recalled range {%llu, %llu} with {%llu, %llu}\n",
+            to->offset, to->length, from->offset, from->length);
+
+        /* calculate the union of the two ranges */
+        to->offset = min(to->offset, from->offset);
+        to->length = max(to_max, from_max) - to->offset;
+
+        /* on success, remove/free the new segment */
+        list_remove(&from->entry);
+        free(from);
+        status = PNFS_SUCCESS;
+
+        /* because the existing segment 'to' has grown, we may
+         * be able to merge it with later segments */
+        from = to;
+    }
+    return status;
+}
+
+static enum pnfs_status file_layout_recall(
+    IN pnfs_layout_state *state,
+    IN const struct cb_layoutrecall_args *recall)
+{
+    const stateid4 *stateid = &recall->recall.args.file.stateid;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    /* under an exclusive lock, flag the layout as recalled */
+    AcquireSRWLockExclusive(&state->lock);
+
+    if (state->stateid.seqid == 0) {
+        /* return NOMATCHINGLAYOUT if it wasn't actually granted */
+        status = PNFSERR_NO_LAYOUT;
+        goto out;
+    }
+    
+    if (recall->recall.type == PNFS_RETURN_FILE) {
+        /* detect races between CB_LAYOUTRECALL and LAYOUTGET/LAYOUTRETURN */
+        if (stateid->seqid > state->stateid.seqid + 1) {
+            /* the server has processed an outstanding LAYOUTGET or
+             * LAYOUTRETURN; we must return ERR_DELAY until we get the
+             * response and update our view of the layout */
+            status = PNFS_PENDING;
+            goto out;
+        }
+
+        /* save the updated seqid */
+        state->stateid.seqid = stateid->seqid;
+    }
+
+    if (state->io_count) {
+        /* save an entry for this recall, and process it once io finishes */
+        struct layout_recall *lrc = calloc(1, sizeof(struct layout_recall));
+        if (lrc == NULL) {
+            /* on failure to allocate, we'll have to respond
+             * to the CB_LAYOUTRECALL with NFS4ERR_DELAY */
+            status = PNFS_PENDING;
+            goto out;
+        }
+        layout_recall_entry_init(lrc, recall);
+        if (layout_recall_merge(&state->recalls, &lrc->layout) != PNFS_SUCCESS)
+            list_add_tail(&state->recalls, &lrc->layout.entry);
+    } else {
+        /* if there is no pending io, process the recall immediately */
+        struct layout_recall lrc = { 0 };
+        layout_recall_entry_init(&lrc, recall);
+        layout_recall_range(state, &lrc.layout);
+    }
+out:
+    ReleaseSRWLockExclusive(&state->lock);
+    return status;
+}
+
+static enum pnfs_status file_layout_recall_file(
+    IN nfs41_client *client,
+    IN const struct cb_layoutrecall_args *recall)
+{
+    struct list_entry *entry;
+    enum pnfs_status status;
+
+    dprintf(FLLVL, "--> file_layout_recall_file()\n");
+
+    EnterCriticalSection(&client->layouts->lock);
+
+    status = layout_entry_find(client->layouts, &recall->recall.args.file.fh, &entry);
+    if (status == PNFS_SUCCESS)
+        status = file_layout_recall(state_entry(entry), recall);
+
+    LeaveCriticalSection(&client->layouts->lock);
+
+    dprintf(FLLVL, "<-- file_layout_recall_file() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+static bool_t fsid_matches(
+    IN const nfs41_fsid *lhs,
+    IN const nfs41_fsid *rhs)
+{
+    return lhs->major == rhs->major && lhs->minor == rhs->minor;
+}
+
+static enum pnfs_status file_layout_recall_fsid(
+    IN nfs41_client *client,
+    IN const struct cb_layoutrecall_args *recall)
+{
+    struct list_entry *entry;
+    pnfs_layout_state *state;
+    nfs41_fh *fh;
+    enum pnfs_status status = PNFSERR_NO_LAYOUT;
+
+    dprintf(FLLVL, "--> file_layout_recall_fsid(%llu, %llu)\n",
+        recall->recall.args.fsid.major, recall->recall.args.fsid.minor);
+
+    EnterCriticalSection(&client->layouts->lock);
+
+    list_for_each(entry, &client->layouts->head) {
+        state = state_entry(entry);
+        /* no locks needed to read layout.meta_fh or superblock.fsid,
+         * because they are only written once on creation */
+        fh = &state->meta_fh;
+        if (fsid_matches(&recall->recall.args.fsid, &fh->superblock->fsid))
+            status = file_layout_recall(state, recall);
+    }
+
+    LeaveCriticalSection(&client->layouts->lock);
+
+    /* bulk recalls require invalidation of cached device info */
+    pnfs_file_device_list_invalidate(client->devices);
+
+    dprintf(FLLVL, "<-- file_layout_recall_fsid() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+static enum pnfs_status file_layout_recall_all(
+    IN nfs41_client *client,
+    IN const struct cb_layoutrecall_args *recall)
+{
+    struct list_entry *entry;
+    enum pnfs_status status = PNFSERR_NO_LAYOUT;
+
+    dprintf(FLLVL, "--> file_layout_recall_all()\n");
+
+    EnterCriticalSection(&client->layouts->lock);
+
+    list_for_each(entry, &client->layouts->head)
+        status = file_layout_recall(state_entry(entry), recall);
+
+    LeaveCriticalSection(&client->layouts->lock);
+
+    /* bulk recalls require invalidation of cached device info */
+    pnfs_file_device_list_invalidate(client->devices);
+
+    dprintf(FLLVL, "<-- file_layout_recall_all() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+enum pnfs_status pnfs_file_layout_recall(
+    IN nfs41_client *client,
+    IN const struct cb_layoutrecall_args *recall)
+{
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    dprintf(FLLVL, "--> pnfs_file_layout_recall(%u, %s, %u)\n",
+        recall->recall.type, pnfs_iomode_string(recall->iomode),
+        recall->changed);
+
+    if (recall->type != PNFS_LAYOUTTYPE_FILE) {
+        dprintf(FLLVL, "invalid layout type %u (%s)!\n",
+            recall->type, pnfs_layout_type_string(recall->type));
+        status = PNFSERR_NOT_SUPPORTED;
+        goto out;
+    }
+
+    switch (recall->recall.type) {
+    case PNFS_RETURN_FILE:
+        status = file_layout_recall_file(client, recall);
+        break;
+    case PNFS_RETURN_FSID:
+        status = file_layout_recall_fsid(client, recall);
+        break;
+    case PNFS_RETURN_ALL:
+        status = file_layout_recall_all(client, recall);
+        break;
+
+    default:
+        dprintf(FLLVL, "invalid return type %u!\n", recall->recall);
+        status = PNFSERR_NOT_SUPPORTED;
+        goto out;
+    }
+out:
+    dprintf(FLLVL, "<-- pnfs_file_layout_recall() returning %s\n",
+        pnfs_error_string(status));
+    return status;
+}
+
+/* expects caller to hold a shared lock on pnfs_layout_state */
+enum pnfs_status pnfs_layout_recall_status(
+    IN const pnfs_layout_state *state,
+    IN const pnfs_layout *layout)
+{
+    struct list_entry *entry;
+    enum pnfs_status status = PNFS_SUCCESS;
+
+    /* search for a pending recall that intersects with the given segment */
+    list_for_each(entry, &state->recalls) {
+        const struct layout_recall *recall = recall_entry(entry);
+        if (!layout_recall_compatible(layout, &recall->layout))
+            continue;
+
+        if (recall->changed)
+            status = PNFSERR_LAYOUT_CHANGED;
+        else
+            status = PNFSERR_LAYOUT_RECALLED;
+        break;
+    }
+    return status;
+}
+
+void pnfs_layout_recall_fenced(
+    IN pnfs_layout_state *state,
+    IN const pnfs_layout *layout)
+{
+    struct layout_recall *lrc = calloc(1, sizeof(struct layout_recall));
+    if (lrc == NULL)
+        return;
+
+    AcquireSRWLockExclusive(&state->lock);
+
+    list_init(&lrc->layout.entry);
+    lrc->layout.offset = layout->offset;
+    lrc->layout.length = layout->length;
+    lrc->layout.iomode = layout->iomode;
+    lrc->layout.type = layout->type;
+    lrc->changed = TRUE;
+
+    if (layout_recall_merge(&state->recalls, &lrc->layout) != PNFS_SUCCESS)
+        list_add_tail(&state->recalls, &lrc->layout.entry);
+
+    ReleaseSRWLockExclusive(&state->lock);
+}
+
+/* expects caller to hold an exclusive lock on pnfs_layout_state */
+void pnfs_layout_io_start(
+    IN pnfs_layout_state *state)
+{
+    /* take a reference on the layout, so that it won't be recalled
+     * until all io is finished */
+    state->io_count++;
+    dprintf(FLLVL, "pnfs_layout_io_start(): count -> %u\n",
+        state->io_count);
+}
+
+void pnfs_layout_io_finished(
+    IN pnfs_layout_state *state)
+{
+    AcquireSRWLockExclusive(&state->lock);
+
+    /* return the reference to signify that an io request is finished */
+    state->io_count--;
+    dprintf(FLLVL, "pnfs_layout_io_finished() count -> %u\n",
+        state->io_count);
+
+    if (state->io_count > 0) /* more io pending */
+        goto out_unlock;
+
+    /* once all io is finished, process any layout recalls */
+    layout_state_deferred_recalls(state);
+
+    /* finish any segment merging that was delayed during io */
+    if (!list_empty(&state->layouts))
+        layout_state_merge(state, file_layout_entry(state->layouts.next));
+
+out_unlock:
+    ReleaseSRWLockExclusive(&state->lock);
+}
diff --git a/reactos/base/services/nfsd/readdir.c b/reactos/base/services/nfsd/readdir.c
new file mode 100644 (file)
index 0000000..eaa9301
--- /dev/null
@@ -0,0 +1,649 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdlib.h>
+#include "from_kernel.h"
+#include "nfs41_ops.h"
+#include "daemon_debug.h"
+#include "upcall.h"
+#include "util.h"
+
+
+typedef union _FILE_DIR_INFO_UNION {
+    ULONG NextEntryOffset;
+    FILE_NAMES_INFORMATION fni;
+    FILE_DIRECTORY_INFO fdi;
+    FILE_FULL_DIR_INFO ffdi;
+    FILE_ID_FULL_DIR_INFO fifdi;
+    FILE_BOTH_DIR_INFORMATION fbdi;
+    FILE_ID_BOTH_DIR_INFO fibdi;
+} FILE_DIR_INFO_UNION, *PFILE_DIR_INFO_UNION;
+
+
+/* NFS41_DIR_QUERY */
+static int parse_readdir(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    readdir_upcall_args *args = &upcall->args.readdir;
+
+    status = safe_read(&buffer, &length, &args->query_class, sizeof(args->query_class));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
+    if (status) goto out;
+    status = get_name(&buffer, &length, &args->filter);
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->initial, sizeof(args->initial));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->restart, sizeof(args->restart));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->single, sizeof(args->single));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->kbuf, sizeof(args->kbuf));
+    if (status) goto out;
+    args->root = upcall->root_ref;
+    args->state = upcall->state_ref;
+
+    dprintf(1, "parsing NFS41_DIR_QUERY: info_class=%d buf_len=%d "
+        "filter='%s'\n\tInitial\\Restart\\Single %d\\%d\\%d buf=%p\n",
+        args->query_class, args->buf_len, args->filter,
+        args->initial, args->restart, args->single, args->kbuf);
+out:
+    return status;
+}
+
+#define FILTER_STAR '*'
+#define FILTER_QM   '>'
+
+static __inline const char* skip_stars(
+    const char *filter)
+{
+    while (*filter == FILTER_STAR)
+        filter++;
+    return filter;
+}
+
+static int readdir_filter(
+    const char *filter,
+    const char *name)
+{
+    const char *f = filter, *n = name;
+
+    while (*f && *n) {
+        if (*f == FILTER_STAR) {
+            f = skip_stars(f);
+            if (*f == '\0')
+                return 1;
+            while (*n && !readdir_filter(f, n))
+                n++;
+        } else if (*f == FILTER_QM || *f == *n) {
+            f++;
+            n++;
+        } else
+            return 0;
+    }
+    return *f == *n || *skip_stars(f) == '\0';
+}
+
+static uint32_t readdir_size_for_entry(
+    IN int query_class,
+    IN uint32_t wname_size)
+{
+    uint32_t needed = wname_size;
+    switch (query_class)
+    {
+    case FileDirectoryInformation:
+        needed += FIELD_OFFSET(FILE_DIRECTORY_INFO, FileName);
+        break;
+    case FileIdFullDirectoryInformation:
+        needed += FIELD_OFFSET(FILE_ID_FULL_DIR_INFO, FileName);
+        break;
+    case FileFullDirectoryInformation:
+        needed += FIELD_OFFSET(FILE_FULL_DIR_INFO, FileName);
+        break;
+    case FileIdBothDirectoryInformation:
+        needed += FIELD_OFFSET(FILE_ID_BOTH_DIR_INFO, FileName);
+        break;
+    case FileBothDirectoryInformation:
+        needed += FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName);
+        break;
+    case FileNamesInformation:
+        needed += FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName);
+        break;
+    default:
+        eprintf("unhandled dir query class %d\n", query_class);
+        return 0;
+    }
+    return needed;
+}
+
+static void readdir_copy_dir_info(
+    IN nfs41_readdir_entry *entry,
+    IN PFILE_DIR_INFO_UNION info)
+{
+    info->fdi.FileIndex = (ULONG)entry->attr_info.fileid;
+    nfs_time_to_file_time(&entry->attr_info.time_create,
+        &info->fdi.CreationTime);
+    nfs_time_to_file_time(&entry->attr_info.time_access,
+        &info->fdi.LastAccessTime);
+    nfs_time_to_file_time(&entry->attr_info.time_modify,
+        &info->fdi.LastWriteTime);
+    /* XXX: was using 'change' attr, but that wasn't giving a time */
+    nfs_time_to_file_time(&entry->attr_info.time_modify,
+        &info->fdi.ChangeTime);
+    info->fdi.EndOfFile.QuadPart =
+        info->fdi.AllocationSize.QuadPart =
+            entry->attr_info.size;
+    info->fdi.FileAttributes = nfs_file_info_to_attributes(
+        &entry->attr_info);
+}
+
+static void readdir_copy_shortname(
+    IN LPCWSTR name,
+    OUT LPWSTR name_out,
+    OUT CCHAR *name_size_out)
+{
+    /* GetShortPathName returns number of characters, not including \0 */
+    *name_size_out = (CCHAR)GetShortPathNameW(name, name_out, 12);
+    if (*name_size_out) {
+        *name_size_out++;
+        *name_size_out *= sizeof(WCHAR);
+    }
+}
+
+static void readdir_copy_full_dir_info(
+    IN nfs41_readdir_entry *entry,
+    IN PFILE_DIR_INFO_UNION info)
+{
+    readdir_copy_dir_info(entry, info);
+    /* for files with the FILE_ATTRIBUTE_REPARSE_POINT attribute,
+     * EaSize is used instead to specify its reparse tag. this makes
+     * the 'dir' command to show files as <SYMLINK>, and triggers a
+     * FSCTL_GET_REPARSE_POINT to query the symlink target
+     */
+    info->fifdi.EaSize = entry->attr_info.type == NF4LNK ?
+        IO_REPARSE_TAG_SYMLINK : 0;
+}
+
+static void readdir_copy_both_dir_info(
+    IN nfs41_readdir_entry *entry,
+    IN LPWSTR wname,
+    IN PFILE_DIR_INFO_UNION info)
+{
+    readdir_copy_full_dir_info(entry, info);
+    readdir_copy_shortname(wname, info->fbdi.ShortName,
+        &info->fbdi.ShortNameLength);
+}
+
+static void readdir_copy_filename(
+    IN LPCWSTR name,
+    IN uint32_t name_size,
+    OUT LPWSTR name_out,
+    OUT ULONG *name_size_out)
+{
+    *name_size_out = name_size;
+    memcpy(name_out, name, name_size);
+}
+
+static int format_abs_path(
+    IN const nfs41_abs_path *path,
+    IN const nfs41_component *name,
+    OUT nfs41_abs_path *path_out)
+{
+    /* format an absolute path 'parent\name' */
+    int status = NO_ERROR;
+
+    InitializeSRWLock(&path_out->lock);
+    abs_path_copy(path_out, path);
+    if (FAILED(StringCchPrintfA(path_out->path + path_out->len,
+        NFS41_MAX_PATH_LEN - path_out->len, "\\%s", name->name))) {
+        status = ERROR_FILENAME_EXCED_RANGE;
+        goto out;
+    }
+    path_out->len += name->len + 1;
+out:
+    return status;
+}
+
+static int lookup_entry(
+    IN nfs41_root *root,
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    OUT nfs41_readdir_entry *entry)
+{
+    nfs41_abs_path path;
+    nfs41_component name;
+    int status;
+
+    name.name = entry->name;
+    name.len = (unsigned short)entry->name_len - 1;
+
+    status = format_abs_path(parent->path, &name, &path);
+    if (status) goto out;
+
+    status = nfs41_lookup(root, session, &path,
+        NULL, NULL, &entry->attr_info, NULL);
+    if (status) goto out;
+out:
+    return status;
+}
+
+static int lookup_symlink(
+    IN nfs41_root *root,
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN const nfs41_component *name,
+    OUT nfs41_file_info *info_out)
+{
+    nfs41_abs_path path;
+    nfs41_path_fh file;
+    nfs41_file_info info;
+    int status;
+
+    status = format_abs_path(parent->path, name, &path);
+    if (status) goto out;
+
+    file.path = &path;
+    status = nfs41_lookup(root, session, &path, NULL, &file, &info, &session);
+    if (status) goto out;
+
+    last_component(path.path, path.path + path.len, &file.name);
+
+    status = nfs41_symlink_follow(root, session, &file, &info);
+    if (status) goto out;
+
+    info_out->symlink_dir = info.type == NF4DIR;
+out:
+    return status;
+}
+
+static int readdir_copy_entry(
+    IN readdir_upcall_args *args,
+    IN nfs41_readdir_entry *entry,
+    IN OUT unsigned char **dst_pos,
+    IN OUT uint32_t *dst_len)
+{
+    int status = 0;
+    WCHAR wname[NFS4_OPAQUE_LIMIT];
+    uint32_t wname_len, wname_size, needed;
+    PFILE_DIR_INFO_UNION info;
+
+    wname_len = MultiByteToWideChar(CP_UTF8, 0,
+        entry->name, entry->name_len, wname, NFS4_OPAQUE_LIMIT);
+    wname_size = (wname_len - 1) * sizeof(WCHAR);
+
+    needed = readdir_size_for_entry(args->query_class, wname_size);
+    if (!needed || needed > *dst_len) {
+        status = -1;
+        goto out;
+    }
+
+    info = (PFILE_DIR_INFO_UNION)*dst_pos;
+    info->NextEntryOffset = align8(needed);
+    *dst_pos += info->NextEntryOffset;
+    *dst_len -= info->NextEntryOffset;
+
+    if (entry->attr_info.rdattr_error == NFS4ERR_MOVED) {
+        entry->attr_info.type = NF4DIR; /* default to dir */
+        /* look up attributes for referral entries, but ignore return value;
+         * it's okay if lookup fails, we'll just write garbage attributes */
+        lookup_entry(args->root, args->state->session,
+            &args->state->file, entry);
+    } else if (entry->attr_info.type == NF4LNK) {
+        nfs41_component name;
+        name.name = entry->name;
+        name.len = (unsigned short)entry->name_len - 1;
+        /* look up the symlink target to see whether it's a directory */
+        lookup_symlink(args->root, args->state->session,
+            &args->state->file, &name, &entry->attr_info);
+    }
+
+    switch (args->query_class)
+    {
+    case FileNamesInformation:
+        info->fni.FileIndex = 0;
+        readdir_copy_filename(wname, wname_size,
+            info->fni.FileName, &info->fni.FileNameLength);
+        break;
+    case FileDirectoryInformation:
+        readdir_copy_dir_info(entry, info);
+        readdir_copy_filename(wname, wname_size,
+            info->fdi.FileName, &info->fdi.FileNameLength);
+        break;
+    case FileFullDirectoryInformation:
+        readdir_copy_full_dir_info(entry, info);
+        readdir_copy_filename(wname, wname_size,
+            info->ffdi.FileName, &info->ffdi.FileNameLength);
+        break;
+    case FileIdFullDirectoryInformation:
+        readdir_copy_full_dir_info(entry, info);
+        info->fibdi.FileId.QuadPart = (LONGLONG)entry->attr_info.fileid;
+        readdir_copy_filename(wname, wname_size,
+            info->fifdi.FileName, &info->fifdi.FileNameLength);
+        break;
+    case FileBothDirectoryInformation:
+        readdir_copy_both_dir_info(entry, wname, info);
+        readdir_copy_filename(wname, wname_size,
+            info->fbdi.FileName, &info->fbdi.FileNameLength);
+        break;
+    case FileIdBothDirectoryInformation:
+        readdir_copy_both_dir_info(entry, wname, info);
+        info->fibdi.FileId.QuadPart = (LONGLONG)entry->attr_info.fileid;
+        readdir_copy_filename(wname, wname_size,
+            info->fibdi.FileName, &info->fibdi.FileNameLength);
+        break;
+    default:
+        eprintf("unhandled dir query class %d\n", args->query_class);
+        status = -1;
+        break;
+    }
+out:
+    return status;
+}
+
+#define COOKIE_DOT      ((uint64_t)-2)
+#define COOKIE_DOTDOT   ((uint64_t)-1)
+
+static int readdir_add_dots(
+    IN readdir_upcall_args *args,
+    IN OUT unsigned char *entry_buf,
+    IN uint32_t entry_buf_len,
+    OUT uint32_t *len_out,
+    OUT uint32_t **last_offset)
+{
+    int status = 0;
+    const uint32_t entry_len = (uint32_t)FIELD_OFFSET(nfs41_readdir_entry, name);
+    nfs41_readdir_entry *entry;
+    nfs41_open_state *state = args->state;
+
+    *len_out = 0;
+    *last_offset = NULL;
+    switch (state->cookie.cookie) {
+    case 0:
+        if (entry_buf_len < entry_len + 2) {
+            status = ERROR_BUFFER_OVERFLOW;
+            dprintf(1, "not enough room for '.' entry. received %d need %d\n",
+                    entry_buf_len, entry_len + 2);
+            args->query_reply_len = entry_len + 2;
+            goto out;
+        }
+
+        entry = (nfs41_readdir_entry*)entry_buf;
+        ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info));
+
+        status = nfs41_cached_getattr(state->session,
+            &state->file, &entry->attr_info);
+        if (status) {
+            dprintf(1, "failed to add '.' entry.\n");
+            goto out;
+        }
+        entry->cookie = COOKIE_DOT;
+        entry->name_len = 2;
+        StringCbCopyA(entry->name, entry->name_len, ".");
+        entry->next_entry_offset = entry_len + entry->name_len;
+
+        entry_buf += entry->next_entry_offset;
+        entry_buf_len -= entry->next_entry_offset;
+        *len_out += entry->next_entry_offset;
+        *last_offset = &entry->next_entry_offset;
+        if (args->single)
+            break;
+        /* else no break! */
+    case COOKIE_DOT:
+        if (entry_buf_len < entry_len + 3) {
+            status = ERROR_BUFFER_OVERFLOW;
+            dprintf(1, "not enough room for '..' entry. received %d need %d\n",
+                    entry_buf_len, entry_len);
+            args->query_reply_len = entry_len + 2;
+            goto out;
+        }
+        /* XXX: this skips '..' when listing root fh */
+        if (state->file.name.len == 0)
+            break;
+
+        entry = (nfs41_readdir_entry*)entry_buf;
+        ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info));
+
+        status = nfs41_cached_getattr(state->session,
+            &state->parent, &entry->attr_info);
+        if (status) {
+            status = ERROR_FILE_NOT_FOUND;
+            dprintf(1, "failed to add '..' entry.\n");
+            goto out;
+        }
+        entry->cookie = COOKIE_DOTDOT;
+        entry->name_len = 3;
+        StringCbCopyA(entry->name, entry->name_len, "..");
+        entry->next_entry_offset = entry_len + entry->name_len;
+
+        entry_buf += entry->next_entry_offset;
+        entry_buf_len -= entry->next_entry_offset;
+        *len_out += entry->next_entry_offset;
+        *last_offset = &entry->next_entry_offset;
+        break;
+    }
+    if (state->cookie.cookie == COOKIE_DOTDOT ||
+        state->cookie.cookie == COOKIE_DOT)
+        ZeroMemory(&state->cookie, sizeof(nfs41_readdir_cookie));
+out:
+    return status;
+}
+
+static int handle_readdir(nfs41_upcall *upcall)
+{
+    int status;
+    readdir_upcall_args *args = &upcall->args.readdir;
+    nfs41_open_state *state = upcall->state_ref;
+    unsigned char *entry_buf = NULL;
+    uint32_t entry_buf_len;
+    bitmap4 attr_request;
+    bool_t eof;
+    /* make sure we allocate enough space for one nfs41_readdir_entry */
+    const uint32_t max_buf_len = max(args->buf_len,
+        sizeof(nfs41_readdir_entry) + NFS41_MAX_COMPONENT_LEN);
+
+    dprintf(1, "-> handle_nfs41_dirquery(%s,%d,%d,%d)\n",
+        args->filter, args->initial, args->restart, args->single);
+
+    args->query_reply_len = 0;
+
+    if (args->initial || args->restart) {
+        ZeroMemory(&state->cookie, sizeof(nfs41_readdir_cookie));
+        if (!state->cookie.cookie)
+            dprintf(1, "initializing the 1st readdir cookie\n");
+        else if (args->restart)
+            dprintf(1, "restarting; clearing previous cookie %llu\n",
+                state->cookie.cookie);
+        else if (args->initial)
+            dprintf(1, "*** initial; clearing previous cookie %llu!\n",
+                state->cookie.cookie);
+    } else if (!state->cookie.cookie) {
+        dprintf(1, "handle_nfs41_readdir: EOF\n");
+        status = ERROR_NO_MORE_FILES;
+        goto out;
+    }
+
+    entry_buf = calloc(max_buf_len, sizeof(unsigned char));
+    if (entry_buf == NULL) {
+        status = GetLastError();
+        goto out_free_cookie;
+    }
+fetch_entries:
+    entry_buf_len = max_buf_len;
+
+    nfs41_superblock_getattr_mask(state->file.fh.superblock, &attr_request);
+    attr_request.arr[0] |= FATTR4_WORD0_RDATTR_ERROR;
+
+    if (strchr(args->filter, FILTER_STAR) || strchr(args->filter, FILTER_QM)) {
+        /* use READDIR for wildcards */
+
+        uint32_t dots_len = 0;
+        uint32_t *dots_next_offset = NULL;
+
+        if (args->filter[0] == '*' && args->filter[1] == '\0') {
+            status = readdir_add_dots(args, entry_buf,
+                entry_buf_len, &dots_len, &dots_next_offset);
+            if (status)
+                goto out_free_cookie;
+            entry_buf_len -= dots_len;
+        }
+
+        if (dots_len && args->single) {
+            dprintf(2, "skipping nfs41_readdir because the single query "
+                "will use . or ..\n");
+            entry_buf_len = 0;
+            eof = 0;
+        } else {
+            dprintf(2, "calling nfs41_readdir with cookie %llu\n",
+                state->cookie.cookie);
+            status = nfs41_readdir(state->session, &state->file,
+                &attr_request, &state->cookie, entry_buf + dots_len,
+                &entry_buf_len, &eof);
+            if (status) {
+                dprintf(1, "nfs41_readdir failed with %s\n",
+                    nfs_error_string(status));
+                status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+                goto out_free_cookie;
+            }
+        }
+
+        if (!entry_buf_len && dots_next_offset)
+            *dots_next_offset = 0;
+        entry_buf_len += dots_len;
+    } else {
+        /* use LOOKUP for single files */
+        nfs41_readdir_entry *entry = (nfs41_readdir_entry*)entry_buf;
+        entry->cookie = 0;
+        entry->name_len = (uint32_t)strlen(args->filter) + 1;
+        StringCbCopyA(entry->name, entry->name_len, args->filter);
+        entry->next_entry_offset = 0;
+
+        status = lookup_entry(upcall->root_ref,
+             state->session, &state->file, entry);
+        if (status) {
+            dprintf(1, "single_lookup failed with %d\n", status);
+            goto out_free_cookie;
+        }
+        entry_buf_len = entry->name_len +
+                FIELD_OFFSET(nfs41_readdir_entry, name);
+
+        eof = 1;
+    }
+
+    status = args->initial ? ERROR_FILE_NOT_FOUND : ERROR_NO_MORE_FILES;
+
+    if (entry_buf_len) {
+        unsigned char *entry_pos = entry_buf;
+        unsigned char *dst_pos = args->kbuf;
+        uint32_t dst_len = args->buf_len;
+        nfs41_readdir_entry *entry;
+        PULONG offset, last_offset = NULL;
+
+        for (;;) {
+            entry = (nfs41_readdir_entry*)entry_pos;
+            offset = (PULONG)dst_pos; /* ULONG NextEntryOffset */
+
+            dprintf(2, "filter %s looking at %s with cookie %d\n",
+                args->filter, entry->name, entry->cookie);
+            if (readdir_filter((const char*)args->filter, entry->name)) {
+                if (readdir_copy_entry(args, entry, &dst_pos, &dst_len)) {
+                    eof = 0;
+                    dprintf(2, "not enough space to copy entry %s (cookie %d)\n",
+                        entry->name, entry->cookie);
+                    break;
+                }
+                last_offset = offset;
+                status = NO_ERROR;
+            }
+            state->cookie.cookie = entry->cookie;
+
+            /* last entry we got from the server */
+            if (!entry->next_entry_offset)
+                break;
+
+            /* we found our single entry, but the server has more */
+            if (args->single && last_offset) {
+                eof = 0;
+                break;
+            }
+            entry_pos += entry->next_entry_offset;
+        }
+        args->query_reply_len = args->buf_len - dst_len;
+        if (last_offset) {
+            *last_offset = 0;
+        } else if (!eof) {
+            dprintf(1, "no entries matched; fetch more\n");
+            goto fetch_entries;
+        }
+    }
+
+    if (eof) {
+        dprintf(1, "we don't need to save a cookie\n");
+        goto out_free_cookie;
+    } else
+        dprintf(1, "saving cookie %llu\n", state->cookie.cookie);
+
+out_free_entry:
+    free(entry_buf);
+out:
+    dprintf(1, "<- handle_nfs41_dirquery(%s,%d,%d,%d) returning ",
+        args->filter, args->initial, args->restart, args->single);
+    if (status) {
+        switch (status) {
+        case ERROR_FILE_NOT_FOUND:
+            dprintf(1, "ERROR_FILE_NOT_FOUND.\n");
+            break;
+        case ERROR_NO_MORE_FILES:
+            dprintf(1, "ERROR_NO_MORE_FILES.\n");
+            break;
+        case ERROR_BUFFER_OVERFLOW:
+            upcall->last_error = status;
+            status = ERROR_SUCCESS;
+            break;
+        default:
+            dprintf(1, "error code %d.\n", status);
+            break;
+        }
+    } else {
+        dprintf(1, "success!\n");
+    }
+    return status;
+out_free_cookie:
+    state->cookie.cookie = 0;
+    goto out_free_entry;
+}
+
+static int marshall_readdir(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    int status;
+    readdir_upcall_args *args = &upcall->args.readdir;
+
+    status = safe_write(&buffer, length, &args->query_reply_len, sizeof(args->query_reply_len));
+    return status;
+}
+
+
+const nfs41_upcall_op nfs41_op_readdir = {
+    parse_readdir,
+    handle_readdir,
+    marshall_readdir
+};
diff --git a/reactos/base/services/nfsd/readwrite.c b/reactos/base/services/nfsd/readwrite.c
new file mode 100644 (file)
index 0000000..6844df5
--- /dev/null
@@ -0,0 +1,325 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+
+#include "nfs41_ops.h"
+#include "name_cache.h"
+#include "upcall.h"
+#include "daemon_debug.h"
+#include "util.h"
+
+
+/* number of times to retry on write/commit verifier mismatch */
+#define MAX_WRITE_RETRIES 6
+
+
+const stateid4 special_read_stateid = {0xffffffff, 
+    {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
+
+static int parse_rw(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    readwrite_upcall_args *args = &upcall->args.rw;
+
+    status = safe_read(&buffer, &length, &args->len, sizeof(args->len));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->offset, sizeof(args->offset));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->buffer, sizeof(args->buffer));
+    if (status) goto out;
+
+    dprintf(1, "parsing %s len=%lu offset=%llu buf=%p\n", 
+            opcode2string(upcall->opcode), args->len, args->offset, args->buffer);
+out:
+    return status;
+}
+
+/* NFS41_READ */
+static int read_from_mds(
+    IN nfs41_upcall *upcall,
+    IN stateid_arg *stateid)
+{
+    nfs41_session *session = upcall->state_ref->session;
+    nfs41_path_fh *file = &upcall->state_ref->file;
+    readwrite_upcall_args *args = &upcall->args.rw;
+    int status = 0;
+    bool_t eof;
+    unsigned char *p = args->buffer;
+    ULONG to_rcv = args->len, reloffset = 0, len = 0;
+    const uint32_t maxreadsize = max_read_size(session, &file->fh);
+
+    if (to_rcv > maxreadsize)
+        dprintf(1, "handle_nfs41_read: reading %d in chunks of %d\n",
+            to_rcv, maxreadsize);
+
+    while(to_rcv > 0) {
+        uint32_t bytes_read = 0, chunk = min(to_rcv, maxreadsize);
+
+        status = nfs41_read(session, file, stateid, args->offset + reloffset, chunk, 
+                p, &bytes_read, &eof);
+        if (status == NFS4ERR_OPENMODE && !len) {
+            stateid->type = STATEID_SPECIAL;
+            memcpy(&stateid->stateid, &special_read_stateid, sizeof(stateid4));
+            continue;
+        } else if (status && !len) {
+            status = nfs_to_windows_error(status, ERROR_NET_WRITE_FAULT);
+            goto out;
+        }
+
+        p += bytes_read;
+        to_rcv -= bytes_read;
+        len += bytes_read;
+        args->offset += bytes_read;
+        if (status) {
+            status = NO_ERROR;
+            break;
+        }
+        if (eof) {
+            if (!len)
+                status = ERROR_HANDLE_EOF;
+            break;
+        }
+    }
+out:
+    args->out_len = len;
+    return status;
+}
+
+static int read_from_pnfs(
+    IN nfs41_upcall *upcall,
+    IN stateid_arg *stateid)
+{
+    readwrite_upcall_args *args = &upcall->args.rw;
+    pnfs_layout_state *layout;
+    enum pnfs_status pnfsstat;
+    int status = NO_ERROR;
+
+    if (pnfs_layout_state_open(upcall->state_ref, &layout)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out;
+    }
+
+    pnfsstat = pnfs_read(upcall->root_ref, upcall->state_ref, stateid, layout, 
+        args->offset, args->len, args->buffer, &args->out_len);
+    switch (pnfsstat) {
+    case PNFS_SUCCESS:
+        break;
+    case PNFS_READ_EOF:
+        status = ERROR_HANDLE_EOF;
+        break;
+    default:
+        status = ERROR_READ_FAULT;
+        break;
+    }
+out:
+    return status;
+}
+
+static int handle_read(nfs41_upcall *upcall)
+{
+    readwrite_upcall_args *args = &upcall->args.rw;
+    stateid_arg stateid;
+    ULONG pnfs_bytes_read = 0;
+    int status = NO_ERROR;
+
+    nfs41_open_stateid_arg(upcall->state_ref, &stateid);
+
+#ifdef PNFS_ENABLE_READ
+    status = read_from_pnfs(upcall, &stateid);
+
+    if (status == NO_ERROR || status == ERROR_HANDLE_EOF)
+        goto out;
+
+    if (args->out_len) {
+        pnfs_bytes_read = args->out_len;
+        args->out_len = 0;
+
+        args->offset += pnfs_bytes_read;
+        args->buffer += pnfs_bytes_read;
+        args->len -= pnfs_bytes_read;
+    }
+#endif
+
+    status = read_from_mds(upcall, &stateid);
+
+    args->out_len += pnfs_bytes_read;
+out:
+    return status;
+}
+
+
+/* NFS41_WRITE */
+static int write_to_mds(
+    IN nfs41_upcall *upcall,
+    IN stateid_arg *stateid)
+{
+    nfs41_session *session = upcall->state_ref->session;
+    nfs41_path_fh *file = &upcall->state_ref->file;
+    readwrite_upcall_args *args = &upcall->args.rw;
+    nfs41_write_verf verf;
+    enum stable_how4 stable, committed;
+    unsigned char *p;
+    const uint32_t maxwritesize = max_write_size(session, &file->fh);
+    uint32_t to_send, reloffset, len;
+    int status = 0;
+    /* on write verifier mismatch, retry N times before failing */
+    uint32_t retries = MAX_WRITE_RETRIES;
+    nfs41_file_info info = { 0 };
+
+retry_write:
+    p = args->buffer;
+    to_send = args->len;
+    reloffset = 0;
+    len = 0;
+    stable = to_send <= maxwritesize ? FILE_SYNC4 : UNSTABLE4;
+    committed = FILE_SYNC4;
+
+    if (to_send > maxwritesize)
+        dprintf(1, "handle_nfs41_write: writing %d in chunks of %d\n",
+            to_send, maxwritesize);
+
+    while(to_send > 0) {
+        uint32_t bytes_written = 0, chunk = min(to_send, maxwritesize);
+
+        status = nfs41_write(session, file, stateid, p, chunk,
+            args->offset + reloffset, stable, &bytes_written, &verf, &info);
+        if (status && !len)
+            goto out;
+        p += bytes_written;
+        to_send -= bytes_written;
+        len += bytes_written;
+        reloffset += bytes_written;
+        if (status) {
+            status = 0;
+            break;
+        }
+        if (!verify_write(&verf, &committed)) {
+            if (retries--) goto retry_write;
+            goto out_verify_failed;
+        }
+    }
+    if (committed != FILE_SYNC4) {
+        dprintf(1, "sending COMMIT for offset=%d and len=%d\n", args->offset, len);
+        status = nfs41_commit(session, file, args->offset, len, 1, &verf, &info);
+        if (status)
+            goto out;
+
+        if (!verify_commit(&verf)) {
+            if (retries--) goto retry_write;
+            goto out_verify_failed;
+        }
+    } else if (stable == UNSTABLE4) {
+               nfs41_file_info info;
+        bitmap4 attr_request; 
+        nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request);
+               status = nfs41_getattr(session, file, &attr_request, &info);
+               if (status)
+                       goto out;
+       }
+    args->ctime = info.change;
+out:
+    args->out_len = len;
+    return nfs_to_windows_error(status, ERROR_NET_WRITE_FAULT);
+
+out_verify_failed:
+    len = 0;
+    status = NFS4ERR_IO;
+    goto out;
+}
+
+static int write_to_pnfs(
+    IN nfs41_upcall *upcall,
+    IN stateid_arg *stateid)
+{
+    readwrite_upcall_args *args = &upcall->args.rw;
+    pnfs_layout_state *layout;
+    int status = NO_ERROR;
+    nfs41_file_info info = { 0 };
+
+    if (pnfs_layout_state_open(upcall->state_ref, &layout)) {
+        status = ERROR_NOT_SUPPORTED;
+        goto out;
+    }
+
+    if (pnfs_write(upcall->root_ref, upcall->state_ref, stateid, layout, 
+            args->offset, args->len, args->buffer, &args->out_len, &info)) {
+        status = ERROR_WRITE_FAULT;
+        goto out;
+    }
+    args->ctime = info.change;
+out:
+    return status;
+}
+
+static int handle_write(nfs41_upcall *upcall)
+{
+    readwrite_upcall_args *args = &upcall->args.rw;
+    stateid_arg stateid;
+    uint32_t pnfs_bytes_written = 0;
+    int status;
+
+    nfs41_open_stateid_arg(upcall->state_ref, &stateid);
+
+#ifdef PNFS_ENABLE_WRITE
+    status = write_to_pnfs(upcall, &stateid);
+    if (args->out_len) {
+        pnfs_bytes_written = args->out_len;
+        args->out_len = 0;
+
+        args->offset += pnfs_bytes_written;
+        args->buffer += pnfs_bytes_written;
+        args->len -= pnfs_bytes_written;
+
+        if (args->len == 0)
+            goto out;
+    }
+#endif
+
+    status = write_to_mds(upcall, &stateid);
+out:
+    args->out_len += pnfs_bytes_written;
+    return status;
+}
+
+static int marshall_rw(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    readwrite_upcall_args *args = &upcall->args.rw;
+    int status;
+    status = safe_write(&buffer, length, &args->out_len, sizeof(args->out_len));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->ctime, sizeof(args->ctime));
+out:
+    return status;
+}
+
+
+const nfs41_upcall_op nfs41_op_read = {
+    parse_rw,
+    handle_read,
+    marshall_rw
+};
+const nfs41_upcall_op nfs41_op_write = {
+    parse_rw,
+    handle_write,
+    marshall_rw
+};
diff --git a/reactos/base/services/nfsd/recovery.c b/reactos/base/services/nfsd/recovery.c
new file mode 100644 (file)
index 0000000..e6ee5b1
--- /dev/null
@@ -0,0 +1,855 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <time.h>
+
+#include "recovery.h"
+#include "delegation.h"
+#include "nfs41_callback.h"
+#include "nfs41_compound.h"
+#include "nfs41_ops.h"
+#include "daemon_debug.h"
+
+
+/* session/client recovery uses a lock and condition variable in nfs41_client
+ * to prevent multiple threads from attempting to recover at the same time */
+bool_t nfs41_recovery_start_or_wait(
+    IN nfs41_client *client)
+{
+    bool_t status = TRUE;
+
+    EnterCriticalSection(&client->recovery.lock);
+
+    if (!client->recovery.in_recovery) {
+        dprintf(1, "Entering recovery mode for client %llu\n", client->clnt_id);
+        client->recovery.in_recovery = TRUE;
+    } else {
+        status = FALSE;
+        dprintf(1, "Waiting for recovery of client %llu\n", client->clnt_id);
+        while (client->recovery.in_recovery)
+            SleepConditionVariableCS(&client->recovery.cond,
+                &client->recovery.lock, INFINITE);
+        dprintf(1, "Woke up after recovery of client %llu\n", client->clnt_id);
+    }
+
+    LeaveCriticalSection(&client->recovery.lock);
+    return status;
+}
+
+void nfs41_recovery_finish(
+    IN nfs41_client *client)
+{
+    EnterCriticalSection(&client->recovery.lock);
+    dprintf(1, "Finished recovery for client %llu\n", client->clnt_id);
+    client->recovery.in_recovery = FALSE;
+    WakeAllConditionVariable(&client->recovery.cond);
+    LeaveCriticalSection(&client->recovery.lock);
+}
+
+
+/* session/client/state recovery */
+int nfs41_recover_session(
+    IN nfs41_session *session,
+    IN bool_t client_state_lost)
+{
+    enum nfsstat4 status = NFS4_OK;
+
+restart_recovery:
+    /* recover the session */
+    status = nfs41_session_renew(session);
+
+    if (status == NFS4ERR_STALE_CLIENTID) {
+        /* recover the client */
+        client_state_lost = TRUE;
+        status = nfs41_client_renew(session->client);
+        if (status == NFS4_OK)
+            goto restart_recovery; /* resume session recovery */
+
+        eprintf("nfs41_client_renew() failed with %d\n", status);
+    } else if (status) {
+        eprintf("nfs41_session_renew() failed with %d\n", status);
+    } else if (client_state_lost) {
+        /* recover the client's state */
+        status = nfs41_recover_client_state(session, session->client);
+        if (status == NFS4ERR_BADSESSION)
+            goto restart_recovery;
+    }
+    return status;
+}
+
+void nfs41_recover_sequence_flags(
+    IN nfs41_session *session,
+    IN uint32_t flags)
+{
+    const uint32_t revoked = flags &
+        (SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED
+        | SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED
+        | SEQ4_STATUS_ADMIN_STATE_REVOKED
+        | SEQ4_STATUS_RECALLABLE_STATE_REVOKED);
+    const uint32_t restarted = flags &
+        SEQ4_STATUS_RESTART_RECLAIM_NEEDED;
+
+    /* no state recovery needed */
+    if (revoked == 0 && restarted == 0)
+        return;
+
+    if (!nfs41_recovery_start_or_wait(session->client))
+        return;
+
+    if (revoked) {
+        /* free stateids and attempt to recover them */
+        nfs41_client_state_revoked(session, session->client, revoked);
+
+        /* if RESTART_RECLAIM_NEEDED is also set, just do RECLAIM_COMPLETE */
+        if (restarted) nfs41_reclaim_complete(session);
+
+    } else if (restarted) {
+        /* do server reboot state recovery */
+        uint32_t status = nfs41_recover_client_state(session, session->client);
+        if (status == NFS4ERR_BADSESSION) {
+            /* recover the session and finish state recovery */
+            nfs41_recover_session(session, TRUE);
+        }
+    }
+
+    nfs41_recovery_finish(session->client);
+}
+
+
+/* client state recovery for server reboot or lease expiration */
+static int recover_open_grace(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN nfs41_path_fh *file,
+    IN state_owner4 *owner,
+    IN uint32_t access,
+    IN uint32_t deny,
+    OUT stateid4 *stateid,
+    OUT open_delegation4 *delegation)
+{
+    /* reclaim the open stateid with CLAIM_PREVIOUS */
+    open_claim4 claim;
+    claim.claim = CLAIM_PREVIOUS;
+    claim.u.prev.delegate_type = delegation->type;
+
+    return nfs41_open(session, parent, file, owner, &claim, access, deny, 
+        OPEN4_NOCREATE, 0, NULL, FALSE, stateid, delegation, NULL);
+}
+
+static int recover_open_no_grace(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *parent,
+    IN nfs41_path_fh *file,
+    IN state_owner4 *owner,
+    IN uint32_t access,
+    IN uint32_t deny,
+    OUT stateid4 *stateid,
+    OUT open_delegation4 *delegation)
+{
+    open_claim4 claim;
+    int status;
+
+    if (delegation->type != OPEN_DELEGATE_NONE) {
+        /* attempt out-of-grace recovery with CLAIM_DELEGATE_PREV */
+        claim.claim = CLAIM_DELEGATE_PREV;
+        claim.u.deleg_prev.filename = &file->name;
+
+        status = nfs41_open(session, parent, file, owner,
+            &claim, access, deny, OPEN4_NOCREATE, 0, NULL, FALSE,
+            stateid, delegation, NULL);
+        if (status == NFS4_OK || status == NFS4ERR_BADSESSION)
+            goto out;
+
+        /* server support for CLAIM_DELEGATE_PREV is optional;
+         * fall back to CLAIM_NULL on errors */
+    }
+
+    /* attempt out-of-grace recovery with CLAIM_NULL */
+    claim.claim = CLAIM_NULL;
+    claim.u.null.filename = &file->name;
+
+    /* ask nicely for the delegation we had */
+    if (delegation->type == OPEN_DELEGATE_READ)
+        access |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG;
+    else if (delegation->type == OPEN_DELEGATE_WRITE)
+        access |= OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG;
+
+    status = nfs41_open(session, parent, file, owner,
+        &claim, access, deny, OPEN4_NOCREATE, 0, NULL, FALSE,
+        stateid, delegation, NULL);
+out:
+    return status;
+}
+
+static int recover_open(
+    IN nfs41_session *session,
+    IN nfs41_open_state *open,
+    IN OUT bool_t *grace)
+{
+    open_delegation4 delegation = { 0 };
+    stateid4 stateid = { 0 };
+    int status = NFS4ERR_BADHANDLE;
+
+    /* check for an associated delegation */
+    AcquireSRWLockExclusive(&open->lock);
+    if (open->delegation.state) {
+        nfs41_delegation_state *deleg = open->delegation.state;
+        if (deleg->revoked) {
+            /* reclaim the delegation along with the open */
+            AcquireSRWLockShared(&deleg->lock);
+            delegation.type = deleg->state.type;
+            ReleaseSRWLockShared(&deleg->lock);
+        } else if (deleg->state.recalled) {
+            /* we'll need an open stateid regardless */
+        } else if (list_empty(&open->locks.list)) {
+            /* if there are locks, we need an open stateid to
+             * reclaim them; otherwise, the open can be delegated */
+            open->do_close = FALSE;
+            status = NFS4_OK;
+        }
+    }
+    ReleaseSRWLockExclusive(&open->lock);
+
+    if (status == NFS4_OK) /* use existing delegation */
+        goto out;
+
+    if (*grace) {
+        status = recover_open_grace(session, &open->parent, &open->file,
+            &open->owner, open->share_access, open->share_deny,
+            &stateid, &delegation);
+        if (status == NFS4ERR_NO_GRACE) {
+            *grace = FALSE;
+            /* send RECLAIM_COMPLETE before any out-of-grace recovery */
+            nfs41_reclaim_complete(session);
+        }
+    }
+    if (!*grace) {
+        status = recover_open_no_grace(session, &open->parent, &open->file,
+            &open->owner, open->share_access, open->share_deny,
+            &stateid, &delegation);
+    }
+
+    if (status)
+        goto out;
+
+    AcquireSRWLockExclusive(&open->lock);
+    /* update the open stateid */
+    memcpy(&open->stateid, &stateid, sizeof(stateid4));
+    open->do_close = TRUE;
+
+    if (open->delegation.state) {
+        nfs41_delegation_state *deleg = open->delegation.state;
+        if (deleg->revoked) {
+            /* update delegation state */
+            AcquireSRWLockExclusive(&deleg->lock);
+            if (delegation.type != OPEN_DELEGATE_READ &&
+                delegation.type != OPEN_DELEGATE_WRITE) {
+                eprintf("recover_open() got delegation type %u, "
+                    "expected %u\n", delegation.type, deleg->state.type);
+            } else {
+                memcpy(&deleg->state, &delegation, sizeof(open_delegation4));
+                deleg->revoked = FALSE;
+            }
+            ReleaseSRWLockExclusive(&deleg->lock);
+        }
+    } else /* granted a new delegation? */
+        nfs41_delegation_granted(session, &open->parent, &open->file,
+            &delegation, FALSE, &open->delegation.state);
+    ReleaseSRWLockExclusive(&open->lock);
+out:
+    return status;
+}
+
+static int recover_locks(
+    IN nfs41_session *session,
+    IN nfs41_open_state *open,
+    IN OUT bool_t *grace)
+{
+    stateid_arg stateid;
+    struct list_entry *entry;
+    nfs41_lock_state *lock;
+    int status = NFS4_OK;
+
+    AcquireSRWLockExclusive(&open->lock);
+
+    /* initialize the open stateid for the first lock request */
+    memcpy(&stateid.stateid, &open->stateid, sizeof(stateid4));
+    stateid.type = STATEID_OPEN;
+    stateid.open = open;
+    stateid.delegation = NULL;
+
+    /* recover any locks for this open */
+    list_for_each(entry, &open->locks.list) {
+        lock = list_container(entry, nfs41_lock_state, open_entry);
+        if (lock->delegated)
+            continue;
+
+        if (*grace) {
+            status = nfs41_lock(session, &open->file, &open->owner, 
+                lock->exclusive ? WRITE_LT : READ_LT, lock->offset, 
+                lock->length, TRUE, FALSE, &stateid);
+            if (status == NFS4ERR_NO_GRACE) {
+                *grace = FALSE;
+                /* send RECLAIM_COMPLETE before any out-of-grace recovery */
+                nfs41_reclaim_complete(session);
+            }
+        }
+        if (!*grace) {
+            /* attempt out-of-grace recovery with a normal LOCK */
+            status = nfs41_lock(session, &open->file, &open->owner, 
+                lock->exclusive ? WRITE_LT : READ_LT, lock->offset, 
+                lock->length, FALSE, FALSE, &stateid);
+        }
+        if (status == NFS4ERR_BADSESSION)
+            break;
+    }
+
+    if (status != NFS4ERR_BADSESSION) {
+        /* if we got a lock stateid back, save the lock with the open */
+        if (stateid.type == STATEID_LOCK)
+            memcpy(&open->locks.stateid, &stateid.stateid, sizeof(stateid4));
+        else
+            open->locks.stateid.seqid = 0;
+    }
+
+    ReleaseSRWLockExclusive(&open->lock);
+    return status;
+}
+
+/* delegation recovery via WANT_DELEGATION */
+static int recover_delegation_want(
+    IN nfs41_session *session,
+    IN nfs41_delegation_state *deleg,
+    IN OUT bool_t *grace)
+{
+    deleg_claim4 claim;
+    open_delegation4 delegation = { 0 };
+    uint32_t want_flags = 0;
+    int status = NFS4_OK;
+
+    AcquireSRWLockShared(&deleg->lock);
+    delegation.type = deleg->state.type;
+    ReleaseSRWLockShared(&deleg->lock);
+
+    if (delegation.type == OPEN_DELEGATE_READ)
+        want_flags |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG;
+    else
+        want_flags |= OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG;
+
+    if (*grace) {
+        /* recover the delegation with WANT_DELEGATION/CLAIM_PREVIOUS */
+        claim.claim = CLAIM_PREVIOUS;
+        claim.prev_delegate_type = delegation.type;
+
+        status = nfs41_want_delegation(session, &deleg->file, &claim, 
+            want_flags, FALSE, &delegation);
+        if (status == NFS4ERR_NO_GRACE) {
+            *grace = FALSE;
+            /* send RECLAIM_COMPLETE before any out-of-grace recovery */
+            nfs41_reclaim_complete(session);
+        }
+    }
+    if (!*grace) {
+        /* attempt out-of-grace recovery with with CLAIM_DELEG_PREV_FH */
+        claim.claim = CLAIM_DELEG_PREV_FH;
+
+        status = nfs41_want_delegation(session, &deleg->file, &claim, 
+            want_flags, FALSE, &delegation);
+    }
+    if (status)
+        goto out;
+    
+    /* update delegation state */
+    AcquireSRWLockExclusive(&deleg->lock);
+    if (delegation.type != OPEN_DELEGATE_READ &&
+            delegation.type != OPEN_DELEGATE_WRITE) {
+        eprintf("recover_delegation_want() got delegation type %u, "
+            "expected %u\n", delegation.type, deleg->state.type);
+    } else {
+        memcpy(&deleg->state, &delegation, sizeof(open_delegation4));
+        deleg->revoked = FALSE;
+    }
+    ReleaseSRWLockExclusive(&deleg->lock);
+out:
+    return status;
+}
+
+/* delegation recovery via OPEN (requires corresponding CLOSE) */
+static int recover_delegation_open(
+    IN nfs41_session *session,
+    IN nfs41_delegation_state *deleg,
+    IN OUT bool_t *grace)
+{
+    state_owner4 owner;
+    open_delegation4 delegation = { 0 };
+    stateid_arg stateid;
+    uint32_t access = OPEN4_SHARE_ACCESS_READ;
+    uint32_t deny = OPEN4_SHARE_DENY_NONE;
+    int status = NFS4_OK;
+
+    /* choose the desired access mode based on delegation type */
+    AcquireSRWLockShared(&deleg->lock);
+    delegation.type = deleg->state.type;
+    if (delegation.type == OPEN_DELEGATE_WRITE)
+        access |= OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG;
+    else
+        access |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG;
+    ReleaseSRWLockShared(&deleg->lock);
+
+    /* construct a temporary open owner by concatenating the time
+     * in seconds with the delegation pointer */
+    time((time_t*)owner.owner);
+    memcpy(owner.owner + sizeof(time_t), deleg, sizeof(deleg));
+    owner.owner_len = sizeof(time_t) + sizeof(deleg);
+
+    if (*grace) {
+        status = recover_open_grace(session, &deleg->parent, &deleg->file,
+            &owner, access, deny, &stateid.stateid, &delegation);
+        if (status == NFS4ERR_NO_GRACE) {
+            *grace = FALSE;
+            /* send RECLAIM_COMPLETE before any out-of-grace recovery */
+            nfs41_reclaim_complete(session);
+        }
+    }
+    if (!*grace) {
+        status = recover_open_no_grace(session, &deleg->parent, &deleg->file,
+            &owner, access, deny, &stateid.stateid, &delegation);
+    }
+    if (status)
+        goto out;
+
+    /* update delegation state */
+    AcquireSRWLockExclusive(&deleg->lock);
+    if (delegation.type != OPEN_DELEGATE_READ &&
+            delegation.type != OPEN_DELEGATE_WRITE) {
+        eprintf("recover_delegation_open() got delegation type %u, "
+            "expected %u\n", delegation.type, deleg->state.type);
+    } else {
+        memcpy(&deleg->state, &delegation, sizeof(open_delegation4));
+        deleg->revoked = FALSE;
+    }
+    ReleaseSRWLockExclusive(&deleg->lock);
+
+    /* send CLOSE to free the open stateid */
+    stateid.open = NULL;
+    stateid.delegation = NULL;
+    stateid.type = STATEID_OPEN;
+    nfs41_close(session, &deleg->file, &stateid);
+out:
+    return status;
+}
+
+static int recover_delegation(
+    IN nfs41_session *session,
+    IN nfs41_delegation_state *deleg,
+    IN OUT bool_t *grace,
+    IN OUT bool_t *want_supported)
+{
+    int status;
+
+    /* 10.2.1. Delegation Recovery
+     * When a client needs to reclaim a delegation and there is no
+     * associated open, the client may use the CLAIM_PREVIOUS variant
+     * of the WANT_DELEGATION operation.  However, since the server is
+     * not required to support this operation, an alternative is to
+     * reclaim via a dummy OPEN together with the delegation using an
+     * OPEN of type CLAIM_PREVIOUS. */
+    if (*want_supported)
+        status = recover_delegation_want(session, deleg, grace);
+    else
+        status = NFS4ERR_NOTSUPP;
+
+    if (status == NFS4ERR_NOTSUPP) {
+        *want_supported = FALSE;
+        status = recover_delegation_open(session, deleg, grace);
+    }
+    return status;
+}
+
+int nfs41_recover_client_state(
+    IN nfs41_session *session,
+    IN nfs41_client *client)
+{
+    const struct cb_layoutrecall_args recall = { PNFS_LAYOUTTYPE_FILE,
+        PNFS_IOMODE_ANY, TRUE, { PNFS_RETURN_ALL } };
+    struct client_state *state = &session->client->state;
+    struct list_entry *entry;
+    nfs41_open_state *open;
+    nfs41_delegation_state *deleg;
+    bool_t grace = TRUE;
+    bool_t want_supported = TRUE;
+    int status = NFS4_OK;
+
+    EnterCriticalSection(&state->lock);
+
+    /* flag all delegations as revoked until successful recovery;
+     * recover_open() and recover_delegation_open() will only ask
+     * for delegations when revoked = TRUE */
+    list_for_each(entry, &state->delegations) {
+        deleg = list_container(entry, nfs41_delegation_state, client_entry);
+        deleg->revoked = TRUE;
+    }
+
+    /* recover each of the client's opens and associated delegations */
+    list_for_each(entry, &state->opens) {
+        open = list_container(entry, nfs41_open_state, client_entry);
+        status = recover_open(session, open, &grace);
+        if (status == NFS4_OK)
+            status = recover_locks(session, open, &grace);
+        if (status == NFS4ERR_BADSESSION)
+            goto unlock;
+    }
+
+    /* recover delegations that weren't associated with any opens */
+    list_for_each(entry, &state->delegations) {
+        deleg = list_container(entry, nfs41_delegation_state, client_entry);
+        if (deleg->revoked) {
+            status = recover_delegation(session,
+                deleg, &grace, &want_supported);
+            if (status == NFS4ERR_BADSESSION)
+                goto unlock;
+        }
+    }
+
+    /* return any delegations that were reclaimed as 'recalled' */
+    status = nfs41_client_delegation_recovery(client);
+unlock:
+    LeaveCriticalSection(&state->lock);
+
+    /* revoke all of the client's layouts */
+    pnfs_file_layout_recall(client, &recall);
+
+    if (grace && status != NFS4ERR_BADSESSION) {
+        /* send reclaim_complete, but don't fail on errors */
+        nfs41_reclaim_complete(session);
+    }
+    return status;
+}
+
+static uint32_t stateid_array(
+    IN struct list_entry *delegations,
+    IN struct list_entry *opens,
+    OUT stateid_arg **stateids_out,
+    OUT uint32_t **statuses_out)
+{
+    struct list_entry *entry;
+    nfs41_open_state *open;
+    nfs41_delegation_state *deleg;
+    stateid_arg *stateids = NULL;
+    uint32_t *statuses = NULL;
+    uint32_t i = 0, count = 0;
+
+    /* count how many stateids the client needs to test */
+    list_for_each(entry, delegations)
+        count++;
+    list_for_each(entry, opens)
+        count += 3; /* open and potentially lock and layout */
+
+    if (count == 0)
+        goto out;
+
+    /* allocate the stateid and status arrays */
+    stateids = calloc(count, sizeof(stateid_arg));
+    if (stateids == NULL)
+        goto out_err;
+    statuses = calloc(count, sizeof(uint32_t));
+    if (statuses == NULL)
+        goto out_err;
+    memset(statuses, NFS4ERR_BAD_STATEID, count * sizeof(uint32_t));
+
+    /* copy stateids into the array */
+    list_for_each(entry, delegations) {
+        deleg = list_container(entry, nfs41_delegation_state, client_entry);
+        AcquireSRWLockShared(&deleg->lock);
+        /* delegation stateid */
+        memcpy(&stateids[i].stateid, &deleg->state.stateid, sizeof(stateid4));
+        stateids[i].type = STATEID_DELEG_FILE;
+        stateids[i].delegation = deleg;
+        i++;
+        ReleaseSRWLockShared(&deleg->lock);
+    }
+
+    list_for_each(entry, opens) {
+        open = list_container(entry, nfs41_open_state, client_entry);
+
+        AcquireSRWLockShared(&open->lock);
+        /* open stateid */
+        memcpy(&stateids[i].stateid, &open->stateid, sizeof(stateid4));
+        stateids[i].type = STATEID_OPEN;
+        stateids[i].open = open;
+        i++;
+
+        if (open->locks.stateid.seqid) { /* lock stateid? */
+            memcpy(&stateids[i].stateid, &open->locks.stateid, sizeof(stateid4));
+            stateids[i].type = STATEID_LOCK;
+            stateids[i].open = open;
+            i++;
+        }
+
+        if (open->layout) { /* layout stateid? */
+            AcquireSRWLockShared(&open->layout->lock);
+            if (open->layout->stateid.seqid) {
+                memcpy(&stateids[i].stateid, &open->layout->stateid, sizeof(stateid4));
+                stateids[i].type = STATEID_LAYOUT;
+                stateids[i].open = open;
+                i++;
+            }
+            ReleaseSRWLockShared(&open->layout->lock);
+        }
+        ReleaseSRWLockShared(&open->lock);
+    }
+
+    count = i;
+    *stateids_out = stateids;
+    *statuses_out = statuses;
+out:
+    return count;
+
+out_err:
+    free(stateids);
+    free(statuses);
+    count = 0;
+    goto out;
+}
+
+void nfs41_client_state_revoked(
+    IN nfs41_session *session,
+    IN nfs41_client *client,
+    IN uint32_t revoked)
+{
+    const struct cb_layoutrecall_args recall = { PNFS_LAYOUTTYPE_FILE,
+        PNFS_IOMODE_ANY, TRUE, { PNFS_RETURN_ALL } };
+    struct list_entry empty, *opens;
+    struct client_state *clientstate = &session->client->state;
+    stateid_arg *stateids = NULL;
+    uint32_t *statuses = NULL;
+    uint32_t i, count;
+    bool_t grace = TRUE;
+    bool_t want_supported = TRUE;
+
+    EnterCriticalSection(&clientstate->lock);
+
+    if (revoked == SEQ4_STATUS_RECALLABLE_STATE_REVOKED) {
+        /* only delegations were revoked. use an empty list for opens */
+        list_init(&empty);
+        opens = &empty;
+    } else {
+        opens = &clientstate->opens;
+    }
+
+    /* get an array of the client's stateids */
+    count = stateid_array(&clientstate->delegations,
+        opens, &stateids, &statuses);
+    if (count == 0)
+        goto out;
+
+    /* determine which stateids were revoked with TEST_STATEID */
+    if ((revoked & SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED) == 0)
+        nfs41_test_stateid(session, stateids, count, statuses);
+
+    /* free all revoked stateids with FREE_STATEID */
+    for (i = 0; i < count; i++)
+        if (statuses[i])
+            nfs41_free_stateid(session, &stateids[i].stateid);
+
+    /* revoke all of the client's layouts */
+    pnfs_file_layout_recall(client, &recall);
+
+    /* recover the revoked stateids */
+    for (i = 0; i < count; i++) {
+        if (statuses[i]) {
+            if (stateids[i].type == STATEID_DELEG_FILE)
+                stateids[i].delegation->revoked = TRUE;
+            else if (stateids[i].type == STATEID_OPEN)
+                recover_open(session, stateids[i].open, &grace);
+            else if (stateids[i].type == STATEID_LOCK)
+                recover_locks(session, stateids[i].open, &grace);
+        }
+    }
+    for (i = 0; i < count; i++) {
+        /* delegations that weren't recovered by recover_open() */
+        if (statuses[i] && stateids[i].type == STATEID_DELEG_FILE
+            && stateids[i].delegation->revoked)
+            recover_delegation(session, stateids[i].delegation,
+                &grace, &want_supported);
+    }
+
+    nfs41_client_delegation_recovery(client);
+out:
+    LeaveCriticalSection(&clientstate->lock);
+    free(stateids);
+    free(statuses);
+}
+
+
+static bool_t recover_stateid_open(
+    IN nfs_argop4 *argop,
+    IN stateid_arg *stateid)
+{
+    bool_t retry = FALSE;
+
+    if (stateid->open) {
+        stateid4 *source = &stateid->open->stateid;
+
+        /* if the source stateid is different, update and retry */
+        AcquireSRWLockShared(&stateid->open->lock);
+        if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
+            memcpy(&stateid->stateid, source, sizeof(stateid4));
+            retry = TRUE;
+        }
+        ReleaseSRWLockShared(&stateid->open->lock);
+    }
+    return retry;
+}
+
+static bool_t recover_stateid_lock(
+    IN nfs_argop4 *argop,
+    IN stateid_arg *stateid)
+{
+    bool_t retry = FALSE;
+
+    if (stateid->open) {
+        stateid4 *source = &stateid->open->locks.stateid;
+
+        /* if the source stateid is different, update and retry */
+        AcquireSRWLockShared(&stateid->open->lock);
+        if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
+            if (argop->op == OP_LOCK && source->seqid == 0) {
+                /* resend LOCK with an open stateid */
+                nfs41_lock_args *lock = (nfs41_lock_args*)argop->arg;
+                lock->locker.new_lock_owner = 1;
+                lock->locker.u.open_owner.open_stateid = stateid;
+                lock->locker.u.open_owner.lock_owner = &stateid->open->owner;
+                source = &stateid->open->stateid;
+            }
+
+            memcpy(&stateid->stateid, source, sizeof(stateid4));
+            retry = TRUE;
+        }
+        ReleaseSRWLockShared(&stateid->open->lock);
+    }
+    return retry;
+}
+
+static bool_t recover_stateid_delegation(
+    IN nfs_argop4 *argop,
+    IN stateid_arg *stateid)
+{
+    bool_t retry = FALSE;
+
+    if (stateid->open) {
+        /* if the source stateid is different, update and retry */
+        AcquireSRWLockShared(&stateid->open->lock);
+        if (argop->op == OP_OPEN && stateid->open->do_close) {
+            /* for nfs41_delegation_to_open(); if we've already reclaimed
+             * an open stateid, just fail this OPEN with BAD_STATEID */
+        } else if (stateid->open->delegation.state) {
+            nfs41_delegation_state *deleg = stateid->open->delegation.state;
+            stateid4 *source = &deleg->state.stateid;
+            AcquireSRWLockShared(&deleg->lock);
+            if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
+                memcpy(&stateid->stateid, source, sizeof(stateid4));
+                retry = TRUE;
+            }
+            ReleaseSRWLockShared(&deleg->lock);
+        }
+        ReleaseSRWLockShared(&stateid->open->lock);
+    } else if (stateid->delegation) {
+        nfs41_delegation_state *deleg = stateid->delegation;
+        stateid4 *source = &deleg->state.stateid;
+        AcquireSRWLockShared(&deleg->lock);
+        if (memcmp(&stateid->stateid, source, sizeof(stateid4))) {
+            memcpy(&stateid->stateid, source, sizeof(stateid4));
+            retry = TRUE;
+        }
+        ReleaseSRWLockShared(&deleg->lock);
+    }
+    return retry;
+}
+
+bool_t nfs41_recover_stateid(
+    IN nfs41_session *session,
+    IN nfs_argop4 *argop)
+{
+    stateid_arg *stateid = NULL;
+
+    /* get the stateid_arg from the operation's arguments */
+    if (argop->op == OP_OPEN) {
+        nfs41_op_open_args *open = (nfs41_op_open_args*)argop->arg;
+        if (open->claim->claim == CLAIM_DELEGATE_CUR)
+            stateid = open->claim->u.deleg_cur.delegate_stateid;
+        else if (open->claim->claim == CLAIM_DELEG_CUR_FH)
+            stateid = open->claim->u.deleg_cur_fh.delegate_stateid;
+    } else if (argop->op == OP_CLOSE) {
+        nfs41_op_close_args *close = (nfs41_op_close_args*)argop->arg;
+        stateid = close->stateid;
+    } else if (argop->op == OP_READ) {
+        nfs41_read_args *read = (nfs41_read_args*)argop->arg;
+        stateid = read->stateid;
+    } else if (argop->op == OP_WRITE) {
+        nfs41_write_args *write = (nfs41_write_args*)argop->arg;
+        stateid = write->stateid;
+    } else if (argop->op == OP_LOCK) {
+        nfs41_lock_args *lock = (nfs41_lock_args*)argop->arg;
+        if (lock->locker.new_lock_owner)
+            stateid = lock->locker.u.open_owner.open_stateid;
+        else
+            stateid = lock->locker.u.lock_owner.lock_stateid;
+    } else if (argop->op == OP_LOCKU) {
+        nfs41_locku_args *locku = (nfs41_locku_args*)argop->arg;
+        stateid = locku->lock_stateid;
+    } else if (argop->op == OP_SETATTR) {
+        nfs41_setattr_args *setattr = (nfs41_setattr_args*)argop->arg;
+        stateid = setattr->stateid;
+    } else if (argop->op == OP_LAYOUTGET) {
+        pnfs_layoutget_args *lget = (pnfs_layoutget_args*)argop->arg;
+        stateid = lget->stateid;
+    } else if (argop->op == OP_DELEGRETURN) {
+        nfs41_delegreturn_args *dr = (nfs41_delegreturn_args*)argop->arg;
+        stateid = dr->stateid;
+    }
+    if (stateid == NULL)
+        return FALSE;
+
+    /* if there's recovery in progress, wait for it to finish */
+    EnterCriticalSection(&session->client->recovery.lock);
+    while (session->client->recovery.in_recovery)
+        SleepConditionVariableCS(&session->client->recovery.cond,
+            &session->client->recovery.lock, INFINITE);
+    LeaveCriticalSection(&session->client->recovery.lock);
+
+    switch (stateid->type) {
+    case STATEID_OPEN:
+        return recover_stateid_open(argop, stateid);
+
+    case STATEID_LOCK:
+        return recover_stateid_lock(argop, stateid);
+
+    case STATEID_DELEG_FILE:
+        return recover_stateid_delegation(argop, stateid);
+
+    default:
+        eprintf("%s can't recover stateid type %u\n",
+            nfs_opnum_to_string(argop->op), stateid->type);
+        break;
+    }
+    return FALSE;
+}
diff --git a/reactos/base/services/nfsd/recovery.h b/reactos/base/services/nfsd/recovery.h
new file mode 100644 (file)
index 0000000..6bd63d3
--- /dev/null
@@ -0,0 +1,59 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 RECOVERY_H
+#define RECOVERY_H
+
+#include "nfs41.h"
+
+
+/* session/client recovery uses a lock and condition variable in nfs41_client
+ * to prevent multiple threads from attempting to recover at the same time */
+bool_t nfs41_recovery_start_or_wait(
+    IN nfs41_client *client);
+
+void nfs41_recovery_finish(
+    IN nfs41_client *client);
+
+
+int nfs41_recover_session(
+    IN nfs41_session *session,
+    IN bool_t client_state_lost);
+
+void nfs41_recover_sequence_flags(
+    IN nfs41_session *session,
+    IN uint32_t flags);
+
+int nfs41_recover_client_state(
+    IN nfs41_session *session,
+    IN nfs41_client *client);
+
+void nfs41_client_state_revoked(
+    IN nfs41_session *session,
+    IN nfs41_client *client,
+    IN uint32_t revoked);
+
+struct __nfs_argop4;
+bool_t nfs41_recover_stateid(
+    IN nfs41_session *session,
+    IN struct __nfs_argop4 *argop);
+
+#endif /* RECOVERY_H */
diff --git a/reactos/base/services/nfsd/service.c b/reactos/base/services/nfsd/service.c
new file mode 100644 (file)
index 0000000..c31fd14
--- /dev/null
@@ -0,0 +1,607 @@
+/*---------------------------------------------------------------------------
+THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+PARTICULAR PURPOSE.
+
+Copyright (C) Microsoft Corporation.  All rights reserved.
+
+MODULE:   service.c
+
+PURPOSE:  Implements functions required by all Windows NT services
+
+FUNCTIONS:
+  main(int argc, char **argv);
+  service_ctrl(DWORD dwCtrlCode);
+  service_main(DWORD dwArgc, LPTSTR *lpszArgv);
+  CmdInstallService();
+  CmdRemoveService();
+  CmdDebugService(int argc, char **argv);
+  ControlHandler ( DWORD dwCtrlType );
+  GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );
+
+  ---------------------------------------------------------------------------*/
+#include <windows.h>
+#ifndef STANDALONE_NFSD
+#include <stdio.h>
+#include <stdlib.h>
+#include <process.h>
+#include <tchar.h>
+
+#include "service.h"
+
+// internal variables
+SERVICE_STATUS          ssStatus;       // current status of the service
+SERVICE_STATUS_HANDLE   sshStatusHandle;
+DWORD                   dwErr = 0;
+BOOL                    bDebug = FALSE;
+TCHAR                   szErr[256];
+
+// internal function prototypes
+VOID WINAPI service_ctrl(DWORD dwCtrlCode);
+VOID WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv);
+VOID CmdInstallService();
+VOID CmdRemoveService();
+VOID CmdDebugService(int argc, char **argv);
+BOOL WINAPI ControlHandler ( DWORD dwCtrlType );
+LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );
+
+//
+//  FUNCTION: main
+//
+//  PURPOSE: entrypoint for service
+//
+//  PARAMETERS:
+//    argc - number of command line arguments
+//    argv - array of command line arguments
+//
+//  RETURN VALUE:
+//    none
+//
+//  COMMENTS:
+//    main() either performs the command line task, or
+//    call StartServiceCtrlDispatcher to register the
+//    main service thread.  When the this call returns,
+//    the service has stopped, so exit.
+//
+void __cdecl main(int argc, char **argv)
+{
+   SERVICE_TABLE_ENTRY dispatchTable[] =
+   {
+      { TEXT(SZSERVICENAME), (LPSERVICE_MAIN_FUNCTION)service_main},
+      { NULL, NULL}
+   };
+
+   if ( (argc > 1) &&
+        ((*argv[1] == '-') || (*argv[1] == '/')) )
+   {
+      if ( _stricmp( "install", argv[1]+1 ) == 0 )
+      {
+         CmdInstallService();
+      }
+      else if ( _stricmp( "remove", argv[1]+1 ) == 0 )
+      {
+         CmdRemoveService();
+      }
+      else if ( _stricmp( "debug", argv[1]+1 ) == 0 )
+      {
+         bDebug = TRUE;
+         CmdDebugService(argc, argv);
+      }
+      else
+      {
+         goto dispatch;
+      }
+      exit(0);
+   }
+
+   // if it doesn't match any of the above parameters
+   // the service control manager may be starting the service
+   // so we must call StartServiceCtrlDispatcher
+   dispatch:
+   // this is just to be friendly
+   printf( "%s -install          to install the service\n", SZAPPNAME );
+   printf( "%s -remove           to remove the service\n", SZAPPNAME );
+   printf( "%s -debug <params>   to run as a console app for debugging\n", SZAPPNAME );
+   printf( "\nStartServiceCtrlDispatcher being called.\n" );
+   printf( "This may take several seconds.  Please wait.\n" );
+
+   if (!StartServiceCtrlDispatcher(dispatchTable))
+      AddToMessageLog(TEXT("StartServiceCtrlDispatcher failed."));
+}
+
+
+
+//
+//  FUNCTION: service_main
+//
+//  PURPOSE: To perform actual initialization of the service
+//
+//  PARAMETERS:
+//    dwArgc   - number of command line arguments
+//    lpszArgv - array of command line arguments
+//
+//  RETURN VALUE:
+//    none
+//
+//  COMMENTS:
+//    This routine performs the service initialization and then calls
+//    the user defined ServiceStart() routine to perform majority
+//    of the work.
+//
+void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv)
+{
+
+   // register our service control handler:
+   //
+   sshStatusHandle = RegisterServiceCtrlHandler( TEXT(SZSERVICENAME), service_ctrl);
+
+   if (!sshStatusHandle)
+      goto cleanup;
+
+   // SERVICE_STATUS members that don't change in example
+   //
+   ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+   ssStatus.dwServiceSpecificExitCode = 0;
+
+
+   // report the status to the service control manager.
+   //
+   if (!ReportStatusToSCMgr(
+                           SERVICE_START_PENDING, // service state
+                           NO_ERROR,              // exit code
+                           3000))                 // wait hint
+      goto cleanup;
+
+
+   ServiceStart( dwArgc, lpszArgv );
+
+   cleanup:
+
+   // try to report the stopped status to the service control manager.
+   //
+   if (sshStatusHandle)
+      (VOID)ReportStatusToSCMgr(
+                               SERVICE_STOPPED,
+                               dwErr,
+                               0);
+
+   return;
+}
+
+
+
+//
+//  FUNCTION: service_ctrl
+//
+//  PURPOSE: This function is called by the SCM whenever
+//           ControlService() is called on this service.
+//
+//  PARAMETERS:
+//    dwCtrlCode - type of control requested
+//
+//  RETURN VALUE:
+//    none
+//
+//  COMMENTS:
+//
+VOID WINAPI service_ctrl(DWORD dwCtrlCode)
+{
+   // Handle the requested control code.
+   //
+   switch (dwCtrlCode)
+   {
+   // Stop the service.
+   //
+   // SERVICE_STOP_PENDING should be reported before
+   // setting the Stop Event - hServerStopEvent - in
+   // ServiceStop().  This avoids a race condition
+   // which may result in a 1053 - The Service did not respond...
+   // error.
+   case SERVICE_CONTROL_STOP:
+      ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0);
+      ServiceStop();
+      return;
+
+      // Update the service status.
+      //
+   case SERVICE_CONTROL_INTERROGATE:
+      break;
+
+      // invalid control code
+      //
+   default:
+      break;
+
+   }
+
+   ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
+}
+
+
+
+//
+//  FUNCTION: ReportStatusToSCMgr()
+//
+//  PURPOSE: Sets the current status of the service and
+//           reports it to the Service Control Manager
+//
+//  PARAMETERS:
+//    dwCurrentState - the state of the service
+//    dwWin32ExitCode - error code to report
+//    dwWaitHint - worst case estimate to next checkpoint
+//
+//  RETURN VALUE:
+//    TRUE  - success
+//    FALSE - failure
+//
+//  COMMENTS:
+//
+BOOL ReportStatusToSCMgr(DWORD dwCurrentState,
+                         DWORD dwWin32ExitCode,
+                         DWORD dwWaitHint)
+{
+   static DWORD dwCheckPoint = 1;
+   BOOL fResult = TRUE;
+
+
+   if ( !bDebug ) // when debugging we don't report to the SCM
+   {
+      if (dwCurrentState == SERVICE_START_PENDING)
+         ssStatus.dwControlsAccepted = 0;
+      else
+         ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+
+      ssStatus.dwCurrentState = dwCurrentState;
+      ssStatus.dwWin32ExitCode = dwWin32ExitCode;
+      ssStatus.dwWaitHint = dwWaitHint;
+
+      if ( ( dwCurrentState == SERVICE_RUNNING ) ||
+           ( dwCurrentState == SERVICE_STOPPED ) )
+         ssStatus.dwCheckPoint = 0;
+      else
+         ssStatus.dwCheckPoint = dwCheckPoint++;
+
+
+      // Report the status of the service to the service control manager.
+      fResult = SetServiceStatus(sshStatusHandle, &ssStatus);
+      if (!fResult)
+         AddToMessageLog(TEXT("SetServiceStatus"));
+   }
+   return fResult;
+}
+
+
+
+//
+//  FUNCTION: AddToMessageLog(LPTSTR lpszMsg)
+//
+//  PURPOSE: Allows any thread to log an error message
+//
+//  PARAMETERS:
+//    lpszMsg - text for message
+//
+//  RETURN VALUE:
+//    none
+//
+//  COMMENTS:
+//
+VOID AddToMessageLog(LPTSTR lpszMsg)
+{
+   TCHAR szMsg [(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100 ];
+   HANDLE  hEventSource;
+   LPTSTR  lpszStrings[2];
+
+   if ( !bDebug )
+   {
+      dwErr = GetLastError();
+
+      // Use event logging to log the error.
+      //
+      hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME));
+
+#ifndef __REACTOS__
+      _stprintf_s(szMsg,(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr);
+#else
+      _sntprintf(szMsg,(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr);
+#endif
+      lpszStrings[0] = szMsg;
+      lpszStrings[1] = lpszMsg;
+
+      if (hEventSource != NULL)
+      {
+         ReportEvent(hEventSource, // handle of event source
+                     EVENTLOG_ERROR_TYPE,  // event type
+                     0,                    // event category
+                     0,                    // event ID
+                     NULL,                 // current user's SID
+                     2,                    // strings in lpszStrings
+                     0,                    // no bytes of raw data
+                     lpszStrings,          // array of error strings
+                     NULL);                // no raw data
+
+         (VOID) DeregisterEventSource(hEventSource);
+      }
+   }
+}
+
+
+
+
+///////////////////////////////////////////////////////////////////
+//
+//  The following code handles service installation and removal
+//
+
+
+//
+//  FUNCTION: CmdInstallService()
+//
+//  PURPOSE: Installs the service
+//
+//  PARAMETERS:
+//    none
+//
+//  RETURN VALUE:
+//    none
+//
+//  COMMENTS:
+//
+void CmdInstallService()
+{
+   SC_HANDLE   schService;
+   SC_HANDLE   schSCManager;
+
+   TCHAR szPath[512];
+
+   if ( GetModuleFileName( NULL, szPath, 512 ) == 0 )
+   {
+      _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256));
+      return;
+   }
+
+   schSCManager = OpenSCManager(
+                               NULL,                   // machine (NULL == local)
+                               NULL,                   // database (NULL == default)
+                               SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE  // access required
+                               );
+   if ( schSCManager )
+   {
+      schService = CreateService(
+                                schSCManager,               // SCManager database
+                                TEXT(SZSERVICENAME),        // name of service
+                                TEXT(SZSERVICEDISPLAYNAME), // name to display
+                                SERVICE_QUERY_STATUS,       // desired access
+                                SERVICE_WIN32_OWN_PROCESS,  // service type
+                                SERVICE_AUTO_START,         // start type
+                                SERVICE_ERROR_NORMAL,       // error control type
+                                szPath,                     // service's binary
+                                NULL,                       // no load ordering group
+                                NULL,                       // no tag identifier
+                                TEXT(SZDEPENDENCIES),       // dependencies
+                                NULL,                       // LocalSystem account
+                                NULL);                      // no password
+
+      if ( schService )
+      {
+         _tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
+         CloseServiceHandle(schService);
+      }
+      else
+      {
+         _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 256));
+      }
+
+      CloseServiceHandle(schSCManager);
+   }
+   else
+      _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256));
+}
+
+
+
+//
+//  FUNCTION: CmdRemoveService()
+//
+//  PURPOSE: Stops and removes the service
+//
+//  PARAMETERS:
+//    none
+//
+//  RETURN VALUE:
+//    none
+//
+//  COMMENTS:
+//
+void CmdRemoveService()
+{
+   SC_HANDLE   schService;
+   SC_HANDLE   schSCManager;
+
+   schSCManager = OpenSCManager(
+                               NULL,                   // machine (NULL == local)
+                               NULL,                   // database (NULL == default)
+                               SC_MANAGER_CONNECT   // access required
+                               );
+   if ( schSCManager )
+   {
+      schService = OpenService(schSCManager, TEXT(SZSERVICENAME), DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);
+
+      if (schService)
+      {
+         // try to stop the service
+         if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) )
+         {
+            _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME));
+            Sleep( 1000 );
+
+            while ( QueryServiceStatus( schService, &ssStatus ) )
+            {
+               if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING )
+               {
+                  _tprintf(TEXT("."));
+                  Sleep( 1000 );
+               }
+               else
+                  break;
+            }
+
+            if ( ssStatus.dwCurrentState == SERVICE_STOPPED )
+               _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME) );
+            else
+               _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME) );
+
+         }
+
+         // now remove the service
+         if ( DeleteService(schService) )
+            _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
+         else
+            _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(szErr,256));
+
+
+         CloseServiceHandle(schService);
+      }
+      else
+         _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256));
+
+      CloseServiceHandle(schSCManager);
+   }
+   else
+      _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256));
+}
+
+
+
+
+///////////////////////////////////////////////////////////////////
+//
+//  The following code is for running the service as a console app
+//
+
+
+//
+//  FUNCTION: CmdDebugService(int argc, char ** argv)
+//
+//  PURPOSE: Runs the service as a console application
+//
+//  PARAMETERS:
+//    argc - number of command line arguments
+//    argv - array of command line arguments
+//
+//  RETURN VALUE:
+//    none
+//
+//  COMMENTS:
+//
+void CmdDebugService(int argc, char ** argv)
+{
+   DWORD dwArgc;
+   LPTSTR *lpszArgv;
+
+#ifdef UNICODE
+   lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(dwArgc) );
+   if (NULL == lpszArgv)
+   {
+       // CommandLineToArvW failed!!
+       _tprintf(TEXT("CmdDebugService CommandLineToArgvW returned NULL\n"));
+       return;
+   }
+#else
+   dwArgc   = (DWORD) argc;
+   lpszArgv = argv;
+#endif
+
+   _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME));
+
+   SetConsoleCtrlHandler( ControlHandler, TRUE );
+
+   ServiceStart( dwArgc, lpszArgv );
+
+#ifdef UNICODE
+// Must free memory allocated for arguments
+
+   GlobalFree(lpszArgv);
+#endif // UNICODE
+
+}
+
+
+//
+//  FUNCTION: ControlHandler ( DWORD dwCtrlType )
+//
+//  PURPOSE: Handled console control events
+//
+//  PARAMETERS:
+//    dwCtrlType - type of control event
+//
+//  RETURN VALUE:
+//    True - handled
+//    False - unhandled
+//
+//  COMMENTS:
+//
+BOOL WINAPI ControlHandler ( DWORD dwCtrlType )
+{
+   switch ( dwCtrlType )
+   {
+   case CTRL_BREAK_EVENT:  // use Ctrl+C or Ctrl+Break to simulate
+   case CTRL_C_EVENT:      // SERVICE_CONTROL_STOP in debug mode
+      _tprintf(TEXT("Stopping %s.\n"), TEXT(SZSERVICEDISPLAYNAME));
+      ServiceStop();
+      return TRUE;
+      break;
+
+   }
+   return FALSE;
+}
+
+//
+//  FUNCTION: GetLastErrorText
+//
+//  PURPOSE: copies error message text to string
+//
+//  PARAMETERS:
+//    lpszBuf - destination buffer
+//    dwSize - size of buffer
+//
+//  RETURN VALUE:
+//    destination buffer
+//
+//  COMMENTS:
+//
+LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize )
+{
+   DWORD dwRet;
+   LPTSTR lpszTemp = NULL;
+
+   dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY,
+                          NULL,
+                          GetLastError(),
+                          LANG_NEUTRAL,
+                          (LPTSTR)&lpszTemp,
+                          0,
+                          NULL );
+
+   // supplied buffer is not long enough
+   if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) )
+      lpszBuf[0] = TEXT('\0');
+   else
+   {
+       if (NULL != lpszTemp)
+       {
+           lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0');  //remove cr and newline character
+#ifndef __REACTOS__
+           _stprintf_s( lpszBuf, dwSize, TEXT("%s (0x%x)"), lpszTemp, GetLastError() );
+#else
+           _sntprintf( lpszBuf, dwSize, TEXT("%s (0x%x)"), lpszTemp, GetLastError() );
+#endif
+       }
+   }
+
+   if ( NULL != lpszTemp )
+      LocalFree((HLOCAL) lpszTemp );
+
+   return lpszBuf;
+}
+#endif
diff --git a/reactos/base/services/nfsd/service.h b/reactos/base/services/nfsd/service.h
new file mode 100644 (file)
index 0000000..1bee89b
--- /dev/null
@@ -0,0 +1,137 @@
+/*---------------------------------------------------------------------------
+THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+PARTICULAR PURPOSE.
+
+Copyright (C) Microsoft Corporation.  All rights reserved.
+
+ MODULE: service.h
+
+ Comments:  The use of this header file and the accompanying service.c
+ file simplifies the process of writting a service.  You as a developer
+ simply need to follow the TODO's outlined in this header file, and
+ implement the ServiceStart() and ServiceStop() functions.
+
+ There is no need to modify the code in service.c.  Just add service.c
+ to your project and link with the following libraries...
+
+ libcmt.lib kernel32.lib advapi.lib shell32.lib
+
+ This code also supports unicode.  Be sure to compile both service.c and
+ and code #include "service.h" with the same Unicode setting.
+
+ Upon completion, your code will have the following command line interface
+
+ <service exe> -?                to display this list
+ <service exe> -install          to install the service
+ <service exe> -remove           to remove the service
+ <service exe> -debug <params>   to run as a console app for debugging
+
+ Note: This code also implements Ctrl+C and Ctrl+Break handlers
+       when using the debug option.  These console events cause
+       your ServiceStop routine to be called
+
+       Also, this code only handles the OWN_SERVICE service type
+       running in the LOCAL_SYSTEM security context.
+
+       To control your service ( start, stop, etc ) you may use the
+       Services control panel applet or the NET.EXE program.
+
+       To aid in writing/debugging service, the
+       SDK contains a utility (MSTOOLS\BIN\SC.EXE) that
+       can be used to control, configure, or obtain service status.
+       SC displays complete status for any service/driver
+       in the service database, and allows any of the configuration
+       parameters to be easily changed at the command line.
+       For more information on SC.EXE, type SC at the command line.
+
+
+------------------------------------------------------------------------------*/
+
+#ifndef _SERVICE_H
+#define _SERVICE_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//// todo: change to desired strings
+////
+// name of the executable
+#define SZAPPNAME            "nfsd"
+// internal name of the service
+#define SZSERVICENAME        "pnfs"
+// displayed name of the service
+#define SZSERVICEDISPLAYNAME "NFSv4.1 Client"
+// list of service dependencies - "dep1\0dep2\0\0"
+#define SZDEPENDENCIES       ""
+//////////////////////////////////////////////////////////////////////////////
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//// todo: ServiceStart()must be defined by in your code.
+////       The service should use ReportStatusToSCMgr to indicate
+////       progress.  This routine must also be used by StartService()
+////       to report to the SCM when the service is running.
+////
+////       If a ServiceStop procedure is going to take longer than
+////       3 seconds to execute, it should spawn a thread to
+////       execute the stop code, and return.  Otherwise, the
+////       ServiceControlManager will believe that the service has
+////       stopped responding
+////
+   VOID ServiceStart(DWORD dwArgc, LPTSTR *lpszArgv);
+   VOID ServiceStop();
+//////////////////////////////////////////////////////////////////////////////
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//// The following are procedures which
+//// may be useful to call within the above procedures,
+//// but require no implementation by the user.
+//// They are implemented in service.c
+
+//
+//  FUNCTION: ReportStatusToSCMgr()
+//
+//  PURPOSE: Sets the current status of the service and
+//           reports it to the Service Control Manager
+//
+//  PARAMETERS:
+//    dwCurrentState - the state of the service
+//    dwWin32ExitCode - error code to report
+//    dwWaitHint - worst case estimate to next checkpoint
+//
+//  RETURN VALUE:
+//    TRUE  - success
+//    FALSE - failure
+//
+   BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint);
+
+
+//
+//  FUNCTION: AddToMessageLog(LPTSTR lpszMsg)
+//
+//  PURPOSE: Allows any thread to log an error message
+//
+//  PARAMETERS:
+//    lpszMsg - text for message
+//
+//  RETURN VALUE:
+//    none
+//
+   void AddToMessageLog(LPTSTR lpszMsg);
+//////////////////////////////////////////////////////////////////////////////
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/reactos/base/services/nfsd/setattr.c b/reactos/base/services/nfsd/setattr.c
new file mode 100644 (file)
index 0000000..ea4b058
--- /dev/null
@@ -0,0 +1,526 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <strsafe.h>
+
+#include "from_kernel.h"
+#include "nfs41_ops.h"
+#include "delegation.h"
+#include "name_cache.h"
+#include "upcall.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+/* NFS41_FILE_SET */
+static int parse_setattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    setattr_upcall_args *args = &upcall->args.setattr;
+
+    status = get_name(&buffer, &length, &args->path);
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->set_class, sizeof(args->set_class));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
+    if (status) goto out;
+
+    args->buf = buffer;
+    args->root = upcall->root_ref;
+    args->state = upcall->state_ref;
+
+    dprintf(1, "parsing NFS41_FILE_SET: filename='%s' info_class=%d "
+        "buf_len=%d\n", args->path, args->set_class, args->buf_len);
+out:
+    return status;
+}
+
+static int handle_nfs41_setattr(setattr_upcall_args *args)
+{
+    PFILE_BASIC_INFO basic_info = (PFILE_BASIC_INFO)args->buf;
+    nfs41_open_state *state = args->state;
+    nfs41_superblock *superblock = state->file.fh.superblock;
+    stateid_arg stateid;
+    nfs41_file_info info = { 0 }, old_info = { 0 };
+    int status = NO_ERROR, getattr_status;
+       
+       if (basic_info->FileAttributes) {
+               info.hidden = basic_info->FileAttributes & FILE_ATTRIBUTE_HIDDEN ? 1 : 0;
+               info.system = basic_info->FileAttributes & FILE_ATTRIBUTE_SYSTEM ? 1 : 0;
+               info.archive = basic_info->FileAttributes & FILE_ATTRIBUTE_ARCHIVE ? 1 : 0;
+               getattr_status = nfs41_attr_cache_lookup(session_name_cache(state->session),
+                       state->file.fh.fileid, &old_info);
+
+               if (getattr_status || info.hidden != old_info.hidden) {
+                       info.attrmask.arr[0] = FATTR4_WORD0_HIDDEN;
+                       info.attrmask.count = 1;
+               }
+               if (getattr_status || info.archive != old_info.archive) {
+                       info.attrmask.arr[0] |= FATTR4_WORD0_ARCHIVE;
+                       info.attrmask.count = 1;
+               }
+               if (getattr_status || info.system != old_info.system) {
+                       info.attrmask.arr[1] = FATTR4_WORD1_SYSTEM;
+                       info.attrmask.count = 2;
+               }
+       }
+    if (old_info.mode == 0444 && 
+            ((basic_info->FileAttributes & FILE_ATTRIBUTE_READONLY) == 0)) {
+        info.mode = 0644;
+        info.attrmask.arr[1] |= FATTR4_WORD1_MODE;
+        info.attrmask.count = 2;
+    }
+
+    if (superblock->cansettime) {
+        /* set the time_delta so xdr_settime4() can decide
+         * whether or not to use SET_TO_SERVER_TIME4 */
+        info.time_delta = &superblock->time_delta;
+
+        /* time_create */
+        if (basic_info->CreationTime.QuadPart > 0) {
+            file_time_to_nfs_time(&basic_info->CreationTime,
+                &info.time_create);
+            info.attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE;
+            info.attrmask.count = 2;
+        }
+        /* time_access_set */
+        if (basic_info->LastAccessTime.QuadPart > 0) {
+            file_time_to_nfs_time(&basic_info->LastAccessTime,
+                &info.time_access);
+            info.attrmask.arr[1] |= FATTR4_WORD1_TIME_ACCESS_SET;
+            info.attrmask.count = 2;
+        }
+        /* time_modify_set */
+        if (basic_info->LastWriteTime.QuadPart > 0) {
+            file_time_to_nfs_time(&basic_info->LastWriteTime,
+                &info.time_modify);
+            info.attrmask.arr[1] |= FATTR4_WORD1_TIME_MODIFY_SET;
+            info.attrmask.count = 2;
+        }
+    }
+
+    /* mode */
+    if (basic_info->FileAttributes & FILE_ATTRIBUTE_READONLY) {
+        info.mode = 0444;
+        info.attrmask.arr[1] |= FATTR4_WORD1_MODE;
+        info.attrmask.count = 2;
+    }
+
+    /* mask out unsupported attributes */
+    nfs41_superblock_supported_attrs(superblock, &info.attrmask);
+
+    if (!info.attrmask.count)
+        goto out;
+
+    /* break read delegations before SETATTR */
+    nfs41_delegation_return(state->session, &state->file,
+        OPEN_DELEGATE_READ, FALSE);
+
+    nfs41_open_stateid_arg(state, &stateid);
+
+    status = nfs41_setattr(state->session, &state->file, &stateid, &info);
+    if (status) {
+        dprintf(1, "nfs41_setattr() failed with error %s.\n",
+            nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_NOT_SUPPORTED);
+    }
+    args->ctime = info.change;
+out:
+    return status;
+}
+
+static int handle_nfs41_remove(setattr_upcall_args *args)
+{
+    nfs41_open_state *state = args->state;
+    int status;
+
+    /* break any delegations and truncate before REMOVE */
+    nfs41_delegation_return(state->session, &state->file,
+        OPEN_DELEGATE_WRITE, TRUE);
+
+    status = nfs41_remove(state->session, &state->parent,
+        &state->file.name, state->file.fh.fileid);
+    if (status)
+        dprintf(1, "nfs41_remove() failed with error %s.\n",
+            nfs_error_string(status));
+
+    return nfs_to_windows_error(status, ERROR_ACCESS_DENIED);
+}
+
+static void open_state_rename(
+    OUT nfs41_open_state *state,
+    IN const nfs41_abs_path *path)
+{
+    AcquireSRWLockExclusive(&state->path.lock);
+
+    abs_path_copy(&state->path, path);
+    last_component(state->path.path, state->path.path + state->path.len,
+        &state->file.name);
+    last_component(state->path.path, state->file.name.name,
+        &state->parent.name);
+
+    ReleaseSRWLockExclusive(&state->path.lock);
+}
+
+static int nfs41_abs_path_compare(
+    IN const struct list_entry *entry,
+    IN const void *value)
+{
+    nfs41_open_state *client = list_container(entry, nfs41_open_state, client_entry);
+    const nfs41_abs_path *name = (const nfs41_abs_path *)value;
+    if (client->path.len == name->len && 
+            !strncmp(client->path.path, name->path, client->path.len))
+        return NO_ERROR;
+    return ERROR_FILE_NOT_FOUND;
+}
+
+static int is_dst_name_opened(nfs41_abs_path *dst_path, nfs41_session *dst_session)
+{
+    int status;
+    nfs41_client *client = dst_session->client;
+
+    EnterCriticalSection(&client->state.lock);
+    if (list_search(&client->state.opens, dst_path, nfs41_abs_path_compare))
+        status = TRUE;
+    else
+        status = FALSE;
+    LeaveCriticalSection(&client->state.lock);
+
+    return status;
+}
+static int handle_nfs41_rename(setattr_upcall_args *args)
+{
+    nfs41_open_state *state = args->state;
+    nfs41_session *dst_session;
+    PFILE_RENAME_INFO rename = (PFILE_RENAME_INFO)args->buf;
+    nfs41_abs_path dst_path = { 0 };
+    nfs41_path_fh dst_dir, dst;
+    nfs41_component dst_name, *src_name;
+    uint32_t depth = 0;
+    int status;
+
+    src_name = &state->file.name;
+
+    if (rename->FileNameLength == 0) {
+        /* start from state->path instead of args->path, in case we got
+         * the file from a referred server */
+        AcquireSRWLockShared(&state->path.lock);
+        abs_path_copy(&dst_path, &state->path);
+        ReleaseSRWLockShared(&state->path.lock);
+
+        path_fh_init(&dst_dir, &dst_path);
+        fh_copy(&dst_dir.fh, &state->parent.fh);
+
+        create_silly_rename(&dst_path, &state->file.fh, &dst_name);
+        dprintf(1, "silly rename: %s -> %s\n", src_name->name, dst_name.name);
+
+        /* break any delegations and truncate before silly rename */
+        nfs41_delegation_return(state->session, &state->file,
+            OPEN_DELEGATE_WRITE, TRUE);
+
+        status = nfs41_rename(state->session,
+            &state->parent, src_name,
+            &dst_dir, &dst_name);
+        if (status) {
+            dprintf(1, "nfs41_rename() failed with error %s.\n",
+                nfs_error_string(status));
+            status = nfs_to_windows_error(status, ERROR_ACCESS_DENIED);
+        } else {
+            /* rename state->path on success */
+            open_state_rename(state, &dst_path);
+        }
+        goto out;
+    }
+
+    dst_path.len = (unsigned short)WideCharToMultiByte(CP_UTF8, 0,
+        rename->FileName, rename->FileNameLength/sizeof(WCHAR),
+        dst_path.path, NFS41_MAX_PATH_LEN, NULL, NULL);
+    if (dst_path.len == 0) {
+        eprintf("WideCharToMultiByte failed to convert destination "
+            "filename %S.\n", rename->FileName);
+        status = ERROR_INVALID_PARAMETER;
+        goto out;
+    }
+    path_fh_init(&dst_dir, &dst_path);
+
+    /* the destination path is absolute, so start from the root session */
+    status = nfs41_lookup(args->root, nfs41_root_session(args->root),
+        &dst_path, &dst_dir, &dst, NULL, &dst_session);
+
+    while (status == ERROR_REPARSE) {
+        if (++depth > NFS41_MAX_SYMLINK_DEPTH) {
+            status = ERROR_TOO_MANY_LINKS;
+            goto out;
+        }
+
+        /* replace the path with the symlink target's */
+        status = nfs41_symlink_target(dst_session, &dst_dir, &dst_path);
+        if (status) {
+            eprintf("nfs41_symlink_target() for %s failed with %d\n", 
+                dst_dir.path->path, status);
+            goto out;
+        }
+
+        /* redo the lookup until it doesn't return REPARSE */
+        status = nfs41_lookup(args->root, dst_session,
+            &dst_path, &dst_dir, NULL, NULL, &dst_session);
+    }
+
+    /* get the components after lookup in case a referral changed its path */
+    last_component(dst_path.path, dst_path.path + dst_path.len, &dst_name);
+    last_component(dst_path.path, dst_name.name, &dst_dir.name);
+
+    if (status == NO_ERROR) {
+        if (!rename->ReplaceIfExists) {
+            status = ERROR_FILE_EXISTS;
+            goto out;
+        }
+        /* break any delegations and truncate the destination file */
+        nfs41_delegation_return(dst_session, &dst,
+            OPEN_DELEGATE_WRITE, TRUE);
+    } else if (status != ERROR_FILE_NOT_FOUND) {
+        dprintf(1, "nfs41_lookup('%s') failed to find destination "
+            "directory with %d\n", dst_path.path, status);
+        goto out;
+    }
+
+    /* http://tools.ietf.org/html/rfc5661#section-18.26.3
+     * "Source and target directories MUST reside on the same
+     * file system on the server." */
+    if (state->parent.fh.superblock != dst_dir.fh.superblock) {
+        status = ERROR_NOT_SAME_DEVICE;
+        goto out;
+    }
+
+    status = is_dst_name_opened(&dst_path, dst_session);
+    if (status) {
+        /* AGLO: 03/21/2011: we can't handle rename of a file with a filename 
+         * that is currently opened by this client
+         */
+        eprintf("handle_nfs41_rename: %s is opened\n", dst_path.path);
+        status = ERROR_FILE_EXISTS;
+        goto out;
+    }
+
+    /* break any delegations on the source file */
+    nfs41_delegation_return(state->session, &state->file,
+        OPEN_DELEGATE_WRITE, FALSE);
+
+    status = nfs41_rename(state->session,
+        &state->parent, src_name,
+        &dst_dir, &dst_name);
+    if (status) {
+        dprintf(1, "nfs41_rename() failed with error %s.\n",
+            nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_ACCESS_DENIED);
+    } else {
+        /* rename state->path on success */
+        open_state_rename(state, &dst_path);
+    }
+out:
+    return status;
+}
+
+static int handle_nfs41_set_size(setattr_upcall_args *args)
+{
+    nfs41_file_info info = { 0 };
+    stateid_arg stateid;
+    /* note: this is called with either FILE_END_OF_FILE_INFO or
+     * FILE_ALLOCATION_INFO, both of which contain a single LARGE_INTEGER */
+    PLARGE_INTEGER size = (PLARGE_INTEGER)args->buf;
+    nfs41_open_state *state = args->state;
+    int status;
+
+    /* break read delegations before SETATTR */
+    nfs41_delegation_return(state->session, &state->file,
+        OPEN_DELEGATE_READ, FALSE);
+
+    nfs41_open_stateid_arg(state, &stateid);
+
+    info.size = size->QuadPart;
+    info.attrmask.count = 1;
+    info.attrmask.arr[0] = FATTR4_WORD0_SIZE;
+
+    dprintf(2, "calling setattr() with size=%lld\n", info.size);
+    status = nfs41_setattr(state->session, &state->file, &stateid, &info);
+    if (status) {
+        dprintf(1, "nfs41_setattr() failed with error %s.\n",
+            nfs_error_string(status));
+        goto out;
+    }
+
+    /* update the last offset for LAYOUTCOMMIT */
+    AcquireSRWLockExclusive(&state->lock);
+    state->pnfs_last_offset = info.size ? info.size - 1 : 0;
+    ReleaseSRWLockExclusive(&state->lock);
+    args->ctime = info.change;
+out:
+    return status = nfs_to_windows_error(status, ERROR_NOT_SUPPORTED);
+}
+
+static int handle_nfs41_link(setattr_upcall_args *args)
+{
+    nfs41_open_state *state = args->state;
+    PFILE_LINK_INFORMATION link = (PFILE_LINK_INFORMATION)args->buf;
+    nfs41_session *dst_session;
+    nfs41_abs_path dst_path = { 0 };
+    nfs41_path_fh dst_dir, dst;
+    nfs41_component dst_name;
+    uint32_t depth = 0;
+    nfs41_file_info info = { 0 };
+    int status;
+
+    dst_path.len = (unsigned short)WideCharToMultiByte(CP_UTF8, 0,
+        link->FileName, link->FileNameLength/sizeof(WCHAR),
+        dst_path.path, NFS41_MAX_PATH_LEN, NULL, NULL);
+    if (dst_path.len == 0) {
+        eprintf("WideCharToMultiByte failed to convert destination "
+            "filename %S.\n", link->FileName);
+        status = ERROR_INVALID_PARAMETER;
+        goto out;
+    }
+    path_fh_init(&dst_dir, &dst_path);
+
+    /* the destination path is absolute, so start from the root session */
+    status = nfs41_lookup(args->root, nfs41_root_session(args->root),
+        &dst_path, &dst_dir, &dst, NULL, &dst_session);
+
+    while (status == ERROR_REPARSE) {
+        if (++depth > NFS41_MAX_SYMLINK_DEPTH) {
+            status = ERROR_TOO_MANY_LINKS;
+            goto out;
+        }
+
+        /* replace the path with the symlink target's */
+        status = nfs41_symlink_target(dst_session, &dst_dir, &dst_path);
+        if (status) {
+            eprintf("nfs41_symlink_target() for %s failed with %d\n", 
+                dst_dir.path->path, status);
+            goto out;
+        }
+
+        /* redo the lookup until it doesn't return REPARSE */
+        status = nfs41_lookup(args->root, dst_session,
+            &dst_path, &dst_dir, &dst, NULL, &dst_session);
+    }
+
+    /* get the components after lookup in case a referral changed its path */
+    last_component(dst_path.path, dst_path.path + dst_path.len, &dst_name);
+    last_component(dst_path.path, dst_name.name, &dst_dir.name);
+
+    if (status == NO_ERROR) {
+        if (!link->ReplaceIfExists) {
+            status = ERROR_FILE_EXISTS;
+            goto out;
+        }
+    } else if (status != ERROR_FILE_NOT_FOUND) {
+        dprintf(1, "nfs41_lookup('%s') failed to find destination "
+            "directory with %d\n", dst_path.path, status);
+        goto out;
+    }
+
+    /* http://tools.ietf.org/html/rfc5661#section-18.9.3
+     * "The existing file and the target directory must reside within
+     * the same file system on the server." */
+    if (state->file.fh.superblock != dst_dir.fh.superblock) {
+        status = ERROR_NOT_SAME_DEVICE;
+        goto out;
+    }
+
+    if (status == NO_ERROR) {
+        /* break any delegations and truncate the destination file */
+        nfs41_delegation_return(dst_session, &dst,
+            OPEN_DELEGATE_WRITE, TRUE);
+
+        /* LINK will return NFS4ERR_EXIST if the target file exists,
+         * so we have to remove it ourselves */
+        status = nfs41_remove(state->session,
+            &dst_dir, &dst_name, dst.fh.fileid);
+        if (status) {
+            dprintf(1, "nfs41_remove() failed with error %s.\n",
+                nfs_error_string(status));
+            status = ERROR_FILE_EXISTS;
+            goto out;
+        }
+    }
+
+    /* break read delegations on the source file */
+    nfs41_delegation_return(state->session, &state->file,
+        OPEN_DELEGATE_READ, FALSE);
+
+    status = nfs41_link(state->session, &state->file, &dst_dir, &dst_name, 
+            &info);
+    if (status) {
+        dprintf(1, "nfs41_link() failed with error %s.\n",
+            nfs_error_string(status));
+        status = nfs_to_windows_error(status, ERROR_INVALID_PARAMETER);
+    }
+    args->ctime = info.change;
+out:
+    return status;
+}
+
+static int handle_setattr(nfs41_upcall *upcall)
+{
+    setattr_upcall_args *args = &upcall->args.setattr;
+    int status;
+
+    switch (args->set_class) {
+    case FileBasicInformation:
+        status = handle_nfs41_setattr(args);
+        break;
+    case FileDispositionInformation:
+        status = handle_nfs41_remove(args);
+        break;
+    case FileRenameInformation:
+        status = handle_nfs41_rename(args);
+        break;
+    case FileAllocationInformation:
+    case FileEndOfFileInformation:
+        status = handle_nfs41_set_size(args);
+        break;
+    case FileLinkInformation:
+        status = handle_nfs41_link(args);
+        break;
+    default:
+        eprintf("unknown set_file information class %d\n",
+            args->set_class);
+        status = ERROR_NOT_SUPPORTED;
+        break;
+    }
+
+    return status;
+}
+
+static int marshall_setattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    setattr_upcall_args *args = &upcall->args.setattr;
+    return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime));
+}
+
+
+const nfs41_upcall_op nfs41_op_setattr = {
+    parse_setattr,
+    handle_setattr,
+    marshall_setattr
+};
diff --git a/reactos/base/services/nfsd/sources b/reactos/base/services/nfsd/sources
new file mode 100644 (file)
index 0000000..af4eef9
--- /dev/null
@@ -0,0 +1,32 @@
+TARGETTYPE=PROGRAM
+TARGETNAME=nfsd
+SOURCES=nfs41_daemon.c daemon_debug.c nfs41_ops.c nfs41_compound.c nfs41_xdr.c \
+       nfs41_server.c nfs41_client.c nfs41_superblock.c nfs41_session.c lookup.c \
+       mount.c open.c readwrite.c lock.c readdir.c getattr.c setattr.c upcall.c \
+       nfs41_rpc.c util.c pnfs_layout.c pnfs_device.c pnfs_debug.c pnfs_io.c \
+       name_cache.c namespace.c rbtree.c volume.c callback_server.c callback_xdr.c \
+       service.c symlink.c idmap.c
+UMTYPE=console
+USE_LIBCMT=1
+#USE_MSVCRT=1
+C_DEFINES=-DSTANDALONE_NFSD #-- use this for non-service nfsd
+INCLUDES=..\sys;..\dll;..\libtirpc\tirpc
+# Use the following for "service" version of nfsd
+#TARGETLIBS=$(SDK_LIB_PATH)\ws2_32.lib $(SDK_LIB_PATH)\iphlpapi.lib \
+#        ..\libtirpc\src\obj$(BUILD_ALT_DIR)\*\libtirpc.lib \
+#              $(SDK_LIB_PATH)\kernel32.lib \
+#              $(SDK_LIB_PATH)\advapi32.lib \
+#              $(SDK_LIB_PATH)\shell32.lib
+TARGETLIBS=$(SDK_LIB_PATH)\ws2_32.lib $(SDK_LIB_PATH)\iphlpapi.lib \
+        ..\libtirpc\src\obj$(BUILD_ALT_DIR)\*\libtirpc.lib
+
+!IF 0
+/W3 is default level
+bump to /Wall, but suppress warnings generated by system includes,
+as well as the following warnings:
+4100 - unused function call arguments (we have lots of stubs)
+4127 - constant conditional (I like to use if(0) or if(1))
+4220 - varargs matching remaining parameters
+4204 - nonstandard extension
+!ENDIF
+MSC_WARNING_LEVEL=/Wall /wd4668 /wd4619 /wd4820 /wd4255 /wd4100 /wd4127 /wd4711 /wd4220 /wd4204
diff --git a/reactos/base/services/nfsd/symlink.c b/reactos/base/services/nfsd/symlink.c
new file mode 100644 (file)
index 0000000..53b6f7b
--- /dev/null
@@ -0,0 +1,299 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+
+#include "nfs41_ops.h"
+#include "upcall.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+static int abs_path_link(
+    OUT nfs41_abs_path *path,
+    IN char *path_pos,
+    IN const char *link,
+    IN uint32_t link_len)
+{
+    nfs41_component name;
+    const char *path_max = path->path + NFS41_MAX_PATH_LEN;
+    const char *link_pos = link;
+    const char *link_end = link + link_len;
+    int status = NO_ERROR;
+
+    /* if link is an absolute path, start path_pos at the beginning */
+    if (is_delimiter(*link))
+        path_pos = path->path;
+
+    /* copy each component of link into the path */
+    while (next_component(link_pos, link_end, &name)) {
+        link_pos = name.name + name.len;
+
+        if (is_delimiter(*path_pos))
+            path_pos++;
+
+        /* handle special components . and .. */
+        if (name.len == 1 && name.name[0] == '.')
+            continue;
+        if (name.len == 2 && name.name[0] == '.' && name.name[1] == '.') {
+            /* back path_pos up by one component */
+            if (!last_component(path->path, path_pos, &name)) {
+                eprintf("symlink with .. that points below server root!\n");
+                status = ERROR_BAD_NETPATH;
+                goto out;
+            }
+            path_pos = (char*)prev_delimiter(name.name, path->path);
+            continue;
+        }
+
+        /* copy the component and add a \ */
+        if (FAILED(StringCchCopyNA(path_pos, path_max-path_pos, name.name, 
+                name.len))) {
+            status = ERROR_BUFFER_OVERFLOW;
+            goto out;
+        }
+        path_pos += name.len;
+        if (FAILED(StringCchCopyNA(path_pos, path_max-path_pos, "\\", 1))) {
+            status = ERROR_BUFFER_OVERFLOW;
+            goto out;
+        }
+    }
+
+    /* make sure the path is null terminated */
+    if (path_pos == path_max) {
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out;
+    }
+    *path_pos = '\0';
+out:
+    path->len = (unsigned short)(path_pos - path->path);
+    return status;
+}
+
+int nfs41_symlink_target(
+    IN nfs41_session *session,
+    IN nfs41_path_fh *file,
+    OUT nfs41_abs_path *target)
+{
+    char link[NFS41_MAX_PATH_LEN];
+    const nfs41_abs_path *path = file->path;
+    ptrdiff_t path_offset;
+    uint32_t link_len;
+    int status;
+
+    /* read the link */
+    status = nfs41_readlink(session, file, NFS41_MAX_PATH_LEN, link, &link_len);
+    if (status) {
+        eprintf("nfs41_readlink() for %s failed with %s\n", file->path->path, 
+            nfs_error_string(status));
+        status = ERROR_PATH_NOT_FOUND;
+        goto out;
+    }
+
+    dprintf(2, "--> nfs41_symlink_target('%s', '%s')\n", path->path, link);
+
+    /* append any components after the symlink */
+    if (FAILED(StringCchCatA(link, NFS41_MAX_PATH_LEN,
+            file->name.name + file->name.len))) {
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out;
+    }
+    link_len = (uint32_t)strlen(link);
+
+    /* overwrite the last component of the path; get the starting offset */
+    path_offset = file->name.name - path->path;
+
+    /* copy the path and update it with the results from link */
+    if (target != path) {
+        target->len = path->len;
+        if (FAILED(StringCchCopyNA(target->path, NFS41_MAX_PATH_LEN,
+                path->path, path->len))) {
+            status = ERROR_BUFFER_OVERFLOW;
+            goto out;
+        }
+    }
+    status = abs_path_link(target, target->path + path_offset, link, link_len);
+    if (status) {
+        eprintf("abs_path_link() for path %s with link %s failed with %d\n", 
+            target->path, link, status);
+        goto out;
+    }
+out:
+    dprintf(2, "<-- nfs41_symlink_target('%s') returning %d\n",
+        target->path, status);
+    return status;
+}
+
+int nfs41_symlink_follow(
+    IN nfs41_root *root,
+    IN nfs41_session *session,
+    IN nfs41_path_fh *symlink,
+    OUT nfs41_file_info *info)
+{
+    nfs41_abs_path path;
+    nfs41_path_fh file;
+    uint32_t depth = 0;
+    int status = NO_ERROR;
+
+    file.path = &path;
+    InitializeSRWLock(&path.lock);
+
+    dprintf(2, "--> nfs41_symlink_follow('%s')\n", symlink->path->path);
+
+    do {
+        if (++depth > NFS41_MAX_SYMLINK_DEPTH) {
+            status = ERROR_TOO_MANY_LINKS;
+            goto out;
+        }
+
+        /* construct the target path */
+        status = nfs41_symlink_target(session, symlink, &path);
+        if (status) goto out;
+
+        dprintf(2, "looking up '%s'\n", path.path);
+
+        last_component(path.path, path.path + path.len, &file.name);
+
+        /* get attributes for the target */
+        status = nfs41_lookup(root, session, &path,
+            NULL, &file, info, &session);
+        if (status) goto out;
+
+        symlink = &file;
+    } while (info->type == NF4LNK);
+out:
+    dprintf(2, "<-- nfs41_symlink_follow() returning %d\n", status);
+    return status;
+}
+
+
+/* NFS41_SYMLINK */
+static int parse_symlink(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    symlink_upcall_args *args = &upcall->args.symlink;
+    int status;
+
+    status = get_name(&buffer, &length, &args->path);
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &args->set, sizeof(BOOLEAN));
+    if (status) goto out;
+
+    if (args->set)
+        status = get_name(&buffer, &length, &args->target_set);
+    else
+        args->target_set = NULL;
+
+    dprintf(1, "parsing NFS41_SYMLINK: path='%s' set=%u target='%s'\n",
+        args->path, args->set, args->target_set);
+out:
+    return status;
+}
+
+static int handle_symlink(nfs41_upcall *upcall)
+{
+    symlink_upcall_args *args = &upcall->args.symlink;
+    nfs41_open_state *state = upcall->state_ref;
+    int status = NO_ERROR;
+
+    if (args->set) {
+        nfs41_file_info info, createattrs;
+
+        /* don't send windows slashes to the server */
+        char *p;
+        for (p = args->target_set; *p; p++) if (*p == '\\') *p = '/';
+
+        if (state->file.fh.len) {
+            /* the check in handle_open() didn't catch that we're creating
+             * a symlink, so we have to remove the file it already created */
+            eprintf("handle_symlink: attempting to create a symlink when "
+                "the file=%s was already created on open; sending REMOVE "
+                "first\n", state->file.path->path);
+            status = nfs41_remove(state->session, &state->parent,
+                &state->file.name, state->file.fh.fileid);
+            if (status) {
+                eprintf("nfs41_remove() for symlink=%s failed with %s\n",
+                    args->target_set, nfs_error_string(status));
+                status = map_symlink_errors(status);
+                goto out;
+            }
+        }
+
+        /* create the symlink */
+        createattrs.attrmask.count = 2;
+        createattrs.attrmask.arr[0] = 0;
+        createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE;
+        createattrs.mode = 0777;
+        status = nfs41_create(state->session, NF4LNK, &createattrs,
+            args->target_set, &state->parent, &state->file, &info);
+        if (status) {
+            eprintf("nfs41_create() for symlink=%s failed with %s\n",
+                args->target_set, nfs_error_string(status));
+            status = map_symlink_errors(status);
+            goto out;
+        }
+    } else {
+        uint32_t len;
+
+        /* read the link */
+        status = nfs41_readlink(state->session, &state->file,
+            NFS41_MAX_PATH_LEN, args->target_get.path, &len);
+        if (status) {
+            eprintf("nfs41_readlink() for filename=%s failed with %s\n",
+                state->file.path->path, nfs_error_string(status));
+            status = map_symlink_errors(status);
+            goto out;
+        }
+        args->target_get.len = (unsigned short)len;
+        dprintf(2, "returning symlink target '%s'\n", args->target_get.path);
+    }
+out:
+    return status;
+}
+
+static int marshall_symlink(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    symlink_upcall_args *args = &upcall->args.symlink;
+    unsigned short len = (args->target_get.len + 1) * sizeof(WCHAR);
+    int status = NO_ERROR;
+
+    if (args->set)
+        goto out;
+
+    status = safe_write(&buffer, length, &len, sizeof(len));
+    if (status) goto out;
+
+    if (*length <= len || !MultiByteToWideChar(CP_UTF8, 0,
+            args->target_get.path, args->target_get.len,
+            (LPWSTR)buffer, len / sizeof(WCHAR))) {
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out;
+    }
+out:
+    return status;
+}
+
+
+const nfs41_upcall_op nfs41_op_symlink = {
+    parse_symlink,
+    handle_symlink,
+    marshall_symlink
+};
diff --git a/reactos/base/services/nfsd/tree.h b/reactos/base/services/nfsd/tree.h
new file mode 100644 (file)
index 0000000..5a1bbd5
--- /dev/null
@@ -0,0 +1,765 @@
+/*     $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $  */
+/*     $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $    */
+/* $FreeBSD: src/sys/sys/tree.h,v 1.9.2.1.4.1 2010/06/14 02:09:06 kensmith Exp $ */
+
+/*-
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef        _SYS_TREE_H_
+#define        _SYS_TREE_H_
+
+//#include <sys/cdefs.h>
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure.  Every operation
+ * on the tree causes a splay to happen.  The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree.  On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n).  The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute.  It fulfills a set of conditions:
+ *     - every search path from the root to a leaf consists of the
+ *       same number of black nodes,
+ *     - each red node (except for the root) has a black parent,
+ *     - each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *sph_root; /* root of the tree */                   \
+}
+
+#define SPLAY_INITIALIZER(root)                                                \
+       { NULL }
+
+#define SPLAY_INIT(root) do {                                          \
+       (root)->sph_root = NULL;                                        \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *spe_left; /* left element */                       \
+       struct type *spe_right; /* right element */                     \
+}
+
+#define SPLAY_LEFT(elm, field)         (elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field)                (elm)->field.spe_right
+#define SPLAY_ROOT(head)               (head)->sph_root
+#define SPLAY_EMPTY(head)              (SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do {                      \
+       SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);  \
+       SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \
+       (head)->sph_root = tmp;                                         \
+} while (/*CONSTCOND*/ 0)
+       
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do {                       \
+       SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);  \
+       SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \
+       (head)->sph_root = tmp;                                         \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do {                          \
+       SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \
+       tmp = (head)->sph_root;                                         \
+       (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);         \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do {                         \
+       SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \
+       tmp = (head)->sph_root;                                         \
+       (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);        \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do {            \
+       SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
+       SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+       SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
+       SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
+} while (/*CONSTCOND*/ 0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp)                                \
+void name##_SPLAY(struct name *, struct type *);                       \
+void name##_SPLAY_MINMAX(struct name *, int);                          \
+struct type *name##_SPLAY_INSERT(struct name *, struct type *);                \
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *);                \
+                                                                       \
+/* Finds the node with the same key as elm */                          \
+static __inline struct type *                                          \
+name##_SPLAY_FIND(struct name *head, struct type *elm)                 \
+{                                                                      \
+       if (SPLAY_EMPTY(head))                                          \
+               return(NULL);                                           \
+       name##_SPLAY(head, elm);                                        \
+       if ((cmp)(elm, (head)->sph_root) == 0)                          \
+               return (head->sph_root);                                \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+static __inline struct type *                                          \
+name##_SPLAY_NEXT(struct name *head, struct type *elm)                 \
+{                                                                      \
+       name##_SPLAY(head, elm);                                        \
+       if (SPLAY_RIGHT(elm, field) != NULL) {                          \
+               elm = SPLAY_RIGHT(elm, field);                          \
+               while (SPLAY_LEFT(elm, field) != NULL) {                \
+                       elm = SPLAY_LEFT(elm, field);                   \
+               }                                                       \
+       } else                                                          \
+               elm = NULL;                                             \
+       return (elm);                                                   \
+}                                                                      \
+                                                                       \
+static __inline struct type *                                          \
+name##_SPLAY_MIN_MAX(struct name *head, int val)                       \
+{                                                                      \
+       name##_SPLAY_MINMAX(head, val);                                 \
+        return (SPLAY_ROOT(head));                                     \
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp)                         \
+struct type *                                                          \
+name##_SPLAY_INSERT(struct name *head, struct type *elm)               \
+{                                                                      \
+    if (SPLAY_EMPTY(head)) {                                           \
+           SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;    \
+    } else {                                                           \
+           int __comp;                                                 \
+           name##_SPLAY(head, elm);                                    \
+           __comp = (cmp)(elm, (head)->sph_root);                      \
+           if(__comp < 0) {                                            \
+                   SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+                   SPLAY_RIGHT(elm, field) = (head)->sph_root;         \
+                   SPLAY_LEFT((head)->sph_root, field) = NULL;         \
+           } else if (__comp > 0) {                                    \
+                   SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+                   SPLAY_LEFT(elm, field) = (head)->sph_root;          \
+                   SPLAY_RIGHT((head)->sph_root, field) = NULL;        \
+           } else                                                      \
+                   return ((head)->sph_root);                          \
+    }                                                                  \
+    (head)->sph_root = (elm);                                          \
+    return (NULL);                                                     \
+}                                                                      \
+                                                                       \
+struct type *                                                          \
+name##_SPLAY_REMOVE(struct name *head, struct type *elm)               \
+{                                                                      \
+       struct type *__tmp;                                             \
+       if (SPLAY_EMPTY(head))                                          \
+               return (NULL);                                          \
+       name##_SPLAY(head, elm);                                        \
+       if ((cmp)(elm, (head)->sph_root) == 0) {                        \
+               if (SPLAY_LEFT((head)->sph_root, field) == NULL) {      \
+                       (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+               } else {                                                \
+                       __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                       (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+                       name##_SPLAY(head, elm);                        \
+                       SPLAY_RIGHT((head)->sph_root, field) = __tmp;   \
+               }                                                       \
+               return (elm);                                           \
+       }                                                               \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+void                                                                   \
+name##_SPLAY(struct name *head, struct type *elm)                      \
+{                                                                      \
+       struct type __node, *__left, *__right, *__tmp;                  \
+       int __comp;                                                     \
+\
+       SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+       __left = __right = &__node;                                     \
+\
+       while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) {          \
+               if (__comp < 0) {                                       \
+                       __tmp = SPLAY_LEFT((head)->sph_root, field);    \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if ((cmp)(elm, __tmp) < 0){                     \
+                               SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+                               if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKLEFT(head, __right, field);           \
+               } else if (__comp > 0) {                                \
+                       __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if ((cmp)(elm, __tmp) > 0){                     \
+                               SPLAY_ROTATE_LEFT(head, __tmp, field);  \
+                               if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKRIGHT(head, __left, field);           \
+               }                                                       \
+       }                                                               \
+       SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \
+}                                                                      \
+                                                                       \
+/* Splay with either the minimum or the maximum element                        \
+ * Used to find minimum or maximum element in tree.                    \
+ */                                                                    \
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{                                                                      \
+       struct type __node, *__left, *__right, *__tmp;                  \
+\
+       SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+       __left = __right = &__node;                                     \
+\
+       while (1) {                                                     \
+               if (__comp < 0) {                                       \
+                       __tmp = SPLAY_LEFT((head)->sph_root, field);    \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if (__comp < 0){                                \
+                               SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+                               if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKLEFT(head, __right, field);           \
+               } else if (__comp > 0) {                                \
+                       __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if (__comp > 0) {                               \
+                               SPLAY_ROTATE_LEFT(head, __tmp, field);  \
+                               if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKRIGHT(head, __left, field);           \
+               }                                                       \
+       }                                                               \
+       SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \
+}
+
+#define SPLAY_NEGINF   -1
+#define SPLAY_INF      1
+
+#define SPLAY_INSERT(name, x, y)       name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y)       name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y)         name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y)         name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x)             (SPLAY_EMPTY(x) ? NULL  \
+                                       : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x)             (SPLAY_EMPTY(x) ? NULL  \
+                                       : name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head)                                   \
+       for ((x) = SPLAY_MIN(name, head);                               \
+            (x) != NULL;                                               \
+            (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type)                                            \
+struct name {                                                          \
+       struct type *rbh_root; /* root of the tree */                   \
+}
+
+#define RB_INITIALIZER(root)                                           \
+       { NULL }
+
+#define RB_INIT(root) do {                                             \
+       (root)->rbh_root = NULL;                                        \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_BLACK       0
+#define RB_RED         1
+#define RB_ENTRY(type)                                                 \
+struct {                                                               \
+       struct type *rbe_left;          /* left element */              \
+       struct type *rbe_right;         /* right element */             \
+       struct type *rbe_parent;        /* parent element */            \
+       int rbe_color;                  /* node color */                \
+}
+
+#define RB_LEFT(elm, field)            (elm)->field.rbe_left
+#define RB_RIGHT(elm, field)           (elm)->field.rbe_right
+#define RB_PARENT(elm, field)          (elm)->field.rbe_parent
+#define RB_COLOR(elm, field)           (elm)->field.rbe_color
+#define RB_ROOT(head)                  (head)->rbh_root
+#define RB_EMPTY(head)                 (RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do {                                        \
+       RB_PARENT(elm, field) = parent;                                 \
+       RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;              \
+       RB_COLOR(elm, field) = RB_RED;                                  \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_SET_BLACKRED(black, red, field) do {                                \
+       RB_COLOR(black, field) = RB_BLACK;                              \
+       RB_COLOR(red, field) = RB_RED;                                  \
+} while (/*CONSTCOND*/ 0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x)  do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do {                     \
+       (tmp) = RB_RIGHT(elm, field);                                   \
+       if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) {     \
+               RB_PARENT(RB_LEFT(tmp, field), field) = (elm);          \
+       }                                                               \
+       RB_AUGMENT(elm);                                                \
+       if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) {  \
+               if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \
+                       RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \
+               else                                                    \
+                       RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+       } else                                                          \
+               (head)->rbh_root = (tmp);                               \
+       RB_LEFT(tmp, field) = (elm);                                    \
+       RB_PARENT(elm, field) = (tmp);                                  \
+       RB_AUGMENT(tmp);                                                \
+       if ((RB_PARENT(tmp, field)))                                    \
+               RB_AUGMENT(RB_PARENT(tmp, field));                      \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do {                    \
+       (tmp) = RB_LEFT(elm, field);                                    \
+       if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) {     \
+               RB_PARENT(RB_RIGHT(tmp, field), field) = (elm);         \
+       }                                                               \
+       RB_AUGMENT(elm);                                                \
+       if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) {  \
+               if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \
+                       RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \
+               else                                                    \
+                       RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+       } else                                                          \
+               (head)->rbh_root = (tmp);                               \
+       RB_RIGHT(tmp, field) = (elm);                                   \
+       RB_PARENT(elm, field) = (tmp);                                  \
+       RB_AUGMENT(tmp);                                                \
+       if ((RB_PARENT(tmp, field)))                                    \
+               RB_AUGMENT(RB_PARENT(tmp, field));                      \
+} while (/*CONSTCOND*/ 0)
+
+/* Generates prototypes and inline functions */
+#define        RB_PROTOTYPE(name, type, field, cmp)                            \
+       RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define        RB_PROTOTYPE_STATIC(name, type, field, cmp)                     \
+       RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)            \
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *);                \
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *);      \
+attr struct type *name##_RB_INSERT(struct name *, struct type *);      \
+attr struct type *name##_RB_FIND(struct name *, struct type *);                \
+attr struct type *name##_RB_NFIND(struct name *, struct type *);       \
+attr struct type *name##_RB_NEXT(struct type *);                       \
+attr struct type *name##_RB_PREV(struct type *);                       \
+attr struct type *name##_RB_MINMAX(struct name *, int);                        \
+                                                                       \
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define        RB_GENERATE(name, type, field, cmp)                             \
+       RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define        RB_GENERATE_STATIC(name, type, field, cmp)                      \
+       RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)             \
+attr void                                                              \
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm)            \
+{                                                                      \
+       struct type *parent, *gparent, *tmp;                            \
+       while ((parent = RB_PARENT(elm, field)) != NULL &&              \
+           RB_COLOR(parent, field) == RB_RED) {                        \
+               gparent = RB_PARENT(parent, field);                     \
+               if (parent == RB_LEFT(gparent, field)) {                \
+                       tmp = RB_RIGHT(gparent, field);                 \
+                       if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \
+                               RB_COLOR(tmp, field) = RB_BLACK;        \
+                               RB_SET_BLACKRED(parent, gparent, field);\
+                               elm = gparent;                          \
+                               continue;                               \
+                       }                                               \
+                       if (RB_RIGHT(parent, field) == elm) {           \
+                               RB_ROTATE_LEFT(head, parent, tmp, field);\
+                               tmp = parent;                           \
+                               parent = elm;                           \
+                               elm = tmp;                              \
+                       }                                               \
+                       RB_SET_BLACKRED(parent, gparent, field);        \
+                       RB_ROTATE_RIGHT(head, gparent, tmp, field);     \
+               } else {                                                \
+                       tmp = RB_LEFT(gparent, field);                  \
+                       if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \
+                               RB_COLOR(tmp, field) = RB_BLACK;        \
+                               RB_SET_BLACKRED(parent, gparent, field);\
+                               elm = gparent;                          \
+                               continue;                               \
+                       }                                               \
+                       if (RB_LEFT(parent, field) == elm) {            \
+                               RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                               tmp = parent;                           \
+                               parent = elm;                           \
+                               elm = tmp;                              \
+                       }                                               \
+                       RB_SET_BLACKRED(parent, gparent, field);        \
+                       RB_ROTATE_LEFT(head, gparent, tmp, field);      \
+               }                                                       \
+       }                                                               \
+       RB_COLOR(head->rbh_root, field) = RB_BLACK;                     \
+}                                                                      \
+                                                                       \
+attr void                                                              \
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{                                                                      \
+       struct type *tmp;                                               \
+       while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&     \
+           elm != RB_ROOT(head)) {                                     \
+               if (RB_LEFT(parent, field) == elm) {                    \
+                       tmp = RB_RIGHT(parent, field);                  \
+                       if (RB_COLOR(tmp, field) == RB_RED) {           \
+                               RB_SET_BLACKRED(tmp, parent, field);    \
+                               RB_ROTATE_LEFT(head, parent, tmp, field);\
+                               tmp = RB_RIGHT(parent, field);          \
+                       }                                               \
+                       if ((RB_LEFT(tmp, field) == NULL ||             \
+                           RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+                           (RB_RIGHT(tmp, field) == NULL ||            \
+                           RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+                               RB_COLOR(tmp, field) = RB_RED;          \
+                               elm = parent;                           \
+                               parent = RB_PARENT(elm, field);         \
+                       } else {                                        \
+                               if (RB_RIGHT(tmp, field) == NULL ||     \
+                                   RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+                                       struct type *oleft;             \
+                                       if ((oleft = RB_LEFT(tmp, field)) \
+                                           != NULL)                    \
+                                               RB_COLOR(oleft, field) = RB_BLACK;\
+                                       RB_COLOR(tmp, field) = RB_RED;  \
+                                       RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+                                       tmp = RB_RIGHT(parent, field);  \
+                               }                                       \
+                               RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+                               RB_COLOR(parent, field) = RB_BLACK;     \
+                               if (RB_RIGHT(tmp, field))               \
+                                       RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+                               RB_ROTATE_LEFT(head, parent, tmp, field);\
+                               elm = RB_ROOT(head);                    \
+                               break;                                  \
+                       }                                               \
+               } else {                                                \
+                       tmp = RB_LEFT(parent, field);                   \
+                       if (RB_COLOR(tmp, field) == RB_RED) {           \
+                               RB_SET_BLACKRED(tmp, parent, field);    \
+                               RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                               tmp = RB_LEFT(parent, field);           \
+                       }                                               \
+                       if ((RB_LEFT(tmp, field) == NULL ||             \
+                           RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+                           (RB_RIGHT(tmp, field) == NULL ||            \
+                           RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+                               RB_COLOR(tmp, field) = RB_RED;          \
+                               elm = parent;                           \
+                               parent = RB_PARENT(elm, field);         \
+                       } else {                                        \
+                               if (RB_LEFT(tmp, field) == NULL ||      \
+                                   RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+                                       struct type *oright;            \
+                                       if ((oright = RB_RIGHT(tmp, field)) \
+                                           != NULL)                    \
+                                               RB_COLOR(oright, field) = RB_BLACK;\
+                                       RB_COLOR(tmp, field) = RB_RED;  \
+                                       RB_ROTATE_LEFT(head, tmp, oright, field);\
+                                       tmp = RB_LEFT(parent, field);   \
+                               }                                       \
+                               RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+                               RB_COLOR(parent, field) = RB_BLACK;     \
+                               if (RB_LEFT(tmp, field))                \
+                                       RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+                               RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                               elm = RB_ROOT(head);                    \
+                               break;                                  \
+                       }                                               \
+               }                                                       \
+       }                                                               \
+       if (elm)                                                        \
+               RB_COLOR(elm, field) = RB_BLACK;                        \
+}                                                                      \
+                                                                       \
+attr struct type *                                                     \
+name##_RB_REMOVE(struct name *head, struct type *elm)                  \
+{                                                                      \
+       struct type *child, *parent, *old = elm;                        \
+       int color;                                                      \
+       if (RB_LEFT(elm, field) == NULL)                                \
+               child = RB_RIGHT(elm, field);                           \
+       else if (RB_RIGHT(elm, field) == NULL)                          \
+               child = RB_LEFT(elm, field);                            \
+       else {                                                          \
+               struct type *left;                                      \
+               elm = RB_RIGHT(elm, field);                             \
+               while ((left = RB_LEFT(elm, field)) != NULL)            \
+                       elm = left;                                     \
+               child = RB_RIGHT(elm, field);                           \
+               parent = RB_PARENT(elm, field);                         \
+               color = RB_COLOR(elm, field);                           \
+               if (child)                                              \
+                       RB_PARENT(child, field) = parent;               \
+               if (parent) {                                           \
+                       if (RB_LEFT(parent, field) == elm)              \
+                               RB_LEFT(parent, field) = child;         \
+                       else                                            \
+                               RB_RIGHT(parent, field) = child;        \
+                       RB_AUGMENT(parent);                             \
+               } else                                                  \
+                       RB_ROOT(head) = child;                          \
+               if (RB_PARENT(elm, field) == old)                       \
+                       parent = elm;                                   \
+               (elm)->field = (old)->field;                            \
+               if (RB_PARENT(old, field)) {                            \
+                       if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+                               RB_LEFT(RB_PARENT(old, field), field) = elm;\
+                       else                                            \
+                               RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+                       RB_AUGMENT(RB_PARENT(old, field));              \
+               } else                                                  \
+                       RB_ROOT(head) = elm;                            \
+               RB_PARENT(RB_LEFT(old, field), field) = elm;            \
+               if (RB_RIGHT(old, field))                               \
+                       RB_PARENT(RB_RIGHT(old, field), field) = elm;   \
+               if (parent) {                                           \
+                       left = parent;                                  \
+                       do {                                            \
+                               RB_AUGMENT(left);                       \
+                       } while ((left = RB_PARENT(left, field)) != NULL); \
+               }                                                       \
+               goto color;                                             \
+       }                                                               \
+       parent = RB_PARENT(elm, field);                                 \
+       color = RB_COLOR(elm, field);                                   \
+       if (child)                                                      \
+               RB_PARENT(child, field) = parent;                       \
+       if (parent) {                                                   \
+               if (RB_LEFT(parent, field) == elm)                      \
+                       RB_LEFT(parent, field) = child;                 \
+               else                                                    \
+                       RB_RIGHT(parent, field) = child;                \
+               RB_AUGMENT(parent);                                     \
+       } else                                                          \
+               RB_ROOT(head) = child;                                  \
+color:                                                                 \
+       if (color == RB_BLACK)                                          \
+               name##_RB_REMOVE_COLOR(head, parent, child);            \
+       return (old);                                                   \
+}                                                                      \
+                                                                       \
+/* Inserts a node into the RB tree */                                  \
+attr struct type *                                                     \
+name##_RB_INSERT(struct name *head, struct type *elm)                  \
+{                                                                      \
+       struct type *tmp;                                               \
+       struct type *parent = NULL;                                     \
+       int comp = 0;                                                   \
+       tmp = RB_ROOT(head);                                            \
+       while (tmp) {                                                   \
+               parent = tmp;                                           \
+               comp = (cmp)(elm, parent);                              \
+               if (comp < 0)                                           \
+                       tmp = RB_LEFT(tmp, field);                      \
+               else if (comp > 0)                                      \
+                       tmp = RB_RIGHT(tmp, field);                     \
+               else                                                    \
+                       return (tmp);                                   \
+       }                                                               \
+       RB_SET(elm, parent, field);                                     \
+       if (parent != NULL) {                                           \
+               if (comp < 0)                                           \
+                       RB_LEFT(parent, field) = elm;                   \
+               else                                                    \
+                       RB_RIGHT(parent, field) = elm;                  \
+               RB_AUGMENT(parent);                                     \
+       } else                                                          \
+               RB_ROOT(head) = elm;                                    \
+       name##_RB_INSERT_COLOR(head, elm);                              \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+/* Finds the node with the same key as elm */                          \
+attr struct type *                                                     \
+name##_RB_FIND(struct name *head, struct type *elm)                    \
+{                                                                      \
+       struct type *tmp = RB_ROOT(head);                               \
+       int comp;                                                       \
+       while (tmp) {                                                   \
+               comp = cmp(elm, tmp);                                   \
+               if (comp < 0)                                           \
+                       tmp = RB_LEFT(tmp, field);                      \
+               else if (comp > 0)                                      \
+                       tmp = RB_RIGHT(tmp, field);                     \
+               else                                                    \
+                       return (tmp);                                   \
+       }                                                               \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+/* Finds the first node greater than or equal to the search key */     \
+attr struct type *                                                     \
+name##_RB_NFIND(struct name *head, struct type *elm)                   \
+{                                                                      \
+       struct type *tmp = RB_ROOT(head);                               \
+       struct type *res = NULL;                                        \
+       int comp;                                                       \
+       while (tmp) {                                                   \
+               comp = cmp(elm, tmp);                                   \
+               if (comp < 0) {                                         \
+                       res = tmp;                                      \
+                       tmp = RB_LEFT(tmp, field);                      \
+               }                                                       \
+               else if (comp > 0)                                      \
+                       tmp = RB_RIGHT(tmp, field);                     \
+               else                                                    \
+                       return (tmp);                                   \
+       }                                                               \
+       return (res);                                                   \
+}                                                                      \
+                                                                       \
+/* ARGSUSED */                                                         \
+attr struct type *                                                     \
+name##_RB_NEXT(struct type *elm)                                       \
+{                                                                      \
+       if (RB_RIGHT(elm, field)) {                                     \
+               elm = RB_RIGHT(elm, field);                             \
+               while (RB_LEFT(elm, field))                             \
+                       elm = RB_LEFT(elm, field);                      \
+       } else {                                                        \
+               if (RB_PARENT(elm, field) &&                            \
+                   (elm == RB_LEFT(RB_PARENT(elm, field), field)))     \
+                       elm = RB_PARENT(elm, field);                    \
+               else {                                                  \
+                       while (RB_PARENT(elm, field) &&                 \
+                           (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+                               elm = RB_PARENT(elm, field);            \
+                       elm = RB_PARENT(elm, field);                    \
+               }                                                       \
+       }                                                               \
+       return (elm);                                                   \
+}                                                                      \
+                                                                       \
+/* ARGSUSED */                                                         \
+attr struct type *                                                     \
+name##_RB_PREV(struct type *elm)                                       \
+{                                                                      \
+       if (RB_LEFT(elm, field)) {                                      \
+               elm = RB_LEFT(elm, field);                              \
+               while (RB_RIGHT(elm, field))                            \
+                       elm = RB_RIGHT(elm, field);                     \
+       } else {                                                        \
+               if (RB_PARENT(elm, field) &&                            \
+                   (elm == RB_RIGHT(RB_PARENT(elm, field), field)))    \
+                       elm = RB_PARENT(elm, field);                    \
+               else {                                                  \
+                       while (RB_PARENT(elm, field) &&                 \
+                           (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+                               elm = RB_PARENT(elm, field);            \
+                       elm = RB_PARENT(elm, field);                    \
+               }                                                       \
+       }                                                               \
+       return (elm);                                                   \
+}                                                                      \
+                                                                       \
+attr struct type *                                                     \
+name##_RB_MINMAX(struct name *head, int val)                           \
+{                                                                      \
+       struct type *tmp = RB_ROOT(head);                               \
+       struct type *parent = NULL;                                     \
+       while (tmp) {                                                   \
+               parent = tmp;                                           \
+               if (val < 0)                                            \
+                       tmp = RB_LEFT(tmp, field);                      \
+               else                                                    \
+                       tmp = RB_RIGHT(tmp, field);                     \
+       }                                                               \
+       return (parent);                                                \
+}
+
+#define RB_NEGINF      -1
+#define RB_INF 1
+
+#define RB_INSERT(name, x, y)  name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y)  name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y)    name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y)   name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y)    name##_RB_NEXT(y)
+#define RB_PREV(name, x, y)    name##_RB_PREV(y)
+#define RB_MIN(name, x)                name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x)                name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head)                                      \
+       for ((x) = RB_MIN(name, head);                                  \
+            (x) != NULL;                                               \
+            (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_FROM(x, name, y)                                    \
+       for ((x) = (y);                                                 \
+           ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL);    \
+            (x) = (y))
+
+#define RB_FOREACH_SAFE(x, name, head, y)                              \
+       for ((x) = RB_MIN(name, head);                                  \
+           ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL);    \
+            (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head)                              \
+       for ((x) = RB_MAX(name, head);                                  \
+            (x) != NULL;                                               \
+            (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_FROM(x, name, y)                            \
+       for ((x) = (y);                                                 \
+           ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL);    \
+            (x) = (y))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y)                      \
+       for ((x) = RB_MAX(name, head);                                  \
+           ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL);    \
+            (x) = (y))
+
+#endif /* _SYS_TREE_H_ */
diff --git a/reactos/base/services/nfsd/upcall.c b/reactos/base/services/nfsd/upcall.c
new file mode 100644 (file)
index 0000000..c259e07
--- /dev/null
@@ -0,0 +1,212 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "upcall.h"
+#include "daemon_debug.h"
+#include "util.h"
+
+extern const nfs41_upcall_op nfs41_op_mount;
+extern const nfs41_upcall_op nfs41_op_unmount;
+extern const nfs41_upcall_op nfs41_op_open;
+extern const nfs41_upcall_op nfs41_op_close;
+extern const nfs41_upcall_op nfs41_op_read;
+extern const nfs41_upcall_op nfs41_op_write;
+extern const nfs41_upcall_op nfs41_op_lock;
+extern const nfs41_upcall_op nfs41_op_unlock;
+extern const nfs41_upcall_op nfs41_op_readdir;
+extern const nfs41_upcall_op nfs41_op_getattr;
+extern const nfs41_upcall_op nfs41_op_setattr;
+extern const nfs41_upcall_op nfs41_op_getexattr;
+extern const nfs41_upcall_op nfs41_op_setexattr;
+extern const nfs41_upcall_op nfs41_op_symlink;
+extern const nfs41_upcall_op nfs41_op_volume;
+extern const nfs41_upcall_op nfs41_op_getacl;
+extern const nfs41_upcall_op nfs41_op_setacl;
+
+static const nfs41_upcall_op *g_upcall_op_table[] = {
+    &nfs41_op_mount,
+    &nfs41_op_unmount,
+    &nfs41_op_open,
+    &nfs41_op_close,
+    &nfs41_op_read,
+    &nfs41_op_write,
+    &nfs41_op_lock,
+    &nfs41_op_unlock,
+    &nfs41_op_readdir,
+    &nfs41_op_getattr,
+    &nfs41_op_setattr,
+    &nfs41_op_getexattr,
+    &nfs41_op_setexattr,
+    &nfs41_op_symlink,
+    &nfs41_op_volume,
+    &nfs41_op_getacl,
+    &nfs41_op_setacl,
+    NULL,
+    NULL
+};
+#ifdef __REACTOS__
+static const uint32_t g_upcall_op_table_size = (sizeof(g_upcall_op_table) / sizeof(g_upcall_op_table[0]));
+#else
+static const uint32_t g_upcall_op_table_size = ARRAYSIZE(g_upcall_op_table);
+#endif
+
+int upcall_parse(
+    IN unsigned char *buffer,
+    IN uint32_t length,
+    OUT nfs41_upcall *upcall)
+{
+    int status;
+    const nfs41_upcall_op *op;
+    DWORD version;
+
+    ZeroMemory(upcall, sizeof(nfs41_upcall));
+    if (!length) {
+        eprintf("empty upcall\n");
+        upcall->status = status = 102;
+        goto out;
+    }
+
+    dprintf(2, "received %d bytes upcall data: processing upcall\n", length);
+    print_hexbuf(4, (unsigned char *)"upcall buffer: ", buffer, length);
+
+    /* parse common elements */
+    status = safe_read(&buffer, &length, &version, sizeof(uint32_t));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &upcall->xid, sizeof(uint64_t));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &upcall->opcode, sizeof(uint32_t));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &upcall->root_ref, sizeof(HANDLE));
+    if (status) goto out;
+    status = safe_read(&buffer, &length, &upcall->state_ref, sizeof(HANDLE));
+    if (status) goto out;
+
+    dprintf(2, "time=%ld version=%d xid=%d opcode=%s session=0x%x open_state=0x%x\n", 
+        time(NULL), version, upcall->xid, opcode2string(upcall->opcode), upcall->root_ref, 
+        upcall->state_ref);
+    if (version != NFS41D_VERSION) {
+        eprintf("received version %d expecting version %d\n", version, NFS41D_VERSION);
+        upcall->status = status = NFSD_VERSION_MISMATCH;
+        goto out;
+    }
+    if (upcall->opcode >= g_upcall_op_table_size) {
+        status = ERROR_NOT_SUPPORTED;
+        eprintf("unrecognized upcall opcode %d!\n", upcall->opcode);
+        goto out;
+    }
+    if (upcall->root_ref != INVALID_HANDLE_VALUE)
+        nfs41_root_ref(upcall->root_ref);
+    if (upcall->state_ref != INVALID_HANDLE_VALUE)
+        nfs41_open_state_ref(upcall->state_ref);
+
+    /* parse the operation's arguments */
+    op = g_upcall_op_table[upcall->opcode];
+    if (op && op->parse) {
+        status = op->parse(buffer, length, upcall);
+        if (status) {
+            eprintf("parsing of upcall '%s' failed with %d.\n",
+                opcode2string(upcall->opcode), status);
+            goto out;
+        }
+    }
+out:
+    return status;
+}
+
+int upcall_handle(
+    IN nfs41_upcall *upcall)
+{
+    int status = NO_ERROR;
+    const nfs41_upcall_op *op;
+
+    op = g_upcall_op_table[upcall->opcode];
+    if (op == NULL || op->handle == NULL) {
+        status = ERROR_NOT_SUPPORTED;
+        eprintf("upcall '%s' missing handle function!\n",
+            opcode2string(upcall->opcode));
+        goto out;
+    }
+
+    upcall->status = op->handle(upcall);
+out:
+    return status;
+}
+#pragma warning (disable : 4706) /* assignment within conditional expression */
+void upcall_marshall(
+    IN nfs41_upcall *upcall,
+    OUT unsigned char *buffer,
+    IN uint32_t length,
+    OUT uint32_t *length_out)
+{
+    const nfs41_upcall_op *op;
+    unsigned char *orig_buf = buffer;
+    const uint32_t total = length, orig_len = length;
+
+    /* marshall common elements */
+write_downcall:
+    length = orig_len;
+    buffer = orig_buf;
+    safe_write(&buffer, &length, &upcall->xid, sizeof(upcall->xid));
+    safe_write(&buffer, &length, &upcall->opcode, sizeof(upcall->opcode));
+    safe_write(&buffer, &length, &upcall->status, sizeof(upcall->status));
+    safe_write(&buffer, &length, &upcall->last_error, sizeof(upcall->last_error));
+
+    if (upcall->status)
+        goto out;
+
+    /* marshall the operation's results */
+    op = g_upcall_op_table[upcall->opcode];
+    if (op && op->marshall) {
+        if ((upcall->status = op->marshall(buffer, &length, upcall)))
+            goto write_downcall;
+    }
+out:
+    *length_out = total - length;
+}
+
+void upcall_cancel(
+    IN nfs41_upcall *upcall)
+{
+    const nfs41_upcall_op *op = g_upcall_op_table[upcall->opcode];
+    if (op && op->cancel)
+        op->cancel(upcall);
+}
+
+void upcall_cleanup(
+    IN nfs41_upcall *upcall)
+{
+    const nfs41_upcall_op *op = g_upcall_op_table[upcall->opcode];
+    if (op && op->cleanup && upcall->status != NFSD_VERSION_MISMATCH)
+        op->cleanup(upcall);
+
+    if (upcall->state_ref && upcall->state_ref != INVALID_HANDLE_VALUE) {
+        nfs41_open_state_deref(upcall->state_ref);
+        upcall->state_ref = NULL;
+    }
+    if (upcall->root_ref && upcall->root_ref != INVALID_HANDLE_VALUE) {
+        nfs41_root_deref(upcall->root_ref);
+        upcall->root_ref = NULL;
+    }
+}
diff --git a/reactos/base/services/nfsd/upcall.h b/reactos/base/services/nfsd/upcall.h
new file mode 100644 (file)
index 0000000..4eeaf5e
--- /dev/null
@@ -0,0 +1,248 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_UPCALL_H__
+#define __NFS41_DAEMON_UPCALL_H__
+
+#include "nfs41_ops.h"
+#include "from_kernel.h"
+
+#define NFSD_VERSION_MISMATCH 116
+
+/* structures for upcall arguments */
+typedef struct __mount_upcall_args {
+    const char *hostname;
+    const char *path;
+    DWORD       sec_flavor;
+    DWORD       rsize;
+    DWORD       wsize;
+    DWORD       lease_time;
+    FILE_FS_ATTRIBUTE_INFORMATION FsAttrs;
+} mount_upcall_args;
+
+typedef struct __open_upcall_args {
+    nfs41_abs_path symlink;
+    FILE_BASIC_INFO basic_info;
+    FILE_STANDARD_INFO std_info;
+    const char *path;
+    ULONG access_mask;
+    ULONG access_mode; 
+    ULONG file_attrs;
+    ULONG disposition;
+    ULONG create_opts;
+    LONG open_owner_id;
+    DWORD mode;
+    ULONGLONG changeattr;
+    HANDLE srv_open;
+    DWORD deleg_type;
+    PFILE_FULL_EA_INFORMATION ea;
+    BOOLEAN created;
+    BOOLEAN symlink_embedded;
+} open_upcall_args;
+
+typedef struct __close_upcall_args {
+    HANDLE srv_open;
+    const char *path;
+    BOOLEAN remove;
+    BOOLEAN renamed;
+} close_upcall_args;
+
+typedef struct __readwrite_upcall_args {
+    unsigned char *buffer;
+    ULONGLONG offset;
+    ULONG len;
+    ULONG out_len;
+    ULONGLONG ctime;
+} readwrite_upcall_args;
+
+typedef struct __lock_upcall_args {
+    uint64_t offset;
+    uint64_t length;
+    BOOLEAN exclusive;
+    BOOLEAN blocking;
+    BOOLEAN acquired;
+} lock_upcall_args;
+
+typedef struct __unlock_upcall_args {
+    uint32_t count;
+    unsigned char *buf;
+    uint32_t buf_len;
+} unlock_upcall_args;
+
+typedef struct __getattr_upcall_args {
+    FILE_BASIC_INFO basic_info;
+    FILE_STANDARD_INFO std_info;
+    FILE_ATTRIBUTE_TAG_INFO tag_info;
+    FILE_INTERNAL_INFORMATION intr_info;
+    FILE_NETWORK_OPEN_INFORMATION network_info;
+    int query_class;
+    int buf_len;
+    int query_reply_len;
+    ULONGLONG ctime;
+} getattr_upcall_args;
+
+typedef struct __setattr_upcall_args {
+    const char *path;
+    nfs41_root *root;
+    nfs41_open_state *state;
+    unsigned char *buf;
+    uint32_t buf_len;
+    int set_class;
+    ULONGLONG ctime;
+} setattr_upcall_args;
+
+typedef struct __getexattr_upcall_args {
+    const char *path;
+    unsigned char *buf;
+    uint32_t buf_len;
+    ULONG eaindex;
+    unsigned char *ealist;
+    uint32_t ealist_len;
+    uint32_t overflow;
+    BOOLEAN single;
+    BOOLEAN restart;
+} getexattr_upcall_args;
+
+
+typedef struct __setexattr_upcall_args {
+    const char *path;
+    unsigned char *buf;
+    uint32_t buf_len;
+    uint32_t mode;
+    ULONGLONG ctime;
+} setexattr_upcall_args;
+
+typedef struct __readdir_upcall_args {
+    const char *filter;
+    nfs41_root *root;
+    nfs41_open_state *state;
+    int buf_len;
+    int query_class;
+    int query_reply_len;
+    BOOLEAN initial;
+    BOOLEAN restart;
+    BOOLEAN single;
+    unsigned char *kbuf;
+} readdir_upcall_args;
+
+typedef struct __symlink_upcall_args {
+    nfs41_abs_path target_get;
+    char *target_set;
+    const char *path;
+    BOOLEAN set;
+} symlink_upcall_args;
+
+typedef struct __volume_upcall_args {
+    FS_INFORMATION_CLASS query;
+    int len;
+    union {
+        FILE_FS_SIZE_INFORMATION size;
+        FILE_FS_FULL_SIZE_INFORMATION fullsize;
+        FILE_FS_ATTRIBUTE_INFORMATION attribute;
+    } info;
+} volume_upcall_args;
+
+typedef struct __getacl_upcall_args {
+    SECURITY_INFORMATION query;
+    PSECURITY_DESCRIPTOR sec_desc;
+    DWORD sec_desc_len;
+} getacl_upcall_args;
+
+typedef struct __setacl_upcall_args {
+    SECURITY_INFORMATION query;
+    PSECURITY_DESCRIPTOR sec_desc;
+    ULONGLONG ctime;
+} setacl_upcall_args;
+
+typedef union __upcall_args {
+    mount_upcall_args       mount;
+    open_upcall_args        open;
+    close_upcall_args       close;
+    readwrite_upcall_args   rw;
+    lock_upcall_args        lock;
+    unlock_upcall_args      unlock;
+    getattr_upcall_args     getattr;
+    getexattr_upcall_args   getexattr;
+    setattr_upcall_args     setattr;
+    setexattr_upcall_args   setexattr;
+    readdir_upcall_args     readdir;
+    symlink_upcall_args     symlink;
+    volume_upcall_args      volume;
+    getacl_upcall_args      getacl;
+    setacl_upcall_args      setacl;
+} upcall_args;
+
+typedef struct __nfs41_upcall {
+    uint64_t                xid;
+    uint32_t                opcode;
+    uint32_t                status;
+    uint32_t                last_error;
+    upcall_args             args;
+
+    uid_t                   uid;
+    gid_t                   gid;
+
+    /* store referenced pointers with the upcall for
+     * automatic dereferencing on upcall_cleanup();
+     * see upcall_root_ref() and upcall_open_state_ref() */
+    nfs41_root              *root_ref;
+    nfs41_open_state        *state_ref;
+} nfs41_upcall;
+
+
+/* upcall operation interface */
+typedef int (*upcall_parse_proc)(unsigned char*, uint32_t, nfs41_upcall*);
+typedef int (*upcall_handle_proc)(nfs41_upcall*);
+typedef int (*upcall_marshall_proc)(unsigned char*, uint32_t*, nfs41_upcall*);
+typedef void (*upcall_cancel_proc)(nfs41_upcall*);
+typedef void (*upcall_cleanup_proc)(nfs41_upcall*);
+
+typedef struct __nfs41_upcall_op {
+    upcall_parse_proc       parse;
+    upcall_handle_proc      handle;
+    upcall_marshall_proc    marshall;
+    upcall_cancel_proc      cancel;
+    upcall_cleanup_proc     cleanup;
+} nfs41_upcall_op;
+
+
+/* upcall.c */
+int upcall_parse(
+    IN unsigned char *buffer,
+    IN uint32_t length,
+    OUT nfs41_upcall *upcall);
+
+int upcall_handle(
+    IN nfs41_upcall *upcall);
+
+void upcall_marshall(
+    IN nfs41_upcall *upcall,
+    OUT unsigned char *buffer,
+    IN uint32_t length,
+    OUT uint32_t *length_out);
+
+void upcall_cancel(
+    IN nfs41_upcall *upcall);
+
+void upcall_cleanup(
+    IN nfs41_upcall *upcall);
+
+#endif /* !__NFS41_DAEMON_UPCALL_H__ */
diff --git a/reactos/base/services/nfsd/util.c b/reactos/base/services/nfsd/util.c
new file mode 100644 (file)
index 0000000..c565c15
--- /dev/null
@@ -0,0 +1,448 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wincrypt.h> /* for Crypt*() functions */
+
+#include "daemon_debug.h"
+#include "util.h"
+#include "nfs41_ops.h"
+
+
+int safe_read(unsigned char **pos, uint32_t *remaining, void *dest, uint32_t dest_len) 
+{
+    if (*remaining < dest_len)
+        return ERROR_BUFFER_OVERFLOW;
+
+    CopyMemory(dest, *pos, dest_len);
+    *pos += dest_len;
+    *remaining -= dest_len;
+    return 0;
+}
+
+int safe_write(unsigned char **pos, uint32_t *remaining, void *src, uint32_t src_len)
+{
+    if (*remaining < src_len)
+        return ERROR_BUFFER_OVERFLOW;
+
+    CopyMemory(*pos, src, src_len);
+    *pos += src_len;
+    *remaining -= src_len;
+    return 0;
+}
+
+int get_name(unsigned char **pos, uint32_t *remaining, const char **out_name)
+{
+    int status;
+    USHORT len;
+    
+    status = safe_read(pos, remaining, &len, sizeof(USHORT));
+    if (status) goto out;
+    if (*remaining < len) {
+        status = ERROR_BUFFER_OVERFLOW;
+        goto out;
+    }
+    *out_name = (const char*)*pos;
+    *pos += len;
+    *remaining -= len;
+out:
+    return status;
+}
+
+const char* strip_path(
+    IN const char *path,
+    OUT uint32_t *len_out)
+{
+    const char *name = strrchr(path, '\\');
+    name = name ? name + 1 : path;
+    if (len_out)
+        *len_out = (uint32_t)strlen(name);
+    return name;
+}
+
+uint32_t max_read_size(
+    IN const nfs41_session *session,
+    IN const nfs41_fh *fh)
+{
+    const uint32_t maxresponse = session->fore_chan_attrs.ca_maxresponsesize;
+    return (uint32_t)min(fh->superblock->maxread, maxresponse - READ_OVERHEAD);
+}
+
+uint32_t max_write_size(
+    IN const nfs41_session *session,
+    IN const nfs41_fh *fh)
+{
+    const uint32_t maxrequest = session->fore_chan_attrs.ca_maxrequestsize;
+    return (uint32_t)min(fh->superblock->maxwrite, maxrequest - WRITE_OVERHEAD);
+}
+
+bool_t verify_write(
+    IN nfs41_write_verf *verf,
+    IN OUT enum stable_how4 *stable)
+{
+    if (verf->committed != UNSTABLE4) {
+        *stable = verf->committed;
+        dprintf(3, "verify_write: committed to stable storage\n");
+        return 1;
+    }
+
+    if (*stable != UNSTABLE4) {
+        memcpy(verf->expected, verf->verf, NFS4_VERIFIER_SIZE);
+        *stable = UNSTABLE4;
+        dprintf(3, "verify_write: first unstable write, saving verifier\n");
+        return 1;
+    }
+
+    if (memcmp(verf->expected, verf->verf, NFS4_VERIFIER_SIZE) == 0) {
+        dprintf(3, "verify_write: verifier matches expected\n");
+        return 1;
+    }
+
+    dprintf(2, "verify_write: verifier changed; writes have been lost!\n");
+    return 0;
+}
+
+bool_t verify_commit(
+    IN nfs41_write_verf *verf)
+{
+    if (memcmp(verf->expected, verf->verf, NFS4_VERIFIER_SIZE) == 0) {
+        dprintf(3, "verify_commit: verifier matches expected\n");
+        return 1;
+    }
+    dprintf(2, "verify_commit: verifier changed; writes have been lost!\n");
+    return 0;
+}
+
+ULONG nfs_file_info_to_attributes(
+    IN const nfs41_file_info *info)
+{
+    ULONG attrs = 0;
+    if (info->type == NF4DIR)
+        attrs |= FILE_ATTRIBUTE_DIRECTORY;
+    else if (info->type == NF4LNK) {
+        attrs |= FILE_ATTRIBUTE_REPARSE_POINT;
+        if (info->symlink_dir)
+            attrs |= FILE_ATTRIBUTE_DIRECTORY;
+    } else if (info->type != NF4REG)
+        dprintf(1, "unhandled file type %d, defaulting to NF4REG\n",
+            info->type);
+
+    if (info->mode == 0444) /* XXX: 0444 for READONLY */
+        attrs |= FILE_ATTRIBUTE_READONLY;
+
+    if (info->hidden) attrs |= FILE_ATTRIBUTE_HIDDEN;
+    if (info->system) attrs |= FILE_ATTRIBUTE_SYSTEM;
+    if (info->archive) attrs |= FILE_ATTRIBUTE_ARCHIVE;
+
+    // FILE_ATTRIBUTE_NORMAL attribute is only set if no other attributes are present.
+    // all other override this value.
+    return attrs ? attrs : FILE_ATTRIBUTE_NORMAL;
+}
+
+void nfs_to_basic_info(
+    IN const nfs41_file_info *info,
+    OUT PFILE_BASIC_INFO basic_out)
+{
+    nfs_time_to_file_time(&info->time_create, &basic_out->CreationTime);
+    nfs_time_to_file_time(&info->time_access, &basic_out->LastAccessTime);
+    nfs_time_to_file_time(&info->time_modify, &basic_out->LastWriteTime);
+    /* XXX: was using 'change' attr, but that wasn't giving a time */
+    nfs_time_to_file_time(&info->time_modify, &basic_out->ChangeTime);
+    basic_out->FileAttributes = nfs_file_info_to_attributes(info);
+}
+
+void nfs_to_standard_info(
+    IN const nfs41_file_info *info,
+    OUT PFILE_STANDARD_INFO std_out)
+{
+    const ULONG FileAttributes = nfs_file_info_to_attributes(info);
+
+    std_out->AllocationSize.QuadPart =
+        std_out->EndOfFile.QuadPart = (LONGLONG)info->size;
+    std_out->NumberOfLinks = info->numlinks;
+    std_out->DeletePending = FALSE;
+    std_out->Directory = FileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
+        TRUE : FALSE;
+}
+
+void nfs_to_network_openinfo(
+    IN const nfs41_file_info *info,
+    OUT PFILE_NETWORK_OPEN_INFORMATION net_out)
+{
+    
+    nfs_time_to_file_time(&info->time_create, &net_out->CreationTime);
+    nfs_time_to_file_time(&info->time_access, &net_out->LastAccessTime);
+    nfs_time_to_file_time(&info->time_modify, &net_out->LastWriteTime);
+    /* XXX: was using 'change' attr, but that wasn't giving a time */
+    nfs_time_to_file_time(&info->time_modify, &net_out->ChangeTime);
+    net_out->AllocationSize.QuadPart =
+        net_out->EndOfFile.QuadPart = (LONGLONG)info->size;
+    net_out->FileAttributes = nfs_file_info_to_attributes(info);
+}
+
+void get_file_time(
+    OUT PLARGE_INTEGER file_time)
+{
+    GetSystemTimeAsFileTime((LPFILETIME)file_time);
+}
+
+void get_nfs_time(
+    OUT nfstime4 *nfs_time)
+{
+    LARGE_INTEGER file_time;
+    get_file_time(&file_time);
+    file_time_to_nfs_time(&file_time, nfs_time);
+}
+
+bool_t multi_addr_find(
+    IN const multi_addr4 *addrs,
+    IN const netaddr4 *addr,
+    OUT OPTIONAL uint32_t *index_out)
+{
+    uint32_t i;
+    for (i = 0; i < addrs->count; i++) {
+        const netaddr4 *saddr = &addrs->arr[i];
+        if (!strncmp(saddr->netid, addr->netid, NFS41_NETWORK_ID_LEN) &&
+            !strncmp(saddr->uaddr, addr->uaddr, NFS41_UNIVERSAL_ADDR_LEN)) {
+            if (index_out) *index_out = i;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int nfs_to_windows_error(int status, int default_error)
+{
+    /* make sure this is actually an nfs error */
+    if (status < 0 || (status > 70 && status < 10001) || status > 10087) {
+        eprintf("nfs_to_windows_error called with non-nfs "
+            "error code %d; returning the error as is\n", status);
+        return status;
+    }
+
+    switch (status) {
+    case NFS4_OK:               return NO_ERROR;
+    case NFS4ERR_PERM:          return ERROR_ACCESS_DENIED;
+    case NFS4ERR_NOENT:         return ERROR_FILE_NOT_FOUND;
+    case NFS4ERR_IO:            return ERROR_NET_WRITE_FAULT;
+    case NFS4ERR_ACCESS:        return ERROR_ACCESS_DENIED;
+    case NFS4ERR_EXIST:         return ERROR_FILE_EXISTS;
+    case NFS4ERR_XDEV:          return ERROR_NOT_SAME_DEVICE;
+    case NFS4ERR_INVAL:         return ERROR_INVALID_PARAMETER;
+    case NFS4ERR_FBIG:          return ERROR_FILE_TOO_LARGE;
+    case NFS4ERR_NOSPC:         return ERROR_DISK_FULL;
+    case NFS4ERR_ROFS:          return ERROR_NETWORK_ACCESS_DENIED;
+    case NFS4ERR_MLINK:         return ERROR_TOO_MANY_LINKS;
+    case NFS4ERR_NAMETOOLONG:   return ERROR_FILENAME_EXCED_RANGE;
+    case NFS4ERR_STALE:         return ERROR_NETNAME_DELETED;
+    case NFS4ERR_NOTEMPTY:      return ERROR_NOT_EMPTY;
+    case NFS4ERR_DENIED:        return ERROR_LOCK_FAILED;
+    case NFS4ERR_TOOSMALL:      return ERROR_BUFFER_OVERFLOW;
+    case NFS4ERR_LOCKED:        return ERROR_LOCK_VIOLATION;
+    case NFS4ERR_SHARE_DENIED:  return ERROR_SHARING_VIOLATION;
+    case NFS4ERR_LOCK_RANGE:    return ERROR_NOT_LOCKED;
+    case NFS4ERR_ATTRNOTSUPP:   return ERROR_NOT_SUPPORTED;
+    case NFS4ERR_OPENMODE:      return ERROR_ACCESS_DENIED;
+    case NFS4ERR_LOCK_NOTSUPP:  return ERROR_ATOMIC_LOCKS_NOT_SUPPORTED;
+
+    case NFS4ERR_BADCHAR:
+    case NFS4ERR_BADNAME:       return ERROR_INVALID_NAME;
+
+    case NFS4ERR_NOTDIR:
+    case NFS4ERR_ISDIR:
+    case NFS4ERR_SYMLINK:
+    case NFS4ERR_WRONG_TYPE:    return ERROR_INVALID_PARAMETER;
+
+    case NFS4ERR_EXPIRED:
+    case NFS4ERR_NOFILEHANDLE:
+    case NFS4ERR_OLD_STATEID:
+    case NFS4ERR_BAD_STATEID:
+    case NFS4ERR_ADMIN_REVOKED: return ERROR_FILE_INVALID;
+
+    case NFS4ERR_WRONGSEC:      return ERROR_ACCESS_DENIED;
+
+    default:
+        dprintf(1, "nfs error %s not mapped to windows error; "
+            "returning default error %d\n",
+            nfs_error_string(status), default_error);
+        return default_error;
+    }
+}
+
+int map_symlink_errors(int status)
+{
+    switch (status) {
+    case NFS4ERR_BADCHAR:
+    case NFS4ERR_BADNAME:       return ERROR_INVALID_REPARSE_DATA;
+    case NFS4ERR_WRONG_TYPE:    return ERROR_NOT_A_REPARSE_POINT;
+    case NFS4ERR_ACCESS:        return ERROR_ACCESS_DENIED;
+    case NFS4ERR_NOTEMPTY:      return ERROR_NOT_EMPTY;
+    default: return nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+    }
+}
+
+bool_t next_component(
+    IN const char *path,
+    IN const char *path_end,
+    OUT nfs41_component *component)
+{
+    const char *component_end;
+    component->name = next_non_delimiter(path, path_end);
+    component_end = next_delimiter(component->name, path_end);
+    component->len = (unsigned short)(component_end - component->name);
+    return component->len > 0;
+}
+
+bool_t last_component(
+    IN const char *path,
+    IN const char *path_end,
+    OUT nfs41_component *component)
+{
+    const char *component_end = prev_delimiter(path_end, path);
+    component->name = prev_non_delimiter(component_end, path);
+    component->name = prev_delimiter(component->name, path);
+    component->name = next_non_delimiter(component->name, component_end);
+    component->len = (unsigned short)(component_end - component->name);
+    return component->len > 0;
+}
+
+bool_t is_last_component(
+    IN const char *path,
+    IN const char *path_end)
+{
+    path = next_delimiter(path, path_end);
+    return next_non_delimiter(path, path_end) == path_end;
+}
+
+void abs_path_copy(
+    OUT nfs41_abs_path *dst,
+    IN const nfs41_abs_path *src)
+{
+    dst->len = src->len;
+    StringCchCopyNA(dst->path, NFS41_MAX_PATH_LEN, src->path, dst->len);
+}
+
+void path_fh_init(
+    OUT nfs41_path_fh *file,
+    IN nfs41_abs_path *path)
+{
+    file->path = path;
+    last_component(path->path, path->path + path->len, &file->name);
+}
+
+void fh_copy(
+    OUT nfs41_fh *dst,
+    IN const nfs41_fh *src)
+{
+    dst->fileid = src->fileid;
+    dst->superblock = src->superblock;
+    dst->len = src->len;
+    memcpy(dst->fh, src->fh, dst->len);
+}
+
+void path_fh_copy(
+    OUT nfs41_path_fh *dst,
+    IN const nfs41_path_fh *src)
+{
+    dst->path = src->path;
+    if (dst->path) {
+        const size_t name_start = src->name.name - src->path->path;
+        dst->name.name = dst->path->path + name_start;
+        dst->name.len = src->name.len;
+    } else {
+        dst->name.name = NULL;
+        dst->name.len = 0;
+    }
+    fh_copy(&dst->fh, &src->fh);
+}
+
+int create_silly_rename(
+    IN nfs41_abs_path *path,
+    IN const nfs41_fh *fh,
+    OUT nfs41_component *silly)
+{
+    HCRYPTPROV context;
+    HCRYPTHASH hash;
+    PBYTE buffer;
+    DWORD length;
+    const char *end = path->path + NFS41_MAX_PATH_LEN;
+    const unsigned short extra_len = 2 + 16; //md5 is 16
+    char name[NFS41_MAX_COMPONENT_LEN+1];
+    unsigned char fhmd5[17] = { 0 };
+    char *tmp;
+    int status = NO_ERROR, i;
+
+    if (path->len + extra_len >= NFS41_MAX_PATH_LEN) {
+        status = ERROR_FILENAME_EXCED_RANGE;
+        goto out;
+    }
+
+    /* set up the md5 hash generator */
+    if (!CryptAcquireContext(&context, NULL, NULL,
+        PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
+        status = GetLastError();
+        eprintf("CryptAcquireContext() failed with %d\n", status);
+        goto out;
+    }
+    if (!CryptCreateHash(context, CALG_MD5, 0, 0, &hash)) {
+        status = GetLastError();
+        eprintf("CryptCreateHash() failed with %d\n", status);
+        goto out_context;
+    }
+
+    if (!CryptHashData(hash, (const BYTE*)fh->fh, (DWORD)fh->len, 0)) {
+        status = GetLastError();
+        eprintf("CryptHashData() failed with %d\n", status);
+        goto out_hash;
+    }
+
+    /* extract the hash buffer */
+    buffer = (PBYTE)fhmd5;
+    length = 16;
+    if (!CryptGetHashParam(hash, HP_HASHVAL, buffer, &length, 0)) {
+        status = GetLastError();
+        eprintf("CryptGetHashParam(val) failed with %d\n", status);
+        goto out_hash;
+    }    
+
+    last_component(path->path, path->path + path->len, silly);
+    StringCchCopyNA(name, NFS41_MAX_COMPONENT_LEN+1, silly->name, silly->len);
+
+    tmp = (char*)silly->name;
+    StringCchPrintf(tmp, end - tmp, ".%s.", name);
+    tmp += silly->len + 2;
+
+    for (i = 0; i < 16; i++, tmp++)
+        StringCchPrintf(tmp, end - tmp, "%x", fhmd5[i]);
+
+    path->len = path->len + extra_len;
+    silly->len = silly->len + extra_len;
+
+out_hash:
+    CryptDestroyHash(hash);
+out_context:
+    CryptReleaseContext(context, 0);
+out:
+    return status;
+}
diff --git a/reactos/base/services/nfsd/util.h b/reactos/base/services/nfsd/util.h
new file mode 100644 (file)
index 0000000..735c91d
--- /dev/null
@@ -0,0 +1,288 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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_UTIL_H__
+#define __NFS41_DAEMON_UTIL_H__
+
+#include "nfs41_types.h"
+#include "from_kernel.h"
+
+extern DWORD NFS41D_VERSION;
+struct __nfs41_session;
+struct __nfs41_write_verf;
+enum stable_how4;
+
+int safe_read(unsigned char **pos, uint32_t *remaining, void *dest, uint32_t dest_len);
+int safe_write(unsigned char **pos, uint32_t *remaining, void *dest, uint32_t dest_len);
+int get_name(unsigned char **pos, uint32_t *remaining, const char **out_name);
+
+const char* strip_path(
+    IN const char *path,
+    OUT uint32_t *len_out OPTIONAL);
+
+uint32_t max_read_size(
+    IN const struct __nfs41_session *session,
+    IN const nfs41_fh *fh);
+uint32_t max_write_size(
+    IN const struct __nfs41_session *session,
+    IN const nfs41_fh *fh);
+
+bool_t verify_write(
+    IN nfs41_write_verf *verf,
+    IN OUT enum stable_how4 *stable);
+bool_t verify_commit(
+    IN nfs41_write_verf *verf);
+
+/* bitmap4 */
+static __inline bool_t bitmap_isset(
+    IN const bitmap4 *mask,
+    IN uint32_t word,
+    IN uint32_t flag)
+{
+    return mask->count > word && mask->arr[word] & flag;
+}
+static __inline void bitmap_set(
+    IN bitmap4 *mask,
+    IN uint32_t word,
+    IN uint32_t flag)
+{
+    if (mask->count > word)
+        mask->arr[word] |= flag;
+    else {
+        mask->count = word + 1;
+        mask->arr[word] = flag;
+    }
+}
+static __inline void bitmap_unset(
+    IN bitmap4 *mask,
+    IN uint32_t word,
+    IN uint32_t flag)
+{
+    if (mask->count > word) {
+        mask->arr[word] &= ~flag;
+        while (mask->count && mask->arr[mask->count-1] == 0)
+            mask->count--;
+    }
+}
+static __inline void bitmap_intersect(
+    IN bitmap4 *dst,
+    IN const bitmap4 *src)
+{
+    uint32_t i, count = 0;
+    for (i = 0; i < 3; i++) {
+        dst->arr[i] &= src->arr[i];
+        if (dst->arr[i])
+            count = i+1;
+    }
+    dst->count = min(dst->count, count);
+}
+
+ULONG nfs_file_info_to_attributes(
+    IN const nfs41_file_info *info);
+void nfs_to_basic_info(
+    IN const nfs41_file_info *info,
+    OUT PFILE_BASIC_INFO basic_out);
+void nfs_to_standard_info(
+    IN const nfs41_file_info *info,
+    OUT PFILE_STANDARD_INFO std_out);
+void nfs_to_network_openinfo(
+    IN const nfs41_file_info *info,
+    OUT PFILE_NETWORK_OPEN_INFORMATION std_out);
+
+/* http://msdn.microsoft.com/en-us/library/ms724290%28VS.85%29.aspx:
+ * A file time is a 64-bit value that represents the number of
+ * 100-nanosecond intervals that have elapsed since 12:00 A.M.
+ * January 1, 1601 Coordinated Universal Time (UTC). */
+#define FILETIME_EPOCH 116444736000000000LL
+
+static __inline void file_time_to_nfs_time(
+    IN const PLARGE_INTEGER file_time,
+    OUT nfstime4 *nfs_time)
+{
+    LONGLONG diff = file_time->QuadPart - FILETIME_EPOCH;
+    nfs_time->seconds = diff / 10000000;
+    nfs_time->nseconds = (uint32_t)((diff % 10000000)*100);
+}
+
+static __inline void nfs_time_to_file_time(
+    IN const nfstime4 *nfs_time,
+    OUT PLARGE_INTEGER file_time)
+{
+    file_time->QuadPart = FILETIME_EPOCH +
+        nfs_time->seconds * 10000000 +
+        nfs_time->nseconds / 100;
+}
+
+void get_file_time(
+    OUT PLARGE_INTEGER file_time);
+void get_nfs_time(
+    OUT nfstime4 *nfs_time);
+
+static __inline void nfstime_normalize(
+    IN OUT nfstime4 *nfstime)
+{
+    /* return time in normalized form (0 <= nsec < 1s) */
+    while ((int32_t)nfstime->nseconds < 0) {
+        nfstime->nseconds += 1000000000;
+        nfstime->seconds--;
+    }
+}
+static __inline void nfstime_diff(
+    IN const nfstime4 *lhs,
+    IN const nfstime4 *rhs,
+    OUT nfstime4 *result)
+{
+    /* result = lhs - rhs */
+    result->seconds = lhs->seconds - rhs->seconds;
+    result->nseconds = lhs->nseconds - rhs->nseconds;
+    nfstime_normalize(result);
+}
+static __inline void nfstime_abs(
+    IN const nfstime4 *nt,
+    OUT nfstime4 *result)
+{
+    if (nt->seconds < 0) {
+        const nfstime4 zero = { 0, 0 };
+        nfstime_diff(&zero, nt, result); /* result = 0 - nt */
+    } else if (result != nt)
+        memcpy(result, nt, sizeof(nfstime4));
+}
+
+
+int create_silly_rename(
+    IN nfs41_abs_path *path,
+    IN const nfs41_fh *fh,
+    OUT nfs41_component *silly);
+
+bool_t multi_addr_find(
+    IN const multi_addr4 *addrs,
+    IN const netaddr4 *addr,
+    OUT OPTIONAL uint32_t *index_out);
+
+/* nfs_to_windows_error
+ *   Returns a windows ERROR_ code corresponding to the given NFS4ERR_ status.
+ * If the status is outside the range of valid NFS4ERR_ values, it is returned
+ * unchanged.  Otherwise, if the status does not match a value in the mapping,
+ * a debug warning is generated and the default_error value is returned.
+ */
+int nfs_to_windows_error(int status, int default_error);
+
+int map_symlink_errors(int status);
+
+#ifndef __REACTOS__
+__inline uint32_t align8(uint32_t offset) {
+#else
+FORCEINLINE uint32_t align8(uint32_t offset) {
+#endif
+    return 8 + ((offset - 1) & ~7);
+}
+#ifndef __REACTOS__
+__inline uint32_t align4(uint32_t offset) {
+#else
+FORCEINLINE uint32_t align4(uint32_t offset) {
+#endif
+    return 4 + ((offset - 1) & ~3);
+}
+
+/* path parsing */
+#ifndef __REACTOS__
+__inline int is_delimiter(char c) {
+#else
+FORCEINLINE int is_delimiter(char c) {
+#endif
+    return c == '\\' || c == '/' || c == '\0';
+}
+#ifndef __REACTOS__
+__inline const char* next_delimiter(const char *pos, const char *end) {
+#else
+FORCEINLINE const char* next_delimiter(const char *pos, const char *end) {
+#endif
+    while (pos < end && !is_delimiter(*pos))
+        pos++;
+    return pos;
+}
+#ifndef __REACTOS__
+__inline const char* prev_delimiter(const char *pos, const char *start) {
+#else
+FORCEINLINE const char* prev_delimiter(const char *pos, const char *start) {
+#endif
+    while (pos > start && !is_delimiter(*pos))
+        pos--;
+    return pos;
+}
+#ifndef __REACTOS__
+__inline const char* next_non_delimiter(const char *pos, const char *end) {
+#else
+FORCEINLINE const char* next_non_delimiter(const char *pos, const char *end) {
+#endif
+    while (pos < end && is_delimiter(*pos))
+        pos++;
+    return pos;
+}
+#ifndef __REACTOS__
+__inline const char* prev_non_delimiter(const char *pos, const char *start) {
+#else
+FORCEINLINE const char* prev_non_delimiter(const char *pos, const char *start) {
+#endif
+    while (pos > start && is_delimiter(*pos))
+        pos--;
+    return pos;
+}
+
+bool_t next_component(
+    IN const char *path,
+    IN const char *path_end,
+    OUT nfs41_component *component);
+
+bool_t last_component(
+    IN const char *path,
+    IN const char *path_end,
+    OUT nfs41_component *component);
+
+bool_t is_last_component(
+    IN const char *path,
+    IN const char *path_end);
+
+void abs_path_copy(
+    OUT nfs41_abs_path *dst,
+    IN const nfs41_abs_path *src);
+
+void path_fh_init(
+    OUT nfs41_path_fh *file,
+    IN nfs41_abs_path *path);
+
+void fh_copy(
+    OUT nfs41_fh *dst,
+    IN const nfs41_fh *src);
+
+void path_fh_copy(
+    OUT nfs41_path_fh *dst,
+    IN const nfs41_path_fh *src);
+
+#ifndef __REACTOS__
+__inline int valid_handle(HANDLE handle) {
+#else
+FORCEINLINE int valid_handle(HANDLE handle) {
+#endif
+    return handle != INVALID_HANDLE_VALUE && handle != 0;
+}
+
+#endif /* !__NFS41_DAEMON_UTIL_H__ */
diff --git a/reactos/base/services/nfsd/volume.c b/reactos/base/services/nfsd/volume.c
new file mode 100644 (file)
index 0000000..7473677
--- /dev/null
@@ -0,0 +1,176 @@
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "nfs41_ops.h"
+#include "from_kernel.h"
+#include "upcall.h"
+#include "util.h"
+#include "daemon_debug.h"
+
+
+/* windows volume queries want size in 'units', so we have to
+ * convert the nfs space_* attributes from bytes to units */
+#define SECTORS_PER_UNIT    8
+#define BYTES_PER_SECTOR    512
+#define BYTES_PER_UNIT      (SECTORS_PER_UNIT * BYTES_PER_SECTOR)
+
+#define TO_UNITS(bytes) (bytes / BYTES_PER_UNIT)
+
+#define VOLUME_CACHE_EXPIRATION 20
+
+
+/* NFS41_VOLUME_QUERY */
+static int parse_volume(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
+{
+    int status;
+    volume_upcall_args *args = &upcall->args.volume;
+
+    status = safe_read(&buffer, &length, &args->query, sizeof(FS_INFORMATION_CLASS));
+    if (status) goto out;
+
+    dprintf(1, "parsing NFS41_VOLUME_QUERY: query=%d\n", args->query);
+out:
+    return status;
+}
+
+static int get_volume_size_info(
+    IN nfs41_open_state *state,
+    IN const char *query,
+    OUT OPTIONAL PLONGLONG total_out,
+    OUT OPTIONAL PLONGLONG user_out,
+    OUT OPTIONAL PLONGLONG avail_out)
+{
+    nfs41_file_info info = { 0 };
+    nfs41_superblock *superblock = state->file.fh.superblock;
+    int status = ERROR_NOT_FOUND;
+
+    AcquireSRWLockShared(&superblock->lock);
+    /* check superblock for cached attributes */
+    if (time(NULL) <= superblock->cache_expiration) {
+        info.space_total = superblock->space_total;
+        info.space_avail = superblock->space_avail;
+        info.space_free = superblock->space_free;
+        status = NO_ERROR;
+
+        dprintf(2, "%s cached: %llu user, %llu free of %llu total\n",
+            query, info.space_avail, info.space_free, info.space_total);
+    }
+    ReleaseSRWLockShared(&superblock->lock);
+
+    if (status) {
+        bitmap4 attr_request = { 2, { 0, FATTR4_WORD1_SPACE_AVAIL |
+            FATTR4_WORD1_SPACE_FREE | FATTR4_WORD1_SPACE_TOTAL } };
+
+        /* query the space_ attributes of the filesystem */
+        status = nfs41_getattr(state->session, &state->file,
+            &attr_request, &info);
+        if (status) {
+            eprintf("nfs41_getattr() failed with %s\n",
+                nfs_error_string(status));
+            status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
+            goto out;
+        }
+
+        AcquireSRWLockExclusive(&superblock->lock);
+        superblock->space_total = info.space_total;
+        superblock->space_avail = info.space_avail;
+        superblock->space_free = info.space_free;
+        superblock->cache_expiration = time(NULL) + VOLUME_CACHE_EXPIRATION;
+        ReleaseSRWLockExclusive(&superblock->lock);
+
+        dprintf(2, "%s: %llu user, %llu free of %llu total\n",
+            query, info.space_avail, info.space_free, info.space_total);
+    }
+
+    if (total_out) *total_out = TO_UNITS(info.space_total);
+    if (user_out) *user_out = TO_UNITS(info.space_avail);
+    if (avail_out) *avail_out = TO_UNITS(info.space_free);
+out:
+    return status;
+}
+
+static int handle_volume(nfs41_upcall *upcall)
+{
+    volume_upcall_args *args = &upcall->args.volume;
+    int status = NO_ERROR;
+
+    switch (args->query) {
+    case FileFsSizeInformation:
+        args->len = sizeof(args->info.size);
+        args->info.size.SectorsPerAllocationUnit = SECTORS_PER_UNIT;
+        args->info.size.BytesPerSector = BYTES_PER_SECTOR;
+
+        status = get_volume_size_info(upcall->state_ref,
+            "FileFsSizeInformation",
+            &args->info.size.TotalAllocationUnits.QuadPart,
+            &args->info.size.AvailableAllocationUnits.QuadPart,
+            NULL);
+        break;
+
+    case FileFsFullSizeInformation:
+        args->len = sizeof(args->info.fullsize);
+        args->info.fullsize.SectorsPerAllocationUnit = SECTORS_PER_UNIT;
+        args->info.fullsize.BytesPerSector = BYTES_PER_SECTOR;
+
+        status = get_volume_size_info(upcall->state_ref,
+            "FileFsFullSizeInformation",
+            &args->info.fullsize.TotalAllocationUnits.QuadPart,
+            &args->info.fullsize.CallerAvailableAllocationUnits.QuadPart,
+            &args->info.fullsize.ActualAvailableAllocationUnits.QuadPart);
+        break;
+
+    case FileFsAttributeInformation:
+        args->len = sizeof(args->info.attribute);
+        nfs41_superblock_fs_attributes(upcall->state_ref->file.fh.superblock,
+            &args->info.attribute);
+        break;
+
+    default:
+        eprintf("unhandled fs query class %d\n", args->query);
+        status = ERROR_INVALID_PARAMETER;
+        break;
+    }
+    return status;
+}
+
+static int marshall_volume(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
+{
+    int status;
+    volume_upcall_args *args = &upcall->args.volume;
+
+    status = safe_write(&buffer, length, &args->len, sizeof(args->len));
+    if (status) goto out;
+    status = safe_write(&buffer, length, &args->info, args->len);
+out:
+    return status;
+}
+
+
+const nfs41_upcall_op nfs41_op_volume = {
+    parse_volume,
+    handle_volume,
+    marshall_volume
+};