]> git.tuebingen.mpg.de Git - paraslash.git/blob - server.c
the new plm database tool
[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 }