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