/*  This is all the smarts behind NFS - or at least glue to the real stuff

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

/*
    30-Nov-1999 - Major changes to support new RPC code.
    23-Feb-2000 - Reads and Writes now go via the file handle cache.
*/

#include "nfsd.h"
#include "memory.h"
#include "util.h"
#include "handle_list.h"
#include "auth.h"
#include "file_cache.h"

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

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

static int32 *fillinfibattrs(int32 *rptr, struct LocalFile *lf);
static nfsstat set_attrs(struct LocalFile *lf, struct sattr *sa, BPTR handle);
static BOOL SeekWithExtend(BPTR handle, LONG offset);

#define HEADER_INT32S (6)
#define FATTR_INT32S (11 + (3 * 2))
#define FHANDLE_INT32S (INT32S(NFS_FHSIZE))

#define DIROPRES_INT32S (HEADER_INT32S + 1 + FHANDLE_INT32S + FATTR_INT32S)

#define ISERR(x) ((x) != NFS_OK)
#define PERR(c,s,x) VERBOSE(print_auth_once(c); printf(s " failed - %s\n", reason(x)))
#define PRES(c,s,x) if(ISERR(x)){PERR(c,s,x)}

int nfsproc_getattr_2_svc(nfs_fh *handle, struct Call *call, int32 *rptr)
{
    nfsstat status;
    struct LocalFile *lf;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_getattr_2_svc"));

    /* Strictly, Unix would usually require only read access to the
        parent directory here. However, that doesn't make much sense
        under AmigaDOS. It's all a little arbitrary at the moment,
        but it sort of makes sense.
    */

    if (lf = get_by_handle(handle, &status, ACCESS_READ)) {
        DEBUG(printf("Getting status of '%s'\n", lf->lf_Name));

        status = check_permission(call, lf, ALLOW_GETATTRS);

        if (status == NFS_OK) {
            *rptr++ = P_ENUM(NFS_OK);
            fillinfibattrs(rptr, lf);
        } else {
            print_auth_once(call);
            printf("Not authorised to get attributes of '%s' - %s\n", lf->lf_Name,
                reason(status));
        }

        release_lf(lf);
    } else {
        PERR(call, "getattr", status);
    }

    if (status == NFS_OK) {
        return HEADER_INT32S + 1 + FATTR_INT32S;
    } else {
        *rptr = P_ENUM(status);
        return HEADER_INT32S + 1;
    }
}

/* This isn't quite atomic at the moment, although this is only
    tickled in a couple of cases */
int nfsproc_setattr_2_svc(struct sattrargs *args, struct Call *call, int32 *rptr)
{
    nfsstat status;
    struct LocalFile *lf;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_setattr_2_svc"));

    if (lf = get_by_handle(args->handle, &status, ACCESS_READ)) {

        /* Are they allowed to set the attributes? */
        status = check_permission(call, lf, ALLOW_SETATTRS);

        if (status == NFS_OK) {
            status = set_attrs(lf, &args->sattr, (BPTR)NULL);

            if (status == NFS_OK) {
                *rptr++ = P_ENUM(NFS_OK);
                fillinfibattrs(rptr, lf);
            } else {
                print_auth_once(call);
                printf("Failed to set attributes of '%s' - %s\n",
                    lf->lf_Name, reason(status));
            }
        } else {
            print_auth_once(call);
            printf("Attribute setting of '%s' not permitted\n", lf->lf_Name);
        }

        release_lf(lf);
    }

    if (status != NFS_OK) {
        *rptr = P_ENUM(status);
        PERR(call, "setattr", status);
        return HEADER_INT32S + 1;
    }

    return HEADER_INT32S + 1 + FATTR_INT32S;
}

int nfsproc_lookup_2_svc(struct diropargs *argp, struct Call *call, int32 *rptr)
{
    nfsstat status;
    struct LocalFile *dir_lf;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_lookup_2_svc"));

    /* They must have read access to the parent directory,
        as well as to the file being looked up */

    if (dir_lf = get_by_handle(argp->dir_handle, &status, ACCESS_READ))
    {
        status = check_permission(call, dir_lf, ALLOW_READ);

        if (status == NFS_OK) {
            STRPTR name = argp->filename;

            /* A few special cases... */
            if (!strcmp(name, ".") || (*name == '\0')) { // "." or ""
                /* They're looking up the directory itself */
                *rptr++ = P_ENUM(NFS_OK);
                rptr = fh_copy(dir_lf, rptr);
                fillinfibattrs(rptr, dir_lf);
            } else {
                STRPTR full_name;
                /* Frankly this should be handled locally. Some clients
                    don't see it quite the same way, however, so I suppose
                    we'd better deal with it. */
                if (!strcmp(name, "..")) {
                    /* They want the parent of this directory */
                    full_name = DupString(dir_lf->lf_Name);

                    if (full_name) {
                        *PathPart(full_name) = 0; /* Can this *ever* return NULL? */
                        DEBUG(printf("Parent of '%s' is '%s'\n",
                            dir_lf->lf_Name, full_name));
                    } else {
                        status = NFSERR_NOENT;
                        print_auth_once(call);
                        printf("Problem getting parent of '%s'\n",
                            dir_lf->lf_Name);
                    }
                } else {
                    full_name = name_of_diropargs(argp->filename, dir_lf, &status);
                }

                /* Did we get anything? */

                if (full_name) {
                    struct LocalFile *nlf = get_namedlocalfile(full_name);

                    if (nlf) {
                        BPTR lock = Lock(full_name, ACCESS_READ);
                        if (lock) {
                            if (Examine(lock, &global.fib)) { /* Everything seems to have worked! */
                                correct_struct(nlf); /* Give it an inode etc. */

                                /* Can they actually read that file? If
                                    not, then they don't get it */

                                status = check_permission(call,
                                    nlf, ALLOW_READ);

                                if (status == NFS_OK) {
                                    *rptr++ = P_ENUM(NFS_OK);
                                    rptr = fh_copy(nlf, rptr);
                                    fillinfibattrs(rptr, nlf);
                                } else {
                                    print_auth_once(call);
                                    printf("Forbidden from looking up '%s' - %s\n", full_name, reason(status));
                                }
                            } else
                                status = NFS_err(IoErr());

                            UnLock(lock);
                        } else
                            status = NFS_wt_err(NFSERR_NOTDIR);
                    } else
                        status = NFSERR_IO; /* No memory? */

                    FreeVecPooled(full_name);
                }
            }
        }

        release_lf(dir_lf);
    }

    if (status != NFS_OK) {
        *rptr = P_ENUM(status);
        PERR(call, "lookup", status);
        return HEADER_INT32S + 1;
    }

    return DIROPRES_INT32S;
}

int nfsproc_read_2_svc(struct readargs *argp, struct Call *call, int32 *rptr,
    int32 *rlimit)
{
    struct CachedFile *cf;
    int len;

    nfsstat status;
    int count = HEADER_INT32S + 1;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_read_2_svc"));

    /* How odd - I'm using a completely different structure for
        this function. Odd. */

    cf = cfGetByHandle(argp->handle, &status);

    if (!cf) {            /* Some kind of error */
        *rptr = P_ENUM(status);
        return HEADER_INT32S + 1;
    }

    /* Obviously they need to have permission here... */
    status = check_permission(call, cf->cf_LocalFile, ALLOW_READ);

    if (status != NFS_OK) {
        *rptr = P_ENUM(status);
        return HEADER_INT32S + 1;
    }

    if (global.verbose) {
        print_auth_once(call);
        printf("%s: Asked to read %d bytes from %d\n",
            cf->cf_LocalFile->lf_Name, argp->count, argp->offset);
    }

    len = argp->count;
    if (len > NFS_MAXDATA)
        len = NFS_MAXDATA;

    if (Seek(cf->cf_Handle, argp->offset, OFFSET_BEGINNING) != -1) {
        int32 *buffer = rptr + 1 + FATTR_INT32S + 1;

        /* This will be NFS_MAXDATA, but let's check anyway */
        int buf_len = (rlimit - buffer) * SINT32;
        if (len > buf_len) {
            len = buf_len;
        }

        len = Read(cf->cf_Handle, buffer, len);
        if (len == -1) { /* Error */
            LONG e = IoErr();
            if (global.verbose) {
                print_auth_once(call);
                PrintFault(e, "Unable to read from file");
            }
            status = NFS_err(e);
        } else {
            if (ExamineFH(cf->cf_Handle, &global.fib)) {
                /* We succeeded */
                *rptr++ = P_ENUM(NFS_OK);
                rptr = fillinfibattrs(rptr, cf->cf_LocalFile);
                *rptr = P_UINT(len);

                count = HEADER_INT32S + 1 + FATTR_INT32S + 1 + INT32S(len);
            } else {
                status = NFS_err(IoErr());
            }
        }
    } else {
        status = NFS_err(IoErr()); /* That's an error, I'm afraid */
    }

    cfRelease(cf);

    if (status != NFS_OK) {
        *rptr = P_ENUM(status);
        PERR(call, "read", status);
    }

    return count;
}

int nfsproc_write_2_svc(struct writeargs *argp, struct Call *call, int32 *rptr)
{
    nfsstat status;
    struct CachedFile *cf;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_write_2_svc"));

    /* Obviously, they must have write access to this file */

    if (cf = cfGetByHandle(argp->handle, &status)) {
        /* Basic debugging output */
        VERBOSE(printf("'%s' - writing %d bytes at %d\n",
            cf->cf_LocalFile->lf_Name, argp->count, argp->offset));

        status = check_permission(call, cf->cf_LocalFile, ALLOW_WRITE);

        if (status == NFS_OK) {
            /* We have permission. So, we try to make sure we have
                a handle we can write with, seek to the correct point,
                write the actual data, and then examine it.
            */
            if (ChangeMode(CHANGE_FH, cf->cf_Handle, MODE_READWRITE)
                && SeekWithExtend(cf->cf_Handle, argp->offset)
                && (Write(cf->cf_Handle, argp->data, argp->count) == argp->count)
                && ExamineFH(cf->cf_Handle, &global.fib))
            {
                /* Success */
                *rptr++ = P_ENUM(NFS_OK);
                fillinfibattrs(rptr, cf->cf_LocalFile);
            } else {
                status = NFS_err(IoErr());
            }
        }

        cfRelease(cf);
    }

    if (status != NFS_OK) {
        *rptr = P_ENUM(status);
        PERR(call, "write", status);
        return HEADER_INT32S + 1;
    }

    return HEADER_INT32S + 1 + FATTR_INT32S;
}

/* When a creation request comes in, attribute it if it is not
    explicitly claiming ownership */
static void impose_auth(struct Call *call, struct sattr *sa)
{
    if (!call->c_auth.isUnix)
        return;

    if (sa->uid == -1)
        sa->uid = call->c_auth.uid;

    if (sa->gid == -1)
        sa->gid = call->c_auth.gid;
}

int nfsproc_create_2_svc(struct createargs *argp, struct Call *call, int32 *rptr)
{
    struct LocalFile *dir_lf;
    nfsstat status;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_create_2_svc"));

    /* We must have write access to the directory,
        and there must not be an existing entity of that name */

    if (dir_lf = get_by_handle(argp->where.dir_handle,
        &status, ACCESS_READ)) {

        status = check_permission(call, dir_lf, ALLOW_WRITE);

        if (status == NFS_OK) {
            STRPTR full_name;

            if (full_name = name_of_diropargs(argp->where.filename, dir_lf,
                &status))
            {
                struct LocalFile *nlf;

                print_auth_once(call);
                printf("Creating file '%s'\n", full_name);

                nlf = get_namedlocalfile(full_name);

                /* There is room for a race condition here, in which we
                    delete a file just created by another process */

                if (nlf->lf_Type == NFNON) { /* It doesn't already exist */
                    BPTR handle;
                    if (handle = Open(full_name, MODE_NEWFILE)) {
                        /* To set things, we need a non-exclusive handle */
                        if (ChangeMode(CHANGE_FH, handle, MODE_READWRITE)) {
                            ExamineFH(handle, &global.fib);
                            correct_struct(nlf); /* Give it an inode */

                            impose_auth(call, &(argp->sattr));
                            status = set_attrs(nlf, &(argp->sattr), handle);

                            if (status == NFS_OK) {
                                /* Succeeded */
                                *rptr++ = P_ENUM(NFS_OK);
                                rptr = fh_copy(nlf, rptr);
                                rptr = fillinfibattrs(rptr, nlf);
                                DEBUG(printf("Created '%s'\n", full_name));
                            } else {
                                printf("Unable to set attributes of new file - %s\n", reason(status));
                            }
                        } else {
                            PrintFault(IoErr(), "Unable to change a read-only handle into a read-write one");
                            status = NFS_err(IoErr());
                        }
                        Close(handle);
                    } else {
                        PrintFault(IoErr(), "Unable to open new file");
                        status = NFS_err(IoErr());
                    }
                } else {
                    printf("File already exists - type is %d\n", nlf->lf_Type);
                    status = NFSERR_EXIST;
                }

                FreeVecPooled(full_name);
            }
        }
        release_lf(dir_lf);
    }

    if (status != NFS_OK) {
        *rptr = P_ENUM(status);
        PERR(call, "create", status);
        return HEADER_INT32S + 1;
    }

    return DIROPRES_INT32S;
}


/* This will delete files and empty directories */
static nfsstat generic_remove(struct diropargs *argp, struct Call *call)
{
    nfsstat status;
    struct LocalFile *dir_lf;

    /* They only need write access to the parent directory, not access
        to the actual file in question. UNIX has the notion of the 'sticky bit'
        to get around this, and require users to own files they want to delete,
        but we don't consider that here.
    */

    if (dir_lf = get_by_handle(argp->dir_handle, &status, ACCESS_READ))
    {
        /* They must be able to write to this directory */
        status = check_permission(call, dir_lf, ALLOW_WRITE);

        if (status == NFS_OK) {
            STRPTR full_name = name_of_diropargs(argp->filename, dir_lf, &status);

            if (full_name) {
                print_auth_once(call);
                printf("Deleting '%s'\n", full_name);

                /* Remove any trace */
                if (DeleteFile(full_name)) {
                    lose_localfile(full_name);
                    /* We succeeded - status is already NFS_OK */
                } else {
                    status = NFS_wt_err(NFSERR_NOTDIR);
                }

                FreeVecPooled(full_name);
            }
        }
        release_lf(dir_lf);
    }

    return status;
}

nfsstat nfsproc_remove_2_svc(struct diropargs *argp, struct Call *call)
{
    nfsstat result;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_remove_2_svc"));

    /* Note that this will delete directories as well as files -
        I do not consider this a security hole */

    result = generic_remove(argp, call);

    PRES(call, "remove", result);
    return result;
}

nfsstat nfsproc_rmdir_2_svc(struct diropargs *argp, struct Call *call)
{
    nfsstat result;
    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_rmdir_2_svc"));

    /* Note that this will delete files as well as directories -
        I do not consider this a security hole */

    result = generic_remove(argp, call);

    PRES(call, "rmdir", result);
    return result;
}


nfsstat nfsproc_rename_2_svc(struct renameargs *argp, struct Call *call)
{
    nfsstat  result;
    struct LocalFile *lf_orig, *lf_new;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_rename_2_svc"));

    /* Security: We must have write access to both the directories
        involved */

    /* Note that write access to the file itself is *not* required */

    if (lf_orig = get_by_handle(argp->from.dir_handle, &result,
        ACCESS_READ)) {

        result = check_permission(call, lf_orig, ALLOW_WRITE);

        if (result == NFS_OK) {
            if (lf_new = get_by_handle(argp->to.dir_handle, &result,
                ACCESS_READ)) {

                result = check_permission(call, lf_new, ALLOW_WRITE);

                if (result == NFS_OK) {
                    STRPTR source_name =
                        name_of_diropargs(argp->from.filename, lf_orig, &result);

                    if (source_name) {
                        STRPTR dest_name =
                            name_of_diropargs(argp->to.filename, lf_new, &result);

                        if (dest_name) {
                            print_auth_once(call);
                            printf("Renaming '%s' to '%s'\n", source_name, dest_name);

                            if (Rename(source_name, dest_name)) {
                                /* Lose any reference to the old name */
                                lose_localfile(source_name);
                                result = NFS_OK;
                            } else
                                result = NFS_err(IoErr());

                            FreeVecPooled(dest_name);
                        }
                        FreeVecPooled(source_name);
                    }
                }

                release_lf(lf_new);
            }
        }

        release_lf(lf_orig);
    }

    PRES(call, "rename", result);
    return result;
}

int nfsproc_mkdir_2_svc(struct createargs *argp, struct Call *call, int32 *rptr)
{
    nfsstat status;
    struct LocalFile *dir_lf;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_mkdir_2_svc"));

    /* Security - the user must have write access to the parent
        directory */

    if (dir_lf = get_by_handle(argp->where.dir_handle,
        &status, ACCESS_READ)) {

        status = check_permission(call, dir_lf, ALLOW_WRITE);

        if (status == NFS_OK) {
            STRPTR full_name = name_of_diropargs(argp->where.filename,
                dir_lf, &status);

            if (full_name) {
                struct LocalFile *nlf;

                print_auth_once(call);
                printf("Making directory '%s'\n", full_name);

                if (nlf = get_namedlocalfile(full_name)) {
                    if (nlf->lf_Lock = CreateDir(full_name) ) {
                        /* We can't do much with an exclusive lock on it,
                            so turn it into a shared one */

                        if (ChangeMode(CHANGE_LOCK, nlf->lf_Lock, ACCESS_READ)) {
                            if (Examine(nlf->lf_Lock, &global.fib)) {
                                correct_struct(nlf);

                                /* Set the attributes */
                                impose_auth(call, &(argp->sattr));
                                status =
                                set_attrs(nlf, &(argp->sattr), (BPTR)NULL);

                                if (status == NFS_OK) {
                                    *rptr++ = P_ENUM(NFS_OK);
                                    rptr = fh_copy(nlf, rptr);
                                    fillinfibattrs(rptr, nlf);
                                }

                            } else {
                                PrintFault(IoErr(), "Unable to Examine() new directory");
                                status = NFS_err(IoErr());
                            }
                        } else {
                            PrintFault(IoErr(), "Could not change exclusive lock to shared lock on new directory");
                            status = NFS_err(IoErr());
                        }

                        UnLock(nlf->lf_Lock);
                        nlf->lf_Lock = NULL;
                    } else {
                        PrintFault(IoErr(), "Could not create directory");
                        status = NFS_err(IoErr());
                    }
                } else
                    status = NFS_err(IoErr());

                FreeVecPooled(full_name);
            }
        }
    }

    if (status != NFS_OK) {
        *rptr = P_ENUM(status);
        PERR(call, "mkdir", status);
        return HEADER_INT32S + 1;
    }

    return DIROPRES_INT32S;
}


int nfsproc_readdir(struct readdirargs *args, struct Call *call,
    int32 *rptr, int32 *rlimit)
{
    struct LocalFile *dir_lf;
    nfsstat status;
    int count;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_readdir_2_svc"));

    /* They need read access to the directory */

    if (dir_lf = get_by_handle(args->dir_handle, &status, ACCESS_READ)) {

        status = check_permission(call, dir_lf, ALLOW_READ);

        if (status == NFS_OK) {
            count = HEADER_INT32S + readdir(dir_lf, rptr, rlimit, args->cookie, args->count);
        } else {
            *rptr = P_ENUM(status);
            count = 7;
        }
        release_lf(dir_lf);
    } else {
        *rptr = P_ENUM(status);
        count = 7;
    }

    PRES(call, "readdir", status);

    return count;
}

int nfsproc_statfs_2_svc(nfs_fh *handle, struct Call *call, int32 *rptr)
{
    int count;
    nfsstat status;
    struct LocalFile *lf;

    DEBUG(print_auth_once(call));
    DEBUG(puts("nfsproc_statfs_2_svc"));

    if (lf = get_by_handle(handle, &status, ACCESS_READ)) {
        status = check_permission(call, lf, ALLOW_READ);

        if (status == NFS_OK) {
            VERBOSE(printf("BPB %ld; NB %ld; BU %ld\n",
                global.infodata.id_BytesPerBlock,
                global.infodata.id_NumBlocks,
                global.infodata.id_NumBlocksUsed));

            *rptr++ = P_ENUM(NFS_OK);

            *rptr++ = P_UINT(NFS_MAXDATA); // preferred transfer size in bytes
            *rptr++ = P_UINT(global.infodata.id_BytesPerBlock); // fundamental file system block size
            *rptr++ = P_UINT(global.infodata.id_NumBlocks); // total blocks in file system

            /* Free blocks in filing system */
            *rptr++ = P_UINT(global.infodata.id_NumBlocks - global.infodata.id_NumBlocksUsed);

            /* Free blocks available to non-superuser (the same under AmigaOS */
            *rptr   = P_UINT(global.infodata.id_NumBlocks - global.infodata.id_NumBlocksUsed);

            count = 6 + 6;
        } else {
            *rptr = P_ENUM(status);
            count = 7;
        }

        release_lf(lf);
    } else {
        *rptr = P_ENUM(status);
        count = 7;
    }

    PRES(call, "statfs", status);

    return count;
}


/* These are private support functions */

/* Fills in a fattr, using global.fib, global.infodata.id_BytesPerBlock
    and a LocalFile (for forced UID/GID/mode) to do so */
static int32 *fillinfibattrs(int32 *rptr, struct LocalFile *lf)
{
    u_int mode;

    /* What type is it? */
    switch (*rptr++ = P_ENUM(NFS_type(global.fib.fib_DirEntryType))) {
        /* V2 mandates this nasty feature - we need to encode the filetype
            in the mode as well */
        case NFDIR:
            mode = NFSMODE_DIR;
            break;

        case NFREG:
            mode = NFSMODE_REG;
            break;

        case NFLNK:
            mode = NFSMODE_LNK;
            break;

        default:
            mode = 0;
            break;
    }

    /* Set the protection bits here... */
    mode |= NFS_mode((lf->lf_ForcedProtection != -1)
                ? lf->lf_ForcedProtection
                : global.fib.fib_Protection);

    *rptr++ = P_UINT(mode);

    /* # hard links - may confuse Unix programs for directories */
    *rptr++ = P_UINT(1);

    /* Do any UID/GID mappings here */
    *rptr++ = P_UINT((lf->lf_ForcedUID != -1) ? lf->lf_ForcedUID : global.fib.fib_OwnerUID);
    *rptr++ = P_UINT((lf->lf_ForcedGID != -1) ? lf->lf_ForcedGID : global.fib.fib_OwnerGID);

    *rptr++ = P_UINT(global.fib.fib_Size); // file size in bytes
    *rptr++ = P_UINT(global.infodata.id_BytesPerBlock); // preferred block size
    *rptr++ = P_UINT(0); // special device # (we ignore this)
    *rptr++ = P_UINT(global.fib.fib_NumBlocks); // Kb of disk used by file
    *rptr++ = P_UINT(0); // device # - pointless unless we allow re-exports

    if ((*rptr++ = lf->lf_FileID) <= 0) /* Should never happen */
        printf("ERROR: Gave out inode %d for %s\n",
            lf->lf_FileID, lf->lf_Name);

    /* We say that the file was creating, modified *and* used at the same
        time - namely, the real last modification time */
    {
        struct nfstime ntime;
        Unix_date(&(global.fib.fib_Date), &ntime);

    /* NFS uses unsigned values here, so we can't refer to anything before
        1970. In fact, we're not checking that at the moment, so
        problems would result if somebody tried it. */

        *rptr++ = P_UINT(ntime.seconds); // time of last access
        *rptr++ = P_UINT(ntime.useconds);
        *rptr++ = P_UINT(ntime.seconds); // time of last modification
        *rptr++ = P_UINT(ntime.useconds);
        *rptr++ = P_UINT(ntime.seconds); // time of last status change
        *rptr++ = P_UINT(ntime.useconds);
    }

    return rptr;
}

/* Try and set the attributes of a file, taking a locked LocalFile structure
    which may or may not be locked on return.
    If one attribute is not properly set,
    it will continue and try and set the others. You may consider this
    to be wrong.
    It assumes the file was Examine()'d beforehand, and will leave it
    with an up-to-date examination afterwards.
    You may optionally pass an open file handle which will be used.
*/
static nfsstat set_attrs(struct LocalFile *lf, struct sattr *sa, BPTR handle)
{
    nfsstat status = NFS_OK;

    VERBOSE(printf("'%s' - desired attributes: %o %d %d %d (%d %d) (%d %d)\n",
        lf->lf_Name,
        sa->mode, sa->uid, sa->gid, sa->size,
        sa->atime.seconds, sa->atime.useconds,
        sa->mtime.seconds, sa->mtime.useconds));

    /* Only some attributes may need setting */
    #define ISSET(x) ((x) != -1)

    /* Here we only change the protection bits we know about.
        This means delete gets left alone. */
    if (ISSET(sa->mode)) {
        #define MASK (FIBF_READ | FIBF_WRITE | FIBF_EXECUTE \
            | FIBF_GRP_READ | FIBF_GRP_WRITE \
            | FIBF_GRP_EXECUTE | FIBF_OTR_READ \
            | FIBF_OTR_WRITE | FIBF_OTR_EXECUTE)

        LONG prot = Amiga_prot(sa->mode);
        prot &= MASK;
        prot |= (global.fib.fib_Protection & (~MASK));

        /* That is, we take the old values for things we don't
            concern ourselves with */

        if (!SetProtection(lf->lf_Name, prot)) {
            PrintFault(IoErr(), "Unable to set protection bits");
            status = NFS_err(IoErr());
        }

        #undef MASK
    }

    /* There has to be a better way of doing this bit... */
    if (ISSET(sa->uid) || ISSET(sa->gid)) {
        LONG owner = (global.fib.fib_OwnerUID << 16) | global.fib.fib_OwnerGID;

        if (ISSET(sa->uid))
            owner = ((owner & 0x0000ffff) | (sa->uid << 16));

        if (ISSET(sa->gid))
            owner = ((owner & 0xffff0000) | sa->gid);

        /* The ramdisk doesn't handle this in v39 */
        if (!SetOwner(lf->lf_Name, owner)) {
            PrintFault(IoErr(), "Unable to set ownership");
            status = NFS_err(IoErr());
        }
    }

    /* Let's sort out the dates... */

    /* We simply ignore atime - the Amiga has no concept of it,
        and any kludgy use runs the risk of creating subtle problems */
    {
        struct nfstime *mt = &(sa->mtime);
        if (ISSET(mt->seconds)) {
            struct DateStamp ds;

            if (!ISSET(mt->useconds)) {
                mt->useconds = 0;
            }

            Amiga_date(mt, &ds);
            if (!SetFileDate(lf->lf_Name, &ds) && (status == NFS_OK)) {
                PrintFault(IoErr(), "Unable to set date");
                status = NFS_err(IoErr());
            }
        }
    }

    /* Set the size - we may need to open the file */
    if (ISSET(sa->size)) {
        BOOL we_opened = FALSE;
        if (!handle) {
            we_opened = TRUE;
            if (handle = OpenFromLock(lf->lf_Lock)) {
                lf->lf_Lock = NULL;

                /* We possibly need to change the mode of the lock */
                ChangeMode(CHANGE_FH, handle, MODE_READWRITE);

                /* If there's an error changing the mode, it will be
                    reported later when we try and write */
            }
        }

        if (handle) {
            if (-1 == SetFileSize(handle, sa->size, OFFSET_BEGINNING)) {
                PrintFault(IoErr(), "Unable to set file size");
                status = NFS_err(IoErr());
            }
            if (!ExamineFH(handle, &global.fib)) {
                PrintFault(IoErr(), "Unable to examine open file");
                status = NFS_err(IoErr());
            }
            if (we_opened)
                Close(handle);
        } else {
            /* Hmmm... is it an error to set the size of a directory? */
            PrintFault(IoErr(), "Couldn't open file to set its length");
            status = NFS_wt_err(NFSERR_ISDIR);
        }
    }

    #undef ISSET

    /* Examine the lock if we still have it, otherwise trust that
        our last examination was correct */

    if (status == NFS_OK) {
        if (lf->lf_Lock && !Examine(lf->lf_Lock, &global.fib))
                return NFS_err(IoErr());
    }

    return status;
}


/* Seek in a file, extending it if necessary */
static BOOL SeekWithExtend(BPTR handle, LONG offset)
{
    LONG position;

    if (Seek(handle, offset, OFFSET_BEGINNING) != -1) {
        return TRUE; /* It worked fine */
    }

    /* If the write position is past the current EOF then
        we need to extend it ourselves - DOS won't do it.
       Thanks to Henryk Richter
    */

    if (Seek(handle, 0, OFFSET_END) == -1) {
        return FALSE;
    }

    position = Seek(handle, 0, OFFSET_CURRENT);

    /* Do we need to extend it? */
    if (position < offset) {
        if (offset != SetFileSize(handle, offset, OFFSET_BEGINNING))
            return FALSE;

        Seek(handle, offset, OFFSET_BEGINNING);
        position = Seek(handle, 0, OFFSET_CURRENT);
    }

    return (BOOL)(position == offset);
}

