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