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