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 | #include "types.h" |
23 | |
24 | #include <stdio.h> |
25 | #include <string.h> |
26 | #include <stdlib.h> |
27 | #include <errno.h> |
28 | #include <sys/types.h> |
29 | #include <sys/stat.h> |
30 | #include <unistd.h> |
31 | #include <ctype.h> |
32 | #include <stddef.h> |
33 | #include <pwd.h> |
34 | #include <langinfo.h> |
35 | #include <pcre.h> |
36 | #include <signal.h> |
37 | |
38 | #include "configuration.h" |
39 | #include "mem.h" |
40 | #include "log.h" |
41 | #include "split.h" |
42 | #include "syscalls.h" |
43 | #include "table.h" |
44 | #include "inputline.h" |
45 | #include "charset.h" |
46 | #include "defs.h" |
47 | #include "mixer.h" |
48 | #include "printf.h" |
49 | #include "regsub.h" |
50 | #include "signame.h" |
51 | |
52 | char *configfile; |
53 | |
54 | struct config_state { |
55 | const char *path; |
56 | int line; |
57 | struct config *config; |
58 | }; |
59 | |
60 | struct config *config; |
61 | |
62 | struct conf { |
63 | const char *name; |
64 | size_t offset; |
65 | const struct conftype *type; |
66 | int (*validate)(const struct config_state *cs, |
67 | int nvec, char **vec); |
68 | }; |
69 | |
70 | struct conftype { |
71 | int (*set)(const struct config_state *cs, |
72 | const struct conf *whoami, |
73 | int nvec, char **vec); |
74 | void (*free)(struct config *c, const struct conf *whoami); |
75 | }; |
76 | |
77 | #define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset)) |
78 | #define VALUE(C, TYPE) (*ADDRESS(C, TYPE)) |
79 | |
80 | static int set_signal(const struct config_state *cs, |
81 | const struct conf *whoami, |
82 | int nvec, char **vec) { |
83 | int n; |
84 | |
85 | if(nvec != 1) { |
86 | error(0, "%s:%d: '%s' requires one argument", |
87 | cs->path, cs->line, whoami->name); |
88 | return -1; |
89 | } |
90 | if((n = find_signal(vec[0])) == -1) { |
91 | error(0, "%s:%d: unknown signal '%s'", |
92 | cs->path, cs->line, vec[0]); |
93 | return -1; |
94 | } |
95 | VALUE(cs->config, int) = n; |
96 | return 0; |
97 | } |
98 | |
99 | static int set_collections(const struct config_state *cs, |
100 | const struct conf *whoami, |
101 | int nvec, char **vec) { |
102 | struct collectionlist *cl; |
103 | |
104 | if(nvec != 3) { |
105 | error(0, "%s:%d: '%s' requires three arguments", |
106 | cs->path, cs->line, whoami->name); |
107 | return -1; |
108 | } |
109 | if(vec[2][0] != '/') { |
110 | error(0, "%s:%d: collection root must start with '/'", |
111 | cs->path, cs->line); |
112 | return -1; |
113 | } |
114 | if(vec[2][1] && vec[2][strlen(vec[2])-1] == '/') { |
115 | error(0, "%s:%d: collection root must not end with '/'", |
116 | cs->path, cs->line); |
117 | return -1; |
118 | } |
119 | cl = ADDRESS(cs->config, struct collectionlist); |
120 | ++cl->n; |
121 | cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection)); |
122 | cl->s[cl->n - 1].module = xstrdup(vec[0]); |
123 | cl->s[cl->n - 1].encoding = xstrdup(vec[1]); |
124 | cl->s[cl->n - 1].root = xstrdup(vec[2]); |
125 | return 0; |
126 | } |
127 | |
128 | static int set_boolean(const struct config_state *cs, |
129 | const struct conf *whoami, |
130 | int nvec, char **vec) { |
131 | int state; |
132 | |
133 | if(nvec != 1) { |
134 | error(0, "%s:%d: '%s' takes only one argument", |
135 | cs->path, cs->line, whoami->name); |
136 | return -1; |
137 | } |
138 | if(!strcmp(vec[0], "yes")) state = 1; |
139 | else if(!strcmp(vec[0], "no")) state = 0; |
140 | else { |
141 | error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'", |
142 | cs->path, cs->line, whoami->name); |
143 | return -1; |
144 | } |
145 | VALUE(cs->config, int) = state; |
146 | return 0; |
147 | } |
148 | |
149 | static int set_string(const struct config_state *cs, |
150 | const struct conf *whoami, |
151 | int nvec, char **vec) { |
152 | if(nvec != 1) { |
153 | error(0, "%s:%d: '%s' takes only one argument", |
154 | cs->path, cs->line, whoami->name); |
155 | return -1; |
156 | } |
157 | VALUE(cs->config, char *) = xstrdup(vec[0]); |
158 | return 0; |
159 | } |
160 | |
161 | static int set_stringlist(const struct config_state *cs, |
162 | const struct conf *whoami, |
163 | int nvec, char **vec) { |
164 | int n; |
165 | struct stringlist *sl; |
166 | |
167 | sl = ADDRESS(cs->config, struct stringlist); |
168 | sl->n = 0; |
169 | for(n = 0; n < nvec; ++n) { |
170 | sl->n++; |
171 | sl->s = xrealloc(sl->s, (sl->n * sizeof (char *))); |
172 | sl->s[sl->n - 1] = xstrdup(vec[n]); |
173 | } |
174 | return 0; |
175 | } |
176 | |
177 | static int set_integer(const struct config_state *cs, |
178 | const struct conf *whoami, |
179 | int nvec, char **vec) { |
180 | char *e; |
181 | |
182 | if(nvec != 1) { |
183 | error(0, "%s:%d: '%s' takes only one argument", |
184 | cs->path, cs->line, whoami->name); |
185 | return -1; |
186 | } |
187 | if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) { |
188 | error(errno, "%s:%d: converting integer", cs->path, cs->line); |
189 | return -1; |
190 | } |
191 | if(*e) { |
192 | error(0, "%s:%d: invalid integer syntax", cs->path, cs->line); |
193 | return -1; |
194 | } |
195 | return 0; |
196 | } |
197 | |
198 | static int set_stringlist_accum(const struct config_state *cs, |
199 | const struct conf *whoami, |
200 | int nvec, char **vec) { |
201 | int n; |
202 | struct stringlist *s; |
203 | struct stringlistlist *sll; |
204 | |
205 | sll = ADDRESS(cs->config, struct stringlistlist); |
206 | sll->n++; |
207 | sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist))); |
208 | s = &sll->s[sll->n - 1]; |
209 | s->n = nvec; |
210 | s->s = xmalloc((nvec + 1) * sizeof (char *)); |
211 | for(n = 0; n < nvec; ++n) |
212 | s->s[n] = xstrdup(vec[n]); |
213 | return 0; |
214 | } |
215 | |
216 | static int set_string_accum(const struct config_state *cs, |
217 | const struct conf *whoami, |
218 | int nvec, char **vec) { |
219 | int n; |
220 | struct stringlist *sl; |
221 | |
222 | sl = ADDRESS(cs->config, struct stringlist); |
223 | for(n = 0; n < nvec; ++n) { |
224 | sl->n++; |
225 | sl->s = xrealloc(sl->s, (sl->n * sizeof (char *))); |
226 | sl->s[sl->n - 1] = xstrdup(vec[n]); |
227 | } |
228 | return 0; |
229 | } |
230 | |
231 | static int set_restrict(const struct config_state *cs, |
232 | const struct conf *whoami, |
233 | int nvec, char **vec) { |
234 | unsigned r = 0; |
235 | int n, i; |
236 | |
237 | static const struct restriction { |
238 | const char *name; |
239 | unsigned bit; |
240 | } restrictions[] = { |
241 | { "remove", RESTRICT_REMOVE }, |
242 | { "scratch", RESTRICT_SCRATCH }, |
243 | { "move", RESTRICT_MOVE }, |
244 | }; |
245 | |
246 | for(n = 0; n < nvec; ++n) { |
247 | if((i = TABLE_FIND(restrictions, struct restriction, name, vec[n])) < 0) { |
248 | error(0, "%s:%d: invalid restriction '%s'", |
249 | cs->path, cs->line, vec[n]); |
250 | return -1; |
251 | } |
252 | r |= restrictions[i].bit; |
253 | } |
254 | VALUE(cs->config, unsigned) = r; |
255 | return 0; |
256 | } |
257 | |
9d5da576 |
258 | static int parse_sample_format(const struct config_state *cs, |
259 | ao_sample_format *ao, |
260 | int nvec, char **vec) { |
261 | char *p = vec[0]; |
262 | long t; |
263 | |
264 | if (nvec != 1) { |
265 | error(0, "%s:%d: wrong number of arguments", cs->path, cs->line); |
266 | return -1; |
267 | } |
268 | if (xstrtol(&t, p, &p, 0)) { |
269 | error(errno, "%s:%d: converting bits-per-sample", cs->path, cs->line); |
270 | return -1; |
271 | } |
272 | if (t != 8 && t != 16) { |
273 | error(0, "%s:%d: bad bite-per-sample (%ld)", cs->path, cs->line, t); |
274 | return -1; |
275 | } |
276 | if (ao) ao->bits = t; |
277 | switch (*p) { |
278 | case 'l': case 'L': t = AO_FMT_LITTLE; p++; break; |
279 | case 'b': case 'B': t = AO_FMT_BIG; p++; break; |
280 | default: t = AO_FMT_NATIVE; break; |
281 | } |
282 | if (ao) ao->byte_format = t; |
283 | if (*p != '/') { |
284 | error(errno, "%s:%d: expected `/' after bits-per-sample", |
285 | cs->path, cs->line); |
286 | return -1; |
287 | } |
288 | p++; |
289 | if (xstrtol(&t, p, &p, 0)) { |
290 | error(errno, "%s:%d: converting sample-rate", cs->path, cs->line); |
291 | return -1; |
292 | } |
293 | if (t < 1 || t > INT_MAX) { |
294 | error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t); |
295 | return -1; |
296 | } |
297 | if (ao) ao->rate = t; |
298 | if (*p != '/') { |
299 | error(0, "%s:%d: expected `/' after sample-rate", |
300 | cs->path, cs->line); |
301 | return -1; |
302 | } |
303 | p++; |
304 | if (xstrtol(&t, p, &p, 0)) { |
305 | error(errno, "%s:%d: converting channels", cs->path, cs->line); |
306 | return -1; |
307 | } |
308 | if (t < 1 || t > 8) { |
309 | error(0, "%s:%d: silly number (%ld) of channels", cs->path, cs->line, t); |
310 | return -1; |
311 | } |
312 | if (ao) ao->channels = t; |
313 | if (*p) { |
314 | error(0, "%s:%d: junk after channels", cs->path, cs->line); |
315 | return -1; |
316 | } |
317 | return 0; |
318 | } |
319 | |
320 | static int set_sample_format(const struct config_state *cs, |
321 | const struct conf *whoami, |
322 | int nvec, char **vec) { |
323 | return parse_sample_format(cs, ADDRESS(cs->config, ao_sample_format), |
324 | nvec, vec); |
325 | } |
326 | |
460b9539 |
327 | static int set_namepart(const struct config_state *cs, |
328 | const struct conf *whoami, |
329 | int nvec, char **vec) { |
330 | struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist); |
331 | unsigned reflags; |
332 | const char *errstr; |
333 | int erroffset, n; |
334 | pcre *re; |
335 | |
336 | if(nvec < 3) { |
337 | error(0, "%s:%d: namepart needs at least 3 arguments", cs->path, cs->line); |
338 | return -1; |
339 | } |
340 | if(nvec > 5) { |
341 | error(0, "%s:%d: namepart needs at most 5 arguments", cs->path, cs->line); |
342 | return -1; |
343 | } |
344 | reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0; |
345 | if(!(re = pcre_compile(vec[1], |
346 | PCRE_UTF8 |
347 | |regsub_compile_options(reflags), |
348 | &errstr, &erroffset, 0))) { |
349 | error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)", |
350 | cs->path, cs->line, vec[1], errstr, erroffset); |
351 | return -1; |
352 | } |
353 | npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart)); |
354 | npl->s[npl->n].part = xstrdup(vec[0]); |
355 | npl->s[npl->n].re = re; |
356 | npl->s[npl->n].replace = xstrdup(vec[2]); |
357 | npl->s[npl->n].context = xstrdup(vec[3]); |
358 | npl->s[npl->n].reflags = reflags; |
359 | ++npl->n; |
360 | /* XXX a bit of a bodge; relies on there being very few parts. */ |
361 | for(n = 0; (n < cs->config->nparts |
362 | && strcmp(cs->config->parts[n], vec[0])); ++n) |
363 | ; |
364 | if(n >= cs->config->nparts) { |
365 | cs->config->parts = xrealloc(cs->config->parts, |
366 | (cs->config->nparts + 1) * sizeof (char *)); |
367 | cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]); |
368 | } |
369 | return 0; |
370 | } |
371 | |
372 | static int set_transform(const struct config_state *cs, |
373 | const struct conf *whoami, |
374 | int nvec, char **vec) { |
375 | struct transformlist *tl = ADDRESS(cs->config, struct transformlist); |
376 | pcre *re; |
377 | unsigned reflags; |
378 | const char *errstr; |
379 | int erroffset; |
380 | |
381 | if(nvec < 3) { |
382 | error(0, "%s:%d: transform needs at least 3 arguments", cs->path, cs->line); |
383 | return -1; |
384 | } |
385 | if(nvec > 5) { |
386 | error(0, "%s:%d: transform needs at most 5 arguments", cs->path, cs->line); |
387 | return -1; |
388 | } |
389 | reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0); |
390 | if(!(re = pcre_compile(vec[1], |
391 | PCRE_UTF8 |
392 | |regsub_compile_options(reflags), |
393 | &errstr, &erroffset, 0))) { |
394 | error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)", |
395 | cs->path, cs->line, vec[1], errstr, erroffset); |
396 | return -1; |
397 | } |
398 | tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart)); |
399 | tl->t[tl->n].type = xstrdup(vec[0]); |
400 | tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*"); |
401 | tl->t[tl->n].re = re; |
402 | tl->t[tl->n].replace = xstrdup(vec[2]); |
403 | tl->t[tl->n].flags = reflags; |
404 | ++tl->n; |
405 | return 0; |
406 | } |
407 | |
408 | /* free functions */ |
409 | |
410 | static void free_none(struct config attribute((unused)) *c, |
411 | const struct conf attribute((unused)) *whoami) { |
412 | } |
413 | |
414 | static void free_string(struct config *c, |
415 | const struct conf *whoami) { |
416 | xfree(VALUE(c, char *)); |
417 | } |
418 | |
419 | static void free_stringlist(struct config *c, |
420 | const struct conf *whoami) { |
421 | int n; |
422 | struct stringlist *sl = ADDRESS(c, struct stringlist); |
423 | |
424 | for(n = 0; n < sl->n; ++n) |
425 | xfree(sl->s[n]); |
426 | xfree(sl->s); |
427 | } |
428 | |
429 | static void free_stringlistlist(struct config *c, |
430 | const struct conf *whoami) { |
431 | int n, m; |
432 | struct stringlistlist *sll = ADDRESS(c, struct stringlistlist); |
433 | struct stringlist *sl; |
434 | |
435 | for(n = 0; n < sll->n; ++n) { |
436 | sl = &sll->s[n]; |
437 | for(m = 0; m < sl->n; ++m) |
438 | xfree(sl->s[m]); |
439 | xfree(sl->s); |
440 | } |
441 | xfree(sll->s); |
442 | } |
443 | |
444 | static void free_collectionlist(struct config *c, |
445 | const struct conf *whoami) { |
446 | struct collectionlist *cll = ADDRESS(c, struct collectionlist); |
447 | struct collection *cl; |
448 | int n; |
449 | |
450 | for(n = 0; n < cll->n; ++n) { |
451 | cl = &cll->s[n]; |
452 | xfree(cl->module); |
453 | xfree(cl->encoding); |
454 | xfree(cl->root); |
455 | } |
456 | xfree(cll->s); |
457 | } |
458 | |
459 | static void free_namepartlist(struct config *c, |
460 | const struct conf *whoami) { |
461 | struct namepartlist *npl = ADDRESS(c, struct namepartlist); |
462 | struct namepart *np; |
463 | int n; |
464 | |
465 | for(n = 0; n < npl->n; ++n) { |
466 | np = &npl->s[n]; |
467 | xfree(np->part); |
468 | pcre_free(np->re); /* ...whatever pcre_free is set to. */ |
469 | xfree(np->replace); |
470 | xfree(np->context); |
471 | } |
472 | xfree(npl->s); |
473 | } |
474 | |
475 | static void free_transformlist(struct config *c, |
476 | const struct conf *whoami) { |
477 | struct transformlist *tl = ADDRESS(c, struct transformlist); |
478 | struct transform *t; |
479 | int n; |
480 | |
481 | for(n = 0; n < tl->n; ++n) { |
482 | t = &tl->t[n]; |
483 | xfree(t->type); |
484 | pcre_free(t->re); /* ...whatever pcre_free is set to. */ |
485 | xfree(t->replace); |
486 | xfree(t->context); |
487 | } |
488 | xfree(tl->t); |
489 | } |
490 | |
491 | /* configuration types */ |
492 | |
493 | static const struct conftype |
494 | type_signal = { set_signal, free_none }, |
495 | type_collections = { set_collections, free_collectionlist }, |
496 | type_boolean = { set_boolean, free_none }, |
497 | type_string = { set_string, free_string }, |
498 | type_stringlist = { set_stringlist, free_stringlist }, |
499 | type_integer = { set_integer, free_none }, |
500 | type_stringlist_accum = { set_stringlist_accum, free_stringlistlist }, |
501 | type_string_accum = { set_string_accum, free_stringlist }, |
9d5da576 |
502 | type_sample_format = { set_sample_format, free_none }, |
460b9539 |
503 | type_restrict = { set_restrict, free_none }, |
504 | type_namepart = { set_namepart, free_namepartlist }, |
505 | type_transform = { set_transform, free_transformlist }; |
506 | |
507 | /* specific validation routine */ |
508 | |
509 | #define VALIDATE_FILE(test, what) do { \ |
510 | struct stat sb; \ |
511 | int n; \ |
512 | \ |
513 | for(n = 0; n < nvec; ++n) { \ |
514 | if(stat(vec[n], &sb) < 0) { \ |
515 | error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]); \ |
516 | return -1; \ |
517 | } \ |
518 | if(!test(sb.st_mode)) { \ |
519 | error(0, "%s:%d: %s is not a %s", \ |
520 | cs->path, cs->line, vec[n], what); \ |
521 | return -1; \ |
522 | } \ |
523 | } \ |
524 | } while(0) |
525 | |
526 | static int validate_isdir(const struct config_state *cs, |
527 | int nvec, char **vec) { |
528 | VALIDATE_FILE(S_ISDIR, "directory"); |
529 | return 0; |
530 | } |
531 | |
532 | static int validate_isreg(const struct config_state *cs, |
533 | int nvec, char **vec) { |
534 | VALIDATE_FILE(S_ISREG, "regular file"); |
535 | return 0; |
536 | } |
537 | |
538 | static int validate_ischr(const struct config_state *cs, |
539 | int nvec, char **vec) { |
540 | VALIDATE_FILE(S_ISCHR, "character device"); |
541 | return 0; |
542 | } |
543 | |
544 | static int validate_player(const struct config_state *cs, |
545 | int nvec, |
546 | char attribute((unused)) **vec) { |
547 | if(nvec < 2) { |
548 | error(0, "%s:%d: should be at least 'player PATTERN MODULE'", |
549 | cs->path, cs->line); |
550 | return -1; |
551 | } |
552 | return 0; |
553 | } |
554 | |
555 | static int validate_allow(const struct config_state *cs, |
556 | int nvec, |
557 | char attribute((unused)) **vec) { |
558 | if(nvec != 2) { |
559 | error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line); |
560 | return -1; |
561 | } |
562 | return 0; |
563 | } |
564 | |
565 | static int validate_non_negative(const struct config_state *cs, |
566 | int nvec, char **vec) { |
567 | long n; |
568 | |
569 | if(nvec < 1) { |
570 | error(0, "%s:%d: missing argument", cs->path, cs->line); |
571 | return -1; |
572 | } |
573 | if(nvec > 1) { |
574 | error(0, "%s:%d: too many arguments", cs->path, cs->line); |
575 | return -1; |
576 | } |
577 | if(xstrtol(&n, vec[0], 0, 0)) { |
578 | error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno)); |
579 | return -1; |
580 | } |
581 | if(n < 0) { |
582 | error(0, "%s:%d: must not be negative", cs->path, cs->line); |
583 | return -1; |
584 | } |
585 | return 0; |
586 | } |
587 | |
588 | static int validate_positive(const struct config_state *cs, |
589 | int nvec, char **vec) { |
590 | long n; |
591 | |
592 | if(nvec < 1) { |
593 | error(0, "%s:%d: missing argument", cs->path, cs->line); |
594 | return -1; |
595 | } |
596 | if(nvec > 1) { |
597 | error(0, "%s:%d: too many arguments", cs->path, cs->line); |
598 | return -1; |
599 | } |
600 | if(xstrtol(&n, vec[0], 0, 0)) { |
601 | error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno)); |
602 | return -1; |
603 | } |
604 | if(n <= 0) { |
605 | error(0, "%s:%d: must be positive", cs->path, cs->line); |
606 | return -1; |
607 | } |
608 | return 0; |
609 | } |
610 | |
611 | static int validate_isauser(const struct config_state *cs, |
612 | int attribute((unused)) nvec, |
613 | char **vec) { |
614 | struct passwd *pw; |
615 | |
616 | if(!(pw = getpwnam(vec[0]))) { |
617 | error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]); |
618 | return -1; |
619 | } |
620 | return 0; |
621 | } |
622 | |
9d5da576 |
623 | static int validate_sample_format(const struct config_state *cs, |
624 | int attribute((unused)) nvec, |
625 | char **vec) { |
626 | return parse_sample_format(cs, 0, nvec, vec); |
627 | } |
628 | |
460b9539 |
629 | static int validate_channel(const struct config_state *cs, |
630 | int attribute((unused)) nvec, |
631 | char **vec) { |
632 | if(mixer_channel(vec[0]) == -1) { |
633 | error(0, "%s:%d: invalid channel '%s'", cs->path, cs->line, vec[0]); |
634 | return -1; |
635 | } |
636 | return 0; |
637 | } |
638 | |
639 | static int validate_any(const struct config_state attribute((unused)) *cs, |
640 | int attribute((unused)) nvec, |
641 | char attribute((unused)) **vec) { |
642 | return 0; |
643 | } |
644 | |
645 | static int validate_url(const struct config_state attribute((unused)) *cs, |
646 | int attribute((unused)) nvec, |
647 | char **vec) { |
648 | const char *s; |
649 | int n; |
650 | /* absoluteURI = scheme ":" ( hier_part | opaque_part ) |
651 | scheme = alpha *( alpha | digit | "+" | "-" | "." ) */ |
652 | s = vec[0]; |
653 | n = strspn(s, ("abcdefghijklmnopqrstuvwxyz" |
654 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
655 | "0123456789")); |
656 | if(s[n] != ':') { |
657 | error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]); |
658 | return -1; |
659 | } |
660 | if(!strncmp(s, "http:", 5) |
661 | || !strncmp(s, "https:", 6)) { |
662 | s += n + 1; |
663 | /* we only do a rather cursory check */ |
664 | if(strncmp(s, "//", 2)) { |
665 | error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]); |
666 | return -1; |
667 | } |
668 | } |
669 | return 0; |
670 | } |
671 | |
672 | static int validate_alias(const struct config_state *cs, |
673 | int nvec, |
674 | char **vec) { |
675 | const char *s; |
676 | int in_brackets = 0, c; |
677 | |
678 | if(nvec < 1) { |
679 | error(0, "%s:%d: missing argument", cs->path, cs->line); |
680 | return -1; |
681 | } |
682 | if(nvec > 1) { |
683 | error(0, "%s:%d: too many arguments", cs->path, cs->line); |
684 | return -1; |
685 | } |
686 | s = vec[0]; |
687 | while((c = (unsigned char)*s++)) { |
688 | if(in_brackets) { |
689 | if(c == '}') |
690 | in_brackets = 0; |
691 | else if(!isalnum(c)) { |
692 | error(0, "%s:%d: invalid part name in alias expansion in '%s'", |
693 | cs->path, cs->line, vec[0]); |
694 | return -1; |
695 | } |
696 | } else { |
697 | if(c == '{') { |
698 | in_brackets = 1; |
699 | if(*s == '/') |
700 | ++s; |
701 | } else if(c == '\\') { |
702 | if(!(c = (unsigned char)*s++)) { |
703 | error(0, "%s:%d: unterminated escape in alias expansion in '%s'", |
704 | cs->path, cs->line, vec[0]); |
705 | return -1; |
706 | } else if(c != '\\' && c != '{') { |
707 | error(0, "%s:%d: invalid escape in alias expansion in '%s'", |
708 | cs->path, cs->line, vec[0]); |
709 | return -1; |
710 | } |
711 | } |
712 | } |
713 | ++s; |
714 | } |
715 | if(in_brackets) { |
716 | error(0, "%s:%d: unterminated part name in alias expansion in '%s'", |
717 | cs->path, cs->line, vec[0]); |
718 | return -1; |
719 | } |
720 | return 0; |
721 | } |
722 | |
723 | /* configuration table */ |
724 | |
725 | #define C(x) #x, offsetof(struct config, x) |
726 | #define C2(x,y) #x, offsetof(struct config, y) |
727 | |
728 | static const struct conf conf[] = { |
729 | { C(alias), &type_string, validate_alias }, |
730 | { C(allow), &type_stringlist_accum, validate_allow }, |
731 | { C(channel), &type_string, validate_channel }, |
732 | { C(checkpoint_kbyte), &type_integer, validate_non_negative }, |
733 | { C(checkpoint_min), &type_integer, validate_non_negative }, |
734 | { C(collection), &type_collections, validate_any }, |
735 | { C(connect), &type_stringlist, validate_any }, |
736 | { C(device), &type_string, validate_any }, |
737 | { C(gap), &type_integer, validate_non_negative }, |
738 | { C(history), &type_integer, validate_positive }, |
739 | { C(home), &type_string, validate_isdir }, |
740 | { C(listen), &type_stringlist, validate_any }, |
741 | { C(lock), &type_boolean, validate_any }, |
742 | { C(mixer), &type_string, validate_ischr }, |
743 | { C(namepart), &type_namepart, validate_any }, |
744 | { C2(nice, nice_rescan), &type_integer, validate_non_negative }, |
745 | { C(nice_rescan), &type_integer, validate_non_negative }, |
746 | { C(nice_server), &type_integer, validate_any }, |
747 | { C(nice_speaker), &type_integer, validate_any }, |
748 | { C(password), &type_string, validate_any }, |
749 | { C(player), &type_stringlist_accum, validate_player }, |
750 | { C(plugins), &type_string_accum, validate_isdir }, |
751 | { C(prefsync), &type_integer, validate_positive }, |
459d4402 |
752 | { C(queue_pad), &type_integer, validate_positive }, |
460b9539 |
753 | { C(refresh), &type_integer, validate_positive }, |
754 | { C2(restrict, restrictions), &type_restrict, validate_any }, |
9d5da576 |
755 | { C(sample_format), &type_sample_format, validate_sample_format }, |
460b9539 |
756 | { C(scratch), &type_string_accum, validate_isreg }, |
757 | { C(signal), &type_signal, validate_any }, |
5330d674 |
758 | { C(sox_generation), &type_integer, validate_non_negative }, |
9d5da576 |
759 | { C(speaker_command), &type_string, validate_any }, |
460b9539 |
760 | { C(stopword), &type_string_accum, validate_any }, |
761 | { C(templates), &type_string_accum, validate_isdir }, |
762 | { C(transform), &type_transform, validate_any }, |
763 | { C(trust), &type_string_accum, validate_any }, |
764 | { C(url), &type_string, validate_url }, |
765 | { C(user), &type_string, validate_isauser }, |
766 | { C(username), &type_string, validate_any }, |
767 | }; |
768 | |
769 | /* find a configuration item's definition by key */ |
770 | static const struct conf *find(const char *key) { |
771 | int n; |
772 | |
773 | if((n = TABLE_FIND(conf, struct conf, name, key)) < 0) |
774 | return 0; |
775 | return &conf[n]; |
776 | } |
777 | |
778 | /* set a new configuration value */ |
779 | static int config_set(const struct config_state *cs, |
780 | int nvec, char **vec) { |
781 | const struct conf *which; |
782 | |
783 | D(("config_set %s", vec[0])); |
784 | if(!(which = find(vec[0]))) { |
785 | error(0, "%s:%d: unknown configuration key '%s'", |
786 | cs->path, cs->line, vec[0]); |
787 | return -1; |
788 | } |
789 | return (which->validate(cs, nvec - 1, vec + 1) |
790 | || which->type->set(cs, which, nvec - 1, vec + 1)); |
791 | } |
792 | |
793 | static void config_error(const char *msg, void *u) { |
794 | const struct config_state *cs = u; |
795 | |
796 | error(0, "%s:%d: %s", cs->path, cs->line, msg); |
797 | } |
798 | |
799 | /* include a file by name */ |
800 | static int config_include(struct config *c, const char *path) { |
801 | FILE *fp; |
802 | char *buffer, *inputbuffer, **vec; |
803 | int n, ret = 0; |
804 | struct config_state cs; |
805 | |
806 | cs.path = path; |
807 | cs.line = 0; |
808 | cs.config = c; |
809 | D(("%s: reading configuration", path)); |
810 | if(!(fp = fopen(path, "r"))) { |
811 | error(errno, "error opening %s", path); |
812 | return -1; |
813 | } |
814 | while(!inputline(path, fp, &inputbuffer, '\n')) { |
815 | ++cs.line; |
816 | if(!(buffer = mb2utf8(inputbuffer))) { |
817 | error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line); |
818 | ret = -1; |
819 | xfree(inputbuffer); |
820 | continue; |
821 | } |
822 | xfree(inputbuffer); |
823 | if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES, |
824 | config_error, &cs))) { |
825 | ret = -1; |
826 | xfree(buffer); |
827 | continue; |
828 | } |
829 | if(n) { |
830 | if(!strcmp(vec[0], "include")) { |
831 | if(n != 2) { |
832 | error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line); |
833 | ret = -1; |
834 | } else |
835 | config_include(c, vec[1]); |
836 | } else |
837 | ret |= config_set(&cs, n, vec); |
838 | } |
839 | for(n = 0; vec[n]; ++n) xfree(vec[n]); |
840 | xfree(vec); |
841 | xfree(buffer); |
842 | } |
843 | if(ferror(fp)) { |
844 | error(errno, "error reading %s", path); |
845 | ret = -1; |
846 | } |
847 | fclose(fp); |
848 | return ret; |
849 | } |
850 | |
851 | /* make a new default config */ |
852 | static struct config *config_default(void) { |
853 | struct config *c = xmalloc(sizeof *c); |
854 | const char *logname; |
855 | struct passwd *pw; |
856 | |
857 | /* Strings had better be xstrdup'd as they will get freed at some point. */ |
858 | c->gap = 2; |
859 | c->history = 60; |
860 | c->home = xstrdup(pkgstatedir); |
861 | if(!(pw = getpwuid(getuid()))) |
862 | fatal(0, "cannot determine our username"); |
863 | logname = pw->pw_name; |
864 | c->username = xstrdup(logname); |
865 | c->refresh = 15; |
866 | c->prefsync = 3600; |
867 | c->signal = SIGKILL; |
868 | c->alias = xstrdup("{/artist}{/album}{/title}{ext}"); |
869 | c->lock = 1; |
870 | c->device = xstrdup("default"); |
871 | c->nice_rescan = 10; |
9d5da576 |
872 | c->speaker_command = 0; |
873 | c->sample_format.bits = 16; |
874 | c->sample_format.rate = 44100; |
875 | c->sample_format.channels = 2; |
876 | c->sample_format.byte_format = AO_FMT_NATIVE; |
459d4402 |
877 | c->queue_pad = 10; |
460b9539 |
878 | return c; |
879 | } |
880 | |
881 | static char *get_file(struct config *c, const char *name) { |
882 | char *s; |
883 | |
884 | byte_xasprintf(&s, "%s/%s", c->home, name); |
885 | return s; |
886 | } |
887 | |
888 | static void set_configfile(void) { |
889 | if(!configfile) |
890 | byte_xasprintf(&configfile, "%s/config", pkgconfdir); |
891 | } |
892 | |
893 | /* free the config file */ |
894 | static void config_free(struct config *c) { |
895 | int n; |
896 | |
897 | if(c) { |
898 | for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n) |
899 | conf[n].type->free(c, &conf[n]); |
900 | for(n = 0; n < c->nparts; ++n) |
901 | xfree(c->parts[n]); |
902 | xfree(c->parts); |
903 | xfree(c); |
904 | } |
905 | } |
906 | |
907 | static void config_postdefaults(struct config *c) { |
908 | struct config_state cs; |
909 | const struct conf *whoami; |
910 | int n; |
911 | |
912 | static const char *namepart[][4] = { |
913 | { "title", "/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" }, |
914 | { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" }, |
915 | { "album", "/([^/]+)/[^/]+$", "$1", "*" }, |
916 | { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" }, |
917 | { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" }, |
918 | }; |
919 | #define NNAMEPART (int)(sizeof namepart / sizeof *namepart) |
920 | |
921 | static const char *transform[][5] = { |
922 | { "track", "^.*/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" }, |
923 | { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" }, |
924 | { "dir", "^.*/([^/]+)$", "$1", "*", "" }, |
925 | { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", }, |
926 | { "dir", "[[:punct:]]", "", "sort", "g", } |
927 | }; |
928 | #define NTRANSFORM (int)(sizeof transform / sizeof *transform) |
929 | |
930 | cs.path = "<internal>"; |
931 | cs.line = 0; |
932 | cs.config = c; |
933 | if(!c->namepart.n) { |
934 | whoami = find("namepart"); |
935 | for(n = 0; n < NNAMEPART; ++n) |
936 | set_namepart(&cs, whoami, 4, (char **)namepart[n]); |
937 | } |
938 | if(!c->transform.n) { |
939 | whoami = find("transform"); |
940 | for(n = 0; n < NTRANSFORM; ++n) |
941 | set_transform(&cs, whoami, 5, (char **)transform[n]); |
942 | } |
943 | } |
944 | |
945 | /* re-read the config file */ |
946 | int config_read() { |
947 | struct config *c; |
948 | char *privconf; |
949 | struct passwd *pw; |
950 | |
951 | set_configfile(); |
952 | c = config_default(); |
953 | if(config_include(c, configfile)) |
954 | return -1; |
955 | /* if we can read the private config file, do */ |
956 | if((privconf = config_private()) |
957 | && access(privconf, R_OK) == 0 |
958 | && config_include(c, privconf)) |
959 | return -1; |
960 | xfree(privconf); |
961 | /* if there's a per-user system config file for this user, read it */ |
962 | if(!(pw = getpwuid(getuid()))) |
963 | fatal(0, "cannot determine our username"); |
964 | if((privconf = config_usersysconf(pw)) |
965 | && access(privconf, F_OK) == 0 |
966 | && config_include(c, privconf)) |
967 | return -1; |
968 | xfree(privconf); |
969 | /* if we have a password file, read it */ |
970 | if((privconf = config_userconf(getenv("HOME"), pw)) |
971 | && access(privconf, F_OK) == 0 |
972 | && config_include(c, privconf)) |
973 | return -1; |
974 | xfree(privconf); |
975 | /* install default namepart and transform settings */ |
976 | config_postdefaults(c); |
977 | /* everything is good so we shall use the new config */ |
978 | config_free(config); |
979 | config = c; |
980 | return 0; |
981 | } |
982 | |
983 | char *config_private(void) { |
984 | char *s; |
985 | |
986 | set_configfile(); |
987 | byte_xasprintf(&s, "%s.private", configfile); |
988 | return s; |
989 | } |
990 | |
991 | char *config_userconf(const char *home, const struct passwd *pw) { |
992 | char *s; |
993 | |
994 | byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir); |
995 | return s; |
996 | } |
997 | |
998 | char *config_usersysconf(const struct passwd *pw ) { |
999 | char *s; |
1000 | |
1001 | set_configfile(); |
1002 | if(!strchr(pw->pw_name, '/')) { |
1003 | byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name); |
1004 | return s; |
1005 | } else |
1006 | return 0; |
1007 | } |
1008 | |
1009 | char *config_get_file(const char *name) { |
1010 | return get_file(config, name); |
1011 | } |
1012 | |
1013 | /* |
1014 | Local Variables: |
1015 | c-basic-offset:2 |
1016 | comment-column:40 |
1017 | fill-column:79 |
1018 | End: |
1019 | */ |