/*  nfsd - an Amiga NFS daemon

    ©1998, 1999, 2000 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 is the main routine - it opens libraries, calls allocation routines,
    checks versions, and installs RPC dispatchers.
*/

/*
    28-Sep-1999 - Uses ReadArgs. Allows the user to set the GMT offset
    30-Nov-1999 - Major changes to support new RPC code
    23-Feb-2000 - Handles some new command line options
    28-Feb-2000 - KEEPOPEN now configurable
    14-Oct-2000 - Patch from David Trollope; FLUSHINT added as a command line option
*/

#include "nfsd.h"
#include "nfs.h"
#include "mount.h"
#include "pcnfsd.h"

#include "service_nfsd.h" /* service() */

#include "rpc_register.h"
#include "handle_list.h"
#include "memory.h"
#include "config.h"
#include "timerdev.h"
#include "timezone.h"
#include "file_cache.h"
#include "util.h"

#include <stdio.h>
#include <signal.h>
// #include <string.h>         /* strcmp */
#include <sys/socket.h>
#include <netinet/in.h>

#include <dos/dosextens.h>  /* struct Process */

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

/* Seems some SDKs don't follow Amiga conventions... */
#ifndef SOCKETNAME
#define SOCKETNAME "bsdsocket.library"
#endif

/* Global options. Initialised in main() from command-line arguments. */
struct nfsd_Global global;

/* I'm relying on the compiler to have opened these */
extern struct Library *SysBase;
extern struct DosLibrary *DOSBase;

/* Libraries opened manually */
struct Library *SocketBase;

/* This is a rather busy main, but what the hey... */
int main(int argc, char *argv[])
{
    /* Declarations first */

    /* Keep track of whether or not we need to deregister these */
    BOOL registered_nfsd = FALSE,
        registered_mount = FALSE,
        registered_pcnfsd = FALSE;

    /* Our future point of presence in IP space */
    int sock = -1;

    /* We need these to inhibit graphical requesters */
    struct Process *process;
    APTR vptr;

    /* Just a little beta-blaring... */
    puts("nfsd beta, ©1998, 1999, 2000 Joseph Walton <ver@etla.org>\n"
         "This version compiled " __DATE__ ".\n"
         "nfsd is free software, distributed under the terms of the GNU General Public License.\n"
         "It comes with ABSOLUTELY NO WARRANTY.");
    /* Moved from pre-pre- to just plain pre-...
        And now we're a plain beta... */

    /* We'll handle CTRL-C's ourselves */
    signal(SIGINT, SIG_IGN);

    /* We need some v39 exec.library functions - sorry */
    if (SysBase->lib_Version < 39) {
        puts("nfsd requires v39 exec.library");
        goto finish;
    }

    /* We also need some v39 dos.library functions - sorry again */
    if (DOSBase->dl_lib.lib_Version < 39) {
        puts("nfsd requires v39 dos.library");
        goto finish;
    }

    /* Oh, I might need some socket handling routines... */
    SocketBase = OpenLibrary(SOCKETNAME, 4);

    if (!SocketBase) {
        puts("Unable to open " SOCKETNAME " v4");
        goto finish;
    }

    /* We manually open timer.device, as SAS/C won't do it for us */
    if (!td_opentimer()) {
        puts("Unable to open " TIMERNAME);
        goto finish;
    }

    /* Need to get memory to work with */
    if (!init_memory(&global)) {
        puts("Unable to allocate memory");
        goto finish;
    }

    /* Check the command line and initialise global settings */
    {
        STRPTR template = "CFG,GMTOFFSET/K/N,DST/S,ALLOWROOT/S,VERBOSE/S,KEEPOPEN/S,FLUSHINT/K/N";
        LONG opts[7] = {0};
        struct RDArgs *argsptr;

        if (NULL == (argsptr = ReadArgs(template, opts, NULL))) {
            PrintFault(IoErr(), NULL);
            goto finish;
        }

        /* CFG */
        if (opts[0]) {
            if (NULL == (global.cfgFileName = DupString((STRPTR)opts[0]))) {
                puts("Unable to make copy of configuration file name");
                goto finish;
            }
        } else {
            /* This is a sensible default */
            global.cfgFileName = "nfsd.config";
        }

        /* GMTOFFSET */
        if (opts[1]) {
            global.amigaGMTOffset = *(LONG *)opts[1];
        } else {
            if (!localeGetGMTOffset(&global.amigaGMTOffset)) {
                puts("Failed to get GMT offset from locale.library - using 0.");
                global.amigaGMTOffset = 0;
            }
        }

        /* DST */
        if (opts[2]) {
            puts("Daylight savings - local time is one hour ahead");
            global.amigaGMTOffset += 60;
        }

        /* ALLOWROOT */
        if (global.allow_root = opts[3]) {
            puts("Allowing remote accesses by root");
        }

        /* VERBOSE */
        if (global.verbose = opts[4]) {
            puts("Verbose mode enabled");
        }

        /* KEEPOPEN */
        if (global.keep_open = opts[5]) {
            puts("Keeping file handles open across requests (EXPERIMENTAL)");
        }

        /* FLUSHINT */
        if (opts[6]) {
            global.flushint = *((LONG *) opts[6]);
            if (global.flushint) {
                printf("Will flush handles after %ld seconds\n", global.flushint);
            } else {
                puts("Flushing disabled");
            }
        } else {
            /* The default is the value used before - ten minutes */
            global.flushint = 600;
        }

        /* The user can't change this yet: there's no real need to */
        global.allow_nonpriv_port = FALSE;

        FreeArgs(argsptr);
    }

    /* And parse our configuration */
    if (!configuration_load()) {
        puts("Unable to get configuration");
        goto finish;
    }

    /* Now fetch the inode base */
    persistent_inode_fetch();
    /* Any failure is dealt with internally, by starting afresh at 1 */

    /* We need a socket to communicate with */
    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) {
        printSocketError("Could not create a socket");
        goto finish;
    }

    /* nfsd needs to bind to a well-known port */
    {
        struct sockaddr_in addr = {
            sizeof(struct sockaddr_in),
            AF_INET,
            htons(NFS_PORT),
            {htonl(INADDR_ANY)}
        };

        /* Can I do this without a macro? */
        #define STRINGIFY(x) #x

        if (-1 == bind(sock, &addr, sizeof(addr))) {
            printSocketError("Unable to bind to port " STRINGIFY(NFS_PORT));
            goto finish;
        }

        #undef STRINGIFY
    }

    /*
        We don't try to get rid of any old mapping at the moment.
        It might well fail confusingly, and gazumping services like
        that is hardly polite.
    */

    if (global.verbose) {
        puts("Registering RPC services...");
    }

    /* Establish mappings. We bind everything to the same port. */
    if (pmap_set(sock, NFS_PROGRAM, NFS_VERSION, NFS_PORT)) {
        registered_nfsd = TRUE;
    } else {
        puts("Unable to register (NFS_PROGRAM, NFS_VERSION, NFS_PORT).");
        goto finish;
    }

    if (pmap_set(sock, MOUNT_PROGRAM, MOUNT_VERSION, NFS_PORT)) {
        registered_mount = TRUE;
    } else {
        puts("Unable to register (MOUNT_PROGRAM, MOUNT_VERSION, NFS_PORT).");
        goto finish;
    }

    if (pmap_set(sock, PCNFSD_PROGRAM, PCNFSD_VERSION, NFS_PORT)) {
        registered_pcnfsd = TRUE;
    } else {
        puts("Unable to register (PCNFSD_PROGRAM, PCNFSD_VERSION, NFS_PORT).");
        goto finish;
    }

    if (global.verbose) {
        puts("...registered.");
    }

    /* We don't want any requesters popped up */
    process = (struct Process *)FindTask(NULL);
    vptr = process->pr_WindowPtr;
    process->pr_WindowPtr = (APTR)-1L;

    puts("Serving.");
    service(sock); /* This where we do the Real Work */

    puts("Finished.");

    cfCloseAll();

    /* Now return things to normal */
    process->pr_WindowPtr = vptr;

    if (!persistent_inode_store())
        puts("Failed to store current inode");

    /* Now, free any resources and exit */
    finish:
    td_closetimer();

    if (SocketBase) {
        if (sock >= 0) {
            /* If any of these time out we should probably skip the
                rest, but we currently don't export that information
                from the registration process */
            if (registered_pcnfsd)
                if (!pmap_unset(sock, PCNFSD_PROGRAM, PCNFSD_VERSION))
                    puts("Unable to deregister the PCNFSD service.");
            if (registered_nfsd)
                if (!pmap_unset(sock, NFS_PROGRAM, NFS_VERSION))
                    puts("Unable to deregister the NFS service.");
            if (registered_mount)
                if (!pmap_unset(sock, MOUNT_PROGRAM, MOUNT_VERSION))
                    puts("Unable to deregister the mount service.");
            CloseSocket(sock);
        }
        CloseLibrary(SocketBase);
    }

    free_memory();
    return RETURN_OK;
}

