updated docs
[rlserver.git] / run.c
blob6692841980eead2307fe22384218bf7d6e3b052c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <signal.h>
6 #include <sys/socket.h>
7 #include <sys/select.h>
8 #include <sys/wait.h>
9 #include <sys/stat.h>
10 #include <libgen.h>
11 #include <pthread.h>
12 #include <pty.h>
13 #include "util.h"
14 #include "log.h"
15 #include "stats.h"
16 #include "telnet.h"
17 #include "vt100.h"
18 #include "sessions.h"
19 #include "server.h"
20 #include "run.h"
23 extern server_config config;
27 * close everything that is not neccessary before running a game in a child process
29 static void close_server_things (void)
31 close_sessions ();
33 extern int server_socket;
34 close (server_socket);
36 // local stdio is not used
37 close (0);
38 close (1);
39 close (2);
44 static void send_to_observers (session_info *sess, const char *buf, int len)
46 int i;
48 pthread_mutex_lock (&sess->mutex);
49 for (i = 0; i < sess->observer_count; i++)
51 send (sess->observers[i], buf, len, 0);
53 pthread_mutex_unlock (&sess->mutex);
58 static void remove_observers (session_info *sess)
60 const char msg[] = "\033[0m\033[40;37m\r\nThe observed game was disconnected\r\n";
61 send_to_observers (sess, msg, sizeof(msg) - 1);
63 pthread_mutex_lock (&sess->mutex);
64 free (sess->observers);
65 sess->observers = NULL;
66 sess->observer_count = 0;
67 pthread_mutex_unlock (&sess->mutex);
73 #define BUF_SIZE 16*1024
74 static void play_game (session_info *sess, char enter_char)
76 char buf[BUF_SIZE];
77 fd_set rfds;
78 int i, sn = (sess->socket > sess->pty_master) ? sess->socket : sess->pty_master;
81 sess->term = init_term(sess->term_wid, sess->term_hgt);
83 while (1)
85 FD_ZERO (&rfds);
86 FD_SET (sess->pty_master, &rfds);
87 FD_SET (sess->socket, &rfds);
89 // wait until game prints something to output
90 // or player sends something over the net
91 if (select(sn + 1, &rfds, NULL, NULL, NULL) == -1)
93 write_log (LOG_ERROR, "select failed");
94 break;
97 // got output from the running game
98 if (FD_ISSET(sess->pty_master, &rfds))
100 int nr = read(sess->pty_master, buf, BUF_SIZE);
101 if (nr <= 0) break;
103 // send it to the player
104 send (sess->socket, buf, nr, 0);
106 // update terminal
107 if (sess->term != NULL) term_process (sess->term, buf, nr);
109 // duplicate data for the observers
110 send_to_observers (sess, buf, nr);
112 sess->game_stat.bytes_out += nr;
115 // got input from the player trough the net
116 if (FD_ISSET(sess->socket, &rfds))
118 int nr = recv(sess->socket, buf, BUF_SIZE, 0);
119 if (nr <= 0)
121 write_log (LOG_DEBUG, "user %s disconnects while playing %s", sess->user_name, sess->game);
122 break;
125 sess->game_stat.bytes_in += nr;
127 nr = translate_telnet_input(buf, nr, sess);
128 if (nr > 0)
130 // replace ENTER key
131 for (i = 0; i < nr; i++) if (buf[i] == '\n') buf[i] = enter_char;
133 write (sess->pty_master, buf, nr);
136 // update activity timestamp
137 sess->last_activity = time(NULL);
144 static void set_game_dir (const session_info *sess, const game_info *game)
146 char buf[BUF_SIZE];
149 if (!game->dir[0]) return;
151 struct stat st;
153 str_replace (buf, BUF_SIZE, game->dir, "$USER$", sess->user_name);
155 // create directory if it doesn't exist
156 if (stat(buf, &st) != 0)
158 write_log (LOG_DEBUG, "creating directory %s", buf);
159 make_dir (buf);
162 // change to the directory
163 chdir (buf);
168 static void set_game_files (const session_info *sess, const game_info *game)
170 char buf[BUF_SIZE], dir[BUF_SIZE];
171 struct stat st;
172 FILE *file, *copy;
173 int i, sz;
176 for (i = 0; i < GAME_FILES; i++)
178 if (!game->files[i].file[0]) break;
180 // copy file name to bufer, substituting user name
181 str_replace (buf, BUF_SIZE, game->files[i].copy, "$USER$", sess->user_name);
183 // file already exist
184 if (stat(buf, &st) == 0) continue;
186 file = fopen(game->files[i].file, "r");
187 if (file == NULL)
189 write_log (LOG_ERROR, "unable to open file %s for game %s", game->files[i].file, game->id);
190 continue;
193 // create directory for the new file
194 strcpy (dir, buf);
195 make_dir (dirname(dir));
197 copy = fopen(buf, "w");
198 if (copy == NULL)
200 write_log (LOG_ERROR, "unable to copy file %s (specified as %s) for game %s", buf, game->files[i].copy, game->id);
201 fclose (file);
202 continue;
205 // actually copy data
208 sz = fread(buf, 1, BUF_SIZE, file);
209 fwrite(buf, sz, 1, copy);
210 } while (sz >= BUF_SIZE);
212 fclose (file);
213 fclose (copy);
219 #define MAX_ARGS 32
220 static void exec_game (session_info *sess, const game_info *game)
222 char buf[BUF_SIZE];
223 char *arg[MAX_ARGS];
224 int b, a = 0;
227 // change to the directory specified in game config
228 set_game_dir (sess, game);
230 // copy user-specific game files if required by game config
231 set_game_files (sess, game);
233 // copy command line to bufer, substituting user name
234 str_replace (buf, BUF_SIZE, game->cmd, "$USER$", sess->user_name);
236 write_log (LOG_DEBUG, "user %s starts game %s", sess->user_name, buf);
238 // break buf[] into zero-terminated strings, filling arg[] with pointers to them
239 // TODO: add quotes or \ to escape spaces
240 arg[a++] = &buf[0];
241 for (b = 0; buf[b] != 0; b++)
243 if (buf[b] == ' ')
245 buf[b] = 0;
246 if (buf[b + 1] != 0 && a < MAX_ARGS - 1)
248 arg[a++] = &buf[b + 1];
253 arg[a] = NULL;
255 execvp (arg[0], arg);
260 static void close_game (session_info *sess)
262 // check if the game has terminated already
263 if (waitpid(sess->child_pid, NULL, WNOHANG) != 0)
265 sess->child_pid = 0;
266 return;
269 if (waitpid(sess->child_pid, NULL, WNOHANG) == 0)
271 // wait some time to allow the game to handle the last input
272 sleep (config.delay_before_kill);
274 // if the game is still running, send SIGTERM
275 if (waitpid(sess->child_pid, NULL, WNOHANG) == 0)
277 kill (sess->child_pid, SIGTERM);
281 sess->child_pid = 0;
282 sess->game[0] = 0;
287 void run_game (session_info *sess, const game_info *game)
289 int pty_master, pty_slave;
290 int pid;
291 struct winsize ws;
294 ws.ws_col = sess->term_wid;
295 ws.ws_row = sess->term_hgt;
296 if (openpty(&pty_master, &pty_slave, NULL, NULL, &ws) == -1)
298 write_log (LOG_ERROR, "openpty failed");
299 return;
302 if ((pid = fork()) < 0)
304 write_log (LOG_ERROR, "fork failed");
305 close (pty_slave);
306 close (pty_master);
307 return;
310 // child
311 if (pid == 0)
313 close_server_things ();
315 dup2 (pty_slave, 0); // reassign stdin to pty
316 dup2 (pty_slave, 1); // reassign stdout to pty
317 dup2 (pty_slave, 2); // reassign stderr to pty
318 // these are not needed anymore
319 close (pty_slave);
320 close (pty_master);
322 exec_game (sess, game);
324 // exec should never return
325 write_log (LOG_ERROR, "exec in child failed");
326 return;
330 // parent
332 close (pty_slave);
334 sess->pty_master = pty_master;
335 sess->child_pid = pid;
336 strcpy (sess->game, game->name);
337 sess->game_stat.start_time = time(NULL);
338 sess->game_stat.bytes_in = sess->game_stat.bytes_out = 0;
340 play_game (sess, game->enter);
342 // update statistics if it's a real playing session (not editor or scores)
343 if (sess->observer_count != -1) update_stats (game, sess);
345 remove_observers (sess);
347 close_game (sess);
349 sess->pty_master = -1;
350 close (pty_master);