73b8b057cfe6071535733292bde293986f72930d
[paraslash.git] / server.c
1 /*
2 * Copyright (C) 1997-2006 Andre Noll <maan@systemlinux.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
17 */
18
19 /** \file server.c Paraslash's main server */
20
21
22 /** \mainpage Paraslash API Reference
23 *
24 * Good starting points for reading are probably \ref dbtool, \ref sender,
25 * \ref receiver, \ref receiver_node, \ref filter, \ref filter_node.
26 *
27 */
28
29
30
31 #include "server.cmdline.h"
32 #include "db.h"
33 #include "server.h"
34 #include "afs.h"
35 #include "config.h"
36 #include "close_on_fork.h"
37 #include <sys/mman.h> /* MAP_SHARED, PROT_READ, PROT_WRITE */
38 #include "send.h"
39 #include "error.h"
40 #include "net.h"
41 #include "daemon.h"
42 #include "string.h"
43
44 /** define the array of error lists needed by para_server */
45 INIT_SERVER_ERRLISTS;
46
47 /** shut down non-authorized connections after that many seconds */
48 #define ALARM_TIMEOUT 10
49
50 /* these are exported to afs/command/dbtool */
51 struct misc_meta_data *mmd;
52 /** the configuration of para_server
53 *
54 * It also contains the options for all database tools and all supported
55 * senders.
56 */
57 struct gengetopt_args_info conf;
58 char *user_list = NULL;
59 extern void http_send_init(struct sender *);
60 extern void ortp_send_init(struct sender *);
61 extern struct audio_format afl[];
62
63 /** the list of supported database tools */
64 struct dbtool dblist[] = {
65 {
66 .name = "random",
67 .init = random_dbtool_init,
68 .update_audio_file = NULL,
69 },
70 {
71 .name = "plm",
72 .init = plm_dbtool_init,
73 .update_audio_file = NULL,
74 },
75 #ifdef HAVE_MYSQL
76 {
77 .name = "mysql",
78 .init = mysql_dbtool_init,
79 .update_audio_file = NULL,
80 },
81 #endif
82 {
83 .name = NULL,
84 }
85 };
86
87 /** the list of supported senders */
88 struct sender senders[] = {
89 {
90 .name = "http",
91 .init = http_send_init,
92 },
93 #ifdef HAVE_ORTP
94 {
95 .name = "ortp",
96 .init = ortp_send_init,
97 },
98 #endif
99 {
100 .name = NULL,
101 }
102 };
103
104
105 /* global variables for server-internal use */
106 static FILE *logfile;
107 static int mmd_semid;
108 static int signal_pipe;
109
110 /**
111 * para_server's log function
112 *
113 * \param ll the log level
114 * \param fmt the format string describing the log message
115 */
116 void para_log(int ll, char* fmt,...)
117 {
118 va_list argp;
119 FILE *outfd;
120 struct tm *tm;
121 time_t t1;
122 char str[MAXLINE] = "";
123 pid_t mypid;
124
125 if (ll < conf.loglevel_arg)
126 return;
127 if (!logfile) {
128 if (ll < WARNING)
129 outfd = stdout;
130 else
131 outfd = stderr;
132 } else
133 outfd = logfile;
134 if (conf.daemon_given && !logfile)
135 return;
136 time(&t1);
137 tm = localtime(&t1);
138 strftime(str, MAXLINE, "%b %d %H:%M:%S", tm);
139 fprintf(outfd, "%s ", str);
140 if (conf.loglevel_arg <= INFO)
141 fprintf(outfd, "%i: ", ll);
142 mypid = getpid();
143 if (conf.loglevel_arg <= INFO)
144 fprintf(outfd, "(%d) ", mypid);
145 va_start(argp, fmt);
146 vfprintf(outfd, fmt, argp);
147 va_end(argp);
148 }
149
150
151 /*
152 * setup shared memory area and get semaphore for locking
153 */
154 static void shm_init(void)
155 {
156 int fd;
157 caddr_t area;
158
159 if ((fd = open("/dev/zero", O_RDWR)) < 0) {
160 PARA_EMERG_LOG("%s", "failed to open /dev/zero\n");
161 exit(EXIT_FAILURE);
162 }
163 if ((area = mmap(0, sizeof(struct misc_meta_data),
164 PROT_READ | PROT_WRITE,
165 MAP_SHARED, fd, 0)) == (caddr_t) - 1) {
166 PARA_EMERG_LOG("%s", "mmap error\n");
167 exit(EXIT_FAILURE);
168 }
169 close(fd); /* we dont need /dev/zero anymore */
170 mmd = (struct misc_meta_data *)area;
171
172 mmd->dbt_num = 0;
173 mmd->num_played = 0;
174 mmd->num_commands = 0;
175 mmd->events = 0;
176 mmd->num_connects = 0;
177 mmd->active_connections = 0;
178 strcpy(mmd->filename, "(none)");
179 mmd->audio_format = -1;
180 mmd->afs_status_flags = AFS_NEXT;
181 mmd->new_afs_status_flags = AFS_NEXT;
182 mmd->sender_cmd_data.cmd_num = -1;
183
184 mmd_semid = semget(42, 1, IPC_CREAT | 0666);
185 if (mmd_semid == -1) {
186 PARA_EMERG_LOG("%s", "semget failed\n");
187 exit(EXIT_FAILURE);
188 }
189 }
190
191 static void do_semop(struct sembuf *sops, int num)
192 {
193 if (semop(mmd_semid, sops, num) >= 0)
194 return;
195 PARA_WARNING_LOG("semop failed (%s), retrying\n", strerror(errno));
196 while (semop(mmd_semid, sops, num) < 0)
197 ; /* nothing */
198 }
199
200 /**
201 * lock the shared memory area containing the mmd struct
202 *
203 * \sa semop(2), struct misc_meta_data
204 */
205 void mmd_lock(void)
206 {
207 struct sembuf sops[2] = {
208 {
209 .sem_num = 0,
210 .sem_op = 0,
211 .sem_flg = SEM_UNDO
212 },
213 {
214 .sem_num = 0,
215 .sem_op = 1,
216 .sem_flg = SEM_UNDO
217 }
218 };
219 do_semop(sops, 2);
220 }
221
222 /**
223 * unlock the shared memory area containing the mmd struct
224 *
225 * \sa semop(2), struct misc_meta_data
226 */
227 void mmd_unlock(void)
228 {
229 struct sembuf sops[1] = {
230 {
231 .sem_num = 0,
232 .sem_op = -1,
233 .sem_flg = SEM_UNDO
234 },
235 };
236 do_semop(sops, 1);
237 }
238
239 static void parse_config(int override)
240 {
241 char *home = para_homedir();
242 struct stat statbuf;
243 int ret;
244 char *cf;
245
246 if (conf.config_file_given)
247 cf = conf.config_file_arg;
248 else
249 cf = make_message("%s/.paraslash/server.conf", home);
250 free(user_list);
251 if (!conf.user_list_given)
252 user_list = make_message("%s/.paraslash/server.users", home);
253 else
254 user_list = para_strdup(conf.user_list_arg);
255 ret = stat(cf, &statbuf);
256 if (ret && conf.config_file_given) {
257 ret = -1;
258 PARA_EMERG_LOG("can not stat config file %s\n", cf);
259 goto out;
260 }
261 if (!ret) {
262 int tmp = conf.daemon_given;
263 cmdline_parser_configfile(cf, &conf, override, 0, 0);
264 conf.daemon_given = tmp;
265 }
266 /* logfile */
267 if (!conf.logfile_given && conf.daemon_given) {
268 ret = -1;
269 PARA_EMERG_LOG("%s", "daemon, but no log file\n");
270 goto out;
271 }
272 if (conf.logfile_given)
273 logfile = open_log(conf.logfile_arg);
274 ret = 1;
275 out:
276 free(cf);
277 free(home);
278 if (ret > 0)
279 return;
280 free(user_list);
281 user_list = NULL;
282 exit(EXIT_FAILURE);
283 }
284
285 static void setup_signal_handling(void)
286 {
287 int ret = 0;
288
289 signal_pipe = para_signal_init();
290 // fcntl(signal_pipe, F_SETFL, O_NONBLOCK);
291 PARA_NOTICE_LOG("%s", "setting up signal handlers\n");
292 ret += para_install_sighandler(SIGINT);
293 ret += para_install_sighandler(SIGTERM);
294 ret += para_install_sighandler(SIGHUP);
295 ret += para_install_sighandler(SIGCHLD);
296 signal(SIGPIPE, SIG_IGN);
297 signal(SIGUSR1, SIG_IGN);
298 if (ret != 4) {
299 PARA_EMERG_LOG("%s", "could not install signal handlers\n");
300 exit(EXIT_FAILURE);
301 }
302 }
303
304 static void init_dbtool(void)
305 {
306 int i;
307
308 mmd->dbt_change = -1; /* no change nec., set to new dbt num by com_cdt */
309 if (!dblist[1].name)
310 goto random;
311 if (conf.dbtool_given) {
312 for (i = 0; dblist[i].name; i++) {
313 if (strcmp(dblist[i].name, conf.dbtool_arg))
314 continue;
315 PARA_NOTICE_LOG("initializing %s database tool\n",
316 dblist[i].name);
317 if (dblist[i].init(&dblist[i]) < 0) {
318 PARA_WARNING_LOG("init %s failed",
319 dblist[i].name);
320 goto random;
321 }
322 mmd->dbt_num = i;
323 return;
324 }
325 PARA_WARNING_LOG("%s", "no such dbtool, switching to random\n");
326 goto random;
327 }
328 /* use the first dbtool that works
329 * (assuming that random always works)
330 */
331 for (i = 1; dblist[i].name; i++) {
332 int ret = dblist[i].init(&dblist[i]);
333 if (ret >= 0) {
334 PARA_INFO_LOG("initialized %s\n", dblist[i].name);
335 mmd->dbt_num = i;
336 return;
337 }
338 PARA_CRIT_LOG("%s init failed: %s\n", dblist[i].name,
339 PARA_STRERROR(-ret));
340 }
341 random:
342 mmd->dbt_num = 0;
343 dblist[0].init(&dblist[0]); /* always successful */
344 }
345
346 static unsigned init_network(void)
347 {
348 int sockfd = init_tcp_socket(conf.port_arg);
349
350 if (sockfd < 0)
351 exit(EXIT_FAILURE);
352 return sockfd;
353 }
354
355 static void init_random_seed(void)
356 {
357 int fd, ret = -1, len = sizeof(unsigned int);
358 unsigned int seed;
359
360 fd = open("/dev/random", O_RDONLY);
361 if (fd < 0)
362 goto out;
363 ret = -2;
364 if (read(fd, &seed, len) != len)
365 goto out;
366 srandom(seed);
367 ret = 1;
368 out:
369 if (fd >= 0)
370 close(fd);
371 if (ret > 0)
372 return;
373 PARA_EMERG_LOG("can not seed pseudo random generator (ret = %d)\n",
374 ret);
375 exit(EXIT_FAILURE);
376 }
377
378 static unsigned do_inits(int argc, char **argv)
379 {
380 /* connector's address information */
381 int sockfd;
382
383 init_random_seed();
384 /* parse command line options */
385 cmdline_parser(argc, argv, &conf);
386 para_drop_privileges(conf.user_arg);
387 /* parse config file, open log and set defaults */
388 parse_config(0);
389 log_welcome("para_server", conf.loglevel_arg);
390 shm_init(); /* init mmd struct */
391 server_uptime(UPTIME_SET); /* reset server uptime */
392 /* become daemon */
393 if (conf.daemon_given)
394 daemon_init();
395 init_dbtool();
396 PARA_NOTICE_LOG("%s", "initializing audio file sender\n");
397 /* audio file sender */
398 afs_init();
399 mmd->server_pid = getpid();
400 setup_signal_handling();
401 mmd_lock();
402 /* init network socket */
403 PARA_NOTICE_LOG("%s", "initializing tcp command socket\n");
404 sockfd = init_network();
405 if (conf.autoplay_given) {
406 mmd->afs_status_flags |= AFS_PLAYING;
407 mmd->new_afs_status_flags |= AFS_PLAYING;
408 }
409 PARA_NOTICE_LOG("%s", "init complete\n");
410 return sockfd;
411 }
412
413 static void handle_dbt_change(void)
414 {
415 int ret, old = mmd->dbt_num, new = mmd->dbt_change;
416
417 dblist[old].shutdown();
418 ret = dblist[new].init(&dblist[new]);
419 mmd->dbt_change = -1; /* reset */
420 if (ret >= 0) {
421 mmd->dbt_num = new;
422 return;
423 }
424 /* init failed */
425 PARA_ERROR_LOG("%s -- switching to the random dbtool\n", PARA_STRERROR(-ret));
426 dblist[0].init(&dblist[0]);
427 mmd->dbt_num = 0;
428 }
429
430 /*
431 * called when server gets SIGHUP or when client invokes hup command.
432 */
433 static void handle_sighup(void)
434 {
435 PARA_NOTICE_LOG("%s", "SIGHUP\n");
436 close_log(logfile); /* gets reopened if necessary by parse_config */
437 logfile = NULL;
438 parse_config(1); /* reopens log */
439 mmd->dbt_change = mmd->dbt_num; /* do not change dbtool */
440 handle_dbt_change(); /* force reloading dbtool */
441 }
442
443 static void status_refresh(void)
444 {
445 static int prev_uptime = -1, prev_events = -1;
446 int uptime = server_uptime(UPTIME_GET), ret = 1;
447
448 if (prev_events != mmd->events)
449 goto out;
450 if (mmd->new_afs_status_flags != mmd->afs_status_flags)
451 goto out;
452 if (uptime / 60 != prev_uptime / 60)
453 goto out;
454 ret = 0;
455 out:
456 prev_uptime = uptime;
457 prev_events = mmd->events;
458 mmd->afs_status_flags = mmd->new_afs_status_flags;
459 if (ret) {
460 PARA_DEBUG_LOG("%d events, forcing status update, af = %d\n",
461 mmd->events, mmd->audio_format);
462 killpg(0, SIGUSR1);
463 }
464 }
465
466 /*
467 * MAIN
468 */
469 int main(int argc, char *argv[])
470 {
471 /* listen on sock_fd, new connection on new_fd */
472 int sockfd, new_fd;
473 struct sockaddr_in their_addr;
474 int err, i, max_fileno, ret;
475 pid_t chld_pid;
476 fd_set rfds, wfds;
477 struct timeval *timeout;
478
479 valid_fd_012();
480 sockfd = do_inits(argc, argv);
481 repeat:
482 /* check socket and signal pipe in any case */
483 FD_ZERO(&rfds);
484 FD_ZERO(&wfds);
485 FD_SET(sockfd, &rfds);
486 max_fileno = sockfd;
487 FD_SET(signal_pipe, &rfds);
488 max_fileno = MAX(max_fileno, signal_pipe);
489
490 timeout = afs_preselect();
491 status_refresh();
492 for (i = 0; senders[i].name; i++) {
493 if (senders[i].status != SENDER_ON)
494 continue;
495 if (!senders[i].pre_select)
496 continue;
497 senders[i].pre_select(mmd->audio_format >= 0?
498 &afl[mmd->audio_format] : NULL,
499 &max_fileno,
500 &rfds, &wfds);
501 }
502 mmd_unlock();
503 // PARA_DEBUG_LOG("%s: select (max = %i)\n", __func__, max_fileno);
504 ret = select(max_fileno + 1, &rfds, &wfds, NULL, timeout);
505 err = errno;
506 //PARA_DEBUG_LOG("%s: select returned %i\n", __func__, ret);
507 mmd_lock();
508 if (mmd->dbt_change >= 0)
509 handle_dbt_change();
510 if (ret < 0 && err == EINTR)
511 goto repeat;
512 if (ret < 0) {
513 PARA_CRIT_LOG("select error (%s)\n", strerror(err));
514 goto repeat;
515 }
516 for (i = 0; senders[i].name; i++) {
517 if (senders[i].status != SENDER_ON)
518 continue;
519 if (!senders[i].post_select)
520 continue;
521 senders[i].post_select(mmd->audio_format >= 0?
522 &afl[mmd->audio_format] : NULL,
523 &rfds, &wfds);
524 }
525 if (!ret) {
526 afs_send_chunk();
527 status_refresh();
528 }
529 if (FD_ISSET(signal_pipe, &rfds)) {
530 int sig;
531 sig = para_next_signal();
532 switch (sig) {
533 case SIGHUP:
534 handle_sighup();
535 break;
536 case SIGCHLD:
537 para_reap_children();
538 break;
539 /* die on sigint/sigterm. Kill all children too. */
540 case SIGINT:
541 case SIGTERM:
542 PARA_EMERG_LOG("terminating on signal %d\n", sig);
543 kill(0, SIGTERM);
544 exit(EXIT_FAILURE);
545 }
546 }
547 if (mmd->sender_cmd_data.cmd_num >= 0) {
548 int num = mmd->sender_cmd_data.cmd_num,
549 s = mmd->sender_cmd_data.sender_num;
550
551 if (senders[s].client_cmds[num])
552 senders[s].client_cmds[num](&mmd->sender_cmd_data);
553 mmd->sender_cmd_data.cmd_num = -1;
554 }
555 if (!FD_ISSET(sockfd, &rfds))
556 goto repeat;
557
558 new_fd = para_accept(sockfd, &their_addr, sizeof(struct sockaddr_in));
559 if (new_fd < 0)
560 goto repeat;
561 PARA_INFO_LOG("got connection from %s, forking\n",
562 inet_ntoa(their_addr.sin_addr));
563 mmd->num_connects++;
564 mmd->active_connections++;
565 random();
566 chld_pid = fork();
567 if (chld_pid < 0) {
568 PARA_CRIT_LOG("%s", "fork failed\n");
569 goto repeat;
570 }
571 if (chld_pid) {
572 close(new_fd);
573 /* parent keeps accepting connections */
574 goto repeat;
575 }
576 alarm(ALARM_TIMEOUT);
577 close_listed_fds();
578 close(sockfd); /* child doesn't need the listener */
579 /*
580 * put info on who we are serving into argv[0] to make
581 * client ip visible in top/ps
582 */
583 for (i = argc - 1; i >= 0; i--)
584 memset(argv[i], 0, strlen(argv[i]));
585 sprintf(argv[0], "para_server (serving %s)",
586 inet_ntoa(their_addr.sin_addr));
587 return handle_connect(new_fd, &their_addr);
588 }