/*  RPC handling code for the NFS protocol

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

/*
    This would probably benefit from having most functions made inline.
*/

#include "nfs.h"
#include "rpc.h"
#include "nfsd.h"

#include "nfs_functions.h"

#include <stdio.h>

static int32 *readHandle(int32 *ptr, int32 *limit, nfs_fh **hp);
static int32 *readSattr(int32 *ptr, int32 *limit, struct sattr *sa);
static int32 *readDirOpArgs(int32 *ptr, int32 *limit, struct diropargs *doa);

int nfs_procedure(struct Call *call, int32 *ptr, int32 *limit,
    int32 *rptr, int32 *rlimit)
{
    if (call->c_version != NFS_VERSION) {
        *rptr++ = P_ENUM(PROG_MISMATCH);
        *rptr++ = P_UINT(NFS_VERSION);
        *rptr = P_UINT(NFS_VERSION);
        return 8;
    }

    switch (call->c_procedure) {
        case NFS_PROC_NULL:
            *rptr = P_ENUM(SUCCESS);
            return 6;

        case NFS_PROC_GETATTR:
            {
                nfs_fh *handle;

                if (readHandle(ptr, limit, &handle)) {
                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_getattr_2_svc(handle, call, rptr);
                }
            }
            break;

        case NFS_PROC_SETATTR:
            {
                struct sattrargs args;
                ptr = readHandle(ptr, limit, &args.handle);
                ptr = readSattr(ptr, limit, &args.sattr);
                if (ptr) {
                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_setattr_2_svc(&args, call, rptr);
                }
            }
            break;

        case NFS_PROC_LOOKUP:
            {
                struct diropargs args;
                if (readDirOpArgs(ptr, limit, &args)) {
                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_lookup_2_svc(&args, call, rptr);
                }
            }
            break;

        case NFS_PROC_CREATE:
            {
                struct createargs args;
                ptr = readDirOpArgs(ptr, limit, &args.where);
                ptr = readSattr(ptr, limit, &args.sattr);
                if (ptr) {
                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_create_2_svc(&args, call, rptr);
                }
            }
            break;

        case NFS_PROC_REMOVE:
            {
                struct diropargs args;
                if (readDirOpArgs(ptr, limit, &args)) {
                    *rptr++ = P_ENUM(SUCCESS);
                    *rptr = P_ENUM(nfsproc_remove_2_svc(&args, call));
                    return 7;
                }
            }
            break;

        case NFS_PROC_RENAME:
            {
                struct renameargs args;

                ptr = readDirOpArgs(ptr, limit, &args.from);
                if (readDirOpArgs(ptr, limit, &args.to)) {
                    *rptr++ = P_ENUM(SUCCESS);
                    *rptr = P_ENUM(nfsproc_rename_2_svc(&args, call));
                    return 7;
                }
            }
            break;

        case NFS_PROC_MKDIR:
            {
                struct createargs args;
                ptr = readDirOpArgs(ptr, limit, &args.where);
                if (readSattr(ptr, limit, &args.sattr)) {
                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_mkdir_2_svc(&args, call, rptr);
                }
            }
            break;

        case NFS_PROC_RMDIR:
            {
                struct diropargs args;
                if (readDirOpArgs(ptr, limit, &args)) {
                    *rptr++ = P_ENUM(SUCCESS);
                    *rptr = P_ENUM(nfsproc_rmdir_2_svc(&args, call));
                    return 7;
                }
            }
            break;

        case NFS_PROC_STATFS:
            {
                nfs_fh *handle;

                if (readHandle(ptr, limit, &handle)) {
                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_statfs_2_svc(handle, call, rptr);
                }
            }
            break;

        case NFS_PROC_READ:
            {
                struct readargs args;

                if ((limit - ptr) >= (INT32S(NFS_FHSIZE) + 3)) {
                    args.handle = (nfs_fh *)ptr;
                    ptr += INT32S(NFS_FHSIZE);
                    args.offset = G_UINT(*ptr++);
                    args.count = G_UINT(*ptr);
                    /* Ignore totalcount */
                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_read_2_svc(&args, call, rptr, rlimit);
                }
            }
            break;

        case NFS_PROC_WRITE:
            {
                struct writeargs args;

                if ((limit - ptr) >= (INT32S(NFS_FHSIZE) + 4)) {
                    args.handle = (nfs_fh *)ptr;
                    ptr += INT32S(NFS_FHSIZE);
                    ptr++; /* Ignore beginoffset */
                    args.offset = G_UINT(*ptr++);
                    ptr++; /* Ignore totalcount */

                    args.count = G_UINT(*ptr++);

                    if (INT32S(args.count) <= (limit - ptr)) {
                        args.data = (BYTE *)ptr;
                         *rptr++ = P_ENUM(SUCCESS);
                        return nfsproc_write_2_svc(&args, call, rptr);
                    }
                }
            }
            break;

        case NFS_PROC_READDIR:
            {
                struct readdirargs args;

                if ((limit - ptr) >= (INT32S(NFS_FHSIZE) + 2)) {
                    args.dir_handle = (nfs_fh *)ptr;
                    ptr += INT32S(NFS_FHSIZE);

                    args.cookie = G_UINT(*ptr++);
                    args.count = G_UINT(*ptr);

                    *rptr++ = P_ENUM(SUCCESS);
                    return nfsproc_readdir(&args, call, rptr, rlimit);
                }
            }
            break;

        /* Links are not supported currently, as they could potentially
            be used to subvert security */
        case NFS_PROC_READLINK:
        case NFS_PROC_LINK:
        case NFS_PROC_SYMLINK:
            *rptr++ = P_ENUM(SUCCESS);
            *rptr   = P_ENUM(NFSERR_ACCES);
            return 7;

        case NFS_PROC_ROOT:  // This isn't part of the protocol any more
        case NFS_PROC_WRITECACHE: // And this one's not in V2 proper
        default:
            *rptr = P_ENUM(PROC_UNAVAIL);
            return 6;
    }

    /* We fell through */
    *rptr = P_ENUM(GARBAGE_ARGS);
    return 6;
}

static int32 *readHandle(int32 *ptr, int32 *limit, nfs_fh **hp)
{
    if (!ptr)
        return NULL;

    *hp = (nfs_fh *)ptr;
    ptr += INT32S(NFS_FHSIZE);
    if (ptr <= limit) { /* <=, as this may be the last thing in the packet */
        return ptr;
    }
    return NULL;
}

static int32 *readSattr(int32 *ptr, int32 *limit, struct sattr *sa)
{
    /* Check there's enough space */
    if (!ptr || ((limit - ptr) < 8)) {
        return NULL;
    }

    /* This is exactly equivalent to CopyMem(), except in the case
        where this is a little-endian Amiga. Which could happen. */
    sa->mode = G_UINT(*ptr++);
    sa->uid = G_UINT(*ptr++);
    sa->gid = G_UINT(*ptr++);
    sa->size = G_UINT(*ptr++);
    sa->atime.seconds = G_UINT(*ptr++);
    sa->atime.useconds = G_UINT(*ptr++);
    sa->mtime.seconds = G_UINT(*ptr++);
    sa->mtime.useconds = G_UINT(*ptr++);

    return ptr;
}

static int32 *readDirOpArgs(int32 *ptr, int32 *limit, struct diropargs *doa)
{
    if (!ptr) {
        return NULL;
    }

    if (ptr >= limit) {
        return NULL;
    }

    doa->dir_handle = (nfs_fh *)ptr;
    ptr += INT32S(NFS_FHSIZE);

    if (ptr < limit) {
        return getString(ptr, limit, doa->filename, NFS_MAXNAMELEN + 1);
    }
    return NULL;
}

