Implemented game profiles
[rlserver.git] / sessions.c
blobd7f32aca8f4e0ee88f0a915d837b21246e63277d
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdint.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <signal.h>
7 #include <sys/socket.h>
8 #include <sys/select.h>
9 #include <sys/wait.h>
10 #include <pty.h>
11 #include <pthread.h>
12 #include "telnet.h"
13 #include "vt100.h"
14 #include "sessions.h"
16 // grace time before sending SIGTERM
17 #define TIME_BEFORE_SIGTERM 5
19 session_info *sessionlist = NULL;
20 int session_count = 0;
21 pthread_mutex_t sessionlist_mutex = PTHREAD_MUTEX_INITIALIZER;
25 session_info* add_session (int sock)
27 session_info *ps = (session_info*)malloc(sizeof(session_info));
28 if (ps == NULL) return NULL;
30 // fill data
31 ps->prev = NULL;
32 ps->next = NULL;
33 ps->child_pid = 0;
34 ps->socket = sock;
35 ps->user_id = -1;
36 ps->user_name[0] = 0;
37 ps->game[0] = 0;
38 ps->term = NULL;
39 ps->term_wid = 80;
40 ps->term_hgt = 24;
41 ps->pty_master = -1;
42 ps->observer_count = 0;
43 ps->observers = NULL;
44 pthread_mutex_init (&ps->mutex, NULL);
45 ps->last_activity = time(NULL);
48 pthread_mutex_lock (&sessionlist_mutex);
50 // add it to the list
51 if (sessionlist == NULL)
53 sessionlist = ps;
55 else
57 session_info *p = sessionlist;
58 while (p->next != NULL) p = p->next;
59 p->next = ps;
60 ps->prev = p;
62 session_count++;
64 pthread_mutex_unlock (&sessionlist_mutex);
66 return ps;
71 void remove_session (session_info *sess)
73 session_info *s;
75 pthread_mutex_lock (&sessionlist_mutex);
76 pthread_mutex_lock (&sess->mutex);
78 for (s = sessionlist; s != NULL; s = s->next)
80 // remove sess from the list
81 if (s == sess)
83 if (s->next) s->next->prev = s->prev;
84 if (s->prev) s->prev->next = s->next;
86 if (sessionlist == sess) sessionlist = sess->next;
87 session_count--;
89 // remove socket from observers
90 else
92 int i;
93 // no need to lock s->mutex because add_observer() is blocked by sessionlist_mutex
94 for (i = 0; i < s->observer_count; i++)
96 if (s->observers[i] == sess->socket)
98 s->observers[i--] = s->observers[s->observer_count--];
104 pthread_mutex_unlock (&sessionlist_mutex);
106 pthread_mutex_unlock (&sess->mutex);
107 pthread_mutex_destroy (&sess->mutex);
108 free (sess->term);
109 free (sess);
114 void stop_observing (session_info *sess)
116 session_info *s;
118 pthread_mutex_lock (&sessionlist_mutex);
120 for (s = sessionlist; s != NULL; s = s->next)
122 // remove socket from observers
123 int i;
124 // no need to lock s->mutex because add_observer() is blocked by sessionlist_mutex
125 for (i = 0; i < s->observer_count; i++)
127 if (s->observers[i] == sess->socket)
129 s->observers[i--] = s->observers[s->observer_count--];
134 pthread_mutex_unlock (&sessionlist_mutex);
139 int add_observer (int n, int sock)
141 session_info *s;
142 int i = 0, done = 0;
144 pthread_mutex_lock (&sessionlist_mutex);
146 for (s = sessionlist; s != NULL; s = s->next)
148 if (s->game[0] == 0) continue;
149 if (i++ != n) continue;
151 pthread_mutex_lock (&s->mutex);
152 s->observers = (int*)realloc(s->observers, (s->observer_count + 1) * sizeof(int));
153 s->observers[s->observer_count++] = sock;
154 pthread_mutex_unlock (&s->mutex);
155 done = 1;
156 break;
159 if (s->term != NULL) term_copy_data (s->term, sock);
161 pthread_mutex_unlock (&sessionlist_mutex);
162 return done;
168 * close everything that is not neccessary before running a game in a child process
170 static void close_server_things (void)
172 close_sessions ();
174 extern int server_socket;
175 close (server_socket);
177 // local stdio is not used
178 close (0);
179 close (1);
180 close (2);
185 static void send_to_observers (session_info *sess, const uint8_t *buf, int len)
187 int i;
189 pthread_mutex_lock (&sess->mutex);
190 for (i = 0; i < sess->observer_count; i++)
192 send (sess->observers[i], buf, len, 0);
194 pthread_mutex_unlock (&sess->mutex);
199 static void remove_observers (session_info *sess)
201 const char msg[] = "\033[0m\033[40;37m\r\nThe observed game was disconnected\r\n";
202 send_to_observers (sess, msg, sizeof(msg) - 1);
204 pthread_mutex_lock (&sess->mutex);
205 free (sess->observers);
206 sess->observers = NULL;
207 sess->observer_count = 0;
208 pthread_mutex_unlock (&sess->mutex);
213 static void close_game (session_info *sess)
215 // check if the game has terminated already
216 if (waitpid(sess->child_pid, NULL, WNOHANG) != 0)
218 sess->child_pid = 0;
219 return;
222 // wait some time to allow the game to handle the last input
223 sleep (TIME_BEFORE_SIGTERM);
225 // if the game is still running, send SIGTERM
226 if (waitpid(sess->child_pid, NULL, WNOHANG) == 0)
228 kill (sess->child_pid, SIGTERM);
230 sess->child_pid = 0;
235 #define BUF_SIZE 16*1024
236 static void play_game (session_info *sess)
238 uint8_t buf[BUF_SIZE];
239 fd_set rfds;
240 int sn = (sess->socket > sess->pty_master) ? sess->socket : sess->pty_master;
242 sess->term = init_term(sess->term_wid, sess->term_hgt);
244 while (1)
246 FD_ZERO (&rfds);
247 FD_SET (sess->pty_master, &rfds);
248 FD_SET (sess->socket, &rfds);
250 // wait until game prints something to output
251 // or player sends something over the net
252 if (select(sn + 1, &rfds, NULL, NULL, NULL) == -1)
254 perror ("select");
255 break;
258 // got output from the running game
259 if (FD_ISSET(sess->pty_master, &rfds))
261 int nr = read(sess->pty_master, buf, BUF_SIZE);
262 if (nr <= 0) break;
264 // send it to the player
265 send (sess->socket, buf, nr, 0);
267 // update terminal
268 if (sess->term != NULL) term_process (sess->term, buf, nr);
270 // duplicate data for the observers
271 send_to_observers (sess, buf, nr);
274 // got input from the player trough the net
275 if (FD_ISSET(sess->socket, &rfds))
277 int nr = recv(sess->socket, buf, BUF_SIZE, 0);
278 if (nr <= 0) break;
280 nr = translate_telnet_input(buf, nr, sess);
281 if (nr > 0) write (sess->pty_master, buf, nr);
283 // update activity timestamp
284 sess->last_activity = time(NULL);
291 #define MAX_ARGS 32
292 static void exec_game (session_info *sess, const game_info *game)
294 const char user_str[] = "$USER$";
295 char buf[BUF_SIZE]; // assume BUF_SIZE > GAME_COMMAND_LEN + USER_NAME_LEN
296 char *arg[MAX_ARGS];
297 int i, b, a = 0;
300 // change to the specified directory
301 if (game->dir[0]) chdir (game->dir);
303 // copy command line to bufer, substituting user name
304 for (i = b = 0; game->command[i] != 0; i++, b++)
306 if (strncmp(&game->command[i], user_str, sizeof(user_str) - 1) == 0)
308 const char *u = sess->user_name;
309 while ((buf[b++] = *(u++)));
310 i += sizeof(user_str) - 1 - 1;
311 b -= 2;
313 else
315 buf[b] = game->command[i];
318 buf[b] = 0;
320 // break buf[] into zero-terminated strings, filling arg[] with pointers to them
321 // TODO: add quotes or \ to escape spaces
322 arg[a++] = &buf[0];
323 for (b = 0; buf[b] != 0; b++)
325 if (buf[b] == ' ')
327 buf[b] = 0;
328 if (buf[b + 1] != 0 && a < MAX_ARGS - 1)
330 arg[a++] = &buf[b + 1];
335 arg[a] = NULL;
337 execvp (arg[0], arg);
342 void run_game (session_info *sess, const game_info *game)
344 int pty_master, pty_slave;
345 int pid;
346 struct winsize ws;
349 ws.ws_col = sess->term_wid;
350 ws.ws_row = sess->term_hgt;
351 if (openpty(&pty_master, &pty_slave, NULL, NULL, &ws) == -1)
353 perror ("openpty failed");
354 return;
357 if ((pid = fork()) < 0)
359 perror ("fork failed");
360 close (pty_slave);
361 close (pty_master);
362 return;
365 // child
366 if (pid == 0)
368 close_server_things ();
370 dup2 (pty_slave, 0); // reassign stdin to pty
371 dup2 (pty_slave, 1); // reassign stdout to pty
372 dup2 (pty_slave, 2); // reassign stderr to pty
373 // these are not needed anymore
374 close (pty_slave);
375 close (pty_master);
377 exec_game (sess, game);
379 // exec should never return
380 perror ("child failed");
381 return;
385 // parent
387 close (pty_slave);
389 sess->pty_master = pty_master;
390 sess->child_pid = pid;
391 strcpy (sess->game, game->name);
393 play_game (sess);
395 remove_observers (sess);
397 close_game (sess);
399 sess->pty_master = -1;
400 close (pty_master);
405 void close_sessions (void)
407 session_info *s;
408 for (s = sessionlist; s != NULL; s = s->next)
410 if (s->pty_master != -1) close (s->pty_master);
411 close (s->socket);
417 void release_sessions (void)
419 session_info *s;
420 for (s = sessionlist; s != NULL; s = s->next)
422 free (s->observers);
423 pthread_mutex_destroy (&s->mutex);