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