X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=minsk.c;h=4b4b2a3995ac5fd2ab0907a14a7fce692606b5d9;hb=1f1caa3d03716b77e169943c2ece09da7a2e1d87;hp=58652d6ccaed51a8dbad3a6de3b68d16a8ba3c78;hpb=c6a18fa8178ff60767ea2415f381de4d16b5c467;p=minsk.git diff --git a/minsk.c b/minsk.c index 58652d6..4b4b2a3 100644 --- a/minsk.c +++ b/minsk.c @@ -4,12 +4,6 @@ * (c) 2010 Martin Mares */ -/* - * TODO: - * - debugging/play mode - * - we probably have to disable NOP - */ - /* * Things that are not implemented: * @@ -20,16 +14,20 @@ * reader and puncher, card reader and puncher, magnetic tape unit) */ +#define _GNU_SOURCE + #include #include #include +#include #include #include #include -static int trace = 3; +static int trace; static int cpu_quota = -1; static int print_quota = -1; +static void (*error_hook)(char *msg); // Minsk-2 has 37-bit words in sign-magnitude representation (bit 36 = sign) typedef unsigned long long int word; @@ -169,8 +167,10 @@ static int lino; static void parse_error(char *msg) { + if (error_hook) + error_hook("Parse error"); printf("Ошибка входа (стр. %d): %s\n", lino, msg); - exit(1); + exit(0); } static void parse_in(void) @@ -185,10 +185,24 @@ static void parse_in(void) if (!eol) parse_error("Строка слишком долгая"); *eol = 0; + if (eol > line && eol[-1] == '\r') + *--eol = 0; char *c = line; if (!c[0] || c[0] == ';') - continue; + { + if (!strncmp(c, ";daji_zor_i_litva=", 18)) + { + trace = atoi(c+18); + if (error_hook) + error_hook("Secret tracing switch flipped"); + } + continue; + } + + if (c[0] == '.') + return; + if (c[0] == '@') { c++; @@ -200,7 +214,7 @@ static void parse_in(void) if (*c >= '0' && *c <= '7') addr = 8*addr + *c++ - '0'; else - parse_error("Плохая цифва"); + parse_error("Плохая цифра"); } while (*c == ' ') c++; @@ -222,7 +236,7 @@ static void parse_in(void) if (*c >= '0' && *c <= '7') w = 8*w + *c++ - '0'; else - parse_error("Плохая цифва"); + parse_error("Плохая цифра"); } while (*c == ' ') c++; @@ -238,8 +252,10 @@ static word r1, r2, current_ins; static int ip = 00050; // Standard program start location static int prev_ip; -static void stop(char *reason) +static void stop(char *reason, char *notice) { + if (error_hook) + error_hook(notice); printf("Машина остановлена -- %s\n", reason); printf("СчАК:%04o См:%c%012llo Р1:%c%012llo Р2:%c%012llo\n", prev_ip, WF(acc), WF(r1), WF(r2)); exit(0); @@ -247,19 +263,19 @@ static void stop(char *reason) static void over(void) { - stop("Аварийный останов"); + stop("Аварийный останов", "Overflow"); } static void notimp(void) { acc = current_ins; - stop("Устройство разбитое"); + stop("Устройство разбитое", "Not implemented"); } static void noins(void) { acc = current_ins; - stop("Эту команду не знаю"); + stop("Эту команду не знаю", "Illegal instruction"); } static uint16_t linebuf[128]; @@ -297,7 +313,7 @@ static void print_line(int r) if (r & 4) { if (print_quota > 0 && !--print_quota) - stop("Бумага дошла - нужно ехать в Сивирь про новую"); + stop("Бумага дошла - нужно ехать в Сибирь про новую", "Out of paper"); for (int i=0; i<128; i++) { int ch = linebuf[i]; @@ -358,15 +374,13 @@ static void print_ins(int x, int y) eat = 1; break; case 4: // One Russian symbol - bit = 6; - fmt = "r"; + fmt = "xr"; break; case 5: // Russian text fmt = "xrrrrrr"; break; case 6: // One Latin symbol - bit = 6; - fmt = "l"; + fmt = "xl"; break; default: // Latin text fmt = "xllllll"; @@ -423,7 +437,7 @@ static void print_ins(int x, int y) linebuf[pos] = ch; pos = (pos+1) & 0177; } - assert(!bit); + assert(bit >= 0); } static void run(void) @@ -463,7 +477,7 @@ static void run(void) ip = (ip+1) & 07777; if (cpu_quota > 0 && !--cpu_quota) - stop("Тайм-аут"); + stop("Тайм-аут", "CPU quota exceeded"); /* Arithmetic operations */ @@ -606,7 +620,7 @@ static void run(void) case 0100: // Halt r1 = rd(x); acc = rd(y); - stop("Останов машины"); + stop("Останов машины", "Halted"); case 0103: // I/O magtape notimp(); case 0104: // Disable rounding @@ -783,8 +797,471 @@ static void run(void) } } -int main(void) +/*** Daemon interface ***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define DTRACE(msg, args...) fprintf(stderr, msg "\n", ##args) +#define DLOG(msg, args...) fprintf(stderr, msg "\n", ##args) +#else +#define DTRACE(msg, args...) do { } while(0) +#define DLOG(msg, args...) syslog(LOG_INFO, msg, ##args) +#endif + +#define MAX_CONNECTIONS 50 // Per daemon +#define MAX_CONNS_PER_IP 1 // Per IP +#define MAX_TRACKERS 200 // IP address trackers +#define TBF_MAX 5 // Max number of tokens in the bucket +#define TBF_REFILL_PER_SEC 0.2 // Bucket refill rate (buckets/sec) + +#define PID_FILE "/var/run/pd-minsk.pid" +#define UID 124 +#define GID 125 + +static void die(char *msg) +{ + fprintf(stderr, "minsk: "); + fprintf(stderr, msg); + fputc('\n', stderr); + exit(1); +} + +static char **spt_argv; +static char *spt_start, *spt_end; + +static void setproctitle_init(int argc, char **argv) +{ + int i, len; + char **env, **oldenv, *t; + + spt_argv = argv; + + /* Create a backup copy of environment */ + oldenv = __environ; + len = 0; + for (i=0; oldenv[i]; i++) + len += strlen(oldenv[i]) + 1; + __environ = env = malloc(sizeof(char *)*(i+1)); + t = malloc(len); + if (!__environ || !t) + die("malloc failed"); + for (i=0; oldenv[i]; i++) + { + env[i] = t; + len = strlen(oldenv[i]) + 1; + memcpy(t, oldenv[i], len); + t += len; + } + env[i] = NULL; + + /* Scan for consecutive free space */ + spt_start = spt_end = argv[0]; + for (i=0; i spt_start) + { + n = vsnprintf(buf, sizeof(buf), msg, args); + if (n >= (int) sizeof(buf) || n < 0) + sprintf(buf, ""); + n = spt_end - spt_start; + strncpy(spt_start, buf, n); + spt_start[n] = 0; + spt_argv[0] = spt_start; + spt_argv[1] = NULL; + } + va_end(args); +} + +static void sigchld_handler(int sig __attribute__((unused))) +{ +} + +static void sigalrm_handler(int sig __attribute__((unused))) +{ + const char err[] = "--- Timed out. Time machine disconnected. ---\n"; + write(1, err, sizeof(err)); + DLOG("Connection timed out"); + exit(0); +} + +static void child_error_hook(char *err) +{ + DLOG("Stopped: %s", err); +} + +static void child(int sk2) +{ + dup2(sk2, 0); + dup2(sk2, 1); + close(sk2); + + struct sigaction sact = { + .sa_handler = sigalrm_handler, + }; + if (sigaction(SIGALRM, &sact, NULL) < 0) + die("sigaction: %m"); + + // Set up limits + alarm(60); + cpu_quota = 100000; + print_quota = 100; + + const char welcome[] = "+++ Welcome to our computer museum. +++\n+++ Our time machine will connect you to one of our exhibits. +++\n\n"; + write(1, welcome, sizeof(welcome)); + + error_hook = child_error_hook; + parse_in(); + run(); + fflush(stdout); + DTRACE("Finished"); +} + +struct conn { + pid_t pid; + struct in_addr addr; + struct tracker *tracker; +}; + +static struct conn connections[MAX_CONNECTIONS]; + +static struct conn *get_conn(struct in_addr *a) +{ + for (int i=0; ipid) + { + memcpy(&c->addr, a, sizeof(struct in_addr)); + return c; + } + } + return NULL; +} + +static struct conn *pid_to_conn(pid_t pid) +{ + for (int i=0; ipid == pid) + return c; + } + return NULL; +} + +static void put_conn(struct conn *c) +{ + c->pid = 0; + c->tracker = NULL; +} + +struct tracker { + struct in_addr addr; + int active_conns; + time_t last_access; + double tokens; +}; + +static struct tracker trackers[MAX_TRACKERS]; + +static int get_tracker(struct conn *c) +{ + struct tracker *t; + time_t now = time(NULL); + int i; + + for (i=0; iaddr, &c->addr, sizeof(struct in_addr))) + break; + } + if (i < MAX_TRACKERS) + { + if (now > t->last_access) + { + t->tokens += (now - t->last_access) * (double) TBF_REFILL_PER_SEC; + t->last_access = now; + if (t->tokens > TBF_MAX) + t->tokens = TBF_MAX; + } + DTRACE("TBF: Using tracker %d (%.3f tokens)", i, t->tokens); + } + else + { + int min_i = -1; + for (int i=0; iactive_conns && (min_i < 0 || t->last_access < trackers[min_i].last_access)) + min_i = i; + } + if (min_i < 0) + { + DLOG("TBF: Out of trackers!"); + return 0; + } + t = &trackers[min_i]; + if (t->last_access) + DTRACE("TBF: Recycling tracker %d", min_i); + else + DTRACE("TBF: Creating tracker %d", min_i); + memset(t, 0, sizeof(*t)); + t->addr = c->addr; + t->last_access = now; + t->tokens = TBF_MAX; + } + + if (t->active_conns >= MAX_CONNS_PER_IP) + { + DTRACE("TBF: Too many conns per IP"); + return 0; + } + + if (t->tokens >= 0.999) + { + t->tokens -= 1; + t->active_conns++; + c->tracker = t; + DTRACE("TBF: Passed (%d conns)", t->active_conns); + return 1; + } + else + { + DTRACE("TBF: Failed"); + t->tokens = 0; + return 0; + } +} + +static void put_tracker(struct conn *c) +{ + struct tracker *t = c->tracker; + if (!t) + { + DLOG("put_tracker: no tracker?"); + sleep(5); + return; + } + if (t->active_conns <= 0) + { + DLOG("put_tracker: no counter?"); + sleep(5); + return; + } + t->active_conns--; + DTRACE("TBF: Put tracker (%d conns remain)", t->active_conns); +} + +static void run_as_daemon(int do_fork) { + int sk = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sk < 0) + die("socket: %m"); + + int one = 1; + if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) + die("setsockopt: %m"); + + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = ntohs(1969), + .sin_addr.s_addr = INADDR_ANY, + }; + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0) + die("bind: %m"); + if (listen(sk, 128) < 0) + die("listen: %m"); + // if (fcntl(sk, F_SETFL, O_NONBLOCK) < 0) + // die("fcntl: %m"); + + if (do_fork) + { + pid_t pid = fork(); + if (pid < 0) + die("fork: %m"); + if (pid) + { + FILE *f = fopen(PID_FILE, "w"); + if (f) + { + fprintf(f, "%d\n", pid); + fclose(f); + } + exit(0); + } + + chdir("/"); + setresgid(GID, GID, GID); + setresuid(UID, UID, UID); + setsid(); + } + + struct sigaction sact = { + .sa_handler = sigchld_handler, + .sa_flags = SA_RESTART, + }; + if (sigaction(SIGCHLD, &sact, NULL) < 0) + die("sigaction: %m"); + + DLOG("Daemon ready"); + setproctitle("minsk: Listening"); + openlog("minsk", LOG_PID, LOG_LOCAL7); + + for (;;) + { + struct pollfd pfd[1] = { + { .fd = sk, .events = POLLIN }, + }; + + int nfds = poll(pfd, 1, 60000); + if (nfds < 0 && errno != EINTR) + { + DLOG("poll: %m"); + sleep(5); + continue; + } + + int status; + pid_t pid; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) + { + if (!WIFEXITED(status) || WEXITSTATUS(status)) + DLOG("Process %d exited with strange status %x", pid, status); + + struct conn *conn = pid_to_conn(pid); + if (conn) + { + DTRACE("Connection with PID %d exited", pid); + put_tracker(conn); + put_conn(conn); + } + else + DTRACE("PID %d exited, matching no connection", pid); + } + + if (!(pfd[0].revents & POLLIN)) + continue; + + socklen_t salen = sizeof(sa); + int sk2 = accept(sk, (struct sockaddr *) &sa, &salen); + if (sk2 < 0) + { + if (errno != EINTR) + { + DLOG("accept: %m"); + sleep(5); + } + continue; + } + DTRACE("Got connection: fd=%d", sk2); + + struct conn *conn = get_conn(&sa.sin_addr); + const char *reason = NULL; + if (conn) + { + if (!get_tracker(conn)) + { + DLOG("Connection from %s dropped: Throttling", inet_ntoa(sa.sin_addr)); + put_conn(conn); + conn = NULL; + reason = "--- Sorry, but you are sending too many requests. Please slow down. ---\n"; + } + } + else + { + DLOG("Connection from %s dropped: Too many connections", inet_ntoa(sa.sin_addr)); + reason = "--- Sorry, maximum number of connections exceeded. Please come later. ---\n"; + } + + pid = fork(); + if (pid < 0) + { + DLOG("fork failed: %m"); + close(sk2); + continue; + } + if (!pid) + { + close(sk); + if (conn) + { + DLOG("Accepted connection from %s", inet_ntoa(sa.sin_addr)); + setproctitle("minsk: %s", inet_ntoa(sa.sin_addr)); + child(sk2); + } + else + { + DLOG("Sending error message to %s", inet_ntoa(sa.sin_addr)); + setproctitle("minsk: %s ERR", inet_ntoa(sa.sin_addr)); + write(sk2, reason, strlen(reason)); + } + exit(0); + } + + DTRACE("Created process %d", pid); + if (conn) + conn->pid = pid; + close(sk2); + } +} + +static void init_memory(void) +{ + // For the contest, we fill the whole memory with -00 00 0000 0000 (HALT), + // not +00 00 0000 0000 (NOP). Otherwise, an empty program would reveal + // the location of the password :) + for (int i=0; i<4096; i++) + mem[i] = 01000000000000ULL; + + // Store the password + int pos = 02655; + mem[pos++] = 0574060565373; + mem[pos++] = 0371741405340; + mem[pos++] = 0534051524017; +} + +int main(int argc, char **argv) +{ + init_memory(); + + if (argc > 1) + { + setproctitle_init(argc, argv); + if (!strcmp(argv[1], "--daemon")) + run_as_daemon(1); + else if (!strcmp(argv[1], "--net")) + run_as_daemon(0); + else if (!strncmp(argv[1], "--trace=", 8)) + trace = atoi(argv[1] + 8); + else + die("Usage: minsk [--daemon | --net]"); + } + parse_in(); run(); return 0;