460b9539 |
1 | /* |
2 | * This file is part of DisOrder. |
3 | * Copyright (C) 2004, 2005, 2006 Richard Kettlewell |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, but |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program; if not, write to the Free Software |
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
18 | * USA |
19 | */ |
20 | |
21 | #include <config.h> |
22 | |
23 | #include <stdio.h> |
24 | #include <getopt.h> |
25 | #include <pwd.h> |
26 | #include <grp.h> |
27 | #include <sys/types.h> |
28 | #include <errno.h> |
29 | #include <stdlib.h> |
30 | #include <string.h> |
31 | #include <unistd.h> |
32 | #include <signal.h> |
33 | #include <sys/socket.h> |
34 | #include <time.h> |
35 | #include <locale.h> |
36 | #include <syslog.h> |
37 | #include <sys/time.h> |
38 | #include <pcre.h> |
39 | #include <fcntl.h> |
40 | |
41 | #include "daemonize.h" |
42 | #include "event.h" |
43 | #include "log.h" |
44 | #include "configuration.h" |
45 | #include "trackdb.h" |
46 | #include "queue.h" |
47 | #include "mem.h" |
48 | #include "play.h" |
49 | #include "server.h" |
50 | #include "state.h" |
51 | #include "syscalls.h" |
52 | #include "defs.h" |
53 | #include "user.h" |
54 | #include "mixer.h" |
55 | #include "eventlog.h" |
56 | |
57 | static ev_source *ev; |
58 | |
59 | static void rescan_after(long offset); |
60 | static void dbgc_after(long offset); |
61 | static void volumecheck_after(long offset); |
62 | |
63 | static const struct option options[] = { |
64 | { "help", no_argument, 0, 'h' }, |
65 | { "version", no_argument, 0, 'V' }, |
66 | { "config", required_argument, 0, 'c' }, |
67 | { "debug", no_argument, 0, 'd' }, |
68 | { "foreground", no_argument, 0, 'f' }, |
69 | { "log", required_argument, 0, 'l' }, |
70 | { "pidfile", required_argument, 0, 'P' }, |
71 | { "no-initial-rescan", no_argument, 0, 'N' }, |
72 | { 0, 0, 0, 0 } |
73 | }; |
74 | |
75 | /* display usage message and terminate */ |
76 | static void help(void) { |
77 | xprintf("Usage:\n" |
78 | " disorderd [OPTIONS]\n" |
79 | "Options:\n" |
80 | " --help, -h Display usage message\n" |
81 | " --version, -V Display version number\n" |
82 | " --config PATH, -c PATH Set configuration file\n" |
83 | " --debug, -d Turn on debugging\n" |
84 | " --foreground, -f Do not become a daemon\n" |
85 | " --pidfile PATH, -P PATH Leave a pidfile\n"); |
86 | xfclose(stdout); |
87 | exit(0); |
88 | } |
89 | |
90 | /* display version number and terminate */ |
91 | static void version(void) { |
92 | xprintf("disorderd version %s\n", disorder_version_string); |
93 | xfclose(stdout); |
94 | exit(0); |
95 | } |
96 | |
97 | /* SIGHUP callback */ |
98 | static int handle_sighup(ev_source attribute((unused)) *ev_, |
99 | int attribute((unused)) sig, |
100 | void attribute((unused)) *u) { |
101 | info("received SIGHUP"); |
102 | reconfigure(ev, 1); |
103 | return 0; |
104 | } |
105 | |
106 | /* fatal signals */ |
107 | |
108 | static int handle_sigint(ev_source attribute((unused)) *ev_, |
109 | int attribute((unused)) sig, |
110 | void attribute((unused)) *u) { |
111 | info("received SIGINT"); |
112 | quit(ev); |
113 | } |
114 | |
115 | static int handle_sigterm(ev_source attribute((unused)) *ev_, |
116 | int attribute((unused)) sig, |
117 | void attribute((unused)) *u) { |
118 | info("received SIGTERM"); |
119 | quit(ev); |
120 | } |
121 | |
122 | static int rescan_again(ev_source *ev_, |
123 | const struct timeval attribute((unused)) *now, |
124 | void attribute((unused)) *u) { |
125 | trackdb_rescan(ev_); |
126 | rescan_after(86400); |
127 | return 0; |
128 | } |
129 | |
130 | static void rescan_after(long offset) { |
131 | struct timeval w; |
132 | |
133 | gettimeofday(&w, 0); |
134 | w.tv_sec += offset; |
135 | ev_timeout(ev, 0, &w, rescan_again, 0); |
136 | } |
137 | |
138 | static int dbgc_again(ev_source attribute((unused)) *ev_, |
139 | const struct timeval attribute((unused)) *now, |
140 | void attribute((unused)) *u) { |
141 | trackdb_gc(); |
142 | dbgc_after(60); |
143 | return 0; |
144 | } |
145 | |
146 | static void dbgc_after(long offset) { |
147 | struct timeval w; |
148 | |
149 | gettimeofday(&w, 0); |
150 | w.tv_sec += offset; |
151 | ev_timeout(ev, 0, &w, dbgc_again, 0); |
152 | } |
153 | |
154 | static int volumecheck_again(ev_source attribute((unused)) *ev_, |
155 | const struct timeval attribute((unused)) *now, |
156 | void attribute((unused)) *u) { |
157 | int l, r; |
158 | char lb[32], rb[32]; |
159 | |
160 | if(!mixer_control(&l, &r, 0)) { |
161 | if(l != volume_left || r != volume_right) { |
162 | volume_left = l; |
163 | volume_right = r; |
164 | snprintf(lb, sizeof lb, "%d", l); |
165 | snprintf(rb, sizeof rb, "%d", r); |
166 | eventlog("volume", lb, rb, (char *)0); |
167 | } |
168 | } |
169 | volumecheck_after(60); |
170 | return 0; |
171 | } |
172 | |
173 | static void volumecheck_after(long offset) { |
174 | struct timeval w; |
175 | |
176 | gettimeofday(&w, 0); |
177 | w.tv_sec += offset; |
178 | ev_timeout(ev, 0, &w, volumecheck_again, 0); |
179 | } |
180 | |
181 | int main(int argc, char **argv) { |
182 | int n, background = 1; |
183 | const char *pidfile = 0; |
184 | int initial_rescan = 1; |
185 | |
186 | set_progname(argv); |
187 | mem_init(1); |
188 | if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale"); |
189 | /* garbage-collect PCRE's memory */ |
190 | pcre_malloc = xmalloc; |
191 | pcre_free = xfree; |
192 | while((n = getopt_long(argc, argv, "hVc:dfP:N", options, 0)) >= 0) { |
193 | switch(n) { |
194 | case 'h': help(); |
195 | case 'V': version(); |
196 | case 'c': configfile = optarg; break; |
197 | case 'd': debugging = 1; break; |
198 | case 'f': background = 0; break; |
199 | case 'P': pidfile = optarg; break; |
200 | case 'N': initial_rescan = 0; break; |
201 | default: fatal(0, "invalid option"); |
202 | } |
203 | } |
204 | /* go into background if necessary */ |
205 | if(background) |
206 | daemonize(progname, LOG_DAEMON, pidfile); |
207 | info("process ID %lu", (unsigned long)getpid()); |
208 | srand(time(0)); /* don't start the same every time */ |
209 | /* create event loop */ |
210 | ev = ev_new(); |
211 | if(ev_child_setup(ev)) fatal(0, "ev_child_setup failed"); |
212 | /* read config */ |
213 | if(config_read()) |
214 | fatal(0, "cannot read configuration"); |
215 | /* Start the speaker process (as root! - so it can choose its nice value) */ |
216 | speaker_setup(ev); |
217 | /* set server nice value _after_ starting the speaker, so that they |
218 | * are independently niceable */ |
219 | xnice(config->nice_server); |
220 | /* change user */ |
221 | become_mortal(); |
222 | /* make sure we're not root, whatever the config says */ |
223 | if(getuid() == 0 || geteuid() == 0) fatal(0, "do not run as root"); |
224 | /* open a lockfile - we only want one copy of the server to run at once. */ |
225 | if(config->lock) { |
226 | const char *lockfile; |
227 | int lockfd; |
228 | struct flock lock; |
229 | |
230 | lockfile = config_get_file("lock"); |
231 | if((lockfd = open(lockfile, O_RDWR|O_CREAT, 0600)) < 0) |
232 | fatal(errno, "error opening %s", lockfile); |
233 | cloexec(lockfd); |
234 | memset(&lock, 0, sizeof lock); |
235 | lock.l_type = F_WRLCK; |
236 | lock.l_whence = SEEK_SET; |
237 | if(fcntl(lockfd, F_SETLK, &lock) < 0) |
238 | fatal(errno, "error locking %s", lockfile); |
239 | } |
240 | /* initialize database environment */ |
241 | trackdb_init(TRACKDB_NORMAL_RECOVER); |
242 | trackdb_master(ev); |
243 | /* install new config */ |
244 | reconfigure(ev, 0); |
245 | /* re-read config if we receive a SIGHUP */ |
246 | if(ev_signal(ev, SIGHUP, handle_sighup, 0)) fatal(0, "ev_signal failed"); |
247 | /* exit on SIGINT/SIGTERM */ |
248 | if(ev_signal(ev, SIGINT, handle_sigint, 0)) fatal(0, "ev_signal failed"); |
249 | if(ev_signal(ev, SIGTERM, handle_sigterm, 0)) fatal(0, "ev_signal failed"); |
250 | /* ignore SIGPIPE */ |
251 | signal(SIGPIPE, SIG_IGN); |
252 | /* start a rescan straight away */ |
253 | if(initial_rescan) |
254 | trackdb_rescan(ev); |
255 | rescan_after(86400); |
256 | /* periodically tidy up the database */ |
257 | dbgc_after(60); |
258 | /* periodically check the volume */ |
259 | volumecheck_after(60); |
260 | /* set initial state */ |
261 | add_random_track(); |
262 | play(ev); |
263 | /* enter the event loop */ |
264 | n = ev_run(ev); |
265 | /* if we exit the event loop, something must have gone wrong */ |
266 | fatal(errno, "ev_run returned %d", n); |
267 | } |
268 | |
269 | /* |
270 | Local Variables: |
271 | c-basic-offset:2 |
272 | comment-column:40 |
273 | End: |
274 | */ |