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