chiark / gitweb /
log: more general error message formatting
[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 type_namepart = { set_namepart, free_namepartlist },
636 type_transform = { set_transform, free_transformlist },
637 type_netaddress = { set_netaddress, free_netaddress },
638 type_rights = { set_rights, free_string };
639
640/* specific validation routine */
641
642/** @brief Perform a test on a filename
643 * @param test Test function to call on mode bits
644 * @param what Type of file sought
645 *
646 * If @p test returns 0 then the file is not a @p what and an error
647 * is reported and -1 is returned.
648 */
649#define VALIDATE_FILE(test, what) do { \
650 struct stat sb; \
651 int n; \
652 \
653 for(n = 0; n < nvec; ++n) { \
654 if(stat(vec[n], &sb) < 0) { \
655 disorder_error(errno, "%s:%d: %s", \
656 cs->path, cs->line, vec[n]); \
657 return -1; \
658 } \
659 if(!test(sb.st_mode)) { \
660 disorder_error(0, "%s:%d: %s is not a %s", \
661 cs->path, cs->line, vec[n], what); \
662 return -1; \
663 } \
664 } \
665} while(0)
666
667/** @brief Validate an absolute path
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_isabspath(const struct config_state *cs,
674 int nvec, char **vec) {
675 int n;
676
677 for(n = 0; n < nvec; ++n)
678 if(vec[n][0] != '/') {
679 disorder_error(errno, "%s:%d: %s: not an absolute path",
680 cs->path, cs->line, vec[n]);
681 return -1;
682 }
683 return 0;
684}
685
686/** @brief Validate an existing directory
687 * @param cs Configuration state
688 * @param nvec Length of (proposed) new value
689 * @param vec Elements of new value
690 * @return 0 on success, non-0 on error
691 */
692static int validate_isdir(const struct config_state *cs,
693 int nvec, char **vec) {
694 VALIDATE_FILE(S_ISDIR, "directory");
695 return 0;
696}
697
698/** @brief Validate an existing regular file
699 * @param cs Configuration state
700 * @param nvec Length of (proposed) new value
701 * @param vec Elements of new value
702 * @return 0 on success, non-0 on error
703 */
704static int validate_isreg(const struct config_state *cs,
705 int nvec, char **vec) {
706 VALIDATE_FILE(S_ISREG, "regular file");
707 return 0;
708}
709
710/** @brief Validate a player pattern
711 * @param cs Configuration state
712 * @param nvec Length of (proposed) new value
713 * @param vec Elements of new value
714 * @return 0 on success, non-0 on error
715 */
716static int validate_player(const struct config_state *cs,
717 int nvec,
718 char attribute((unused)) **vec) {
719 if(nvec && nvec < 2) {
720 disorder_error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
721 cs->path, cs->line);
722 return -1;
723 }
724 return 0;
725}
726
727/** @brief Validate a track length pattern
728 * @param cs Configuration state
729 * @param nvec Length of (proposed) new value
730 * @param vec Elements of new value
731 * @return 0 on success, non-0 on error
732 */
733static int validate_tracklength(const struct config_state *cs,
734 int nvec,
735 char attribute((unused)) **vec) {
736 if(nvec && nvec < 2) {
737 disorder_error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
738 cs->path, cs->line);
739 return -1;
740 }
741 return 0;
742}
743
744/** @brief Validate a non-negative (@c long) integer
745 * @param cs Configuration state
746 * @param nvec Length of (proposed) new value
747 * @param vec Elements of new value
748 * @return 0 on success, non-0 on error
749 */
750static int validate_non_negative(const struct config_state *cs,
751 int nvec, char **vec) {
752 long n;
753
754 if(nvec < 1) {
755 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
756 return -1;
757 }
758 if(nvec > 1) {
759 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
760 return -1;
761 }
762 if(xstrtol(&n, vec[0], 0, 0)) {
763 disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
764 return -1;
765 }
766 if(n < 0) {
767 disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
768 return -1;
769 }
770 return 0;
771}
772
773/** @brief Validate a positive (@c long) integer
774 * @param cs Configuration state
775 * @param nvec Length of (proposed) new value
776 * @param vec Elements of new value
777 * @return 0 on success, non-0 on error
778 */
779static int validate_positive(const struct config_state *cs,
780 int nvec, char **vec) {
781 long n;
782
783 if(nvec < 1) {
784 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
785 return -1;
786 }
787 if(nvec > 1) {
788 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
789 return -1;
790 }
791 if(xstrtol(&n, vec[0], 0, 0)) {
792 disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
793 return -1;
794 }
795 if(n <= 0) {
796 disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
797 return -1;
798 }
799 return 0;
800}
801
802/** @brief Validate a system username
803 * @param cs Configuration state
804 * @param nvec Length of (proposed) new value
805 * @param vec Elements of new value
806 * @return 0 on success, non-0 on error
807 */
808static int validate_isauser(const struct config_state *cs,
809 int attribute((unused)) nvec,
810 char **vec) {
811 if(!getpwnam(vec[0])) {
812 disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
813 return -1;
814 }
815 return 0;
816}
817
818/** @brief Validate a sample format string
819 * @param cs Configuration state
820 * @param nvec Length of (proposed) new value
821 * @param vec Elements of new value
822 * @return 0 on success, non-0 on error
823 */
824static int validate_sample_format(const struct config_state *cs,
825 int attribute((unused)) nvec,
826 char **vec) {
827 return parse_sample_format(cs, 0, nvec, vec);
828}
829
830/** @brief Validate anything
831 * @param cs Configuration state
832 * @param nvec Length of (proposed) new value
833 * @param vec Elements of new value
834 * @return 0
835 */
836static int validate_any(const struct config_state attribute((unused)) *cs,
837 int attribute((unused)) nvec,
838 char attribute((unused)) **vec) {
839 return 0;
840}
841
842/** @brief Validate a URL
843 * @param cs Configuration state
844 * @param nvec Length of (proposed) new value
845 * @param vec Elements of new value
846 * @return 0 on success, non-0 on error
847 *
848 * Rather cursory.
849 */
850static int validate_url(const struct config_state attribute((unused)) *cs,
851 int attribute((unused)) nvec,
852 char **vec) {
853 const char *s;
854 int n;
855 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
856 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
857 s = vec[0];
858 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
859 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
860 "0123456789"));
861 if(s[n] != ':') {
862 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
863 return -1;
864 }
865 if(!strncmp(s, "http:", 5)
866 || !strncmp(s, "https:", 6)) {
867 s += n + 1;
868 /* we only do a rather cursory check */
869 if(strncmp(s, "//", 2)) {
870 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
871 return -1;
872 }
873 }
874 return 0;
875}
876
877/** @brief Validate an alias pattern
878 * @param cs Configuration state
879 * @param nvec Length of (proposed) new value
880 * @param vec Elements of new value
881 * @return 0 on success, non-0 on error
882 */
883static int validate_alias(const struct config_state *cs,
884 int nvec,
885 char **vec) {
886 const char *s;
887 int in_brackets = 0, c;
888
889 if(nvec < 1) {
890 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
891 return -1;
892 }
893 if(nvec > 1) {
894 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
895 return -1;
896 }
897 s = vec[0];
898 while((c = (unsigned char)*s++)) {
899 if(in_brackets) {
900 if(c == '}')
901 in_brackets = 0;
902 else if(!isalnum(c)) {
903 disorder_error(0, "%s:%d: invalid part name in alias expansion in '%s'",
904 cs->path, cs->line, vec[0]);
905 return -1;
906 }
907 } else {
908 if(c == '{') {
909 in_brackets = 1;
910 if(*s == '/')
911 ++s;
912 } else if(c == '\\') {
913 if(!(c = (unsigned char)*s++)) {
914 disorder_error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
915 cs->path, cs->line, vec[0]);
916 return -1;
917 } else if(c != '\\' && c != '{') {
918 disorder_error(0, "%s:%d: invalid escape in alias expansion in '%s'",
919 cs->path, cs->line, vec[0]);
920 return -1;
921 }
922 }
923 }
924 ++s;
925 }
926 if(in_brackets) {
927 disorder_error(0,
928 "%s:%d: unterminated part name in alias expansion in '%s'",
929 cs->path, cs->line, vec[0]);
930 return -1;
931 }
932 return 0;
933}
934
935/** @brief Validate a hash algorithm name
936 * @param cs Configuration state
937 * @param nvec Length of (proposed) new value
938 * @param vec Elements of new value
939 * @return 0 on success, non-0 on error
940 */
941static int validate_algo(const struct config_state attribute((unused)) *cs,
942 int nvec,
943 char **vec) {
944 if(nvec != 1) {
945 disorder_error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
946 return -1;
947 }
948 if(!valid_authhash(vec[0])) {
949 disorder_error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
950 return -1;
951 }
952 return 0;
953}
954
955/** @brief Validate a playback backend name
956 * @param cs Configuration state
957 * @param nvec Length of (proposed) new value
958 * @param vec Elements of new value
959 * @return 0 on success, non-0 on error
960 */
961static int validate_backend(const struct config_state attribute((unused)) *cs,
962 int nvec,
963 char **vec) {
964 int n;
965 if(nvec != 1) {
966 disorder_error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
967 return -1;
968 }
969 if(!strcmp(vec[0], "network")) {
970 disorder_error(0, "'api network' is deprecated; use 'api rtp'");
971 return 0;
972 }
973 if(config_uaudio_apis) {
974 for(n = 0; config_uaudio_apis[n]; ++n)
975 if(!strcmp(vec[0], config_uaudio_apis[n]->name))
976 return 0;
977 disorder_error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
978 return -1;
979 }
980 /* In non-server processes we have no idea what's valid */
981 return 0;
982}
983
984/** @brief Validate a pause mode string
985 * @param cs Configuration state
986 * @param nvec Length of (proposed) new value
987 * @param vec Elements of new value
988 * @return 0 on success, non-0 on error
989 */
990static int validate_pausemode(const struct config_state attribute((unused)) *cs,
991 int nvec,
992 char **vec) {
993 if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
994 return 0;
995 disorder_error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
996 return -1;
997}
998
999/** @brief Validate a destination network address
1000 * @param cs Configuration state
1001 * @param nvec Length of (proposed) new value
1002 * @param vec Elements of new value
1003 * @return 0 on success, non-0 on error
1004 *
1005 * By a destination address, it is meant that it must not be a wildcard
1006 * address.
1007 */
1008static int validate_destaddr(const struct config_state attribute((unused)) *cs,
1009 int nvec,
1010 char **vec) {
1011 struct netaddress na[1];
1012
1013 if(netaddress_parse(na, nvec, vec)) {
1014 disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
1015 return -1;
1016 }
1017 if(!na->address) {
1018 disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
1019 return -1;
1020 }
1021 xfree(na->address);
1022 return 0;
1023}
1024
1025/** @brief Item name and and offset */
1026#define C(x) #x, offsetof(struct config, x)
1027/** @brief Item name and and offset */
1028#define C2(x,y) #x, offsetof(struct config, y)
1029
1030/** @brief All configuration items */
1031static const struct conf conf[] = {
1032 { C(alias), &type_string, validate_alias },
1033 { C(api), &type_string, validate_backend },
1034 { C(authorization_algorithm), &type_string, validate_algo },
1035 { C(broadcast), &type_netaddress, validate_destaddr },
1036 { C(broadcast_from), &type_netaddress, validate_any },
1037 { C(channel), &type_string, validate_any },
1038 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
1039 { C(checkpoint_min), &type_integer, validate_non_negative },
1040 { C(collection), &type_collections, validate_any },
1041 { C(connect), &type_netaddress, validate_destaddr },
1042 { C(cookie_key_lifetime), &type_integer, validate_positive },
1043 { C(cookie_login_lifetime), &type_integer, validate_positive },
1044 { C(dbversion), &type_integer, validate_positive },
1045 { C(default_rights), &type_rights, validate_any },
1046 { C(device), &type_string, validate_any },
1047 { C(history), &type_integer, validate_positive },
1048 { C(home), &type_string, validate_isabspath },
1049 { C(listen), &type_netaddress, validate_any },
1050 { C(mail_sender), &type_string, validate_any },
1051 { C(mixer), &type_string, validate_any },
1052 { C(mount_rescan), &type_boolean, validate_any },
1053 { C(multicast_loop), &type_boolean, validate_any },
1054 { C(multicast_ttl), &type_integer, validate_non_negative },
1055#if HAVE_PCRE_H
1056 { C(namepart), &type_namepart, validate_any },
1057#endif
1058 { C(new_bias), &type_integer, validate_positive },
1059 { C(new_bias_age), &type_integer, validate_positive },
1060 { C(new_max), &type_integer, validate_positive },
1061 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
1062 { C(nice_rescan), &type_integer, validate_non_negative },
1063 { C(nice_server), &type_integer, validate_any },
1064 { C(nice_speaker), &type_integer, validate_any },
1065 { C(noticed_history), &type_integer, validate_positive },
1066 { C(password), &type_string, validate_any },
1067 { C(pause_mode), &type_string, validate_pausemode },
1068 { C(player), &type_stringlist_accum, validate_player },
1069 { C(playlist_lock_timeout), &type_integer, validate_positive },
1070 { C(playlist_max) , &type_integer, validate_positive },
1071 { C(plugins), &type_string_accum, validate_isdir },
1072 { C(queue_pad), &type_integer, validate_positive },
1073 { C(refresh), &type_integer, validate_positive },
1074 { C(refresh_min), &type_integer, validate_non_negative },
1075 { C(reminder_interval), &type_integer, validate_positive },
1076 { C(remote_userman), &type_boolean, validate_any },
1077 { C(replay_min), &type_integer, validate_non_negative },
1078 { C(rtp_delay_threshold), &type_integer, validate_positive },
1079 { C(rtp_mode), &type_string, validate_any },
1080 { C(rtp_verbose), &type_boolean, validate_any },
1081 { C(sample_format), &type_sample_format, validate_sample_format },
1082 { C(scratch), &type_string_accum, validate_isreg },
1083 { C(sendmail), &type_string, validate_isabspath },
1084 { C(short_display), &type_integer, validate_positive },
1085 { C(signal), &type_signal, validate_any },
1086 { C(smtp_server), &type_string, validate_any },
1087 { C(sox_generation), &type_integer, validate_non_negative },
1088 { C2(speaker_backend, api), &type_string, validate_backend },
1089 { C(speaker_command), &type_string, validate_any },
1090 { C(stopword), &type_string_accum, validate_any },
1091 { C(templates), &type_string_accum, validate_isdir },
1092 { C(tracklength), &type_stringlist_accum, validate_tracklength },
1093#if HAVE_PCRE_H
1094 { C(transform), &type_transform, validate_any },
1095#endif
1096 { C(url), &type_string, validate_url },
1097 { C(user), &type_string, validate_isauser },
1098 { C(username), &type_string, validate_any },
1099};
1100
1101/** @brief Find a configuration item's definition by key */
1102static const struct conf *find(const char *key) {
1103 int n;
1104
1105 if((n = TABLE_FIND(conf, name, key)) < 0)
1106 return 0;
1107 return &conf[n];
1108}
1109
1110/** @brief Set a new configuration value
1111 * @param cs Configuration state
1112 * @param nvec Length of @p vec
1113 * @param vec Name and new value
1114 * @return 0 on success, non-0 on error.
1115 *
1116 * @c vec[0] is the name, the rest is the value.
1117 */
1118static int config_set(const struct config_state *cs,
1119 int nvec, char **vec) {
1120 const struct conf *which;
1121
1122 D(("config_set %s", vec[0]));
1123 if(!(which = find(vec[0]))) {
1124 disorder_error(0, "%s:%d: unknown configuration key '%s'",
1125 cs->path, cs->line, vec[0]);
1126 return -1;
1127 }
1128 return (which->validate(cs, nvec - 1, vec + 1)
1129 || which->type->set(cs, which, nvec - 1, vec + 1));
1130}
1131
1132/** @brief Set a configuration item from parameters
1133 * @param cs Configuration state
1134 * @param which Item to set
1135 * @param ... Value as strings, terminated by (char *)NULL
1136 * @return 0 on success, non-0 on error
1137 */
1138static int config_set_args(const struct config_state *cs,
1139 const char *which, ...) {
1140 va_list ap;
1141 struct vector v[1];
1142 char *s;
1143
1144 vector_init(v);
1145 vector_append(v, (char *)which);
1146 va_start(ap, which);
1147 while((s = va_arg(ap, char *)))
1148 vector_append(v, s);
1149 va_end(ap);
1150 vector_terminate(v);
1151 int rc = config_set(cs, v->nvec, v->vec);
1152 xfree(v->vec);
1153 return rc;
1154}
1155
1156/** @brief Error callback used by config_include()
1157 * @param msg Error message
1158 * @param u User data (@ref config_state)
1159 */
1160static void config_error(const char *msg, void *u) {
1161 const struct config_state *cs = u;
1162
1163 disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
1164}
1165
1166/** @brief Include a file by name
1167 * @param c Configuration to update
1168 * @param path Path to read
1169 * @return 0 on success, non-0 on error
1170 */
1171static int config_include(struct config *c, const char *path) {
1172 FILE *fp;
1173 char *buffer, *inputbuffer, **vec;
1174 int n, ret = 0;
1175 struct config_state cs;
1176
1177 cs.path = path;
1178 cs.line = 0;
1179 cs.config = c;
1180 D(("%s: reading configuration", path));
1181 if(!(fp = fopen(path, "r"))) {
1182 disorder_error(errno, "error opening %s", path);
1183 return -1;
1184 }
1185 while(!inputline(path, fp, &inputbuffer, '\n')) {
1186 ++cs.line;
1187 if(!(buffer = mb2utf8(inputbuffer))) {
1188 disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1189 ret = -1;
1190 xfree(inputbuffer);
1191 continue;
1192 }
1193 xfree(inputbuffer);
1194 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1195 config_error, &cs))) {
1196 ret = -1;
1197 xfree(buffer);
1198 continue;
1199 }
1200 if(n) {
1201 /* 'include' is special-cased */
1202 if(!strcmp(vec[0], "include")) {
1203 if(n != 2) {
1204 disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1205 ret = -1;
1206 } else
1207 config_include(c, vec[1]);
1208 } else
1209 ret |= config_set(&cs, n, vec);
1210 }
1211 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1212 xfree(vec);
1213 xfree(buffer);
1214 }
1215 if(ferror(fp)) {
1216 disorder_error(errno, "error reading %s", path);
1217 ret = -1;
1218 }
1219 fclose(fp);
1220 return ret;
1221}
1222
1223/** @brief Default stopword setting */
1224static const char *const default_stopwords[] = {
1225 "stopword",
1226
1227 "01",
1228 "02",
1229 "03",
1230 "04",
1231 "05",
1232 "06",
1233 "07",
1234 "08",
1235 "09",
1236 "1",
1237 "10",
1238 "11",
1239 "12",
1240 "13",
1241 "14",
1242 "15",
1243 "16",
1244 "17",
1245 "18",
1246 "19",
1247 "2",
1248 "20",
1249 "21",
1250 "22",
1251 "23",
1252 "24",
1253 "25",
1254 "26",
1255 "27",
1256 "28",
1257 "29",
1258 "3",
1259 "30",
1260 "4",
1261 "5",
1262 "6",
1263 "7",
1264 "8",
1265 "9",
1266 "a",
1267 "am",
1268 "an",
1269 "and",
1270 "as",
1271 "for",
1272 "i",
1273 "im",
1274 "in",
1275 "is",
1276 "of",
1277 "on",
1278 "the",
1279 "to",
1280 "too",
1281 "we",
1282};
1283#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1284
1285/** @brief Default player patterns */
1286static const char *const default_players[] = {
1287 "*.ogg",
1288 "*.flac",
1289 "*.mp3",
1290 "*.wav",
1291};
1292#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1293
1294/** @brief Make a new default configuration
1295 * @return New configuration
1296 */
1297static struct config *config_default(void) {
1298 struct config *c = xmalloc(sizeof *c);
1299 const char *logname;
1300 struct passwd *pw;
1301 struct config_state cs;
1302 size_t n;
1303
1304 cs.path = "<internal>";
1305 cs.line = 0;
1306 cs.config = c;
1307 /* Strings had better be xstrdup'd as they will get freed at some point. */
1308 c->history = 60;
1309 c->home = xstrdup(pkgstatedir);
1310 if(!(pw = getpwuid(getuid())))
1311 disorder_fatal(0, "cannot determine our username");
1312 logname = pw->pw_name;
1313 c->username = xstrdup(logname);
1314 c->refresh = 15;
1315 c->refresh_min = 1;
1316 c->signal = SIGKILL;
1317 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1318 c->device = xstrdup("default");
1319 c->nice_rescan = 10;
1320 c->speaker_command = 0;
1321 c->sample_format.bits = 16;
1322 c->sample_format.rate = 44100;
1323 c->sample_format.channels = 2;
1324 c->sample_format.endian = ENDIAN_NATIVE;
1325 c->queue_pad = 10;
1326 c->replay_min = 8 * 3600;
1327 c->api = NULL;
1328 c->multicast_ttl = 1;
1329 c->multicast_loop = 1;
1330 c->authorization_algorithm = xstrdup("sha1");
1331 c->noticed_history = 31;
1332 c->short_display = 32;
1333 c->mixer = 0;
1334 c->channel = 0;
1335 c->dbversion = 2;
1336 c->cookie_login_lifetime = 86400;
1337 c->cookie_key_lifetime = 86400 * 7;
1338 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1339 c->sendmail = xstrdup(sendmail_binary);
1340 c->smtp_server = xstrdup("127.0.0.1");
1341 c->new_max = 100;
1342 c->reminder_interval = 600; /* 10m */
1343 c->new_bias_age = 7 * 86400; /* 1 week */
1344 c->new_bias = 4500000; /* 50 times the base weight */
1345 c->sox_generation = DEFAULT_SOX_GENERATION;
1346 c->playlist_max = INT_MAX; /* effectively no limit */
1347 c->playlist_lock_timeout = 10; /* 10s */
1348 c->mount_rescan = 1;
1349 /* Default stopwords */
1350 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1351 exit(1);
1352 /* Default player configuration */
1353 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1354 if(config_set_args(&cs, "player",
1355 default_players[n], "execraw", "disorder-decode", (char *)0))
1356 exit(1);
1357 if(config_set_args(&cs, "tracklength",
1358 default_players[n], "disorder-tracklength", (char *)0))
1359 exit(1);
1360 }
1361 c->broadcast.af = -1;
1362 c->broadcast_from.af = -1;
1363 c->listen.af = -1;
1364 c->connect.af = -1;
1365 c->rtp_mode = xstrdup("auto");
1366 return c;
1367}
1368
1369/** @brief Construct a filename
1370 * @param c Configuration
1371 * @param name Base filename
1372 * @return Full filename
1373 *
1374 * Usually use config_get_file() instead.
1375 */
1376char *config_get_file2(struct config *c, const char *name) {
1377 char *s;
1378
1379 byte_xasprintf(&s, "%s/%s", c->home, name);
1380 return s;
1381}
1382
1383/** @brief Set the default configuration file */
1384static void set_configfile(void) {
1385 if(!configfile)
1386 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1387}
1388
1389/** @brief Free a configuration object
1390 * @param c Configuration to free
1391 *
1392 * @p c is indeterminate after this function is called.
1393 */
1394void config_free(struct config *c) {
1395 int n;
1396
1397 if(c) {
1398 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1399 conf[n].type->free(c, &conf[n]);
1400 for(n = 0; n < c->nparts; ++n)
1401 xfree(c->parts[n]);
1402 xfree(c->parts);
1403 xfree(c);
1404 }
1405}
1406
1407/** @brief Set post-parse defaults
1408 * @param c Configuration to update
1409 * @param server True when running in the server
1410 *
1411 * If @p server is set then certain parts of the configuration are more
1412 * strictly validated.
1413 */
1414static void config_postdefaults(struct config *c,
1415 int server) {
1416 struct config_state cs;
1417#if HAVE_PCRE_H
1418 const struct conf *whoami;
1419 int n;
1420#endif
1421
1422#if HAVE_PCRE_H
1423 static const char *namepart[][4] = {
1424 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1425 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1426 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1427 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1428 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1429 };
1430#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1431
1432 static const char *transform[][5] = {
1433 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1434 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1435 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1436 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1437 { "dir", "[[:punct:]]", "", "sort", "g", }
1438 };
1439#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1440#endif
1441
1442 cs.path = "<internal>";
1443 cs.line = 0;
1444 cs.config = c;
1445#if HAVE_PCRE_H
1446 if(!c->namepart.n) {
1447 whoami = find("namepart");
1448 for(n = 0; n < NNAMEPART; ++n)
1449 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1450 }
1451 if(!c->transform.n) {
1452 whoami = find("transform");
1453 for(n = 0; n < NTRANSFORM; ++n)
1454 set_transform(&cs, whoami, 5, (char **)transform[n]);
1455 }
1456#endif
1457 if(!c->api) {
1458 if(c->speaker_command)
1459 c->api = xstrdup("command");
1460 else if(c->broadcast.af != -1)
1461 c->api = xstrdup("rtp");
1462 else if(config_uaudio_apis)
1463 c->api = xstrdup(uaudio_default(config_uaudio_apis,
1464 UAUDIO_API_SERVER)->name);
1465 else
1466 c->api = xstrdup("<none>");
1467 }
1468 if(!strcmp(c->api, "network"))
1469 c->api = xstrdup("rtp");
1470 if(server) {
1471 if(!strcmp(c->api, "command") && !c->speaker_command)
1472 disorder_fatal(0, "'api command' but speaker_command is not set");
1473 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1474 disorder_fatal(0, "'api rtp' but broadcast is not set");
1475 }
1476 /* Override sample format */
1477 if(!strcmp(c->api, "rtp")) {
1478 c->sample_format.rate = 44100;
1479 c->sample_format.channels = 2;
1480 c->sample_format.bits = 16;
1481 c->sample_format.endian = ENDIAN_NATIVE;
1482 }
1483 if(!strcmp(c->api, "coreaudio")) {
1484 c->sample_format.rate = 44100;
1485 c->sample_format.channels = 2;
1486 c->sample_format.bits = 16;
1487 c->sample_format.endian = ENDIAN_NATIVE;
1488 }
1489 if(!c->default_rights) {
1490 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1491 |RIGHT_MOVE__MASK
1492 |RIGHT_SCRATCH__MASK
1493 |RIGHT_REMOVE__MASK);
1494 r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
1495 c->default_rights = rights_string(r);
1496 }
1497}
1498
1499/** @brief (Re-)read the config file
1500 * @param server If set, do extra checking
1501 * @param oldconfig Old configuration for compatibility check
1502 * @return 0 on success, non-0 on error
1503 *
1504 * If @p oldconfig is set, then certain compatibility checks are done between
1505 * the old and new configurations.
1506 */
1507int config_read(int server,
1508 const struct config *oldconfig) {
1509 struct config *c;
1510 char *privconf;
1511 struct passwd *pw;
1512
1513 set_configfile();
1514 c = config_default();
1515 /* standalone Disobedience installs might not have a global config file */
1516 if(access(configfile, F_OK) == 0)
1517 if(config_include(c, configfile))
1518 return -1;
1519 /* if we can read the private config file, do */
1520 if((privconf = config_private())
1521 && access(privconf, R_OK) == 0
1522 && config_include(c, privconf))
1523 return -1;
1524 xfree(privconf);
1525 /* if there's a per-user system config file for this user, read it */
1526 if(config_per_user) {
1527 if(!(pw = getpwuid(getuid())))
1528 disorder_fatal(0, "cannot determine our username");
1529 if((privconf = config_usersysconf(pw))
1530 && access(privconf, F_OK) == 0
1531 && config_include(c, privconf))
1532 return -1;
1533 xfree(privconf);
1534 /* if we have a password file, read it */
1535 if((privconf = config_userconf(0, pw))
1536 && access(privconf, F_OK) == 0
1537 && config_include(c, privconf))
1538 return -1;
1539 xfree(privconf);
1540 }
1541 /* install default namepart and transform settings */
1542 config_postdefaults(c, server);
1543 if(oldconfig) {
1544 int failed = 0;
1545 if(strcmp(c->home, oldconfig->home)) {
1546 disorder_error(0, "'home' cannot be changed without a restart");
1547 failed = 1;
1548 }
1549 if(strcmp(c->alias, oldconfig->alias)) {
1550 disorder_error(0, "'alias' cannot be changed without a restart");
1551 failed = 1;
1552 }
1553 if(strcmp(c->user, oldconfig->user)) {
1554 disorder_error(0, "'user' cannot be changed without a restart");
1555 failed = 1;
1556 }
1557 if(c->nice_speaker != oldconfig->nice_speaker) {
1558 disorder_error(0, "'nice_speaker' cannot be changed without a restart");
1559 /* ...but we accept the new config anyway */
1560 }
1561 if(c->nice_server != oldconfig->nice_server) {
1562 disorder_error(0, "'nice_server' cannot be changed without a restart");
1563 /* ...but we accept the new config anyway */
1564 }
1565#if HAVE_PCRE_H
1566 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1567 disorder_error(0, "'namepart' settings cannot be changed without a restart");
1568 failed = 1;
1569 }
1570#endif
1571 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1572 disorder_error(0, "'stopword' settings cannot be changed without a restart");
1573 failed = 1;
1574 }
1575 if(failed) {
1576 disorder_error(0, "not installing incompatible new configuration");
1577 return -1;
1578 }
1579 }
1580 /* everything is good so we shall use the new config */
1581 config_free(config);
1582 /* warn about obsolete directives */
1583 config = c;
1584 return 0;
1585}
1586
1587/** @brief Return the path to the private configuration file */
1588char *config_private(void) {
1589 char *s;
1590
1591 set_configfile();
1592 byte_xasprintf(&s, "%s.private", configfile);
1593 return s;
1594}
1595
1596/** @brief Return the path to user's personal configuration file */
1597char *config_userconf(const char *home, const struct passwd *pw) {
1598 char *s;
1599
1600 if(!home && !pw && !(pw = getpwuid(getuid())))
1601 disorder_fatal(0, "cannot determine our username");
1602 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1603 return s;
1604}
1605
1606/** @brief Return the path to user-specific system configuration */
1607char *config_usersysconf(const struct passwd *pw) {
1608 char *s;
1609
1610 set_configfile();
1611 if(!strchr(pw->pw_name, '/')) {
1612 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1613 return s;
1614 } else
1615 return 0;
1616}
1617
1618/** @brief Get a filename within the home directory
1619 * @param name Relative name
1620 * @return Full path
1621 */
1622char *config_get_file(const char *name) {
1623 return config_get_file2(config, name);
1624}
1625
1626/** @brief Order two stringlists
1627 * @param a First stringlist
1628 * @param b Second stringlist
1629 * @return <0, 0 or >0 if a<b, a=b or a>b
1630 */
1631static int stringlist_compare(const struct stringlist *a,
1632 const struct stringlist *b) {
1633 int n = 0, c;
1634
1635 while(n < a->n && n < b->n) {
1636 if((c = strcmp(a->s[n], b->s[n])))
1637 return c;
1638 ++n;
1639 }
1640 if(a->n < b->n)
1641 return -1;
1642 else if(a->n > b->n)
1643 return 1;
1644 else
1645 return 0;
1646}
1647
1648#if HAVE_PCRE_H
1649/** @brief Order two namepart definitions
1650 * @param a First namepart definition
1651 * @param b Second namepart definition
1652 * @return <0, 0 or >0 if a<b, a=b or a>b
1653 */
1654static int namepart_compare(const struct namepart *a,
1655 const struct namepart *b) {
1656 int c;
1657
1658 if((c = strcmp(a->part, b->part)))
1659 return c;
1660 if((c = strcmp(a->res, b->res)))
1661 return c;
1662 if((c = strcmp(a->replace, b->replace)))
1663 return c;
1664 if((c = strcmp(a->context, b->context)))
1665 return c;
1666 if(a->reflags > b->reflags)
1667 return 1;
1668 if(a->reflags < b->reflags)
1669 return -1;
1670 return 0;
1671}
1672
1673/** @brief Order two lists of namepart definitions
1674 * @param a First list of namepart definitions
1675 * @param b Second list of namepart definitions
1676 * @return <0, 0 or >0 if a<b, a=b or a>b
1677 */
1678static int namepartlist_compare(const struct namepartlist *a,
1679 const struct namepartlist *b) {
1680 int n = 0, c;
1681
1682 while(n < a->n && n < b->n) {
1683 if((c = namepart_compare(&a->s[n], &b->s[n])))
1684 return c;
1685 ++n;
1686 }
1687 if(a->n > b->n)
1688 return 1;
1689 else if(a->n < b->n)
1690 return -1;
1691 else
1692 return 0;
1693}
1694#endif
1695
1696/** @brief Verify configuration table.
1697 * @return The number of problems found
1698*/
1699int config_verify(void) {
1700 int fails = 0;
1701 for(size_t n = 1; n < sizeof conf / sizeof *conf; ++n)
1702 if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1703 fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1704 ++fails;
1705 }
1706 return fails;
1707}
1708
1709/*
1710Local Variables:
1711c-basic-offset:2
1712comment-column:40
1713fill-column:79
1714End:
1715*/