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