258 lines
6.2 KiB
C++
258 lines
6.2 KiB
C++
|
#pragma once
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <sys/resource.h>
|
||
|
#include <signal.h>
|
||
|
#include <time.h>
|
||
|
|
||
|
#include "board.h"
|
||
|
#include "mcp.h"
|
||
|
|
||
|
enum exit_reason {
|
||
|
/* Invalid moves disqualify the player. */
|
||
|
INVALID_MOVE_0 = 1,
|
||
|
INVALID_MOVE_1,
|
||
|
/* Crashes do, too. */
|
||
|
CRASH_0,
|
||
|
CRASH_1,
|
||
|
/* Couldn't execute a player. This is a configuration error. */
|
||
|
EXEC_FAILED
|
||
|
};
|
||
|
|
||
|
/* Time given to players after softlimit has been exhausted. */
|
||
|
//#define CLEANUP_TIME 1 /* seconds */
|
||
|
|
||
|
static struct rlimit cpu_limit = { RLIM_INFINITY, RLIM_INFINITY };
|
||
|
static struct rlimit mem_limit = { RLIM_INFINITY, RLIM_INFINITY };
|
||
|
static bool debug = false;
|
||
|
|
||
|
static struct player {
|
||
|
volatile pid_t pid;
|
||
|
int pipe_from_player;
|
||
|
int pipe_to_player;
|
||
|
|
||
|
volatile bool soft_timeout;
|
||
|
volatile bool hard_timeout;
|
||
|
} player[2];
|
||
|
|
||
|
static struct player * volatile current_player = NULL;
|
||
|
|
||
|
/* Specify seconds = RLIM_INFINITY to disarm timer. */
|
||
|
static void
|
||
|
arm_timer(time_t seconds)
|
||
|
{
|
||
|
struct itimerval t;
|
||
|
|
||
|
t.it_interval.tv_sec = 0;
|
||
|
t.it_interval.tv_usec = 0;
|
||
|
t.it_value.tv_sec = (seconds == (int)RLIM_INFINITY) ? 0 : seconds;
|
||
|
t.it_value.tv_usec = 0;
|
||
|
|
||
|
if (setitimer(ITIMER_REAL, &t, NULL) < 0)
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
alarm_handler(int signum, siginfo_t *, void *)
|
||
|
{
|
||
|
assert(signum == SIGALRM);
|
||
|
|
||
|
struct player *p = current_player;
|
||
|
assert(p);
|
||
|
|
||
|
if (!p->soft_timeout) {
|
||
|
p->soft_timeout = true;
|
||
|
kill(p->pid, SIGXCPU);
|
||
|
arm_timer(static_cast<time_t>(cpu_limit.rlim_max - cpu_limit.rlim_cur));
|
||
|
} else {
|
||
|
p->hard_timeout = true;
|
||
|
kill(p->pid, SIGKILL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static char const *
|
||
|
si_code_str(int si_code)
|
||
|
{
|
||
|
switch(si_code) {
|
||
|
case CLD_EXITED: return "exited";
|
||
|
case CLD_KILLED: return "killed";
|
||
|
case CLD_DUMPED: return "coredumped";
|
||
|
case CLD_TRAPPED: return "trapped";
|
||
|
default: break;
|
||
|
}
|
||
|
return "unknown";
|
||
|
}
|
||
|
|
||
|
static char const *
|
||
|
signal_str(int sig)
|
||
|
{
|
||
|
switch(sig)
|
||
|
{
|
||
|
#define CASE(x) case x: return #x;
|
||
|
CASE(SIGHUP); CASE(SIGINT); CASE(SIGILL); CASE(SIGABRT);
|
||
|
CASE(SIGSEGV); CASE(SIGFPE); CASE(SIGPIPE); CASE(SIGKILL);
|
||
|
CASE(SIGTERM); CASE(SIGALRM); CASE(SIGUSR1); CASE(SIGUSR2);
|
||
|
CASE(SIGBUS); CASE(SIGCHLD);
|
||
|
#undef CASE
|
||
|
}
|
||
|
return "unknown";
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
child_handler(int signum, siginfo_t *si, void *)
|
||
|
{
|
||
|
/* For any SIGCHLD code other than STOP/CONT, we print additional info:
|
||
|
* si_code contains what happened to the child. If the child was terminated
|
||
|
* with a signal (most common case), then si_status additionally contains
|
||
|
* the signal that caused termination.
|
||
|
*/
|
||
|
if ((si->si_code != CLD_STOPPED) &&
|
||
|
(si->si_code != CLD_CONTINUED)) {
|
||
|
// si_status contains the signal that caused the child to terminate
|
||
|
switch(si->si_status) {
|
||
|
case SIGKILL:
|
||
|
case SIGSEGV:
|
||
|
printf("\n\033[31mCHILD signal: %d ", si->si_status);
|
||
|
printf("(%s,%s)\033[0m\n", si_code_str(si->si_code), signal_str(si->si_status));
|
||
|
printf("killing the other one\n");
|
||
|
for (int i = 0; i < 2; i++) {
|
||
|
int status;
|
||
|
kill(player[i].pid, SIGTERM);
|
||
|
kill(player[i].pid, SIGCONT);
|
||
|
waitpid(player[i].pid, &status, 0);
|
||
|
}
|
||
|
printf("MCP exiting\n");
|
||
|
exit(1);
|
||
|
break;
|
||
|
case SIGTERM:
|
||
|
printf("\n\033[32mCHILD terminated successfully\033[0m\n");
|
||
|
break;
|
||
|
default:
|
||
|
printf(" HANDLEME!\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void __attribute__((noreturn))
|
||
|
exit_msg(enum exit_reason r, const char *msg, ...)
|
||
|
{
|
||
|
va_list ap;
|
||
|
va_start(ap, msg);
|
||
|
vfprintf(stderr, msg, ap);
|
||
|
exit(r);
|
||
|
va_end(ap);
|
||
|
}
|
||
|
|
||
|
static bool
|
||
|
player_move(struct player *the_player,
|
||
|
const game_state *state,
|
||
|
game_move *move)
|
||
|
{
|
||
|
bool succ;
|
||
|
|
||
|
the_player->hard_timeout = false;
|
||
|
the_player->soft_timeout = false;
|
||
|
|
||
|
/* Arm timer with softlimit */
|
||
|
current_player = the_player;
|
||
|
arm_timer(static_cast<time_t>(cpu_limit.rlim_cur));
|
||
|
|
||
|
if (kill(the_player->pid, SIGCONT) < 0) return false;
|
||
|
|
||
|
ssize_t written = serialize_state(the_player->pipe_to_player, state);
|
||
|
if (the_player->hard_timeout) goto timeout;
|
||
|
if (written < 0)
|
||
|
return false;
|
||
|
|
||
|
succ = deserialize_move(the_player->pipe_from_player, move);
|
||
|
if (the_player->hard_timeout) goto timeout;
|
||
|
if (!succ) return false;
|
||
|
|
||
|
/* Stop player and disarm timer. */
|
||
|
if (kill(the_player->pid, SIGSTOP) < 0) return false;
|
||
|
arm_timer(static_cast<time_t>(RLIM_INFINITY));
|
||
|
current_player = NULL;
|
||
|
|
||
|
return true;
|
||
|
|
||
|
timeout:
|
||
|
fprintf(stderr, "Player timeout!\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
print_usage()
|
||
|
{
|
||
|
fprintf(stderr, "Usage: mcp [-s state_string] [-t soft-player-time] [-m soft-player-mem]\n"
|
||
|
" [-T hard-player-time] [-M hard-player-mem]\n"
|
||
|
" [-d]\n"
|
||
|
" player1 player2\n"
|
||
|
" state_string - 65 character string representing an initial field state\n"
|
||
|
" (this is the string the MCP prints before every move.)\n"
|
||
|
" player-time - CPU time per turn in seconds\n"
|
||
|
" player-mem - Memory limit per player in megabytes\n"
|
||
|
" -d - turn on debugging, program won't exit on invalid move\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
setup_signal_handlers()
|
||
|
{
|
||
|
/* Ignore SIGPIPE. Accessing dead pipes now returns EPIPE. */
|
||
|
struct sigaction pact;
|
||
|
pact.sa_handler = SIG_IGN;
|
||
|
sigemptyset(&pact.sa_mask);
|
||
|
pact.sa_restorer = 0;
|
||
|
pact.sa_sigaction = 0;
|
||
|
pact.sa_flags = 0;
|
||
|
if (sigaction(SIGPIPE, &pact, NULL) != 0)
|
||
|
abort();
|
||
|
|
||
|
/* Handle SIGALRM. */
|
||
|
struct sigaction aact;
|
||
|
aact.sa_handler = 0;
|
||
|
sigemptyset(&aact.sa_mask);
|
||
|
aact.sa_restorer = 0;
|
||
|
aact.sa_sigaction = alarm_handler;
|
||
|
aact.sa_flags = SA_SIGINFO | SA_RESTART;
|
||
|
if (sigaction(SIGALRM, &aact, NULL) != 0)
|
||
|
abort();
|
||
|
|
||
|
// Catch SIGCHLD
|
||
|
struct sigaction cact;
|
||
|
cact.sa_handler = 0;
|
||
|
sigemptyset(&cact.sa_mask);
|
||
|
cact.sa_restorer = 0;
|
||
|
cact.sa_sigaction = child_handler;
|
||
|
cact.sa_flags = SA_SIGINFO | SA_RESTART /*| SA_NOCLDSTOP*/;
|
||
|
if (sigaction(SIGCHLD, &cact, NULL) != 0)
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void getTimeStr(char *timebuf, size_t len)
|
||
|
{
|
||
|
time_t rawtime;
|
||
|
struct tm *timeinfo;
|
||
|
time(&rawtime);
|
||
|
if (rawtime == (time_t)-1) {
|
||
|
perror("time()");
|
||
|
}
|
||
|
timeinfo = localtime(&rawtime);
|
||
|
if (!timeinfo) {
|
||
|
perror("localtime()");
|
||
|
}
|
||
|
strftime(timebuf, len, "%H:%M:%S", timeinfo);
|
||
|
}
|