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