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