/*  Various utility functions

    ©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 - GMT offset now configurable.
    30-Nov-1999 - reason() to give error codes as strings.
                - NFS_wt_err() made into a global function.
                - Protection/Mode filters rewritten.
*/

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

#include <stdio.h>
#include <string.h>

#ifdef AMITCP
 #include <amitcp/socketbasetags.h>
#else
 #include <bsdsocket/socketbasetags.h>
#endif

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

/*
    Reports the reason for an error encountered during use of
     bsdsocket.library.
*/
void printSocketError(STRPTR s)
{
    int err = Errno();
    struct TagItem taglist[] = {
        {SBTM_GETVAL(SBTC_ERRNOSTRPTR), 0},
        {TAG_END, 0}
    };

    taglist[0].ti_Data = err;
    if (0 == SocketBaseTagList(taglist)) {
        printf("%s: %s\n", s, (STRPTR)taglist[0].ti_Data);
    } else {
        printf("%s: (unkown reason - %d)\n", s, err);
    }
}


/* Maps Amiga error codes to NFS errors
   Remember some special cases:

    User is not the owner of a file they're modifiying, nor root
        NFSERR_PERM

    If ERROR_OBJECT_WRONG_TYPE
        Either NFSERR_ISDIR or NFSERR_NOTDIR, depending on context

    Anything else, NFSERR_IO

    Note that an error code will *always* be returned, even if what was passed
        wasn't an error itself
*/

nfsstat NFS_err(LONG errno)
{
    switch (errno) {
        case ERROR_OBJECT_IN_USE:
            return NFSERR_ACCES;

        case ERROR_OBJECT_EXISTS:
            return NFSERR_EXIST;

        case ERROR_DIR_NOT_FOUND:
        case ERROR_OBJECT_NOT_FOUND:
            return NFSERR_NOENT;

        case ERROR_OBJECT_TOO_LARGE:
            return NFSERR_IO;

        case ERROR_INVALID_COMPONENT_NAME:
            return NFSERR_INVAL;

        case ERROR_DISK_NOT_VALIDATED:
        case ERROR_DISK_WRITE_PROTECTED:
            return NFSERR_ROFS;

        case ERROR_RENAME_ACROSS_DEVICES:
            return NFSERR_ACCES;

        case ERROR_DIRECTORY_NOT_EMPTY:
            return NFSERR_NOTEMPTY;

        case ERROR_DISK_FULL:
            return NFSERR_NOSPC;

        case ERROR_DELETE_PROTECTED:
        case ERROR_WRITE_PROTECTED:
        case ERROR_READ_PROTECTED:
            return NFSERR_ACCES;

        /* Hmhmmhmh... if I ever handle links properly, y'know... */
        case ERROR_IS_SOFT_LINK:
        case ERROR_OBJECT_LINKED:
            return NFSERR_IO;

        case ERROR_NOT_A_DOS_DISK:
        case ERROR_NO_DISK:
        case ERROR_DEVICE_NOT_MOUNTED:
        case ERROR_SEEK_ERROR:
            return NFSERR_IO;

        default:
            return NFSERR_IO;
    }
}


/* This is in so much it's starting to look messy... let's make it a
    function in its own right!
   NFS is more specific than AmigaDOS when it comes to things being
    of the wrong type. If the error is a wrong type one, we return
    the more suitable code supplied (eg. NFSERR_ISDIR)
*/
nfsstat NFS_wt_err(nfsstat wrongtype)
{
    LONG err = IoErr();
    if (err == ERROR_OBJECT_WRONG_TYPE)
        return wrongtype;
    else
        return NFS_err(err);
}


STRPTR reason(nfsstat err)
{
    switch (err) {
        case NFS_OK:
            return "no error";

        case NFSERR_PERM:
            return "not owner";

        case NFSERR_NOENT:
            return "no such file or directory";

        case NFSERR_IO:
            return "I/O error";

        case NFSERR_NXIO:
            return "no such device or address";

        case NFSERR_ACCES:
            return "permission denied";

        case NFSERR_EXIST:
            return "file exists";

        case NFSERR_NODEV:
            return "no such device";

        case NFSERR_NOTDIR:
            return "not a directory";

        case NFSERR_ISDIR:
            return "is a directory";

        case NFSERR_INVAL:
            return "invalid argument";

        case NFSERR_FBIG:
            return "file too large";

        case NFSERR_NOSPC:
            return "no space left on device";

        case NFSERR_ROFS:
            return "read-only file system";

        case NFSERR_NAMETOOLONG:
            return "file name too long";

        case NFSERR_NOTEMPTY:
            return "directory not empty";

        case NFSERR_DQUOT:
            return "disc quota exceeded";

        case NFSERR_STALE:
            return "stale NFS file handle";

        case NFSERR_WFLUSH:
            return "write cache flushed";

        default:
            /* No, this isn't thread safe */
            {
                static char buf[17 + 11];
                sprintf(buf, "unknown error (%d)", err);
                return buf;
            }
    }
}

/* Protection bits */

/* These used to be far more verbose, but these are just tidier */

/* Does a basic conversion of Amiga protection bits to Unix mode
    - all special bits are ignored */
u_int NFS_mode(ULONG prot)
{
    /* For the user bits, 0 means allowed - this is reversed
        for the others. D'oh! */

    return ((~prot & (0x07 << 1)) << 5)
            | ((prot & (0x07 << 9)) >> 6)
            | ((prot & (0x07 << 13)) >> 13);
}

/* Does a basic mapping of Unix modes to Amiga protection bits -
    all special bits are ignored (including 'delete') */
ULONG Amiga_prot(u_int mode)
{
    /* Same craziness as above */
    return ((~mode & 0700) >> 5) | ((mode & 0070) << 6)
        | ((mode & 0007) << 13);
}

/* Date handling */

#define AMIGA_GMT_OFFSET_SECONDS (global.amigaGMTOffset * 60)

/* This is the Unix date that AmigaOS time begins from */
#define UNIX_AMIGA_EPOCH (252460800)

/* Convert an Amiga DateStamp to an nfstime */
void Unix_date(struct DateStamp *ds, struct nfstime *tv)
{
    /* We need the time as seconds from Jan 1 1970 */
    tv->seconds = (ds->ds_Days * 24 * 60
        + ds->ds_Minute) * 60
        + UNIX_AMIGA_EPOCH /* Offset Unix->Amiga epoch */
        - AMIGA_GMT_OFFSET_SECONDS;

    tv->useconds = ds->ds_Tick * (1000 / TICKS_PER_SECOND);

    /* We'd better normalize it */
    while (tv->useconds >= 1000) {
        tv->useconds -= 1000;
        tv->seconds++;
    }

    /* The Amiga stores all dates in local time, and there's no way
        of finding what the correct offset is, so we make the user
        tell us.
    */
}

/* Convert an nfstime to an Amiga DateStamp */
void Amiga_date(struct nfstime *tv, struct DateStamp *ds)
{
    #define MPD (60 * 24)

    LONG ami_seconds = tv->seconds - UNIX_AMIGA_EPOCH
        + AMIGA_GMT_OFFSET_SECONDS,
        ami_minutes = ami_seconds / 60;

    ds->ds_Days = ami_minutes / MPD;
    ds->ds_Minute = ami_minutes % MPD;

    ami_seconds -= ami_minutes * 60;

    ds->ds_Tick = tv->useconds / (1000 / TICKS_PER_SECOND)
        + ami_seconds * TICKS_PER_SECOND;

    /* So, we'd better normalise it... */
    #define TPM (60 * TICKS_PER_SECOND)
    while (ds->ds_Tick >= TPM) {
        ds->ds_Tick -= TPM;
        ds->ds_Minute++;
    }
    #undef TPM

    while (ds->ds_Minute >= MPD) {
        ds->ds_Minute -= MPD;
        ds->ds_Days++;
    }

    #undef MPD

}


/* Convert Amiga to NFS file types */
ftype NFS_type(LONG type)
{
    switch (type) {
        case ST_ROOT:
        case ST_USERDIR:
        case ST_LINKDIR:
            return NFDIR;

        case ST_FILE:
            return NFREG;

        case ST_SOFTLINK:
            return NFLNK;

        default:
            if (type < 0)
                return NFREG;
            else if (type > 0)
                return NFDIR;
            else
                return NFNON;
    }
}


/* Is this a valid Amiga filename component? */
BOOL is_valid_filename(STRPTR name)
{
    if (!name)
        return FALSE;

    if ((FilePart(name) == name) && (strlen(name) > 0))
        return TRUE;

    return FALSE;
}


/* Get the full name from a diropargs */
STRPTR name_of_diropargs(STRPTR filename,
    struct LocalFile *dir_lf, nfsstat *stat)
{
    int length;
    STRPTR full_name;

    if (!is_valid_filename(filename)) {
        *stat = NFSERR_ACCES;
        printf("Illegal reference to file '%s'\n", filename);
        return NULL;
    }

    DEBUG(printf("Referring to '%s' in '%s'\n", filename, dir_lf->lf_Name));

    /* Is it actually a directory? */
    if (dir_lf->lf_Type != NFDIR) {
        *stat = NFSERR_NOTDIR;
        return NULL;
    }

    /* The code above may not be perfect if we're using symlinks... */

    /* So, I guess we need a buffer to concatenate those in */
    length = strlen(filename) + strlen(dir_lf->lf_Name)
        + 2; /* Terminating NULL and separating slash */

    if (full_name = AllocVecPooled(length)) {
        strcpy(full_name, dir_lf->lf_Name);
        if (!AddPart(full_name, filename, length)) {
            /* This *shouldn't* happen */
            FreeVecPooled(full_name);
            *stat = NFSERR_NAMETOOLONG;
            return NULL;
        }
    } else
        *stat = NFSERR_IO; /* No memory? */

    return full_name;
}

