More conditional header inclusion.
[disorder] / lib / configuration.c
1 /*
2 * This file is part of DisOrder.
3 * Copyright (C) 2004-2011, 2013 Richard Kettlewell
4 * Portions copyright (C) 2007 Mark Wooding
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 /** @file lib/configuration.c
20 * @brief Configuration file support
21 */
22
23 #include "common.h"
24
25 #include <errno.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #if HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31 #include <ctype.h>
32 #include <stddef.h>
33 #if HAVE_PWD_H
34 # include <pwd.h>
35 #endif
36 #if HAVE_LANGINFO_H
37 # include <langinfo.h>
38 #endif
39 #if HAVE_PCRE_H
40 # include <pcre.h>
41 #endif
42 #include <signal.h>
43
44 #include "rights.h"
45 #include "configuration.h"
46 #include "mem.h"
47 #include "log.h"
48 #include "split.h"
49 #include "syscalls.h"
50 #include "table.h"
51 #include "inputline.h"
52 #include "charset.h"
53 #include "defs.h"
54 #include "printf.h"
55 #if HAVE_PCRE_H
56 # include "regsub.h"
57 #endif
58 #include "signame.h"
59 #include "authhash.h"
60 #include "vector.h"
61 #include "uaudio.h"
62
63 /** @brief Path to config file
64 *
65 * set_configfile() sets the default if it is null.
66 */
67 char *configfile;
68
69 /** @brief Read user configuration
70 *
71 * If clear, the user-specific configuration is not read.
72 */
73 int config_per_user = 1;
74
75 /** @brief Table of audio APIs
76 *
77 * Only set in server processes.
78 */
79 const struct uaudio *const *config_uaudio_apis;
80
81 /** @brief Config file parser state */
82 struct config_state {
83 /** @brief Filename */
84 const char *path;
85
86 /** @brief Line number */
87 int line;
88
89 /** @brief Configuration object under construction */
90 struct config *config;
91 };
92
93 /** @brief Current configuration */
94 struct config *config;
95
96 /** @brief One configuration item */
97 struct conf {
98 /** @brief Name as it appears in the config file */
99 const char *name;
100
101 /** @brief Offset in @ref config structure */
102 size_t offset;
103
104 /** @brief Pointer to item type */
105 const struct conftype *type;
106
107 /** @brief Pointer to item-specific validation routine
108 * @param cs Configuration state
109 * @param nvec Length of (proposed) new value
110 * @param vec Elements of new value
111 * @return 0 on success, non-0 on error
112 *
113 * The validate function should report any error it detects.
114 */
115 int (*validate)(const struct config_state *cs,
116 int nvec, char **vec);
117 };
118
119 /** @brief Type of a configuration item */
120 struct conftype {
121 /** @brief Pointer to function to set item
122 * @param cs Configuration state
123 * @param whoami Configuration item to set
124 * @param nvec Length of new value
125 * @param vec New value
126 * @return 0 on success, non-0 on error
127 */
128 int (*set)(const struct config_state *cs,
129 const struct conf *whoami,
130 int nvec, char **vec);
131
132 /** @brief Pointer to function to free item
133 * @param c Configuration structure to free an item of
134 * @param whoami Configuration item to free
135 */
136 void (*free)(struct config *c, const struct conf *whoami);
137 };
138
139 /** @brief Compute the address of an item */
140 #define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
141 /** @brief Return the value of an item */
142 #define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
143
144 static int stringlist_compare(const struct stringlist *a,
145 const struct stringlist *b);
146 static int namepartlist_compare(const struct namepartlist *a,
147 const struct namepartlist *b);
148
149 static int set_signal(const struct config_state *cs,
150 const struct conf *whoami,
151 int nvec, char **vec) {
152 int n;
153
154 if(nvec != 1) {
155 disorder_error(0, "%s:%d: '%s' requires one argument",
156 cs->path, cs->line, whoami->name);
157 return -1;
158 }
159 if((n = find_signal(vec[0])) == -1) {
160 disorder_error(0, "%s:%d: unknown signal '%s'",
161 cs->path, cs->line, vec[0]);
162 return -1;
163 }
164 VALUE(cs->config, int) = n;
165 return 0;
166 }
167
168 static int set_collections(const struct config_state *cs,
169 const struct conf *whoami,
170 int nvec, char **vec) {
171 struct collectionlist *cl;
172 const char *root, *encoding, *module;
173
174 switch(nvec) {
175 case 1:
176 module = 0;
177 encoding = 0;
178 root = vec[0];
179 break;
180 case 2:
181 module = vec[0];
182 encoding = 0;
183 root = vec[1];
184 break;
185 case 3:
186 module = vec[0];
187 encoding = vec[1];
188 root = vec[2];
189 break;
190 case 0:
191 disorder_error(0, "%s:%d: '%s' requires at least one argument",
192 cs->path, cs->line, whoami->name);
193 return -1;
194 default:
195 disorder_error(0, "%s:%d: '%s' requires at most three arguments",
196 cs->path, cs->line, whoami->name);
197 return -1;
198 }
199 /* Sanity check root */
200 if(root[0] != '/') {
201 disorder_error(0, "%s:%d: collection root must start with '/'",
202 cs->path, cs->line);
203 return -1;
204 }
205 if(root[1] && root[strlen(root)-1] == '/') {
206 disorder_error(0, "%s:%d: collection root must not end with '/'",
207 cs->path, cs->line);
208 return -1;
209 }
210 /* Defaults */
211 if(!module)
212 module = "fs";
213 #if HAVE_LANGINFO_H
214 if(!encoding)
215 encoding = nl_langinfo(CODESET);
216 #else
217 if(!encoding)
218 encoding = "ascii";
219 #endif
220 cl = ADDRESS(cs->config, struct collectionlist);
221 ++cl->n;
222 cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
223 cl->s[cl->n - 1].module = xstrdup(module);
224 cl->s[cl->n - 1].encoding = xstrdup(encoding);
225 cl->s[cl->n - 1].root = xstrdup(root);
226 return 0;
227 }
228
229 static int set_boolean(const struct config_state *cs,
230 const struct conf *whoami,
231 int nvec, char **vec) {
232 int state;
233
234 if(nvec != 1) {
235 disorder_error(0, "%s:%d: '%s' takes only one argument",
236 cs->path, cs->line, whoami->name);
237 return -1;
238 }
239 if(!strcmp(vec[0], "yes")) state = 1;
240 else if(!strcmp(vec[0], "no")) state = 0;
241 else {
242 disorder_error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
243 cs->path, cs->line, whoami->name);
244 return -1;
245 }
246 VALUE(cs->config, int) = state;
247 return 0;
248 }
249
250 static int set_string(const struct config_state *cs,
251 const struct conf *whoami,
252 int nvec, char **vec) {
253 if(nvec != 1) {
254 disorder_error(0, "%s:%d: '%s' takes only one argument",
255 cs->path, cs->line, whoami->name);
256 return -1;
257 }
258 xfree(VALUE(cs->config, char *));
259 VALUE(cs->config, char *) = xstrdup(vec[0]);
260 return 0;
261 }
262
263 static int set_stringlist(const struct config_state *cs,
264 const struct conf *whoami,
265 int nvec, char **vec) {
266 int n;
267 struct stringlist *sl;
268
269 sl = ADDRESS(cs->config, struct stringlist);
270 sl->n = 0;
271 for(n = 0; n < nvec; ++n) {
272 sl->n++;
273 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
274 sl->s[sl->n - 1] = xstrdup(vec[n]);
275 }
276 return 0;
277 }
278
279 static int set_integer(const struct config_state *cs,
280 const struct conf *whoami,
281 int nvec, char **vec) {
282 char *e;
283
284 if(nvec != 1) {
285 disorder_error(0, "%s:%d: '%s' takes only one argument",
286 cs->path, cs->line, whoami->name);
287 return -1;
288 }
289 if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
290 disorder_error(errno, "%s:%d: converting integer", cs->path, cs->line);
291 return -1;
292 }
293 if(*e) {
294 disorder_error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
295 return -1;
296 }
297 return 0;
298 }
299
300 static int set_stringlist_accum(const struct config_state *cs,
301 const struct conf *whoami,
302 int nvec, char **vec) {
303 int n;
304 struct stringlist *s;
305 struct stringlistlist *sll;
306
307 sll = ADDRESS(cs->config, struct stringlistlist);
308 if(nvec == 0) {
309 sll->n = 0;
310 return 0;
311 }
312 sll->n++;
313 sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
314 s = &sll->s[sll->n - 1];
315 s->n = nvec;
316 s->s = xmalloc((nvec + 1) * sizeof (char *));
317 for(n = 0; n < nvec; ++n)
318 s->s[n] = xstrdup(vec[n]);
319 return 0;
320 }
321
322 static int set_string_accum(const struct config_state *cs,
323 const struct conf *whoami,
324 int nvec, char **vec) {
325 int n;
326 struct stringlist *sl;
327
328 sl = ADDRESS(cs->config, struct stringlist);
329 if(nvec == 0) {
330 sl->n = 0;
331 return 0;
332 }
333 for(n = 0; n < nvec; ++n) {
334 sl->n++;
335 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
336 sl->s[sl->n - 1] = xstrdup(vec[n]);
337 }
338 return 0;
339 }
340
341 static int parse_sample_format(const struct config_state *cs,
342 struct stream_header *format,
343 int nvec, char **vec) {
344 char *p = vec[0];
345 long t;
346
347 if(nvec != 1) {
348 disorder_error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
349 return -1;
350 }
351 if(xstrtol(&t, p, &p, 0)) {
352 disorder_error(errno, "%s:%d: converting bits-per-sample",
353 cs->path, cs->line);
354 return -1;
355 }
356 if(t != 8 && t != 16) {
357 disorder_error(0, "%s:%d: bad bits-per-sample (%ld)",
358 cs->path, cs->line, t);
359 return -1;
360 }
361 if(format) format->bits = t;
362 switch (*p) {
363 case 'l': case 'L': t = ENDIAN_LITTLE; p++; break;
364 case 'b': case 'B': t = ENDIAN_BIG; p++; break;
365 default: t = ENDIAN_NATIVE; break;
366 }
367 if(format) format->endian = t;
368 if(*p != '/') {
369 disorder_error(errno, "%s:%d: expected `/' after bits-per-sample",
370 cs->path, cs->line);
371 return -1;
372 }
373 p++;
374 if(xstrtol(&t, p, &p, 0)) {
375 disorder_error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
376 return -1;
377 }
378 if(t < 1 || t > INT_MAX) {
379 disorder_error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
380 return -1;
381 }
382 if(format) format->rate = t;
383 if(*p != '/') {
384 disorder_error(0, "%s:%d: expected `/' after sample-rate",
385 cs->path, cs->line);
386 return -1;
387 }
388 p++;
389 if(xstrtol(&t, p, &p, 0)) {
390 disorder_error(errno, "%s:%d: converting channels", cs->path, cs->line);
391 return -1;
392 }
393 if(t < 1 || t > 8) {
394 disorder_error(0, "%s:%d: silly number (%ld) of channels",
395 cs->path, cs->line, t);
396 return -1;
397 }
398 if(format) format->channels = t;
399 if(*p) {
400 disorder_error(0, "%s:%d: junk after channels", cs->path, cs->line);
401 return -1;
402 }
403 return 0;
404 }
405
406 static int set_sample_format(const struct config_state *cs,
407 const struct conf *whoami,
408 int nvec, char **vec) {
409 return parse_sample_format(cs, ADDRESS(cs->config, struct stream_header),
410 nvec, vec);
411 }
412
413 #if HAVE_PCRE_H
414 static int set_namepart(const struct config_state *cs,
415 const struct conf *whoami,
416 int nvec, char **vec) {
417 struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
418 unsigned reflags;
419 const char *errstr;
420 int erroffset, n;
421 pcre *re;
422
423 if(nvec < 3) {
424 disorder_error(0, "%s:%d: namepart needs at least 3 arguments",
425 cs->path, cs->line);
426 return -1;
427 }
428 if(nvec > 5) {
429 disorder_error(0, "%s:%d: namepart needs at most 5 arguments",
430 cs->path, cs->line);
431 return -1;
432 }
433 reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
434 if(!(re = pcre_compile(vec[1],
435 PCRE_UTF8
436 |regsub_compile_options(reflags),
437 &errstr, &erroffset, 0))) {
438 disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
439 cs->path, cs->line, vec[1], errstr, erroffset);
440 return -1;
441 }
442 npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
443 npl->s[npl->n].part = xstrdup(vec[0]);
444 npl->s[npl->n].re = re;
445 npl->s[npl->n].res = xstrdup(vec[1]);
446 npl->s[npl->n].replace = xstrdup(vec[2]);
447 npl->s[npl->n].context = xstrdup(vec[3]);
448 npl->s[npl->n].reflags = reflags;
449 ++npl->n;
450 /* XXX a bit of a bodge; relies on there being very few parts. */
451 for(n = 0; (n < cs->config->nparts
452 && strcmp(cs->config->parts[n], vec[0])); ++n)
453 ;
454 if(n >= cs->config->nparts) {
455 cs->config->parts = xrealloc(cs->config->parts,
456 (cs->config->nparts + 1) * sizeof (char *));
457 cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
458 }
459 return 0;
460 }
461
462 static int set_transform(const struct config_state *cs,
463 const struct conf *whoami,
464 int nvec, char **vec) {
465 struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
466 pcre *re;
467 unsigned reflags;
468 const char *errstr;
469 int erroffset;
470
471 if(nvec < 3) {
472 disorder_error(0, "%s:%d: transform needs at least 3 arguments",
473 cs->path, cs->line);
474 return -1;
475 }
476 if(nvec > 5) {
477 disorder_error(0, "%s:%d: transform needs at most 5 arguments",
478 cs->path, cs->line);
479 return -1;
480 }
481 reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
482 if(!(re = pcre_compile(vec[1],
483 PCRE_UTF8
484 |regsub_compile_options(reflags),
485 &errstr, &erroffset, 0))) {
486 disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
487 cs->path, cs->line, vec[1], errstr, erroffset);
488 return -1;
489 }
490 tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
491 tl->t[tl->n].type = xstrdup(vec[0]);
492 tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
493 tl->t[tl->n].re = re;
494 tl->t[tl->n].replace = xstrdup(vec[2]);
495 tl->t[tl->n].flags = reflags;
496 ++tl->n;
497 return 0;
498 }
499 #endif
500
501 static int set_rights(const struct config_state *cs,
502 const struct conf *whoami,
503 int nvec, char **vec) {
504 if(nvec != 1) {
505 disorder_error(0, "%s:%d: '%s' requires one argument",
506 cs->path, cs->line, whoami->name);
507 return -1;
508 }
509 if(parse_rights(vec[0], 0, 1)) {
510 disorder_error(0, "%s:%d: invalid rights string '%s'",
511 cs->path, cs->line, vec[0]);
512 return -1;
513 }
514 return set_string(cs, whoami, nvec, vec);
515 }
516
517 static int set_netaddress(const struct config_state *cs,
518 const struct conf *whoami,
519 int nvec, char **vec) {
520 struct netaddress *na = ADDRESS(cs->config, struct netaddress);
521
522 if(netaddress_parse(na, nvec, vec)) {
523 disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
524 return -1;
525 }
526 return 0;
527 }
528
529 /* free functions */
530
531 static void free_none(struct config attribute((unused)) *c,
532 const struct conf attribute((unused)) *whoami) {
533 }
534
535 static void free_string(struct config *c,
536 const struct conf *whoami) {
537 xfree(VALUE(c, char *));
538 VALUE(c, char *) = 0;
539 }
540
541 static void free_stringlist(struct config *c,
542 const struct conf *whoami) {
543 int n;
544 struct stringlist *sl = ADDRESS(c, struct stringlist);
545
546 for(n = 0; n < sl->n; ++n)
547 xfree(sl->s[n]);
548 xfree(sl->s);
549 }
550
551 static void free_stringlistlist(struct config *c,
552 const struct conf *whoami) {
553 int n, m;
554 struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
555 struct stringlist *sl;
556
557 for(n = 0; n < sll->n; ++n) {
558 sl = &sll->s[n];
559 for(m = 0; m < sl->n; ++m)
560 xfree(sl->s[m]);
561 xfree(sl->s);
562 }
563 xfree(sll->s);
564 }
565
566 static void free_collectionlist(struct config *c,
567 const struct conf *whoami) {
568 struct collectionlist *cll = ADDRESS(c, struct collectionlist);
569 struct collection *cl;
570 int n;
571
572 for(n = 0; n < cll->n; ++n) {
573 cl = &cll->s[n];
574 xfree(cl->module);
575 xfree(cl->encoding);
576 xfree(cl->root);
577 }
578 xfree(cll->s);
579 }
580
581 #if HAVE_PCRE_H
582 static void free_namepartlist(struct config *c,
583 const struct conf *whoami) {
584 struct namepartlist *npl = ADDRESS(c, struct namepartlist);
585 struct namepart *np;
586 int n;
587
588 for(n = 0; n < npl->n; ++n) {
589 np = &npl->s[n];
590 xfree(np->part);
591 pcre_free(np->re); /* ...whatever pcre_free is set to. */
592 xfree(np->res);
593 xfree(np->replace);
594 xfree(np->context);
595 }
596 xfree(npl->s);
597 }
598
599 static void free_transformlist(struct config *c,
600 const struct conf *whoami) {
601 struct transformlist *tl = ADDRESS(c, struct transformlist);
602 struct transform *t;
603 int n;
604
605 for(n = 0; n < tl->n; ++n) {
606 t = &tl->t[n];
607 xfree(t->type);
608 pcre_free(t->re); /* ...whatever pcre_free is set to. */
609 xfree(t->replace);
610 xfree(t->context);
611 }
612 xfree(tl->t);
613 }
614 #endif
615
616 static void free_netaddress(struct config *c,
617 const struct conf *whoami) {
618 struct netaddress *na = ADDRESS(c, struct netaddress);
619
620 xfree(na->address);
621 }
622
623 /* configuration types */
624
625 static const struct conftype
626 type_signal = { set_signal, free_none },
627 type_collections = { set_collections, free_collectionlist },
628 type_boolean = { set_boolean, free_none },
629 type_string = { set_string, free_string },
630 type_stringlist = { set_stringlist, free_stringlist },
631 type_integer = { set_integer, free_none },
632 type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
633 type_string_accum = { set_string_accum, free_stringlist },
634 type_sample_format = { set_sample_format, free_none },
635 #if HAVE_PCRE_H
636 type_namepart = { set_namepart, free_namepartlist },
637 type_transform = { set_transform, free_transformlist },
638 #endif
639 type_netaddress = { set_netaddress, free_netaddress },
640 type_rights = { set_rights, free_string };
641
642 /* specific validation routine */
643
644 /** @brief Perform a test on a filename
645 * @param test Test function to call on mode bits
646 * @param what Type of file sought
647 *
648 * If @p test returns 0 then the file is not a @p what and an error
649 * is reported and -1 is returned.
650 */
651 #define VALIDATE_FILE(test, what) do { \
652 struct stat sb; \
653 int n; \
654 \
655 for(n = 0; n < nvec; ++n) { \
656 if(stat(vec[n], &sb) < 0) { \
657 disorder_error(errno, "%s:%d: %s", \
658 cs->path, cs->line, vec[n]); \
659 return -1; \
660 } \
661 if(!test(sb.st_mode)) { \
662 disorder_error(0, "%s:%d: %s is not a %s", \
663 cs->path, cs->line, vec[n], what); \
664 return -1; \
665 } \
666 } \
667 } while(0)
668
669 /** @brief Validate an absolute path
670 * @param cs Configuration state
671 * @param nvec Length of (proposed) new value
672 * @param vec Elements of new value
673 * @return 0 on success, non-0 on error
674 */
675 static int validate_isabspath(const struct config_state *cs,
676 int nvec, char **vec) {
677 int n;
678
679 for(n = 0; n < nvec; ++n)
680 if(vec[n][0] != '/') {
681 disorder_error(0, "%s:%d: %s: not an absolute path",
682 cs->path, cs->line, vec[n]);
683 return -1;
684 }
685 return 0;
686 }
687
688 /** @brief Validate an existing directory
689 * @param cs Configuration state
690 * @param nvec Length of (proposed) new value
691 * @param vec Elements of new value
692 * @return 0 on success, non-0 on error
693 */
694 static int validate_isdir(const struct config_state *cs,
695 int nvec, char **vec) {
696 VALIDATE_FILE(S_ISDIR, "directory");
697 return 0;
698 }
699
700 /** @brief Validate an existing regular file
701 * @param cs Configuration state
702 * @param nvec Length of (proposed) new value
703 * @param vec Elements of new value
704 * @return 0 on success, non-0 on error
705 */
706 static int validate_isreg(const struct config_state *cs,
707 int nvec, char **vec) {
708 VALIDATE_FILE(S_ISREG, "regular file");
709 return 0;
710 }
711
712 /** @brief Validate a player pattern
713 * @param cs Configuration state
714 * @param nvec Length of (proposed) new value
715 * @param vec Elements of new value
716 * @return 0 on success, non-0 on error
717 */
718 static int validate_player(const struct config_state *cs,
719 int nvec,
720 char attribute((unused)) **vec) {
721 if(nvec && nvec < 2) {
722 disorder_error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
723 cs->path, cs->line);
724 return -1;
725 }
726 return 0;
727 }
728
729 /** @brief Validate a track length pattern
730 * @param cs Configuration state
731 * @param nvec Length of (proposed) new value
732 * @param vec Elements of new value
733 * @return 0 on success, non-0 on error
734 */
735 static int validate_tracklength(const struct config_state *cs,
736 int nvec,
737 char attribute((unused)) **vec) {
738 if(nvec && nvec < 2) {
739 disorder_error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
740 cs->path, cs->line);
741 return -1;
742 }
743 return 0;
744 }
745
746 /** @brief Validate a non-negative (@c long) integer
747 * @param cs Configuration state
748 * @param nvec Length of (proposed) new value
749 * @param vec Elements of new value
750 * @return 0 on success, non-0 on error
751 */
752 static int validate_non_negative(const struct config_state *cs,
753 int nvec, char **vec) {
754 long n;
755
756 if(nvec < 1) {
757 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
758 return -1;
759 }
760 if(nvec > 1) {
761 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
762 return -1;
763 }
764 if(xstrtol(&n, vec[0], 0, 0)) {
765 disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
766 return -1;
767 }
768 if(n < 0) {
769 disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
770 return -1;
771 }
772 return 0;
773 }
774
775 /** @brief Validate a positive (@c long) integer
776 * @param cs Configuration state
777 * @param nvec Length of (proposed) new value
778 * @param vec Elements of new value
779 * @return 0 on success, non-0 on error
780 */
781 static int validate_positive(const struct config_state *cs,
782 int nvec, char **vec) {
783 long n;
784
785 if(nvec < 1) {
786 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
787 return -1;
788 }
789 if(nvec > 1) {
790 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
791 return -1;
792 }
793 if(xstrtol(&n, vec[0], 0, 0)) {
794 disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
795 return -1;
796 }
797 if(n <= 0) {
798 disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
799 return -1;
800 }
801 return 0;
802 }
803
804 /** @brief Validate a system username
805 * @param cs Configuration state
806 * @param nvec Length of (proposed) new value
807 * @param vec Elements of new value
808 * @return 0 on success, non-0 on error
809 */
810 static int validate_isauser(const struct config_state *cs,
811 int attribute((unused)) nvec,
812 char **vec) {
813 if(!getpwnam(vec[0])) {
814 disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
815 return -1;
816 }
817 return 0;
818 }
819
820 /** @brief Validate a sample format string
821 * @param cs Configuration state
822 * @param nvec Length of (proposed) new value
823 * @param vec Elements of new value
824 * @return 0 on success, non-0 on error
825 */
826 static int validate_sample_format(const struct config_state *cs,
827 int attribute((unused)) nvec,
828 char **vec) {
829 return parse_sample_format(cs, 0, nvec, vec);
830 }
831
832 /** @brief Validate anything
833 * @param cs Configuration state
834 * @param nvec Length of (proposed) new value
835 * @param vec Elements of new value
836 * @return 0
837 */
838 static int validate_any(const struct config_state attribute((unused)) *cs,
839 int attribute((unused)) nvec,
840 char attribute((unused)) **vec) {
841 return 0;
842 }
843
844 /** @brief Validate a URL
845 * @param cs Configuration state
846 * @param nvec Length of (proposed) new value
847 * @param vec Elements of new value
848 * @return 0 on success, non-0 on error
849 *
850 * Rather cursory.
851 */
852 static int validate_url(const struct config_state attribute((unused)) *cs,
853 int attribute((unused)) nvec,
854 char **vec) {
855 const char *s;
856 int n;
857 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
858 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
859 s = vec[0];
860 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
861 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
862 "0123456789"));
863 if(s[n] != ':') {
864 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
865 return -1;
866 }
867 if(!strncmp(s, "http:", 5)
868 || !strncmp(s, "https:", 6)) {
869 s += n + 1;
870 /* we only do a rather cursory check */
871 if(strncmp(s, "//", 2)) {
872 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
873 return -1;
874 }
875 }
876 return 0;
877 }
878
879 /** @brief Validate an alias pattern
880 * @param cs Configuration state
881 * @param nvec Length of (proposed) new value
882 * @param vec Elements of new value
883 * @return 0 on success, non-0 on error
884 */
885 static int validate_alias(const struct config_state *cs,
886 int nvec,
887 char **vec) {
888 const char *s;
889 int in_brackets = 0, c;
890
891 if(nvec < 1) {
892 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
893 return -1;
894 }
895 if(nvec > 1) {
896 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
897 return -1;
898 }
899 s = vec[0];
900 while((c = (unsigned char)*s++)) {
901 if(in_brackets) {
902 if(c == '}')
903 in_brackets = 0;
904 else if(!isalnum(c)) {
905 disorder_error(0, "%s:%d: invalid part name in alias expansion in '%s'",
906 cs->path, cs->line, vec[0]);
907 return -1;
908 }
909 } else {
910 if(c == '{') {
911 in_brackets = 1;
912 if(*s == '/')
913 ++s;
914 } else if(c == '\\') {
915 if(!(c = (unsigned char)*s++)) {
916 disorder_error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
917 cs->path, cs->line, vec[0]);
918 return -1;
919 } else if(c != '\\' && c != '{') {
920 disorder_error(0, "%s:%d: invalid escape in alias expansion in '%s'",
921 cs->path, cs->line, vec[0]);
922 return -1;
923 }
924 }
925 }
926 ++s;
927 }
928 if(in_brackets) {
929 disorder_error(0,
930 "%s:%d: unterminated part name in alias expansion in '%s'",
931 cs->path, cs->line, vec[0]);
932 return -1;
933 }
934 return 0;
935 }
936
937 /** @brief Validate a hash algorithm name
938 * @param cs Configuration state
939 * @param nvec Length of (proposed) new value
940 * @param vec Elements of new value
941 * @return 0 on success, non-0 on error
942 */
943 static int validate_algo(const struct config_state attribute((unused)) *cs,
944 int nvec,
945 char **vec) {
946 if(nvec != 1) {
947 disorder_error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
948 return -1;
949 }
950 if(!valid_authhash(vec[0])) {
951 disorder_error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
952 return -1;
953 }
954 return 0;
955 }
956
957 /** @brief Validate a playback backend name
958 * @param cs Configuration state
959 * @param nvec Length of (proposed) new value
960 * @param vec Elements of new value
961 * @return 0 on success, non-0 on error
962 */
963 static int validate_backend(const struct config_state attribute((unused)) *cs,
964 int nvec,
965 char **vec) {
966 int n;
967 if(nvec != 1) {
968 disorder_error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
969 return -1;
970 }
971 if(!strcmp(vec[0], "network")) {
972 disorder_error(0, "'api network' is deprecated; use 'api rtp'");
973 return 0;
974 }
975 if(config_uaudio_apis) {
976 for(n = 0; config_uaudio_apis[n]; ++n)
977 if(!strcmp(vec[0], config_uaudio_apis[n]->name))
978 return 0;
979 disorder_error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
980 return -1;
981 }
982 /* In non-server processes we have no idea what's valid */
983 return 0;
984 }
985
986 /** @brief Validate a pause mode string
987 * @param cs Configuration state
988 * @param nvec Length of (proposed) new value
989 * @param vec Elements of new value
990 * @return 0 on success, non-0 on error
991 */
992 static int validate_pausemode(const struct config_state attribute((unused)) *cs,
993 int nvec,
994 char **vec) {
995 if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
996 return 0;
997 disorder_error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
998 return -1;
999 }
1000
1001 /** @brief Validate a destination network address
1002 * @param cs Configuration state
1003 * @param nvec Length of (proposed) new value
1004 * @param vec Elements of new value
1005 * @return 0 on success, non-0 on error
1006 *
1007 * By a destination address, it is meant that it must not be a wildcard
1008 * address.
1009 */
1010 static int validate_destaddr(const struct config_state attribute((unused)) *cs,
1011 int nvec,
1012 char **vec) {
1013 struct netaddress na[1];
1014
1015 if(netaddress_parse(na, nvec, vec)) {
1016 disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
1017 return -1;
1018 }
1019 if(!na->address) {
1020 disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
1021 return -1;
1022 }
1023 xfree(na->address);
1024 return 0;
1025 }
1026
1027 /** @brief Item name and and offset */
1028 #define C(x) #x, offsetof(struct config, x)
1029 /** @brief Item name and and offset */
1030 #define C2(x,y) #x, offsetof(struct config, y)
1031
1032 /** @brief All configuration items */
1033 static const struct conf conf[] = {
1034 { C(alias), &type_string, validate_alias },
1035 { C(api), &type_string, validate_backend },
1036 { C(authorization_algorithm), &type_string, validate_algo },
1037 { C(broadcast), &type_netaddress, validate_destaddr },
1038 { C(broadcast_from), &type_netaddress, validate_any },
1039 { C(channel), &type_string, validate_any },
1040 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
1041 { C(checkpoint_min), &type_integer, validate_non_negative },
1042 { C(collection), &type_collections, validate_any },
1043 { C(connect), &type_netaddress, validate_destaddr },
1044 { C(cookie_key_lifetime), &type_integer, validate_positive },
1045 { C(cookie_login_lifetime), &type_integer, validate_positive },
1046 { C(dbversion), &type_integer, validate_positive },
1047 { C(default_rights), &type_rights, validate_any },
1048 { C(device), &type_string, validate_any },
1049 { C(history), &type_integer, validate_positive },
1050 { C(home), &type_string, validate_isabspath },
1051 { C(listen), &type_netaddress, validate_any },
1052 { C(mail_sender), &type_string, validate_any },
1053 { C(mixer), &type_string, validate_any },
1054 { C(mount_rescan), &type_boolean, validate_any },
1055 { C(multicast_loop), &type_boolean, validate_any },
1056 { C(multicast_ttl), &type_integer, validate_non_negative },
1057 #if HAVE_PCRE_H
1058 { C(namepart), &type_namepart, validate_any },
1059 #endif
1060 { C(new_bias), &type_integer, validate_positive },
1061 { C(new_bias_age), &type_integer, validate_positive },
1062 { C(new_max), &type_integer, validate_positive },
1063 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
1064 { C(nice_rescan), &type_integer, validate_non_negative },
1065 { C(nice_server), &type_integer, validate_any },
1066 { C(nice_speaker), &type_integer, validate_any },
1067 { C(noticed_history), &type_integer, validate_positive },
1068 { C(password), &type_string, validate_any },
1069 { C(pause_mode), &type_string, validate_pausemode },
1070 { C(player), &type_stringlist_accum, validate_player },
1071 { C(playlist_lock_timeout), &type_integer, validate_positive },
1072 { C(playlist_max) , &type_integer, validate_positive },
1073 { C(plugins), &type_string_accum, validate_isdir },
1074 { C(queue_pad), &type_integer, validate_positive },
1075 { C(refresh), &type_integer, validate_positive },
1076 { C(refresh_min), &type_integer, validate_non_negative },
1077 { C(reminder_interval), &type_integer, validate_positive },
1078 { C(remote_userman), &type_boolean, validate_any },
1079 { C(replay_min), &type_integer, validate_non_negative },
1080 { C(rtp_delay_threshold), &type_integer, validate_positive },
1081 { C(rtp_mode), &type_string, validate_any },
1082 { C(rtp_verbose), &type_boolean, validate_any },
1083 { C(sample_format), &type_sample_format, validate_sample_format },
1084 { C(scratch), &type_string_accum, validate_isreg },
1085 { C(sendmail), &type_string, validate_isabspath },
1086 { C(short_display), &type_integer, validate_positive },
1087 { C(signal), &type_signal, validate_any },
1088 { C(smtp_server), &type_string, validate_any },
1089 { C(sox_generation), &type_integer, validate_non_negative },
1090 { C2(speaker_backend, api), &type_string, validate_backend },
1091 { C(speaker_command), &type_string, validate_any },
1092 { C(stopword), &type_string_accum, validate_any },
1093 { C(templates), &type_string_accum, validate_isdir },
1094 { C(tracklength), &type_stringlist_accum, validate_tracklength },
1095 #if HAVE_PCRE_H
1096 { C(transform), &type_transform, validate_any },
1097 #endif
1098 { C(url), &type_string, validate_url },
1099 { C(user), &type_string, validate_isauser },
1100 { C(username), &type_string, validate_any },
1101 };
1102
1103 /** @brief Find a configuration item's definition by key */
1104 static const struct conf *find(const char *key) {
1105 int n;
1106
1107 if((n = TABLE_FIND(conf, name, key)) < 0)
1108 return 0;
1109 return &conf[n];
1110 }
1111
1112 /** @brief Set a new configuration value
1113 * @param cs Configuration state
1114 * @param nvec Length of @p vec
1115 * @param vec Name and new value
1116 * @return 0 on success, non-0 on error.
1117 *
1118 * @c vec[0] is the name, the rest is the value.
1119 */
1120 static int config_set(const struct config_state *cs,
1121 int nvec, char **vec) {
1122 const struct conf *which;
1123
1124 D(("config_set %s", vec[0]));
1125 if(!(which = find(vec[0]))) {
1126 disorder_error(0, "%s:%d: unknown configuration key '%s'",
1127 cs->path, cs->line, vec[0]);
1128 return -1;
1129 }
1130 return (which->validate(cs, nvec - 1, vec + 1)
1131 || which->type->set(cs, which, nvec - 1, vec + 1));
1132 }
1133
1134 /** @brief Set a configuration item from parameters
1135 * @param cs Configuration state
1136 * @param which Item to set
1137 * @param ... Value as strings, terminated by (char *)NULL
1138 * @return 0 on success, non-0 on error
1139 */
1140 static int config_set_args(const struct config_state *cs,
1141 const char *which, ...) {
1142 va_list ap;
1143 struct vector v[1];
1144 char *s;
1145 int rc;
1146
1147 vector_init(v);
1148 vector_append(v, (char *)which);
1149 va_start(ap, which);
1150 while((s = va_arg(ap, char *)))
1151 vector_append(v, s);
1152 va_end(ap);
1153 vector_terminate(v);
1154 rc = config_set(cs, v->nvec, v->vec);
1155 xfree(v->vec);
1156 return rc;
1157 }
1158
1159 /** @brief Error callback used by config_include()
1160 * @param msg Error message
1161 * @param u User data (@ref config_state)
1162 */
1163 static void config_error(const char *msg, void *u) {
1164 const struct config_state *cs = u;
1165
1166 disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
1167 }
1168
1169 /** @brief Include a file by name
1170 * @param c Configuration to update
1171 * @param path Path to read
1172 * @return 0 on success, non-0 on error
1173 */
1174 static int config_include(struct config *c, const char *path) {
1175 FILE *fp;
1176 char *buffer, *inputbuffer, **vec;
1177 int n, ret = 0;
1178 struct config_state cs;
1179
1180 cs.path = path;
1181 cs.line = 0;
1182 cs.config = c;
1183 D(("%s: reading configuration", path));
1184 if(!(fp = fopen(path, "r"))) {
1185 disorder_error(errno, "error opening %s", path);
1186 return -1;
1187 }
1188 while(!inputline(path, fp, &inputbuffer, '\n')) {
1189 ++cs.line;
1190 if(!(buffer = mb2utf8(inputbuffer))) {
1191 disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1192 ret = -1;
1193 xfree(inputbuffer);
1194 continue;
1195 }
1196 xfree(inputbuffer);
1197 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1198 config_error, &cs))) {
1199 ret = -1;
1200 xfree(buffer);
1201 continue;
1202 }
1203 if(n) {
1204 /* 'include' is special-cased */
1205 if(!strcmp(vec[0], "include")) {
1206 if(n != 2) {
1207 disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1208 ret = -1;
1209 } else
1210 config_include(c, vec[1]);
1211 } else
1212 ret |= config_set(&cs, n, vec);
1213 }
1214 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1215 xfree(vec);
1216 xfree(buffer);
1217 }
1218 if(ferror(fp)) {
1219 disorder_error(errno, "error reading %s", path);
1220 ret = -1;
1221 }
1222 fclose(fp);
1223 return ret;
1224 }
1225
1226 /** @brief Default stopword setting */
1227 static const char *const default_stopwords[] = {
1228 "stopword",
1229
1230 "01",
1231 "02",
1232 "03",
1233 "04",
1234 "05",
1235 "06",
1236 "07",
1237 "08",
1238 "09",
1239 "1",
1240 "10",
1241 "11",
1242 "12",
1243 "13",
1244 "14",
1245 "15",
1246 "16",
1247 "17",
1248 "18",
1249 "19",
1250 "2",
1251 "20",
1252 "21",
1253 "22",
1254 "23",
1255 "24",
1256 "25",
1257 "26",
1258 "27",
1259 "28",
1260 "29",
1261 "3",
1262 "30",
1263 "4",
1264 "5",
1265 "6",
1266 "7",
1267 "8",
1268 "9",
1269 "a",
1270 "am",
1271 "an",
1272 "and",
1273 "as",
1274 "for",
1275 "i",
1276 "im",
1277 "in",
1278 "is",
1279 "of",
1280 "on",
1281 "the",
1282 "to",
1283 "too",
1284 "we",
1285 };
1286 #define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1287
1288 /** @brief Default player patterns */
1289 static const char *const default_players[] = {
1290 "*.ogg",
1291 "*.flac",
1292 "*.mp3",
1293 "*.wav",
1294 };
1295 #define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1296
1297 /** @brief Make a new default configuration
1298 * @return New configuration
1299 */
1300 static struct config *config_default(void) {
1301 struct config *c = xmalloc(sizeof *c);
1302 const char *logname;
1303 struct passwd *pw;
1304 struct config_state cs;
1305 size_t n;
1306
1307 cs.path = "<internal>";
1308 cs.line = 0;
1309 cs.config = c;
1310 /* Strings had better be xstrdup'd as they will get freed at some point. */
1311 c->history = 60;
1312 c->home = xstrdup(pkgstatedir);
1313 if(!(pw = getpwuid(getuid())))
1314 disorder_fatal(0, "cannot determine our username");
1315 logname = pw->pw_name;
1316 c->username = xstrdup(logname);
1317 c->refresh = 15;
1318 c->refresh_min = 1;
1319 c->signal = SIGKILL;
1320 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1321 c->device = xstrdup("default");
1322 c->nice_rescan = 10;
1323 c->speaker_command = 0;
1324 c->sample_format.bits = 16;
1325 c->sample_format.rate = 44100;
1326 c->sample_format.channels = 2;
1327 c->sample_format.endian = ENDIAN_NATIVE;
1328 c->queue_pad = 10;
1329 c->replay_min = 8 * 3600;
1330 c->api = NULL;
1331 c->multicast_ttl = 1;
1332 c->multicast_loop = 1;
1333 c->authorization_algorithm = xstrdup("sha1");
1334 c->noticed_history = 31;
1335 c->short_display = 32;
1336 c->mixer = 0;
1337 c->channel = 0;
1338 c->dbversion = 2;
1339 c->cookie_login_lifetime = 86400;
1340 c->cookie_key_lifetime = 86400 * 7;
1341 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1342 c->sendmail = xstrdup(sendmail_binary);
1343 c->smtp_server = xstrdup("127.0.0.1");
1344 c->new_max = 100;
1345 c->reminder_interval = 600; /* 10m */
1346 c->new_bias_age = 7 * 86400; /* 1 week */
1347 c->new_bias = 4500000; /* 50 times the base weight */
1348 c->sox_generation = DEFAULT_SOX_GENERATION;
1349 c->playlist_max = INT_MAX; /* effectively no limit */
1350 c->playlist_lock_timeout = 10; /* 10s */
1351 c->mount_rescan = 1;
1352 /* Default stopwords */
1353 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1354 exit(1);
1355 /* Default player configuration */
1356 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1357 if(config_set_args(&cs, "player",
1358 default_players[n], "execraw", "disorder-decode", (char *)0))
1359 exit(1);
1360 if(config_set_args(&cs, "tracklength",
1361 default_players[n], "disorder-tracklength", (char *)0))
1362 exit(1);
1363 }
1364 c->broadcast.af = -1;
1365 c->broadcast_from.af = -1;
1366 c->listen.af = -1;
1367 c->connect.af = -1;
1368 c->rtp_mode = xstrdup("auto");
1369 return c;
1370 }
1371
1372 /** @brief Construct a filename
1373 * @param c Configuration
1374 * @param name Base filename
1375 * @return Full filename
1376 *
1377 * Usually use config_get_file() instead.
1378 */
1379 char *config_get_file2(struct config *c, const char *name) {
1380 char *s;
1381
1382 byte_xasprintf(&s, "%s/%s", c->home, name);
1383 return s;
1384 }
1385
1386 /** @brief Set the default configuration file */
1387 static void set_configfile(void) {
1388 if(!configfile)
1389 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1390 }
1391
1392 /** @brief Free a configuration object
1393 * @param c Configuration to free
1394 *
1395 * @p c is indeterminate after this function is called.
1396 */
1397 void config_free(struct config *c) {
1398 int n;
1399
1400 if(c) {
1401 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1402 conf[n].type->free(c, &conf[n]);
1403 for(n = 0; n < c->nparts; ++n)
1404 xfree(c->parts[n]);
1405 xfree(c->parts);
1406 xfree(c);
1407 }
1408 }
1409
1410 /** @brief Set post-parse defaults
1411 * @param c Configuration to update
1412 * @param server True when running in the server
1413 *
1414 * If @p server is set then certain parts of the configuration are more
1415 * strictly validated.
1416 */
1417 static void config_postdefaults(struct config *c,
1418 int server) {
1419 struct config_state cs;
1420 #if HAVE_PCRE_H
1421 const struct conf *whoami;
1422 int n;
1423 #endif
1424
1425 #if HAVE_PCRE_H
1426 static const char *namepart[][4] = {
1427 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1428 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1429 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1430 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1431 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1432 };
1433 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1434
1435 static const char *transform[][5] = {
1436 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1437 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1438 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1439 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1440 { "dir", "[[:punct:]]", "", "sort", "g", }
1441 };
1442 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1443 #endif
1444
1445 cs.path = "<internal>";
1446 cs.line = 0;
1447 cs.config = c;
1448 #if HAVE_PCRE_H
1449 if(!c->namepart.n) {
1450 whoami = find("namepart");
1451 for(n = 0; n < NNAMEPART; ++n)
1452 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1453 }
1454 if(!c->transform.n) {
1455 whoami = find("transform");
1456 for(n = 0; n < NTRANSFORM; ++n)
1457 set_transform(&cs, whoami, 5, (char **)transform[n]);
1458 }
1459 #endif
1460 if(!c->api) {
1461 if(c->speaker_command)
1462 c->api = xstrdup("command");
1463 else if(c->broadcast.af != -1)
1464 c->api = xstrdup("rtp");
1465 else if(config_uaudio_apis)
1466 c->api = xstrdup(uaudio_default(config_uaudio_apis,
1467 UAUDIO_API_SERVER)->name);
1468 else
1469 c->api = xstrdup("<none>");
1470 }
1471 if(!strcmp(c->api, "network"))
1472 c->api = xstrdup("rtp");
1473 if(server) {
1474 if(!strcmp(c->api, "command") && !c->speaker_command)
1475 disorder_fatal(0, "'api command' but speaker_command is not set");
1476 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1477 disorder_fatal(0, "'api rtp' but broadcast is not set");
1478 }
1479 /* Override sample format */
1480 if(!strcmp(c->api, "rtp")) {
1481 c->sample_format.rate = 44100;
1482 c->sample_format.channels = 2;
1483 c->sample_format.bits = 16;
1484 c->sample_format.endian = ENDIAN_NATIVE;
1485 }
1486 if(!strcmp(c->api, "coreaudio")) {
1487 c->sample_format.rate = 44100;
1488 c->sample_format.channels = 2;
1489 c->sample_format.bits = 16;
1490 c->sample_format.endian = ENDIAN_NATIVE;
1491 }
1492 if(!c->default_rights) {
1493 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1494 |RIGHT_MOVE__MASK
1495 |RIGHT_SCRATCH__MASK
1496 |RIGHT_REMOVE__MASK);
1497 r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
1498 c->default_rights = rights_string(r);
1499 }
1500 }
1501
1502 /** @brief (Re-)read the config file
1503 * @param server If set, do extra checking
1504 * @param oldconfig Old configuration for compatibility check
1505 * @return 0 on success, non-0 on error
1506 *
1507 * If @p oldconfig is set, then certain compatibility checks are done between
1508 * the old and new configurations.
1509 */
1510 int config_read(int server,
1511 const struct config *oldconfig) {
1512 struct config *c;
1513 char *privconf;
1514 struct passwd *pw;
1515
1516 set_configfile();
1517 c = config_default();
1518 /* standalone Disobedience installs might not have a global config file */
1519 if(access(configfile, F_OK) == 0)
1520 if(config_include(c, configfile))
1521 return -1;
1522 /* if we can read the private config file, do */
1523 if((privconf = config_private())
1524 && access(privconf, R_OK) == 0
1525 && config_include(c, privconf))
1526 return -1;
1527 xfree(privconf);
1528 /* if there's a per-user system config file for this user, read it */
1529 if(config_per_user) {
1530 if(!(pw = getpwuid(getuid())))
1531 disorder_fatal(0, "cannot determine our username");
1532 if((privconf = config_usersysconf(pw))
1533 && access(privconf, F_OK) == 0
1534 && config_include(c, privconf))
1535 return -1;
1536 xfree(privconf);
1537 /* if we have a password file, read it */
1538 if((privconf = config_userconf(0, pw))
1539 && access(privconf, F_OK) == 0
1540 && config_include(c, privconf))
1541 return -1;
1542 xfree(privconf);
1543 }
1544 /* install default namepart and transform settings */
1545 config_postdefaults(c, server);
1546 if(oldconfig) {
1547 int failed = 0;
1548 if(strcmp(c->home, oldconfig->home)) {
1549 disorder_error(0, "'home' cannot be changed without a restart");
1550 failed = 1;
1551 }
1552 if(strcmp(c->alias, oldconfig->alias)) {
1553 disorder_error(0, "'alias' cannot be changed without a restart");
1554 failed = 1;
1555 }
1556 if(strcmp(c->user, oldconfig->user)) {
1557 disorder_error(0, "'user' cannot be changed without a restart");
1558 failed = 1;
1559 }
1560 if(c->nice_speaker != oldconfig->nice_speaker) {
1561 disorder_error(0, "'nice_speaker' cannot be changed without a restart");
1562 /* ...but we accept the new config anyway */
1563 }
1564 if(c->nice_server != oldconfig->nice_server) {
1565 disorder_error(0, "'nice_server' cannot be changed without a restart");
1566 /* ...but we accept the new config anyway */
1567 }
1568 #if HAVE_PCRE_H
1569 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1570 disorder_error(0, "'namepart' settings cannot be changed without a restart");
1571 failed = 1;
1572 }
1573 #endif
1574 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1575 disorder_error(0, "'stopword' settings cannot be changed without a restart");
1576 failed = 1;
1577 }
1578 if(failed) {
1579 disorder_error(0, "not installing incompatible new configuration");
1580 return -1;
1581 }
1582 }
1583 /* everything is good so we shall use the new config */
1584 config_free(config);
1585 /* warn about obsolete directives */
1586 config = c;
1587 return 0;
1588 }
1589
1590 /** @brief Return the path to the private configuration file */
1591 char *config_private(void) {
1592 char *s;
1593
1594 set_configfile();
1595 byte_xasprintf(&s, "%s.private", configfile);
1596 return s;
1597 }
1598
1599 /** @brief Return the path to user's personal configuration file */
1600 char *config_userconf(const char *home, const struct passwd *pw) {
1601 char *s;
1602
1603 if(!home && !pw && !(pw = getpwuid(getuid())))
1604 disorder_fatal(0, "cannot determine our username");
1605 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1606 return s;
1607 }
1608
1609 /** @brief Return the path to user-specific system configuration */
1610 char *config_usersysconf(const struct passwd *pw) {
1611 char *s;
1612
1613 set_configfile();
1614 if(!strchr(pw->pw_name, '/')) {
1615 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1616 return s;
1617 } else
1618 return 0;
1619 }
1620
1621 /** @brief Get a filename within the home directory
1622 * @param name Relative name
1623 * @return Full path
1624 */
1625 char *config_get_file(const char *name) {
1626 return config_get_file2(config, name);
1627 }
1628
1629 /** @brief Order two stringlists
1630 * @param a First stringlist
1631 * @param b Second stringlist
1632 * @return <0, 0 or >0 if a<b, a=b or a>b
1633 */
1634 static int stringlist_compare(const struct stringlist *a,
1635 const struct stringlist *b) {
1636 int n = 0, c;
1637
1638 while(n < a->n && n < b->n) {
1639 if((c = strcmp(a->s[n], b->s[n])))
1640 return c;
1641 ++n;
1642 }
1643 if(a->n < b->n)
1644 return -1;
1645 else if(a->n > b->n)
1646 return 1;
1647 else
1648 return 0;
1649 }
1650
1651 #if HAVE_PCRE_H
1652 /** @brief Order two namepart definitions
1653 * @param a First namepart definition
1654 * @param b Second namepart definition
1655 * @return <0, 0 or >0 if a<b, a=b or a>b
1656 */
1657 static int namepart_compare(const struct namepart *a,
1658 const struct namepart *b) {
1659 int c;
1660
1661 if((c = strcmp(a->part, b->part)))
1662 return c;
1663 if((c = strcmp(a->res, b->res)))
1664 return c;
1665 if((c = strcmp(a->replace, b->replace)))
1666 return c;
1667 if((c = strcmp(a->context, b->context)))
1668 return c;
1669 if(a->reflags > b->reflags)
1670 return 1;
1671 if(a->reflags < b->reflags)
1672 return -1;
1673 return 0;
1674 }
1675
1676 /** @brief Order two lists of namepart definitions
1677 * @param a First list of namepart definitions
1678 * @param b Second list of namepart definitions
1679 * @return <0, 0 or >0 if a<b, a=b or a>b
1680 */
1681 static int namepartlist_compare(const struct namepartlist *a,
1682 const struct namepartlist *b) {
1683 int n = 0, c;
1684
1685 while(n < a->n && n < b->n) {
1686 if((c = namepart_compare(&a->s[n], &b->s[n])))
1687 return c;
1688 ++n;
1689 }
1690 if(a->n > b->n)
1691 return 1;
1692 else if(a->n < b->n)
1693 return -1;
1694 else
1695 return 0;
1696 }
1697 #endif
1698
1699 /** @brief Verify configuration table.
1700 * @return The number of problems found
1701 */
1702 int config_verify(void) {
1703 int fails = 0;
1704 size_t n;
1705 for(n = 1; n < sizeof conf / sizeof *conf; ++n)
1706 if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1707 fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1708 ++fails;
1709 }
1710 return fails;
1711 }
1712
1713 /*
1714 Local Variables:
1715 c-basic-offset:2
1716 comment-column:40
1717 fill-column:79
1718 End:
1719 */