[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(nfsd)
 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 <