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