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