You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

257 lines
6.2 KiB

#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);
}