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