/* // 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; }