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