/*  Functions to deal with giving out director listings

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

/*
    We build and cache directory listings before giving them out.
    The process may not be hugely efficient, but NFS places several
    constraints on us that are not natural to the Amiga.
*/

/*
    30-Nov-1999 - Comprehensively rewritten to directly create RPC responses.
*/

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

#include <stdio.h>
#include <string.h>
#include <clib/alib_protos.h>

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

static struct DirEntry *create_direntry(STRPTR name, u_int inode);
static void add_in_order(struct DEList *del, struct DirEntry *nde);

static struct DirEntry *create_direntry(STRPTR name, u_int inode)
{
    struct DirEntry *nde;

    if (!name)
        return NULL;

    if (nde = AllocVecPooled(sizeof(struct DirEntry))) {
        int l = strlen(name);
        int n = INT32S(l);

        int32 *mem = AllocVecPooled(n * SINT32);

        if (mem) {
            /* Touch the last int32 with zero, to pad */
            if (n > 0) {
                *(mem + n - 1) = 0;
            }
            memcpy(mem, name, l);

            nde->de_NameChars = l;
            nde->de_NameInt32s = n;
            nde->de_Name = mem;
            nde->de_Inode = inode;
        } else {
            FreeVecPooled(nde);
            return NULL;
        }
    }

    return nde;
}


void free_delist_nodes(struct DEList *del)
{
    if (del) {
        struct DirEntry *de;
        while (de = (struct DirEntry *)RemTail((struct List *)del)) {
            FreeVecPooled(de->de_Name);
            FreeVecPooled(de);
        }
    }
}

/* Get an up-to-date listing of the directory */
static struct DEList *get_delist(struct LocalFile *dir_lf, nfsstat *status)
{
    struct DEList *del = dir_lf->lf_DEList;
    int buf_length;
    STRPTR path_buf;

    /* First up, is it actually a directory? Well,
        we used to check that first of all, but hardlinks and softlinks
        muddy the water a little more.
       We now try it anyway, giving the appropriate error if something
        goes wrong.
    */

    /* Does this contain a still-valid list? */
    if (del) {
        /* Is it still up-to-date? */
        if (CompareDates(&(global.fib.fib_Date), &(del->del_DateStamp)) >= 0) {
            return del;
        }

        /* No, so let's get rid of the current listing */
        free_delist_nodes(del);
    } else {
        if (del = AllocVecPooled(sizeof(struct DEList))) {
            NewList((struct List *)del); /* Hah! Imagine what would happen if I forgot that... */
        }
    }

    /* This is long enough for anything up to 256 char filenames
        in this directory */
    buf_length = strlen(dir_lf->lf_Name) + 256 + 2;
    path_buf = AllocVecPooled(buf_length);

    /* In fact, if we're being strict, that's about 148 characters more
        than it could be, but we'll plan for the future I think */

    if (!del || !path_buf) {
        FreeVecPooled(path_buf);
        FreeVecPooled(del);
        *status = NFSERR_IO; // No memory?
        return (dir_lf->lf_DEList = NULL);
    }

    /* Okay, let's date this list */
    CopyMem(&(global.fib.fib_Date), &(del->del_DateStamp),
        sizeof(struct DateStamp));

    DEBUG(printf("Reading the directory '%s'\n", dir_lf->lf_Name));

    /* Now let's journey the directory... */
    *status = NFS_OK;

    while (ExNext(dir_lf->lf_Lock, &global.fib)) {
        strcpy(path_buf, dir_lf->lf_Name);
        if (AddPart(path_buf, global.fib.fib_FileName, buf_length)) {
            struct LocalFile *lf = get_namedlocalfile(path_buf);
            if (lf) {
                struct DirEntry *nde;
                correct_struct(lf); /* Give it a correct inode */
                nde = create_direntry(global.fib.fib_FileName, lf->lf_FileID);
                if (nde) {
                    add_in_order(del, nde);
                } else {
                    *status = NFSERR_IO; /* No memory? */
                    break;
                }
            } else {
                *status = NFSERR_IO; /* No memory? */
                break;
            }
        } else {
            /* We alert the user and leave it out of the listing */
            printf("AddPart() failed - the filename '%s' may be too long.\n", global.fib.fib_FileName);
        }
    }

    FreeVecPooled(path_buf);

    /* Did our scan finish unnaturally? */
    if (IoErr() != ERROR_NO_MORE_ENTRIES)
        *status = NFS_wt_err(NFSERR_NOTDIR);

    if (*status != NFS_OK) {
        free_delist_nodes(del);
        FreeVecPooled(del);
        printf("Error reading directory '%s'- %s\n", dir_lf->lf_Name,
            reason(*status));
        del = NULL;
    }

    return (dir_lf->lf_DEList = del);
}

/* Add the DirEntry to the list such that the inodes are in ascending order */
static void add_in_order(struct DEList *del, struct DirEntry *nde)
{
    struct DirEntry *de = (struct DirEntry *)del->del_List.mlh_TailPred;

    while (de->de_Node.mln_Pred) {
        if (de->de_Inode < nde->de_Inode) {
            Insert((struct List *)del,
                (struct Node *)nde, (struct Node *)de);
            return;
        }

        de = (struct DirEntry *)de->de_Node.mln_Pred;
    }

    /* It's earlier than all others */
    AddHead((struct List *)del, (struct Node *)nde);
}


/* Public function to satisfy readdir requests. Returns the number
    of int32s it used, rather than the total number in the response.
*/
int readdir(struct LocalFile *dir_lf, int32 *rptr, int32 *rlimit, u_int cookie,
    u_int max_count)
{
    struct DEList *del;
    nfsstat status;

    /* Use the cookie as the inode we served up to. This avoids
        problems with serving files twice if we rescanned between
        dirattrs */

    DEBUG(printf("Asked for listing of '%s' (%d,%d)\n",
        dir_lf->lf_Name, max_count, cookie));

    /* We want the count in int32s */
    max_count /= SINT32;

    /* Enough for any single entry */
    #define SDE_MAX (4 + INT32S(NFS_MAXNAMELEN))

    if (max_count < SDE_MAX) { /* We can't necessarily fit anything in there */
        *rptr = NFSERR_INVAL;
        return 1;
    }

    #undef SDE_MAX

    del = get_delist(dir_lf, &status);

    if (del) {
        int32 *base_ptr = rptr;
        struct DirEntry *de = del->del_List.mlh_Head;
        DEBUG(int i = 0);

        *rptr++ = P_ENUM(NFS_OK);

        /* Move forwards up to where we left off */
        while (de->de_Node.mln_Succ && (de->de_Inode < cookie))
            de = de->de_Node.mln_Succ;

        /* Now actually copy the entries */
        while (de->de_Node.mln_Succ) {

            max_count -= (4 + de->de_NameInt32s);

            if ((max_count < 0) ||
                ((rlimit - rptr) < (6 + de->de_NameInt32s)))
            {
                *rptr++ = P_ENUM_FALSE; // No entry follows
                *rptr++ = P_ENUM_FALSE; // This is not the end of the directory

                DEBUG(printf("Didn't finish the directory (sent %d)\n", i));
                return (rptr - base_ptr);
            }

            *rptr++ = P_ENUM_TRUE; // An entry follows

            *rptr++ = P_UINT(de->de_Inode);
            *rptr++ = P_UINT(de->de_NameChars);
            CopyMem(de->de_Name, rptr, SINT32 * de->de_NameInt32s);
            rptr += de->de_NameInt32s;

            /* Pop a cookie in there */
            *rptr++ = P_UINT(de->de_Inode + 1);
            DEBUG(i++);

            de = de->de_Node.mln_Succ;
        }

        /* Hey - we finished the directory */
        *rptr++ = P_ENUM_FALSE; // No entry follows
        *rptr++ = P_ENUM_TRUE;  // Yes, it is the end of the directory

        DEBUG(printf("Finished the directory (sent %d)\n", i));

        return (rptr - base_ptr);
    } else {
        *rptr = P_ENUM(status);
        printf("Failed to get directory listing of '%s' - %s\n",
            dir_lf->lf_Name, reason(status));
        return 1;
    }
}

