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