/* Copyright (c) 2010, 2011
 * The Regents of the University of Michigan
 * All Rights Reserved
 * 
 * Olga Kornievskaia <aglo@umich.edu>
 * Casey Bodley <cbodley@umich.edu>
 *
 * Permission is granted to use, copy and redistribute this software
 * for noncommercial education and research purposes, so long as no
 * fee is charged, and so long as the name of the University of Michigan
 * is not used in any advertising or publicity pertaining to the use
 * or distribution of this software without specific, written prior
 * authorization.  Permission to modify or otherwise create derivative
 * works of this software is not granted.
 *
 * This software is provided as is, without representation or warranty
 * of any kind either express or implied, including without limitation
 * the implied warranties of merchantability, fitness for a particular
 * purpose, or noninfringement.  The Regents of the University of
 * Michigan shall not be liable for any damages, including special,
 * indirect, incidental, or consequential damages, with respect to any
 * claim arising out of or in connection with the use of the software,
 * even if it has been or is hereafter advised of the possibility of
 * such damages.
 */

#include <Windows.h>
#include <stdio.h>
#include <strsafe.h>

#include "nfs41_ops.h"
#include "delegation.h"
#include "from_kernel.h"
#include "daemon_debug.h"
#include "upcall.h"
#include "util.h"


static int create_open_state(
    IN const char *path,
    IN uint32_t open_owner_id,
    OUT nfs41_open_state **state_out)
{
    int status;
    nfs41_open_state *state;

    state = calloc(1, sizeof(nfs41_open_state));
    if (state == NULL) {
        status = GetLastError();
        goto out;
    }

    InitializeSRWLock(&state->path.lock);
    if (FAILED(StringCchCopyA(state->path.path, NFS41_MAX_PATH_LEN, path))) {
        status = ERROR_BUFFER_OVERFLOW;
        goto out_free;
    }
    state->path.len = (unsigned short)strlen(state->path.path);
    path_fh_init(&state->file, &state->path);
    path_fh_init(&state->parent, &state->path);
    last_component(state->path.path, state->file.name.name, &state->parent.name);

    StringCchPrintfA((LPSTR)state->owner.owner, NFS4_OPAQUE_LIMIT, "%u", 
        open_owner_id);
    state->owner.owner_len = (uint32_t)strlen((const char*)state->owner.owner);
    state->ref_count = 1;
    list_init(&state->locks.list);
    list_init(&state->client_entry);

    *state_out = state;
    status = NO_ERROR;
out:
    return status;

out_free:
    free(state);
    goto out;
}

static void open_state_free(
    IN nfs41_open_state *state)
{
    struct list_entry *entry, *tmp;

    /* free associated lock state */
    list_for_each_tmp(entry, tmp, &state->locks.list)
        free(list_container(entry, nfs41_lock_state, open_entry));
    if (state->delegation.state)
        nfs41_delegation_deref(state->delegation.state);
    free(state);
}


/* open state reference counting */
void nfs41_open_state_ref(
    IN nfs41_open_state *state)
{
    const LONG count = InterlockedIncrement(&state->ref_count);

    dprintf(2, "nfs41_open_state_ref(%s) count %d\n", state->path.path, count);
}

void nfs41_open_state_deref(
    IN nfs41_open_state *state)
{
    const LONG count = InterlockedDecrement(&state->ref_count);

    dprintf(2, "nfs41_open_state_deref(%s) count %d\n", state->path.path, count);
    if (count == 0)
        open_state_free(state);
}

/* 8.2.5. Stateid Use for I/O Operations
 * o  If the client holds a delegation for the file in question, the
 *    delegation stateid SHOULD be used.
 * o  Otherwise, if the entity corresponding to the lock-owner (e.g., a
 *    process) sending the I/O has a byte-range lock stateid for the
 *    associated open file, then the byte-range lock stateid for that
 *    lock-owner and open file SHOULD be used.
 * o  If there is no byte-range lock stateid, then the OPEN stateid for
 *    the open file in question SHOULD be used.
 * o  Finally, if none of the above apply, then a special stateid SHOULD
 *    be used. */
void nfs41_open_stateid_arg(
    IN nfs41_open_state *state,
    OUT stateid_arg *arg)
{
    arg->open = state;
    arg->delegation = NULL;

    AcquireSRWLockShared(&state->lock);

    if (state->delegation.state) {
        nfs41_delegation_state *deleg = state->delegation.state;
        AcquireSRWLockShared(&deleg->lock);
        if (deleg->status == DELEGATION_GRANTED) {
            arg->type = STATEID_DELEG_FILE;
            memcpy(&arg->stateid, &deleg->state.stateid, sizeof(stateid4));
        }
        ReleaseSRWLockShared(&deleg->lock);

        if (arg->type == STATEID_DELEG_FILE)
            goto out;

        dprintf(2, "delegation recalled, waiting for open stateid..\n");

        /* wait for nfs41_delegation_to_open() to recover open stateid */
        while (!state->do_close)
            SleepConditionVariableSRW(&state->delegation.cond, &state->lock,
                INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED);
    }

    if (state->locks.stateid.seqid) {
        memcpy(&arg->stateid, &state->locks.stateid, sizeof(stateid4));
        arg->type = STATEID_LOCK;
    } else if (state->do_close) {
        memcpy(&arg->stateid, &state->stateid, sizeof(stateid4));
        arg->type = STATEID_OPEN;
    } else {
        memset(&arg->stateid, 0, sizeof(stateid4));
        arg->type = STATEID_SPECIAL;
    }
out:
    ReleaseSRWLockShared(&state->lock);
}

/* client list of associated open state */
static void client_state_add(
    IN nfs41_open_state *state)
{
    nfs41_client *client = state->session->client;

    EnterCriticalSection(&client->state.lock);
    list_add_tail(&client->state.opens, &state->client_entry);
    LeaveCriticalSection(&client->state.lock);
}

static void client_state_remove(
    IN nfs41_open_state *state)
{
    nfs41_client *client = state->session->client;

    EnterCriticalSection(&client->state.lock);
    list_remove(&state->client_entry);
    LeaveCriticalSection(&client->state.lock);
}

static int do_open(
    IN OUT nfs41_open_state *state,
    IN uint32_t create,
    IN uint32_t createhow,
    IN uint32_t mode,
    IN bool_t try_recovery,
    OUT nfs41_file_info *info)
{
    open_claim4 claim;
    stateid4 open_stateid;
    open_delegation4 delegation = { 0 };
    nfs41_delegation_state *deleg_state = NULL;
    int status;

    claim.claim = CLAIM_NULL;
    claim.u.null.filename = &state->file.name;

    status = nfs41_open(state->session, &state->parent, &state->file,
        &state->owner, &claim, state->share_access, state->share_deny,
        create, createhow, mode, TRUE, &open_stateid, &delegation, info);
    if (status)
        goto out;

    /* allocate delegation state and register it with the client */
    nfs41_delegation_granted(state->session, &state->parent,
        &state->file, &delegation, TRUE, &deleg_state);
    if (deleg_state) {
        deleg_state->srv_open = state->srv_open;
        dprintf(1, "do_open: received delegation: saving srv_open = %x\n", 
            state->srv_open);
    }

    AcquireSRWLockExclusive(&state->lock);
    /* update the stateid */
    memcpy(&state->stateid, &open_stateid, sizeof(open_stateid));
    state->do_close = 1;
    state->delegation.state = deleg_state;
    ReleaseSRWLockExclusive(&state->lock);
out:
    return status;
}

static int open_or_delegate(
    IN OUT nfs41_open_state *state,
    IN uint32_t create,
    IN uint32_t createhow,
    IN uint32_t mode,
    IN bool_t try_recovery,
    OUT nfs41_file_info *info)
{
    int status;

    /* check for existing delegation */
    status = nfs41_delegate_open(state, create, mode, info);

    /* get an open stateid if we have no delegation stateid */
    if (status)
        status = do_open(state, create, createhow, mode, try_recovery, info);

    state->pnfs_last_offset = info->size ? info->size - 1 : 0;

    /* register the client's open state on success */
    if (status == NFS4_OK)
        client_state_add(state);
    return status;
}


/* NFS41_OPEN */
static int parse_open(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
{
    int status;
    open_upcall_args *args = &upcall->args.open;

    status = get_name(&buffer, &length, &args->path);
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->access_mask, sizeof(ULONG));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->access_mode, sizeof(ULONG));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->file_attrs, sizeof(ULONG));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->create_opts, sizeof(ULONG));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->disposition, sizeof(ULONG));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->open_owner_id, sizeof(LONG));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->mode, sizeof(DWORD));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->srv_open, sizeof(HANDLE));
    if (status) goto out;

    dprintf(1, "parsing NFS41_OPEN: filename='%s' access mask=%d "
        "access mode=%d\n\tfile attrs=0x%x create attrs=0x%x "
        "(kernel) disposition=%d\n\topen_owner_id=%d mode=%o "
        "srv_open=%x\n", args->path, args->access_mask, args->access_mode, 
        args->file_attrs, args->create_opts, args->disposition, 
        args->open_owner_id, args->mode, args->srv_open);
    print_disposition(2, args->disposition);
    print_access_mask(2, args->access_mask);
    print_share_mode(2, args->access_mode);
    print_create_attributes(2, args->create_opts);
out:
    return status;
}

static BOOLEAN open_for_attributes(uint32_t type, ULONG access_mask, 
                                   ULONG disposition)
{
    if (type == NF4DIR) {
        if (disposition == FILE_OPEN || disposition == FILE_OVERWRITE) {
            dprintf(1, "Opening a directory\n");
            return TRUE;
        } else {
            dprintf(1, "Creating a directory\n");
            return FALSE;
        }
    }

    if ((access_mask & FILE_READ_DATA) ||
            (access_mask & FILE_WRITE_DATA) ||
            (access_mask & FILE_APPEND_DATA) ||
            (access_mask & FILE_EXECUTE))
        return FALSE;
    else {
        dprintf(1, "Open call that wants to manage attributes\n");
        return TRUE;
    }
}

static int map_disposition_2_nfsopen(ULONG disposition, int in_status, bool_t persistent,
                                     uint32_t *create, uint32_t *createhowmode,
                                     uint32_t *last_error)
{
    int status = NO_ERROR;
    if (disposition == FILE_SUPERSEDE) {
        if (in_status == NFS4ERR_NOENT) {
            *create = OPEN4_CREATE;
            *last_error = ERROR_FILE_NOT_FOUND;
        }
        else // we need to truncate the file file then open it
            *create = OPEN4_NOCREATE;
    } else if (disposition == FILE_CREATE) {
        // if lookup succeeded which means the file exist, return an error
        if (!in_status)
            status = ERROR_FILE_EXISTS;
        else {
            *create = OPEN4_CREATE;
            if (persistent)
                *createhowmode = GUARDED4;
            else
                *createhowmode = EXCLUSIVE4_1;
        }
    } else if (disposition == FILE_OPEN) {
        if (in_status == NFS4ERR_NOENT)
            status = ERROR_FILE_NOT_FOUND;
        else
            *create = OPEN4_NOCREATE;
    } else if (disposition == FILE_OPEN_IF) {
        if (in_status == NFS4ERR_NOENT) {
            dprintf(1, "creating new file\n");
            *create = OPEN4_CREATE;
            *last_error = ERROR_FILE_NOT_FOUND;
        } else {
            dprintf(1, "opening existing file\n");
            *create = OPEN4_NOCREATE;
        }
    } else if (disposition == FILE_OVERWRITE) {
        if (in_status == NFS4ERR_NOENT)
            status = ERROR_FILE_NOT_FOUND;
        //truncate file
        *create = OPEN4_CREATE;
    } else if (disposition == FILE_OVERWRITE_IF) {
        if (in_status == NFS4ERR_NOENT)
            *last_error = ERROR_FILE_NOT_FOUND;
        //truncate file
        *create = OPEN4_CREATE;
    }
    return status;
}

static void map_access_2_allowdeny(ULONG access_mask, ULONG access_mode,
                                   uint32_t *allow, uint32_t *deny)
{
    if ((access_mask & FILE_WRITE_DATA) &&
            ((access_mask & FILE_READ_DATA) ||
            (access_mask & FILE_EXECUTE)))
        *allow = OPEN4_SHARE_ACCESS_BOTH;
    else if ((access_mask & FILE_READ_DATA) ||
            (access_mask & FILE_EXECUTE))
        *allow = OPEN4_SHARE_ACCESS_READ;
    else if (access_mask & FILE_WRITE_DATA ||
            (access_mask & FILE_APPEND_DATA) ||
            (access_mask & FILE_WRITE_ATTRIBUTES))
        *allow = OPEN4_SHARE_ACCESS_WRITE;
#define FIX_ALLOW_DENY_WIN2NFS_CONVERSION
#ifdef FIX_ALLOW_DENY_WIN2NFS_CONVERSION
    if ((access_mode & FILE_SHARE_READ) &&
            (access_mode & FILE_SHARE_WRITE))
        *deny = OPEN4_SHARE_DENY_NONE;
    else if (access_mode & FILE_SHARE_READ)
        *deny = OPEN4_SHARE_DENY_WRITE;
    else if (access_mode & FILE_SHARE_WRITE)
        *deny = OPEN4_SHARE_DENY_READ;
    else
        *deny = OPEN4_SHARE_DENY_BOTH;
#else
    // AGLO: 11/13/2009.
    // readonly file that is being opened for reading with a
    // share read mode given above logic translates into deny
    // write and linux server does not allow it.
    *deny = OPEN4_SHARE_DENY_NONE;
#endif
}

static int check_execute_access(nfs41_open_state *state)
{
    uint32_t supported, access;
    int status = nfs41_access(state->session, &state->file,
        ACCESS4_EXECUTE | ACCESS4_READ, &supported, &access);
    if (status) {
        eprintf("nfs41_access() failed with %s for %s\n", 
            nfs_error_string(status), state->path.path);
        status = ERROR_ACCESS_DENIED;
    } else if ((supported & ACCESS4_EXECUTE) == 0) {
        /* server can't verify execute access;
         * for now, assume that read access is good enough */
        if ((supported & ACCESS4_READ) == 0 || (access & ACCESS4_READ) == 0) {
            eprintf("server can't verify execute access, and user does "
                "not have read access to file %s\n", state->path.path);
            status = ERROR_ACCESS_DENIED;
        }
    } else if ((access & ACCESS4_EXECUTE) == 0) {
        dprintf(1, "user does not have execute access to file %s\n", 
            state->path.path);
        status = ERROR_ACCESS_DENIED;
    } else
        dprintf(2, "user has execute access to file\n");
    return status;
}

static int handle_open(nfs41_upcall *upcall)
{
    int status = 0;
    open_upcall_args *args = &upcall->args.open;
    nfs41_open_state *state;
    nfs41_file_info info = { 0 };

    status = create_open_state(args->path, args->open_owner_id, &state);
    if (status) {
        eprintf("create_open_state(%d) failed with %d\n",
            args->open_owner_id, status);
        goto out;
    }
    state->srv_open = args->srv_open;

    // first check if windows told us it's a directory
    if (args->create_opts & FILE_DIRECTORY_FILE)
        state->type = NF4DIR;
    else
        state->type = NF4REG;

    // always do a lookup
    status = nfs41_lookup(upcall->root_ref, nfs41_root_session(upcall->root_ref),
        &state->path, &state->parent, &state->file, &info, &state->session);

    if (status == ERROR_REPARSE) {
        uint32_t depth = 0;
        /* one of the parent components was a symlink */
        do {
            if (++depth > NFS41_MAX_SYMLINK_DEPTH) {
                status = ERROR_TOO_MANY_LINKS;
                goto out_free_state;
            }

            /* replace the path with the symlink target's */
            status = nfs41_symlink_target(state->session,
                &state->parent, &state->path);
            if (status) {
                /* can't do the reparse if we can't get the target */
                eprintf("nfs41_symlink_target() failed with %d\n", status);
                goto out_free_state;
            }

            /* redo the lookup until it doesn't return REPARSE */
            status = nfs41_lookup(upcall->root_ref, state->session,
                &state->path, &state->parent, NULL, NULL, &state->session);
        } while (status == ERROR_REPARSE);

        if (status == NO_ERROR || status == ERROR_FILE_NOT_FOUND) {
            abs_path_copy(&args->symlink, &state->path);
            status = NO_ERROR;
            upcall->last_error = ERROR_REPARSE;
            args->symlink_embedded = TRUE;
        }
        goto out_free_state;
    }

    // now if file/dir exists, use type returned by lookup
    if (status == NO_ERROR) {
        if (info.type == NF4DIR) {
            dprintf(2, "handle_nfs41_open: DIRECTORY\n");
            if (args->create_opts & FILE_NON_DIRECTORY_FILE) {
                eprintf("trying to open directory %s as a file\n", 
                    state->path.path);
                status = ERROR_ACCESS_DENIED;
                goto out_free_state;
            }
        } else if (info.type == NF4REG) {
            dprintf(2, "handle nfs41_open: FILE\n");
            if (args->create_opts & FILE_DIRECTORY_FILE) {
                eprintf("trying to open file %s as a directory\n",
                    state->path.path);
#ifdef NOTEPAD_OPEN_FILE_AS_DIRFILE_FIXED
                status = ERROR_ACCESS_DENIED;
                goto out_free_state;
#endif
            }
        } else if (info.type == NF4LNK) {
            dprintf(2, "handle nfs41_open: SYMLINK\n");
            if (args->create_opts & FILE_OPEN_REPARSE_POINT) {
                /* continue and open the symlink itself, but we need to
                 * know if the target is a regular file or directory */
                nfs41_file_info target_info;
                int target_status = nfs41_symlink_follow(upcall->root_ref,
                    state->session, &state->file, &target_info);
                if (target_status == NO_ERROR && target_info.type == NF4DIR)
                    info.symlink_dir = TRUE;
            } else {
                /* replace the path with the symlink target */
                status = nfs41_symlink_target(state->session,
                    &state->file, &args->symlink);
                if (status) {
                    eprintf("nfs41_symlink_target() for %s failed with %d\n",
                        args->path, status);
                } else {
                    /* tell the driver to call RxPrepareToReparseSymbolicLink() */
                    upcall->last_error = ERROR_REPARSE;
                    args->symlink_embedded = FALSE;
                }
                goto out_free_state;
            }
        } else
            dprintf(2, "handle_open(): unsupported type=%d\n", info.type);
        state->type = info.type;
    } else if (status != ERROR_FILE_NOT_FOUND)
        goto out_free_state;

    /* XXX: this is a hard-coded check for the open arguments we see from
     * the CreateSymbolicLink() system call.  we respond to this by deferring
     * the CREATE until we get the upcall to set the symlink.  this approach
     * is troublesome for two reasons:
     * -an application might use these exact arguments to create a normal
     *   file, and we would return success without actually creating it
     * -an application could create a symlink by sending the FSCTL to set
     *   the reparse point manually, and their open might be different.  in
     *   this case we'd create the file on open, and need to remove it
     *   before creating the symlink */
    if (args->disposition == FILE_CREATE &&
            args->access_mask == (FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | DELETE) &&
            args->access_mode == 0 &&
            args->create_opts & FILE_OPEN_REPARSE_POINT) {
        /* fail if the file already exists */
        if (status == NO_ERROR) {
            status = ERROR_FILE_EXISTS;
            goto out_free_state;
        }

        /* defer the call to CREATE until we get the symlink set upcall */
        dprintf(1, "trying to create a symlink, deferring create\n");

        /* because of WRITE_ATTR access, be prepared for a setattr upcall;
         * will crash if the superblock is null, so use the parent's */
        state->file.fh.superblock = state->parent.fh.superblock;

        status = NO_ERROR;
    } else if (open_for_attributes(state->type, args->access_mask, 
                args->disposition)) {
        if (status) {
            dprintf(1, "nfs41_lookup failed with %d\n", status);
            goto out_free_state;
        }

        nfs_to_basic_info(&info, &args->basic_info);
        nfs_to_standard_info(&info, &args->std_info);
        args->mode = info.mode;
        args->changeattr = info.change;
    } else {
        uint32_t create = 0, createhowmode = 0;

        map_access_2_allowdeny(args->access_mask, args->access_mode,
            &state->share_access, &state->share_deny);
        status = map_disposition_2_nfsopen(args->disposition, status, 
                    state->session->flags & CREATE_SESSION4_FLAG_PERSIST, 
                    &create, &createhowmode, &upcall->last_error);
        if (status)
            goto out_free_state;

        if (args->access_mask & FILE_EXECUTE && state->file.fh.len) {
            status = check_execute_access(state);
            if (status)
                goto out_free_state;
        }

        if (create == OPEN4_CREATE && (args->create_opts & FILE_DIRECTORY_FILE)) {
            status = nfs41_create(state->session, NF4DIR, args->mode, NULL, 
                &state->parent, &state->file);
            args->std_info.Directory = 1;
            args->created = status == NFS4_OK ? TRUE : FALSE;
        } else {
            status = open_or_delegate(state, create, createhowmode, args->mode, 
                TRUE, &info);
            if (status == NFS4_OK) {
                nfs_to_basic_info(&info, &args->basic_info);
                nfs_to_standard_info(&info, &args->std_info);
                args->mode = info.mode;
                if (state->delegation.state)
                    args->deleg_type = state->delegation.state->state.type;
            }
        }
        if (status) {
            dprintf(1, "%s failed with %s\n", (create == OPEN4_CREATE && 
                (args->create_opts & FILE_DIRECTORY_FILE))?"nfs41_create":"nfs41_open",
                nfs_error_string(status));
            status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND);
            goto out_free_state;
        }
    }
    upcall->state_ref = state;
    nfs41_open_state_ref(upcall->state_ref);
out:
    return status;
out_free_state:
    nfs41_open_state_deref(state);
    goto out;
}

static int marshall_open(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
{
    int status;
    open_upcall_args *args = &upcall->args.open;

    status = safe_write(&buffer, length, &args->basic_info, sizeof(args->basic_info));
    if (status) goto out;
    status = safe_write(&buffer, length, &args->std_info, sizeof(args->std_info));
    if (status) goto out;
    status = safe_write(&buffer, length, &upcall->state_ref, sizeof(HANDLE));
    if (status) goto out;
    status = safe_write(&buffer, length, &args->mode, sizeof(args->mode));
    if (status) goto out;
    status = safe_write(&buffer, length, &args->changeattr, sizeof(args->changeattr));
    if (status) goto out;
    status = safe_write(&buffer, length, &args->deleg_type, sizeof(args->deleg_type));
    if (status) goto out;
    if (upcall->last_error == ERROR_REPARSE) {
        unsigned short len = (args->symlink.len + 1) * sizeof(WCHAR);
        status = safe_write(&buffer, length, &args->symlink_embedded, sizeof(BOOLEAN));
        if (status) goto out;
        status = safe_write(&buffer, length, &len, sizeof(len));
        if (status) goto out;
        /* convert args->symlink to wchar */
        if (*length <= len || !MultiByteToWideChar(CP_UTF8, 0,
            args->symlink.path, args->symlink.len,
            (LPWSTR)buffer, len / sizeof(WCHAR))) {
            status = ERROR_BUFFER_OVERFLOW;
            goto out;
        }
    }
    dprintf(2, "NFS41_OPEN: passing open_state=0x%p mode %o changeattr 0x%x\n", 
        upcall->state_ref, args->mode, args->changeattr);
out:
    return status;
}

static void cancel_open(IN nfs41_upcall *upcall)
{
    int status = NFS4_OK;
    open_upcall_args *args = &upcall->args.open;
    nfs41_open_state *state = upcall->state_ref;

    dprintf(1, "--> cancel_open('%s')\n", args->path);

    if (upcall->state_ref == NULL || 
            upcall->state_ref == INVALID_HANDLE_VALUE)
        goto out; /* if handle_open() failed, the state was already freed */

    if (state->do_close) {
        stateid_arg stateid;
        stateid.open = state;
        stateid.delegation = NULL;
        stateid.type = STATEID_OPEN;
        memcpy(&stateid.stateid, &state->stateid, sizeof(stateid4));

        status = nfs41_close(state->session, &state->file, &stateid);
        if (status)
            dprintf(1, "cancel_open: nfs41_close() failed with %s\n",
                nfs_error_string(status));

    } else if (args->created) {
        const nfs41_component *name = &state->file.name;
        /* break any delegations and truncate before REMOVE */
        nfs41_delegation_return(state->session, &state->file,
            OPEN_DELEGATE_WRITE, TRUE);
        status = nfs41_remove(state->session, &state->parent,
            name, state->file.fh.fileid);
        if (status)
            dprintf(1, "cancel_open: nfs41_remove() failed with %s\n",
                nfs_error_string(status));
    }

    /* remove from the client's list of state for recovery */
    client_state_remove(state);
    nfs41_open_state_deref(state);
out:
    status = nfs_to_windows_error(status, ERROR_INTERNAL_ERROR);
    dprintf(1, "<-- cancel_open() returning %d\n", status);
}


/* NFS41_CLOSE */
static int parse_close(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
{
    int status;
    close_upcall_args *args = &upcall->args.close;

    status = safe_read(&buffer, &length, &args->remove, sizeof(BOOLEAN));
    if (status) goto out;
    status = safe_read(&buffer, &length, &args->srv_open, sizeof(HANDLE));
    if (status) goto out;
    if (args->remove) {
        status = get_name(&buffer, &length, &args->path);
        if (status) goto out;
        status = safe_read(&buffer, &length, &args->renamed, sizeof(BOOLEAN));
        if (status) goto out;
    }

    dprintf(1, "parsing NFS41_CLOSE: remove=%d srv_open=%x renamed=%d "
        "filename='%s'\n", args->remove, args->srv_open, args->renamed, 
        args->remove ? args->path : "");
out:
    return status;
}

static int do_nfs41_close(nfs41_open_state *state)
{
    int status;
    stateid_arg stateid;
    stateid.open = state;
    stateid.delegation = NULL;
    stateid.type = STATEID_OPEN;
    memcpy(&stateid.stateid, &state->stateid, sizeof(stateid4));

    status = nfs41_close(state->session, &state->file, &stateid);
    if (status) {
        dprintf(1, "nfs41_close() failed with error %s.\n",
            nfs_error_string(status));
        status = nfs_to_windows_error(status, ERROR_INTERNAL_ERROR);
    }

    return status;
}

static int handle_close(nfs41_upcall *upcall)
{
    int status = NFS4_OK, rm_status = NFS4_OK;
    close_upcall_args *args = &upcall->args.close;
    nfs41_open_state *state = upcall->state_ref;

    /* return associated file layouts if necessary */
    if (state->type == NF4REG)
        pnfs_layout_state_close(state->session, state, args->remove);

    if (state->srv_open == args->srv_open)
        nfs41_delegation_remove_srvopen(state->session, &state->file);

    if (args->remove) {
        nfs41_component *name = &state->file.name;

        if (args->renamed) {
            dprintf(1, "removing a renamed file %s\n", name->name);
            create_silly_rename(&state->path, &state->file.fh, name);
            status = do_nfs41_close(state);
            if (status)
                goto out;
            else
                state->do_close = 0;
        }

        /* break any delegations and truncate before REMOVE */
        nfs41_delegation_return(state->session, &state->file,
            OPEN_DELEGATE_WRITE, TRUE);

        dprintf(1, "calling nfs41_remove for %s\n", name->name);
        rm_status = nfs41_remove(state->session, &state->parent,
            name, state->file.fh.fileid);
        if (rm_status) {
            dprintf(1, "nfs41_remove() failed with error %s.\n",
                nfs_error_string(rm_status));
            rm_status = nfs_to_windows_error(rm_status, ERROR_INTERNAL_ERROR);
        }
    }

    if (state->do_close) {
        status = do_nfs41_close(state);
    }
out:
    /* remove from the client's list of state for recovery */
    client_state_remove(state);

    if (status || !rm_status)
        return status;
    else
        return rm_status;
}

static void cleanup_close(nfs41_upcall *upcall)
{
    /* release the initial reference from create_open_state() */
    nfs41_open_state_deref(upcall->state_ref);
}


const nfs41_upcall_op nfs41_op_open = {
    parse_open,
    handle_open,
    marshall_open,
    cancel_open
};
const nfs41_upcall_op nfs41_op_close = {
    parse_close,
    handle_close,
    NULL,
    NULL,
    cleanup_close
};
