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