chiark / gitweb /
Merge branch '5.2.x'
[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
34d37b3e
RK
726/** @brief Validate a non-negative (@c long) integer
727 * @param cs Configuration state
728 * @param nvec Length of (proposed) new value
729 * @param vec Elements of new value
730 * @return 0 on success, non-0 on error
731 */
460b9539 732static int validate_non_negative(const struct config_state *cs,
733 int nvec, char **vec) {
734 long n;
b94e827f 735 char errbuf[1024];
460b9539 736
737 if(nvec < 1) {
2e9ba080 738 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
460b9539 739 return -1;
740 }
741 if(nvec > 1) {
2e9ba080 742 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
460b9539 743 return -1;
744 }
745 if(xstrtol(&n, vec[0], 0, 0)) {
b94e827f
RK
746 disorder_error(0, "%s:%d: %s", cs->path, cs->line,
747 format_error(ec_errno, errno, errbuf, sizeof errbuf));
460b9539 748 return -1;
749 }
750 if(n < 0) {
2e9ba080 751 disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
460b9539 752 return -1;
753 }
754 return 0;
755}
756
34d37b3e
RK
757/** @brief Validate a positive (@c long) integer
758 * @param cs Configuration state
759 * @param nvec Length of (proposed) new value
760 * @param vec Elements of new value
761 * @return 0 on success, non-0 on error
762 */
460b9539 763static int validate_positive(const struct config_state *cs,
764 int nvec, char **vec) {
765 long n;
b94e827f 766 char errbuf[1024];
460b9539 767
768 if(nvec < 1) {
2e9ba080 769 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
460b9539 770 return -1;
771 }
772 if(nvec > 1) {
2e9ba080 773 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
460b9539 774 return -1;
775 }
776 if(xstrtol(&n, vec[0], 0, 0)) {
b94e827f
RK
777 disorder_error(0, "%s:%d: %s", cs->path, cs->line,
778 format_error(ec_errno, errno, errbuf, sizeof errbuf));
460b9539 779 return -1;
780 }
781 if(n <= 0) {
2e9ba080 782 disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
460b9539 783 return -1;
784 }
785 return 0;
786}
787
9e42afcd 788#if !_WIN32
34d37b3e
RK
789/** @brief Validate a system username
790 * @param cs Configuration state
791 * @param nvec Length of (proposed) new value
792 * @param vec Elements of new value
793 * @return 0 on success, non-0 on error
794 */
460b9539 795static int validate_isauser(const struct config_state *cs,
796 int attribute((unused)) nvec,
797 char **vec) {
b3756e27 798 if(!getpwnam(vec[0])) {
2e9ba080 799 disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
460b9539 800 return -1;
801 }
802 return 0;
803}
9e42afcd 804#endif
460b9539 805
34d37b3e
RK
806/** @brief Validate a sample format string
807 * @param cs Configuration state
808 * @param nvec Length of (proposed) new value
809 * @param vec Elements of new value
810 * @return 0 on success, non-0 on error
811 */
9d5da576 812static int validate_sample_format(const struct config_state *cs,
813 int attribute((unused)) nvec,
814 char **vec) {
815 return parse_sample_format(cs, 0, nvec, vec);
816}
817
34d37b3e
RK
818/** @brief Validate anything
819 * @param cs Configuration state
820 * @param nvec Length of (proposed) new value
821 * @param vec Elements of new value
822 * @return 0
823 */
460b9539 824static int validate_any(const struct config_state attribute((unused)) *cs,
825 int attribute((unused)) nvec,
826 char attribute((unused)) **vec) {
827 return 0;
828}
829
34d37b3e
RK
830/** @brief Validate a URL
831 * @param cs Configuration state
832 * @param nvec Length of (proposed) new value
833 * @param vec Elements of new value
834 * @return 0 on success, non-0 on error
835 *
836 * Rather cursory.
837 */
460b9539 838static int validate_url(const struct config_state attribute((unused)) *cs,
839 int attribute((unused)) nvec,
840 char **vec) {
841 const char *s;
842 int n;
843 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
844 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
845 s = vec[0];
846 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
847 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
848 "0123456789"));
849 if(s[n] != ':') {
2e9ba080 850 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
460b9539 851 return -1;
852 }
853 if(!strncmp(s, "http:", 5)
854 || !strncmp(s, "https:", 6)) {
855 s += n + 1;
856 /* we only do a rather cursory check */
857 if(strncmp(s, "//", 2)) {
2e9ba080 858 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
460b9539 859 return -1;
860 }
861 }
862 return 0;
863}
864
34d37b3e
RK
865/** @brief Validate an alias pattern
866 * @param cs Configuration state
867 * @param nvec Length of (proposed) new value
868 * @param vec Elements of new value
869 * @return 0 on success, non-0 on error
870 */
460b9539 871static int validate_alias(const struct config_state *cs,
872 int nvec,
873 char **vec) {
874 const char *s;
875 int in_brackets = 0, c;
876
877 if(nvec < 1) {
2e9ba080 878 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
460b9539 879 return -1;
880 }
881 if(nvec > 1) {
2e9ba080 882 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
460b9539 883 return -1;
884 }
885 s = vec[0];
886 while((c = (unsigned char)*s++)) {
887 if(in_brackets) {
888 if(c == '}')
889 in_brackets = 0;
890 else if(!isalnum(c)) {
2e9ba080
RK
891 disorder_error(0, "%s:%d: invalid part name in alias expansion in '%s'",
892 cs->path, cs->line, vec[0]);
460b9539 893 return -1;
894 }
895 } else {
896 if(c == '{') {
897 in_brackets = 1;
898 if(*s == '/')
899 ++s;
900 } else if(c == '\\') {
901 if(!(c = (unsigned char)*s++)) {
2e9ba080
RK
902 disorder_error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
903 cs->path, cs->line, vec[0]);
460b9539 904 return -1;
905 } else if(c != '\\' && c != '{') {
2e9ba080
RK
906 disorder_error(0, "%s:%d: invalid escape in alias expansion in '%s'",
907 cs->path, cs->line, vec[0]);
460b9539 908 return -1;
909 }
910 }
911 }
912 ++s;
913 }
914 if(in_brackets) {
2e9ba080
RK
915 disorder_error(0,
916 "%s:%d: unterminated part name in alias expansion in '%s'",
917 cs->path, cs->line, vec[0]);
460b9539 918 return -1;
919 }
920 return 0;
921}
922
34d37b3e
RK
923/** @brief Validate a hash algorithm name
924 * @param cs Configuration state
925 * @param nvec Length of (proposed) new value
926 * @param vec Elements of new value
927 * @return 0 on success, non-0 on error
928 */
637fdea3
RK
929static int validate_algo(const struct config_state attribute((unused)) *cs,
930 int nvec,
931 char **vec) {
932 if(nvec != 1) {
2e9ba080 933 disorder_error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
637fdea3
RK
934 return -1;
935 }
936 if(!valid_authhash(vec[0])) {
2e9ba080 937 disorder_error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
637fdea3
RK
938 return -1;
939 }
940 return 0;
941}
942
9e42afcd 943#if !_WIN32
34d37b3e
RK
944/** @brief Validate a playback backend name
945 * @param cs Configuration state
946 * @param nvec Length of (proposed) new value
947 * @param vec Elements of new value
948 * @return 0 on success, non-0 on error
949 */
b50cfb8a
RK
950static int validate_backend(const struct config_state attribute((unused)) *cs,
951 int nvec,
952 char **vec) {
953 int n;
954 if(nvec != 1) {
2e9ba080 955 disorder_error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
b50cfb8a
RK
956 return -1;
957 }
958 if(!strcmp(vec[0], "network")) {
2e9ba080 959 disorder_error(0, "'api network' is deprecated; use 'api rtp'");
b50cfb8a
RK
960 return 0;
961 }
962 if(config_uaudio_apis) {
963 for(n = 0; config_uaudio_apis[n]; ++n)
964 if(!strcmp(vec[0], config_uaudio_apis[n]->name))
965 return 0;
2e9ba080 966 disorder_error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
b50cfb8a
RK
967 return -1;
968 }
969 /* In non-server processes we have no idea what's valid */
970 return 0;
971}
9e42afcd 972#endif
b50cfb8a 973
34d37b3e
RK
974/** @brief Validate a pause mode string
975 * @param cs Configuration state
976 * @param nvec Length of (proposed) new value
977 * @param vec Elements of new value
978 * @return 0 on success, non-0 on error
979 */
f75ab9d3
RK
980static int validate_pausemode(const struct config_state attribute((unused)) *cs,
981 int nvec,
982 char **vec) {
983 if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
984 return 0;
2e9ba080 985 disorder_error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
f75ab9d3
RK
986 return -1;
987}
988
34d37b3e
RK
989/** @brief Validate a destination network address
990 * @param cs Configuration state
991 * @param nvec Length of (proposed) new value
992 * @param vec Elements of new value
993 * @return 0 on success, non-0 on error
994 *
995 * By a destination address, it is meant that it must not be a wildcard
996 * address.
997 */
76e72f65
RK
998static int validate_destaddr(const struct config_state attribute((unused)) *cs,
999 int nvec,
1000 char **vec) {
1001 struct netaddress na[1];
1002
1003 if(netaddress_parse(na, nvec, vec)) {
2e9ba080 1004 disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
76e72f65
RK
1005 return -1;
1006 }
1007 if(!na->address) {
2e9ba080 1008 disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
76e72f65
RK
1009 return -1;
1010 }
47854c5f 1011 xfree(na->address);
76e72f65
RK
1012 return 0;
1013}
1014
3f3bb97b 1015/** @brief Item name and and offset */
460b9539 1016#define C(x) #x, offsetof(struct config, x)
3f3bb97b 1017/** @brief Item name and and offset */
460b9539 1018#define C2(x,y) #x, offsetof(struct config, y)
1019
3f3bb97b 1020/** @brief All configuration items */
460b9539 1021static const struct conf conf[] = {
1022 { C(alias), &type_string, validate_alias },
9e42afcd 1023#if !_WIN32
b50cfb8a 1024 { C(api), &type_string, validate_backend },
9e42afcd 1025#endif
637fdea3 1026 { C(authorization_algorithm), &type_string, validate_algo },
76e72f65
RK
1027 { C(broadcast), &type_netaddress, validate_destaddr },
1028 { C(broadcast_from), &type_netaddress, validate_any },
bd8895a8 1029 { C(channel), &type_string, validate_any },
460b9539 1030 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
1031 { C(checkpoint_min), &type_integer, validate_non_negative },
1032 { C(collection), &type_collections, validate_any },
e41a9999 1033 { C(connect), &type_netaddress, validate_destaddr },
b12be54a 1034 { C(cookie_key_lifetime), &type_integer, validate_positive },
2a1c84fb 1035 { C(cookie_login_lifetime), &type_integer, validate_positive },
8818b7fc 1036 { C(dbversion), &type_integer, validate_positive },
04e1fa7c 1037 { C(default_rights), &type_rights, validate_any },
460b9539 1038 { C(device), &type_string, validate_any },
460b9539 1039 { C(history), &type_integer, validate_positive },
9e42afcd 1040#if !_WIN32
659d87e8 1041 { C(home), &type_string, validate_isabspath },
9e42afcd 1042#endif
80dc2c5f 1043 { C(listen), &type_netaddress, validate_any },
bb6ae3fb 1044 { C(mail_sender), &type_string, validate_any },
bd8895a8 1045 { C(mixer), &type_string, validate_any },
8488cf7d 1046 { C(mount_rescan), &type_boolean, validate_any },
61941295 1047 { C(multicast_loop), &type_boolean, validate_any },
23205f9c 1048 { C(multicast_ttl), &type_integer, validate_non_negative },
460b9539 1049 { C(namepart), &type_namepart, validate_any },
05dcfac6
RK
1050 { C(new_bias), &type_integer, validate_positive },
1051 { C(new_bias_age), &type_integer, validate_positive },
d742bb47 1052 { C(new_max), &type_integer, validate_positive },
460b9539 1053 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
1054 { C(nice_rescan), &type_integer, validate_non_negative },
1055 { C(nice_server), &type_integer, validate_any },
1056 { C(nice_speaker), &type_integer, validate_any },
2a10b70b 1057 { C(noticed_history), &type_integer, validate_positive },
460b9539 1058 { C(password), &type_string, validate_any },
f75ab9d3 1059 { C(pause_mode), &type_string, validate_pausemode },
460b9539 1060 { C(player), &type_stringlist_accum, validate_player },
ddbf05c8 1061 { C(playlist_lock_timeout), &type_integer, validate_positive },
2563dc1f 1062 { C(playlist_max) , &type_integer, validate_positive },
460b9539 1063 { C(plugins), &type_string_accum, validate_isdir },
459d4402 1064 { C(queue_pad), &type_integer, validate_positive },
460b9539 1065 { C(refresh), &type_integer, validate_positive },
533272be 1066 { C(refresh_min), &type_integer, validate_non_negative },
6207d2f3 1067 { C(reminder_interval), &type_integer, validate_positive },
810b8083 1068 { C(remote_userman), &type_boolean, validate_any },
2a1c84fb 1069 { C(replay_min), &type_integer, validate_non_negative },
ba70caca 1070 { C(rtp_delay_threshold), &type_integer, validate_positive },
b0116b5c 1071 { C(rtp_mode), &type_string, validate_any },
87864f77 1072 { C(rtp_verbose), &type_boolean, validate_any },
9d5da576 1073 { C(sample_format), &type_sample_format, validate_sample_format },
460b9539 1074 { C(scratch), &type_string_accum, validate_isreg },
9e42afcd 1075#if !_WIN32
2eee4b0c 1076 { C(sendmail), &type_string, validate_isabspath },
9e42afcd 1077#endif
61507e3c 1078 { C(short_display), &type_integer, validate_positive },
460b9539 1079 { C(signal), &type_signal, validate_any },
bb6ae3fb 1080 { C(smtp_server), &type_string, validate_any },
5330d674 1081 { C(sox_generation), &type_integer, validate_non_negative },
9e42afcd 1082#if !_WIN32
b50cfb8a 1083 { C2(speaker_backend, api), &type_string, validate_backend },
9e42afcd 1084#endif
9d5da576 1085 { C(speaker_command), &type_string, validate_any },
460b9539 1086 { C(stopword), &type_string_accum, validate_any },
1087 { C(templates), &type_string_accum, validate_isdir },
62dc3748 1088 { C(tracklength), &type_stringlist_accum, validate_tracklength },
460b9539 1089 { C(transform), &type_transform, validate_any },
460b9539 1090 { C(url), &type_string, validate_url },
9e42afcd 1091#if !_WIN32
460b9539 1092 { C(user), &type_string, validate_isauser },
9e42afcd 1093#endif
460b9539 1094 { C(username), &type_string, validate_any },
1095};
1096
3f3bb97b 1097/** @brief Find a configuration item's definition by key */
460b9539 1098static const struct conf *find(const char *key) {
1099 int n;
1100
ba937f01 1101 if((n = TABLE_FIND(conf, name, key)) < 0)
460b9539 1102 return 0;
1103 return &conf[n];
1104}
1105
34d37b3e
RK
1106/** @brief Set a new configuration value
1107 * @param cs Configuration state
1108 * @param nvec Length of @p vec
1109 * @param vec Name and new value
1110 * @return 0 on success, non-0 on error.
1111 *
1112 * @c vec[0] is the name, the rest is the value.
1113 */
460b9539 1114static int config_set(const struct config_state *cs,
1115 int nvec, char **vec) {
1116 const struct conf *which;
1117
1118 D(("config_set %s", vec[0]));
1119 if(!(which = find(vec[0]))) {
2e9ba080 1120 disorder_error(0, "%s:%d: unknown configuration key '%s'",
460b9539 1121 cs->path, cs->line, vec[0]);
1122 return -1;
1123 }
1124 return (which->validate(cs, nvec - 1, vec + 1)
1125 || which->type->set(cs, which, nvec - 1, vec + 1));
1126}
1127
34d37b3e
RK
1128/** @brief Set a configuration item from parameters
1129 * @param cs Configuration state
1130 * @param which Item to set
1131 * @param ... Value as strings, terminated by (char *)NULL
1132 * @return 0 on success, non-0 on error
1133 */
e6a35d1c
RK
1134static int config_set_args(const struct config_state *cs,
1135 const char *which, ...) {
1136 va_list ap;
1137 struct vector v[1];
1138 char *s;
05b3f1f6 1139 int rc;
e6a35d1c
RK
1140
1141 vector_init(v);
1142 vector_append(v, (char *)which);
1143 va_start(ap, which);
1144 while((s = va_arg(ap, char *)))
1145 vector_append(v, s);
1146 va_end(ap);
1147 vector_terminate(v);
05b3f1f6 1148 rc = config_set(cs, v->nvec, v->vec);
47854c5f
RK
1149 xfree(v->vec);
1150 return rc;
e6a35d1c
RK
1151}
1152
34d37b3e
RK
1153/** @brief Error callback used by config_include()
1154 * @param msg Error message
1155 * @param u User data (@ref config_state)
1156 */
460b9539 1157static void config_error(const char *msg, void *u) {
1158 const struct config_state *cs = u;
1159
2e9ba080 1160 disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
460b9539 1161}
1162
34d37b3e
RK
1163/** @brief Include a file by name
1164 * @param c Configuration to update
1165 * @param path Path to read
1166 * @return 0 on success, non-0 on error
1167 */
460b9539 1168static int config_include(struct config *c, const char *path) {
1169 FILE *fp;
1170 char *buffer, *inputbuffer, **vec;
1171 int n, ret = 0;
1172 struct config_state cs;
1173
1174 cs.path = path;
1175 cs.line = 0;
1176 cs.config = c;
1177 D(("%s: reading configuration", path));
1178 if(!(fp = fopen(path, "r"))) {
2e9ba080 1179 disorder_error(errno, "error opening %s", path);
460b9539 1180 return -1;
1181 }
1182 while(!inputline(path, fp, &inputbuffer, '\n')) {
1183 ++cs.line;
1184 if(!(buffer = mb2utf8(inputbuffer))) {
2e9ba080 1185 disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
460b9539 1186 ret = -1;
1187 xfree(inputbuffer);
1188 continue;
1189 }
1190 xfree(inputbuffer);
1191 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1192 config_error, &cs))) {
1193 ret = -1;
1194 xfree(buffer);
1195 continue;
1196 }
1197 if(n) {
34d37b3e 1198 /* 'include' is special-cased */
460b9539 1199 if(!strcmp(vec[0], "include")) {
1200 if(n != 2) {
2e9ba080 1201 disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
460b9539 1202 ret = -1;
1203 } else
1204 config_include(c, vec[1]);
1205 } else
1206 ret |= config_set(&cs, n, vec);
1207 }
1208 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1209 xfree(vec);
1210 xfree(buffer);
1211 }
1212 if(ferror(fp)) {
2e9ba080 1213 disorder_error(errno, "error reading %s", path);
460b9539 1214 ret = -1;
1215 }
1216 fclose(fp);
1217 return ret;
1218}
1219
34d37b3e 1220/** @brief Default stopword setting */
86be0c30 1221static const char *const default_stopwords[] = {
1222 "stopword",
1223
1224 "01",
1225 "02",
1226 "03",
1227 "04",
1228 "05",
1229 "06",
1230 "07",
1231 "08",
1232 "09",
1233 "1",
1234 "10",
1235 "11",
1236 "12",
1237 "13",
1238 "14",
1239 "15",
1240 "16",
1241 "17",
1242 "18",
1243 "19",
1244 "2",
1245 "20",
1246 "21",
1247 "22",
1248 "23",
1249 "24",
1250 "25",
1251 "26",
1252 "27",
1253 "28",
1254 "29",
1255 "3",
1256 "30",
1257 "4",
1258 "5",
1259 "6",
1260 "7",
1261 "8",
1262 "9",
1263 "a",
1264 "am",
1265 "an",
1266 "and",
1267 "as",
1268 "for",
1269 "i",
1270 "im",
1271 "in",
1272 "is",
1273 "of",
1274 "on",
1275 "the",
1276 "to",
1277 "too",
1278 "we",
1279};
1280#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1281
34d37b3e 1282/** @brief Default player patterns */
e6a35d1c
RK
1283static const char *const default_players[] = {
1284 "*.ogg",
1285 "*.flac",
1286 "*.mp3",
1287 "*.wav",
1288};
1289#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1290
34d37b3e
RK
1291/** @brief Make a new default configuration
1292 * @return New configuration
1293 */
460b9539 1294static struct config *config_default(void) {
1295 struct config *c = xmalloc(sizeof *c);
9e42afcd 1296#if !_WIN32
460b9539 1297 const char *logname;
1298 struct passwd *pw;
9e42afcd 1299#endif
86be0c30 1300 struct config_state cs;
e6a35d1c 1301 size_t n;
460b9539 1302
86be0c30 1303 cs.path = "<internal>";
1304 cs.line = 0;
1305 cs.config = c;
460b9539 1306 /* Strings had better be xstrdup'd as they will get freed at some point. */
460b9539 1307 c->history = 60;
9e42afcd 1308#if !_WIN32
460b9539 1309 c->home = xstrdup(pkgstatedir);
9e42afcd
RK
1310#endif
1311#if _WIN32
1312 {
1313 char buffer[128];
1314 DWORD bufsize = sizeof buffer;
1315 if(!GetUserNameA(buffer, &bufsize))
1316 disorder_fatal(0, "cannot determine our username");
1317 c->username = xstrdup(buffer);
1318 }
1319#else
460b9539 1320 if(!(pw = getpwuid(getuid())))
2e9ba080 1321 disorder_fatal(0, "cannot determine our username");
460b9539 1322 logname = pw->pw_name;
1323 c->username = xstrdup(logname);
9e42afcd 1324#endif
460b9539 1325 c->refresh = 15;
533272be 1326 c->refresh_min = 1;
9e42afcd 1327#ifdef SIGKILL
460b9539 1328 c->signal = SIGKILL;
9e42afcd
RK
1329#else
1330 c->signal = SIGTERM;
1331#endif
460b9539 1332 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
460b9539 1333 c->device = xstrdup("default");
1334 c->nice_rescan = 10;
9d5da576 1335 c->speaker_command = 0;
1336 c->sample_format.bits = 16;
1337 c->sample_format.rate = 44100;
1338 c->sample_format.channels = 2;
6d2d327c 1339 c->sample_format.endian = ENDIAN_NATIVE;
459d4402 1340 c->queue_pad = 10;
cebe3127 1341 c->replay_min = 8 * 3600;
b50cfb8a 1342 c->api = NULL;
23205f9c 1343 c->multicast_ttl = 1;
61941295 1344 c->multicast_loop = 1;
637fdea3 1345 c->authorization_algorithm = xstrdup("sha1");
2a10b70b 1346 c->noticed_history = 31;
61507e3c 1347 c->short_display = 32;
bd8895a8 1348 c->mixer = 0;
1349 c->channel = 0;
8818b7fc 1350 c->dbversion = 2;
b12be54a
RK
1351 c->cookie_login_lifetime = 86400;
1352 c->cookie_key_lifetime = 86400 * 7;
9e42afcd 1353#if !_WIN32
2eee4b0c
RK
1354 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1355 c->sendmail = xstrdup(sendmail_binary);
9e42afcd 1356#endif
bb6ae3fb 1357 c->smtp_server = xstrdup("127.0.0.1");
d742bb47 1358 c->new_max = 100;
6207d2f3 1359 c->reminder_interval = 600; /* 10m */
05dcfac6 1360 c->new_bias_age = 7 * 86400; /* 1 week */
6151ae7e 1361 c->new_bias = 4500000; /* 50 times the base weight */
419893d7 1362 c->sox_generation = DEFAULT_SOX_GENERATION;
2563dc1f 1363 c->playlist_max = INT_MAX; /* effectively no limit */
ddbf05c8 1364 c->playlist_lock_timeout = 10; /* 10s */
8488cf7d 1365 c->mount_rescan = 1;
e6a35d1c 1366 /* Default stopwords */
86be0c30 1367 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1368 exit(1);
e6a35d1c
RK
1369 /* Default player configuration */
1370 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1371 if(config_set_args(&cs, "player",
1372 default_players[n], "execraw", "disorder-decode", (char *)0))
1373 exit(1);
1374 if(config_set_args(&cs, "tracklength",
1375 default_players[n], "disorder-tracklength", (char *)0))
1376 exit(1);
1377 }
76e72f65
RK
1378 c->broadcast.af = -1;
1379 c->broadcast_from.af = -1;
80dc2c5f 1380 c->listen.af = -1;
e41a9999 1381 c->connect.af = -1;
b0116b5c 1382 c->rtp_mode = xstrdup("auto");
460b9539 1383 return c;
1384}
1385
9e42afcd 1386#if !_WIN32
34d37b3e
RK
1387/** @brief Construct a filename
1388 * @param c Configuration
1389 * @param name Base filename
1390 * @return Full filename
1391 *
1392 * Usually use config_get_file() instead.
1393 */
319d7107 1394char *config_get_file2(struct config *c, const char *name) {
460b9539 1395 char *s;
1396
1397 byte_xasprintf(&s, "%s/%s", c->home, name);
1398 return s;
1399}
9e42afcd 1400#endif
460b9539 1401
3f3bb97b 1402/** @brief Set the default configuration file */
460b9539 1403static void set_configfile(void) {
9e42afcd 1404#if !_WIN32
460b9539 1405 if(!configfile)
1406 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
9e42afcd 1407#endif
460b9539 1408}
1409
34d37b3e
RK
1410/** @brief Free a configuration object
1411 * @param c Configuration to free
1412 *
1413 * @p c is indeterminate after this function is called.
1414 */
47854c5f 1415void config_free(struct config *c) {
460b9539 1416 int n;
1417
1418 if(c) {
1419 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1420 conf[n].type->free(c, &conf[n]);
1421 for(n = 0; n < c->nparts; ++n)
1422 xfree(c->parts[n]);
1423 xfree(c->parts);
1424 xfree(c);
1425 }
1426}
1427
34d37b3e
RK
1428/** @brief Set post-parse defaults
1429 * @param c Configuration to update
1430 * @param server True when running in the server
1431 *
1432 * If @p server is set then certain parts of the configuration are more
1433 * strictly validated.
1434 */
c00fce3a
RK
1435static void config_postdefaults(struct config *c,
1436 int server) {
460b9539 1437 struct config_state cs;
1438 const struct conf *whoami;
1439 int n;
1440
1441 static const char *namepart[][4] = {
bcf50f5c 1442 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
460b9539 1443 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1444 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1445 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1446 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1447 };
1448#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1449
1450 static const char *transform[][5] = {
bcf50f5c 1451 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
460b9539 1452 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1453 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1454 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1455 { "dir", "[[:punct:]]", "", "sort", "g", }
1456 };
1457#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1458
1459 cs.path = "<internal>";
1460 cs.line = 0;
1461 cs.config = c;
1462 if(!c->namepart.n) {
1463 whoami = find("namepart");
1464 for(n = 0; n < NNAMEPART; ++n)
1465 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1466 }
1467 if(!c->transform.n) {
1468 whoami = find("transform");
1469 for(n = 0; n < NTRANSFORM; ++n)
1470 set_transform(&cs, whoami, 5, (char **)transform[n]);
1471 }
b50cfb8a 1472 if(!c->api) {
e83d0967 1473 if(c->speaker_command)
b50cfb8a 1474 c->api = xstrdup("command");
76e72f65 1475 else if(c->broadcast.af != -1)
b50cfb8a 1476 c->api = xstrdup("rtp");
9e42afcd 1477#if !_WIN32
b50cfb8a 1478 else if(config_uaudio_apis)
06385470
RK
1479 c->api = xstrdup(uaudio_default(config_uaudio_apis,
1480 UAUDIO_API_SERVER)->name);
9e42afcd 1481#endif
8aae240b 1482 else
b50cfb8a 1483 c->api = xstrdup("<none>");
e83d0967 1484 }
b50cfb8a
RK
1485 if(!strcmp(c->api, "network"))
1486 c->api = xstrdup("rtp");
c00fce3a 1487 if(server) {
b50cfb8a 1488 if(!strcmp(c->api, "command") && !c->speaker_command)
2e9ba080 1489 disorder_fatal(0, "'api command' but speaker_command is not set");
76e72f65 1490 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
2e9ba080 1491 disorder_fatal(0, "'api rtp' but broadcast is not set");
c00fce3a 1492 }
e99d42b1 1493 /* Override sample format */
b50cfb8a 1494 if(!strcmp(c->api, "rtp")) {
6d2d327c
RK
1495 c->sample_format.rate = 44100;
1496 c->sample_format.channels = 2;
1497 c->sample_format.bits = 16;
b50cfb8a
RK
1498 c->sample_format.endian = ENDIAN_NATIVE;
1499 }
1500 if(!strcmp(c->api, "coreaudio")) {
937be4c0
RK
1501 c->sample_format.rate = 44100;
1502 c->sample_format.channels = 2;
1503 c->sample_format.bits = 16;
1504 c->sample_format.endian = ENDIAN_NATIVE;
04e1fa7c
RK
1505 }
1506 if(!c->default_rights) {
1507 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1508 |RIGHT_MOVE__MASK
1509 |RIGHT_SCRATCH__MASK
1510 |RIGHT_REMOVE__MASK);
657fdb79 1511 r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
0f55e905 1512 c->default_rights = rights_string(r);
04e1fa7c 1513 }
460b9539 1514}
1515
c00fce3a
RK
1516/** @brief (Re-)read the config file
1517 * @param server If set, do extra checking
02ba7921
RK
1518 * @param oldconfig Old configuration for compatibility check
1519 * @return 0 on success, non-0 on error
1520 *
1521 * If @p oldconfig is set, then certain compatibility checks are done between
1522 * the old and new configurations.
c00fce3a 1523 */
02ba7921
RK
1524int config_read(int server,
1525 const struct config *oldconfig) {
460b9539 1526 struct config *c;
1527 char *privconf;
9e42afcd 1528 struct passwd *pw = NULL;
460b9539 1529
1530 set_configfile();
1531 c = config_default();
9e42afcd
RK
1532 /* standalone client installs might not have a global config file */
1533 if(configfile)
1534 if(access(configfile, F_OK) == 0)
1535 if(config_include(c, configfile))
1536 return -1;
460b9539 1537 /* if we can read the private config file, do */
1538 if((privconf = config_private())
1539 && access(privconf, R_OK) == 0
1540 && config_include(c, privconf))
1541 return -1;
1542 xfree(privconf);
1543 /* if there's a per-user system config file for this user, read it */
63ad732f 1544 if(config_per_user) {
9e42afcd 1545#if !_WIN32
63ad732f 1546 if(!(pw = getpwuid(getuid())))
2e9ba080 1547 disorder_fatal(0, "cannot determine our username");
63ad732f
RK
1548 if((privconf = config_usersysconf(pw))
1549 && access(privconf, F_OK) == 0
1550 && config_include(c, privconf))
460b9539 1551 return -1;
63ad732f 1552 xfree(privconf);
9e42afcd 1553#endif
63ad732f 1554 /* if we have a password file, read it */
5b14453f 1555 if((privconf = config_userconf(0, pw))
63ad732f
RK
1556 && access(privconf, F_OK) == 0
1557 && config_include(c, privconf))
1558 return -1;
1559 xfree(privconf);
1560 }
460b9539 1561 /* install default namepart and transform settings */
c00fce3a 1562 config_postdefaults(c, server);
02ba7921
RK
1563 if(oldconfig) {
1564 int failed = 0;
9e42afcd 1565#if !_WIN32
02ba7921 1566 if(strcmp(c->home, oldconfig->home)) {
2e9ba080 1567 disorder_error(0, "'home' cannot be changed without a restart");
02ba7921
RK
1568 failed = 1;
1569 }
9e42afcd 1570#endif
02ba7921 1571 if(strcmp(c->alias, oldconfig->alias)) {
2e9ba080 1572 disorder_error(0, "'alias' cannot be changed without a restart");
02ba7921
RK
1573 failed = 1;
1574 }
1575 if(strcmp(c->user, oldconfig->user)) {
2e9ba080 1576 disorder_error(0, "'user' cannot be changed without a restart");
02ba7921
RK
1577 failed = 1;
1578 }
1579 if(c->nice_speaker != oldconfig->nice_speaker) {
2e9ba080 1580 disorder_error(0, "'nice_speaker' cannot be changed without a restart");
02ba7921
RK
1581 /* ...but we accept the new config anyway */
1582 }
553d8115 1583 if(c->nice_server != oldconfig->nice_server) {
2e9ba080 1584 disorder_error(0, "'nice_server' cannot be changed without a restart");
553d8115
RK
1585 /* ...but we accept the new config anyway */
1586 }
9417e1a3 1587 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
2e9ba080 1588 disorder_error(0, "'namepart' settings cannot be changed without a restart");
9417e1a3
RK
1589 failed = 1;
1590 }
1591 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
2e9ba080 1592 disorder_error(0, "'stopword' settings cannot be changed without a restart");
9417e1a3
RK
1593 failed = 1;
1594 }
02ba7921 1595 if(failed) {
2e9ba080 1596 disorder_error(0, "not installing incompatible new configuration");
02ba7921
RK
1597 return -1;
1598 }
1599 }
460b9539 1600 /* everything is good so we shall use the new config */
1601 config_free(config);
04e1fa7c 1602 /* warn about obsolete directives */
460b9539 1603 config = c;
1604 return 0;
1605}
1606
3f3bb97b 1607/** @brief Return the path to the private configuration file */
460b9539 1608char *config_private(void) {
9e42afcd
RK
1609#if _WIN32
1610 return NULL;
1611#else
460b9539 1612 char *s;
1613
1614 set_configfile();
1615 byte_xasprintf(&s, "%s.private", configfile);
1616 return s;
9e42afcd 1617#endif
460b9539 1618}
1619
3f3bb97b 1620/** @brief Return the path to user's personal configuration file */
460b9539 1621char *config_userconf(const char *home, const struct passwd *pw) {
1622 char *s;
9e42afcd
RK
1623#if _WIN32
1624 wchar_t *wpath = 0;
1625 char *appdata;
1626 if(SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &wpath) != S_OK)
1627 disorder_fatal(0, "error calling SHGetKnownFolderPath");
1628 appdata = win_wtomb(wpath);
1629 CoTaskMemFree(wpath);
1630 byte_xasprintf(&s, "%s\\DisOrder\\passwd", appdata);
1631#else
73f1b9f3 1632 if(!home && !pw && !(pw = getpwuid(getuid())))
2e9ba080 1633 disorder_fatal(0, "cannot determine our username");
460b9539 1634 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
9e42afcd 1635#endif
460b9539 1636 return s;
1637}
1638
9e42afcd 1639#if !_WIN32
3f3bb97b
RK
1640/** @brief Return the path to user-specific system configuration */
1641char *config_usersysconf(const struct passwd *pw) {
460b9539 1642 char *s;
1643
1644 set_configfile();
1645 if(!strchr(pw->pw_name, '/')) {
1646 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1647 return s;
1648 } else
1649 return 0;
1650}
1651
34d37b3e
RK
1652/** @brief Get a filename within the home directory
1653 * @param name Relative name
1654 * @return Full path
1655 */
460b9539 1656char *config_get_file(const char *name) {
319d7107 1657 return config_get_file2(config, name);
460b9539 1658}
9e42afcd 1659#endif
460b9539 1660
34d37b3e
RK
1661/** @brief Order two stringlists
1662 * @param a First stringlist
1663 * @param b Second stringlist
1664 * @return <0, 0 or >0 if a<b, a=b or a>b
1665 */
9417e1a3
RK
1666static int stringlist_compare(const struct stringlist *a,
1667 const struct stringlist *b) {
e4132fd0 1668 int n = 0, c;
9417e1a3
RK
1669
1670 while(n < a->n && n < b->n) {
1671 if((c = strcmp(a->s[n], b->s[n])))
1672 return c;
1673 ++n;
1674 }
1675 if(a->n < b->n)
1676 return -1;
1677 else if(a->n > b->n)
1678 return 1;
1679 else
1680 return 0;
1681}
1682
34d37b3e
RK
1683/** @brief Order two namepart definitions
1684 * @param a First namepart definition
1685 * @param b Second namepart definition
1686 * @return <0, 0 or >0 if a<b, a=b or a>b
1687 */
9417e1a3
RK
1688static int namepart_compare(const struct namepart *a,
1689 const struct namepart *b) {
1690 int c;
1691
1692 if((c = strcmp(a->part, b->part)))
1693 return c;
1694 if((c = strcmp(a->res, b->res)))
1695 return c;
1696 if((c = strcmp(a->replace, b->replace)))
1697 return c;
1698 if((c = strcmp(a->context, b->context)))
1699 return c;
1700 if(a->reflags > b->reflags)
1701 return 1;
1702 if(a->reflags < b->reflags)
1703 return -1;
1704 return 0;
1705}
1706
34d37b3e
RK
1707/** @brief Order two lists of namepart definitions
1708 * @param a First list of namepart definitions
1709 * @param b Second list of namepart definitions
1710 * @return <0, 0 or >0 if a<b, a=b or a>b
1711 */
9417e1a3
RK
1712static int namepartlist_compare(const struct namepartlist *a,
1713 const struct namepartlist *b) {
e4132fd0 1714 int n = 0, c;
9417e1a3
RK
1715
1716 while(n < a->n && n < b->n) {
1717 if((c = namepart_compare(&a->s[n], &b->s[n])))
1718 return c;
1719 ++n;
1720 }
1721 if(a->n > b->n)
1722 return 1;
1723 else if(a->n < b->n)
1724 return -1;
1725 else
1726 return 0;
1727}
1728
2a1c84fb
RK
1729/** @brief Verify configuration table.
1730 * @return The number of problems found
1731*/
1732int config_verify(void) {
1733 int fails = 0;
05b3f1f6
RK
1734 size_t n;
1735 for(n = 1; n < sizeof conf / sizeof *conf; ++n)
2a1c84fb
RK
1736 if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1737 fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1738 ++fails;
1739 }
1740 return fails;
1741}
1742
460b9539 1743/*
1744Local Variables:
1745c-basic-offset:2
1746comment-column:40
1747fill-column:79
1748End:
1749*/