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