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