chiark / gitweb /
doxygen: remove obsolete config file entries
[disorder] / lib / configuration.c
... / ...
CommitLineData
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2004-2011, 2013 Richard Kettlewell
4 * Portions copyright (C) 2007 Mark Wooding
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19/** @file lib/configuration.c
20 * @brief Configuration file support
21 */
22
23#include "common.h"
24
25#include <errno.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#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_mode), &type_string, validate_any },
1059 { C(rtp_verbose), &type_boolean, validate_any },
1060 { C(sample_format), &type_sample_format, validate_sample_format },
1061 { C(scratch), &type_string_accum, validate_isreg },
1062 { C(sendmail), &type_string, validate_isabspath },
1063 { C(short_display), &type_integer, validate_positive },
1064 { C(signal), &type_signal, validate_any },
1065 { C(smtp_server), &type_string, validate_any },
1066 { C(sox_generation), &type_integer, validate_non_negative },
1067 { C2(speaker_backend, api), &type_string, validate_backend },
1068 { C(speaker_command), &type_string, validate_any },
1069 { C(stopword), &type_string_accum, validate_any },
1070 { C(templates), &type_string_accum, validate_isdir },
1071 { C(tracklength), &type_stringlist_accum, validate_tracklength },
1072 { C(transform), &type_transform, validate_any },
1073 { C(url), &type_string, validate_url },
1074 { C(user), &type_string, validate_isauser },
1075 { C(username), &type_string, validate_any },
1076};
1077
1078/** @brief Find a configuration item's definition by key */
1079static const struct conf *find(const char *key) {
1080 int n;
1081
1082 if((n = TABLE_FIND(conf, name, key)) < 0)
1083 return 0;
1084 return &conf[n];
1085}
1086
1087/** @brief Set a new configuration value
1088 * @param cs Configuration state
1089 * @param nvec Length of @p vec
1090 * @param vec Name and new value
1091 * @return 0 on success, non-0 on error.
1092 *
1093 * @c vec[0] is the name, the rest is the value.
1094 */
1095static int config_set(const struct config_state *cs,
1096 int nvec, char **vec) {
1097 const struct conf *which;
1098
1099 D(("config_set %s", vec[0]));
1100 if(!(which = find(vec[0]))) {
1101 disorder_error(0, "%s:%d: unknown configuration key '%s'",
1102 cs->path, cs->line, vec[0]);
1103 return -1;
1104 }
1105 return (which->validate(cs, nvec - 1, vec + 1)
1106 || which->type->set(cs, which, nvec - 1, vec + 1));
1107}
1108
1109/** @brief Set a configuration item from parameters
1110 * @param cs Configuration state
1111 * @param which Item to set
1112 * @param ... Value as strings, terminated by (char *)NULL
1113 * @return 0 on success, non-0 on error
1114 */
1115static int config_set_args(const struct config_state *cs,
1116 const char *which, ...) {
1117 va_list ap;
1118 struct vector v[1];
1119 char *s;
1120
1121 vector_init(v);
1122 vector_append(v, (char *)which);
1123 va_start(ap, which);
1124 while((s = va_arg(ap, char *)))
1125 vector_append(v, s);
1126 va_end(ap);
1127 vector_terminate(v);
1128 int rc = config_set(cs, v->nvec, v->vec);
1129 xfree(v->vec);
1130 return rc;
1131}
1132
1133/** @brief Error callback used by config_include()
1134 * @param msg Error message
1135 * @param u User data (@ref config_state)
1136 */
1137static void config_error(const char *msg, void *u) {
1138 const struct config_state *cs = u;
1139
1140 disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
1141}
1142
1143/** @brief Include a file by name
1144 * @param c Configuration to update
1145 * @param path Path to read
1146 * @return 0 on success, non-0 on error
1147 */
1148static int config_include(struct config *c, const char *path) {
1149 FILE *fp;
1150 char *buffer, *inputbuffer, **vec;
1151 int n, ret = 0;
1152 struct config_state cs;
1153
1154 cs.path = path;
1155 cs.line = 0;
1156 cs.config = c;
1157 D(("%s: reading configuration", path));
1158 if(!(fp = fopen(path, "r"))) {
1159 disorder_error(errno, "error opening %s", path);
1160 return -1;
1161 }
1162 while(!inputline(path, fp, &inputbuffer, '\n')) {
1163 ++cs.line;
1164 if(!(buffer = mb2utf8(inputbuffer))) {
1165 disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1166 ret = -1;
1167 xfree(inputbuffer);
1168 continue;
1169 }
1170 xfree(inputbuffer);
1171 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1172 config_error, &cs))) {
1173 ret = -1;
1174 xfree(buffer);
1175 continue;
1176 }
1177 if(n) {
1178 /* 'include' is special-cased */
1179 if(!strcmp(vec[0], "include")) {
1180 if(n != 2) {
1181 disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1182 ret = -1;
1183 } else
1184 config_include(c, vec[1]);
1185 } else
1186 ret |= config_set(&cs, n, vec);
1187 }
1188 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1189 xfree(vec);
1190 xfree(buffer);
1191 }
1192 if(ferror(fp)) {
1193 disorder_error(errno, "error reading %s", path);
1194 ret = -1;
1195 }
1196 fclose(fp);
1197 return ret;
1198}
1199
1200/** @brief Default stopword setting */
1201static const char *const default_stopwords[] = {
1202 "stopword",
1203
1204 "01",
1205 "02",
1206 "03",
1207 "04",
1208 "05",
1209 "06",
1210 "07",
1211 "08",
1212 "09",
1213 "1",
1214 "10",
1215 "11",
1216 "12",
1217 "13",
1218 "14",
1219 "15",
1220 "16",
1221 "17",
1222 "18",
1223 "19",
1224 "2",
1225 "20",
1226 "21",
1227 "22",
1228 "23",
1229 "24",
1230 "25",
1231 "26",
1232 "27",
1233 "28",
1234 "29",
1235 "3",
1236 "30",
1237 "4",
1238 "5",
1239 "6",
1240 "7",
1241 "8",
1242 "9",
1243 "a",
1244 "am",
1245 "an",
1246 "and",
1247 "as",
1248 "for",
1249 "i",
1250 "im",
1251 "in",
1252 "is",
1253 "of",
1254 "on",
1255 "the",
1256 "to",
1257 "too",
1258 "we",
1259};
1260#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1261
1262/** @brief Default player patterns */
1263static const char *const default_players[] = {
1264 "*.ogg",
1265 "*.flac",
1266 "*.mp3",
1267 "*.wav",
1268};
1269#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1270
1271/** @brief Make a new default configuration
1272 * @return New configuration
1273 */
1274static struct config *config_default(void) {
1275 struct config *c = xmalloc(sizeof *c);
1276 const char *logname;
1277 struct passwd *pw;
1278 struct config_state cs;
1279 size_t n;
1280
1281 cs.path = "<internal>";
1282 cs.line = 0;
1283 cs.config = c;
1284 /* Strings had better be xstrdup'd as they will get freed at some point. */
1285 c->history = 60;
1286 c->home = xstrdup(pkgstatedir);
1287 if(!(pw = getpwuid(getuid())))
1288 disorder_fatal(0, "cannot determine our username");
1289 logname = pw->pw_name;
1290 c->username = xstrdup(logname);
1291 c->refresh = 15;
1292 c->refresh_min = 1;
1293 c->signal = SIGKILL;
1294 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1295 c->device = xstrdup("default");
1296 c->nice_rescan = 10;
1297 c->speaker_command = 0;
1298 c->sample_format.bits = 16;
1299 c->sample_format.rate = 44100;
1300 c->sample_format.channels = 2;
1301 c->sample_format.endian = ENDIAN_NATIVE;
1302 c->queue_pad = 10;
1303 c->replay_min = 8 * 3600;
1304 c->api = NULL;
1305 c->multicast_ttl = 1;
1306 c->multicast_loop = 1;
1307 c->authorization_algorithm = xstrdup("sha1");
1308 c->noticed_history = 31;
1309 c->short_display = 32;
1310 c->mixer = 0;
1311 c->channel = 0;
1312 c->dbversion = 2;
1313 c->cookie_login_lifetime = 86400;
1314 c->cookie_key_lifetime = 86400 * 7;
1315 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1316 c->sendmail = xstrdup(sendmail_binary);
1317 c->smtp_server = xstrdup("127.0.0.1");
1318 c->new_max = 100;
1319 c->reminder_interval = 600; /* 10m */
1320 c->new_bias_age = 7 * 86400; /* 1 week */
1321 c->new_bias = 4500000; /* 50 times the base weight */
1322 c->sox_generation = DEFAULT_SOX_GENERATION;
1323 c->playlist_max = INT_MAX; /* effectively no limit */
1324 c->playlist_lock_timeout = 10; /* 10s */
1325 c->mount_rescan = 1;
1326 /* Default stopwords */
1327 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1328 exit(1);
1329 /* Default player configuration */
1330 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1331 if(config_set_args(&cs, "player",
1332 default_players[n], "execraw", "disorder-decode", (char *)0))
1333 exit(1);
1334 if(config_set_args(&cs, "tracklength",
1335 default_players[n], "disorder-tracklength", (char *)0))
1336 exit(1);
1337 }
1338 c->broadcast.af = -1;
1339 c->broadcast_from.af = -1;
1340 c->listen.af = -1;
1341 c->connect.af = -1;
1342 c->rtp_mode = xstrdup("auto");
1343 return c;
1344}
1345
1346/** @brief Construct a filename
1347 * @param c Configuration
1348 * @param name Base filename
1349 * @return Full filename
1350 *
1351 * Usually use config_get_file() instead.
1352 */
1353char *config_get_file2(struct config *c, const char *name) {
1354 char *s;
1355
1356 byte_xasprintf(&s, "%s/%s", c->home, name);
1357 return s;
1358}
1359
1360/** @brief Set the default configuration file */
1361static void set_configfile(void) {
1362 if(!configfile)
1363 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1364}
1365
1366/** @brief Free a configuration object
1367 * @param c Configuration to free
1368 *
1369 * @p c is indeterminate after this function is called.
1370 */
1371void config_free(struct config *c) {
1372 int n;
1373
1374 if(c) {
1375 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1376 conf[n].type->free(c, &conf[n]);
1377 for(n = 0; n < c->nparts; ++n)
1378 xfree(c->parts[n]);
1379 xfree(c->parts);
1380 xfree(c);
1381 }
1382}
1383
1384/** @brief Set post-parse defaults
1385 * @param c Configuration to update
1386 * @param server True when running in the server
1387 *
1388 * If @p server is set then certain parts of the configuration are more
1389 * strictly validated.
1390 */
1391static void config_postdefaults(struct config *c,
1392 int server) {
1393 struct config_state cs;
1394 const struct conf *whoami;
1395 int n;
1396
1397 static const char *namepart[][4] = {
1398 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1399 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1400 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1401 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1402 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1403 };
1404#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1405
1406 static const char *transform[][5] = {
1407 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1408 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1409 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1410 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1411 { "dir", "[[:punct:]]", "", "sort", "g", }
1412 };
1413#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1414
1415 cs.path = "<internal>";
1416 cs.line = 0;
1417 cs.config = c;
1418 if(!c->namepart.n) {
1419 whoami = find("namepart");
1420 for(n = 0; n < NNAMEPART; ++n)
1421 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1422 }
1423 if(!c->transform.n) {
1424 whoami = find("transform");
1425 for(n = 0; n < NTRANSFORM; ++n)
1426 set_transform(&cs, whoami, 5, (char **)transform[n]);
1427 }
1428 if(!c->api) {
1429 if(c->speaker_command)
1430 c->api = xstrdup("command");
1431 else if(c->broadcast.af != -1)
1432 c->api = xstrdup("rtp");
1433 else if(config_uaudio_apis)
1434 c->api = xstrdup(uaudio_default(config_uaudio_apis,
1435 UAUDIO_API_SERVER)->name);
1436 else
1437 c->api = xstrdup("<none>");
1438 }
1439 if(!strcmp(c->api, "network"))
1440 c->api = xstrdup("rtp");
1441 if(server) {
1442 if(!strcmp(c->api, "command") && !c->speaker_command)
1443 disorder_fatal(0, "'api command' but speaker_command is not set");
1444 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1445 disorder_fatal(0, "'api rtp' but broadcast is not set");
1446 }
1447 /* Override sample format */
1448 if(!strcmp(c->api, "rtp")) {
1449 c->sample_format.rate = 44100;
1450 c->sample_format.channels = 2;
1451 c->sample_format.bits = 16;
1452 c->sample_format.endian = ENDIAN_NATIVE;
1453 }
1454 if(!strcmp(c->api, "coreaudio")) {
1455 c->sample_format.rate = 44100;
1456 c->sample_format.channels = 2;
1457 c->sample_format.bits = 16;
1458 c->sample_format.endian = ENDIAN_NATIVE;
1459 }
1460 if(!c->default_rights) {
1461 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1462 |RIGHT_MOVE__MASK
1463 |RIGHT_SCRATCH__MASK
1464 |RIGHT_REMOVE__MASK);
1465 r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
1466 c->default_rights = rights_string(r);
1467 }
1468}
1469
1470/** @brief (Re-)read the config file
1471 * @param server If set, do extra checking
1472 * @param oldconfig Old configuration for compatibility check
1473 * @return 0 on success, non-0 on error
1474 *
1475 * If @p oldconfig is set, then certain compatibility checks are done between
1476 * the old and new configurations.
1477 */
1478int config_read(int server,
1479 const struct config *oldconfig) {
1480 struct config *c;
1481 char *privconf;
1482 struct passwd *pw;
1483
1484 set_configfile();
1485 c = config_default();
1486 /* standalone Disobedience installs might not have a global config file */
1487 if(access(configfile, F_OK) == 0)
1488 if(config_include(c, configfile))
1489 return -1;
1490 /* if we can read the private config file, do */
1491 if((privconf = config_private())
1492 && access(privconf, R_OK) == 0
1493 && config_include(c, privconf))
1494 return -1;
1495 xfree(privconf);
1496 /* if there's a per-user system config file for this user, read it */
1497 if(config_per_user) {
1498 if(!(pw = getpwuid(getuid())))
1499 disorder_fatal(0, "cannot determine our username");
1500 if((privconf = config_usersysconf(pw))
1501 && access(privconf, F_OK) == 0
1502 && config_include(c, privconf))
1503 return -1;
1504 xfree(privconf);
1505 /* if we have a password file, read it */
1506 if((privconf = config_userconf(0, pw))
1507 && access(privconf, F_OK) == 0
1508 && config_include(c, privconf))
1509 return -1;
1510 xfree(privconf);
1511 }
1512 /* install default namepart and transform settings */
1513 config_postdefaults(c, server);
1514 if(oldconfig) {
1515 int failed = 0;
1516 if(strcmp(c->home, oldconfig->home)) {
1517 disorder_error(0, "'home' cannot be changed without a restart");
1518 failed = 1;
1519 }
1520 if(strcmp(c->alias, oldconfig->alias)) {
1521 disorder_error(0, "'alias' cannot be changed without a restart");
1522 failed = 1;
1523 }
1524 if(strcmp(c->user, oldconfig->user)) {
1525 disorder_error(0, "'user' cannot be changed without a restart");
1526 failed = 1;
1527 }
1528 if(c->nice_speaker != oldconfig->nice_speaker) {
1529 disorder_error(0, "'nice_speaker' cannot be changed without a restart");
1530 /* ...but we accept the new config anyway */
1531 }
1532 if(c->nice_server != oldconfig->nice_server) {
1533 disorder_error(0, "'nice_server' cannot be changed without a restart");
1534 /* ...but we accept the new config anyway */
1535 }
1536 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1537 disorder_error(0, "'namepart' settings cannot be changed without a restart");
1538 failed = 1;
1539 }
1540 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1541 disorder_error(0, "'stopword' settings cannot be changed without a restart");
1542 failed = 1;
1543 }
1544 if(failed) {
1545 disorder_error(0, "not installing incompatible new configuration");
1546 return -1;
1547 }
1548 }
1549 /* everything is good so we shall use the new config */
1550 config_free(config);
1551 /* warn about obsolete directives */
1552 config = c;
1553 return 0;
1554}
1555
1556/** @brief Return the path to the private configuration file */
1557char *config_private(void) {
1558 char *s;
1559
1560 set_configfile();
1561 byte_xasprintf(&s, "%s.private", configfile);
1562 return s;
1563}
1564
1565/** @brief Return the path to user's personal configuration file */
1566char *config_userconf(const char *home, const struct passwd *pw) {
1567 char *s;
1568
1569 if(!home && !pw && !(pw = getpwuid(getuid())))
1570 disorder_fatal(0, "cannot determine our username");
1571 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1572 return s;
1573}
1574
1575/** @brief Return the path to user-specific system configuration */
1576char *config_usersysconf(const struct passwd *pw) {
1577 char *s;
1578
1579 set_configfile();
1580 if(!strchr(pw->pw_name, '/')) {
1581 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1582 return s;
1583 } else
1584 return 0;
1585}
1586
1587/** @brief Get a filename within the home directory
1588 * @param name Relative name
1589 * @return Full path
1590 */
1591char *config_get_file(const char *name) {
1592 return config_get_file2(config, name);
1593}
1594
1595/** @brief Order two stringlists
1596 * @param a First stringlist
1597 * @param b Second stringlist
1598 * @return <0, 0 or >0 if a<b, a=b or a>b
1599 */
1600static int stringlist_compare(const struct stringlist *a,
1601 const struct stringlist *b) {
1602 int n = 0, c;
1603
1604 while(n < a->n && n < b->n) {
1605 if((c = strcmp(a->s[n], b->s[n])))
1606 return c;
1607 ++n;
1608 }
1609 if(a->n < b->n)
1610 return -1;
1611 else if(a->n > b->n)
1612 return 1;
1613 else
1614 return 0;
1615}
1616
1617/** @brief Order two namepart definitions
1618 * @param a First namepart definition
1619 * @param b Second namepart definition
1620 * @return <0, 0 or >0 if a<b, a=b or a>b
1621 */
1622static int namepart_compare(const struct namepart *a,
1623 const struct namepart *b) {
1624 int c;
1625
1626 if((c = strcmp(a->part, b->part)))
1627 return c;
1628 if((c = strcmp(a->res, b->res)))
1629 return c;
1630 if((c = strcmp(a->replace, b->replace)))
1631 return c;
1632 if((c = strcmp(a->context, b->context)))
1633 return c;
1634 if(a->reflags > b->reflags)
1635 return 1;
1636 if(a->reflags < b->reflags)
1637 return -1;
1638 return 0;
1639}
1640
1641/** @brief Order two lists of namepart definitions
1642 * @param a First list of namepart definitions
1643 * @param b Second list of namepart definitions
1644 * @return <0, 0 or >0 if a<b, a=b or a>b
1645 */
1646static int namepartlist_compare(const struct namepartlist *a,
1647 const struct namepartlist *b) {
1648 int n = 0, c;
1649
1650 while(n < a->n && n < b->n) {
1651 if((c = namepart_compare(&a->s[n], &b->s[n])))
1652 return c;
1653 ++n;
1654 }
1655 if(a->n > b->n)
1656 return 1;
1657 else if(a->n < b->n)
1658 return -1;
1659 else
1660 return 0;
1661}
1662
1663/** @brief Verify configuration table.
1664 * @return The number of problems found
1665*/
1666int config_verify(void) {
1667 int fails = 0;
1668 for(size_t n = 1; n < sizeof conf / sizeof *conf; ++n)
1669 if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1670 fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1671 ++fails;
1672 }
1673 return fails;
1674}
1675
1676/*
1677Local Variables:
1678c-basic-offset:2
1679comment-column:40
1680fill-column:79
1681End:
1682*/