server.c (6747B)
1 #include "server.h" 2 3 struct opts opts = { 4 .verbose = false, 5 .game_seed = 0, 6 .agent_threads = 4, 7 .agent_mem_mib = 1024, 8 .agent_timeout = 300, 9 .agents = {0}, 10 }; 11 12 static char const *optstr = "hvg:t:m:s:"; 13 14 static void 15 usage(char **argv) 16 { 17 fprintf(stderr, "Usage: %s [-hv] [-g <game-seed>] [-t <agent-thread-limit] [-m <agent-mem-limit-mib>] [-s <agent-timeout>] <uid:agent>...\n", argv[0]); 18 fprintf(stderr, "\t-h :\n\t\tdisplay this help message\n"); 19 fprintf(stderr, "\t-v :\n\t\tenable verbose logging\n"); 20 fprintf(stderr, "\t-g :\n\t\tset the seed for the generated game board\n"); 21 fprintf(stderr, "\t-t :\n\t\tset the per-agent thread limit (default: 4)\n"); 22 fprintf(stderr, "\t-m :\n\t\tset the per-agent memory limit in MiB (default: 1024 MiB)\n"); 23 fprintf(stderr, "\t-s :\n\t\tset the per-agent timeout (default 300 secs)\n"); 24 fprintf(stderr, "\n"); 25 fprintf(stderr, "\tagent :\n\t\tadds the given agent, with the given uid (between %d and %d times)\n", GAME_AGENTS_MIN, GAME_AGENTS_MAX); 26 fprintf(stderr, "\n"); 27 fprintf(stderr, "\tNOTE: agent programs must expect to receive the following commandline arguments:\n"); 28 fprintf(stderr, "\t\t1. host :\n\t\t\tsocket host to connect to\n"); 29 fprintf(stderr, "\t\t2. port :\n\t\t\tsocket port to connect to\n"); 30 } 31 32 #include <getopt.h> 33 34 static bool 35 parse_args(int argc, char **argv) 36 { 37 int opt; 38 while ((opt = getopt(argc, argv, optstr)) != -1) { 39 switch (opt) { 40 case 'v': 41 opts.verbose = true; 42 break; 43 44 case 'g': 45 if (!(opts.game_seed = strtoull(optarg, NULL, 10))) 46 return false; 47 break; 48 49 case 't': 50 if (!(opts.agent_threads = strtoull(optarg, NULL, 10))) 51 return false; 52 break; 53 54 case 'm': 55 if (!(opts.agent_mem_mib = strtoull(optarg, NULL, 10))) 56 return false; 57 break; 58 59 case 's': 60 if (!(opts.agent_timeout = strtoull(optarg, NULL, 10))) 61 return false; 62 break; 63 64 default: 65 return false; 66 } 67 } 68 69 opts.agents.ptr = argv + optind; 70 opts.agents.len = argc - optind; 71 72 if (opts.agents.len < GAME_AGENTS_MIN || opts.agents.len > GAME_AGENTS_MAX) 73 return false; 74 75 return true; 76 } 77 78 int 79 main(int argc, char **argv) 80 { 81 if (!parse_args(argc, argv)) { 82 usage(argv); 83 exit(EXIT_FAILURE); 84 }; 85 86 fprintf(stderr, "====== Args ======\n"); 87 fprintf(stderr, " verbose: %d\n", opts.verbose); 88 fprintf(stderr, " game seed: %" PRIu32 "\n", opts.game_seed); 89 fprintf(stderr, " agent threads: %" PRIu32 "\n", opts.agent_threads); 90 fprintf(stderr, " agent mem MiB: %" PRIu32 "\n", opts.agent_mem_mib); 91 fprintf(stderr, " agent timeout: %" PRIu32 " secs\n", opts.agent_timeout); 92 fprintf(stderr, " agents: %" PRIuMAX "\n", opts.agents.len); 93 for (size_t i = 0; i < opts.agents.len; i++) 94 fprintf(stderr, "\tagent %zu: %s\n", i, opts.agents.ptr[i]); 95 96 struct server_state server; 97 if (!server_init(&server)) { 98 fprintf(stderr, "Failed to initialise server\n"); 99 exit(EXIT_FAILURE); 100 } 101 102 struct agent_state agents[opts.agents.len]; 103 for (size_t i = 0; i < opts.agents.len; i++) { 104 char *agent = opts.agents.ptr[i]; 105 106 char *saveptr; 107 char *raw_uid = strtok_r(agent, ":", &saveptr); 108 char *cmd = strtok_r(NULL, "\n", &saveptr); 109 110 if (!cmd) { 111 fprintf(stderr, "Bad agent string: %s\n", agent); 112 exit(EXIT_FAILURE); 113 } 114 115 uid_t uid = strtoul(raw_uid, NULL, 10); 116 if (!uid) { 117 fprintf(stderr, "Bad uid given: %s\n", raw_uid); 118 exit(EXIT_FAILURE); 119 } 120 121 if (!server_start_agent(&server, &agents[i], i, cmd, uid)) { 122 fprintf(stderr, "Failed to start agent %zu, '%s' as uid %d\n", i, cmd, uid); 123 exit(EXIT_FAILURE); 124 } 125 } 126 127 server_run(&server, agents, ARRLEN(agents)); 128 129 for (size_t i = 0; i < opts.agents.len; i++) { 130 server_wait_agent(&agents[i]); 131 } 132 133 exit(EXIT_SUCCESS); 134 } 135 136 bool 137 server_init(struct server_state *server) 138 { 139 struct addrinfo hints = { 140 .ai_flags = AI_PASSIVE | AI_NUMERICSERV, 141 .ai_family = AF_UNSPEC, 142 .ai_socktype = SOCK_STREAM, 143 }, *addrinfo, *ptr; 144 145 int res; 146 if ((res = getaddrinfo(NULL, "0", &hints, &addrinfo))) { 147 fprintf(stderr, "Failed to get address info for server socket: %s\n", gai_strerror(res)); 148 return false; 149 } 150 151 int fd; 152 for (ptr = addrinfo; ptr; ptr = ptr->ai_next) { 153 fd = socket(ptr->ai_family, ptr->ai_socktype | SOCK_CLOEXEC, ptr->ai_protocol); 154 if (fd == -1) 155 continue; 156 157 if (bind(fd, ptr->ai_addr, ptr->ai_addrlen) == -1) { 158 perror("bind"); 159 close(fd); 160 continue; 161 } 162 163 if (listen(fd, GAME_AGENTS_MAX) == -1) { 164 perror("listen"); 165 close(fd); 166 continue; 167 } 168 169 break; 170 } 171 172 if (ptr) { 173 server->sockfd = fd; 174 175 struct sockaddr_storage addr; 176 socklen_t addrlen = sizeof addr; 177 getsockname(fd, (struct sockaddr *) &addr, &addrlen); 178 179 getnameinfo((struct sockaddr *) &addr, addrlen, 180 server->addr, ARRLEN(server->addr), 181 server->port, ARRLEN(server->port), 182 AI_NUMERICHOST | AI_NUMERICSERV); 183 184 fprintf(stderr, "Hosting server on %s:%s\n", server->addr, server->port); 185 } 186 187 freeaddrinfo(addrinfo); 188 189 if (!ptr) { 190 fprintf(stderr, "Failed to bind server socket\n"); 191 return false; 192 } 193 194 // TODO: initialise board 195 196 return true; 197 } 198 199 bool 200 server_start_agent(struct server_state *server, struct agent_state *agent, 201 uint8_t id, char *cmd, uid_t uid) 202 { 203 pid_t pid = fork(); 204 if (pid == -1) { 205 perror("fork"); 206 return false; 207 } else if (pid == 0) /* child */ { 208 struct rlimit nproc_rlimit = { 209 .rlim_cur = opts.agent_threads, 210 .rlim_max = opts.agent_threads, 211 }; 212 213 prlimit(getpid(), RLIMIT_NPROC, &nproc_rlimit, NULL); 214 215 struct rlimit data_rlimit = { 216 .rlim_cur = opts.agent_mem_mib * MiB, 217 .rlim_max = opts.agent_mem_mib * MiB, 218 }; 219 220 prlimit(getpid(), RLIMIT_DATA, &data_rlimit, NULL); 221 222 setuid(uid); 223 224 char *args[] = { 225 cmd, 226 server->addr, 227 server->port, 228 NULL, 229 }; 230 231 char *envp[] = { NULL, }; 232 233 execvpe(cmd, args, envp); 234 235 perror("execv"); 236 exit(EXIT_FAILURE); 237 } 238 239 /* parent */ 240 agent->id = id; 241 agent->pid = pid; 242 243 struct pollfd acceptfd = { 244 .fd = server->sockfd, 245 .events = POLLIN, 246 }; 247 248 int res = poll(&acceptfd, 1, AGENT_ACCEPT_TIMEOUT_MS); 249 if (res <= 0) { 250 fprintf(stderr, "Failed to accept agent: %s, timeout\n", cmd); 251 return false; 252 } 253 254 agent->sockfd = accept4(server->sockfd, NULL, NULL, SOCK_CLOEXEC); 255 if (agent->sockfd < 0) { 256 fprintf(stderr, "Failed to accept agent: %s\n", cmd); 257 return false; 258 } 259 260 return true; 261 } 262 263 void 264 server_wait_agent(struct agent_state *agent) 265 { 266 kill(agent->pid, SIGKILL); 267 268 int stats; 269 if (waitpid(agent->pid, &stats, WNOHANG) == -1) { 270 kill(agent->pid, SIGTERM); 271 272 waitpid(agent->pid, &stats, 0); 273 } 274 } 275 276 void 277 server_run(struct server_state *server, struct agent_state *agents, size_t len) 278 { 279 (void) server; 280 (void) agents; 281 (void) len; 282 283 // TODO: send begin message 284 // TODO: send setup message 285 // TODO: start turns 286 // TODO: play until an agent wins 287 // TODO: send end message 288 }