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