chiark / gitweb /
autogen.sh: use /bin/sh
[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(config_uaudio_apis[0]->name);
1433 else
1434 c->api = xstrdup("<none>");
1435 }
1436 if(!strcmp(c->api, "network"))
1437 c->api = xstrdup("rtp");
1438 if(server) {
1439 if(!strcmp(c->api, "command") && !c->speaker_command)
1440 disorder_fatal(0, "'api command' but speaker_command is not set");
1441 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1442 disorder_fatal(0, "'api rtp' but broadcast is not set");
1443 }
1444 /* Override sample format */
1445 if(!strcmp(c->api, "rtp")) {
1446 c->sample_format.rate = 44100;
1447 c->sample_format.channels = 2;
1448 c->sample_format.bits = 16;
1449 c->sample_format.endian = ENDIAN_NATIVE;
1450 }
1451 if(!strcmp(c->api, "coreaudio")) {
1452 c->sample_format.rate = 44100;
1453 c->sample_format.channels = 2;
1454 c->sample_format.bits = 16;
1455 c->sample_format.endian = ENDIAN_NATIVE;
1456 }
1457 if(!c->default_rights) {
1458 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1459 |RIGHT_MOVE__MASK
1460 |RIGHT_SCRATCH__MASK
1461 |RIGHT_REMOVE__MASK);
1462 r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
1463 c->default_rights = rights_string(r);
1464 }
1465}
1466
1467/** @brief (Re-)read the config file
1468 * @param server If set, do extra checking
1469 * @param oldconfig Old configuration for compatibility check
1470 * @return 0 on success, non-0 on error
1471 *
1472 * If @p oldconfig is set, then certain compatibility checks are done between
1473 * the old and new configurations.
1474 */
1475int config_read(int server,
1476 const struct config *oldconfig) {
1477 struct config *c;
1478 char *privconf;
1479 struct passwd *pw;
1480
1481 set_configfile();
1482 c = config_default();
1483 /* standalone Disobedience installs might not have a global config file */
1484 if(access(configfile, F_OK) == 0)
1485 if(config_include(c, configfile))
1486 return -1;
1487 /* if we can read the private config file, do */
1488 if((privconf = config_private())
1489 && access(privconf, R_OK) == 0
1490 && config_include(c, privconf))
1491 return -1;
1492 xfree(privconf);
1493 /* if there's a per-user system config file for this user, read it */
1494 if(config_per_user) {
1495 if(!(pw = getpwuid(getuid())))
1496 disorder_fatal(0, "cannot determine our username");
1497 if((privconf = config_usersysconf(pw))
1498 && access(privconf, F_OK) == 0
1499 && config_include(c, privconf))
1500 return -1;
1501 xfree(privconf);
1502 /* if we have a password file, read it */
1503 if((privconf = config_userconf(0, pw))
1504 && access(privconf, F_OK) == 0
1505 && config_include(c, privconf))
1506 return -1;
1507 xfree(privconf);
1508 }
1509 /* install default namepart and transform settings */
1510 config_postdefaults(c, server);
1511 if(oldconfig) {
1512 int failed = 0;
1513 if(strcmp(c->home, oldconfig->home)) {
1514 disorder_error(0, "'home' cannot be changed without a restart");
1515 failed = 1;
1516 }
1517 if(strcmp(c->alias, oldconfig->alias)) {
1518 disorder_error(0, "'alias' cannot be changed without a restart");
1519 failed = 1;
1520 }
1521 if(strcmp(c->user, oldconfig->user)) {
1522 disorder_error(0, "'user' cannot be changed without a restart");
1523 failed = 1;
1524 }
1525 if(c->nice_speaker != oldconfig->nice_speaker) {
1526 disorder_error(0, "'nice_speaker' cannot be changed without a restart");
1527 /* ...but we accept the new config anyway */
1528 }
1529 if(c->nice_server != oldconfig->nice_server) {
1530 disorder_error(0, "'nice_server' cannot be changed without a restart");
1531 /* ...but we accept the new config anyway */
1532 }
1533 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1534 disorder_error(0, "'namepart' settings cannot be changed without a restart");
1535 failed = 1;
1536 }
1537 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1538 disorder_error(0, "'stopword' settings cannot be changed without a restart");
1539 failed = 1;
1540 }
1541 if(failed) {
1542 disorder_error(0, "not installing incompatible new configuration");
1543 return -1;
1544 }
1545 }
1546 /* everything is good so we shall use the new config */
1547 config_free(config);
1548 /* warn about obsolete directives */
1549 config = c;
1550 return 0;
1551}
1552
1553/** @brief Return the path to the private configuration file */
1554char *config_private(void) {
1555 char *s;
1556
1557 set_configfile();
1558 byte_xasprintf(&s, "%s.private", configfile);
1559 return s;
1560}
1561
1562/** @brief Return the path to user's personal configuration file */
1563char *config_userconf(const char *home, const struct passwd *pw) {
1564 char *s;
1565
1566 if(!home && !pw && !(pw = getpwuid(getuid())))
1567 disorder_fatal(0, "cannot determine our username");
1568 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1569 return s;
1570}
1571
1572/** @brief Return the path to user-specific system configuration */
1573char *config_usersysconf(const struct passwd *pw) {
1574 char *s;
1575
1576 set_configfile();
1577 if(!strchr(pw->pw_name, '/')) {
1578 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1579 return s;
1580 } else
1581 return 0;
1582}
1583
1584/** @brief Get a filename within the home directory
1585 * @param name Relative name
1586 * @return Full path
1587 */
1588char *config_get_file(const char *name) {
1589 return config_get_file2(config, name);
1590}
1591
1592/** @brief Order two stringlists
1593 * @param a First stringlist
1594 * @param b Second stringlist
1595 * @return <0, 0 or >0 if a<b, a=b or a>b
1596 */
1597static int stringlist_compare(const struct stringlist *a,
1598 const struct stringlist *b) {
1599 int n = 0, c;
1600
1601 while(n < a->n && n < b->n) {
1602 if((c = strcmp(a->s[n], b->s[n])))
1603 return c;
1604 ++n;
1605 }
1606 if(a->n < b->n)
1607 return -1;
1608 else if(a->n > b->n)
1609 return 1;
1610 else
1611 return 0;
1612}
1613
1614/** @brief Order two namepart definitions
1615 * @param a First namepart definition
1616 * @param b Second namepart definition
1617 * @return <0, 0 or >0 if a<b, a=b or a>b
1618 */
1619static int namepart_compare(const struct namepart *a,
1620 const struct namepart *b) {
1621 int c;
1622
1623 if((c = strcmp(a->part, b->part)))
1624 return c;
1625 if((c = strcmp(a->res, b->res)))
1626 return c;
1627 if((c = strcmp(a->replace, b->replace)))
1628 return c;
1629 if((c = strcmp(a->context, b->context)))
1630 return c;
1631 if(a->reflags > b->reflags)
1632 return 1;
1633 if(a->reflags < b->reflags)
1634 return -1;
1635 return 0;
1636}
1637
1638/** @brief Order two lists of namepart definitions
1639 * @param a First list of namepart definitions
1640 * @param b Second list of namepart definitions
1641 * @return <0, 0 or >0 if a<b, a=b or a>b
1642 */
1643static int namepartlist_compare(const struct namepartlist *a,
1644 const struct namepartlist *b) {
1645 int n = 0, c;
1646
1647 while(n < a->n && n < b->n) {
1648 if((c = namepart_compare(&a->s[n], &b->s[n])))
1649 return c;
1650 ++n;
1651 }
1652 if(a->n > b->n)
1653 return 1;
1654 else if(a->n < b->n)
1655 return -1;
1656 else
1657 return 0;
1658}
1659
1660/** @brief Verify configuration table.
1661 * @return The number of problems found
1662*/
1663int config_verify(void) {
1664 int fails = 0;
1665 for(size_t n = 1; n < sizeof conf / sizeof *conf; ++n)
1666 if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1667 fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1668 ++fails;
1669 }
1670 return fails;
1671}
1672
1673/*
1674Local Variables:
1675c-basic-offset:2
1676comment-column:40
1677fill-column:79
1678End:
1679*/