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