6 #include <sys/socket.h>
7 #include <sys/select.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)
33 extern int server_socket
;
34 close (server_socket
);
36 // local stdio is not used
44 static void send_to_observers (session_info
*sess
, const char *buf
, int len
)
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
)
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
);
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");
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
);
103 // send it to the player
104 send (sess
->socket
, buf
, nr
, 0);
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);
121 write_log (LOG_DEBUG
, "user %s disconnects while playing %s", sess
->user_name
, sess
->game
);
125 sess
->game_stat
.bytes_in
+= nr
;
127 nr
= translate_telnet_input(buf
, nr
, sess
);
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
)
149 if (!game
->dir
[0]) return;
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
);
162 // change to the directory
168 static void set_game_files (const session_info
*sess
, const game_info
*game
)
170 char buf
[BUF_SIZE
], dir
[BUF_SIZE
];
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");
189 write_log (LOG_ERROR
, "unable to open file %s for game %s", game
->files
[i
].file
, game
->id
);
193 // create directory for the new file
195 make_dir (dirname(dir
));
197 copy
= fopen(buf
, "w");
200 write_log (LOG_ERROR
, "unable to copy file %s (specified as %s) for game %s", buf
, game
->files
[i
].copy
, game
->id
);
205 // actually copy data
208 sz
= fread(buf
, 1, BUF_SIZE
, file
);
209 fwrite(buf
, sz
, 1, copy
);
210 } while (sz
>= BUF_SIZE
);
220 static void exec_game (session_info
*sess
, const game_info
*game
)
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
241 for (b
= 0; buf
[b
] != 0; b
++)
246 if (buf
[b
+ 1] != 0 && a
< MAX_ARGS
- 1)
248 arg
[a
++] = &buf
[b
+ 1];
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)
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
);
287 void run_game (session_info
*sess
, const game_info
*game
)
289 int pty_master
, pty_slave
;
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");
302 if ((pid
= fork()) < 0)
304 write_log (LOG_ERROR
, "fork failed");
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
322 exec_game (sess
, game
);
324 // exec should never return
325 write_log (LOG_ERROR
, "exec in child failed");
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
);
349 sess
->pty_master
= -1;