/
Genesis-1.0p36-DEV/
Genesis-1.0p36-DEV/bin/
Genesis-1.0p36-DEV/doc/
Genesis-1.0p36-DEV/etc/
Genesis-1.0p36-DEV/src/data/
/*
// Full copyright information is available in the file ../doc/CREDITS
//
// Main file for 'genesis' executable
*/

#define _main_

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#ifdef __UNIX__
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <grp.h>
#include <pwd.h>
#endif
#include <ctype.h>
#include <time.h>
#include "cdc_pcode.h"
#include "cdc_db.h"
#include "strutil.h"
#include "util.h"
#include "file.h"
#include "net.h"
#include "sig.h"

INTERNAL void initialize(Int argc, char **argv);
INTERNAL void main_loop(void);

#ifdef __UNIX__
uid_t uid;
gid_t gid;
#endif

void usage (char * name);

/*
// if we have a logs/genesis.run, unlink it when we exit,
// hooked into exiting with 'atexit()'
*/
void unlink_runningfile(void) {
    if (unlink(c_runfile)) {
        char buf[BUF];

        /* grasp! */
        strcpy(buf, "rm -f ");
        strcat(buf, c_runfile);
        system(buf);
    }
}

/*
// --------------------------------------------------------------------
*/

INTERNAL void prebind_port_with(char * str, char * name) {
    int port;
    char * addr = NULL, * s = str;
    Bool tcp = YES;

    if (isdigit(*s)) {
        addr = s;
        while (*s && *s != ':') s++;
        if (!*s) {
            usage(name);
            fprintf(stderr, "** No port given with -p %s\n", str);
            exit(1);
        }
    }

    if (*s != ':') {
        usage(name);
        fprintf(stderr, "** Invalid prebind format: %s\n", str);
        exit(1);
    }
    *s = (char) NULL;
    s++;

    port = atoi(s);

    if (port < 0) {
        tcp = NO;
        port = -port;
    } else if (port == 0) {
        usage(name);
        fprintf(stderr, "** Invalid port: 0\n");
        exit(1);
    }

    /* now prebind it */
    if (prebind_port(port, addr, tcp)) {
        if (addr)
            fprintf(stderr, "prebound %s:%d\n", addr, tcp ? port : -port);
        else
            fprintf(stderr, "prebound :%d\n", tcp ? port : -port);
    } else {
        fprintf(stderr, "** unable to prebind port: Invalid address\n");
        exit(1);
    }
}

/*
// --------------------------------------------------------------------
//
// The big kahuna
//
*/

int main(int argc, char **argv) {
    /* make us look purdy */

    initialize((Int) argc, argv);
    main_loop();

#ifdef PROFILE_EXECUTE
   dump_execute_profile();
#endif

    /* Sync the cache, flush output buffers, and exit normally. */
    cache_sync();
    db_close();
    flush_output();
    close_files();

    return 0;
}

/*
// --------------------------------------------------------------------
//
// Initialization
//
*/
void get_my_hostname(void) {
    char   cbuf[LINE];

    /* for those OS's that do not do this */
    memset(cbuf, 0, LINE);
    if (!gethostname(cbuf, LINE)) {
        if (cbuf[LINE-1] != (char) NULL) { 
            fprintf(stderr, "Unable to determine hostname: name too long.\n");
        } else {
            string_discard(str_hostname);
            str_hostname = string_from_chars(cbuf, strlen(cbuf));
        }
    } else {
        fprintf(stderr, "Unable to determine hostname.\n");
    }
}

#define addarg(__str) { \
        arg.type = STRING; \
        str = string_from_chars(__str, strlen(__str)); \
        arg.u.str = str; \
        args = list_add(args, &arg); \
        string_discard(str); \
    } \

#define NEWFILE(var, name) { \
        free(var); \
        var = EMALLOC(char, strlen(name) + 1); \
        strcpy(var, name); \
    }

/*
// --------------------------------------------------------------------
*/

INTERNAL void initialize(Int argc, char **argv) {
    cList   * args;
    cStr     * str;
    cData     arg;
    char     * opt,
             * name,
             * basedir = NULL,
             * buf;
    FILE     * fp;
    Bool       dofork = YES;
    pid_t      pid;

    name = *argv;
    argv++;
    argc--;

    /* Ditch stdin, so we can reuse the file descriptor */
    fclose(stdin);

    /* basic initialization */
#ifdef __UNIX__
    uid = getuid();
    gid = getgid();
#endif
    init_defs();
    init_match();
    init_util();
    init_ident();

    /* db argument list */
    args = list_new(0);

    /* parse arguments */
    while (argc) {
        opt = *argv;
        if (*opt == '-') {
            opt++;
 
            /* catch db options */
            if (*opt == '-') {
                addarg(opt);
                goto end;
            }

            switch (*opt) {
            case 'd': /* directory */
                opt++;
                if (*opt == (char) NULL) {
                    usage(name);
                    fputs("** Invalid directory option: -d\n", stderr);
                    exit(1);
                }
                argv += getarg(name,&buf,opt,argv,&argc,usage);
                switch (*opt) { 
                  case 'b':
                      NEWFILE(c_dir_binary, buf);
                      break;
                  case 'r':
                      NEWFILE(c_dir_root, buf);
                      break;
                  case 'x':
                      NEWFILE(c_dir_bin, buf);
                      break;
                  default:
                    usage(name);
                    fprintf(stderr, "** Invalid directory option: -d%c\n",*opt);
                    exit(1);
                }
                break;
            case 'l': /* logfile */
                opt++;
                if (*opt == (char) NULL) {
                    usage(name);
                    fputs("** Invalid file option: -l\n", stderr);
                    exit(1);
                }
                argv += getarg(name,&buf,opt,argv,&argc,usage);
                switch (*opt) { 
                  case 'd':
                      NEWFILE(c_logfile, buf);
                      break;
                  case 'g':
                      NEWFILE(c_errfile, buf);
                      break;
                  case 'p':
                      NEWFILE(c_runfile, buf);
                      break;
                  default:
                      usage(name);
                      fprintf(stderr, "** Invalid file option: -l%c\n",*opt);
                      exit(1);
                }
                break;
            case 'f':
                dofork = NO;
                break;
            case 'p':
                argv += getarg(name,&buf,opt,argv,&argc,usage);
                prebind_port_with(buf, name);
                break;
            case 'h':
                usage(name);
                exit(0);
                break;
            case 'v':
                printf("Genesis %d.%d-%d\n",
                       VERSION_MAJOR,
                       VERSION_MINOR,
                       VERSION_PATCH);
                exit(0);
                break;
            case 'n':
                argv += getarg(name,&buf,opt,argv,&argc,usage);
                string_discard(str_hostname);
                str_hostname = string_from_chars(buf, strlen(buf));
                break;
            case 's': {
                char * p;

                argv += getarg(name, &buf, opt, argv, &argc, usage);
                p = buf;
                cache_width = atoi(p);
                while (*p && isdigit(*p))
                    p++;
                if ((char) LCASE(*p) == 'x') {
                    p++;
                    cache_depth = atoi(p);
                } else {
                    usage(name);
                    printf("\n** Invalid WIDTHxDEPTH: '%s'\n", buf);
                    exit(0);
                }
                if (cache_width == 0 && cache_depth == 0) {
                    usage(name);
                 puts("\n** The WIDTH and DEPTH cannot both be zero\n");
                    exit(0);
                }
                break;
              }
#ifdef __UNIX__
            case 'g': {
                struct group * gr; 
 
                argv += getarg(name,&buf,opt,argv,&argc,usage);
                if (buf[0] == '#') {
                    gid = (gid_t) atoi(&buf[1]);
                } else if (!(gr = getgrnam(buf))) {
                    usage(name);
                    fprintf(stderr,
                            "** invalid group name -g: '%s'\n",buf);
                    exit(1);
                } else {
                    gid = (gid_t) gr->gr_gid;
                }
                break;
              }
            case 'u': {
                struct passwd * pw; 
 
                argv += getarg(name,&buf,opt,argv,&argc,usage);
                if (buf[0] == '#') {
                    uid = (uid_t) atoi(&buf[1]);
                } else if (!(pw = getpwnam(buf))) {
                    usage(name);
                    fprintf(stderr,
                            "** invalid user name -u: '%s'\n",buf);
                    exit(1);
                } else {
                    uid = (uid_t) pw->pw_uid;
                }
                break;
              }
#endif
            default:
                usage(name);
                fprintf(stderr, "** Invalid argument -%s\n** send arguments to the database by prefixing them with '--', not '-'\n", opt); 
                exit(1);
            }
        } else {
            if (basedir == NULL)
                basedir = *argv;
            else
                addarg(*argv);
        }

        end:
        argv++;
        argc--;
    }

    /* Initialize internal tables and variables. */
#ifdef DRIVER_DEBUG
    init_debug();
#endif
    init_codegen();
    init_op_table();
    init_sig();
    init_execute();
    init_token();
    init_modules(argc, argv);
    init_net();
    init_instances();

    /* Figure out our hostname */
    if (!string_length(str_hostname))
        get_my_hostname();

    /* where is the base db directory ? */
    if (basedir == NULL)
        basedir = ".";

    /* Switch into database directory. */
    if (chdir(basedir) == F_FAILURE) {
        usage(name);
        fprintf(stderr, "** Couldn't change to base directory \"%s\".\n",
                basedir);
        exit(1);
    }

    /* open the correct logfiles */
    if (strccmp(c_logfile, "stderr") == 0)
        logfile = stderr;
    else if (strccmp(c_logfile, "stdout") != 0 &&
             (logfile = fopen(c_logfile, "ab")) == NULL)
    {
        fprintf(stderr, "Unable to open log %s: %s\nDefaulting to stdout..\n",
                c_logfile, strerror(GETERR()));
        logfile = stdout;
    }

    if (strccmp(c_errfile, "stdout") == 0)
        logfile = stdout;
    else if (strccmp(c_errfile, "stderr") != 0 &&
             (errfile = fopen(c_errfile, "ab")) == NULL)
    {
        fprintf(stderr, "Unable to open log %s: %s\nDefaulting to stderr..\n",
                c_errfile, strerror(GETERR()));
        errfile = stderr;
    }

    /*
    // Clean up our execution privs
    */
#ifdef __UNIX__

#define ROOT_UID 0
#define DIE(_msg_) { fprintf(errfile, _msg_); fputc('\n', errfile); exit(1); }

    if (gid != getgid()) {
        if (geteuid() != ROOT_UID)
            DIE("** setgid attempted when not running as root, exiting..")
        if (setgid(gid) == F_FAILURE)
            DIE("** setgid(): unable to change group, exiting..")
    }

    if (uid != getuid()) {
        if (geteuid() != ROOT_UID)
            DIE("** setuid attempted when not running as root, exiting..")
        if (setuid(uid) == F_FAILURE)
            DIE("** setuid(): unable to change user, exiting..")
    }

#undef ROOT_UID
#undef DIE

#endif

    /*
    // Now fork a child, this will also have the effect of clearing
    // any residual benefits of setuid()
    */
#ifdef __UNIX__
    if (dofork) {
        pid = FORK_PROCESS();
        if (pid != 0) {
            int ignore;
            if (pid == -1)
                fprintf(stderr,"genesis: unable to fork: %s\n",
                                strerror(GETERR()));
            else if (waitpid(pid, &ignore, WNOHANG) == F_FAILURE)
                fprintf(stderr,"genesis: waitpid: %s\n",
                                strerror(GETERR()));
            exit(0);
        }
    }
#endif

    /* print the PID */
    if ((fp = fopen(c_runfile, "wb")) != NULL) {
        fprintf(fp, "%ld\n", (long) getpid());
        fclose(fp);
        atexit(unlink_runningfile);
    } else {
        fprintf(errfile, "genesis pid: %ld\n", (long) getpid());
    }

    /* Initialize database and network modules. */
    init_scratch_file();
    init_cache();
    init_binary_db();
    init_core_objects();

    /* give useful information, good for debugging */
    { /* reduce the scope */
        cData   * d;
        cStr     * str;
        Bool       first = YES;

        fputs("Calling $sys.startup([", errfile);
        for (d=list_first(args); d; d=list_next(args, d)) {
            str = data_to_literal(d, TRUE);
            if (!first) {
                fputc(',', errfile);
                fputc(' ', errfile);
            } else {
                first = NO;
            }
            fputs(string_chars(str), errfile);
            string_discard(str);
        }
        fputs("])...\n", errfile);
    }

    /* call $sys.startup() */
    arg.type = LIST;
    arg.u.list = args;
    task(SYSTEM_OBJNUM, startup_id, 1, &arg);
    list_discard(args);
}

/*
// --------------------------------------------------------------------
//
// The core of the interpreter, while this is looping it is interpreting
//
// use 'gettimeofday' since most OS's simply wrap time() around it
*/

INTERNAL void main_loop(void) {
    register Int     seconds;
    register time_t  next, last;

#ifdef __Win32__
    time_t           tm;

#define SECS tm
#define GETTIME() time(&tm)

#else
    /* Unix--most unix systems wrap time() around gettimeofday() */
    struct timeval   tp;

#define SECS tp.tv_sec
#define GETTIME() gettimeofday(&tp, NULL)
#endif

    setjmp(main_jmp);

    seconds = 0;
    next = last = 0;

    while (running) {
        flush_defunct();

        /* cache_sanity_check(); */

        /* determine io wait */
        if (heartbeat_freq != -1) {
            next = (last - (last % heartbeat_freq)) + heartbeat_freq;
            GETTIME();
            seconds = (preempted ? 0 :
                       ((SECS >= next) ? 0 : next - SECS));
        }

        /* push our dump along, diddle with the wait if we need to */
        switch (dump_some_blocks(DUMP_BLOCK_SIZE)) {
            case DUMP_FINISHED:
                finish_backup();
                task(SYSTEM_OBJNUM, backup_done_id, 0);
                break;
            case DUMP_DUMPED_BLOCKS:
                seconds = 0; /* we are still dumping, dont wait */
                break;
        }

        handle_io_event_wait(seconds);
        handle_connection_input();
        handle_new_and_pending_connections();

        if (heartbeat_freq != -1) {
            GETTIME();
            if (SECS >= next) {
                last = SECS;
                task(SYSTEM_OBJNUM, heartbeat_id, 0);
#ifdef CLEAN_CACHE
                cache_cleanup();
#endif
            }
        }

        handle_connection_output();
        if (preempted)
            run_paused_tasks();
    }
}

/*
// --------------------------------------------------------------------
*/

void usage (char * name) {
    fprintf(stderr, "\n-- Genesis %d.%d-%d --\n\n\
Usage: %s [base dir] [options]\n\n\
    Base directory will default to \".\" if unspecified.  Arguments which\n\
    the driver does not recognize, or options which begin with \"--\" rather\n\
    than \"-\" are passed onto the database as arguments to $sys.startup().\n\n\
    Note: specifying \"stdin\" or \"stderr\" for either of the logs will\n\
    direct them appropriately.\n\n\
Options:\n\n\
    -v          version.\n\
    -f          do not fork on startup\n\
    -db <dir>   alternate binary directory, default: \"%s\"\n\
    -dr <dir>   alternate root file directory, defualt: \"%s\"\n\
    -dx <dir>   alternate executables directory, default: \"%s\"\n\
    -ld <file>  alternate database logfile, default: \"%s\"\n\
    -lg <file>  alternate driver (genesis) logfile, default: \"%s\"\n\
    -lp <file>  alternate runtime pid logfile, default: \"%s\"\n\
    -s <size>   Cache size, given as WIDTHxDEPTH, default %dx%d\n\
    -n <name>   specify the hostname (rather than looking it up)\n\
    -u <user>   if running as root, setuid to this user.  This only works\n\
                in unix.  Genesis must first be run as root.\n\
    -g <group>  if running as root, setgid to this group.  This only works\n\
                in unix.  Genesis must first be run as root.\n\
    -p <port>   prebind port, can exist multiple times.  Port must be\n\
                formatted as: [ADDR]:PORT. UDP ports are specified with\n\
                negative numbers. Address is any, if unspecified, or must\n\
                be an IP address.  All below are valid:\n\n\
                    206.81.134.103:80\n\
                    :-20\n\
                    :23\n\n",

     VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, name, c_dir_binary,
     c_dir_root, c_dir_bin, c_logfile, c_errfile, c_runfile, cache_width,
     cache_depth);
}

/* TEMPORARY-- we need an area where identical functions 'names' (yet
   different behaviours) between genesis/coldcc exist */

cObjnum get_object_name(Ident id) {
    cObjnum num;

    if (!lookup_retrieve_name(id, &num))
        num = db_top++;
    return num;
}