/* 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 "daemon_debug.h"
#include "nfs41.h"
#include "nfs41_ops.h"
#include "util.h"


#define SBLVL 3 /* dprintf level for superblock logging */


static __inline int compare_fsid(
    IN const nfs41_fsid *lhs,
    IN const nfs41_fsid *rhs)
{
    if (lhs->major > rhs->major) return 1;
    if (lhs->major < rhs->major) return -1;
    if (lhs->minor > rhs->minor) return 1;
    if (lhs->minor < rhs->minor) return -1;
    return 0;
}


/* nfs41_superblock */
static int superblock_create(
    IN const nfs41_fsid *fsid,
    OUT nfs41_superblock **superblock_out)
{
    int status = NO_ERROR;
    nfs41_superblock *superblock;

    dprintf(SBLVL, "creating superblock for fsid(%llu,%llu)\n",
        fsid->major, fsid->minor);

    superblock = calloc(1, sizeof(nfs41_superblock));
    if (superblock == NULL) {
        status = GetLastError();
        eprintf("failed to allocate superblock "
            "for fsid(%llu,%llu)\n", fsid->major, fsid->minor);
        goto out;
    }

    memcpy(&superblock->fsid, fsid, sizeof(nfs41_fsid));
    InitializeSRWLock(&superblock->lock);

    *superblock_out = superblock;
out:
    return status;
}

static int get_superblock_attrs(
    IN nfs41_session *session,
    IN nfs41_superblock *superblock,
    IN nfs41_path_fh *file)
{
    int status;
    bitmap4 attr_request;
    nfs41_file_info info = { 0 };

    attr_request.arr[0] = FATTR4_WORD0_SUPPORTED_ATTRS |
        FATTR4_WORD0_LINK_SUPPORT | FATTR4_WORD0_SYMLINK_SUPPORT |
        FATTR4_WORD0_ACLSUPPORT | FATTR4_WORD0_CANSETTIME |
        FATTR4_WORD0_CASE_INSENSITIVE | FATTR4_WORD0_CASE_PRESERVING |
        FATTR4_WORD0_MAXREAD | (uint32_t)(FATTR4_WORD0_MAXWRITE);
    attr_request.arr[1] = FATTR4_WORD1_FS_LAYOUT_TYPE |
        FATTR4_WORD1_TIME_DELTA;
    attr_request.count = 2;

    info.supported_attrs = &superblock->supported_attrs;
    info.time_delta = &superblock->time_delta;

    status = nfs41_getattr(session, file, &attr_request, &info);
    if (status) {
        eprintf("nfs41_getattr() failed with %s when fetching attributes for "
            "fsid(%llu,%llu)\n", nfs_error_string(status),
            superblock->fsid.major, superblock->fsid.minor);
        goto out;
    }

    if (info.maxread)
        superblock->maxread = info.maxread;
    else
        superblock->maxread = session->fore_chan_attrs.ca_maxresponsesize;

    if (info.maxwrite)
        superblock->maxwrite = info.maxwrite;
    else
        superblock->maxwrite = session->fore_chan_attrs.ca_maxrequestsize;

    superblock->layout_types = info.fs_layout_types;
    superblock->aclsupport = info.aclsupport;
    superblock->link_support = info.link_support;
    superblock->symlink_support = info.symlink_support;
    superblock->case_preserving = info.case_preserving;
    superblock->case_insensitive = info.case_insensitive;

    if (bitmap_isset(&info.attrmask, 0, FATTR4_WORD0_CANSETTIME))
        superblock->cansettime = info.cansettime;
    else /* cansettime is not supported, try setting them anyway */
        superblock->cansettime = 1;

    /* if time_delta is not supported, default to 1s */
    if (!bitmap_isset(&info.attrmask, 1, FATTR4_WORD1_TIME_DELTA))
        superblock->time_delta.seconds = 1;

    dprintf(SBLVL, "attributes for fsid(%llu,%llu): "
        "maxread=%llu, maxwrite=%llu, layout_types: 0x%X, "
        "cansettime=%u, time_delta={%llu,%u}, aclsupport=%u, "
        "link_support=%u, symlink_support=%u, case_preserving=%u, "
        "case_insensitive=%u\n",
        superblock->fsid.major, superblock->fsid.minor,
        superblock->maxread, superblock->maxwrite,
        superblock->layout_types, superblock->cansettime,
        superblock->time_delta.seconds, superblock->time_delta.nseconds,
        superblock->aclsupport, superblock->link_support,
        superblock->symlink_support, superblock->case_preserving,
        superblock->case_insensitive);
out:
    return status;
}


/* nfs41_superblock_list */
#define superblock_entry(pos) list_container(pos, nfs41_superblock, entry)

static int superblock_compare(
    const struct list_entry *entry,
    const void *value)
{
    const nfs41_superblock *superblock = superblock_entry(entry);
    return compare_fsid(&superblock->fsid, (const nfs41_fsid*)value);
}

static nfs41_superblock* find_superblock(
    IN nfs41_superblock_list *superblocks,
    IN const nfs41_fsid *fsid)
{
    struct list_entry *entry;
    entry = list_search(&superblocks->head, fsid, superblock_compare);
    return entry ? superblock_entry(entry) : NULL;
}

void nfs41_superblock_list_init(
    IN nfs41_superblock_list *superblocks)
{
    list_init(&superblocks->head);
    InitializeSRWLock(&superblocks->lock);
}

void nfs41_superblock_list_free(
    IN nfs41_superblock_list *superblocks)
{
    struct list_entry *entry, *tmp;

    dprintf(SBLVL, "nfs41_superblock_list_free()\n");

    list_for_each_tmp(entry, tmp, &superblocks->head)
        free(superblock_entry(entry));
}


int nfs41_superblock_for_fh(
    IN nfs41_session *session,
    IN const nfs41_fsid *fsid,
    IN const nfs41_fh *parent OPTIONAL,
    OUT nfs41_path_fh *file)
{
    int status = NFS4_OK;
    nfs41_server *server = client_server(session->client);
    nfs41_superblock_list *superblocks = &server->superblocks;
    nfs41_superblock *superblock;

    dprintf(SBLVL, "--> nfs41_superblock_for_fh(fsid(%llu,%llu))\n",
        fsid->major, fsid->minor);

    /* compare with the parent's fsid, and use that if it matches */
    if (parent && parent->superblock &&
            compare_fsid(fsid, &parent->superblock->fsid) == 0) {
        file->fh.superblock = parent->superblock;
        dprintf(SBLVL, "using superblock from parent\n");
        goto out;
    }

    /* using a shared lock, search for an existing superblock */
    AcquireSRWLockShared(&superblocks->lock);
    superblock = find_superblock(superblocks, fsid);
    ReleaseSRWLockShared(&superblocks->lock);

    if (superblock) {
        dprintf(SBLVL, "found existing superblock in server list "
            "[shared lock]\n");
    } else {
        AcquireSRWLockExclusive(&superblocks->lock);
        /* must search again under an exclusive lock, in case another thread
         * created it after our first search */
        superblock = find_superblock(superblocks, fsid);
        if (superblock) {
            dprintf(SBLVL, "found newly created superblock in server list "
                "[exclusive lock]\n");
        } else {
            /* create the superblock */
            status = superblock_create(fsid, &superblock);
            if (status == NO_ERROR) /* add it to the list */
                list_add_tail(&superblocks->head, &superblock->entry);
        }
        ReleaseSRWLockExclusive(&superblocks->lock);
    }

    if (status == NO_ERROR && superblock->supported_attrs.count == 0) {
        /* exclusive lock on the superblock while fetching attributes */
        AcquireSRWLockExclusive(&superblock->lock);
        if (superblock->supported_attrs.count == 0)
            status = get_superblock_attrs(session, superblock, file);
        ReleaseSRWLockExclusive(&superblock->lock);
    }

    file->fh.superblock = superblock;
out:
    dprintf(SBLVL, "<-- nfs41_superblock_for_fh() returning %p, status %d\n",
        file->fh.superblock, status);
    return status;
}

void nfs41_superblock_supported_attrs(
    IN nfs41_superblock *superblock,
    IN OUT bitmap4 *attrs)
{
    uint32_t i, count = 0;

    AcquireSRWLockShared(&superblock->lock);
    for (i = 0; i < 3; i++) {
        attrs->arr[i] &= superblock->supported_attrs.arr[i];
        if (attrs->arr[i])
            count = i+1;
    }
    attrs->count = min(attrs->count, count);
    ReleaseSRWLockShared(&superblock->lock);
}

void nfs41_superblock_space_changed(
    IN nfs41_superblock *superblock)
{
    /* invalidate cached volume size attributes */
    AcquireSRWLockExclusive(&superblock->lock);
    superblock->cache_expiration = 0;
    ReleaseSRWLockExclusive(&superblock->lock);
}
