/*  Routines to manage a list of local files and their NFS representation

    ©1998, 1999 Joseph Walton

    This software is distributed under the terms of the GNU General Public
    License; either version 2 of the License, or (at your option) any
    later version.
*/

/*
     29-Sep-1999 - Hashtables now used - should go faster.
      4-Oct-1999 - Now prunes unused entries from memory.
     30-Nov-1999 - Changes to integrate with new RPC code.
     28-Feb-2000 - Calls impose_configuration before returning anything.
     14-Oct-2000 - Uses global.flushint
*/

#include "nfsd.h"
#include "handle_list.h"
#include "auth.h"
#include "memory.h"
#include "util.h"
#include "dirlist.h"
#include "hashtable.h"

#include <stdio.h>
#include <stdlib.h> /* rand() prototype */
#include <ctype.h> /* tolower() */
#include <string.h>

#include <exec/memory.h>
#include <clib/alib_protos.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/timer.h>

/* The file to store persistent inode data in */
#define INODE_FILE "nfsd.inode"

/*
    lf_list is a list of all LocalFiles in the system.
    handle_ht is a hash from handles to nodes in the list.
    name_ht is a case-insensitive hash from names to nodes.
    Iterate over lf_list - use the others just for lookup
*/
static struct List lf_list;
static struct HashTable *handle_ht;
static struct HashTable *name_ht;

/* This needs to be unique when used */
static u_int next_id = 0;

/* Internal prototypes */
static void fh_fillin(nfs_fh *fh);
static u_int new_inode(void);
static struct LocalFile *create_localfile(STRPTR name);
static void free_localfile(struct LocalFile *lf);
static void remove_localfile(struct LocalFile *lf);

/* Prototypes for hashtable callbacks */
HashFunc handleHash; // We export this so file_cache can use it
static EqualsFunc handleEquals;
static HashFunc stringHash;
static EqualsFunc stringEquals;

/* Initialise the internal data for handle_list */
BOOL init_handle_list()
{
    handle_ht = htMakeNew(&handleHash, &handleEquals);
    name_ht = htMakeNew(&stringHash, &stringEquals);
    if (handle_ht && name_ht) {
        NewList(&lf_list);
        return TRUE;
    }
    return FALSE;
}

/* next_id needs to be unique amongst subsequent runs - we
    need to keep a persistent copy somewhere */

/* Okay, I'm writing it in binary rather than text. This is probably
    the Wrong Thing, but I'm in no mood right now. */

#define SNI sizeof(next_id)

/* Fetch the base inode from persistent store */
void persistent_inode_fetch()
{
    BPTR file;
    if (file = Open(INODE_FILE, MODE_OLDFILE)) {
        if (Read(file, &next_id, SNI) != SNI) {
            next_id = 0;
            PrintFault(IoErr(), "failed to read from " INODE_FILE);
        }

        Close(file);
    } else
        PrintFault(IoErr(), "unable to open " INODE_FILE);

    /* Was it set? */
    if (!next_id) {
        next_id = 1;
        puts("Unable to get persistent inode data. This will cause problems unless this is a first run.");
    }
    printf("First inode is %d.\n", next_id);
}

/* Place the current inode in persistent store */
BOOL persistent_inode_store()
{
    BPTR file;
    BOOL result = TRUE;
    if (file = Open(INODE_FILE, MODE_NEWFILE)) {
        if (SNI != Write(file, &next_id, SNI)) {
            result = FALSE;
            PrintFault(IoErr(), "failed to write inode data");
        }
        Close(file);
    } else {
        result = FALSE;
        PrintFault(IoErr(), "failed to open " INODE_FILE " for writing");
    }

    return result;
}

#undef SNI

/* Create a new node for a given filename */
static struct LocalFile *create_localfile(STRPTR name)
{
    struct LocalFile *nlf;
    if (name == NULL)
        return NULL;

    nlf = AllocVecPooled(sizeof(struct LocalFile));
    if (nlf) {
        nlf->lf_Name = DupString(name);

        if (nlf->lf_Name) {
            fh_fillin(&nlf->lf_NFSHandle); /* Give it a new handle */
            nlf->lf_FileID = 0; /* This will be filled in when it's used */
            nlf->lf_IsMount = FALSE;
            nlf->lf_Type = NFNON; /* This as well */
            nlf->lf_Lock = NULL;
            nlf->lf_DEList = NULL;

            /* These values, if set, will override the real ones */
            nlf->lf_CfgSerial = 0;
            nlf->lf_ForcedUID = -1;
            nlf->lf_ForcedGID = -1;
            nlf->lf_ForcedProtection = -1;

            /* We now impose_configuration on demand */

            GetSysTime(&(nlf->lf_LastUsed));

            /* And add it to the list */
            AddTail(&lf_list, (struct Node *)nlf);

            /* Put it in the hashtables */
            htAdd(handle_ht, &nlf->lf_NFSHandle, nlf);
            htAdd(name_ht, nlf->lf_Name, nlf);

            DEBUG(printf("Created handle for '%s'\n", nlf->lf_Name));

            /* fh_print(&(nlf->lf_NFSHandle)); */
        } else {
            FreeVecPooled(nlf);
            nlf = NULL;
        }
    }
    return nlf;
}


/* Free a LocalFile and any resources directly attached to it.
    Make sure you're not using it for list navigation at this point!
*/
static void free_localfile(struct LocalFile *lf)
{
    if (lf) {
        FreeVecPooled(lf->lf_Name);
        if (lf->lf_DEList) {
            free_delist_nodes(lf->lf_DEList);
            FreeVecPooled(lf->lf_DEList);
        }
        FreeVecPooled(lf);
    }
}

/* Remove a LocalFile from internal structures it may belong to.
    Afterwards it is safe to free it.
*/
static void remove_localfile(struct LocalFile *lf)
{
    if (lf) {
        Remove((struct Node *)lf); /* Assumes all LocalFiles are in a list */

        /* Remove it from the hashtables as well */
        if (!htRemove(handle_ht, &lf->lf_NFSHandle)
            || !htRemove(name_ht, lf->lf_Name))
        {
            printf("Internal error - %lx was not found in the hashtables", lf);
        }
    }
}


/* Search the list for a given filename (case insensitive) */
struct LocalFile *find_namedlocalfile(STRPTR name)
{
    struct LocalFile *lf;

    if (!name)
        return NULL;

    lf = htGet(name_ht, name);

    impose_configuration(lf);

    return lf;
}

/* Get a structure for the given filename, creating one
    if need be */
struct LocalFile *get_namedlocalfile(STRPTR name)
{
    struct LocalFile *lf = find_namedlocalfile(name);

    /* Nothing found? Create a new one, then. */
    if (!lf) {
        lf = create_localfile(name);
    }

    impose_configuration(lf);

    return lf;
}

/* Search the list for a given NFS file handle */
struct LocalFile *find_handle(nfs_fh *fh)
{
    struct LocalFile *lf;

    if (!fh)
        return NULL;

    lf = (struct LocalFile *)htGet(handle_ht, fh);

    if (!lf) {
        /* We couldn't find this handle, so print it.
            If it ever prunes old handles, take this out, because it'll
            happen fairly often. */
        DEBUG(puts("Stale handle received "));
        DEBUG(fh_print(fh));
    }

    impose_configuration(lf);
    return lf;
}

/* "Lose" a LocalFile, removing it from the list and freeing the
    memory */
void lose_localfile(STRPTR full_name)
{
    struct LocalFile *lf;

    if (lf = find_namedlocalfile(full_name)) {
        release_lf(lf);
        remove_localfile(lf);
        free_localfile(lf);
    }
}

/* Gets access to a file we've given out a handle to */
struct LocalFile *get_by_handle(nfs_fh *fh, nfsstat *stat, ULONG mode)
{
    BPTR lock;
    struct LocalFile *lf = find_handle(fh);
    if (lf == NULL) { /* We don't know this handle */
        *stat = NFSERR_STALE;
        return NULL;
    }

    lock = Lock(lf->lf_Name, mode);

    /* Why did we fail to lock it? */
    if (lock == NULL) {
        LONG err = IoErr();
        PrintFault(IoErr(), "Unable to lock");
        if ((err == ERROR_OBJECT_NOT_FOUND) || (err == ERROR_DIR_NOT_FOUND)) {
            lf->lf_Type = NFNON; /* It's gone away... */
            /* We could get rid of this straight away, but it'll
                be flushed eventually anyway */
        }
        *stat = NFS_err(err);
        return NULL;
    }

    /* Try and get details of the drive and file */
    if (Info(lock, &global.infodata) && Examine(lock, &global.fib)) {
        correct_struct(lf);
        lf->lf_Lock = lock;
        impose_configuration(lf);
        return lf;
    } else {
        *stat = NFS_err(IoErr());
        UnLock(lock);
        return NULL;
    }
}

/* Give up any interest in this LocalFile */
void release_lf(struct LocalFile *rlf)
{
    if (rlf) {
        if (rlf->lf_Lock) {
            UnLock(rlf->lf_Lock);
            rlf->lf_Lock = NULL;
        }
        /* One day we'll use this to determine what can go */
        GetSysTime(&rlf->lf_LastUsed);
    }
}

/* Flushes any handles that haven't been used for a while from
    the system. Returns true if there would be any purpose in calling
    it again.
*/
BOOL flush_handles()
{
    struct timeval flush_before;
    BOOL handles_pending = FALSE;
    struct LocalFile *lf;

    if (global.flushint == 0) {
        return FALSE;
    }

    /* Now - do we need to flush? */
    GetSysTime(&flush_before);
    flush_before.tv_secs -= global.flushint;

    lf = lf_list.lh_Head;
    /* This is a fairly expensive operation */
    while (lf->lf_Node.mln_Succ) {
        struct LocalFile *next = (struct LocalFile *)lf->lf_Node.mln_Succ;
        /* We never flush mountpoints. It isn't, is it? */
        if (!lf->lf_IsMount) {
            if (CmpTime(&flush_before, &lf->lf_LastUsed) < 0) {
                /* It's not a mountpoint, and it hasn't been used for
                    a fair while */
                DEBUG(printf("Flushing handle for '%s'\n", lf->lf_Name));
                remove_localfile(lf);
                free_localfile(lf);
            } else {
                /* It should be flushed, but not yet */
                DEBUG(printf("'%s' is a pending flushee\n", lf->lf_Name));
                handles_pending = TRUE;
            }
        }
        lf = next;
    }

    return handles_pending;
}

/* Compare two NFS file handles */
BOOL fh_match(nfs_fh *fha, nfs_fh *fhb)
{
    return (BOOL)(memcmp(fha, fhb, NFS_FHSIZE) == 0);
}

/* Copy an NFS handle for a response */
int32 *fh_copy(struct LocalFile *lf, int32 *dest)
{
    if (!lf || !dest) {
        puts("null arguments to fh_copy");
        return NULL;
    }

    CopyMem(&lf->lf_NFSHandle, dest, NFS_FHSIZE);

    return dest + INT32S(NFS_FHSIZE);
}

/* Print a handle, purely for debugging */
void fh_print(nfs_fh *fh)
{
    int i;
    printf("{");
    for (i = 0 ; i < NFS_FHSIZE ; i++) {
        if (i > 0)
            putchar(',');
        printf("%2x", fh->data[i]);
    }
    puts("}");
}

/* Fill in a unique NFS handle */
static void fh_fillin(nfs_fh *fh)
{
    /* As well as all the random data, ensure it actually is unique
        by placing a counter in there */
    static ULONG handle_counter = 1;

    int32 *ptr = (int32 *)fh;

    GetSysTime((struct timeval *)ptr); /* First 8 bytes */
    ptr += 2;

    *ptr++ = handle_counter++;

    /* We have 5 more int32s; pad with noise to make spoofing harder */
    *ptr++ = rand();
    *ptr++ = rand();
    *ptr++ = rand();
    *ptr++ = rand();
    *ptr = rand();
}

/* Give a unique inode */
static u_int new_inode()
{
    return next_id++;
}

/* Correct the type of a LocalFile structure,
    and possibly give it a new inode */
void correct_struct(struct LocalFile *lf)
{
    u_int type = NFS_type(global.fib.fib_DirEntryType);

    if (type != lf->lf_Type) { /* It's changed... */
        lf->lf_Type = type;
        lf->lf_FileID = new_inode();
    }
}

/* Callbacks for hash tables */
ULONG handleHash(APTR handle)
{
    ULONG val = 0;
    nfs_fh *fh = handle;
    int i;

    for (i = 0 ; i < NFS_FHSIZE ; i++) {
        val = (val << 5) ^ (val >> 27) ^ fh->data[i];
    }
    return val;
}

static BOOL handleEquals(APTR a, APTR b)
{
    return fh_match((nfs_fh *)a, (nfs_fh *)b);
}

/* This is a case insensitive hash */
static ULONG stringHash(APTR string)
{
    ULONG val = 0;
    STRPTR ba = (STRPTR)string;
    char c;

    while (c = *ba) {
        val = (val << 5) ^ (val >> 27) ^ tolower(c);
        ba++;
    }
    return val;
}

static BOOL stringEquals(APTR a, APTR b)
{
    return (BOOL)(0 == stricmp((STRPTR)a, (STRPTR)b));
}

