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