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