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