/* 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
 */

#define MINIRDR__NAME "Value is ignored, only fact of definition"
#include <rx.h>
#include <windef.h>
#include <winerror.h>

#include <Ntstrsafe.h>

#include "nfs41_driver.h"
#include "nfs41_np.h"
#include "nfs41_debug.h"

#define USE_MOUNT_SEC_CONTEXT

/* debugging printout defines */
#define DEBUG_MARSHAL_HEADER
#define DEBUG_MARSHAL_DETAIL
//#define DEBUG_OPEN
//#define DEBUG_CLOSE
//#define DEBUG_CACHE
#define DEBUG_INVALIDATE_CACHE
//#define DEBUG_READ
//#define DEBUG_WRITE
//#define DEBUG_DIR_QUERY
//#define DEBUG_FILE_QUERY
//#define DEBUG_FILE_SET
//#define DEBUG_ACL_QUERY
//#define DEBUG_ACL_SET
//#define DEBUG_EA_QUERY
//#define DEBUG_EA_SET
//#define DEBUG_LOCK
//#define DEBUG_MISC
#define DEBUG_TIME_BASED_COHERENCY
//#define DEBUG_MOUNT
//#define DEBUG_VOLUME_QUERY

//#define ENABLE_TIMINGS
//#define ENABLE_INDV_TIMINGS
#ifdef ENABLE_TIMINGS
typedef struct __nfs41_timings {
    LONG tops, sops;
    LONGLONG ticks, size;
} nfs41_timings;

nfs41_timings lookup, readdir, open, close, getattr, setattr, getacl, setacl, volume,
    read, write, lock, unlock, setexattr, getexattr;
#endif
DRIVER_INITIALIZE DriverEntry;
DRIVER_UNLOAD nfs41_driver_unload;
DRIVER_DISPATCH ( nfs41_FsdDispatch );

struct _MINIRDR_DISPATCH nfs41_ops;
PRDBSS_DEVICE_OBJECT nfs41_dev;

#define DISABLE_CACHING 0
#define ENABLE_READ_CACHING 1
#define ENABLE_WRITE_CACHING 2
#define ENABLE_READWRITE_CACHING 3

#define NFS41_MM_POOLTAG        ('nfs4')
#define NFS41_MM_POOLTAG_ACL    ('acls')
#define NFS41_MM_POOLTAG_MOUNT  ('mnts')
#define NFS41_MM_POOLTAG_OPEN   ('open')
#define NFS41_MM_POOLTAG_UP     ('upca')
#define NFS41_MM_POOLTAG_DOWN   ('down')

KEVENT upcallEvent;
FAST_MUTEX upcallLock, downcallLock, fcblistLock;
FAST_MUTEX xidLock;
FAST_MUTEX openOwnerLock;

LONGLONG xid = 0;
LONG open_owner_id = 1;

#define DECLARE_CONST_ANSI_STRING(_var, _string) \
    const CHAR _var ## _buffer[] = _string; \
    const ANSI_STRING _var = { sizeof(_string) - sizeof(CHAR), \
        sizeof(_string), (PCH) _var ## _buffer }
#define RELATIVE(wait) (-(wait))
#define NANOSECONDS(nanos) (((signed __int64)(nanos)) / 100L)
#define MICROSECONDS(micros) (((signed __int64)(micros)) * NANOSECONDS(1000L))
#define MILLISECONDS(milli) (((signed __int64)(milli)) * MICROSECONDS(1000L))
#define SECONDS(seconds) (((signed __int64)(seconds)) * MILLISECONDS(1000L))

DECLARE_CONST_ANSI_STRING(NfsV3Attributes, "NfsV3Attributes");
DECLARE_CONST_ANSI_STRING(NfsSymlinkTargetName, "NfsSymlinkTargetName");
DECLARE_CONST_ANSI_STRING(NfsActOnLink, "NfsActOnLink");

INLINE BOOL AnsiStrEq(
    IN const ANSI_STRING *lhs,
    IN const CHAR *rhs,
    IN const UCHAR rhs_len)
{
    return lhs->Length == rhs_len &&
        RtlCompareMemory(lhs->Buffer, rhs, rhs_len) == rhs_len;
}

typedef struct _nfs3_attrs {
    DWORD type, mode, nlink, uid, gid, filler1;
    LARGE_INTEGER size, used;
    struct {
        DWORD specdata1;
        DWORD specdata2;
    } rdev;
    LONGLONG fsid, fileid;
    LONGLONG atime, mtime, ctime;
} nfs3_attrs;
LARGE_INTEGER unix_time_diff; //needed to convert windows time to unix

enum ftype3 {
    NF3REG = 1,
    NF3DIR,
    NF3BLK,
    NF3CHR,
    NF3LNK,
    NF3SOCK,
    NF3FIFO
};

typedef enum _nfs41_updowncall_state {
    NFS41_WAITING_FOR_UPCALL,
    NFS41_WAITING_FOR_DOWNCALL,
    NFS41_DONE_PROCESSING,
    NFS41_NOT_WAITING
} nfs41_updowncall_state;

typedef struct _updowncall_entry {
    DWORD version;
    LONGLONG xid;
    DWORD opcode;
    NTSTATUS status;
    nfs41_updowncall_state state;
    FAST_MUTEX lock;
    LIST_ENTRY next;
    KEVENT cond;
    DWORD errno;
    BOOLEAN async_op;
    SECURITY_CLIENT_CONTEXT sec_ctx;
    PSECURITY_CLIENT_CONTEXT psec_ctx;
    HANDLE open_state;
    HANDLE session;
    PUNICODE_STRING filename;
    PVOID buf;
    ULONG buf_len;
    ULONGLONG ChangeTime;
    union {
        struct {
            PUNICODE_STRING srv_name;
            PUNICODE_STRING root;
            PFILE_FS_ATTRIBUTE_INFORMATION FsAttrs;
            DWORD sec_flavor;
            DWORD rsize;
            DWORD wsize;
            DWORD lease_time;
        } Mount;
        struct {                       
            PMDL MdlAddress;
            ULONGLONG offset;
            PRX_CONTEXT rxcontext;
        } ReadWrite;
        struct {
            LONGLONG offset;
            LONGLONG length;
            BOOLEAN exclusive;
            BOOLEAN blocking;
        } Lock;
        struct {
            ULONG count;
            LOWIO_LOCK_LIST locks;
        } Unlock;
        struct {
            FILE_BASIC_INFORMATION binfo;
            FILE_STANDARD_INFORMATION sinfo;
            UNICODE_STRING symlink;
            ULONG access_mask;
            ULONG access_mode;
            ULONG attrs;
            ULONG copts;
            ULONG disp;
            ULONG cattrs;
            LONG open_owner_id;
            DWORD mode;
            HANDLE srv_open;
            DWORD deleg_type;
            BOOLEAN symlink_embedded;
            PMDL EaMdl;
            PVOID EaBuffer;
        } Open;
        struct {
            HANDLE srv_open;
            BOOLEAN remove;
            BOOLEAN renamed;
        } Close;
        struct {
            PUNICODE_STRING filter;
            FILE_INFORMATION_CLASS InfoClass;
            BOOLEAN restart_scan;
            BOOLEAN return_single;
            BOOLEAN initial_query;
            PMDL mdl;
            PVOID mdl_buf;
        } QueryFile;
        struct {
            FILE_INFORMATION_CLASS InfoClass;
        } SetFile;
        struct {
            DWORD mode;
        } SetEa;
        struct {
            PVOID EaList;
            ULONG EaListLength;
            ULONG Overflow;
            ULONG EaIndex;
            BOOLEAN ReturnSingleEntry;
            BOOLEAN RestartScan;
        } QueryEa;
        struct {
            PUNICODE_STRING target;
            BOOLEAN set;
        } Symlink;
        struct {
            FS_INFORMATION_CLASS query;
        } Volume;
        struct {
            SECURITY_INFORMATION query;
        } Acl;
    } u;

} nfs41_updowncall_entry;

typedef struct _updowncall_list {
    LIST_ENTRY head;
} nfs41_updowncall_list;
nfs41_updowncall_list upcall, downcall;

typedef struct _nfs41_mount_entry {
    LIST_ENTRY next;
    LUID login_id;
    HANDLE authsys_session;
    HANDLE gss_session;
    HANDLE gssi_session;
    HANDLE gssp_session;
} nfs41_mount_entry;

typedef struct _nfs41_mount_list {
    LIST_ENTRY head;
} nfs41_mount_list;

#define nfs41_AddEntry(lock,list,pEntry)                    \
            ExAcquireFastMutex(&lock);                      \
            InsertTailList(&(list).head, &(pEntry)->next);  \
            ExReleaseFastMutex(&lock);
#define nfs41_RemoveFirst(lock,list,pEntry)                 \
            ExAcquireFastMutex(&lock);                      \
            pEntry = (IsListEmpty(&(list).head)             \
            ? NULL                                          \
            : RemoveHeadList(&(list).head));                \
            ExReleaseFastMutex(&lock);
#define nfs41_RemoveEntry(lock,pEntry)                      \
            ExAcquireFastMutex(&lock);                      \
            RemoveEntryList(&pEntry->next);                 \
            ExReleaseFastMutex(&lock);
#define nfs41_IsListEmpty(lock,list,flag)                   \
            ExAcquireFastMutex(&lock);                      \
            flag = IsListEmpty(&(list).head);               \
            ExReleaseFastMutex(&lock);
#define nfs41_GetFirstEntry(lock,list,pEntry)               \
            ExAcquireFastMutex(&lock);                      \
            pEntry = (IsListEmpty(&(list).head)             \
             ? NULL                                         \
             : (nfs41_updowncall_entry *)                   \
               (CONTAINING_RECORD((list).head.Flink,        \
                                  nfs41_updowncall_entry,   \
                                  next)));                  \
            ExReleaseFastMutex(&lock);
#define nfs41_GetFirstMountEntry(lock,list,pEntry)          \
            ExAcquireFastMutex(&lock);                      \
            pEntry = (IsListEmpty(&(list).head)             \
             ? NULL                                         \
             : (nfs41_mount_entry *)                        \
               (CONTAINING_RECORD((list).head.Flink,        \
                                  nfs41_mount_entry,        \
                                  next)));                  \
            ExReleaseFastMutex(&lock);

/* In order to cooperate with other network providers,
 * we only claim paths of the format '\\server\nfs4\path' */
DECLARE_CONST_UNICODE_STRING(NfsPrefix, L"\\nfs4");
DECLARE_CONST_UNICODE_STRING(AUTH_SYS_NAME, L"sys");
DECLARE_CONST_UNICODE_STRING(AUTHGSS_KRB5_NAME, L"krb5");
DECLARE_CONST_UNICODE_STRING(AUTHGSS_KRB5I_NAME, L"krb5i");
DECLARE_CONST_UNICODE_STRING(AUTHGSS_KRB5P_NAME, L"krb5p");
DECLARE_CONST_UNICODE_STRING(SLASH, L"\\");
DECLARE_CONST_UNICODE_STRING(EMPTY_STRING, L"");

#define SERVER_NAME_BUFFER_SIZE         1024
#define MOUNT_CONFIG_RW_SIZE_MIN        1024
#define MOUNT_CONFIG_RW_SIZE_DEFAULT    1048576
#define MOUNT_CONFIG_RW_SIZE_MAX        1048576
#define MAX_SEC_FLAVOR_LEN              12
#define UPCALL_TIMEOUT_DEFAULT          50  /* in seconds */

typedef struct _NFS41_MOUNT_CONFIG {
    DWORD ReadSize;
    DWORD WriteSize;
    BOOLEAN ReadOnly;
    BOOLEAN write_thru;
    BOOLEAN nocache;
    WCHAR srv_buffer[SERVER_NAME_BUFFER_SIZE];
    UNICODE_STRING SrvName;
    WCHAR mntpt_buffer[MAX_PATH];
    UNICODE_STRING MntPt;
    WCHAR sec_flavor[MAX_SEC_FLAVOR_LEN];
    UNICODE_STRING SecFlavor;
    DWORD timeout;
} NFS41_MOUNT_CONFIG, *PNFS41_MOUNT_CONFIG;

typedef struct _NFS41_NETROOT_EXTENSION {
    NODE_TYPE_CODE          NodeTypeCode;
    NODE_BYTE_SIZE          NodeByteSize;
    DWORD                   nfs41d_version;
    BOOLEAN                 mounts_init;
    FAST_MUTEX              mountLock;
    nfs41_mount_list        mounts;
} NFS41_NETROOT_EXTENSION, *PNFS41_NETROOT_EXTENSION;
#define NFS41GetNetRootExtension(pNetRoot)      \
        (((pNetRoot) == NULL) ? NULL :          \
        (PNFS41_NETROOT_EXTENSION)((pNetRoot)->Context))

/* FileSystemName as reported by FileFsAttributeInfo query */
#define FS_NAME     L"NFS"
#define FS_NAME_LEN (sizeof(FS_NAME) - sizeof(WCHAR))
#define FS_ATTR_LEN (sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + FS_NAME_LEN)

/* FileSystemName as reported by FileFsAttributeInfo query */
#define VOL_NAME     L"PnfsVolume"
#define VOL_NAME_LEN (sizeof(VOL_NAME) - sizeof(WCHAR))
#define VOL_ATTR_LEN (sizeof(FILE_FS_VOLUME_INFORMATION) + VOL_NAME_LEN)

typedef struct _NFS41_V_NET_ROOT_EXTENSION {
    NODE_TYPE_CODE          NodeTypeCode;
    NODE_BYTE_SIZE          NodeByteSize;
    HANDLE                  session;
    FILE_FS_ATTRIBUTE_INFORMATION FsAttrs;
    DWORD                   sec_flavor;
    DWORD                   timeout;
    USHORT                  MountPathLen;
    BOOLEAN                 read_only;
    BOOLEAN                 write_thru;
    BOOLEAN                 nocache;
#define STORE_MOUNT_SEC_CONTEXT
#ifdef STORE_MOUNT_SEC_CONTEXT
    SECURITY_CLIENT_CONTEXT mount_sec_ctx;
#endif
} NFS41_V_NET_ROOT_EXTENSION, *PNFS41_V_NET_ROOT_EXTENSION;
#define NFS41GetVNetRootExtension(pVNetRoot)      \
        (((pVNetRoot) == NULL) ? NULL :           \
        (PNFS41_V_NET_ROOT_EXTENSION)((pVNetRoot)->Context))

typedef struct _NFS41_FCB {
    NODE_TYPE_CODE          NodeTypeCode;
    NODE_BYTE_SIZE          NodeByteSize;
    FILE_BASIC_INFORMATION  BasicInfo;
    FILE_STANDARD_INFORMATION StandardInfo;
    BOOLEAN                 Renamed;
    BOOLEAN                 DeletePending;
    DWORD                   mode;
    ULONGLONG               changeattr;    
} NFS41_FCB, *PNFS41_FCB;
#define NFS41GetFcbExtension(pFcb)      \
        (((pFcb) == NULL) ? NULL : (PNFS41_FCB)((pFcb)->Context))

typedef struct _NFS41_FOBX {
    NODE_TYPE_CODE          NodeTypeCode;
    NODE_BYTE_SIZE          NodeByteSize;

    HANDLE nfs41_open_state;
    SECURITY_CLIENT_CONTEXT sec_ctx;
    PVOID acl;
    DWORD acl_len;
    LARGE_INTEGER time; 
    DWORD deleg_type;
    BOOLEAN write_thru;
    BOOLEAN nocache;
} NFS41_FOBX, *PNFS41_FOBX;
#define NFS41GetFobxExtension(pFobx)  \
        (((pFobx) == NULL) ? NULL : (PNFS41_FOBX)((pFobx)->Context))

typedef struct _NFS41_SERVER_ENTRY {
    PMRX_SRV_CALL                 pRdbssSrvCall;
    WCHAR                         NameBuffer[SERVER_NAME_BUFFER_SIZE];
    UNICODE_STRING                Name;             // the server name.
} NFS41_SERVER_ENTRY, *PNFS41_SERVER_ENTRY;

typedef struct _NFS41_DEVICE_EXTENSION {
    NODE_TYPE_CODE          NodeTypeCode;
    NODE_BYTE_SIZE          NodeByteSize;
    PRDBSS_DEVICE_OBJECT    DeviceObject;
    ULONG                   ActiveNodes;
    HANDLE                  SharedMemorySection;
    DWORD                   nfs41d_version;
    BYTE                    VolAttrs[VOL_ATTR_LEN];
    DWORD                   VolAttrsLen;
    HANDLE                  openlistHandle;
} NFS41_DEVICE_EXTENSION, *PNFS41_DEVICE_EXTENSION;

#define NFS41GetDeviceExtension(RxContext,pExt)        \
        PNFS41_DEVICE_EXTENSION pExt = (PNFS41_DEVICE_EXTENSION) \
        ((PBYTE)(RxContext->RxDeviceObject) + sizeof(RDBSS_DEVICE_OBJECT))

typedef struct _nfs41_fcb_list_entry {
    LIST_ENTRY next;
    PMRX_FCB fcb;
    HANDLE session;
    PNFS41_FOBX nfs41_fobx;
    ULONGLONG ChangeTime;
    BOOLEAN skip;
} nfs41_fcb_list_entry;

typedef struct _nfs41_fcb_list {
    LIST_ENTRY head;
} nfs41_fcb_list;
nfs41_fcb_list openlist;

typedef enum _NULMRX_STORAGE_TYPE_CODES {
    NTC_NFS41_DEVICE_EXTENSION      =   (NODE_TYPE_CODE)0xFC00,    
} NFS41_STORAGE_TYPE_CODES;
#define RxDefineNode( node, type )          \
        node->NodeTypeCode = NTC_##type;    \
        node->NodeByteSize = sizeof(type);

#define RDR_NULL_STATE  0
#define RDR_UNLOADED    1
#define RDR_UNLOADING   2
#define RDR_LOADING     3
#define RDR_LOADED      4
#define RDR_STOPPED     5
#define RDR_STOPPING    6
#define RDR_STARTING    7
#define RDR_STARTED     8

nfs41_init_driver_state nfs41_init_state = NFS41_INIT_DRIVER_STARTABLE;
nfs41_start_driver_state nfs41_start_state = NFS41_START_DRIVER_STARTABLE;

NTSTATUS map_readwrite_errors(DWORD status);

void print_debug_header(
    PRX_CONTEXT RxContext)
{

    PIO_STACK_LOCATION IrpSp = RxContext->CurrentIrpSp;

    if (IrpSp) {
        DbgP("FileOject %p name %wZ access r=%d,w=%d,d=%d share r=%d,w=%d,d=%d\n", 
            IrpSp->FileObject, &IrpSp->FileObject->FileName, 
            IrpSp->FileObject->ReadAccess, IrpSp->FileObject->WriteAccess,
            IrpSp->FileObject->DeleteAccess, IrpSp->FileObject->SharedRead,
            IrpSp->FileObject->SharedWrite, IrpSp->FileObject->SharedDelete);
        print_file_object(0, IrpSp->FileObject);
        print_irps_flags(0, RxContext->CurrentIrpSp);
    } else
        DbgP("Couldn't print FileObject IrpSp is NULL\n");

    print_fo_all(1, RxContext);
    if (RxContext->CurrentIrp)
        print_irp_flags(0, RxContext->CurrentIrp);
}

/* convert strings from unicode -> ansi during marshalling to
 * save space in the upcall buffers and avoid extra copies */
INLINE ULONG length_as_utf8(
    PCUNICODE_STRING str)
{
    ULONG ActualCount = 0;
    RtlUnicodeToUTF8N(NULL, 0xffff, &ActualCount, str->Buffer, str->Length);
    return sizeof(str->MaximumLength) + ActualCount + sizeof(UNICODE_NULL);
}

NTSTATUS marshall_unicode_as_utf8(
    IN OUT unsigned char **pos,
    IN PCUNICODE_STRING str)
{
    ANSI_STRING ansi;
    ULONG ActualCount;
    NTSTATUS status;

    if (str->Length == 0) {
        status = STATUS_SUCCESS;
        ActualCount = 0;
        ansi.MaximumLength = 1;
        goto out_copy;
    }

    /* query the number of bytes required for the utf8 encoding */
    status = RtlUnicodeToUTF8N(NULL, 0xffff,
        &ActualCount, str->Buffer, str->Length);
    if (status) {
        print_error("RtlUnicodeToUTF8N('%wZ') failed with 0x%08X\n",
            str, status);
        goto out;
    }

    /* convert the string directly into the upcall buffer */
    ansi.Buffer = (PCHAR)*pos + sizeof(ansi.MaximumLength);
    ansi.MaximumLength = (USHORT)ActualCount + sizeof(UNICODE_NULL);
    status = RtlUnicodeToUTF8N(ansi.Buffer, ansi.MaximumLength,
        &ActualCount, str->Buffer, str->Length);
    if (status) {
        print_error("RtlUnicodeToUTF8N(%hu, '%wZ', %hu) failed with 0x%08X\n",
            ansi.MaximumLength, str, str->Length, status);
        goto out;
    }

out_copy:
    RtlCopyMemory(*pos, &ansi.MaximumLength, sizeof(ansi.MaximumLength));
    *pos += sizeof(ansi.MaximumLength);
    (*pos)[ActualCount] = '\0';
    *pos += ansi.MaximumLength;
out:
    return status;
}

NTSTATUS marshal_nfs41_header(
    nfs41_updowncall_entry *entry,
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    header_len = sizeof(entry->version) + sizeof(entry->xid) + 
        sizeof(entry->opcode) + 2 * sizeof(HANDLE);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    else
        *len = header_len;
    RtlCopyMemory(tmp, &entry->version, sizeof(entry->version));
    tmp += sizeof(entry->version);
    RtlCopyMemory(tmp, &entry->xid, sizeof(entry->xid));
    tmp += sizeof(entry->xid);
    RtlCopyMemory(tmp, &entry->opcode, sizeof(entry->opcode));
    tmp += sizeof(entry->opcode);
    RtlCopyMemory(tmp, &entry->session, sizeof(HANDLE));
    tmp += sizeof(HANDLE);
    RtlCopyMemory(tmp, &entry->open_state, sizeof(HANDLE));
    tmp += sizeof(HANDLE);

#ifdef DEBUG_MARSHAL_HEADER
    if (MmIsAddressValid(entry->filename))
        DbgP("[upcall header] xid=%lld opcode=%s filename=%wZ version=%d "
            "session=0x%x open_state=0x%x\n", entry->xid, 
            opcode2string(entry->opcode), entry->filename,
            entry->version, entry->session, entry->open_state);
    else
        status = STATUS_INTERNAL_ERROR;
#endif
out:
    return status;
}

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";
}
NTSTATUS marshal_nfs41_mount(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    /* 03/25/2011: Kernel crash to nfsd not running but mount upcall cued up */
    if (!MmIsAddressValid(entry->u.Mount.srv_name) || 
            !MmIsAddressValid(entry->u.Mount.root)) {
        status = STATUS_INTERNAL_ERROR;
        goto out;
    }
    header_len = *len + length_as_utf8(entry->u.Mount.srv_name) +
        length_as_utf8(entry->u.Mount.root) + 3 * sizeof(DWORD);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    status = marshall_unicode_as_utf8(&tmp, entry->u.Mount.srv_name);
    if (status) goto out;
    status = marshall_unicode_as_utf8(&tmp, entry->u.Mount.root);
    if (status) goto out;
    RtlCopyMemory(tmp, &entry->u.Mount.sec_flavor, sizeof(DWORD));
    tmp += sizeof(DWORD);
    RtlCopyMemory(tmp, &entry->u.Mount.rsize, sizeof(DWORD));
    tmp += sizeof(DWORD);
    RtlCopyMemory(tmp, &entry->u.Mount.wsize, sizeof(DWORD));

    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_mount: server name=%wZ mount point=%wZ sec_flavor=%s "
         "rsize=%d wsize=%d\n", entry->u.Mount.srv_name, entry->u.Mount.root, 
         secflavorop2name(entry->u.Mount.sec_flavor), entry->u.Mount.rsize,
         entry->u.Mount.wsize);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_unmount(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len) 
{
    return marshal_nfs41_header(entry, buf, buf_len, len);
}

NTSTATUS marshal_nfs41_open(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + length_as_utf8(entry->filename) +
        7 * sizeof(ULONG) + 2 * sizeof(HANDLE) +
        length_as_utf8(&entry->u.Open.symlink);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    status = marshall_unicode_as_utf8(&tmp, entry->filename);
    if (status) goto out;
    RtlCopyMemory(tmp, &entry->u.Open.access_mask, 
        sizeof(entry->u.Open.access_mask));
    tmp += sizeof(entry->u.Open.access_mask);
    RtlCopyMemory(tmp, &entry->u.Open.access_mode, 
        sizeof(entry->u.Open.access_mode));
    tmp += sizeof(entry->u.Open.access_mode);
    RtlCopyMemory(tmp, &entry->u.Open.attrs, sizeof(entry->u.Open.attrs));
    tmp += sizeof(entry->u.Open.attrs);
    RtlCopyMemory(tmp, &entry->u.Open.copts, sizeof(entry->u.Open.copts));
    tmp += sizeof(entry->u.Open.copts);
    RtlCopyMemory(tmp, &entry->u.Open.disp, sizeof(entry->u.Open.disp));
    tmp += sizeof(entry->u.Open.disp);
    RtlCopyMemory(tmp, &entry->u.Open.open_owner_id,
        sizeof(entry->u.Open.open_owner_id));
    tmp += sizeof(entry->u.Open.open_owner_id);
    RtlCopyMemory(tmp, &entry->u.Open.mode, sizeof(DWORD));
    tmp += sizeof(DWORD);
    RtlCopyMemory(tmp, &entry->u.Open.srv_open, sizeof(HANDLE));
    tmp += sizeof(HANDLE);
    status = marshall_unicode_as_utf8(&tmp, &entry->u.Open.symlink);
    if (status) goto out;

    __try {
        if (entry->u.Open.EaMdl) {
            entry->u.Open.EaBuffer =
                MmMapLockedPagesSpecifyCache(entry->u.Open.EaMdl,
                    UserMode, MmNonCached, NULL, TRUE, NormalPagePriority);
            if (entry->u.Open.EaBuffer == NULL) {
                print_error("MmMapLockedPagesSpecifyCache failed to map pages\n");
                status = STATUS_INSUFFICIENT_RESOURCES;
                goto out;
            }
        }
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        print_error("Call to MmMapLocked failed due to exception 0x%x\n", GetExceptionCode());
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
    RtlCopyMemory(tmp, &entry->u.Open.EaBuffer, sizeof(HANDLE));
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_open: name=%wZ mask=0x%x access=0x%x attrs=0x%x "
         "opts=0x%x dispo=0x%x open_owner_id=0x%x mode=%o srv_open=%p ea=%p\n",
         entry->filename, entry->u.Open.access_mask,
         entry->u.Open.access_mode, entry->u.Open.attrs, entry->u.Open.copts,
         entry->u.Open.disp, entry->u.Open.open_owner_id, entry->u.Open.mode,
         entry->u.Open.srv_open, entry->u.Open.EaBuffer);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_rw(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len,
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + sizeof(entry->buf_len) +
        sizeof(entry->u.ReadWrite.offset) + sizeof(HANDLE);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    RtlCopyMemory(tmp, &entry->buf_len, sizeof(entry->buf_len));
    tmp += sizeof(entry->buf_len);
    RtlCopyMemory(tmp, &entry->u.ReadWrite.offset, 
        sizeof(entry->u.ReadWrite.offset));
    tmp += sizeof(entry->u.ReadWrite.offset);
    __try {
        entry->u.ReadWrite.MdlAddress->MdlFlags |= MDL_MAPPING_CAN_FAIL;
        entry->buf = 
            MmMapLockedPagesSpecifyCache(entry->u.ReadWrite.MdlAddress, 
                UserMode, MmNonCached, NULL, TRUE, NormalPagePriority);
        if (entry->buf == NULL) {
            print_error("MmMapLockedPagesSpecifyCache failed to map pages\n");
            status = STATUS_INSUFFICIENT_RESOURCES;
            goto out;
        }
    } __except(EXCEPTION_EXECUTE_HANDLER) { 
        NTSTATUS code; 
        code = GetExceptionCode(); 
        print_error("Call to MmMapLocked failed due to exception 0x%x\n", code);
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
    RtlCopyMemory(tmp, &entry->buf, sizeof(HANDLE));
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_rw: len=%lu offset=%llu MdlAddress=%p Userspace=%p\n", 
         entry->buf_len, entry->u.ReadWrite.offset, 
         entry->u.ReadWrite.MdlAddress, entry->buf);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_lock(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + 2 * sizeof(LONGLONG) + 2 * sizeof(BOOLEAN);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    RtlCopyMemory(tmp, &entry->u.Lock.offset, sizeof(LONGLONG));
    tmp += sizeof(LONGLONG);
    RtlCopyMemory(tmp, &entry->u.Lock.length, sizeof(LONGLONG));
    tmp += sizeof(LONGLONG);
    RtlCopyMemory(tmp, &entry->u.Lock.exclusive, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    RtlCopyMemory(tmp, &entry->u.Lock.blocking, sizeof(BOOLEAN));
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_lock: offset=%llx length=%llx exclusive=%u "
         "blocking=%u\n", entry->u.Lock.offset, entry->u.Lock.length,
         entry->u.Lock.exclusive, entry->u.Lock.blocking);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_unlock(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;
    PLOWIO_LOCK_LIST lock;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + sizeof(ULONG) + 
        entry->u.Unlock.count * 2 * sizeof(LONGLONG);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    RtlCopyMemory(tmp, &entry->u.Unlock.count, sizeof(ULONG));
    tmp += sizeof(ULONG);

    lock = &entry->u.Unlock.locks;
    while (lock) {
        RtlCopyMemory(tmp, &lock->ByteOffset, sizeof(LONGLONG));
        tmp += sizeof(LONGLONG);
        RtlCopyMemory(tmp, &lock->Length, sizeof(LONGLONG));
        tmp += sizeof(LONGLONG);
        lock = lock->Next;
    }
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_unlock: count=%u\n", entry->u.Unlock.count);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_close(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + sizeof(BOOLEAN) + sizeof(HANDLE);
    if (entry->u.Close.remove)
        header_len += length_as_utf8(entry->filename) +
            sizeof(BOOLEAN);

    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    RtlCopyMemory(tmp, &entry->u.Close.remove, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    RtlCopyMemory(tmp, &entry->u.Close.srv_open, sizeof(HANDLE));
    if (entry->u.Close.remove) {
        tmp += sizeof(HANDLE);
        status = marshall_unicode_as_utf8(&tmp, entry->filename);
        if (status) goto out;
        RtlCopyMemory(tmp, &entry->u.Close.renamed, sizeof(BOOLEAN));
    }
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_close: name=%wZ remove=%d srv_open=%p renamed=%d\n", 
        entry->filename->Length?entry->filename:&SLASH, 
        entry->u.Close.remove, entry->u.Close.srv_open, entry->u.Close.renamed);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_dirquery(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + 2 * sizeof(ULONG) + sizeof(HANDLE) +
        length_as_utf8(entry->u.QueryFile.filter) + 3 * sizeof(BOOLEAN);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    RtlCopyMemory(tmp, &entry->u.QueryFile.InfoClass, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, &entry->buf_len, sizeof(ULONG));
    tmp += sizeof(ULONG);
    status = marshall_unicode_as_utf8(&tmp, entry->u.QueryFile.filter);
    if (status) goto out;
    RtlCopyMemory(tmp, &entry->u.QueryFile.initial_query, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    RtlCopyMemory(tmp, &entry->u.QueryFile.restart_scan, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    RtlCopyMemory(tmp, &entry->u.QueryFile.return_single, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    __try {
        entry->u.QueryFile.mdl_buf = 
            MmMapLockedPagesSpecifyCache(entry->u.QueryFile.mdl, 
                UserMode, MmNonCached, NULL, TRUE, NormalPagePriority);
        if (entry->u.QueryFile.mdl_buf == NULL) {
            print_error("MmMapLockedPagesSpecifyCache failed to map pages\n");
            status = STATUS_INSUFFICIENT_RESOURCES;
            goto out;
        }
    } __except(EXCEPTION_EXECUTE_HANDLER) { 
        NTSTATUS code; 
        code = GetExceptionCode(); 
        print_error("Call to MmMapLocked failed due to exception 0x%x\n", code);
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
    RtlCopyMemory(tmp, &entry->u.QueryFile.mdl_buf, sizeof(HANDLE));
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_dirquery: filter='%wZ'class=%d len=%d "
         "1st\\restart\\single=%d\\%d\\%d\n", entry->u.QueryFile.filter, 
         entry->u.QueryFile.InfoClass, entry->buf_len, 
         entry->u.QueryFile.initial_query, entry->u.QueryFile.restart_scan, 
         entry->u.QueryFile.return_single);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_filequery(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + 2 * sizeof(ULONG);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    RtlCopyMemory(tmp, &entry->u.QueryFile.InfoClass, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, &entry->buf_len, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, &entry->session, sizeof(HANDLE));
    tmp += sizeof(HANDLE);
    RtlCopyMemory(tmp, &entry->open_state, sizeof(HANDLE));
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_filequery: class=%d\n", entry->u.QueryFile.InfoClass);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_fileset(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + length_as_utf8(entry->filename) +
        2 * sizeof(ULONG) + entry->buf_len;
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    status = marshall_unicode_as_utf8(&tmp, entry->filename);
    if (status) goto out;
    RtlCopyMemory(tmp, &entry->u.SetFile.InfoClass, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, &entry->buf_len, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, entry->buf, entry->buf_len);
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_fileset: filename='%wZ' class=%d\n",
        entry->filename, entry->u.SetFile.InfoClass);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_easet(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + length_as_utf8(entry->filename) + 
        sizeof(ULONG) + entry->buf_len  + sizeof(DWORD);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    status = marshall_unicode_as_utf8(&tmp, entry->filename);
    if (status) goto out;
    RtlCopyMemory(tmp, &entry->u.SetEa.mode, sizeof(DWORD));
    tmp += sizeof(DWORD);
    RtlCopyMemory(tmp, &entry->buf_len, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, entry->buf, entry->buf_len);    
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_easet: filename=%wZ, buflen=%d mode=0x%x\n", 
        entry->filename, entry->buf_len, entry->u.SetEa.mode);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_eaget(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + length_as_utf8(entry->filename) + 
        3 * sizeof(ULONG) + entry->u.QueryEa.EaListLength + 2 * sizeof(BOOLEAN);

    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    status = marshall_unicode_as_utf8(&tmp, entry->filename);
    if (status) goto out;
    RtlCopyMemory(tmp, &entry->u.QueryEa.EaIndex, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, &entry->u.QueryEa.RestartScan, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    RtlCopyMemory(tmp, &entry->u.QueryEa.ReturnSingleEntry, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    RtlCopyMemory(tmp, &entry->buf_len, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, &entry->u.QueryEa.EaListLength, sizeof(ULONG));
    tmp += sizeof(ULONG);
    if (entry->u.QueryEa.EaList && entry->u.QueryEa.EaListLength)
        RtlCopyMemory(tmp, entry->u.QueryEa.EaList,
            entry->u.QueryEa.EaListLength);
    *len = header_len; 

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_eaget: filename=%wZ, index=%d list_len=%d "
        "rescan=%d single=%d\n", entry->filename, 
        entry->u.QueryEa.EaIndex, entry->u.QueryEa.EaListLength, 
        entry->u.QueryEa.RestartScan, entry->u.QueryEa.ReturnSingleEntry);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_symlink(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + sizeof(BOOLEAN) + length_as_utf8(entry->filename);
    if (entry->u.Symlink.set)
        header_len += length_as_utf8(entry->u.Symlink.target);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    status = marshall_unicode_as_utf8(&tmp, entry->filename);
    if (status) goto out;
    RtlCopyMemory(tmp, &entry->u.Symlink.set, sizeof(BOOLEAN));
    tmp += sizeof(BOOLEAN);
    if (entry->u.Symlink.set) {
        status = marshall_unicode_as_utf8(&tmp, entry->u.Symlink.target);
        if (status) goto out;
    }
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_symlink: name %wZ symlink target %wZ\n", 
         entry->filename, 
         entry->u.Symlink.set?entry->u.Symlink.target : NULL);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_volume(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + sizeof(FS_INFORMATION_CLASS);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    RtlCopyMemory(tmp, &entry->u.Volume.query, sizeof(FS_INFORMATION_CLASS));
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_volume: class=%d\n", entry->u.Volume.query);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_getacl(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + sizeof(SECURITY_INFORMATION);
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    RtlCopyMemory(tmp, &entry->u.Acl.query, sizeof(SECURITY_INFORMATION));
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_getacl: class=0x%x\n", entry->u.Acl.query);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_setacl(
    nfs41_updowncall_entry *entry,
    unsigned char *buf,
    ULONG buf_len,
    ULONG *len)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG header_len = 0;
    unsigned char *tmp = buf;

    status = marshal_nfs41_header(entry, tmp, buf_len, len);
    if (status) goto out;
    else tmp += *len;

    header_len = *len + sizeof(SECURITY_INFORMATION) +
        sizeof(ULONG) + entry->buf_len;
    if (header_len > buf_len) { 
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    RtlCopyMemory(tmp, &entry->u.Acl.query, sizeof(SECURITY_INFORMATION));
    tmp += sizeof(SECURITY_INFORMATION);
    RtlCopyMemory(tmp, &entry->buf_len, sizeof(ULONG));
    tmp += sizeof(ULONG);
    RtlCopyMemory(tmp, entry->buf, entry->buf_len);
    *len = header_len;

#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("marshal_nfs41_setacl: class=0x%x sec_desc_len=%lu\n", 
         entry->u.Acl.query, entry->buf_len);
#endif
out:
    return status;
}

NTSTATUS marshal_nfs41_shutdown(
    nfs41_updowncall_entry *entry, 
    unsigned char *buf, 
    ULONG buf_len, 
    ULONG *len) 
{
    return marshal_nfs41_header(entry, buf, buf_len, len);
}

void nfs41_invalidate_cache (
    IN PRX_CONTEXT RxContext)
{
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    unsigned char *buf = LowIoContext->ParamsFor.IoCtl.pInputBuffer;
    ULONG flag = DISABLE_CACHING;
    PMRX_SRV_OPEN srv_open;

    RtlCopyMemory(&srv_open, buf, sizeof(HANDLE));
#ifdef DEBUG_INVALIDATE_CACHE
    DbgP("nfs41_invalidate_cache: received srv_open=%p %wZ\n", 
        srv_open, srv_open->pAlreadyPrefixedName);
#endif
    if (MmIsAddressValid(srv_open))
        RxIndicateChangeOfBufferingStateForSrvOpen(
            srv_open->pFcb->pNetRoot->pSrvCall, srv_open,
            srv_open->Key, ULongToPtr(flag));
}

NTSTATUS handle_upcall(
    IN PRX_CONTEXT RxContext,
    IN nfs41_updowncall_entry *entry,
    OUT ULONG *len)
{
    NTSTATUS status = STATUS_SUCCESS;
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    ULONG cbOut = LowIoContext->ParamsFor.IoCtl.OutputBufferLength;
    unsigned char *pbOut = LowIoContext->ParamsFor.IoCtl.pOutputBuffer;

    status = SeImpersonateClientEx(entry->psec_ctx, NULL);
    if (status != STATUS_SUCCESS) {
        print_error("SeImpersonateClientEx failed %x\n", status);
        goto out;
    }

    switch(entry->opcode) {
    case NFS41_SHUTDOWN:
        status = marshal_nfs41_shutdown(entry, pbOut, cbOut, len);
        KeSetEvent(&entry->cond, 0, FALSE);
        break;
    case NFS41_MOUNT:
        status = marshal_nfs41_mount(entry, pbOut, cbOut, len);
        break;
    case NFS41_UNMOUNT:
        status = marshal_nfs41_unmount(entry, pbOut, cbOut, len);
        break;
    case NFS41_OPEN:
        status = marshal_nfs41_open(entry, pbOut, cbOut, len);
        break;
    case NFS41_READ:
        status = marshal_nfs41_rw(entry, pbOut, cbOut, len);
        break;
    case NFS41_WRITE:
        status = marshal_nfs41_rw(entry, pbOut, cbOut, len);
        break;
    case NFS41_LOCK:
        status = marshal_nfs41_lock(entry, pbOut, cbOut, len);
        break;
    case NFS41_UNLOCK:
        status = marshal_nfs41_unlock(entry, pbOut, cbOut, len);
        break;
    case NFS41_CLOSE:
        status = marshal_nfs41_close(entry, pbOut, cbOut, len);
        break;
    case NFS41_DIR_QUERY:
        status = marshal_nfs41_dirquery(entry, pbOut, cbOut, len);
        break;
    case NFS41_FILE_QUERY:
        status = marshal_nfs41_filequery(entry, pbOut, cbOut, len);
        break;
    case NFS41_FILE_SET:
        status = marshal_nfs41_fileset(entry, pbOut, cbOut, len);
        break;
    case NFS41_EA_SET:
        status = marshal_nfs41_easet(entry, pbOut, cbOut, len);
        break;
    case NFS41_EA_GET:
        status = marshal_nfs41_eaget(entry, pbOut, cbOut, len);
        break;
    case NFS41_SYMLINK:
        status = marshal_nfs41_symlink(entry, pbOut, cbOut, len);
        break;
    case NFS41_VOLUME_QUERY:
        status = marshal_nfs41_volume(entry, pbOut, cbOut, len);
        break;
    case NFS41_ACL_QUERY:
        status = marshal_nfs41_getacl(entry, pbOut, cbOut, len);
        break;
    case NFS41_ACL_SET:
        status = marshal_nfs41_setacl(entry, pbOut, cbOut, len);
        break;
    default:
        status = STATUS_INVALID_PARAMETER;
        print_error("Unknown nfs41 ops %d\n", entry->opcode);
    }

    if (status == STATUS_SUCCESS)
        print_hexbuf(0, (unsigned char *)"upcall buffer", pbOut, *len);

out:
    return status;
}

NTSTATUS nfs41_UpcallCreate(
    IN DWORD opcode,
    IN PSECURITY_CLIENT_CONTEXT clnt_sec_ctx,
    IN HANDLE session,
    IN HANDLE open_state,
    IN DWORD version,
    IN PUNICODE_STRING filename,
    OUT nfs41_updowncall_entry **entry_out)
{
    NTSTATUS status = STATUS_SUCCESS;
    nfs41_updowncall_entry *entry;
    SECURITY_SUBJECT_CONTEXT sec_ctx;
    SECURITY_QUALITY_OF_SERVICE sec_qos;

    entry = RxAllocatePoolWithTag(NonPagedPool, sizeof(nfs41_updowncall_entry), 
                NFS41_MM_POOLTAG_UP);
    if (entry == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    RtlZeroMemory(entry, sizeof(nfs41_updowncall_entry));
    entry->xid = InterlockedIncrement64(&xid);
    entry->opcode = opcode;
    entry->state = NFS41_WAITING_FOR_UPCALL;
    entry->session = session;
    entry->open_state = open_state;
    entry->version = version;
    if (filename && filename->Length) entry->filename = filename;
    else if (filename && !filename->Length) entry->filename = (PUNICODE_STRING)&SLASH;
    else entry->filename = (PUNICODE_STRING)&EMPTY_STRING;
    /*XXX KeInitializeEvent will bugcheck under verifier if allocated 
     * from PagedPool? */
    KeInitializeEvent(&entry->cond, SynchronizationEvent, FALSE);
    ExInitializeFastMutex(&entry->lock);

    if (clnt_sec_ctx == NULL) {
        SeCaptureSubjectContext(&sec_ctx);
        sec_qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
        sec_qos.ImpersonationLevel = SecurityImpersonation;
        sec_qos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
        sec_qos.EffectiveOnly = 0;
        status = SeCreateClientSecurityFromSubjectContext(&sec_ctx, &sec_qos, 
                    1, &entry->sec_ctx);
        if (status != STATUS_SUCCESS) {
            print_error("nfs41_UpcallCreate: "
                "SeCreateClientSecurityFromSubjectContext failed with %x\n", 
                status);
            RxFreePool(entry);
        } else
            entry->psec_ctx = &entry->sec_ctx;
        SeReleaseSubjectContext(&sec_ctx);
    } else
        entry->psec_ctx = clnt_sec_ctx;

    *entry_out = entry;
out:
    return status;
}

NTSTATUS nfs41_UpcallWaitForReply(
    IN nfs41_updowncall_entry *entry,
    IN DWORD secs)
{
    NTSTATUS status = STATUS_SUCCESS;

    nfs41_AddEntry(upcallLock, upcall, entry);
    KeSetEvent(&upcallEvent, 0, FALSE);
    if (!entry->async_op) {
        LARGE_INTEGER timeout;
        timeout.QuadPart = RELATIVE(SECONDS(secs));
        /* 02/03/2011 AGLO: it is not clear what the "right" waiting design 
         * should be. Having non-interruptable waiting seems to be the right 
         * approach. However, when things go wrong, the only wait to proceed 
         * is a reboot (since "waits" are not interruptable we can't stop a 
         * hung task. Having interruptable wait causes issues with security 
         * context. For now, I'm making CLOSE non-interruptable but keeping 
         * the rest interruptable so that we don't have to reboot all the time
         */
        /* 02/15/2011 cbodley: added NFS41_UNLOCK for the same reason. locking
         * tests were triggering an interrupted unlock, which led to a bugcheck
         * in CloseSrvOpen() */
#define MAKE_WAITONCLOSE_NONITERRUPTABLE
#ifdef MAKE_WAITONCLOSE_NONITERRUPTABLE
        if (entry->opcode == NFS41_CLOSE || entry->opcode == NFS41_UNLOCK)
            status = KeWaitForSingleObject(&entry->cond, Executive, 
                        KernelMode, FALSE, &timeout);
        else {
            status = KeWaitForSingleObject(&entry->cond, Executive, 
                        UserMode, TRUE, &timeout);
        }
        if (status != STATUS_SUCCESS) {
            print_wait_status(1, "[downcall]", status, 
                opcode2string(entry->opcode), entry, entry->xid);
            if (status == STATUS_TIMEOUT)
                status = STATUS_NETWORK_UNREACHABLE;
        }
#else

        status = KeWaitForSingleObject(&entry->cond, Executive, KernelMode, FALSE, NULL);
#endif
        print_wait_status(0, "[downcall]", status, opcode2string(entry->opcode), 
            entry, entry->xid);
    } else 
        goto out;

    switch(status) {
    case STATUS_SUCCESS: break;
    case STATUS_USER_APC:
    case STATUS_ALERTED:
    default:
        ExAcquireFastMutex(&entry->lock);
        if (entry->state == NFS41_DONE_PROCESSING) {
            ExReleaseFastMutex(&entry->lock);
            break;
        }
        DbgP("[upcall] abandoning %s entry=%p xid=%lld\n", 
            opcode2string(entry->opcode), entry, entry->xid);
        entry->state = NFS41_NOT_WAITING;
        ExReleaseFastMutex(&entry->lock);
        goto out;
    }
    nfs41_RemoveEntry(downcallLock, entry);
out:
    return status;
}

NTSTATUS nfs41_upcall(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    nfs41_updowncall_entry *entry = NULL;
    ULONG len = 0;
    PLIST_ENTRY pEntry;

process_upcall:
    nfs41_RemoveFirst(upcallLock, upcall, pEntry);
    if (pEntry) {
        entry = (nfs41_updowncall_entry *)CONTAINING_RECORD(pEntry, 
                    nfs41_updowncall_entry, next);
        ExAcquireFastMutex(&entry->lock);
        nfs41_AddEntry(downcallLock, downcall, entry);
        status = handle_upcall(RxContext, entry, &len);
        if (status == STATUS_SUCCESS && 
                entry->state == NFS41_WAITING_FOR_UPCALL)
            entry->state = NFS41_WAITING_FOR_DOWNCALL;
        ExReleaseFastMutex(&entry->lock);
        if (status) {
            entry->status = status;
            KeSetEvent(&entry->cond, 0, FALSE);
            RxContext->InformationToReturn = 0;
        } else 
            RxContext->InformationToReturn = len;
    }
    else {
        status = KeWaitForSingleObject(&upcallEvent, Executive, UserMode, TRUE,
            (PLARGE_INTEGER) NULL);
        print_wait_status(0, "[upcall]", status, NULL, NULL, 0);
        switch (status) {
        case STATUS_SUCCESS: goto process_upcall;
        case STATUS_USER_APC:
        case STATUS_ALERTED:
        default: goto out;
        }
    }
out:
    return status;
}

void unmarshal_nfs41_header(
    nfs41_updowncall_entry *tmp,
    unsigned char **buf)
{
    RtlZeroMemory(tmp, sizeof(nfs41_updowncall_entry));

    RtlCopyMemory(&tmp->xid, *buf, sizeof(tmp->xid));
    *buf += sizeof(tmp->xid);
    RtlCopyMemory(&tmp->opcode, *buf, sizeof(tmp->opcode));
    *buf += sizeof(tmp->opcode);
    RtlCopyMemory(&tmp->status, *buf, sizeof(tmp->status));
    *buf += sizeof(tmp->status);
    RtlCopyMemory(&tmp->errno, *buf, sizeof(tmp->errno));
    *buf += sizeof(tmp->errno);
#ifdef DEBUG_MARSHAL_HEADER
    DbgP("[downcall header] xid=%lld opcode=%s status=%d errno=%d\n", tmp->xid, 
        opcode2string(tmp->opcode), tmp->status, tmp->errno);
#endif
}

void unmarshal_nfs41_mount(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    RtlCopyMemory(&cur->session, *buf, sizeof(HANDLE));
    *buf += sizeof(HANDLE);
    RtlCopyMemory(&cur->version, *buf, sizeof(DWORD));
    *buf += sizeof(DWORD);
    RtlCopyMemory(&cur->u.Mount.lease_time, *buf, sizeof(DWORD));
    *buf += sizeof(DWORD);
    RtlCopyMemory(cur->u.Mount.FsAttrs, *buf, sizeof(FILE_FS_ATTRIBUTE_INFORMATION));
#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("unmarshal_nfs41_mount: session pointer 0x%x version %d lease_time "
         "%d\n", cur->session, cur->version, cur->u.Mount.lease_time);
#endif
}

VOID unmarshal_nfs41_setattr(
    nfs41_updowncall_entry *cur,
    PULONGLONG dest_buf,
    unsigned char **buf)
{
    RtlCopyMemory(dest_buf, *buf, sizeof(ULONGLONG));
#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("unmarshal_nfs41_setattr: returned ChangeTime %llu\n", *dest_buf);
#endif
}

NTSTATUS unmarshal_nfs41_rw(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    NTSTATUS status = STATUS_SUCCESS;

    RtlCopyMemory(&cur->buf_len, *buf, sizeof(cur->buf_len));
    *buf += sizeof(cur->buf_len);
    RtlCopyMemory(&cur->ChangeTime, *buf, sizeof(ULONGLONG));
#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("unmarshal_nfs41_rw: returned len %lu ChangeTime %llu\n", 
        cur->buf_len, cur->ChangeTime);
#endif
#if 1
    /* 08/27/2010: it looks like we really don't need to call 
        * MmUnmapLockedPages() eventhough we called 
        * MmMapLockedPagesSpecifyCache() as the MDL passed to us
        * is already locked. 
        */
    __try {
        MmUnmapLockedPages(cur->buf, cur->u.ReadWrite.MdlAddress);
    } __except(EXCEPTION_EXECUTE_HANDLER) { 
        NTSTATUS code; 
        code = GetExceptionCode(); 
        print_error("Call to MmUnmapLockedPages failed due to"
            " exception 0x%0x\n", code);
        status = STATUS_ACCESS_DENIED;
    }
#endif
    return status;
}

NTSTATUS unmarshal_nfs41_open(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    NTSTATUS status = STATUS_SUCCESS;

    __try {
        if (cur->u.Open.EaBuffer)
            MmUnmapLockedPages(cur->u.Open.EaBuffer, cur->u.Open.EaMdl);
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        print_error("MmUnmapLockedPages thrown exception=0x%0x\n", GetExceptionCode());
        status = cur->status = STATUS_ACCESS_DENIED;
        goto out;
    }

    RtlCopyMemory(&cur->u.Open.binfo, *buf, sizeof(FILE_BASIC_INFORMATION));
    *buf += sizeof(FILE_BASIC_INFORMATION);
    RtlCopyMemory(&cur->u.Open.sinfo, *buf, sizeof(FILE_STANDARD_INFORMATION));
    *buf += sizeof(FILE_STANDARD_INFORMATION);
    RtlCopyMemory(&cur->open_state, *buf, sizeof(HANDLE));
    *buf += sizeof(HANDLE);
    RtlCopyMemory(&cur->u.Open.mode, *buf, sizeof(DWORD));
    *buf += sizeof(DWORD);
    RtlCopyMemory(&cur->ChangeTime, *buf, sizeof(ULONGLONG));
    *buf += sizeof(ULONGLONG);
    RtlCopyMemory(&cur->u.Open.deleg_type, *buf, sizeof(DWORD));
    *buf += sizeof(DWORD);
    if (cur->errno == ERROR_REPARSE) {
        RtlCopyMemory(&cur->u.Open.symlink_embedded, *buf, sizeof(BOOLEAN));
        *buf += sizeof(BOOLEAN);
        RtlCopyMemory(&cur->u.Open.symlink.MaximumLength, *buf, 
            sizeof(USHORT));
        *buf += sizeof(USHORT);
        cur->u.Open.symlink.Length = cur->u.Open.symlink.MaximumLength -
            sizeof(WCHAR);
        cur->u.Open.symlink.Buffer = RxAllocatePoolWithTag(NonPagedPool, 
            cur->u.Open.symlink.MaximumLength, NFS41_MM_POOLTAG);
        if (cur->u.Open.symlink.Buffer == NULL) {
            cur->status = STATUS_INSUFFICIENT_RESOURCES;
            status = STATUS_UNSUCCESSFUL;
            goto out;
        }
        RtlCopyMemory(cur->u.Open.symlink.Buffer, *buf, 
            cur->u.Open.symlink.MaximumLength);
#ifdef DEBUG_MARSHAL_DETAIL
        DbgP("unmarshal_nfs41_open: ERROR_REPARSE -> '%wZ'\n", &cur->u.Open.symlink);
#endif
    }
#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("unmarshal_nfs41_open: open_state 0x%x mode %o changeattr %llu "
        "deleg_type %d\n", cur->open_state, cur->u.Open.mode, 
        cur->ChangeTime, cur->u.Open.deleg_type);
#endif
out:
    return status;
}

NTSTATUS unmarshal_nfs41_dirquery(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG buf_len;
    
    RtlCopyMemory(&buf_len, *buf, sizeof(ULONG));
#ifdef DEBUG_MARSHAL_DETAIL
    DbgP("unmarshal_nfs41_dirquery: reply size %d\n", buf_len);
#endif
    *buf += sizeof(ULONG);
    __try {
        MmUnmapLockedPages(cur->u.QueryFile.mdl_buf, cur->u.QueryFile.mdl);
    } __except(EXCEPTION_EXECUTE_HANDLER) { 
        NTSTATUS code; 
        code = GetExceptionCode(); 
        print_error("MmUnmapLockedPages thrown exception=0x%0x\n", code);
        status = STATUS_ACCESS_DENIED;
    }
    if (buf_len > cur->buf_len)
        cur->status = STATUS_BUFFER_TOO_SMALL;
    cur->buf_len = buf_len;

    return status;
}

void unmarshal_nfs41_attrget(
    nfs41_updowncall_entry *cur,
    PVOID attr_value,
    ULONG *attr_len,
    unsigned char **buf)
{
    ULONG buf_len;
    RtlCopyMemory(&buf_len, *buf, sizeof(ULONG));
    if (buf_len > *attr_len) {
        cur->status = STATUS_BUFFER_TOO_SMALL;        
        return;
    }
    *buf += sizeof(ULONG);
    *attr_len = buf_len;
    RtlCopyMemory(attr_value, *buf, buf_len);
    *buf += buf_len;
}

void unmarshal_nfs41_eaget(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    RtlCopyMemory(&cur->u.QueryEa.Overflow, *buf, sizeof(ULONG));
    *buf += sizeof(ULONG);
    RtlCopyMemory(&cur->buf_len, *buf, sizeof(ULONG));
    *buf += sizeof(ULONG);
    if (cur->u.QueryEa.Overflow != ERROR_INSUFFICIENT_BUFFER) {
        RtlCopyMemory(cur->buf, *buf, cur->buf_len);
        *buf += cur->buf_len;
    }
}

void unmarshal_nfs41_getattr(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    unmarshal_nfs41_attrget(cur, cur->buf, &cur->buf_len, buf);
    RtlCopyMemory(&cur->ChangeTime, *buf, sizeof(LONGLONG));
#ifdef DEBUG_MARSHAL_DETAIL
    if (cur->u.QueryFile.InfoClass == FileBasicInformation)
        DbgP("[unmarshal_nfs41_getattr] ChangeTime %llu\n", cur->ChangeTime);
#endif
}

NTSTATUS unmarshal_nfs41_getacl(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    NTSTATUS status = STATUS_SUCCESS;
    DWORD buf_len;

    RtlCopyMemory(&buf_len, *buf, sizeof(DWORD));
    *buf += sizeof(DWORD);
    cur->buf = RxAllocatePoolWithTag(NonPagedPool, 
        buf_len, NFS41_MM_POOLTAG_ACL);
    if (cur->buf == NULL) {
        cur->status = status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    RtlCopyMemory(cur->buf, *buf, buf_len);
    if (buf_len > cur->buf_len)
        cur->status = STATUS_BUFFER_TOO_SMALL;
    cur->buf_len = buf_len;

out:
    return status;
}

void unmarshal_nfs41_symlink(
    nfs41_updowncall_entry *cur,
    unsigned char **buf)
{
    if (cur->u.Symlink.set) return;

    RtlCopyMemory(&cur->u.Symlink.target->Length, *buf, sizeof(USHORT));
    *buf += sizeof(USHORT);
    if (cur->u.Symlink.target->Length > 
            cur->u.Symlink.target->MaximumLength) {
        cur->status = STATUS_BUFFER_TOO_SMALL;
        return;
    }
    RtlCopyMemory(cur->u.Symlink.target->Buffer, *buf,
        cur->u.Symlink.target->Length);
    cur->u.Symlink.target->Length -= sizeof(UNICODE_NULL);
}

NTSTATUS nfs41_downcall(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    ULONG in_len = LowIoContext->ParamsFor.IoCtl.InputBufferLength;
    unsigned char *buf = LowIoContext->ParamsFor.IoCtl.pInputBuffer;
    PLIST_ENTRY pEntry;
    nfs41_updowncall_entry *tmp, *cur= NULL;
    BOOLEAN found = 0;

    print_hexbuf(0, (unsigned char *)"downcall buffer", buf, in_len);

    tmp = RxAllocatePoolWithTag(NonPagedPool, sizeof(nfs41_updowncall_entry), 
            NFS41_MM_POOLTAG_DOWN);
    if (tmp == NULL) goto out;

    unmarshal_nfs41_header(tmp, &buf);

    ExAcquireFastMutex(&downcallLock); 
    pEntry = &downcall.head;
    pEntry = pEntry->Flink;
    while (pEntry != NULL) {
        cur = (nfs41_updowncall_entry *)CONTAINING_RECORD(pEntry, 
                nfs41_updowncall_entry, next);
        if (cur->xid == tmp->xid) {
            found = 1;
            break;
        }
        if (pEntry->Flink == &downcall.head)
            break;
        pEntry = pEntry->Flink;
    }
    ExReleaseFastMutex(&downcallLock);
    SeStopImpersonatingClient();
    if (!found) {
        print_error("Didn't find xid=%lld entry\n", tmp->xid);
        goto out_free;
    }

    ExAcquireFastMutex(&cur->lock);    
    if (cur->state == NFS41_NOT_WAITING) {
        DbgP("[downcall] Nobody is waiting for this request!!!\n");
        switch(cur->opcode) {
        case NFS41_WRITE:
        case NFS41_READ:
            MmUnmapLockedPages(cur->buf, cur->u.ReadWrite.MdlAddress);
            break;
        case NFS41_DIR_QUERY:
            MmUnmapLockedPages(cur->u.QueryFile.mdl_buf, 
                    cur->u.QueryFile.mdl);
            IoFreeMdl(cur->u.QueryFile.mdl);
            break;
        case NFS41_OPEN:
            if (cur->u.Open.EaMdl) {
                MmUnmapLockedPages(cur->u.Open.EaBuffer,
                        cur->u.Open.EaMdl);
                IoFreeMdl(cur->u.Open.EaMdl);
            }
            break;
        }
        ExReleaseFastMutex(&cur->lock);
        nfs41_RemoveEntry(downcallLock, cur);
        RxFreePool(cur);
        status = STATUS_UNSUCCESSFUL;
        goto out_free;
    }
    cur->state = NFS41_DONE_PROCESSING;
    cur->status = tmp->status;
    cur->errno = tmp->errno;
    status = STATUS_SUCCESS;

    if (!tmp->status) {
        switch (tmp->opcode) {
        case NFS41_MOUNT:
            unmarshal_nfs41_mount(cur, &buf);
            break;
        case NFS41_WRITE:
        case NFS41_READ:
            status = unmarshal_nfs41_rw(cur, &buf);
            break;
        case NFS41_OPEN:
            status = unmarshal_nfs41_open(cur, &buf);
            break;
        case NFS41_DIR_QUERY:
            status = unmarshal_nfs41_dirquery(cur, &buf);
            break;
        case NFS41_FILE_QUERY:
            unmarshal_nfs41_getattr(cur, &buf);
            break;
        case NFS41_EA_GET:
            unmarshal_nfs41_eaget(cur, &buf);
            break;
        case NFS41_SYMLINK:
            unmarshal_nfs41_symlink(cur, &buf);
            break;
        case NFS41_VOLUME_QUERY:
            unmarshal_nfs41_attrget(cur, cur->buf, &cur->buf_len, &buf);
            break;
        case NFS41_ACL_QUERY:
            status = unmarshal_nfs41_getacl(cur, &buf);
            break;
        case NFS41_FILE_SET:
            unmarshal_nfs41_setattr(cur, &cur->ChangeTime, &buf);
            break;
        case NFS41_EA_SET:
            unmarshal_nfs41_setattr(cur, &cur->ChangeTime, &buf);
            break;
        case NFS41_ACL_SET:
            unmarshal_nfs41_setattr(cur, &cur->ChangeTime, &buf);
            break;
        }
    }
    ExReleaseFastMutex(&cur->lock);
    if (cur->async_op) {
        if (cur->status == STATUS_SUCCESS) {
            cur->u.ReadWrite.rxcontext->StoredStatus = STATUS_SUCCESS;
            cur->u.ReadWrite.rxcontext->InformationToReturn = 
                cur->buf_len;
        } else {
            cur->u.ReadWrite.rxcontext->StoredStatus = 
                map_readwrite_errors(cur->status);
            cur->u.ReadWrite.rxcontext->InformationToReturn = 0;
        }
        nfs41_RemoveEntry(downcallLock, cur);
        RxLowIoCompletion(cur->u.ReadWrite.rxcontext);
        RxFreePool(cur);
    } else
        KeSetEvent(&cur->cond, 0, FALSE);    

out_free:
    RxFreePool(tmp);
out:
    return status;
}

NTSTATUS nfs41_shutdown_daemon(
    DWORD version)
{
    NTSTATUS status = STATUS_SUCCESS;
    nfs41_updowncall_entry *entry = NULL;

    DbgEn();
    status = nfs41_UpcallCreate(NFS41_SHUTDOWN, NULL, INVALID_HANDLE_VALUE,
        INVALID_HANDLE_VALUE, version, NULL, &entry);
    if (status) goto out;

    status = nfs41_UpcallWaitForReply(entry, UPCALL_TIMEOUT_DEFAULT);
    SeDeleteClientSecurity(&entry->sec_ctx);
    if (status) goto out;

    RxFreePool(entry);
out:
    DbgEx();
    return status;
}

NTSTATUS SharedMemoryInit(
    OUT PHANDLE phSection)
{
    NTSTATUS status;
    HANDLE hSection;
    UNICODE_STRING SectionName;
    SECURITY_DESCRIPTOR SecurityDesc;
    OBJECT_ATTRIBUTES SectionAttrs;
    LARGE_INTEGER nSectionSize;

    DbgEn();

    RtlInitUnicodeString(&SectionName, NFS41_SHARED_MEMORY_NAME);

    /* XXX: setting dacl=NULL grants access to everyone */
    status = RtlCreateSecurityDescriptor(&SecurityDesc,
        SECURITY_DESCRIPTOR_REVISION);
    if (status) {
        print_error("RtlCreateSecurityDescriptor() failed with %08X\n", status);
        goto out;
    }
    status = RtlSetDaclSecurityDescriptor(&SecurityDesc, TRUE, NULL, FALSE);
    if (status) {
        print_error("RtlSetDaclSecurityDescriptor() failed with %08X\n", status);
        goto out;
    }

    InitializeObjectAttributes(&SectionAttrs, &SectionName,
        0, NULL, &SecurityDesc);

    nSectionSize.QuadPart = sizeof(NFS41NP_SHARED_MEMORY);

    status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE,
        &SectionAttrs, &nSectionSize, PAGE_READWRITE, SEC_COMMIT, NULL);
    switch (status) {
    case STATUS_SUCCESS:
        break;
    case STATUS_OBJECT_NAME_COLLISION:
        DbgP("section already created; returning success\n");
        status = STATUS_SUCCESS;
        goto out;
    default:
        DbgP("ZwCreateSection failed with %08X\n", status);
        goto out;
    }
out:
    DbgEx();
    return status;
}

NTSTATUS SharedMemoryFree(
    IN HANDLE hSection)
{
    NTSTATUS status;
    DbgEn();
    status = ZwClose(hSection);
    DbgEx();
    return status;
}

NTSTATUS nfs41_Start(
    IN OUT PRX_CONTEXT RxContext, 
    IN OUT PRDBSS_DEVICE_OBJECT dev)
{
    NTSTATUS status;
    NFS41GetDeviceExtension(RxContext, DevExt);

    DbgEn();

    status = SharedMemoryInit(&DevExt->SharedMemorySection);
    if (status) {
        print_error("InitSharedMemory failed with %08X\n", status);
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    InterlockedCompareExchange((PLONG)&nfs41_start_state,
        NFS41_START_DRIVER_STARTED,
        NFS41_START_DRIVER_START_IN_PROGRESS);
out:
    DbgEx();
    return status;
}

NTSTATUS nfs41_Stop(
    IN OUT PRX_CONTEXT RxContext,
    IN OUT PRDBSS_DEVICE_OBJECT dev)
{
    NTSTATUS status;
    NFS41GetDeviceExtension(RxContext, DevExt);
    DbgEn();
    status = SharedMemoryFree(DevExt->SharedMemorySection);
    DbgEx();
    return status;
}

NTSTATUS GetConnectionHandle(
    IN PUNICODE_STRING ConnectionName,
    IN PVOID EaBuffer,
    IN ULONG EaLength,
    OUT PHANDLE Handle)
{
    NTSTATUS status;
    IO_STATUS_BLOCK IoStatusBlock;
    OBJECT_ATTRIBUTES ObjectAttributes;

#ifdef DEBUG_MOUNT
    DbgEn();
#endif
    InitializeObjectAttributes(&ObjectAttributes, ConnectionName,
        OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL);

    status = ZwCreateFile(Handle, SYNCHRONIZE, &ObjectAttributes,
        &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        FILE_OPEN_IF,
        FILE_CREATE_TREE_CONNECTION | FILE_SYNCHRONOUS_IO_NONALERT,
        EaBuffer, EaLength);

#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_GetConnectionInfoFromBuffer(
    IN PVOID Buffer,
    IN ULONG BufferLen,
    OUT PUNICODE_STRING pConnectionName,
    OUT PVOID *ppEaBuffer,
    OUT PULONG pEaLength)
{
    NTSTATUS status = STATUS_SUCCESS;
    USHORT NameLength, EaPadding;
    ULONG EaLength, BufferLenExpected;
    PBYTE ptr;

    /* make sure buffer is at least big enough for header */
    if (BufferLen < sizeof(USHORT) + sizeof(USHORT) + sizeof(ULONG)) {
        status = STATUS_BAD_NETWORK_NAME;
        print_error("Invalid input buffer.\n");
        pConnectionName->Length = pConnectionName->MaximumLength = 0;
        *ppEaBuffer = NULL;
        *pEaLength = 0;
        goto out;
    }

    ptr = Buffer;
    NameLength = *(PUSHORT)ptr;
    ptr += sizeof(USHORT);
    EaPadding = *(PUSHORT)ptr;
    ptr += sizeof(USHORT);
    EaLength = *(PULONG)ptr;
    ptr += sizeof(ULONG);

    /* validate buffer length */
    BufferLenExpected = sizeof(USHORT) + sizeof(USHORT) + sizeof(ULONG) +
        NameLength + EaPadding + EaLength;
    if (BufferLen != BufferLenExpected) {
        status = STATUS_BAD_NETWORK_NAME;
        print_error("Received buffer of length %lu, but expected %lu bytes.\n",
            BufferLen, BufferLenExpected);
        pConnectionName->Length = pConnectionName->MaximumLength = 0;
        *ppEaBuffer = NULL;
        *pEaLength = 0;
        goto out;
    }

    pConnectionName->Buffer = (PWCH)ptr;
    pConnectionName->Length = NameLength - sizeof(WCHAR);
    pConnectionName->MaximumLength = NameLength;

    if (EaLength)
        *ppEaBuffer = ptr + NameLength + EaPadding;
    else
        *ppEaBuffer = NULL;
    *pEaLength = EaLength;

out:
    return status;
}

NTSTATUS nfs41_CreateConnection(
    IN PRX_CONTEXT RxContext,
    OUT PBOOLEAN PostToFsp)
{
    NTSTATUS status = STATUS_SUCCESS;
    HANDLE Handle = INVALID_HANDLE_VALUE;
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    PVOID Buffer = LowIoContext->ParamsFor.IoCtl.pInputBuffer, EaBuffer;
    ULONG BufferLen = LowIoContext->ParamsFor.IoCtl.InputBufferLength, EaLength;
    UNICODE_STRING FileName;
    BOOLEAN Wait = BooleanFlagOn(RxContext->Flags, RX_CONTEXT_FLAG_WAIT);

#ifdef DEBUG_MOUNT
    DbgEn();
#endif

    if (!Wait) {
        //just post right now!
        DbgP("returning STATUS_PENDING\n");
        *PostToFsp = TRUE;
        status = STATUS_PENDING;
        goto out;
    }

    status = nfs41_GetConnectionInfoFromBuffer(Buffer, BufferLen,
        &FileName, &EaBuffer, &EaLength);
    if (status != STATUS_SUCCESS)
        goto out;

    status = GetConnectionHandle(&FileName, EaBuffer, EaLength, &Handle);
    if (!status && Handle != INVALID_HANDLE_VALUE)
        ZwClose(Handle);
out:
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

#ifdef ENABLE_TIMINGS
void print_op_stat(
    const char *op_str, 
    nfs41_timings *time, BOOLEAN clear) 
{
    DbgP("%-9s: num_ops=%-10d delta_ticks=%-10d size=%-10d\n", op_str, 
        time->tops, time->tops ? time->ticks/time->tops : 0,
        time->sops ? time->size/time->sops : 0);
    if (clear) {
        time->tops = 0;
        time->ticks = 0;
        time->size = 0;
        time->sops = 0;
    }
}
#endif
NTSTATUS nfs41_unmount(
    HANDLE session, 
    DWORD version,
    DWORD timeout)
{
    NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
    nfs41_updowncall_entry *entry;

#ifdef DEBUG_MOUNT
    DbgEn();
#endif
    status = nfs41_UpcallCreate(NFS41_UNMOUNT, NULL, session, 
        INVALID_HANDLE_VALUE, version, NULL, &entry);
    SeDeleteClientSecurity(&entry->sec_ctx);
    if (status) goto out;

    nfs41_UpcallWaitForReply(entry, timeout);
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    print_op_stat("lookup", &lookup, 1);
    print_op_stat("open", &open, 1);
    print_op_stat("close", &close, 1);
    print_op_stat("volume", &volume, 1);
    print_op_stat("getattr", &getattr, 1);
    print_op_stat("setattr", &setattr, 1);
    print_op_stat("getexattr", &getexattr, 1);
    print_op_stat("setexattr", &setexattr, 1);
    print_op_stat("readdir", &readdir, 1);
    print_op_stat("getacl", &getacl, 1);
    print_op_stat("setacl", &setacl, 1);
    print_op_stat("read", &read, 1);
    print_op_stat("write", &write, 1);
    print_op_stat("lock", &lock, 1);
    print_op_stat("unlock", &unlock, 1);
#endif
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_DeleteConnection (
    IN PRX_CONTEXT RxContext,
    OUT PBOOLEAN PostToFsp)
{
    NTSTATUS status = STATUS_INVALID_PARAMETER;
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    PWCHAR ConnectName = LowIoContext->ParamsFor.IoCtl.pInputBuffer;
    ULONG ConnectNameLen = LowIoContext->ParamsFor.IoCtl.InputBufferLength;
    HANDLE Handle;
    UNICODE_STRING FileName;
    PFILE_OBJECT pFileObject;
    BOOLEAN Wait = BooleanFlagOn(RxContext->Flags, RX_CONTEXT_FLAG_WAIT);

#ifdef DEBUG_MOUNT
    DbgEn();
#endif

    if (!Wait) {
        //just post right now!
        *PostToFsp = TRUE;
        DbgP("returning STATUS_PENDING\n");
        status = STATUS_PENDING;
        goto out;
    }

    FileName.Buffer = ConnectName;
    FileName.Length = (USHORT) ConnectNameLen - sizeof(WCHAR);
    FileName.MaximumLength = (USHORT) ConnectNameLen;

    status = GetConnectionHandle(&FileName, NULL, 0, &Handle);
    if (status != STATUS_SUCCESS)
        goto out;

    status = ObReferenceObjectByHandle(Handle, 0L, NULL, KernelMode,
                (PVOID *)&pFileObject, NULL);
    if (NT_SUCCESS(status)) {
        PV_NET_ROOT VNetRoot;

        // VNetRoot exists as FOBx in the FsContext2
        VNetRoot = (PV_NET_ROOT) pFileObject->FsContext2;
        // make sure the node looks right
        if (NodeType(VNetRoot) == RDBSS_NTC_V_NETROOT)
        {
#ifdef DEBUG_MOUNT
            DbgP("Calling RxFinalizeConnection for NetRoot %p from VNetRoot %p\n",
                VNetRoot->NetRoot, VNetRoot);
#endif
            status = RxFinalizeConnection(VNetRoot->NetRoot, VNetRoot, TRUE);
        }
        else
            status = STATUS_BAD_NETWORK_NAME;

        ObDereferenceObject(pFileObject);
    }
    ZwClose(Handle);
out:
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_DevFcbXXXControlFile(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
    UCHAR op = RxContext->MajorFunction;
    PLOWIO_CONTEXT io_ctx = &RxContext->LowIoContext;
    ULONG fsop = io_ctx->ParamsFor.FsCtl.FsControlCode, state;
    ULONG in_len = io_ctx->ParamsFor.IoCtl.InputBufferLength;
    DWORD *buf = io_ctx->ParamsFor.IoCtl.pInputBuffer;
    NFS41GetDeviceExtension(RxContext, DevExt);
    DWORD nfs41d_version = 0;

    //DbgEn();

    print_ioctl(0, op);
    switch(op) {
    case IRP_MJ_FILE_SYSTEM_CONTROL:
        status = STATUS_INVALID_DEVICE_REQUEST;
        break;
    case IRP_MJ_DEVICE_CONTROL:
    case IRP_MJ_INTERNAL_DEVICE_CONTROL:
        print_fs_ioctl(0, fsop);
        switch (fsop) {
        case IOCTL_NFS41_INVALCACHE:
            nfs41_invalidate_cache(RxContext);
            status = STATUS_SUCCESS;
            break;
        case IOCTL_NFS41_READ:
            status = nfs41_upcall(RxContext);
            break;
        case IOCTL_NFS41_WRITE:
            status = nfs41_downcall(RxContext);
            break;
        case IOCTL_NFS41_ADDCONN:
            status = nfs41_CreateConnection(RxContext, &RxContext->PostRequest);
            break;
        case IOCTL_NFS41_DELCONN:
            if (RxContext->RxDeviceObject->NumberOfActiveFcbs > 0) {
                DbgP("device has open handles %d\n", 
                    RxContext->RxDeviceObject->NumberOfActiveFcbs);
                status = STATUS_REDIRECTOR_HAS_OPEN_HANDLES;
                break;
            }
            status = nfs41_DeleteConnection(RxContext, &RxContext->PostRequest);
            break;
        case IOCTL_NFS41_GETSTATE:
            state = RDR_NULL_STATE;

            if (io_ctx->ParamsFor.IoCtl.OutputBufferLength >= sizeof(ULONG)) {
                // map the states to control app's equivalents
                print_driver_state(nfs41_start_state);
                switch (nfs41_start_state) {
                case NFS41_START_DRIVER_STARTABLE:
                case NFS41_START_DRIVER_STOPPED:
                    state = RDR_STOPPED;
                    break;
                case NFS41_START_DRIVER_START_IN_PROGRESS:
                    state = RDR_STARTING;
                    break;
                case NFS41_START_DRIVER_STARTED:
                    state = RDR_STARTED;
                    break;
                }
                *(ULONG *)io_ctx->ParamsFor.IoCtl.pOutputBuffer = state;
                RxContext->InformationToReturn = sizeof(ULONG);
                status = STATUS_SUCCESS;
            } else
                status = STATUS_INVALID_PARAMETER;
            break;
        case IOCTL_NFS41_START:
            print_driver_state(nfs41_start_state);
            if (in_len >= sizeof(DWORD)) {
                RtlCopyMemory(&nfs41d_version, buf, sizeof(DWORD));
                DbgP("NFS41 Daemon sent start request with version %d\n", 
                    nfs41d_version);
                DbgP("Currently used NFS41 Daemon version is %d\n", 
                    DevExt->nfs41d_version);
                DevExt->nfs41d_version = nfs41d_version;
            }
            switch(nfs41_start_state) {
            case NFS41_START_DRIVER_STARTABLE:
                (nfs41_start_driver_state)InterlockedCompareExchange(
                              (PLONG)&nfs41_start_state,
                              NFS41_START_DRIVER_START_IN_PROGRESS,
                              NFS41_START_DRIVER_STARTABLE);
                    //lack of break is intentional
            case NFS41_START_DRIVER_START_IN_PROGRESS:
                status = RxStartMinirdr(RxContext, &RxContext->PostRequest);
                if (status == STATUS_REDIRECTOR_STARTED) {
                    DbgP("redirector started\n");
                    status = STATUS_SUCCESS;
                } else if (status == STATUS_PENDING && 
                            RxContext->PostRequest == TRUE) {
                    DbgP("RxStartMinirdr pending %08lx\n", status);
                    status = STATUS_MORE_PROCESSING_REQUIRED;
                } 
                break;
            case NFS41_START_DRIVER_STARTED:
                status = STATUS_SUCCESS;
                break;
            default:
                status = STATUS_INVALID_PARAMETER;
            }
            break;
        case IOCTL_NFS41_STOP:
            if (nfs41_start_state == NFS41_START_DRIVER_STARTED)
                nfs41_shutdown_daemon(DevExt->nfs41d_version);
            if (RxContext->RxDeviceObject->NumberOfActiveFcbs > 0) {
                DbgP("device has open handles %d\n", 
                    RxContext->RxDeviceObject->NumberOfActiveFcbs);
                status = STATUS_REDIRECTOR_HAS_OPEN_HANDLES;
                break;
            }

            state = (nfs41_start_driver_state)InterlockedCompareExchange(
                        (PLONG)&nfs41_start_state, 
                        NFS41_START_DRIVER_STARTABLE, 
                        NFS41_START_DRIVER_STARTED);

            status = RxStopMinirdr(RxContext, &RxContext->PostRequest);
            DbgP("RxStopMinirdr status %08lx\n", status);
            if (status == STATUS_PENDING && RxContext->PostRequest == TRUE )
                status = STATUS_MORE_PROCESSING_REQUIRED;
            break;
        default:
            status = STATUS_INVALID_DEVICE_REQUEST;
        };
        break;
    default:
        status = STATUS_INVALID_DEVICE_REQUEST;
    };

    //DbgEx();
    return status;
}

NTSTATUS _nfs41_CreateSrvCall(
    PMRX_SRVCALL_CALLBACK_CONTEXT pCallbackContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    PMRX_SRVCALL_CALLBACK_CONTEXT SCCBC = pCallbackContext;
    PMRX_SRV_CALL pSrvCall;
    PMRX_SRVCALLDOWN_STRUCTURE SrvCalldownStructure =
        (PMRX_SRVCALLDOWN_STRUCTURE)(SCCBC->SrvCalldownStructure);
    PNFS41_SERVER_ENTRY pServerEntry = NULL;

#ifdef DEBUG_MOUNT
    DbgEn();
#endif

    pSrvCall = SrvCalldownStructure->SrvCall;

    ASSERT( pSrvCall );
    ASSERT( NodeType(pSrvCall) == RDBSS_NTC_SRVCALL );
    print_srv_call(0, pSrvCall);

    // validate the server name with the test name of 'pnfs'
#ifdef DEBUG_MOUNT
    DbgP("SrvCall: Connection Name Length: %d %wZ\n",
        pSrvCall->pSrvCallName->Length, pSrvCall->pSrvCallName);
#endif

    if (pSrvCall->pSrvCallName->Length > SERVER_NAME_BUFFER_SIZE) {
        print_error("Server name '%wZ' too long for server entry (max %u)\n",
            pSrvCall->pSrvCallName, SERVER_NAME_BUFFER_SIZE);
        status = STATUS_NAME_TOO_LONG;
        goto out;
    }

    /* Let's create our own representation of the server */
    pServerEntry = (PNFS41_SERVER_ENTRY)RxAllocatePoolWithTag(PagedPool, 
        sizeof(NFS41_SERVER_ENTRY), NFS41_MM_POOLTAG);
    if (pServerEntry == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    RtlZeroMemory(pServerEntry, sizeof(NFS41_SERVER_ENTRY));

    pServerEntry->Name.Buffer = pServerEntry->NameBuffer;
    pServerEntry->Name.Length = pSrvCall->pSrvCallName->Length;
    pServerEntry->Name.MaximumLength = SERVER_NAME_BUFFER_SIZE;
    RtlCopyMemory(pServerEntry->Name.Buffer, pSrvCall->pSrvCallName->Buffer,
        pServerEntry->Name.Length);

    pCallbackContext->RecommunicateContext = pServerEntry;
    InterlockedExchangePointer(&pServerEntry->pRdbssSrvCall, pSrvCall);

out:
    SCCBC->Status = status;
    SrvCalldownStructure->CallBack(SCCBC);

#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_CreateSrvCall(
    PMRX_SRV_CALL pSrvCall,
    PMRX_SRVCALL_CALLBACK_CONTEXT pCallbackContext)
{
    NTSTATUS status;

    ASSERT( pSrvCall );
    ASSERT( NodeType(pSrvCall) == RDBSS_NTC_SRVCALL );

    if (IoGetCurrentProcess() == RxGetRDBSSProcess()) {
        DbgP("executing with RDBSS context\n");
        status = _nfs41_CreateSrvCall(pCallbackContext);
    } else {
        status = RxDispatchToWorkerThread(nfs41_dev, DelayedWorkQueue, 
            _nfs41_CreateSrvCall, pCallbackContext);
        if (status != STATUS_SUCCESS) {
            print_error("RxDispatchToWorkerThread returned status %08lx\n", 
                status);
            pCallbackContext->Status = status;
            pCallbackContext->SrvCalldownStructure->CallBack(pCallbackContext);
            status = STATUS_PENDING;
        }
    }
    /* RDBSS expects MRxCreateSrvCall to return STATUS_PENDING */
    if (status == STATUS_SUCCESS)
        status = STATUS_PENDING;

    return status;
}

NTSTATUS nfs41_SrvCallWinnerNotify(
    IN OUT PMRX_SRV_CALL pSrvCall, 
    IN BOOLEAN ThisMinirdrIsTheWinner,
    IN OUT PVOID pSrvCallContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    PNFS41_SERVER_ENTRY pServerEntry;

    pServerEntry = (PNFS41_SERVER_ENTRY)pSrvCallContext;

    if (!ThisMinirdrIsTheWinner) {
        ASSERT(1);
        goto out;
    }

    pSrvCall->Context = pServerEntry;
out:
    return status;
}

NTSTATUS map_mount_errors(
    DWORD status)
{
    switch (status) {
    case NO_ERROR:              return STATUS_SUCCESS;
    case ERROR_NETWORK_UNREACHABLE: return STATUS_NETWORK_UNREACHABLE;
    case ERROR_BAD_NET_RESP:    return STATUS_UNEXPECTED_NETWORK_ERROR;
    case ERROR_BAD_NET_NAME:    return STATUS_BAD_NETWORK_NAME;
    case ERROR_BAD_NETPATH:     return STATUS_BAD_NETWORK_PATH;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INSUFFICIENT_RESOURCES\n", status);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
}

NTSTATUS nfs41_mount(
    PNFS41_MOUNT_CONFIG config, 
    DWORD sec_flavor, 
    PHANDLE session, 
    DWORD *version,
    PFILE_FS_ATTRIBUTE_INFORMATION FsAttrs)
{
    NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
    nfs41_updowncall_entry *entry;

#ifdef DEBUG_MOUNT
    DbgEn();
    DbgP("Server Name %wZ Mount Point %wZ SecFlavor %d\n",
        &config->SrvName, &config->MntPt, sec_flavor);
#endif
    status = nfs41_UpcallCreate(NFS41_MOUNT, NULL, *session,
        INVALID_HANDLE_VALUE, *version, &config->MntPt, &entry);
    if (status) goto out;

    entry->u.Mount.srv_name = &config->SrvName;
    entry->u.Mount.root = &config->MntPt;
    entry->u.Mount.rsize = config->ReadSize;
    entry->u.Mount.wsize = config->WriteSize;
    entry->u.Mount.sec_flavor = sec_flavor;
    entry->u.Mount.FsAttrs = FsAttrs;

    status = nfs41_UpcallWaitForReply(entry, config->timeout);
    SeDeleteClientSecurity(&entry->sec_ctx);
    if (status) goto out;
    *session = entry->session;
    if (entry->u.Mount.lease_time > config->timeout)
        config->timeout = entry->u.Mount.lease_time;

    /* map windows ERRORs to NTSTATUS */
    status = map_mount_errors(entry->status);
    if (status == STATUS_SUCCESS)
        *version = entry->version;
    RxFreePool(entry);
out:
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

/* TODO: move mount config stuff to another file -cbodley */

void nfs41_MountConfig_InitDefaults(
    OUT PNFS41_MOUNT_CONFIG Config)
{
    RtlZeroMemory(Config, sizeof(NFS41_MOUNT_CONFIG));

    Config->ReadSize = MOUNT_CONFIG_RW_SIZE_DEFAULT;
    Config->WriteSize = MOUNT_CONFIG_RW_SIZE_DEFAULT;
    Config->ReadOnly = FALSE;
    Config->write_thru = FALSE;
    Config->nocache = FALSE;
    Config->SrvName.Length = 0;
    Config->SrvName.MaximumLength = SERVER_NAME_BUFFER_SIZE;
    Config->SrvName.Buffer = Config->srv_buffer;
    Config->MntPt.Length = 0;
    Config->MntPt.MaximumLength = MAX_PATH;
    Config->MntPt.Buffer = Config->mntpt_buffer;
    Config->SecFlavor.Length = 0;
    Config->SecFlavor.MaximumLength = MAX_SEC_FLAVOR_LEN;
    Config->SecFlavor.Buffer = Config->sec_flavor;
    RtlCopyUnicodeString(&Config->SecFlavor, &AUTH_SYS_NAME);
    Config->timeout = UPCALL_TIMEOUT_DEFAULT;
}

NTSTATUS nfs41_MountConfig_ParseBoolean(
    IN PFILE_FULL_EA_INFORMATION Option,
    IN PUNICODE_STRING usValue,
    OUT PBOOLEAN Value)
{
    NTSTATUS status = STATUS_SUCCESS;

    /* if no value is specified, assume TRUE
     * if a value is specified, it must be a '1' */
    if (Option->EaValueLength == 0 || *usValue->Buffer == L'1')
        *Value = TRUE;
    else
        *Value = FALSE;

    DbgP("    '%ls' -> '%wZ' -> %u\n",
        (LPWSTR)Option->EaName, usValue, *Value);
    return status;
}

NTSTATUS nfs41_MountConfig_ParseDword(
    IN PFILE_FULL_EA_INFORMATION Option,
    IN PUNICODE_STRING usValue,
    OUT PDWORD Value,
    IN DWORD Minimum,
    IN DWORD Maximum)
{
    NTSTATUS status = STATUS_INVALID_PARAMETER;
    LPWSTR Name = (LPWSTR)Option->EaName;

    if (Option->EaValueLength) {
        status = RtlUnicodeStringToInteger(usValue, 0, Value);
        if (status == STATUS_SUCCESS) {
#ifdef IMPOSE_MINMAX_RWSIZES
            if (*Value < Minimum)
                *Value = Minimum;
            if (*Value > Maximum)
                *Value = Maximum;
            DbgP("    '%ls' -> '%wZ' -> %lu\n", Name, usValue, *Value);
#endif
        }
        else
            print_error("Failed to convert %s='%wZ' to unsigned long.\n",
                Name, usValue);
    }

    return status;
}

NTSTATUS nfs41_MountConfig_ParseOptions(
    IN PFILE_FULL_EA_INFORMATION EaBuffer,
    IN ULONG EaLength,
    IN OUT PNFS41_MOUNT_CONFIG Config)
{
    NTSTATUS  status = STATUS_SUCCESS;
    PFILE_FULL_EA_INFORMATION Option;
    LPWSTR Name;
    size_t NameLen;
    UNICODE_STRING  usValue;
    Option = EaBuffer;
    while (status == STATUS_SUCCESS) {
        Name = (LPWSTR)Option->EaName;
        NameLen = Option->EaNameLength/sizeof(WCHAR);

        usValue.Length = usValue.MaximumLength = Option->EaValueLength;
        usValue.Buffer = (PWCH)(Option->EaName +
            Option->EaNameLength + sizeof(WCHAR));

        if (wcsncmp(L"ro", Name, NameLen) == 0) {
            status = nfs41_MountConfig_ParseBoolean(Option, &usValue,
                &Config->ReadOnly);
        }
        else if (wcsncmp(L"writethru", Name, NameLen) == 0) {
            status = nfs41_MountConfig_ParseBoolean(Option, &usValue,
                &Config->write_thru);
        }
        else if (wcsncmp(L"nocache", Name, NameLen) == 0) {
            status = nfs41_MountConfig_ParseBoolean(Option, &usValue,
                &Config->nocache);
        }
        else if (wcsncmp(L"timeout", Name, NameLen) == 0) {
            status = nfs41_MountConfig_ParseDword(Option, &usValue,
                &Config->timeout, UPCALL_TIMEOUT_DEFAULT,
                UPCALL_TIMEOUT_DEFAULT);
        }
        else if (wcsncmp(L"rsize", Name, NameLen) == 0) {
            status = nfs41_MountConfig_ParseDword(Option, &usValue,
                &Config->ReadSize, MOUNT_CONFIG_RW_SIZE_MIN,
                MOUNT_CONFIG_RW_SIZE_MAX);
        }
        else if (wcsncmp(L"wsize", Name, NameLen) == 0) {
            status = nfs41_MountConfig_ParseDword(Option, &usValue,
                &Config->WriteSize, MOUNT_CONFIG_RW_SIZE_MIN,
                MOUNT_CONFIG_RW_SIZE_MAX);
        }
        else if (wcsncmp(L"srvname", Name, NameLen) == 0) {
            if (usValue.Length > Config->SrvName.MaximumLength)
                status = STATUS_NAME_TOO_LONG;
            else
                RtlCopyUnicodeString(&Config->SrvName, &usValue);
        }
        else if (wcsncmp(L"mntpt", Name, NameLen) == 0) {
            if (usValue.Length > Config->MntPt.MaximumLength)
                status = STATUS_NAME_TOO_LONG;
            else
                RtlCopyUnicodeString(&Config->MntPt, &usValue);
        }
        else if (wcsncmp(L"sec", Name, NameLen) == 0) {
            if (usValue.Length > Config->SecFlavor.MaximumLength)
                status = STATUS_NAME_TOO_LONG;
            else
                RtlCopyUnicodeString(&Config->SecFlavor, &usValue);
        }
        else {
            status = STATUS_INVALID_PARAMETER;
            print_error("Unrecognized option '%ls' -> '%wZ'\n",
                Name, usValue);
        }

        if (Option->NextEntryOffset == 0)
            break;

        Option = (PFILE_FULL_EA_INFORMATION)
            ((PBYTE)Option + Option->NextEntryOffset);
    }

    return status;
}

NTSTATUS has_nfs_prefix(
    IN PUNICODE_STRING SrvCallName,
    IN PUNICODE_STRING NetRootName)
{
    NTSTATUS status = STATUS_BAD_NETWORK_NAME;

    if (NetRootName->Length == SrvCallName->Length + NfsPrefix.Length) {
        const UNICODE_STRING NetRootPrefix = {
            NfsPrefix.Length,
            NetRootName->MaximumLength - SrvCallName->Length,
            &NetRootName->Buffer[SrvCallName->Length/2]
        };
        if (RtlCompareUnicodeString(&NetRootPrefix, &NfsPrefix, FALSE) == 0)
            status = STATUS_SUCCESS;
    }
    return status;
}

NTSTATUS map_sec_flavor(
    IN PUNICODE_STRING sec_flavor_name,
    OUT PDWORD sec_flavor)
{
    if (RtlCompareUnicodeString(sec_flavor_name, &AUTH_SYS_NAME, FALSE) == 0)
        *sec_flavor = RPCSEC_AUTH_SYS;
    else if (RtlCompareUnicodeString(sec_flavor_name, &AUTHGSS_KRB5_NAME, FALSE) == 0)
        *sec_flavor = RPCSEC_AUTHGSS_KRB5;
    else if (RtlCompareUnicodeString(sec_flavor_name, &AUTHGSS_KRB5I_NAME, FALSE) == 0)
        *sec_flavor = RPCSEC_AUTHGSS_KRB5I;
    else if (RtlCompareUnicodeString(sec_flavor_name, &AUTHGSS_KRB5P_NAME, FALSE) == 0)
        *sec_flavor = RPCSEC_AUTHGSS_KRB5P;
    else return STATUS_INVALID_PARAMETER;
    return STATUS_SUCCESS;
}

NTSTATUS nfs41_GetLUID(
    PLUID id)
{
    NTSTATUS status = STATUS_SUCCESS;
    SECURITY_SUBJECT_CONTEXT sec_ctx;
    SECURITY_QUALITY_OF_SERVICE sec_qos;
    SECURITY_CLIENT_CONTEXT clnt_sec_ctx;

    SeCaptureSubjectContext(&sec_ctx);
    sec_qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
    sec_qos.ImpersonationLevel = SecurityIdentification;
    sec_qos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
    sec_qos.EffectiveOnly = 0;
    status = SeCreateClientSecurityFromSubjectContext(&sec_ctx, &sec_qos, 1, 
                &clnt_sec_ctx);
    if (status) {
        print_error("nfs41_GetLUID: SeCreateClientSecurityFromSubjectContext "
             "failed %x\n", status);
        goto release_sec_ctx;
    }
    status = SeQueryAuthenticationIdToken(clnt_sec_ctx.ClientToken, id);
    if (status) {
        print_error("SeQueryAuthenticationIdToken failed %x\n", status);
        goto release_clnt_sec_ctx;
    }
release_clnt_sec_ctx:
    SeDeleteClientSecurity(&clnt_sec_ctx);
release_sec_ctx:
    SeReleaseSubjectContext(&sec_ctx);

    return status;
}

NTSTATUS nfs41_get_sec_ctx(
    IN enum _SECURITY_IMPERSONATION_LEVEL level,
    OUT PSECURITY_CLIENT_CONTEXT out_ctx)
{
    NTSTATUS status;
    SECURITY_SUBJECT_CONTEXT ctx;
    SECURITY_QUALITY_OF_SERVICE sec_qos;

    SeCaptureSubjectContext(&ctx);
    sec_qos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
    sec_qos.ImpersonationLevel = level;
    sec_qos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
    sec_qos.EffectiveOnly = 0;
    status = SeCreateClientSecurityFromSubjectContext(&ctx, &sec_qos, 1, out_ctx);
    if (status != STATUS_SUCCESS) {
        print_error("SeCreateClientSecurityFromSubjectContext "
            "failed with %x\n", status);
    }
#ifdef DEBUG_MOUNT
    DbgP("Created client security token %p\n", out_ctx->ClientToken);
#endif
    SeReleaseSubjectContext(&ctx);

    return status;
}

NTSTATUS nfs41_CreateVNetRoot(
    IN OUT PMRX_CREATENETROOT_CONTEXT pCreateNetRootContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    NFS41_MOUNT_CONFIG *Config;
    __notnull PMRX_V_NET_ROOT pVNetRoot = (PMRX_V_NET_ROOT)
        pCreateNetRootContext->pVNetRoot;
    __notnull PMRX_NET_ROOT pNetRoot = pVNetRoot->pNetRoot;
    __notnull PMRX_SRV_CALL pSrvCall = pNetRoot->pSrvCall;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(pNetRoot);
    NFS41GetDeviceExtension(pCreateNetRootContext->RxContext,DevExt);
    DWORD nfs41d_version = DevExt->nfs41d_version;
    nfs41_mount_entry *existing_mount = NULL;
    LUID luid;
    BOOLEAN found_existing_mount = FALSE, found_matching_flavor = FALSE;

    ASSERT((NodeType(pNetRoot) == RDBSS_NTC_NETROOT) &&
        (NodeType(pNetRoot->pSrvCall) == RDBSS_NTC_SRVCALL));

#ifdef DEBUG_MOUNT
    DbgEn();
    print_srv_call(0, pSrvCall);
    print_net_root(0, pNetRoot);
    print_v_net_root(0, pVNetRoot);

    DbgP("pVNetRoot=%p pNetRoot=%p pSrvCall=%p\n", pVNetRoot, pNetRoot, pSrvCall);
    DbgP("pNetRoot=%wZ Type=%d pSrvCallName=%wZ VirtualNetRootStatus=0x%x "
        "NetRootStatus=0x%x\n", pNetRoot->pNetRootName, 
        pNetRoot->Type, pSrvCall->pSrvCallName, 
        pCreateNetRootContext->VirtualNetRootStatus, 
        pCreateNetRootContext->NetRootStatus);
#endif

    if (pNetRoot->Type != NET_ROOT_DISK && pNetRoot->Type != NET_ROOT_WILD) {
        print_error("nfs41_CreateVNetRoot: Unsupported NetRoot Type %u\n", 
            pNetRoot->Type);
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }

    pVNetRootContext->session = INVALID_HANDLE_VALUE;

    /* In order to cooperate with other network providers, we must
     * only claim paths of the form '\\server\nfs4\path' */
    status = has_nfs_prefix(pSrvCall->pSrvCallName, pNetRoot->pNetRootName);
    if (status) {
        print_error("nfs41_CreateVNetRoot: NetRootName %wZ doesn't match "
            "'\\nfs4'!\n", pNetRoot->pNetRootName);
        goto out;
    }
    pNetRoot->MRxNetRootState = MRX_NET_ROOT_STATE_GOOD;
    pNetRoot->DeviceType = FILE_DEVICE_DISK;

    Config = RxAllocatePoolWithTag(NonPagedPool, 
            sizeof(NFS41_MOUNT_CONFIG), NFS41_MM_POOLTAG);
    if (Config == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }
    nfs41_MountConfig_InitDefaults(Config);

    if (pCreateNetRootContext->RxContext->Create.EaLength) {
        /* parse the extended attributes for mount options */
        status = nfs41_MountConfig_ParseOptions(
            pCreateNetRootContext->RxContext->Create.EaBuffer,
            pCreateNetRootContext->RxContext->Create.EaLength,
            Config);
        if (status != STATUS_SUCCESS)
            goto out_free;
        pVNetRootContext->read_only = Config->ReadOnly;
        pVNetRootContext->write_thru = Config->write_thru;
        pVNetRootContext->nocache = Config->nocache;        
    } else {
        /* use the SRV_CALL name (without leading \) as the hostname */
        Config->SrvName.Buffer = pSrvCall->pSrvCallName->Buffer + 1;
        Config->SrvName.Length =
            pSrvCall->pSrvCallName->Length - sizeof(WCHAR);
        Config->SrvName.MaximumLength =
            pSrvCall->pSrvCallName->MaximumLength - sizeof(WCHAR);
    }
    pVNetRootContext->MountPathLen = Config->MntPt.Length;
    pVNetRootContext->timeout = Config->timeout;

    status = map_sec_flavor(&Config->SecFlavor, &pVNetRootContext->sec_flavor);
    if (status != STATUS_SUCCESS) {
        DbgP("Invalid rpcsec security flavor %wZ\n", &Config->SecFlavor);
        goto out_free;
    }

    status = nfs41_GetLUID(&luid);
    if (status)
        goto out_free;

    if (!pNetRootContext->mounts_init) {
#ifdef DEBUG_MOUNT
        DbgP("Initializing mount array\n");
#endif
        ExInitializeFastMutex(&pNetRootContext->mountLock);
        InitializeListHead(&pNetRootContext->mounts.head);
        pNetRootContext->mounts_init = TRUE;
    } else {
        PLIST_ENTRY pEntry;

        ExAcquireFastMutex(&pNetRootContext->mountLock); 
        pEntry = &pNetRootContext->mounts.head;
        pEntry = pEntry->Flink;
        while (pEntry != NULL) {
            existing_mount = (nfs41_mount_entry *)CONTAINING_RECORD(pEntry, 
                    nfs41_mount_entry, next);
#ifdef DEBUG_MOUNT
            DbgP("comparing %x.%x with %x.%x\n", luid.HighPart, luid.LowPart,
                existing_mount->login_id.HighPart, existing_mount->login_id.LowPart);
#endif
            if (RtlEqualLuid(&luid, &existing_mount->login_id)) {
#ifdef DEBUG_MOUNT
                DbgP("Found a matching LUID entry\n");
#endif
                found_existing_mount = TRUE;
                switch(pVNetRootContext->sec_flavor) {
                case RPCSEC_AUTH_SYS:
                    if (existing_mount->authsys_session != INVALID_HANDLE_VALUE)
                        pVNetRootContext->session = 
                            existing_mount->authsys_session;
                    break;
                case RPCSEC_AUTHGSS_KRB5:
                    if (existing_mount->gssi_session != INVALID_HANDLE_VALUE)
                        pVNetRootContext->session = existing_mount->gss_session;
                    break;
                case RPCSEC_AUTHGSS_KRB5I:
                    if (existing_mount->gss_session != INVALID_HANDLE_VALUE)
                        pVNetRootContext->session = existing_mount->gssi_session;
                    break;
                case RPCSEC_AUTHGSS_KRB5P:
                    if (existing_mount->gssp_session != INVALID_HANDLE_VALUE)
                        pVNetRootContext->session = existing_mount->gssp_session;
                    break;
                }
                if (pVNetRootContext->session && 
                        pVNetRootContext->session != INVALID_HANDLE_VALUE)
                    found_matching_flavor = 1;
                break;
            }
            if (pEntry->Flink == &pNetRootContext->mounts.head)
                break;
            pEntry = pEntry->Flink;
        }
        ExReleaseFastMutex(&pNetRootContext->mountLock);
#ifdef DEBUG_MOUNT
        if (!found_matching_flavor)
            DbgP("Didn't find matching security flavor\n");
#endif
    }

    /* send the mount upcall */
    status = nfs41_mount(Config, pVNetRootContext->sec_flavor,
        &pVNetRootContext->session, &nfs41d_version,
        &pVNetRootContext->FsAttrs);
    if (status != STATUS_SUCCESS) {
        BOOLEAN MountsEmpty;
        nfs41_IsListEmpty(pNetRootContext->mountLock,
            pNetRootContext->mounts, MountsEmpty);
        if (!found_existing_mount && MountsEmpty)
            pNetRootContext->mounts_init = FALSE;
        pVNetRootContext->session = INVALID_HANDLE_VALUE;
        goto out_free;
    }
    pVNetRootContext->timeout = Config->timeout;

    if (!found_existing_mount) {
        /* create a new mount entry and add it to the list */
        nfs41_mount_entry *entry;
        entry = RxAllocatePoolWithTag(NonPagedPool, sizeof(nfs41_mount_entry), 
            NFS41_MM_POOLTAG_MOUNT);
        if (entry == NULL) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            goto out_free;
        }
        entry->authsys_session = entry->gss_session = 
            entry->gssi_session = entry->gssp_session = INVALID_HANDLE_VALUE;
        switch (pVNetRootContext->sec_flavor) {
        case RPCSEC_AUTH_SYS:
            entry->authsys_session = pVNetRootContext->session; break;
        case RPCSEC_AUTHGSS_KRB5:
            entry->gss_session = pVNetRootContext->session; break;
        case RPCSEC_AUTHGSS_KRB5I:
            entry->gssi_session = pVNetRootContext->session; break;
        case RPCSEC_AUTHGSS_KRB5P:
            entry->gssp_session = pVNetRootContext->session; break;
        }
        RtlCopyLuid(&entry->login_id, &luid);
        nfs41_AddEntry(pNetRootContext->mountLock,
            pNetRootContext->mounts, entry);
    } else if (!found_matching_flavor) {
        ASSERT(existing_mount != NULL);
        /* modify existing mount entry */
#ifdef DEBUG_MOUNT
        DbgP("Using existing %d flavor session 0x%x\n", 
            pVNetRootContext->sec_flavor);
#endif
        switch (pVNetRootContext->sec_flavor) {
        case RPCSEC_AUTH_SYS:
            existing_mount->authsys_session = pVNetRootContext->session; break;
        case RPCSEC_AUTHGSS_KRB5:
            existing_mount->gss_session = pVNetRootContext->session; break;
        case RPCSEC_AUTHGSS_KRB5I:
            existing_mount->gssi_session = pVNetRootContext->session; break;
        case RPCSEC_AUTHGSS_KRB5P:
            existing_mount->gssp_session = pVNetRootContext->session; break;
        }
    }
    pNetRootContext->nfs41d_version = nfs41d_version;
#ifdef DEBUG_MOUNT
    DbgP("Saving new session 0x%x\n", pVNetRootContext->session);
#endif
#ifdef STORE_MOUNT_SEC_CONTEXT
    status = nfs41_get_sec_ctx(SecurityImpersonation, 
        &pVNetRootContext->mount_sec_ctx);
#endif

out_free:
    RxFreePool(Config);
out:
    pCreateNetRootContext->VirtualNetRootStatus = status;
    if (pNetRoot->Context == NULL)
        pCreateNetRootContext->NetRootStatus = status;
    pCreateNetRootContext->Callback(pCreateNetRootContext);

    /* RDBSS expects that MRxCreateVNetRoot returns STATUS_PENDING 
     * on success or failure */
    status = STATUS_PENDING;
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

VOID nfs41_ExtractNetRootName(
    IN PUNICODE_STRING FilePathName,
    IN PMRX_SRV_CALL SrvCall,
    OUT PUNICODE_STRING NetRootName,
    OUT PUNICODE_STRING RestOfName OPTIONAL)
{
    ULONG length = FilePathName->Length;
    PWCH w = FilePathName->Buffer;
    PWCH wlimit = (PWCH)(((PCHAR)w)+length);
    PWCH wlow;

    w += (SrvCall->pSrvCallName->Length/sizeof(WCHAR));
    NetRootName->Buffer = wlow = w;
    /* parse the entire path into NetRootName */
#if USE_ENTIRE_PATH
    w = wlimit;
#else
    for (;;) {
        if (w >= wlimit)
            break;
        if ((*w == OBJ_NAME_PATH_SEPARATOR) && (w != wlow))
            break;
        w++;
    }
#endif
    NetRootName->Length = NetRootName->MaximumLength
                = (USHORT)((PCHAR)w - (PCHAR)wlow);
#ifdef DEBUG_MOUNT
    DbgP("In: pSrvCall %p PathName=%wZ SrvCallName=%wZ Out: NetRootName=%wZ\n", 
        SrvCall, FilePathName, SrvCall->pSrvCallName, NetRootName);
#endif
    return;

}

NTSTATUS nfs41_FinalizeSrvCall(
    PMRX_SRV_CALL pSrvCall,
    BOOLEAN Force)
{
    NTSTATUS status = STATUS_SUCCESS;
    PNFS41_SERVER_ENTRY pServerEntry = (PNFS41_SERVER_ENTRY)(pSrvCall->Context);

#ifdef DEBUG_MOUNT
    DbgEn();
#endif
    print_srv_call(0, pSrvCall);

    if (pSrvCall->Context == NULL)
        goto out;

    InterlockedCompareExchangePointer(&pServerEntry->pRdbssSrvCall, 
        NULL, pSrvCall);
    RxFreePool(pServerEntry);

    pSrvCall->Context = NULL;
out:
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_FinalizeNetRoot(
    IN OUT PMRX_NET_ROOT pNetRoot,
    IN PBOOLEAN ForceDisconnect)
{
    NTSTATUS status = STATUS_SUCCESS;
    PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension((PMRX_NET_ROOT)pNetRoot);
    nfs41_updowncall_entry *tmp;
    nfs41_mount_entry *mount_tmp;
    
#ifdef DEBUG_MOUNT
    DbgEn();
    print_net_root(1, pNetRoot);
#endif

    if (pNetRoot->Type != NET_ROOT_DISK && pNetRoot->Type != NET_ROOT_WILD) {
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }

    if (pNetRootContext == NULL || !pNetRootContext->mounts_init) {
        print_error("nfs41_FinalizeNetRoot: No valid session established\n");
        goto out;
    }

    if (pNetRoot->NumberOfFcbs > 0 || pNetRoot->NumberOfSrvOpens > 0) {
        print_error("%d open Fcbs %d open SrvOpens\n", pNetRoot->NumberOfFcbs, 
            pNetRoot->NumberOfSrvOpens);
        goto out;
    }

    do {
        nfs41_GetFirstMountEntry(pNetRootContext->mountLock,
            pNetRootContext->mounts, mount_tmp);
        if (mount_tmp == NULL)
            break;
#ifdef DEBUG_MOUNT
        DbgP("Removing entry luid %x.%x from mount list\n", 
            mount_tmp->login_id.HighPart, mount_tmp->login_id.LowPart);
#endif
        if (mount_tmp->authsys_session != INVALID_HANDLE_VALUE) {
            status = nfs41_unmount(mount_tmp->authsys_session, 
                pNetRootContext->nfs41d_version, UPCALL_TIMEOUT_DEFAULT);
            if (status)
                print_error("nfs41_unmount AUTH_SYS failed with %d\n", status);
        }
        if (mount_tmp->gss_session != INVALID_HANDLE_VALUE) {
            status = nfs41_unmount(mount_tmp->gss_session, 
                pNetRootContext->nfs41d_version, UPCALL_TIMEOUT_DEFAULT);
            if (status)
                print_error("nfs41_unmount RPCSEC_GSS_KRB5 failed with %d\n", 
                            status);
        }
        if (mount_tmp->gssi_session != INVALID_HANDLE_VALUE) {
            status = nfs41_unmount(mount_tmp->gssi_session, 
                pNetRootContext->nfs41d_version, UPCALL_TIMEOUT_DEFAULT);
            if (status)
                print_error("nfs41_unmount RPCSEC_GSS_KRB5I failed with %d\n", 
                            status);
        }
        if (mount_tmp->gssp_session != INVALID_HANDLE_VALUE) {
            status = nfs41_unmount(mount_tmp->gssp_session, 
                pNetRootContext->nfs41d_version, UPCALL_TIMEOUT_DEFAULT);
            if (status)
                print_error("nfs41_unmount RPCSEC_GSS_KRB5P failed with %d\n", 
                            status);
        }
        nfs41_RemoveEntry(pNetRootContext->mountLock, mount_tmp);
        RxFreePool(mount_tmp);
    } while (1);
    /* ignore any errors from unmount */
    status = STATUS_SUCCESS;

    // check if there is anything waiting in the upcall or downcall queue
    do {
        nfs41_GetFirstEntry(upcallLock, upcall, tmp);
        if (tmp != NULL) {
            DbgP("Removing entry from upcall list\n");
            nfs41_RemoveEntry(upcallLock, tmp);
            tmp->status = STATUS_INSUFFICIENT_RESOURCES;
            KeSetEvent(&tmp->cond, 0, FALSE);
        } else
            break;
    } while (1);

    do {
        nfs41_GetFirstEntry(downcallLock, downcall, tmp);
        if (tmp != NULL) {
            DbgP("Removing entry from downcall list\n");
            nfs41_RemoveEntry(downcallLock, tmp);
            tmp->status = STATUS_INSUFFICIENT_RESOURCES;
            KeSetEvent(&tmp->cond, 0, FALSE);
        } else
            break;
    } while (1);
out:
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}


NTSTATUS nfs41_FinalizeVNetRoot(
    IN OUT PMRX_V_NET_ROOT pVNetRoot,
    IN PBOOLEAN ForceDisconnect)
{
    NTSTATUS status = STATUS_SUCCESS;
    PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(pVNetRoot);
#ifdef DEBUG_MOUNT
    DbgEn();
    print_v_net_root(1, pVNetRoot);
#endif
    if (pVNetRoot->pNetRoot->Type != NET_ROOT_DISK && 
            pVNetRoot->pNetRoot->Type != NET_ROOT_WILD)
        status = STATUS_NOT_SUPPORTED;
#ifdef STORE_MOUNT_SEC_CONTEXT
    else if (pVNetRootContext->session != INVALID_HANDLE_VALUE) {
#ifdef DEBUG_MOUNT
        DbgP("nfs41_FinalizeVNetRoot: deleting security context: %p\n",
            pVNetRootContext->mount_sec_ctx.ClientToken);
#endif
        SeDeleteClientSecurity(&pVNetRootContext->mount_sec_ctx);
    }
#endif
#ifdef DEBUG_MOUNT
    DbgEx();
#endif
    return status;
}

BOOLEAN isDataAccess(
    ACCESS_MASK mask) 
{
    if (mask & (FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA))
        return TRUE;
    return FALSE;
}

BOOLEAN isOpen2Create(
    ULONG disposition)
{
    if (disposition == FILE_CREATE || disposition == FILE_OPEN_IF ||
            disposition == FILE_OVERWRITE_IF || disposition == FILE_SUPERSEDE)
        return TRUE;
    return FALSE;
}

BOOLEAN isFilenameTooLong(
    PUNICODE_STRING name, 
    PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext)
{
    PFILE_FS_ATTRIBUTE_INFORMATION attrs = &pVNetRootContext->FsAttrs;
    LONG len = attrs->MaximumComponentNameLength, count = 1, i;
    PWCH p = name->Buffer;
    for (i = 0; i < name->Length / 2; i++) {
        if (p[0] == L'\\') count = 1;
        else { 
            if (p[0] == L'\0') return FALSE;
            if (count > len) return TRUE;
            count++;
        }
        p++;
    }
    return FALSE;
}

BOOLEAN isStream(
    PUNICODE_STRING name)
{
    LONG i;
    PWCH p = name->Buffer;
    for (i = 0; i < name->Length / 2; i++) {
        if (p[0] == L':') return TRUE;
        else if (p[0] == L'\0') return FALSE;
        p++;
    }
    return FALSE;
}

BOOLEAN areOpenParamsValid(NT_CREATE_PARAMETERS *params)
{
    /* from ms-fsa page 52 */
    if ((params->CreateOptions & FILE_DELETE_ON_CLOSE) && 
            !(params->DesiredAccess & DELETE))
        return FALSE;
    if ((params->CreateOptions & FILE_DIRECTORY_FILE) &&
            (params->Disposition == FILE_SUPERSEDE || 
                params->Disposition == FILE_OVERWRITE ||
                params->Disposition == FILE_OVERWRITE_IF))
        return FALSE;
    if ((params->CreateOptions & FILE_NO_INTERMEDIATE_BUFFERING) &&
            (params->DesiredAccess & FILE_APPEND_DATA) &&
            !(params->DesiredAccess & FILE_WRITE_DATA))
        return FALSE;
    /* from ms-fsa 3.1.5.1.1 page 56 */
    if ((params->CreateOptions & FILE_DIRECTORY_FILE) &&
            (params->FileAttributes & FILE_ATTRIBUTE_TEMPORARY))
        return FALSE;
    return TRUE;
}

NTSTATUS map_open_errors(
    DWORD status, 
    USHORT len)
{
    switch (status) {
    case NO_ERROR:                      return STATUS_SUCCESS;
    case ERROR_ACCESS_DENIED:
        if (len > 0)                    return STATUS_ACCESS_DENIED;
        else                            return STATUS_SUCCESS;
    case ERROR_INVALID_REPARSE_DATA:
    case ERROR_INVALID_NAME:            return STATUS_OBJECT_NAME_INVALID;
    case ERROR_FILE_EXISTS:             return STATUS_OBJECT_NAME_COLLISION;
    case ERROR_FILE_INVALID:            return STATUS_FILE_INVALID;
    case ERROR_FILE_NOT_FOUND:          return STATUS_OBJECT_NAME_NOT_FOUND;
    case ERROR_FILENAME_EXCED_RANGE:    return STATUS_NAME_TOO_LONG;
    case ERROR_NETWORK_ACCESS_DENIED:   return STATUS_NETWORK_ACCESS_DENIED;
    case ERROR_PATH_NOT_FOUND:          return STATUS_OBJECT_PATH_NOT_FOUND;
    case ERROR_BAD_NETPATH:             return STATUS_BAD_NETWORK_PATH;
    case ERROR_SHARING_VIOLATION:       return STATUS_SHARING_VIOLATION;
    case ERROR_REPARSE:                 return STATUS_REPARSE;
    case ERROR_TOO_MANY_LINKS:          return STATUS_TOO_MANY_LINKS;
    case ERROR_DIRECTORY:               return STATUS_FILE_IS_A_DIRECTORY;
    case ERROR_BAD_FILE_TYPE:           return STATUS_NOT_A_DIRECTORY;
    default:
        print_error("[ERROR] nfs41_Create: upcall returned %d returning "
            "STATUS_INSUFFICIENT_RESOURCES\n", status);
    case ERROR_OUTOFMEMORY:             return STATUS_INSUFFICIENT_RESOURCES;
    }
}

DWORD map_disposition_to_create_retval(
    DWORD disposition, 
    DWORD errno)
{
    switch(disposition) {
    case FILE_SUPERSEDE:
        if (errno == ERROR_FILE_NOT_FOUND)  return FILE_CREATED;
        else                                return FILE_SUPERSEDED;
    case FILE_CREATE:                       return FILE_CREATED;
    case FILE_OPEN:                         return FILE_OPENED;
    case FILE_OPEN_IF:
        if (errno == ERROR_FILE_NOT_FOUND)  return FILE_CREATED;
        else                                return FILE_OPENED;
    case FILE_OVERWRITE:                    return FILE_OVERWRITTEN;
    case FILE_OVERWRITE_IF:
        if (errno == ERROR_FILE_NOT_FOUND)  return FILE_CREATED;
        else                                return FILE_OVERWRITTEN;
    default:
        print_error("unknown disposition %d\n", disposition);
        return FILE_OPENED;
    }
}

static BOOLEAN create_should_pass_ea(
    IN PFILE_FULL_EA_INFORMATION ea,
    IN ULONG disposition)
{
    /* don't pass cygwin EAs */
    if (AnsiStrEq(&NfsV3Attributes, ea->EaName, ea->EaNameLength)
        || AnsiStrEq(&NfsActOnLink, ea->EaName, ea->EaNameLength)
        || AnsiStrEq(&NfsSymlinkTargetName, ea->EaName, ea->EaNameLength))
        return FALSE;
    /* only set EAs on file creation */
    return disposition == FILE_SUPERSEDE || disposition == FILE_CREATE
        || disposition == FILE_OPEN_IF || disposition == FILE_OVERWRITE
        || disposition == FILE_OVERWRITE_IF;
}

NTSTATUS check_nfs41_create_args(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    PNT_CREATE_PARAMETERS params = &RxContext->Create.NtCreateParameters;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PFILE_FS_ATTRIBUTE_INFORMATION FsAttrs =
        &pVNetRootContext->FsAttrs;
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PMRX_FCB Fcb = RxContext->pFcb;
    __notnull PNFS41_FCB nfs41_fcb = (PNFS41_FCB)Fcb->Context;
    PFILE_FULL_EA_INFORMATION ea = (PFILE_FULL_EA_INFORMATION)
        RxContext->CurrentIrp->AssociatedIrp.SystemBuffer;

    if (Fcb->pNetRoot->Type != NET_ROOT_DISK && 
            Fcb->pNetRoot->Type != NET_ROOT_WILD) {
        print_error("nfs41_Create: Unsupported NetRoot Type %u\n", 
            Fcb->pNetRoot->Type);
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }

    if (FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
        print_error("FCB_STATE_PAGING_FILE not implemented\n");
        status = STATUS_NOT_IMPLEMENTED;
        goto out;
    }
    
    if (!pNetRootContext->mounts_init) {
        print_error("nfs41_Create: No valid session established\n");
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out;
    }

    if (isStream(SrvOpen->pAlreadyPrefixedName)) {
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }

    if (pVNetRootContext->read_only && 
            (params->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
        status = STATUS_NETWORK_ACCESS_DENIED;
        goto out;
    }

    /* if FCB was marked for deletion and opened multiple times, as soon 
     * as first close happen, FCB transitions into delete_pending state 
     * no more opens allowed
     */
    if (Fcb->OpenCount && nfs41_fcb->DeletePending) {
        status = STATUS_DELETE_PENDING;
        goto out;
    }

    /* ms-fsa: 3.1.5.1.2.1 page 68 */
    if (Fcb->OpenCount && nfs41_fcb->StandardInfo.DeletePending &&
            !(params->ShareAccess & FILE_SHARE_DELETE) && 
                (params->DesiredAccess & (FILE_EXECUTE | FILE_READ_DATA |
                    FILE_WRITE_DATA | FILE_APPEND_DATA))) {
        status = STATUS_SHARING_VIOLATION;
        goto out;
    }

    /* rdbss seems miss this sharing_violation check */
    if (Fcb->OpenCount && params->Disposition == FILE_SUPERSEDE) {
        if ((!RxContext->CurrentIrpSp->FileObject->SharedRead && 
                (params->DesiredAccess & FILE_READ_DATA)) ||
            (!RxContext->CurrentIrpSp->FileObject->SharedWrite &&
                (params->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA | 
                    FILE_WRITE_ATTRIBUTES)) ||
            (!RxContext->CurrentIrpSp->FileObject->SharedDelete &&
                (params->DesiredAccess & DELETE)))) {
            status = STATUS_SHARING_VIOLATION;
            goto out;
        }
    }
    if (isFilenameTooLong(SrvOpen->pAlreadyPrefixedName, pVNetRootContext)) {
        status = STATUS_OBJECT_NAME_INVALID;
        goto out;
    }

    if (!areOpenParamsValid(params)) {
        status = STATUS_INVALID_PARAMETER;
        goto out;
    }

    /* from ms-fsa 3.1.5.1.1 page 56 */
    if ((params->CreateOptions & FILE_DELETE_ON_CLOSE) &&
            (params->FileAttributes & FILE_ATTRIBUTE_READONLY)) {
        status = STATUS_CANNOT_DELETE;
        goto out;
    }

    if (ea) {
        /* ignore cygwin EAs when checking support and access */
        if (!AnsiStrEq(&NfsV3Attributes, ea->EaName, ea->EaNameLength) &&
            !AnsiStrEq(&NfsActOnLink, ea->EaName, ea->EaNameLength) &&
            !AnsiStrEq(&NfsSymlinkTargetName, ea->EaName, ea->EaNameLength)) {
            if (!(FsAttrs->FileSystemAttributes & FILE_SUPPORTS_EXTENDED_ATTRIBUTES)) {
                status = STATUS_EAS_NOT_SUPPORTED;
                goto out;
            }
        }
    } else if (RxContext->CurrentIrpSp->Parameters.Create.EaLength) {
        status = STATUS_INVALID_PARAMETER;
        goto out;
    }

out:
    return status;
}

NTSTATUS nfs41_Create(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
    nfs41_updowncall_entry *entry = NULL;
    PNT_CREATE_PARAMETERS params = &RxContext->Create.NtCreateParameters;
    PFILE_FULL_EA_INFORMATION ea = (PFILE_FULL_EA_INFORMATION)
        RxContext->CurrentIrp->AssociatedIrp.SystemBuffer;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PMRX_FCB Fcb = RxContext->pFcb;
    __notnull PNFS41_FCB nfs41_fcb = (PNFS41_FCB)Fcb->Context;
    PNFS41_FOBX nfs41_fobx = NULL;
    BOOLEAN oldDeletePending = nfs41_fcb->StandardInfo.DeletePending;
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

    ASSERT( NodeType(SrvOpen) == RDBSS_NTC_SRVOPEN );

#ifdef DEBUG_OPEN
    DbgEn();
    print_debug_header(RxContext);
    print_nt_create_params(1, RxContext->Create.NtCreateParameters);
    if (ea) print_ea_info(0, ea);
#endif

    status = check_nfs41_create_args(RxContext);
    if (status) goto out;

#if defined(STORE_MOUNT_SEC_CONTEXT) && defined (USE_MOUNT_SEC_CONTEXT)
    status = nfs41_UpcallCreate(NFS41_OPEN, &pVNetRootContext->mount_sec_ctx,
#else
    status = nfs41_UpcallCreate(NFS41_OPEN, NULL,
#endif
        pVNetRootContext->session, INVALID_HANDLE_VALUE, 
        pNetRootContext->nfs41d_version, 
        SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Open.access_mask = params->DesiredAccess;
    entry->u.Open.access_mode = params->ShareAccess;
    entry->u.Open.attrs = params->FileAttributes;
    if (!(params->CreateOptions & FILE_DIRECTORY_FILE))
        entry->u.Open.attrs |= FILE_ATTRIBUTE_ARCHIVE;
    entry->u.Open.disp = params->Disposition;
    entry->u.Open.copts = params->CreateOptions;
    entry->u.Open.srv_open = SrvOpen;
    /* treat the NfsActOnLink ea as FILE_OPEN_REPARSE_POINT */
    if ((ea && AnsiStrEq(&NfsActOnLink, ea->EaName, ea->EaNameLength)) ||
            (entry->u.Open.access_mask & DELETE))
        entry->u.Open.copts |= FILE_OPEN_REPARSE_POINT;
    if (isDataAccess(params->DesiredAccess) || isOpen2Create(params->Disposition))
        entry->u.Open.open_owner_id = InterlockedIncrement(&open_owner_id);
    // if we are creating a file check if nfsv3attributes were passed in
    if (params->Disposition != FILE_OPEN && params->Disposition != FILE_OVERWRITE) {
        entry->u.Open.mode = 0777;
        if (ea && AnsiStrEq(&NfsV3Attributes, ea->EaName, ea->EaNameLength)) {
            nfs3_attrs *attrs = (nfs3_attrs *)(ea->EaName + ea->EaNameLength + 1);
#ifdef DEBUG_OPEN
            DbgP("creating file with mode %o\n", attrs->mode);
#endif
            entry->u.Open.mode = attrs->mode;
        }
        if (params->FileAttributes & FILE_ATTRIBUTE_READONLY)
            entry->u.Open.mode = 0444;
    }
    if (entry->u.Open.disp == FILE_CREATE && ea &&
            AnsiStrEq(&NfsSymlinkTargetName, ea->EaName, ea->EaNameLength)) {
        /* for a cygwin symlink, given as a unicode string */
        entry->u.Open.symlink.Buffer = (PWCH)(ea->EaName + ea->EaNameLength + 1);
        entry->u.Open.symlink.MaximumLength = entry->u.Open.symlink.Length = ea->EaValueLength;
    }
retry_on_link:
    if (ea && create_should_pass_ea(ea, params->Disposition)) {
        /* lock the extended attribute buffer for read access in user space */
        entry->u.Open.EaMdl = IoAllocateMdl(ea,
            RxContext->CurrentIrpSp->Parameters.Create.EaLength,
            FALSE, FALSE, NULL);
        if (entry->u.Open.EaMdl == NULL) {
            status = STATUS_INTERNAL_ERROR;
            RxFreePool(entry);
            goto out;
        }
        entry->u.Open.EaMdl->MdlFlags |= MDL_MAPPING_CAN_FAIL;
        MmProbeAndLockPages(entry->u.Open.EaMdl, KernelMode, IoModifyAccess);
    }

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
#ifndef USE_MOUNT_SEC_CONTEXT
    SeDeleteClientSecurity(&entry->sec_ctx);
#endif
    if (status) goto out;

    if (entry->u.Open.EaMdl) {
        MmUnlockPages(entry->u.Open.EaMdl);
        IoFreeMdl(entry->u.Open.EaMdl);
    }

    if (entry->status == NO_ERROR && entry->errno == ERROR_REPARSE) {
        /* symbolic link handling. when attempting to open a symlink when the
         * FILE_OPEN_REPARSE_POINT flag is not set, replace the filename with
         * the symlink target's by calling RxPrepareToReparseSymbolicLink()
         * and returning STATUS_REPARSE. the object manager will attempt to
         * open the new path, and return its handle for the original open */
        PRDBSS_DEVICE_OBJECT DeviceObject = RxContext->RxDeviceObject;
        PV_NET_ROOT VNetRoot = (PV_NET_ROOT)
            RxContext->pRelevantSrvOpen->pVNetRoot;
        PUNICODE_STRING VNetRootPrefix = &VNetRoot->PrefixEntry.Prefix;
        UNICODE_STRING AbsPath;
        PCHAR buf;
        BOOLEAN ReparseRequired;

        /* allocate the string for RxPrepareToReparseSymbolicLink(), and
         * format an absolute path "DeviceName+VNetRootName+symlink" */
        AbsPath.Length = DeviceObject->DeviceName.Length +
            VNetRootPrefix->Length + entry->u.Open.symlink.Length;
        AbsPath.MaximumLength = AbsPath.Length + sizeof(UNICODE_NULL);
        AbsPath.Buffer = RxAllocatePoolWithTag(NonPagedPool,
            AbsPath.MaximumLength, NFS41_MM_POOLTAG);
        if (AbsPath.Buffer == NULL) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            goto out_free;
        }

        buf = (PCHAR)AbsPath.Buffer;
        RtlCopyMemory(buf, DeviceObject->DeviceName.Buffer, 
            DeviceObject->DeviceName.Length);
        buf += DeviceObject->DeviceName.Length;
        RtlCopyMemory(buf, VNetRootPrefix->Buffer, VNetRootPrefix->Length);
        buf += VNetRootPrefix->Length;
        RtlCopyMemory(buf, entry->u.Open.symlink.Buffer, 
            entry->u.Open.symlink.Length);
        RxFreePool(entry->u.Open.symlink.Buffer);
        buf += entry->u.Open.symlink.Length;
        *(PWCHAR)buf = UNICODE_NULL;

        status = RxPrepareToReparseSymbolicLink(RxContext,
            entry->u.Open.symlink_embedded, &AbsPath, TRUE, &ReparseRequired);
#ifdef DEBUG_OPEN 
        DbgP("RxPrepareToReparseSymbolicLink(%u, '%wZ') returned %08lX, "
            "FileName is '%wZ'\n", entry->u.Open.symlink_embedded,
            &AbsPath, status, &RxContext->CurrentIrpSp->FileObject->FileName);
#endif
        if (status == STATUS_SUCCESS) {
            /* if a reparse is not required, reopen the link itself.  this
             * happens with operations on cygwin symlinks, where the reparse
             * flag is not set */
            if (!ReparseRequired) {
                entry->u.Open.symlink.Length = 0;
                entry->u.Open.copts |= FILE_OPEN_REPARSE_POINT;
                goto retry_on_link;
            }
            status = STATUS_REPARSE;
        }
        goto out_free;
    }

    status = map_open_errors(entry->status, 
                SrvOpen->pAlreadyPrefixedName->Length);
    if (status) {
#ifdef DEBUG_OPEN 
        print_open_error(1, status);
#endif
        goto out_free;
    }

    if (!RxIsFcbAcquiredExclusive(Fcb)) {
        ASSERT(!RxIsFcbAcquiredShared(Fcb));
        RxAcquireExclusiveFcbResourceInMRx(Fcb);
    }

    RxContext->pFobx = RxCreateNetFobx(RxContext, SrvOpen);
    if (RxContext->pFobx == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto out_free;
    }
#ifdef DEBUG_OPEN
    DbgP("nfs41_Create: created FOBX %p\n", RxContext->pFobx);
#endif
    nfs41_fobx = (PNFS41_FOBX)(RxContext->pFobx)->Context;
    nfs41_fobx->nfs41_open_state = entry->open_state;
#ifndef USE_MOUNT_SEC_CONTEXT
    status = nfs41_get_sec_ctx(SecurityImpersonation, &nfs41_fobx->sec_ctx);
    if (status)
        goto out_free;
#else
    RtlCopyMemory(&nfs41_fobx->sec_ctx, &pVNetRootContext->mount_sec_ctx,
        sizeof(nfs41_fobx->sec_ctx));
#endif

    // we get attributes only for data access and file (not directories)
    if (Fcb->OpenCount == 0 || 
            (Fcb->OpenCount > 0 && 
                nfs41_fcb->changeattr != entry->ChangeTime)) {
        FCB_INIT_PACKET InitPacket;
        RX_FILE_TYPE StorageType = FileTypeNotYetKnown;
        RtlCopyMemory(&nfs41_fcb->BasicInfo, &entry->u.Open.binfo, 
            sizeof(entry->u.Open.binfo));
        RtlCopyMemory(&nfs41_fcb->StandardInfo, &entry->u.Open.sinfo, 
            sizeof(entry->u.Open.sinfo));
        nfs41_fcb->mode = entry->u.Open.mode;
        nfs41_fcb->changeattr = entry->ChangeTime;
        if (((params->CreateOptions & FILE_DELETE_ON_CLOSE) && 
                !pVNetRootContext->read_only) || oldDeletePending)
            nfs41_fcb->StandardInfo.DeletePending = TRUE;

        RxFormInitPacket(InitPacket,
            &entry->u.Open.binfo.FileAttributes,
            &entry->u.Open.sinfo.NumberOfLinks,
            &entry->u.Open.binfo.CreationTime,
            &entry->u.Open.binfo.LastAccessTime,
            &entry->u.Open.binfo.LastWriteTime,
            &entry->u.Open.binfo.ChangeTime,
            &entry->u.Open.sinfo.AllocationSize,
            &entry->u.Open.sinfo.EndOfFile,
            &entry->u.Open.sinfo.EndOfFile);

        if (entry->u.Open.sinfo.Directory)
            StorageType = FileTypeDirectory;
        else
            StorageType = FileTypeFile;

        RxFinishFcbInitialization(Fcb, RDBSS_STORAGE_NTC(StorageType), 
                                    &InitPacket);
    }
#ifdef DEBUG_OPEN
    else
        DbgP("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");

    print_basic_info(1, &nfs41_fcb->BasicInfo);
    print_std_info(1, &nfs41_fcb->StandardInfo);
#endif

    /* aglo: 05/10/2012. it seems like always have to invalid the cache if the
     * file has been opened before and being opened again for data access. 
     * If the file was opened before, RDBSS might have cached (unflushed) data
     * and by opening it again, we will not have the correct representation of
     * the file size and data content. fileio tests 208, 219, 221.
     */
    if (Fcb->OpenCount > 0 && (isDataAccess(params->DesiredAccess) || 
            nfs41_fcb->changeattr != entry->ChangeTime) && 
                !nfs41_fcb->StandardInfo.Directory) {
        ULONG flag = DISABLE_CACHING;
#ifdef DEBUG_OPEN
        DbgP("nfs41_Create: reopening (changed) file %wZ\n", 
            SrvOpen->pAlreadyPrefixedName);
#endif
        RxChangeBufferingState((PSRV_OPEN)SrvOpen, ULongToPtr(flag), 1);
    } 
    if (!nfs41_fcb->StandardInfo.Directory && 
            isDataAccess(params->DesiredAccess)) {
        nfs41_fobx->deleg_type = entry->u.Open.deleg_type;
#ifdef DEBUG_OPEN
        DbgP("nfs41_Create: received delegation %d\n", entry->u.Open.deleg_type);
#endif
        if (!(params->CreateOptions & FILE_WRITE_THROUGH) &&
                !pVNetRootContext->write_thru &&
                (entry->u.Open.deleg_type == 2 ||
                (params->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)))) {
#ifdef DEBUG_OPEN
            DbgP("nfs41_Create: enabling write buffering\n");
#endif
            SrvOpen->BufferingFlags |= 
                (FCB_STATE_WRITECACHING_ENABLED | 
                FCB_STATE_WRITEBUFFERING_ENABLED);
        } else if (params->CreateOptions & FILE_WRITE_THROUGH ||
                    pVNetRootContext->write_thru)
            nfs41_fobx->write_thru = TRUE;
        if (entry->u.Open.deleg_type >= 1 ||
                params->DesiredAccess & FILE_READ_DATA) {
#ifdef DEBUG_OPEN
            DbgP("nfs41_Create: enabling read buffering\n");
#endif
            SrvOpen->BufferingFlags |= 
                (FCB_STATE_READBUFFERING_ENABLED | 
                FCB_STATE_READCACHING_ENABLED);
        }
        if (pVNetRootContext->nocache || 
                (params->CreateOptions & FILE_NO_INTERMEDIATE_BUFFERING)) {
#ifdef DEBUG_OPEN
            DbgP("nfs41_Create: disabling buffering\n");
#endif
            SrvOpen->BufferingFlags = FCB_STATE_DISABLE_LOCAL_BUFFERING;
            nfs41_fobx->nocache = TRUE;
        } else if (!entry->u.Open.deleg_type && !Fcb->OpenCount) {
            nfs41_fcb_list_entry *oentry;
#ifdef DEBUG_OPEN
            DbgP("nfs41_Create: received no delegations: srv_open=%p "
                "ctime=%llu\n", SrvOpen, entry->ChangeTime);
#endif
            oentry = RxAllocatePoolWithTag(NonPagedPool, 
                sizeof(nfs41_fcb_list_entry), NFS41_MM_POOLTAG_OPEN);
            if (oentry == NULL) {
                status = STATUS_INSUFFICIENT_RESOURCES;
                goto out_free;
            }
            oentry->fcb = RxContext->pFcb;
            oentry->nfs41_fobx = nfs41_fobx;
            oentry->session = pVNetRootContext->session;
            oentry->ChangeTime = entry->ChangeTime;
            oentry->skip = FALSE;
            nfs41_AddEntry(fcblistLock, openlist, oentry);
        }
    }

    if ((params->CreateOptions & FILE_DELETE_ON_CLOSE) && 
            !pVNetRootContext->read_only)
        nfs41_fcb->StandardInfo.DeletePending = TRUE;

    RxContext->Create.ReturnedCreateInformation = 
        map_disposition_to_create_retval(params->Disposition, entry->errno);

    RxContext->pFobx->OffsetOfNextEaToReturn = 1;
    RxContext->CurrentIrp->IoStatus.Information = 
        RxContext->Create.ReturnedCreateInformation;
    status = RxContext->CurrentIrp->IoStatus.Status = STATUS_SUCCESS;

out_free:
    if (entry)
        RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    if ((params->DesiredAccess & FILE_READ_DATA) ||
            (params->DesiredAccess & FILE_WRITE_DATA) ||
            (params->DesiredAccess & FILE_APPEND_DATA) ||
            (params->DesiredAccess & FILE_EXECUTE)) {
        InterlockedIncrement(&open.tops); 
        InterlockedAdd64(&open.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_Create open delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, open.tops, open.ticks);
#endif
    } else {
        InterlockedIncrement(&lookup.tops); 
        InterlockedAdd64(&lookup.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_Create lookup delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, lookup.tops, lookup.ticks);
#endif
    }
#endif
#ifdef DEBUG_OPEN
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_CollapseOpen(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_MORE_PROCESSING_REQUIRED;
    DbgEn();
    DbgEx();
    return status;
}

NTSTATUS nfs41_ShouldTryToCollapseThisOpen(
    IN OUT PRX_CONTEXT RxContext)
{
    if (RxContext->pRelevantSrvOpen == NULL) 
        return STATUS_SUCCESS;
    else return STATUS_MORE_PROCESSING_REQUIRED;
}

ULONG nfs41_ExtendForCache(
    IN OUT PRX_CONTEXT RxContext,
    IN PLARGE_INTEGER pNewFileSize,
    OUT PLARGE_INTEGER pNewAllocationSize)
{
    NTSTATUS status = STATUS_SUCCESS;
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
#ifdef DEBUG_CACHE
    PLOWIO_CONTEXT LowIoContext  = &RxContext->LowIoContext;
    DbgEn();
    print_debug_header(RxContext);
    DbgP("input: byte count 0x%x filesize 0x%x alloc size 0x%x\n", 
        LowIoContext->ParamsFor.ReadWrite.ByteCount, *pNewFileSize, 
        *pNewAllocationSize);
#endif
    pNewAllocationSize->QuadPart = pNewFileSize->QuadPart + 8192;
    nfs41_fcb->StandardInfo.AllocationSize.QuadPart = 
        pNewAllocationSize->QuadPart;
    nfs41_fcb->StandardInfo.EndOfFile.QuadPart = pNewFileSize->QuadPart;
#ifdef DEBUG_CACHE
    DbgP("new filesize 0x%x new allocation size 0x%x\n", *pNewFileSize, 
        *pNewAllocationSize);
#endif
#ifdef DEBUG_CACHE
    DbgEx();
#endif
    return status;
}

VOID nfs41_remove_fcb_entry(
    PMRX_FCB fcb)
{
    PLIST_ENTRY pEntry;
    nfs41_fcb_list_entry *cur;
    ExAcquireFastMutex(&fcblistLock);

    pEntry = openlist.head.Flink;
    while (!IsListEmpty(&openlist.head)) {
        cur = (nfs41_fcb_list_entry *)CONTAINING_RECORD(pEntry, 
                nfs41_fcb_list_entry, next);
        if (cur->fcb == fcb) {
#ifdef DEBUG_CLOSE
            DbgP("nfs41_remove_srvopen_entry: Found match for fcb=%p\n", fcb);
#endif
            RemoveEntryList(pEntry);
            RxFreePool(cur);
            break;
        }
        if (pEntry->Flink == &openlist.head) {
#ifdef DEBUG_CLOSE
            DbgP("nfs41_remove_srvopen_entry: reached EOL looking for fcb "
                "%p\n", fcb);
#endif
            break;
        }
        pEntry = pEntry->Flink;
    }
    ExReleaseFastMutex(&fcblistLock);
}

NTSTATUS map_close_errors(
    DWORD status)
{
    switch (status) {
    case NO_ERROR:              return STATUS_SUCCESS;
    case ERROR_NETNAME_DELETED: return STATUS_NETWORK_NAME_DELETED;
    case ERROR_NOT_EMPTY:       return STATUS_DIRECTORY_NOT_EMPTY;
    case ERROR_FILE_INVALID:    return STATUS_FILE_INVALID;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INTERNAL_ERROR\n", status);
    case ERROR_INTERNAL_ERROR: return STATUS_INTERNAL_ERROR;
    }
}

NTSTATUS nfs41_CloseSrvOpen(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
    nfs41_updowncall_entry *entry;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_CLOSE
    DbgEn();
    print_debug_header(RxContext);
#endif

    if (!nfs41_fobx->deleg_type && !nfs41_fcb->StandardInfo.Directory &&
            !RxContext->pFcb->OpenCount) {
        nfs41_remove_fcb_entry(RxContext->pFcb);
    }

    status = nfs41_UpcallCreate(NFS41_CLOSE, &nfs41_fobx->sec_ctx,
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state, 
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Close.srv_open = SrvOpen;
    if (nfs41_fcb->StandardInfo.DeletePending)
        nfs41_fcb->DeletePending = TRUE;
    if (!RxContext->pFcb->OpenCount || 
            (nfs41_fcb->StandardInfo.DeletePending &&
                nfs41_fcb->StandardInfo.Directory))
        entry->u.Close.remove = nfs41_fcb->StandardInfo.DeletePending;
    if (!RxContext->pFcb->OpenCount)
        entry->u.Close.renamed = nfs41_fcb->Renamed;

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
#ifndef USE_MOUNT_SEC_CONTEXT
    SeDeleteClientSecurity(&nfs41_fobx->sec_ctx);
#endif
    if (status) goto out;

    /* map windows ERRORs to NTSTATUS */
    status = map_close_errors(entry->status);
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&close.tops); 
    InterlockedAdd64(&close.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_CloseSrvOpen delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, close.tops, close.ticks);
#endif
#endif
#ifdef DEBUG_CLOSE
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_Flush(
    IN OUT PRX_CONTEXT RxContext)
{
    return STATUS_SUCCESS;
}

NTSTATUS nfs41_DeallocateForFcb(
    IN OUT PMRX_FCB pFcb)
{
    return STATUS_SUCCESS;
}

NTSTATUS nfs41_DeallocateForFobx(
    IN OUT PMRX_FOBX pFobx)
{
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(pFobx);
    if (nfs41_fobx->acl)
        RxFreePool(nfs41_fobx->acl);
    return STATUS_SUCCESS;
}

void print_debug_filedirquery_header(
    PRX_CONTEXT RxContext)
{
    print_debug_header(RxContext);
    DbgP("FileName='%wZ', InfoClass = %s\n", 
        GET_ALREADY_PREFIXED_NAME_FROM_CONTEXT(RxContext), 
        print_file_information_class(RxContext->Info.FileInformationClass));
}

void print_querydir_args(
    PRX_CONTEXT RxContext)
{
    print_debug_filedirquery_header(RxContext);
    DbgP("Filter='%wZ', Index=%d, Restart/Single/Specified/Init=%d/%d/%d/%d\n",
        &RxContext->pFobx->UnicodeQueryTemplate, 
        RxContext->QueryDirectory.FileIndex,
        RxContext->QueryDirectory.RestartScan,
        RxContext->QueryDirectory.ReturnSingleEntry,
        RxContext->QueryDirectory.IndexSpecified,
        RxContext->QueryDirectory.InitialQuery);
}

NTSTATUS map_querydir_errors(
    DWORD status)
{
    switch (status) {
    case ERROR_ACCESS_DENIED:       return STATUS_ACCESS_DENIED;
    case ERROR_BUFFER_OVERFLOW:     return STATUS_BUFFER_OVERFLOW;
    case ERROR_FILE_NOT_FOUND:      return STATUS_NO_SUCH_FILE;
    case ERROR_NETNAME_DELETED:     return STATUS_NETWORK_NAME_DELETED;
    case ERROR_INVALID_PARAMETER:   return STATUS_INVALID_PARAMETER;
    case ERROR_NO_MORE_FILES:       return STATUS_NO_MORE_FILES;
    case ERROR_OUTOFMEMORY:         return STATUS_INSUFFICIENT_RESOURCES;
    case ERROR_FILENAME_EXCED_RANGE: return STATUS_NAME_TOO_LONG;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_NETWORK_RESPONSE\n", status);
    case ERROR_BAD_NET_RESP:        return STATUS_INVALID_NETWORK_RESPONSE;
    }
}

NTSTATUS check_nfs41_dirquery_args(
    IN PRX_CONTEXT RxContext)
{
    if (RxContext->Info.Buffer == NULL)
        return STATUS_INVALID_USER_BUFFER;
    return STATUS_SUCCESS;
}

NTSTATUS nfs41_QueryDirectory(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INVALID_PARAMETER;
    nfs41_updowncall_entry *entry;
    FILE_INFORMATION_CLASS InfoClass = RxContext->Info.FileInformationClass;
    PUNICODE_STRING Filter = &RxContext->pFobx->UnicodeQueryTemplate;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_DIR_QUERY
    DbgEn();
    print_querydir_args(RxContext);
#endif

    status = check_nfs41_dirquery_args(RxContext);
    if (status) goto out;

    switch (InfoClass) {
    /* classes handled in readdir_copy_entry() and readdir_size_for_entry() */
    case FileNamesInformation:
    case FileDirectoryInformation:
    case FileFullDirectoryInformation:
    case FileIdFullDirectoryInformation:
    case FileBothDirectoryInformation:
    case FileIdBothDirectoryInformation:
        break;
    default:
        print_error("nfs41_QueryDirectory: unhandled dir query class %d\n", 
            InfoClass);
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }
    status = nfs41_UpcallCreate(NFS41_DIR_QUERY, &nfs41_fobx->sec_ctx,
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.QueryFile.InfoClass = InfoClass;
    entry->buf_len = RxContext->Info.LengthRemaining;
    entry->buf = RxContext->Info.Buffer;
    entry->u.QueryFile.mdl = IoAllocateMdl(RxContext->Info.Buffer, 
        RxContext->Info.LengthRemaining, FALSE, FALSE, NULL);
    if (entry->u.QueryFile.mdl == NULL) {
        status = STATUS_INTERNAL_ERROR;
        RxFreePool(entry);
        goto out;
    }
    entry->u.QueryFile.mdl->MdlFlags |= MDL_MAPPING_CAN_FAIL;
    MmProbeAndLockPages(entry->u.QueryFile.mdl, KernelMode, IoModifyAccess);

    entry->u.QueryFile.filter = Filter;
    entry->u.QueryFile.initial_query = RxContext->QueryDirectory.InitialQuery;
    entry->u.QueryFile.restart_scan = RxContext->QueryDirectory.RestartScan;
    entry->u.QueryFile.return_single = RxContext->QueryDirectory.ReturnSingleEntry;

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;
    MmUnlockPages(entry->u.QueryFile.mdl);

    if (entry->status == STATUS_BUFFER_TOO_SMALL) {
        DbgP("nfs41_QueryDirectory: buffer too small provided %d need %lu\n", 
            RxContext->Info.LengthRemaining, entry->buf_len);
        RxContext->InformationToReturn = entry->buf_len;
        status = STATUS_BUFFER_TOO_SMALL;
    } else if (entry->status == STATUS_SUCCESS) {
#ifdef ENABLE_TIMINGS
        InterlockedIncrement(&readdir.sops); 
        InterlockedAdd64(&readdir.size, entry->u.QueryFile.buf_len);
#endif
        RxContext->Info.LengthRemaining -= entry->buf_len;
        status = STATUS_SUCCESS;
    } else {
        /* map windows ERRORs to NTSTATUS */
        status = map_querydir_errors(entry->status);
    }
    IoFreeMdl(entry->u.QueryFile.mdl);
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&readdir.tops); 
    InterlockedAdd64(&readdir.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_QueryDirectory delta = %d ops=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, readdir.tops, readdir.ticks);
#endif
#endif
#ifdef DEBUG_DIR_QUERY
    DbgEx();
#endif
    return status;
}

void print_queryvolume_args(
    PRX_CONTEXT RxContext)
{
    print_debug_header(RxContext);
    DbgP("FileName='%wZ', InfoClass = %s BufferLen = %d\n", 
        GET_ALREADY_PREFIXED_NAME_FROM_CONTEXT(RxContext), 
        print_fs_information_class(RxContext->Info.FileInformationClass), 
        RxContext->Info.LengthRemaining);
}

NTSTATUS map_volume_errors(
    DWORD status)
{
    switch (status) {
    case ERROR_ACCESS_DENIED:       return STATUS_ACCESS_DENIED;
    case ERROR_VC_DISCONNECTED:     return STATUS_CONNECTION_DISCONNECTED;
    case ERROR_NETNAME_DELETED:     return STATUS_NETWORK_NAME_DELETED;
    case ERROR_INVALID_PARAMETER:   return STATUS_INVALID_PARAMETER;
    case ERROR_OUTOFMEMORY:         return STATUS_INSUFFICIENT_RESOURCES;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_NETWORK_RESPONSE\n", status);
    case ERROR_BAD_NET_RESP:        return STATUS_INVALID_NETWORK_RESPONSE;
    }
}

void nfs41_create_volume_info(PFILE_FS_VOLUME_INFORMATION pVolInfo, DWORD *len)
{
    DECLARE_CONST_UNICODE_STRING(VolName, VOL_NAME);

    RtlZeroMemory(pVolInfo, sizeof(FILE_FS_VOLUME_INFORMATION));
    pVolInfo->VolumeSerialNumber = 0xBABAFACE;
    pVolInfo->VolumeLabelLength = VolName.Length;
    RtlCopyMemory(&pVolInfo->VolumeLabel[0], (PVOID)VolName.Buffer, 
        VolName.MaximumLength);
    *len = sizeof(FILE_FS_VOLUME_INFORMATION) + VolName.Length;
}

static BOOLEAN is_root_directory(
    PRX_CONTEXT RxContext)
{
    __notnull PV_NET_ROOT VNetRoot = (PV_NET_ROOT)
        RxContext->pRelevantSrvOpen->pVNetRoot;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(RxContext->pRelevantSrvOpen->pVNetRoot);

    /* calculate the root directory's length, including vnetroot prefix,
     * mount path, and a trailing \ */
    const USHORT RootPathLen = VNetRoot->PrefixEntry.Prefix.Length +
            pVNetRootContext->MountPathLen + sizeof(WCHAR);

    return RxContext->CurrentIrpSp->FileObject->FileName.Length <= RootPathLen;
}

NTSTATUS nfs41_QueryVolumeInformation(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INVALID_PARAMETER;
    nfs41_updowncall_entry *entry;
    ULONG RemainingLength = RxContext->Info.LengthRemaining, SizeUsed;
    FS_INFORMATION_CLASS InfoClass = RxContext->Info.FsInformationClass;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
    NFS41GetDeviceExtension(RxContext, DevExt);

#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_VOLUME_QUERY
    DbgEn();
    print_queryvolume_args(RxContext);
#endif

    status = check_nfs41_dirquery_args(RxContext);
    if (status) goto out;

    switch (InfoClass) {
    case FileFsVolumeInformation:
        if ((ULONG)RxContext->Info.LengthRemaining >= DevExt->VolAttrsLen) {
            RtlCopyMemory(RxContext->Info.Buffer, DevExt->VolAttrs, 
                DevExt->VolAttrsLen);
            RxContext->Info.LengthRemaining -= DevExt->VolAttrsLen;
            status = STATUS_SUCCESS;
        } else {
            RtlCopyMemory(RxContext->Info.Buffer, DevExt->VolAttrs, 
                RxContext->Info.LengthRemaining);
            status = STATUS_BUFFER_OVERFLOW;
        }
        goto out;
    case FileFsDeviceInformation:
    {
        PFILE_FS_DEVICE_INFORMATION pDevInfo = RxContext->Info.Buffer;

        SizeUsed = sizeof(FILE_FS_DEVICE_INFORMATION);
        if (RemainingLength < SizeUsed) {
            status = STATUS_BUFFER_TOO_SMALL;
            RxContext->InformationToReturn = SizeUsed;
            goto out;
        }
        pDevInfo->DeviceType = RxContext->pFcb->pNetRoot->DeviceType;
        pDevInfo->Characteristics = FILE_REMOTE_DEVICE | FILE_DEVICE_IS_MOUNTED;
        RxContext->Info.LengthRemaining -= SizeUsed;
        status = STATUS_SUCCESS;
        goto out;
    }
    case FileAccessInformation:
        status = STATUS_NOT_SUPPORTED;
        goto out;

    case FileFsAttributeInformation:
        if (RxContext->Info.LengthRemaining < FS_ATTR_LEN) {
            RxContext->InformationToReturn = FS_ATTR_LEN;
            status = STATUS_BUFFER_TOO_SMALL;
            goto out;
        }

        /* on attribute queries for the root directory,
         * use cached volume attributes from mount */
        if (is_root_directory(RxContext)) {
            PFILE_FS_ATTRIBUTE_INFORMATION attrs =
                (PFILE_FS_ATTRIBUTE_INFORMATION)RxContext->Info.Buffer;
            DECLARE_CONST_UNICODE_STRING(FsName, FS_NAME);

            RtlCopyMemory(attrs, &pVNetRootContext->FsAttrs,
                sizeof(pVNetRootContext->FsAttrs));

            /* fill in the FileSystemName */
            RtlCopyMemory(attrs->FileSystemName, FsName.Buffer,
                FsName.MaximumLength); /* 'MaximumLength' to include null */
            attrs->FileSystemNameLength = FsName.Length;

            RxContext->Info.LengthRemaining -= FS_ATTR_LEN;
            goto out;
        }
        /* else fall through and send the upcall */
    case FileFsSizeInformation:
    case FileFsFullSizeInformation:
        break;

    default:
        print_error("nfs41_QueryVolumeInformation: unhandled class %d\n", InfoClass);
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }
    status = nfs41_UpcallCreate(NFS41_VOLUME_QUERY, &nfs41_fobx->sec_ctx,
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state, 
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Volume.query = InfoClass;
    entry->buf = RxContext->Info.Buffer;
    entry->buf_len = RxContext->Info.LengthRemaining;

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;

    if (entry->status == STATUS_BUFFER_TOO_SMALL) {
        RxContext->InformationToReturn = entry->buf_len;
        status = STATUS_BUFFER_TOO_SMALL;
    } else if (entry->status == STATUS_SUCCESS) {
        if (InfoClass == FileFsAttributeInformation) {
            /* fill in the FileSystemName */
            PFILE_FS_ATTRIBUTE_INFORMATION attrs =
                (PFILE_FS_ATTRIBUTE_INFORMATION)RxContext->Info.Buffer;
            DECLARE_CONST_UNICODE_STRING(FsName, FS_NAME);

            RtlCopyMemory(attrs->FileSystemName, FsName.Buffer,
                FsName.MaximumLength); /* 'MaximumLength' to include null */
            attrs->FileSystemNameLength = FsName.Length;

            entry->buf_len = FS_ATTR_LEN;
        }
#ifdef ENABLE_TIMINGS
        InterlockedIncrement(&volume.sops); 
        InterlockedAdd64(&volume.size, entry->u.Volume.buf_len);
#endif
        RxContext->Info.LengthRemaining -= entry->buf_len;
        status = STATUS_SUCCESS;
    } else {
        status = map_volume_errors(entry->status);
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&volume.tops); 
    InterlockedAdd64(&volume.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_QueryVolumeInformation delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, volume.tops, volume.ticks);
#endif
#endif
#ifdef DEBUG_VOLUME_QUERY
    DbgEx();
#endif
    return status;
}

VOID nfs41_update_fcb_list(
    PMRX_FCB fcb,
    ULONGLONG ChangeTime)
{
    PLIST_ENTRY pEntry;
    nfs41_fcb_list_entry *cur;
    ExAcquireFastMutex(&fcblistLock); 
    pEntry = openlist.head.Flink;
    while (!IsListEmpty(&openlist.head)) {
        cur = (nfs41_fcb_list_entry *)CONTAINING_RECORD(pEntry, 
                nfs41_fcb_list_entry, next);
        if (cur->fcb == fcb && 
                cur->ChangeTime != ChangeTime) {
#if defined(DEBUG_FILE_SET) || defined(DEBUG_ACL_SET) || \
    defined(DEBUG_WRITE) || defined(DEBUG_EA_SET)
            DbgP("nfs41_update_fcb_list: Found match for fcb %p: updating "
                "%llu to %llu\n", fcb, cur->ChangeTime, ChangeTime);
#endif
            cur->ChangeTime = ChangeTime;
            break;
        }
        /* place an upcall for this srv_open */
        if (pEntry->Flink == &openlist.head) {
#if defined(DEBUG_FILE_SET) || defined(DEBUG_ACL_SET) || \
    defined(DEBUG_WRITE) || defined(DEBUG_EA_SET)
            DbgP("nfs41_update_fcb_list: reached EOL loooking for "
                "fcb=%p\n", fcb);
#endif
            break;
        }
        pEntry = pEntry->Flink;
    }
    ExReleaseFastMutex(&fcblistLock);
}

void print_nfs3_attrs(
    nfs3_attrs *attrs)
{
    DbgP("type=%d mode=%o nlink=%d size=%d atime=%x mtime=%x ctime=%x\n",
        attrs->type, attrs->mode, attrs->nlink, attrs->size, attrs->atime,
        attrs->mtime, attrs->ctime);
}

void file_time_to_nfs_time(
    IN const PLARGE_INTEGER file_time,
    OUT LONGLONG *nfs_time)
{
    LARGE_INTEGER diff = unix_time_diff;
    diff.QuadPart = file_time->QuadPart - diff.QuadPart;
    *nfs_time = diff.QuadPart / 10000000;
}

void create_nfs3_attrs(
    nfs3_attrs *attrs, 
    PNFS41_FCB nfs41_fcb)
{
    RtlZeroMemory(attrs, sizeof(nfs3_attrs));
    if (nfs41_fcb->BasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
        attrs->type = NF3LNK;
    else if (nfs41_fcb->StandardInfo.Directory)
        attrs->type = NF3DIR;
    else
        attrs->type = NF3REG;
    attrs->mode = nfs41_fcb->mode;
    attrs->nlink = nfs41_fcb->StandardInfo.NumberOfLinks;
    attrs->size.QuadPart = attrs->used.QuadPart = 
        nfs41_fcb->StandardInfo.EndOfFile.QuadPart;
    file_time_to_nfs_time(&nfs41_fcb->BasicInfo.LastAccessTime, &attrs->atime);
    file_time_to_nfs_time(&nfs41_fcb->BasicInfo.ChangeTime, &attrs->mtime);
    file_time_to_nfs_time(&nfs41_fcb->BasicInfo.CreationTime, &attrs->ctime);
}


NTSTATUS map_setea_error(
    DWORD error)
{
    switch (error) {
    case NO_ERROR:                      return STATUS_SUCCESS;
    case ERROR_FILE_NOT_FOUND:          return STATUS_NO_EAS_ON_FILE;
    case ERROR_ACCESS_DENIED:           return STATUS_ACCESS_DENIED;
    case ERROR_NETWORK_ACCESS_DENIED:   return STATUS_NETWORK_ACCESS_DENIED;
    case ERROR_NETNAME_DELETED:         return STATUS_NETWORK_NAME_DELETED;
    case ERROR_FILE_TOO_LARGE:          return STATUS_EA_TOO_LARGE;
    case ERROR_BUFFER_OVERFLOW:         return STATUS_BUFFER_OVERFLOW;
    case STATUS_BUFFER_TOO_SMALL:
    case ERROR_INSUFFICIENT_BUFFER:     return STATUS_BUFFER_TOO_SMALL;
    case ERROR_INVALID_EA_HANDLE:       return STATUS_NONEXISTENT_EA_ENTRY;
    case ERROR_NO_MORE_FILES:           return STATUS_NO_MORE_EAS;
    case ERROR_EA_FILE_CORRUPT:         return STATUS_EA_CORRUPT_ERROR;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_PARAMETER\n", error);
    case ERROR_INVALID_PARAMETER:       return STATUS_INVALID_PARAMETER;
    }
}

NTSTATUS check_nfs41_setea_args(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(RxContext->pRelevantSrvOpen->pVNetRoot);
    __notnull PFILE_FS_ATTRIBUTE_INFORMATION FsAttrs =
        &pVNetRootContext->FsAttrs;
    __notnull PFILE_FULL_EA_INFORMATION ea =
        (PFILE_FULL_EA_INFORMATION)RxContext->Info.Buffer;

    status = check_nfs41_dirquery_args(RxContext);
    if (status) goto out;

    if (ea == NULL) {
        status = STATUS_INVALID_PARAMETER;
        goto out;
    }
    if (AnsiStrEq(&NfsActOnLink, ea->EaName, ea->EaNameLength) ||
        AnsiStrEq(&NfsSymlinkTargetName, ea->EaName, ea->EaNameLength)) {
        status = STATUS_INVALID_PARAMETER; /* only allowed on create */
        goto out;
    }
    /* ignore cygwin EAs when checking support */
    if (!(FsAttrs->FileSystemAttributes & FILE_SUPPORTS_EXTENDED_ATTRIBUTES)
        && !AnsiStrEq(&NfsV3Attributes, ea->EaName, ea->EaNameLength)) {
        status = STATUS_EAS_NOT_SUPPORTED;
        goto out;
    }
    if ((RxContext->pRelevantSrvOpen->DesiredAccess & FILE_WRITE_EA) == 0) {
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
    if (pVNetRootContext->read_only) {
        print_error("check_nfs41_setattr_args: Read-only mount\n");
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
out:
    return status;
}

NTSTATUS nfs41_SetEaInformation(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_EAS_NOT_SUPPORTED;
    nfs41_updowncall_entry *entry;
    __notnull PFILE_FULL_EA_INFORMATION eainfo = 
        (PFILE_FULL_EA_INFORMATION)RxContext->Info.Buffer;        
    nfs3_attrs *attrs = NULL;
    ULONG buflen = RxContext->CurrentIrpSp->Parameters.SetEa.Length, error_offset;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_EA_SET
    DbgEn();
    print_debug_header(RxContext);
    print_ea_info(1, eainfo);
#endif

    status = check_nfs41_setea_args(RxContext);
    if (status) goto out;

    status = nfs41_UpcallCreate(NFS41_EA_SET, &nfs41_fobx->sec_ctx,
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    if (AnsiStrEq(&NfsV3Attributes, eainfo->EaName, eainfo->EaNameLength)) {
        attrs = (nfs3_attrs *)(eainfo->EaName + eainfo->EaNameLength + 1);
#ifdef DEBUG_EA_SET
        print_nfs3_attrs(attrs);
        DbgP("old mode is %o new mode is %o\n", nfs41_fcb->mode, attrs->mode);
#endif
        entry->u.SetEa.mode = attrs->mode;
    } else {
        entry->u.SetEa.mode = 0;
        status = IoCheckEaBufferValidity(eainfo, buflen, &error_offset);
        if (status) {
            RxFreePool(entry);
            goto out;
        }
    }
    entry->buf = eainfo;
    entry->buf_len = buflen;
    
    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;
#ifdef ENABLE_TIMINGS
    if (entry->status == STATUS_SUCCESS) {
        InterlockedIncrement(&setexattr.sops); 
        InterlockedAdd64(&setexattr.size, entry->u.SetEa.buf_len);
    }
#endif
    status = map_setea_error(entry->status);
    if (!status) {
        if (!nfs41_fobx->deleg_type && entry->ChangeTime &&
                (SrvOpen->DesiredAccess & 
                (FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA)))
            nfs41_update_fcb_list(RxContext->pFcb, entry->ChangeTime);
        nfs41_fcb->changeattr = entry->ChangeTime;
        nfs41_fcb->mode = entry->u.SetEa.mode;
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&setexattr.tops); 
    InterlockedAdd64(&setexattr.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_SetEaInformation delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, setexattr.tops, setexattr.ticks);
#endif
#endif
#ifdef DEBUG_EA_SET
    DbgEx();
#endif
    return status;
}

NTSTATUS check_nfs41_queryea_args(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(RxContext->pRelevantSrvOpen->pVNetRoot);
    __notnull PFILE_FS_ATTRIBUTE_INFORMATION FsAttrs =
        &pVNetRootContext->FsAttrs;
    PFILE_GET_EA_INFORMATION ea = (PFILE_GET_EA_INFORMATION)
            RxContext->CurrentIrpSp->Parameters.QueryEa.EaList;

    status = check_nfs41_dirquery_args(RxContext);
    if (status) goto out;

    if (!(FsAttrs->FileSystemAttributes & FILE_SUPPORTS_EXTENDED_ATTRIBUTES)) {
        if (ea == NULL) {
            status = STATUS_EAS_NOT_SUPPORTED;
            goto out;
        }
        /* ignore cygwin EAs when checking support */
        if (!AnsiStrEq(&NfsV3Attributes, ea->EaName, ea->EaNameLength) &&
            !AnsiStrEq(&NfsActOnLink, ea->EaName, ea->EaNameLength) &&
            !AnsiStrEq(&NfsSymlinkTargetName, ea->EaName, ea->EaNameLength)) {
            status = STATUS_EAS_NOT_SUPPORTED;
            goto out;
        }
    }
    if ((RxContext->pRelevantSrvOpen->DesiredAccess & FILE_READ_EA) == 0) {
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
out:
    return status;
}

static NTSTATUS QueryCygwinSymlink(
    IN OUT PRX_CONTEXT RxContext,
    IN PFILE_GET_EA_INFORMATION query,
    OUT PFILE_FULL_EA_INFORMATION info)
{
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION VNetRootContext =
            NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION NetRootContext =
            NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FOBX Fobx = NFS41GetFobxExtension(RxContext->pFobx);
    nfs41_updowncall_entry *entry;
    UNICODE_STRING TargetName;
    const USHORT HeaderLen = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
        query->EaNameLength + 1;
    NTSTATUS status;

    if (RxContext->Info.LengthRemaining < HeaderLen) {
        status = STATUS_BUFFER_TOO_SMALL;
        RxContext->InformationToReturn = HeaderLen;
        goto out;
    }

    TargetName.Buffer = (PWCH)(info->EaName + query->EaNameLength + 1);
    TargetName.MaximumLength = (USHORT)min(RxContext->Info.LengthRemaining -
        HeaderLen, 0xFFFF);

    status = nfs41_UpcallCreate(NFS41_SYMLINK, &Fobx->sec_ctx, 
        VNetRootContext->session, Fobx->nfs41_open_state,
        NetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Symlink.target = &TargetName;
    entry->u.Symlink.set = FALSE;

    status = nfs41_UpcallWaitForReply(entry, VNetRootContext->timeout);
    if (status) goto out;

    status = map_setea_error(entry->status);
    if (status == STATUS_SUCCESS) {
        info->NextEntryOffset = 0;
        info->Flags = 0;
        info->EaNameLength = query->EaNameLength;
        info->EaValueLength = TargetName.Length - sizeof(UNICODE_NULL);
        TargetName.Buffer[TargetName.Length/sizeof(WCHAR)] = UNICODE_NULL;
        RtlCopyMemory(info->EaName, query->EaName, query->EaNameLength);
        RxContext->Info.LengthRemaining = HeaderLen + info->EaValueLength;
    } else if (status == STATUS_BUFFER_TOO_SMALL) {
        RxContext->InformationToReturn = HeaderLen +
            entry->u.Symlink.target->Length;
    }
    RxFreePool(entry);
out:
    return status;
}

static NTSTATUS QueryCygwinEA(
    IN OUT PRX_CONTEXT RxContext,
    IN PFILE_GET_EA_INFORMATION query,
    OUT PFILE_FULL_EA_INFORMATION info)
{
    NTSTATUS status = STATUS_NONEXISTENT_EA_ENTRY;

    if (query == NULL)
        goto out;

    if (AnsiStrEq(&NfsSymlinkTargetName, query->EaName, query->EaNameLength)) {
        __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
        if (nfs41_fcb->BasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
            status = QueryCygwinSymlink(RxContext, query, info);
            goto out;
        } else {
            const LONG LengthRequired = sizeof(FILE_FULL_EA_INFORMATION) +
                NfsSymlinkTargetName.Length - sizeof(CHAR);
            if (LengthRequired > RxContext->Info.LengthRemaining) {
                status = STATUS_BUFFER_TOO_SMALL;
                RxContext->InformationToReturn = LengthRequired;
                goto out;
            }
            info->NextEntryOffset = 0;
            info->Flags = 0;
            info->EaValueLength = 0;
            info->EaNameLength = (UCHAR)NfsActOnLink.Length;
            RtlCopyMemory(info->EaName, NfsSymlinkTargetName.Buffer, 
                NfsSymlinkTargetName.Length);
            RxContext->Info.LengthRemaining = LengthRequired;
            status = STATUS_SUCCESS;
            goto out;
        }
    }

    if (AnsiStrEq(&NfsV3Attributes, query->EaName, query->EaNameLength)) {
        nfs3_attrs attrs;

        const LONG LengthRequired = sizeof(FILE_FULL_EA_INFORMATION) +
            NfsV3Attributes.Length + sizeof(nfs3_attrs) - sizeof(CHAR);
        if (LengthRequired > RxContext->Info.LengthRemaining) {
            status = STATUS_BUFFER_TOO_SMALL;
            RxContext->InformationToReturn = LengthRequired;
            goto out;
        }

        create_nfs3_attrs(&attrs, NFS41GetFcbExtension(RxContext->pFcb));
#ifdef DEBUG_EA_QUERY
        print_nfs3_attrs(&attrs);
#endif

        info->NextEntryOffset = 0;
        info->Flags = 0;
        info->EaNameLength = (UCHAR)NfsV3Attributes.Length;
        info->EaValueLength = sizeof(nfs3_attrs);
        RtlCopyMemory(info->EaName, NfsV3Attributes.Buffer, 
            NfsV3Attributes.Length);
        RtlCopyMemory(info->EaName + info->EaNameLength + 1, &attrs, 
            sizeof(nfs3_attrs));
        RxContext->Info.LengthRemaining = LengthRequired;
        status = STATUS_SUCCESS;
        goto out;
    }

    if (AnsiStrEq(&NfsActOnLink, query->EaName, query->EaNameLength)) {

        const LONG LengthRequired = sizeof(FILE_FULL_EA_INFORMATION) +
            query->EaNameLength - sizeof(CHAR);
        if (LengthRequired > RxContext->Info.LengthRemaining) {
            status = STATUS_BUFFER_TOO_SMALL;
            RxContext->InformationToReturn = LengthRequired;
            goto out;
        }

        info->NextEntryOffset = 0;
        info->Flags = 0;
        info->EaNameLength = query->EaNameLength;
        info->EaValueLength = 0;
        RtlCopyMemory(info->EaName, query->EaName, query->EaNameLength);
        RxContext->Info.LengthRemaining = LengthRequired;
        status = STATUS_SUCCESS;
        goto out;
    }
out:
    return status;
}

NTSTATUS nfs41_QueryEaInformation(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_EAS_NOT_SUPPORTED;
    nfs41_updowncall_entry *entry;
    PFILE_GET_EA_INFORMATION query = (PFILE_GET_EA_INFORMATION)
            RxContext->CurrentIrpSp->Parameters.QueryEa.EaList;
    ULONG buflen = RxContext->CurrentIrpSp->Parameters.QueryEa.Length;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
            NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
            NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_EA_QUERY
    DbgEn();
    print_debug_header(RxContext);
    print_get_ea(1, query);
#endif
    status = check_nfs41_queryea_args(RxContext);
    if (status) goto out;

    /* handle queries for cygwin EAs */
    status = QueryCygwinEA(RxContext, query,
        (PFILE_FULL_EA_INFORMATION)RxContext->Info.Buffer);
    if (status != STATUS_NONEXISTENT_EA_ENTRY)
        goto out;

    status = nfs41_UpcallCreate(NFS41_EA_GET, &nfs41_fobx->sec_ctx,
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->buf_len = buflen;
    entry->buf = RxContext->Info.Buffer;
    entry->u.QueryEa.EaList = query;
    entry->u.QueryEa.EaListLength = query == NULL ? 0 :
        RxContext->QueryEa.UserEaListLength;
    entry->u.QueryEa.EaIndex = RxContext->QueryEa.IndexSpecified ?
        RxContext->QueryEa.UserEaIndex : 0;
    entry->u.QueryEa.RestartScan = RxContext->QueryEa.RestartScan;
    entry->u.QueryEa.ReturnSingleEntry = RxContext->QueryEa.ReturnSingleEntry;

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;

    if (entry->status == STATUS_SUCCESS) {
        switch (entry->u.QueryEa.Overflow) {
        case ERROR_INSUFFICIENT_BUFFER:
            status = STATUS_BUFFER_TOO_SMALL;
            break;
        case ERROR_BUFFER_OVERFLOW:
            status = RxContext->IoStatusBlock.Status = STATUS_BUFFER_OVERFLOW;
            break;
        default:
            RxContext->IoStatusBlock.Status = STATUS_SUCCESS;
            break;
        }
        RxContext->InformationToReturn = entry->buf_len;
#ifdef ENABLE_TIMINGS
        InterlockedIncrement(&getexattr.sops); 
        InterlockedAdd64(&getexattr.size, entry->u.QueryEa.buf_len);
#endif
    } else {
        status = map_setea_error(entry->status);
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&getexattr.tops); 
    InterlockedAdd64(&getexattr.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_QueryEaInformation delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, getexattr.tops, getexattr.ticks);
#endif
#endif
#ifdef DEBUG_EA_QUERY
    DbgEx();
#endif
    return status;
}

NTSTATUS map_query_acl_error(
    DWORD error)
{
    switch (error) {
    case NO_ERROR:                  return STATUS_SUCCESS;
    case ERROR_NOT_SUPPORTED:       return STATUS_NOT_SUPPORTED;
    case ERROR_ACCESS_DENIED:       return STATUS_ACCESS_DENIED;
    case ERROR_FILE_NOT_FOUND:      return STATUS_OBJECT_NAME_NOT_FOUND;
    case ERROR_INVALID_PARAMETER:   return STATUS_INVALID_PARAMETER;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_NETWORK_RESPONSE\n", error);
    case ERROR_BAD_NET_RESP:        return STATUS_INVALID_NETWORK_RESPONSE;
    }
}

NTSTATUS check_nfs41_getacl_args(
    PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    SECURITY_INFORMATION info_class =
        RxContext->CurrentIrpSp->Parameters.QuerySecurity.SecurityInformation;

    /* we don't support sacls */
    if (info_class == SACL_SECURITY_INFORMATION || 
            info_class == LABEL_SECURITY_INFORMATION) {
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }
    if (RxContext->CurrentIrp->UserBuffer == NULL &&
            RxContext->CurrentIrpSp->Parameters.QuerySecurity.Length)
        status = STATUS_INVALID_USER_BUFFER;
out:
    return status;
}

NTSTATUS nfs41_QuerySecurityInformation(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_NOT_SUPPORTED;
    nfs41_updowncall_entry *entry;
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    SECURITY_INFORMATION info_class =
        RxContext->CurrentIrpSp->Parameters.QuerySecurity.SecurityInformation;
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_ACL_QUERY
    DbgEn();
    print_debug_header(RxContext);
    print_acl_args(info_class);
#endif

    status = check_nfs41_getacl_args(RxContext);
    if (status) goto out;

    if (nfs41_fobx->acl && nfs41_fobx->acl_len) {
        LARGE_INTEGER current_time;
        KeQuerySystemTime(&current_time);
#ifdef DEBUG_ACL_QUERY
        DbgP("CurrentTime %lx Saved Acl time %lx\n", 
            current_time.QuadPart, nfs41_fobx->time.QuadPart);
#endif
        if (current_time.QuadPart - nfs41_fobx->time.QuadPart <= 20*1000) {         
            PSECURITY_DESCRIPTOR sec_desc = (PSECURITY_DESCRIPTOR)
                RxContext->CurrentIrp->UserBuffer;
            RtlCopyMemory(sec_desc, nfs41_fobx->acl, nfs41_fobx->acl_len); 
            RxContext->IoStatusBlock.Information = 
                RxContext->InformationToReturn = nfs41_fobx->acl_len;
            RxContext->IoStatusBlock.Status = status = STATUS_SUCCESS;
#ifdef ENABLE_TIMINGS
            InterlockedIncrement(&getacl.sops);
            InterlockedAdd64(&getacl.size, nfs41_fobx->acl_len);
#endif
        } else status = 1;
        RxFreePool(nfs41_fobx->acl);
        nfs41_fobx->acl = NULL;
        nfs41_fobx->acl_len = 0;
        if (!status)
            goto out;
    }

    status = nfs41_UpcallCreate(NFS41_ACL_QUERY, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Acl.query = info_class;
    /* we can't provide RxContext->CurrentIrp->UserBuffer to the upcall thread 
     * because it becomes an invalid pointer with that execution context
     */
    entry->buf_len = RxContext->CurrentIrpSp->Parameters.QuerySecurity.Length;

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;

    if (entry->status == STATUS_BUFFER_TOO_SMALL) {
#ifdef DEBUG_ACL_QUERY
        DbgP("nfs41_QuerySecurityInformation: provided buffer size=%d but we "
             "need %lu\n", 
             RxContext->CurrentIrpSp->Parameters.QuerySecurity.Length, 
             entry->buf_len);
#endif
        status = STATUS_BUFFER_OVERFLOW;
        RxContext->InformationToReturn = entry->buf_len;

        /* Save ACL buffer */
        nfs41_fobx->acl = entry->buf;
        nfs41_fobx->acl_len = entry->buf_len;
        KeQuerySystemTime(&nfs41_fobx->time);
    } else if (entry->status == STATUS_SUCCESS) {
        PSECURITY_DESCRIPTOR sec_desc = (PSECURITY_DESCRIPTOR)
            RxContext->CurrentIrp->UserBuffer;
        RtlCopyMemory(sec_desc, entry->buf, entry->buf_len); 
#ifdef ENABLE_TIMINGS
        InterlockedIncrement(&getacl.sops);
        InterlockedAdd64(&getacl.size, entry->u.Acl.buf_len);
#endif
        RxFreePool(entry->buf);
        nfs41_fobx->acl = NULL;
        nfs41_fobx->acl_len = 0;
        RxContext->IoStatusBlock.Information = RxContext->InformationToReturn = 
            entry->buf_len;
        RxContext->IoStatusBlock.Status = status = STATUS_SUCCESS;
    } else {
        status = map_query_acl_error(entry->status);
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    /* only count getacl that we made an upcall for */
    if (status == STATUS_BUFFER_OVERFLOW) {
        InterlockedIncrement(&getacl.tops); 
        InterlockedAdd64(&getacl.ticks, t2.QuadPart - t1.QuadPart);
    }
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_QuerySecurityInformation: delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, getacl.tops, getacl.ticks);
#endif
#endif
#ifdef DEBUG_ACL_QUERY
    DbgEx();
#endif
    return status;
}

NTSTATUS check_nfs41_setacl_args(
    PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(RxContext->pRelevantSrvOpen->pVNetRoot);
    SECURITY_INFORMATION info_class = 
        RxContext->CurrentIrpSp->Parameters.SetSecurity.SecurityInformation;

    if (pVNetRootContext->read_only) {
        print_error("check_nfs41_setacl_args: Read-only mount\n");
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
    /* we don't support sacls */
    if (info_class == SACL_SECURITY_INFORMATION  || 
            info_class == LABEL_SECURITY_INFORMATION) {
        status = STATUS_NOT_SUPPORTED;       
        goto out;
    }
out:
    return status;
}

NTSTATUS nfs41_SetSecurityInformation(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_NOT_SUPPORTED;
    nfs41_updowncall_entry *entry;
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PSECURITY_DESCRIPTOR sec_desc = 
        RxContext->CurrentIrpSp->Parameters.SetSecurity.SecurityDescriptor;
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
    SECURITY_INFORMATION info_class = 
        RxContext->CurrentIrpSp->Parameters.SetSecurity.SecurityInformation;
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_ACL_SET
    DbgEn();
    print_debug_header(RxContext);
    print_acl_args(info_class);
#endif

    status = check_nfs41_setacl_args(RxContext);
    if (status) goto out;

    /* check that ACL is present */
    if (info_class & DACL_SECURITY_INFORMATION) {
        PACL acl;
        BOOLEAN present, dacl_default;
        status = RtlGetDaclSecurityDescriptor(sec_desc, &present, &acl, 
                    &dacl_default);
        if (status) {
            DbgP("RtlGetDaclSecurityDescriptor failed %x\n", status);
            goto out;
        }
        if (present == FALSE) {
            DbgP("NO ACL present\n");
            goto out;
        }
    }

    status = nfs41_UpcallCreate(NFS41_ACL_SET, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Acl.query = info_class;
    entry->buf = sec_desc;
    entry->buf_len = RtlLengthSecurityDescriptor(sec_desc);
#ifdef ENABLE_TIMINGS
    InterlockedIncrement(&setacl.sops); 
    InterlockedAdd64(&setacl.size, entry->u.Acl.buf_len);    
#endif

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;
 
    status = map_query_acl_error(entry->status);
    if (!status) {
        if (!nfs41_fobx->deleg_type && entry->ChangeTime &&
                (SrvOpen->DesiredAccess & 
                (FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA)))
            nfs41_update_fcb_list(RxContext->pFcb, entry->ChangeTime);
        nfs41_fcb->changeattr = entry->ChangeTime;
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&setacl.tops); 
    InterlockedAdd64(&setacl.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_SetSecurityInformation delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, setacl.tops, setacl.ticks);
#endif
#endif
#ifdef DEBUG_ACL_SET
    DbgEx();
#endif
    return status;
}

NTSTATUS map_queryfile_error(
    DWORD error)
{
    switch (error) {
    case ERROR_ACCESS_DENIED:       return STATUS_ACCESS_DENIED;
    case ERROR_NETNAME_DELETED:     return STATUS_NETWORK_NAME_DELETED;
    case ERROR_INVALID_PARAMETER:   return STATUS_INVALID_PARAMETER;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_NETWORK_RESPONSE\n", error);
    case ERROR_BAD_NET_RESP:        return STATUS_INVALID_NETWORK_RESPONSE;
    }
}

NTSTATUS nfs41_QueryFileInformation(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_OBJECT_NAME_NOT_FOUND;
    FILE_INFORMATION_CLASS InfoClass = RxContext->Info.FileInformationClass;
    nfs41_updowncall_entry *entry;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_FILE_QUERY
    DbgEn();
    print_debug_filedirquery_header(RxContext);
#endif

    status = check_nfs41_dirquery_args(RxContext);
    if (status) goto out;

    switch (InfoClass) {
    case FileEaInformation:
    {
        PFILE_EA_INFORMATION info =
            (PFILE_EA_INFORMATION)RxContext->Info.Buffer;
        info->EaSize = 0;
        RxContext->Info.LengthRemaining -= sizeof(FILE_EA_INFORMATION);
        status = STATUS_SUCCESS;
        goto out;
    }
    case FileBasicInformation:
    case FileStandardInformation:
    case FileInternalInformation: 
    case FileAttributeTagInformation:
    case FileNetworkOpenInformation:
        break;
    default:
        print_error("nfs41_QueryFileInformation: unhandled class %d\n", InfoClass);
        status = STATUS_NOT_SUPPORTED;
        goto out;
    }

    status = nfs41_UpcallCreate(NFS41_FILE_QUERY, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.QueryFile.InfoClass = InfoClass;
    entry->buf = RxContext->Info.Buffer;
    entry->buf_len = RxContext->Info.LengthRemaining;

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;

    if (entry->status == STATUS_BUFFER_TOO_SMALL) {
        RxContext->InformationToReturn = entry->buf_len;
        status = STATUS_BUFFER_TOO_SMALL;
    } else if (entry->status == STATUS_SUCCESS) {
        BOOLEAN DeletePending = FALSE;
#ifdef ENABLE_TIMINGS
        InterlockedIncrement(&getattr.sops); 
        InterlockedAdd64(&getattr.size, entry->u.QueryFile.buf_len);
#endif
        RxContext->Info.LengthRemaining -= entry->buf_len;
        status = STATUS_SUCCESS;

        switch (InfoClass) {
        case FileBasicInformation:
            RtlCopyMemory(&nfs41_fcb->BasicInfo, RxContext->Info.Buffer, 
                sizeof(nfs41_fcb->BasicInfo));
#ifdef DEBUG_FILE_QUERY
            print_basic_info(1, &nfs41_fcb->BasicInfo);
#endif
            break;
        case FileStandardInformation:
            /* this a fix for RDBSS behaviour when it first calls ExtendForCache,
             * then it sends a file query irp for standard attributes and 
             * expects to receive EndOfFile of value set by the ExtendForCache.
             * It seems to cache the filesize based on that instead of sending
             * a file size query for after doing the write. 
             */
        {
            PFILE_STANDARD_INFORMATION std_info;
            std_info = (PFILE_STANDARD_INFORMATION)RxContext->Info.Buffer;
            if (nfs41_fcb->StandardInfo.AllocationSize.QuadPart > 
                    std_info->AllocationSize.QuadPart) {
#ifdef DEBUG_FILE_QUERY
                DbgP("Old AllocationSize is bigger: saving %x\n", 
                    nfs41_fcb->StandardInfo.AllocationSize.QuadPart);
#endif
                std_info->AllocationSize.QuadPart = 
                    nfs41_fcb->StandardInfo.AllocationSize.QuadPart;
            }
            if (nfs41_fcb->StandardInfo.EndOfFile.QuadPart > 
                    std_info->EndOfFile.QuadPart) {
#ifdef DEBUG_FILE_QUERY
                DbgP("Old EndOfFile is bigger: saving %x\n", 
                    nfs41_fcb->StandardInfo.EndOfFile);
#endif
                std_info->EndOfFile.QuadPart = 
                    nfs41_fcb->StandardInfo.EndOfFile.QuadPart;
            }
            std_info->DeletePending = nfs41_fcb->DeletePending;
        }
            if (nfs41_fcb->StandardInfo.DeletePending)
                DeletePending = TRUE;
            RtlCopyMemory(&nfs41_fcb->StandardInfo, RxContext->Info.Buffer, 
                sizeof(nfs41_fcb->StandardInfo));
            nfs41_fcb->StandardInfo.DeletePending = DeletePending;
#ifdef DEBUG_FILE_QUERY
            print_std_info(1, &nfs41_fcb->StandardInfo);
#endif
            break;
        }
    } else {
        status = map_queryfile_error(entry->status);
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&getattr.tops); 
    InterlockedAdd64(&getattr.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_QueryFileInformation delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, getattr.tops, getattr.ticks);
#endif
#endif
#ifdef DEBUG_FILE_QUERY
    DbgEx();
#endif
    return status;
}

NTSTATUS map_setfile_error(
    DWORD error)
{
    switch (error) {
    case NO_ERROR:                      return STATUS_SUCCESS;
    case ERROR_NOT_EMPTY:               return STATUS_DIRECTORY_NOT_EMPTY;
    case ERROR_FILE_EXISTS:             return STATUS_OBJECT_NAME_COLLISION;
    case ERROR_FILE_NOT_FOUND:          return STATUS_OBJECT_NAME_NOT_FOUND;
    case ERROR_PATH_NOT_FOUND:          return STATUS_OBJECT_PATH_NOT_FOUND;
    case ERROR_ACCESS_DENIED:           return STATUS_ACCESS_DENIED;
    case ERROR_FILE_INVALID:            return STATUS_FILE_INVALID;
    case ERROR_NOT_SAME_DEVICE:         return STATUS_NOT_SAME_DEVICE;
    case ERROR_NOT_SUPPORTED:           return STATUS_NOT_IMPLEMENTED;
    case ERROR_NETWORK_ACCESS_DENIED:   return STATUS_NETWORK_ACCESS_DENIED;
    case ERROR_NETNAME_DELETED:         return STATUS_NETWORK_NAME_DELETED;
    case ERROR_BUFFER_OVERFLOW:         return STATUS_INSUFFICIENT_RESOURCES;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_PARAMETER\n", error);
    case ERROR_INVALID_PARAMETER:       return STATUS_INVALID_PARAMETER;
    }
}

NTSTATUS check_nfs41_setattr_args(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    FILE_INFORMATION_CLASS InfoClass = RxContext->Info.FileInformationClass;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(RxContext->pRelevantSrvOpen->pVNetRoot);

    if (pVNetRootContext->read_only) {
        print_error("check_nfs41_setattr_args: Read-only mount\n");
        status = STATUS_ACCESS_DENIED;
        goto out;
    }

    /* http://msdn.microsoft.com/en-us/library/ff469355(v=PROT.10).aspx
     * http://msdn.microsoft.com/en-us/library/ff469424(v=PROT.10).aspx
     * If Open.GrantedAccess does not contain FILE_WRITE_DATA, the operation 
     * MUST be failed with STATUS_ACCESS_DENIED.
     */
    if (InfoClass == FileAllocationInformation || 
            InfoClass == FileEndOfFileInformation) {
        if (!(RxContext->pRelevantSrvOpen->DesiredAccess & FILE_WRITE_DATA)) {
            status = STATUS_ACCESS_DENIED;
            goto out;
        }
    }
    status = check_nfs41_dirquery_args(RxContext);
    if (status) goto out;

    switch (InfoClass) {
    case FileRenameInformation:
    {
        PFILE_RENAME_INFORMATION rinfo = 
            (PFILE_RENAME_INFORMATION)RxContext->Info.Buffer;
        UNICODE_STRING dst = { (USHORT)rinfo->FileNameLength,
            (USHORT)rinfo->FileNameLength, rinfo->FileName };
#ifdef DEBUG_FILE_SET
        DbgP("Attempting to rename to '%wZ'\n", &dst);
#endif
        if (isFilenameTooLong(&dst, pVNetRootContext)) {
            status = STATUS_OBJECT_NAME_INVALID;
            goto out;
        }
        if (rinfo->RootDirectory) {
            status = STATUS_INVALID_PARAMETER;
            goto out;
        }
        break;
    }
    case FileLinkInformation:
    {
        PFILE_LINK_INFORMATION linfo = 
            (PFILE_LINK_INFORMATION)RxContext->Info.Buffer;
        UNICODE_STRING dst = { (USHORT)linfo->FileNameLength,
            (USHORT)linfo->FileNameLength, linfo->FileName };
#ifdef DEBUG_FILE_SET
        DbgP("Attempting to add link as '%wZ'\n", &dst);
#endif
        if (isFilenameTooLong(&dst, pVNetRootContext)) {
            status = STATUS_OBJECT_NAME_INVALID;
            goto out;
        }
        if (linfo->RootDirectory) {
            status = STATUS_INVALID_PARAMETER;
            goto out;
        }
        break;
    }
    case FileDispositionInformation:
    {
        PFILE_DISPOSITION_INFORMATION dinfo =
            (PFILE_DISPOSITION_INFORMATION)RxContext->Info.Buffer;
        __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
        if (dinfo->DeleteFile && nfs41_fcb->DeletePending) {
            status = STATUS_DELETE_PENDING;
            goto out;
        } 
        break;
    }
    case FileBasicInformation:
    case FileAllocationInformation:
    case FileEndOfFileInformation:
        break;
    default:
        print_error("nfs41_SetFileInformation: unhandled class %d\n", InfoClass);
        status = STATUS_NOT_SUPPORTED;
    }

out:
    return status;
}

NTSTATUS nfs41_SetFileInformation(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INVALID_PARAMETER;
    nfs41_updowncall_entry *entry;
    FILE_INFORMATION_CLASS InfoClass = RxContext->Info.FileInformationClass;
    FILE_RENAME_INFORMATION rinfo;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_FILE_SET
    DbgEn();
    print_debug_filedirquery_header(RxContext);
#endif

    status = check_nfs41_setattr_args(RxContext);
    if (status) goto out;

    switch (InfoClass) {
    case FileDispositionInformation:
        {
            PFILE_DISPOSITION_INFORMATION dinfo =
                (PFILE_DISPOSITION_INFORMATION)RxContext->Info.Buffer;
            if (dinfo->DeleteFile) {
                nfs41_fcb->DeletePending = TRUE;
                // we can delete directories right away
                if (nfs41_fcb->StandardInfo.Directory)
                    break;
                nfs41_fcb->StandardInfo.DeletePending = TRUE;
                if (RxContext->pFcb->OpenCount > 1) {
                    rinfo.ReplaceIfExists = 0;
                    rinfo.RootDirectory = INVALID_HANDLE_VALUE;
                    rinfo.FileNameLength = 0;
                    rinfo.FileName[0] = L'\0';
                    InfoClass = FileRenameInformation;
                    nfs41_fcb->Renamed = TRUE;
                    break;
                }
            } else {
                /* section 4.3.3 of [FSBO] 
                 * "file system behavior in the microsoft windows environment" 
                 */
                if (nfs41_fcb->DeletePending) {
                    nfs41_fcb->DeletePending = 0;
                    nfs41_fcb->StandardInfo.DeletePending = 0;
                }
            }
            status = STATUS_SUCCESS;
            goto out;
        }
    case FileEndOfFileInformation:
        {
            PFILE_END_OF_FILE_INFORMATION info =
                (PFILE_END_OF_FILE_INFORMATION)RxContext->Info.Buffer;
            nfs41_fcb->StandardInfo.AllocationSize =
                nfs41_fcb->StandardInfo.EndOfFile = info->EndOfFile;
            break;
        }
    case FileRenameInformation:
        {
            /* noop if filename and destination are the same */
            PFILE_RENAME_INFORMATION prinfo =
                (PFILE_RENAME_INFORMATION)RxContext->Info.Buffer;
            const UNICODE_STRING dst = { (USHORT)prinfo->FileNameLength,
                (USHORT)prinfo->FileNameLength, prinfo->FileName };
            if (RtlCompareUnicodeString(&dst,
                    SrvOpen->pAlreadyPrefixedName, FALSE) == 0) {
                status = STATUS_SUCCESS;
                goto out;
            }
        }
    }

    status = nfs41_UpcallCreate(NFS41_FILE_SET, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.SetFile.InfoClass = InfoClass;

    /* original irp has infoclass for remove but we need to rename instead, 
     * thus we changed the local variable infoclass */
    if (RxContext->Info.FileInformationClass == FileDispositionInformation && 
            InfoClass == FileRenameInformation) {
        entry->buf = &rinfo;
        entry->buf_len = sizeof(rinfo);
    } else {
        entry->buf = RxContext->Info.Buffer;
        entry->buf_len = RxContext->Info.Length;
    }
#ifdef ENABLE_TIMINGS
    InterlockedIncrement(&setattr.sops); 
    InterlockedAdd64(&setattr.size, entry->u.SetFile.buf_len);
#endif

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;

    status = map_setfile_error(entry->status);
    if (!status) {
        if (!nfs41_fobx->deleg_type && entry->ChangeTime &&
                (SrvOpen->DesiredAccess & 
                (FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA)))
            nfs41_update_fcb_list(RxContext->pFcb, entry->ChangeTime);
        nfs41_fcb->changeattr = entry->ChangeTime;
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&setattr.tops); 
    InterlockedAdd64(&setattr.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_SetFileInformation delta = %d op=%d sum=%d\n", 
        t2.QuadPart - t1.QuadPart, setattr.tops, setattr.ticks);
#endif
#endif
#ifdef DEBUG_FILE_SET
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_SetFileInformationAtCleanup(
      IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status;
    DbgEn();
    status = nfs41_SetFileInformation(RxContext);
    DbgEx();
    return status;
}

NTSTATUS nfs41_IsValidDirectory (
    IN OUT PRX_CONTEXT RxContext,
    IN PUNICODE_STRING DirectoryName)
{
    return STATUS_SUCCESS;
}

NTSTATUS nfs41_ComputeNewBufferingState(
    IN OUT PMRX_SRV_OPEN pSrvOpen,
    IN PVOID pMRxContext,
    OUT ULONG *pNewBufferingState)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG flag = PtrToUlong(pMRxContext), oldFlags = pSrvOpen->BufferingFlags;

    switch(flag) {
    case DISABLE_CACHING:
        if (pSrvOpen->BufferingFlags & 
            (FCB_STATE_READBUFFERING_ENABLED | FCB_STATE_READCACHING_ENABLED))
            pSrvOpen->BufferingFlags &= 
                ~(FCB_STATE_READBUFFERING_ENABLED | 
                  FCB_STATE_READCACHING_ENABLED);
        if (pSrvOpen->BufferingFlags & 
            (FCB_STATE_WRITECACHING_ENABLED | FCB_STATE_WRITEBUFFERING_ENABLED))
            pSrvOpen->BufferingFlags &= 
                ~(FCB_STATE_WRITECACHING_ENABLED | 
                  FCB_STATE_WRITEBUFFERING_ENABLED);
        pSrvOpen->BufferingFlags |= FCB_STATE_DISABLE_LOCAL_BUFFERING;
        break;
    case ENABLE_READ_CACHING:
        pSrvOpen->BufferingFlags |= 
            (FCB_STATE_READBUFFERING_ENABLED | FCB_STATE_READCACHING_ENABLED);
        break;
    case ENABLE_WRITE_CACHING:
        pSrvOpen->BufferingFlags |= 
            (FCB_STATE_WRITECACHING_ENABLED | FCB_STATE_WRITEBUFFERING_ENABLED);
        break;
    case ENABLE_READWRITE_CACHING:
        pSrvOpen->BufferingFlags = 
            (FCB_STATE_READBUFFERING_ENABLED | FCB_STATE_READCACHING_ENABLED | 
            FCB_STATE_WRITECACHING_ENABLED | FCB_STATE_WRITEBUFFERING_ENABLED);
    }
#ifdef DEBUG_TIME_BASED_COHERENCY
    DbgP("nfs41_ComputeNewBufferingState: %wZ pSrvOpen %p Old %08x New %08x\n", 
         pSrvOpen->pAlreadyPrefixedName, pSrvOpen, oldFlags,
         pSrvOpen->BufferingFlags);
    *pNewBufferingState = pSrvOpen->BufferingFlags;
#endif
    return status;
}

void print_readwrite_args(
    PRX_CONTEXT RxContext)
{
    PLOWIO_CONTEXT LowIoContext  = &RxContext->LowIoContext;

    print_debug_header(RxContext);
    DbgP("Bytecount 0x%x Byteoffset 0x%x Buffer %p\n", 
        LowIoContext->ParamsFor.ReadWrite.ByteCount, 
        LowIoContext->ParamsFor.ReadWrite.ByteOffset, 
        LowIoContext->ParamsFor.ReadWrite.Buffer);
}

void enable_caching(
    PMRX_SRV_OPEN SrvOpen,
    PNFS41_FOBX nfs41_fobx,
    ULONGLONG ChangeTime,
    HANDLE session)
{
    ULONG flag = 0;
    PLIST_ENTRY pEntry;
    nfs41_fcb_list_entry *cur;
    BOOLEAN found = FALSE;

    if (SrvOpen->DesiredAccess & FILE_READ_DATA)
        flag = ENABLE_READ_CACHING;
    if ((SrvOpen->DesiredAccess & FILE_WRITE_DATA) && 
            !nfs41_fobx->write_thru)
        flag = ENABLE_WRITE_CACHING;
    if ((SrvOpen->DesiredAccess & FILE_READ_DATA) && 
            (SrvOpen->DesiredAccess & FILE_WRITE_DATA) &&
            !nfs41_fobx->write_thru)
        flag = ENABLE_READWRITE_CACHING;

#if defined(DEBUG_TIME_BASED_COHERENCY) || \
        defined(DEBUG_WRITE) || defined(DEBUG_READ)
    print_caching_level(1, flag, SrvOpen->pAlreadyPrefixedName);
#endif

    if (!flag)
        return;

    RxChangeBufferingState((PSRV_OPEN)SrvOpen, ULongToPtr(flag), 1);

    ExAcquireFastMutex(&fcblistLock);
    pEntry = openlist.head.Flink;
    while (!IsListEmpty(&openlist.head)) {
        cur = (nfs41_fcb_list_entry *)CONTAINING_RECORD(pEntry,
                nfs41_fcb_list_entry, next);
        if (cur->fcb == SrvOpen->pFcb) {
#ifdef DEBUG_TIME_BASED_COHERENCY
            DbgP("enable_caching: Looked&Found match for fcb=%p %wZ\n",
                SrvOpen->pFcb, SrvOpen->pAlreadyPrefixedName);
#endif
            cur->skip = FALSE;
            found = TRUE;
            break;
        }
        if (pEntry->Flink == &openlist.head) {
#ifdef DEBUG_TIME_BASED_COHERENCY
            DbgP("enable_caching: reached EOL looking for fcb=%p %wZ\n",
                SrvOpen->pFcb, SrvOpen->pAlreadyPrefixedName);
#endif
            break;
        }
        pEntry = pEntry->Flink;
    }
    if (!found && nfs41_fobx->deleg_type) {
        nfs41_fcb_list_entry *oentry;
#ifdef DEBUG_TIME_BASED_COHERENCY
        DbgP("enable_caching: delegation recalled: srv_open=%p\n", SrvOpen);
#endif
        oentry = RxAllocatePoolWithTag(NonPagedPool, 
            sizeof(nfs41_fcb_list_entry), NFS41_MM_POOLTAG_OPEN);
        if (oentry == NULL) return;
        oentry->fcb = SrvOpen->pFcb;
        oentry->session = session;
        oentry->nfs41_fobx = nfs41_fobx;
        oentry->ChangeTime = ChangeTime;
        oentry->skip = FALSE;
        InsertTailList(&openlist.head, &oentry->next);
        nfs41_fobx->deleg_type = 0;
    }
    ExReleaseFastMutex(&fcblistLock);
}

NTSTATUS map_readwrite_errors(
    DWORD status)
{
    switch (status) {
    case ERROR_ACCESS_DENIED:           return STATUS_ACCESS_DENIED;
    case ERROR_HANDLE_EOF:              return STATUS_END_OF_FILE;
    case ERROR_FILE_INVALID:            return STATUS_FILE_INVALID;
    case ERROR_INVALID_PARAMETER:       return STATUS_INVALID_PARAMETER;
    case ERROR_LOCK_VIOLATION:          return STATUS_FILE_LOCK_CONFLICT;
    case ERROR_NETWORK_ACCESS_DENIED:   return STATUS_NETWORK_ACCESS_DENIED;
    case ERROR_NETNAME_DELETED:         return STATUS_NETWORK_NAME_DELETED;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_NET_WRITE_FAULT\n", status);
    case ERROR_NET_WRITE_FAULT:         return STATUS_NET_WRITE_FAULT;
    }
}

NTSTATUS check_nfs41_read_args(
    IN PRX_CONTEXT RxContext)
{
    if (!RxContext->LowIoContext.ParamsFor.ReadWrite.Buffer)
        return STATUS_INVALID_USER_BUFFER;
    return STATUS_SUCCESS;
}

NTSTATUS nfs41_Read(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
    nfs41_updowncall_entry *entry;
    BOOLEAN async = FALSE;
    PLOWIO_CONTEXT LowIoContext  = &RxContext->LowIoContext;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
    DWORD io_delay;
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_READ
    DbgEn();
    print_readwrite_args(RxContext);
#endif
    status = check_nfs41_read_args(RxContext);
    if (status) goto out;

    status = nfs41_UpcallCreate(NFS41_READ, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.ReadWrite.MdlAddress = LowIoContext->ParamsFor.ReadWrite.Buffer;
    entry->buf_len = LowIoContext->ParamsFor.ReadWrite.ByteCount;
    entry->u.ReadWrite.offset = LowIoContext->ParamsFor.ReadWrite.ByteOffset;
    if (FlagOn(RxContext->CurrentIrpSp->FileObject->Flags, 
            FO_SYNCHRONOUS_IO) == FALSE) {
        entry->u.ReadWrite.rxcontext = RxContext;
        async = entry->async_op = TRUE;
    }

    /* assume network speed is 100MB/s and disk speed is 100MB/s so add
     * time to transfer requested bytes over the network and read from disk
     */
    io_delay = pVNetRootContext->timeout + 2 * entry->buf_len / 104857600;
    status = nfs41_UpcallWaitForReply(entry, io_delay);
    if (status) goto out;

    if (async) {
#ifdef DEBUG_READ
        DbgP("This is asynchronous read, returning control back to the user\n");
#endif
        status = STATUS_PENDING;
        goto out;
    }

    if (entry->status == NO_ERROR) {
#ifdef ENABLE_TIMINGS
        InterlockedIncrement(&read.sops); 
        InterlockedAdd64(&read.size, entry->u.ReadWrite.len);
#endif
        status = RxContext->CurrentIrp->IoStatus.Status = STATUS_SUCCESS;
        RxContext->IoStatusBlock.Information = entry->buf_len;

        if ((!BooleanFlagOn(LowIoContext->ParamsFor.ReadWrite.Flags, 
                LOWIO_READWRITEFLAG_PAGING_IO) && 
                (SrvOpen->DesiredAccess & FILE_READ_DATA) &&
                !pVNetRootContext->nocache && !nfs41_fobx->nocache &&
                !(SrvOpen->BufferingFlags & 
                (FCB_STATE_READBUFFERING_ENABLED | 
                 FCB_STATE_READCACHING_ENABLED)))) {
            enable_caching(SrvOpen, nfs41_fobx, nfs41_fcb->changeattr,
                pVNetRootContext->session);
        }
    } else {
        status = map_readwrite_errors(entry->status);
        RxContext->CurrentIrp->IoStatus.Status = status;
        RxContext->IoStatusBlock.Information = 0;
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&read.tops); 
    InterlockedAdd64(&read.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_Read delta = %d op=%d sum=%d\n", t2.QuadPart - t1.QuadPart, 
        read.tops, read.ticks);
#endif
#endif
#ifdef DEBUG_READ
    DbgEx();
#endif
    return status;
}

NTSTATUS check_nfs41_write_args(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(RxContext->pRelevantSrvOpen->pVNetRoot);

    if (!RxContext->LowIoContext.ParamsFor.ReadWrite.Buffer) {
        status = STATUS_INVALID_USER_BUFFER;
        goto out;
    }

    if (pVNetRootContext->read_only) {
        print_error("check_nfs41_write_args: Read-only mount\n");
        status = STATUS_ACCESS_DENIED;
        goto out;
    }
out:
    return status;
}

NTSTATUS nfs41_Write(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
    nfs41_updowncall_entry *entry;
    BOOLEAN async = FALSE;
    PLOWIO_CONTEXT LowIoContext  = &RxContext->LowIoContext;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    __notnull PNFS41_FCB nfs41_fcb = NFS41GetFcbExtension(RxContext->pFcb);
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
    DWORD io_delay;
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_WRITE
    DbgEn();
    print_readwrite_args(RxContext);
#endif

    status = check_nfs41_write_args(RxContext);
    if (status) goto out;

    status = nfs41_UpcallCreate(NFS41_WRITE, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.ReadWrite.MdlAddress = LowIoContext->ParamsFor.ReadWrite.Buffer;
    entry->buf_len = LowIoContext->ParamsFor.ReadWrite.ByteCount;
    entry->u.ReadWrite.offset = LowIoContext->ParamsFor.ReadWrite.ByteOffset;

    if (FlagOn(RxContext->CurrentIrpSp->FileObject->Flags, 
            FO_SYNCHRONOUS_IO) == FALSE) {
        entry->u.ReadWrite.rxcontext = RxContext;
        async = entry->async_op = TRUE;
    }

    /* assume network speed is 100MB/s and disk speed is 100MB/s so add
     * time to transfer requested bytes over the network and write to disk
     */
    io_delay = pVNetRootContext->timeout + 2 * entry->buf_len / 104857600;
    status = nfs41_UpcallWaitForReply(entry, io_delay);
    if (status) goto out;

    if (async) {
#ifdef DEBUG_WRITE
        DbgP("This is asynchronous write, returning control back to the user\n");
#endif
        status = STATUS_PENDING;
        goto out;
    }
    
    if (entry->status == NO_ERROR) {
        //update cached file attributes
#ifdef ENABLE_TIMINGS
        InterlockedIncrement(&write.sops); 
        InterlockedAdd64(&write.size, entry->u.ReadWrite.len);
#endif
        nfs41_fcb->StandardInfo.EndOfFile.QuadPart = entry->buf_len + 
            entry->u.ReadWrite.offset;
        status = RxContext->CurrentIrp->IoStatus.Status = STATUS_SUCCESS;
        RxContext->IoStatusBlock.Information = entry->buf_len;
        nfs41_fcb->changeattr = entry->ChangeTime;

        //re-enable write buffering
        if (!BooleanFlagOn(LowIoContext->ParamsFor.ReadWrite.Flags, 
                LOWIO_READWRITEFLAG_PAGING_IO) && 
                (SrvOpen->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) &&
                !pVNetRootContext->write_thru &&
                !pVNetRootContext->nocache &&
                !nfs41_fobx->write_thru && !nfs41_fobx->nocache &&
                !(SrvOpen->BufferingFlags & 
                (FCB_STATE_WRITEBUFFERING_ENABLED | 
                 FCB_STATE_WRITECACHING_ENABLED))) {
            enable_caching(SrvOpen, nfs41_fobx, nfs41_fcb->changeattr, 
                pVNetRootContext->session);
        } else if (!nfs41_fobx->deleg_type) 
            nfs41_update_fcb_list(RxContext->pFcb, entry->ChangeTime);

    } else {
        status = map_readwrite_errors(entry->status);
        RxContext->CurrentIrp->IoStatus.Status = status;
        RxContext->IoStatusBlock.Information = 0;
    }
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&write.tops); 
    InterlockedAdd64(&write.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_Write delta = %d op=%d sum=%d\n", t2.QuadPart - t1.QuadPart, 
        write.tops, write.ticks);
#endif
#endif
#ifdef DEBUG_WRITE
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_IsLockRealizable(
    IN OUT PMRX_FCB pFcb,
    IN PLARGE_INTEGER  ByteOffset,
    IN PLARGE_INTEGER  Length,
    IN ULONG  LowIoLockFlags)
{
    NTSTATUS status = STATUS_SUCCESS;
#ifdef DEBUG_LOCK
    DbgEn();
    DbgP("offset 0x%llx, length 0x%llx, exclusive=%u, blocking=%u\n",
        ByteOffset->QuadPart,Length->QuadPart,
        BooleanFlagOn(LowIoLockFlags, SL_EXCLUSIVE_LOCK),
        !BooleanFlagOn(LowIoLockFlags, SL_FAIL_IMMEDIATELY));
#endif

    /* NFS lock operations with length=0 MUST fail with NFS4ERR_INVAL */
    if (Length->QuadPart == 0)
        status = STATUS_NOT_SUPPORTED;

#ifdef DEBUG_LOCK
    DbgEx();
#endif
    return status;
}

NTSTATUS map_lock_errors(
    DWORD status)
{
    switch (status) {
    case NO_ERROR:                  return STATUS_SUCCESS;
    case ERROR_NETNAME_DELETED:     return STATUS_NETWORK_NAME_DELETED;
    case ERROR_LOCK_FAILED:         return STATUS_LOCK_NOT_GRANTED;
    case ERROR_NOT_LOCKED:          return STATUS_RANGE_NOT_LOCKED;
    case ERROR_ATOMIC_LOCKS_NOT_SUPPORTED: return STATUS_UNSUCCESSFUL;
    case ERROR_OUTOFMEMORY:         return STATUS_INSUFFICIENT_RESOURCES;
    case ERROR_SHARING_VIOLATION:   return STATUS_SHARING_VIOLATION;
    case ERROR_FILE_INVALID:        return STATUS_FILE_INVALID;
    /* if we return ERROR_INVALID_PARAMETER, Windows translates that to
     * success!! */
    case ERROR_INVALID_PARAMETER:   return STATUS_LOCK_NOT_GRANTED;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_NETWORK_RESPONSE\n", status);
    case ERROR_BAD_NET_RESP:        return STATUS_INVALID_NETWORK_RESPONSE;
    }
}

void print_lock_args(
    PRX_CONTEXT RxContext)
{
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    const ULONG flags = LowIoContext->ParamsFor.Locks.Flags;
    print_debug_header(RxContext);
    DbgP("offset 0x%llx, length 0x%llx, exclusive=%u, blocking=%u\n",
        LowIoContext->ParamsFor.Locks.ByteOffset,
        LowIoContext->ParamsFor.Locks.Length,
        BooleanFlagOn(flags, SL_EXCLUSIVE_LOCK),
        !BooleanFlagOn(flags, SL_FAIL_IMMEDIATELY));
}


/* use exponential backoff between polls for blocking locks */
#define MSEC_TO_RELATIVE_WAIT   (-10000)
#define MIN_LOCK_POLL_WAIT      (500 * MSEC_TO_RELATIVE_WAIT) /* 500ms */
#define MAX_LOCK_POLL_WAIT      (30000 * MSEC_TO_RELATIVE_WAIT) /* 30s */

void denied_lock_backoff(
    IN OUT PLARGE_INTEGER delay)
{
    if (delay->QuadPart == 0)
        delay->QuadPart = MIN_LOCK_POLL_WAIT;
    else
        delay->QuadPart <<= 1;

    if (delay->QuadPart < MAX_LOCK_POLL_WAIT)
        delay->QuadPart = MAX_LOCK_POLL_WAIT;
}

NTSTATUS nfs41_Lock(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    nfs41_updowncall_entry *entry;
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    const ULONG flags = LowIoContext->ParamsFor.Locks.Flags;
    LARGE_INTEGER poll_delay = {0};
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif

#ifdef DEBUG_LOCK
    DbgEn();
    print_lock_args(RxContext);
#endif

/*  RxReleaseFcbResourceForThreadInMRx(RxContext, RxContext->pFcb,
        LowIoContext->ResourceThreadId); */

    status = nfs41_UpcallCreate(NFS41_LOCK, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Lock.offset = LowIoContext->ParamsFor.Locks.ByteOffset;
    entry->u.Lock.length = LowIoContext->ParamsFor.Locks.Length;
    entry->u.Lock.exclusive = BooleanFlagOn(flags, SL_EXCLUSIVE_LOCK);
    entry->u.Lock.blocking = !BooleanFlagOn(flags, SL_FAIL_IMMEDIATELY);

retry_upcall:
    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;

    /* blocking locks keep trying until it succeeds */
    if (entry->status == ERROR_LOCK_FAILED && entry->u.Lock.blocking) {
        denied_lock_backoff(&poll_delay);
        DbgP("returned ERROR_LOCK_FAILED; retrying in %llums\n",
            poll_delay.QuadPart / MSEC_TO_RELATIVE_WAIT);
        KeDelayExecutionThread(KernelMode, FALSE, &poll_delay);
        entry->state = NFS41_WAITING_FOR_UPCALL;
        goto retry_upcall;
    }

    status = map_lock_errors(entry->status);
    RxContext->CurrentIrp->IoStatus.Status = status;

    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&lock.tops); 
    InterlockedAdd64(&lock.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_Lock delta = %d op=%d sum=%d\n", t2.QuadPart - t1.QuadPart,
        lock.tops, lock.ticks);
#endif
#endif
#ifdef DEBUG_LOCK
    DbgEx();
#endif
    return status;
}

void print_unlock_args(
    PRX_CONTEXT RxContext)
{
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    print_debug_header(RxContext);
    if (LowIoContext->Operation == LOWIO_OP_UNLOCK_MULTIPLE) {
        PLOWIO_LOCK_LIST lock = LowIoContext->ParamsFor.Locks.LockList;
        DbgP("LOWIO_OP_UNLOCK_MULTIPLE:");
        while (lock) {
            DbgP(" (offset=%llu, length=%llu)", lock->ByteOffset, lock->Length);
            lock = lock->Next;
        }
        DbgP("\n");
    } else {
        DbgP("LOWIO_OP_UNLOCK: offset=%llu, length=%llu\n",
            LowIoContext->ParamsFor.Locks.ByteOffset,
            LowIoContext->ParamsFor.Locks.Length);
    }
}

__inline ULONG unlock_list_count(
    PLOWIO_LOCK_LIST lock)
{
    ULONG count = 0;
    while (lock) {
        count++;
        lock = lock->Next;
    }
    return count;
}

NTSTATUS nfs41_Unlock(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    nfs41_updowncall_entry *entry;
    PLOWIO_CONTEXT LowIoContext  = &RxContext->LowIoContext;
    __notnull PNFS41_FOBX nfs41_fobx = NFS41GetFobxExtension(RxContext->pFobx);
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION pVNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
#ifdef ENABLE_TIMINGS
    LARGE_INTEGER t1, t2;
    t1 = KeQueryPerformanceCounter(NULL);
#endif
#ifdef DEBUG_LOCK
    DbgEn();
    print_lock_args(RxContext);
#endif

/*  RxReleaseFcbResourceForThreadInMRx(RxContext, RxContext->pFcb,
        LowIoContext->ResourceThreadId); */

    status = nfs41_UpcallCreate(NFS41_UNLOCK, &nfs41_fobx->sec_ctx, 
        pVNetRootContext->session, nfs41_fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    if (LowIoContext->Operation == LOWIO_OP_UNLOCK_MULTIPLE) {
        entry->u.Unlock.count = unlock_list_count(
            LowIoContext->ParamsFor.Locks.LockList);
        RtlCopyMemory(&entry->u.Unlock.locks,
            LowIoContext->ParamsFor.Locks.LockList,
            sizeof(LOWIO_LOCK_LIST));
    } else {
        entry->u.Unlock.count = 1;
        entry->u.Unlock.locks.ByteOffset =
            LowIoContext->ParamsFor.Locks.ByteOffset;
        entry->u.Unlock.locks.Length =
            LowIoContext->ParamsFor.Locks.Length;
    }

    status = nfs41_UpcallWaitForReply(entry, pVNetRootContext->timeout);
    if (status) goto out;

    status = map_lock_errors(entry->status);
    RxContext->CurrentIrp->IoStatus.Status = status;
    RxFreePool(entry);
out:
#ifdef ENABLE_TIMINGS
    t2 = KeQueryPerformanceCounter(NULL);
    InterlockedIncrement(&unlock.tops); 
    InterlockedAdd64(&unlock.ticks, t2.QuadPart - t1.QuadPart);
#ifdef ENABLE_INDV_TIMINGS
    DbgP("nfs41_Unlock delta = %d op=%d sum=%d\n", t2.QuadPart - t1.QuadPart,
        unlock.tops, unlock.ticks);
#endif
#endif
#ifdef DEBUG_LOCK
    DbgEx();
#endif
    return status;
}

NTSTATUS map_symlink_errors(
    NTSTATUS status)
{
    switch (status) {
    case NO_ERROR:                  return STATUS_SUCCESS;
    case ERROR_INVALID_REPARSE_DATA: return STATUS_IO_REPARSE_DATA_INVALID;
    case ERROR_NOT_A_REPARSE_POINT: return STATUS_NOT_A_REPARSE_POINT;
    case ERROR_ACCESS_DENIED:       return STATUS_ACCESS_DENIED;
    case ERROR_NOT_EMPTY:           return STATUS_DIRECTORY_NOT_EMPTY;
    case ERROR_OUTOFMEMORY:         return STATUS_INSUFFICIENT_RESOURCES;
    case ERROR_INSUFFICIENT_BUFFER: return STATUS_BUFFER_TOO_SMALL;
    case STATUS_BUFFER_TOO_SMALL:
    case ERROR_BUFFER_OVERFLOW:     return STATUS_BUFFER_OVERFLOW;
    default:
        print_error("failed to map windows error %d to NTSTATUS; "
            "defaulting to STATUS_INVALID_NETWORK_RESPONSE\n", status);
    case ERROR_BAD_NET_RESP:        return STATUS_INVALID_NETWORK_RESPONSE;
    }
}

void print_reparse_buffer(
    PREPARSE_DATA_BUFFER Reparse)
{
    UNICODE_STRING name;
    DbgP("ReparseTag:           %08X\n", Reparse->ReparseTag);
    DbgP("ReparseDataLength:    %8u\n", Reparse->ReparseDataLength);
    DbgP("Reserved:             %8u\n", Reparse->Reserved);
    DbgP("SubstituteNameOffset: %8u\n", 
         Reparse->SymbolicLinkReparseBuffer.SubstituteNameOffset);
    DbgP("SubstituteNameLength: %8u\n", 
         Reparse->SymbolicLinkReparseBuffer.SubstituteNameLength);
    DbgP("PrintNameOffset:      %8u\n", 
         Reparse->SymbolicLinkReparseBuffer.PrintNameOffset);
    DbgP("PrintNameLength:      %8u\n", 
         Reparse->SymbolicLinkReparseBuffer.PrintNameLength);
    DbgP("Flags:                %08X\n", 
         Reparse->SymbolicLinkReparseBuffer.Flags);

    name.Buffer = &Reparse->SymbolicLinkReparseBuffer.PathBuffer[
        Reparse->SymbolicLinkReparseBuffer.SubstituteNameOffset/sizeof(WCHAR)];
    name.MaximumLength = name.Length =
        Reparse->SymbolicLinkReparseBuffer.SubstituteNameLength;
    DbgP("SubstituteName:       %wZ\n", &name);

    name.Buffer = &Reparse->SymbolicLinkReparseBuffer.PathBuffer[
        Reparse->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR)];
    name.MaximumLength = name.Length =
        Reparse->SymbolicLinkReparseBuffer.PrintNameLength;
    DbgP("PrintName:            %wZ\n", &name);
}

NTSTATUS check_nfs41_setreparse_args(
    IN PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    __notnull XXCTL_LOWIO_COMPONENT *FsCtl = &RxContext->LowIoContext.ParamsFor.FsCtl;
    __notnull PREPARSE_DATA_BUFFER Reparse = (PREPARSE_DATA_BUFFER)FsCtl->pInputBuffer;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION VNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    const ULONG HeaderLen = REPARSE_DATA_BUFFER_HEADER_SIZE;

    /* access checks */
    if (VNetRootContext->read_only) {
        status = STATUS_MEDIA_WRITE_PROTECTED;
        goto out;
    }
    if (!(SrvOpen->DesiredAccess & (FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES))) {
        status = STATUS_ACCESS_DENIED;
        goto out;
    }

    /* must have a filename longer than vnetroot name,
     * or it's trying to operate on the volume itself */
    if (is_root_directory(RxContext)) {
        status = STATUS_INVALID_PARAMETER;
        goto out;
    }
    if (FsCtl->pOutputBuffer != NULL) {
        status = STATUS_INVALID_PARAMETER;
        goto out;
    }

    /* validate input buffer and length */
    if (!Reparse) {
        status = STATUS_INVALID_BUFFER_SIZE;
        goto out;
    }

    if (FsCtl->InputBufferLength < HeaderLen ||
            FsCtl->InputBufferLength > MAXIMUM_REPARSE_DATA_BUFFER_SIZE) {
        status = STATUS_IO_REPARSE_DATA_INVALID;
        goto out;
    }
    if (FsCtl->InputBufferLength != HeaderLen + Reparse->ReparseDataLength) {
        status = STATUS_IO_REPARSE_DATA_INVALID;
        goto out;
    }

    /* validate reparse tag */
    if (!IsReparseTagValid(Reparse->ReparseTag)) {
        status = STATUS_IO_REPARSE_TAG_INVALID;
        goto out;
    }
    if (Reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
        status = STATUS_IO_REPARSE_TAG_MISMATCH;
        goto out;
    }
out:
    return status;
}

NTSTATUS nfs41_SetReparsePoint(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status;
    UNICODE_STRING TargetName;
    __notnull XXCTL_LOWIO_COMPONENT *FsCtl = &RxContext->LowIoContext.ParamsFor.FsCtl;
    __notnull PREPARSE_DATA_BUFFER Reparse = (PREPARSE_DATA_BUFFER)FsCtl->pInputBuffer;
    __notnull PNFS41_FOBX Fobx = NFS41GetFobxExtension(RxContext->pFobx);
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION VNetRootContext =
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    nfs41_updowncall_entry *entry;

#ifdef DEBUG_SYMLINK
    DbgEn();
    print_debug_header(RxContext);
    print_reparse_buffer(Reparse);
#endif
    status = check_nfs41_setreparse_args(RxContext);
    if (status) goto out;

    TargetName.MaximumLength = TargetName.Length =
        Reparse->SymbolicLinkReparseBuffer.PrintNameLength;
    TargetName.Buffer = &Reparse->SymbolicLinkReparseBuffer.PathBuffer[
        Reparse->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR)];

    status = nfs41_UpcallCreate(NFS41_SYMLINK, &Fobx->sec_ctx, 
        VNetRootContext->session, Fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Symlink.target = &TargetName;
    entry->u.Symlink.set = TRUE;

    status = nfs41_UpcallWaitForReply(entry, VNetRootContext->timeout);
    if (status) goto out;

    status = map_symlink_errors(entry->status);
    RxFreePool(entry);
out:
#ifdef DEBUG_SYMLINK
    DbgEx();
#endif
    return status;
}

NTSTATUS check_nfs41_getreparse_args(
    PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_SUCCESS;
    XXCTL_LOWIO_COMPONENT *FsCtl = &RxContext->LowIoContext.ParamsFor.FsCtl;
    const USHORT HeaderLen = FIELD_OFFSET(REPARSE_DATA_BUFFER,
        SymbolicLinkReparseBuffer.PathBuffer);

    /* must have a filename longer than vnetroot name,
     * or it's trying to operate on the volume itself */
    if (is_root_directory(RxContext)) {
        status = STATUS_INVALID_PARAMETER;
        goto out;
    }
    /* ifs reparse tests expect STATUS_INVALID_PARAMETER,
     * but 'dir' passes a buffer here when querying symlinks
    if (FsCtl->pInputBuffer != NULL) {
        status = STATUS_INVALID_PARAMETER;
        goto out;
    } */
    if (!FsCtl->pOutputBuffer) {
        status = STATUS_INVALID_USER_BUFFER;
        goto out;
    }
    if (!BooleanFlagOn(RxContext->pFcb->Attributes,
            FILE_ATTRIBUTE_REPARSE_POINT)) {
        status = STATUS_NOT_A_REPARSE_POINT;
        DbgP("FILE_ATTRIBUTE_REPARSE_POINT is not set!\n");
        goto out;
    }

    if (FsCtl->OutputBufferLength < HeaderLen) {
        RxContext->InformationToReturn = HeaderLen;
        status = STATUS_BUFFER_TOO_SMALL;
        goto out;
    }
out:
    return status;
}

NTSTATUS nfs41_GetReparsePoint(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status;
    UNICODE_STRING TargetName;
    XXCTL_LOWIO_COMPONENT *FsCtl = &RxContext->LowIoContext.ParamsFor.FsCtl;
    __notnull PNFS41_FOBX Fobx = NFS41GetFobxExtension(RxContext->pFobx);
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PNFS41_V_NET_ROOT_EXTENSION VNetRootContext = 
        NFS41GetVNetRootExtension(SrvOpen->pVNetRoot);
    __notnull PNFS41_NETROOT_EXTENSION pNetRootContext =
        NFS41GetNetRootExtension(SrvOpen->pVNetRoot->pNetRoot);
    nfs41_updowncall_entry *entry;
    const USHORT HeaderLen = FIELD_OFFSET(REPARSE_DATA_BUFFER,
        SymbolicLinkReparseBuffer.PathBuffer);

#ifdef DEBUG_SYMLINK
    DbgEn();
#endif
    status = check_nfs41_getreparse_args(RxContext);
    if (status) goto out;

    TargetName.Buffer = (PWCH)((PBYTE)FsCtl->pOutputBuffer + HeaderLen);
    TargetName.MaximumLength = (USHORT)min(FsCtl->OutputBufferLength - 
        HeaderLen, 0xFFFF);

    status = nfs41_UpcallCreate(NFS41_SYMLINK, &Fobx->sec_ctx, 
        VNetRootContext->session, Fobx->nfs41_open_state,
        pNetRootContext->nfs41d_version, SrvOpen->pAlreadyPrefixedName, &entry);
    if (status) goto out;

    entry->u.Symlink.target = &TargetName;
    entry->u.Symlink.set = FALSE;

    status = nfs41_UpcallWaitForReply(entry, VNetRootContext->timeout);
    if (status) goto out;

    status = map_symlink_errors(entry->status);
    if (status == STATUS_SUCCESS) {
        /* fill in the output buffer */
        PREPARSE_DATA_BUFFER Reparse = (PREPARSE_DATA_BUFFER)
            FsCtl->pOutputBuffer;
        Reparse->ReparseTag = IO_REPARSE_TAG_SYMLINK;
        Reparse->ReparseDataLength = HeaderLen + TargetName.Length -
            REPARSE_DATA_BUFFER_HEADER_SIZE;
        Reparse->Reserved = 0;
        Reparse->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
        /* PrintName and SubstituteName point to the same string */
        Reparse->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
        Reparse->SymbolicLinkReparseBuffer.SubstituteNameLength = 
            TargetName.Length;
        Reparse->SymbolicLinkReparseBuffer.PrintNameOffset = 0;
        Reparse->SymbolicLinkReparseBuffer.PrintNameLength = TargetName.Length;
        print_reparse_buffer(Reparse);

        RxContext->IoStatusBlock.Information = HeaderLen + TargetName.Length;
    } else if (status == STATUS_BUFFER_TOO_SMALL) {
        RxContext->InformationToReturn = HeaderLen + TargetName.Length;
    }
    RxFreePool(entry);
out:
#ifdef DEBUG_SYMLINK
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_FsCtl(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
#ifdef DEBUG_MISC
    DbgEn();
    print_debug_header(RxContext);
#endif
    switch (RxContext->LowIoContext.ParamsFor.FsCtl.FsControlCode) {
    case FSCTL_SET_REPARSE_POINT:
        status = nfs41_SetReparsePoint(RxContext);
        break;

    case FSCTL_GET_REPARSE_POINT:
        status = nfs41_GetReparsePoint(RxContext);
        break;
#ifdef DEBUG_MISC
    default:
        DbgP("FsControlCode: %d\n", 
             RxContext->LowIoContext.ParamsFor.FsCtl.FsControlCode);
#endif
    }
#ifdef DEBUG_MISC
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_CompleteBufferingStateChangeRequest(
    IN OUT PRX_CONTEXT RxContext,
    IN OUT PMRX_SRV_OPEN SrvOpen,
    IN PVOID pContext)
{
    return STATUS_SUCCESS;
}

NTSTATUS nfs41_FsdDispatch (
    IN PDEVICE_OBJECT dev,
    IN PIRP Irp)
{
#ifdef DEBUG_FSDDISPATCH
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
#endif
    NTSTATUS status;

#ifdef DEBUG_FSDDISPATCH
    DbgEn();
    DbgP("CURRENT IRP = %d.%d\n", IrpSp->MajorFunction, IrpSp->MinorFunction);
    if(IrpSp->FileObject)
        DbgP("FileOject %p Filename %wZ\n", IrpSp->FileObject, 
                &IrpSp->FileObject->FileName);
#endif

    if (dev != (PDEVICE_OBJECT)nfs41_dev) {
        print_error("*** not ours ***\n");
        Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT );
        status = STATUS_INVALID_DEVICE_REQUEST;
        goto out;
    }

    status = RxFsdDispatch((PRDBSS_DEVICE_OBJECT)dev,Irp);
    /* AGLO: 08/05/2009 - looks like RxFsdDispatch frees IrpSp */

out:
#ifdef DEBUG_FSDDISPATCH
    DbgP("IoStatus status = 0x%x info = 0x%x\n", Irp->IoStatus.Status, 
         Irp->IoStatus.Information);
    DbgEx();
#endif
    return status;
}

NTSTATUS nfs41_Unimplemented(
    PRX_CONTEXT RxContext)
{
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS nfs41_AreFilesAliased(
    PFCB a,
    PFCB b)
{
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS nfs41_init_ops()
{
    DbgEn();

    ZeroAndInitializeNodeType(&nfs41_ops, RDBSS_NTC_MINIRDR_DISPATCH, 
        sizeof(MINIRDR_DISPATCH));

    nfs41_ops.MRxFlags = (RDBSS_MANAGE_NET_ROOT_EXTENSION |
                            RDBSS_MANAGE_V_NET_ROOT_EXTENSION |
                            RDBSS_MANAGE_FCB_EXTENSION |
                            RDBSS_MANAGE_FOBX_EXTENSION);

    nfs41_ops.MRxSrvCallSize  = 0; // srvcall extension is not handled in rdbss
    nfs41_ops.MRxNetRootSize  = sizeof(NFS41_NETROOT_EXTENSION);
    nfs41_ops.MRxVNetRootSize = sizeof(NFS41_V_NET_ROOT_EXTENSION);
    nfs41_ops.MRxFcbSize      = sizeof(NFS41_FCB);
    nfs41_ops.MRxFobxSize     = sizeof(NFS41_FOBX);

    // Mini redirector cancel routine ..
    
    nfs41_ops.MRxCancel = NULL;

    //
    // Mini redirector Start/Stop. Each mini-rdr can be started or stopped
    // while the others continue to operate.
    //

    nfs41_ops.MRxStart                = nfs41_Start;
    nfs41_ops.MRxStop                 = nfs41_Stop;
    nfs41_ops.MRxDevFcbXXXControlFile = nfs41_DevFcbXXXControlFile;

    //
    // Mini redirector name resolution.
    //

    nfs41_ops.MRxCreateSrvCall       = nfs41_CreateSrvCall;
    nfs41_ops.MRxSrvCallWinnerNotify = nfs41_SrvCallWinnerNotify;
    nfs41_ops.MRxCreateVNetRoot      = nfs41_CreateVNetRoot;
    nfs41_ops.MRxExtractNetRootName  = nfs41_ExtractNetRootName;
    nfs41_ops.MRxFinalizeSrvCall     = nfs41_FinalizeSrvCall;
    nfs41_ops.MRxFinalizeNetRoot     = nfs41_FinalizeNetRoot;
    nfs41_ops.MRxFinalizeVNetRoot    = nfs41_FinalizeVNetRoot;

    //
    // File System Object Creation/Deletion.
    //

    nfs41_ops.MRxCreate            = nfs41_Create;
    nfs41_ops.MRxCollapseOpen      = nfs41_CollapseOpen;
    nfs41_ops.MRxShouldTryToCollapseThisOpen = nfs41_ShouldTryToCollapseThisOpen;
    nfs41_ops.MRxExtendForCache    = nfs41_ExtendForCache;
    nfs41_ops.MRxExtendForNonCache = nfs41_ExtendForCache;
    nfs41_ops.MRxCloseSrvOpen      = nfs41_CloseSrvOpen;
    nfs41_ops.MRxFlush             = nfs41_Flush;
    nfs41_ops.MRxDeallocateForFcb  = nfs41_DeallocateForFcb;
    nfs41_ops.MRxDeallocateForFobx = nfs41_DeallocateForFobx;
    nfs41_ops.MRxIsLockRealizable    = nfs41_IsLockRealizable;

    //
    // File System Objects query/Set
    //

    nfs41_ops.MRxQueryDirectory       = nfs41_QueryDirectory;
    nfs41_ops.MRxQueryVolumeInfo      = nfs41_QueryVolumeInformation;
    nfs41_ops.MRxQueryEaInfo          = nfs41_QueryEaInformation;
    nfs41_ops.MRxSetEaInfo            = nfs41_SetEaInformation;
    nfs41_ops.MRxQuerySdInfo          = nfs41_QuerySecurityInformation;
    nfs41_ops.MRxSetSdInfo            = nfs41_SetSecurityInformation;
    nfs41_ops.MRxQueryFileInfo        = nfs41_QueryFileInformation;
    nfs41_ops.MRxSetFileInfo          = nfs41_SetFileInformation;

    //
    // Buffering state change
    //

    nfs41_ops.MRxComputeNewBufferingState = nfs41_ComputeNewBufferingState;

    //
    // File System Object I/O
    //

    nfs41_ops.MRxLowIOSubmit[LOWIO_OP_READ]            = nfs41_Read;
    nfs41_ops.MRxLowIOSubmit[LOWIO_OP_WRITE]           = nfs41_Write;
    nfs41_ops.MRxLowIOSubmit[LOWIO_OP_SHAREDLOCK]      = nfs41_Lock;
    nfs41_ops.MRxLowIOSubmit[LOWIO_OP_EXCLUSIVELOCK]   = nfs41_Lock;
    nfs41_ops.MRxLowIOSubmit[LOWIO_OP_UNLOCK]          = nfs41_Unlock;
    nfs41_ops.MRxLowIOSubmit[LOWIO_OP_UNLOCK_MULTIPLE] = nfs41_Unlock;
    nfs41_ops.MRxLowIOSubmit[LOWIO_OP_FSCTL]           = nfs41_FsCtl;

    //
    // Miscellanous
    //

    nfs41_ops.MRxCompleteBufferingStateChangeRequest = 
        nfs41_CompleteBufferingStateChangeRequest;
    nfs41_ops.MRxIsValidDirectory     = nfs41_IsValidDirectory;

    nfs41_ops.MRxTruncate = nfs41_Unimplemented;
    nfs41_ops.MRxZeroExtend = nfs41_Unimplemented;
    nfs41_ops.MRxAreFilesAliased = nfs41_AreFilesAliased;
    nfs41_ops.MRxQueryQuotaInfo = nfs41_Unimplemented;
    nfs41_ops.MRxSetQuotaInfo = nfs41_Unimplemented;
    nfs41_ops.MRxSetVolumeInfo = nfs41_Unimplemented;

    DbgR();
    return(STATUS_SUCCESS);
}

KSTART_ROUTINE fcbopen_main;
VOID fcbopen_main(PVOID ctx)
{
    NTSTATUS status;
    LARGE_INTEGER timeout;

    DbgEn();
    timeout.QuadPart = RELATIVE(SECONDS(30));
    while(1) {
        PLIST_ENTRY pEntry;
        nfs41_fcb_list_entry *cur;
        status = KeDelayExecutionThread(KernelMode, TRUE, &timeout);
        ExAcquireFastMutex(&fcblistLock);
        pEntry = openlist.head.Flink;
        while (!IsListEmpty(&openlist.head)) {
            PNFS41_NETROOT_EXTENSION pNetRootContext;
            nfs41_updowncall_entry *entry;
            FILE_BASIC_INFORMATION binfo;
            PNFS41_FCB nfs41_fcb;
            cur = (nfs41_fcb_list_entry *)CONTAINING_RECORD(pEntry, 
                    nfs41_fcb_list_entry, next);

#ifdef DEBUG_TIME_BASED_COHERENCY
            DbgP("fcbopen_main: Checking attributes for fcb=%p "
                "change_time=%llu skipping=%d\n", cur->fcb, 
                cur->ChangeTime, cur->skip);
#endif
            if (cur->skip) goto out;
            pNetRootContext = 
                NFS41GetNetRootExtension(cur->fcb->pNetRoot);
            /* place an upcall for this srv_open */
            status = nfs41_UpcallCreate(NFS41_FILE_QUERY, 
                &cur->nfs41_fobx->sec_ctx, cur->session, 
                cur->nfs41_fobx->nfs41_open_state,
                pNetRootContext->nfs41d_version, NULL, &entry);
            if (status) goto out;

            entry->u.QueryFile.InfoClass = FileBasicInformation;
            entry->buf = &binfo;
            entry->buf_len = sizeof(binfo);

            status = nfs41_UpcallWaitForReply(entry, UPCALL_TIMEOUT_DEFAULT);
            if (status) goto out;

            if (cur->ChangeTime != entry->ChangeTime) {
                ULONG flag = DISABLE_CACHING;
                PMRX_SRV_OPEN srv_open;
                PLIST_ENTRY psrvEntry;
#ifdef DEBUG_TIME_BASED_COHERENCY
                DbgP("fcbopen_main: old ctime=%llu new_ctime=%llu\n", 
                    cur->ChangeTime, entry->ChangeTime);
#endif
                cur->ChangeTime = entry->ChangeTime;
                cur->skip = TRUE;
                psrvEntry = &cur->fcb->SrvOpenList;
                psrvEntry = psrvEntry->Flink;
                while (!IsListEmpty(&cur->fcb->SrvOpenList)) {
                    srv_open = (PMRX_SRV_OPEN)CONTAINING_RECORD(psrvEntry, 
                            MRX_SRV_OPEN, SrvOpenQLinks);
                    if (srv_open->DesiredAccess & 
                            (FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA)) {
#ifdef DEBUG_TIME_BASED_COHERENCY
                        DbgP("fcbopen_main: ************ Invalidate the cache %wZ"
                             "************\n", srv_open->pAlreadyPrefixedName);
#endif
                        RxIndicateChangeOfBufferingStateForSrvOpen(
                            cur->fcb->pNetRoot->pSrvCall, srv_open,
                            srv_open->Key, ULongToPtr(flag));
                    }
                    if (psrvEntry->Flink == &cur->fcb->SrvOpenList) {
#ifdef DEBUG_TIME_BASED_COHERENCY
                        DbgP("fcbopen_main: reached end of srvopen for fcb %p\n",
                            cur->fcb);
#endif
                        break;
                    }
                    psrvEntry = psrvEntry->Flink;
                };
            }
            nfs41_fcb = (PNFS41_FCB)cur->fcb->Context;
            nfs41_fcb->changeattr = entry->ChangeTime;
            RxFreePool(entry);
out:
            if (pEntry->Flink == &openlist.head) {
#ifdef DEBUG_TIME_BASED_COHERENCY
                DbgP("fcbopen_main: reached end of the fcb list\n");
#endif
                break;
            }
            pEntry = pEntry->Flink;
        }
        ExReleaseFastMutex(&fcblistLock);
    }
    DbgEx();
}

NTSTATUS DriverEntry(
    IN PDRIVER_OBJECT drv, 
    IN PUNICODE_STRING path)
{
    NTSTATUS status;
    ULONG flags = 0, i;
    UNICODE_STRING dev_name, user_dev_name;
    PNFS41_DEVICE_EXTENSION dev_exts;
    TIME_FIELDS jan_1_1970 = {1970, 1, 1, 0, 0, 0, 0, 0};
    ACCESS_MASK mask = 0;
    OBJECT_ATTRIBUTES oattrs;

    DbgEn();

    status = RxDriverEntry(drv, path);
    if (status != STATUS_SUCCESS) {
        print_error("RxDriverEntry failed: %08lx\n", status);
        goto out;
    }

    RtlInitUnicodeString(&dev_name, NFS41_DEVICE_NAME);
    SetFlag(flags, RX_REGISTERMINI_FLAG_DONT_PROVIDE_MAILSLOTS);

    status = nfs41_init_ops();
    if (status != STATUS_SUCCESS) {
        print_error("nfs41_init_ops failed to initialize dispatch table\n");
        goto out;
    }

    DbgP("calling RxRegisterMinirdr\n");
    status = RxRegisterMinirdr(&nfs41_dev, drv, &nfs41_ops, flags, &dev_name,
                sizeof(NFS41_DEVICE_EXTENSION), 
                FILE_DEVICE_NETWORK_FILE_SYSTEM, FILE_REMOTE_DEVICE);
    if (status != STATUS_SUCCESS) {
        print_error("RxRegisterMinirdr failed: %08lx\n", status);
        goto out;
    }
    nfs41_dev->Flags |= DO_BUFFERED_IO;

    dev_exts = (PNFS41_DEVICE_EXTENSION)
        ((PBYTE)(nfs41_dev) + sizeof(RDBSS_DEVICE_OBJECT));

    RxDefineNode(dev_exts, NFS41_DEVICE_EXTENSION);
    dev_exts->DeviceObject = nfs41_dev;
    nfs41_create_volume_info((PFILE_FS_VOLUME_INFORMATION)dev_exts->VolAttrs, 
        &dev_exts->VolAttrsLen);

    RtlInitUnicodeString(&user_dev_name, NFS41_SHADOW_DEVICE_NAME);
    DbgP("calling IoCreateSymbolicLink %wZ %wZ\n", &user_dev_name, &dev_name);
    status = IoCreateSymbolicLink(&user_dev_name, &dev_name);
    if (status != STATUS_SUCCESS) {
        print_error("Device name IoCreateSymbolicLink failed: %08lx\n", status);
        goto out_unregister;
    }

    KeInitializeEvent(&upcallEvent, SynchronizationEvent, FALSE );
    ExInitializeFastMutex(&upcallLock);
    ExInitializeFastMutex(&downcallLock);
    ExInitializeFastMutex(&xidLock);
    ExInitializeFastMutex(&openOwnerLock);
    ExInitializeFastMutex(&fcblistLock);
    InitializeListHead(&upcall.head);
    InitializeListHead(&downcall.head);
    InitializeListHead(&openlist.head);
    InitializeObjectAttributes(&oattrs, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
    status = PsCreateSystemThread(&dev_exts->openlistHandle, mask, 
        &oattrs, NULL, NULL, &fcbopen_main, NULL);
    if (status != STATUS_SUCCESS)
        goto out_unregister;

    drv->DriverUnload = nfs41_driver_unload;

    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
        drv->MajorFunction[i] = (PDRIVER_DISPATCH)nfs41_FsdDispatch;

    RtlTimeFieldsToTime(&jan_1_1970, &unix_time_diff);

out_unregister:
    if (status != STATUS_SUCCESS)
        RxUnregisterMinirdr(nfs41_dev);
out:
    DbgEx();
    return status;
}

VOID nfs41_driver_unload(IN PDRIVER_OBJECT drv)
{
    PRX_CONTEXT RxContext;
    NTSTATUS    status;
    UNICODE_STRING dev_name, pipe_name;

    DbgEn();

    RxContext = RxCreateRxContext(NULL, nfs41_dev, RX_CONTEXT_FLAG_IN_FSP);
    if (RxContext == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto unload;
    }
    status = RxStopMinirdr(RxContext, &RxContext->PostRequest);
    RxDereferenceAndDeleteRxContext(RxContext);

unload:
    RtlInitUnicodeString(&dev_name, NFS41_SHADOW_DEVICE_NAME);
    status = IoDeleteSymbolicLink(&dev_name);
    if (status != STATUS_SUCCESS) {
        print_error("couldn't delete device symbolic link\n");
    }
    RtlInitUnicodeString(&pipe_name, NFS41_SHADOW_PIPE_NAME);
    status = IoDeleteSymbolicLink(&pipe_name);
    if (status != STATUS_SUCCESS) {
        print_error("couldn't delete pipe symbolic link\n");
    }
    RxUnload(drv);

    DbgP("driver unloaded %p\n", drv);
    DbgR();
}
