chiark / gitweb /
5d3f919c24c672049322d8f5da4b42799afdf464
[disorder] / lib / configuration.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2004-2011, 2013 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 3 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,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU 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, see <http://www.gnu.org/licenses/>.
18  */
19 /** @file lib/configuration.c
20  * @brief Configuration file support
21  */
22
23 #include "common.h"
24
25 #include <errno.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #if HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31 #include <ctype.h>
32 #include <stddef.h>
33 #if HAVE_PWD_H
34 # include <pwd.h>
35 #endif
36 #if HAVE_LANGINFO_H
37 # include <langinfo.h>
38 #endif
39 #if HAVE_PCRE_H
40 # include <pcre.h>
41 #endif
42 #include <signal.h>
43
44 #include "rights.h"
45 #include "configuration.h"
46 #include "mem.h"
47 #include "log.h"
48 #include "split.h"
49 #include "syscalls.h"
50 #include "table.h"
51 #include "inputline.h"
52 #include "charset.h"
53 #include "defs.h"
54 #include "printf.h"
55 #if HAVE_PCRE_H
56 # include "regsub.h"
57 #endif
58 #include "signame.h"
59 #include "authhash.h"
60 #include "vector.h"
61 #include "uaudio.h"
62
63 /** @brief Path to config file 
64  *
65  * set_configfile() sets the default if it is null.
66  */
67 char *configfile;
68
69 /** @brief Read user configuration
70  *
71  * If clear, the user-specific configuration is not read.
72  */
73 int config_per_user = 1;
74
75 /** @brief Table of audio APIs
76  *
77  * Only set in server processes.
78  */
79 const struct uaudio *const *config_uaudio_apis;
80
81 /** @brief Config file parser state */
82 struct config_state {
83   /** @brief Filename */
84   const char *path;
85
86   /** @brief Line number */
87   int line;
88
89   /** @brief Configuration object under construction */
90   struct config *config;
91 };
92
93 /** @brief Current configuration */
94 struct config *config;
95
96 /** @brief One configuration item */
97 struct conf {
98   /** @brief Name as it appears in the config file */
99   const char *name;
100
101   /** @brief Offset in @ref config structure */
102   size_t offset;
103
104   /** @brief Pointer to item type */
105   const struct conftype *type;
106
107   /** @brief Pointer to item-specific validation routine
108    * @param cs Configuration state
109    * @param nvec Length of (proposed) new value
110    * @param vec Elements of new value
111    * @return 0 on success, non-0 on error
112    *
113    * The validate function should report any error it detects.
114    */
115   int (*validate)(const struct config_state *cs,
116                   int nvec, char **vec);
117 };
118
119 /** @brief Type of a configuration item */
120 struct conftype {
121   /** @brief Pointer to function to set item
122    * @param cs Configuration state
123    * @param whoami Configuration item to set
124    * @param nvec Length of new value
125    * @param vec New value
126    * @return 0 on success, non-0 on error
127    */
128   int (*set)(const struct config_state *cs,
129              const struct conf *whoami,
130              int nvec, char **vec);
131
132   /** @brief Pointer to function to free item
133    * @param c Configuration structure to free an item of
134    * @param whoami Configuration item to free
135    */
136   void (*free)(struct config *c, const struct conf *whoami);
137 };
138
139 /** @brief Compute the address of an item */
140 #define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
141 /** @brief Return the value of an item */
142 #define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
143
144 static int stringlist_compare(const struct stringlist *a,
145                               const struct stringlist *b);
146 static int namepartlist_compare(const struct namepartlist *a,
147                                 const struct namepartlist *b);
148
149 static int set_signal(const struct config_state *cs,
150                       const struct conf *whoami,
151                       int nvec, char **vec) {
152   int n;
153   
154   if(nvec != 1) {
155     disorder_error(0, "%s:%d: '%s' requires one argument",
156           cs->path, cs->line, whoami->name);
157     return -1;
158   }
159   if((n = find_signal(vec[0])) == -1) {
160     disorder_error(0, "%s:%d: unknown signal '%s'",
161           cs->path, cs->line, vec[0]);
162     return -1;
163   }
164   VALUE(cs->config, int) = n;
165   return 0;
166 }
167
168 static int set_collections(const struct config_state *cs,
169                            const struct conf *whoami,
170                            int nvec, char **vec) {
171   struct collectionlist *cl;
172   const char *root, *encoding, *module;
173   
174   switch(nvec) {
175   case 1:
176     module = 0;
177     encoding = 0;
178     root = vec[0];
179     break;
180   case 2:
181     module = vec[0];
182     encoding = 0;
183     root = vec[1];
184     break;
185   case 3:
186     module = vec[0];
187     encoding = vec[1];
188     root = vec[2];
189     break;
190   case 0:
191     disorder_error(0, "%s:%d: '%s' requires at least one argument",
192                    cs->path, cs->line, whoami->name);
193     return -1;
194   default:
195     disorder_error(0, "%s:%d: '%s' requires at most three arguments",
196                    cs->path, cs->line, whoami->name);
197     return -1;
198   }
199   /* Sanity check root */
200   if(root[0] != '/') {
201     disorder_error(0, "%s:%d: collection root must start with '/'",
202                    cs->path, cs->line);
203     return -1;
204   }
205   if(root[1] && root[strlen(root)-1] == '/') {
206     disorder_error(0, "%s:%d: collection root must not end with '/'",
207                    cs->path, cs->line);
208     return -1;
209   }
210   /* Defaults */
211   if(!module)
212     module = "fs";
213 #if HAVE_LANGINFO_H
214   if(!encoding)
215     encoding = nl_langinfo(CODESET);
216 #else
217   if(!encoding)
218     encoding = "ascii";
219 #endif
220   cl = ADDRESS(cs->config, struct collectionlist);
221   ++cl->n;
222   cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
223   cl->s[cl->n - 1].module = xstrdup(module);
224   cl->s[cl->n - 1].encoding = xstrdup(encoding);
225   cl->s[cl->n - 1].root = xstrdup(root);
226   return 0;
227 }
228
229 static int set_boolean(const struct config_state *cs,
230                        const struct conf *whoami,
231                        int nvec, char **vec) {
232   int state;
233   
234   if(nvec != 1) {
235     disorder_error(0, "%s:%d: '%s' takes only one argument",
236                    cs->path, cs->line, whoami->name);
237     return -1;
238   }
239   if(!strcmp(vec[0], "yes")) state = 1;
240   else if(!strcmp(vec[0], "no")) state = 0;
241   else {
242     disorder_error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
243                    cs->path, cs->line, whoami->name);
244     return -1;
245   }
246   VALUE(cs->config, int) = state;
247   return 0;
248 }
249
250 static int set_string(const struct config_state *cs,
251                       const struct conf *whoami,
252                       int nvec, char **vec) {
253   if(nvec != 1) {
254     disorder_error(0, "%s:%d: '%s' takes only one argument",
255                    cs->path, cs->line, whoami->name);
256     return -1;
257   }
258   xfree(VALUE(cs->config, char *));
259   VALUE(cs->config, char *) = xstrdup(vec[0]);
260   return 0;
261 }
262
263 static int set_stringlist(const struct config_state *cs,
264                           const struct conf *whoami,
265                           int nvec, char **vec) {
266   int n;
267   struct stringlist *sl;
268
269   sl = ADDRESS(cs->config, struct stringlist);
270   sl->n = 0;
271   for(n = 0; n < nvec; ++n) {
272     sl->n++;
273     sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
274     sl->s[sl->n - 1] = xstrdup(vec[n]);
275   }
276   return 0;
277 }
278
279 static int set_integer(const struct config_state *cs,
280                        const struct conf *whoami,
281                        int nvec, char **vec) {
282   char *e;
283
284   if(nvec != 1) {
285     disorder_error(0, "%s:%d: '%s' takes only one argument",
286                    cs->path, cs->line, whoami->name);
287     return -1;
288   }
289   if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
290     disorder_error(errno, "%s:%d: converting integer", cs->path, cs->line);
291     return -1;
292   }
293   if(*e) {
294     disorder_error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
295     return -1;
296   }
297   return 0;
298 }
299
300 static int set_stringlist_accum(const struct config_state *cs,
301                                 const struct conf *whoami,
302                                 int nvec, char **vec) {
303   int n;
304   struct stringlist *s;
305   struct stringlistlist *sll;
306
307   sll = ADDRESS(cs->config, struct stringlistlist);
308   if(nvec == 0) {
309     sll->n = 0;
310     return 0;
311   }
312   sll->n++;
313   sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
314   s = &sll->s[sll->n - 1];
315   s->n = nvec;
316   s->s = xmalloc((nvec + 1) * sizeof (char *));
317   for(n = 0; n < nvec; ++n)
318     s->s[n] = xstrdup(vec[n]);
319   return 0;
320 }
321
322 static int set_string_accum(const struct config_state *cs,
323                             const struct conf *whoami,
324                             int nvec, char **vec) {
325   int n;
326   struct stringlist *sl;
327
328   sl = ADDRESS(cs->config, struct stringlist);
329   if(nvec == 0) {
330     sl->n = 0;
331     return 0;
332   }
333   for(n = 0; n < nvec; ++n) {
334     sl->n++;
335     sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
336     sl->s[sl->n - 1] = xstrdup(vec[n]);
337   }
338   return 0;
339 }
340
341 static int parse_sample_format(const struct config_state *cs,
342                                struct stream_header *format,
343                                int nvec, char **vec) {
344   char *p = vec[0];
345   long t;
346
347   if(nvec != 1) {
348     disorder_error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
349     return -1;
350   }
351   if(xstrtol(&t, p, &p, 0)) {
352     disorder_error(errno, "%s:%d: converting bits-per-sample",
353                    cs->path, cs->line);
354     return -1;
355   }
356   if(t != 8 && t != 16) {
357     disorder_error(0, "%s:%d: bad bits-per-sample (%ld)",
358                    cs->path, cs->line, t);
359     return -1;
360   }
361   if(format) format->bits = t;
362   switch (*p) {
363     case 'l': case 'L': t = ENDIAN_LITTLE; p++; break;
364     case 'b': case 'B': t = ENDIAN_BIG; p++; break;
365     default: t = ENDIAN_NATIVE; break;
366   }
367   if(format) format->endian = t;
368   if(*p != '/') {
369     disorder_error(errno, "%s:%d: expected `/' after bits-per-sample",
370           cs->path, cs->line);
371     return -1;
372   }
373   p++;
374   if(xstrtol(&t, p, &p, 0)) {
375     disorder_error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
376     return -1;
377   }
378   if(t < 1 || t > INT_MAX) {
379     disorder_error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
380     return -1;
381   }
382   if(format) format->rate = t;
383   if(*p != '/') {
384     disorder_error(0, "%s:%d: expected `/' after sample-rate",
385                    cs->path, cs->line);
386     return -1;
387   }
388   p++;
389   if(xstrtol(&t, p, &p, 0)) {
390     disorder_error(errno, "%s:%d: converting channels", cs->path, cs->line);
391     return -1;
392   }
393   if(t < 1 || t > 8) {
394     disorder_error(0, "%s:%d: silly number (%ld) of channels",
395                    cs->path, cs->line, t);
396     return -1;
397   }
398   if(format) format->channels = t;
399   if(*p) {
400     disorder_error(0, "%s:%d: junk after channels", cs->path, cs->line);
401     return -1;
402   }
403   return 0;
404 }
405
406 static int set_sample_format(const struct config_state *cs,
407                              const struct conf *whoami,
408                              int nvec, char **vec) {
409   return parse_sample_format(cs, ADDRESS(cs->config, struct stream_header),
410                              nvec, vec);
411 }
412
413 #if HAVE_PCRE_H
414 static int set_namepart(const struct config_state *cs,
415                         const struct conf *whoami,
416                         int nvec, char **vec) {
417   struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
418   unsigned reflags;
419   const char *errstr;
420   int erroffset, n;
421   pcre *re;
422
423   if(nvec < 3) {
424     disorder_error(0, "%s:%d: namepart needs at least 3 arguments",
425                    cs->path, cs->line);
426     return -1;
427   }
428   if(nvec > 5) {
429     disorder_error(0, "%s:%d: namepart needs at most 5 arguments",
430                    cs->path, cs->line);
431     return -1;
432   }
433   reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
434   if(!(re = pcre_compile(vec[1],
435                          PCRE_UTF8
436                          |regsub_compile_options(reflags),
437                          &errstr, &erroffset, 0))) {
438     disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
439                    cs->path, cs->line, vec[1], errstr, erroffset);
440     return -1;
441   }
442   npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
443   npl->s[npl->n].part = xstrdup(vec[0]);
444   npl->s[npl->n].re = re;
445   npl->s[npl->n].res = xstrdup(vec[1]);
446   npl->s[npl->n].replace = xstrdup(vec[2]);
447   npl->s[npl->n].context = xstrdup(vec[3]);
448   npl->s[npl->n].reflags = reflags;
449   ++npl->n;
450   /* XXX a bit of a bodge; relies on there being very few parts. */
451   for(n = 0; (n < cs->config->nparts
452               && strcmp(cs->config->parts[n], vec[0])); ++n)
453     ;
454   if(n >= cs->config->nparts) {
455     cs->config->parts = xrealloc(cs->config->parts,
456                                  (cs->config->nparts + 1) * sizeof (char *));
457     cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
458   }
459   return 0;
460 }
461
462 static int set_transform(const struct config_state *cs,
463                          const struct conf *whoami,
464                          int nvec, char **vec) {
465   struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
466   pcre *re;
467   unsigned reflags;
468   const char *errstr;
469   int erroffset;
470
471   if(nvec < 3) {
472     disorder_error(0, "%s:%d: transform needs at least 3 arguments",
473                    cs->path, cs->line);
474     return -1;
475   }
476   if(nvec > 5) {
477     disorder_error(0, "%s:%d: transform needs at most 5 arguments",
478                    cs->path, cs->line);
479     return -1;
480   }
481   reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
482   if(!(re = pcre_compile(vec[1],
483                          PCRE_UTF8
484                          |regsub_compile_options(reflags),
485                          &errstr, &erroffset, 0))) {
486     disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
487                    cs->path, cs->line, vec[1], errstr, erroffset);
488     return -1;
489   }
490   tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
491   tl->t[tl->n].type = xstrdup(vec[0]);
492   tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
493   tl->t[tl->n].re = re;
494   tl->t[tl->n].replace = xstrdup(vec[2]);
495   tl->t[tl->n].flags = reflags;
496   ++tl->n;
497   return 0;
498 }
499 #endif
500
501 static int set_rights(const struct config_state *cs,
502                       const struct conf *whoami,
503                       int nvec, char **vec) {
504   if(nvec != 1) {
505     disorder_error(0, "%s:%d: '%s' requires one argument",
506                    cs->path, cs->line, whoami->name);
507     return -1;
508   }
509   if(parse_rights(vec[0], 0, 1)) {
510     disorder_error(0, "%s:%d: invalid rights string '%s'",
511                    cs->path, cs->line, vec[0]);
512     return -1;
513   }
514   return set_string(cs, whoami, nvec, vec);
515 }
516
517 static int set_netaddress(const struct config_state *cs,
518                           const struct conf *whoami,
519                           int nvec, char **vec) {
520   struct netaddress *na = ADDRESS(cs->config, struct netaddress);
521
522   if(netaddress_parse(na, nvec, vec)) {
523     disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
524     return -1;
525   }
526   return 0;
527 }
528
529 /* free functions */
530
531 static void free_none(struct config attribute((unused)) *c,
532                       const struct conf attribute((unused)) *whoami) {
533 }
534
535 static void free_string(struct config *c,
536                         const struct conf *whoami) {
537   xfree(VALUE(c, char *));
538   VALUE(c, char *) = 0;
539 }
540
541 static void free_stringlist(struct config *c,
542                             const struct conf *whoami) {
543   int n;
544   struct stringlist *sl = ADDRESS(c, struct stringlist);
545
546   for(n = 0; n < sl->n; ++n)
547     xfree(sl->s[n]);
548   xfree(sl->s);
549 }
550
551 static void free_stringlistlist(struct config *c,
552                                 const struct conf *whoami) {
553   int n, m;
554   struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
555   struct stringlist *sl;
556
557   for(n = 0; n < sll->n; ++n) {
558     sl = &sll->s[n];
559     for(m = 0; m < sl->n; ++m)
560       xfree(sl->s[m]);
561     xfree(sl->s);
562   }
563   xfree(sll->s);
564 }
565
566 static void free_collectionlist(struct config *c,
567                                 const struct conf *whoami) {
568   struct collectionlist *cll = ADDRESS(c, struct collectionlist);
569   struct collection *cl;
570   int n;
571
572   for(n = 0; n < cll->n; ++n) {
573     cl = &cll->s[n];
574     xfree(cl->module);
575     xfree(cl->encoding);
576     xfree(cl->root);
577   }
578   xfree(cll->s);
579 }
580
581 #if HAVE_PCRE_H
582 static void free_namepartlist(struct config *c,
583                               const struct conf *whoami) {
584   struct namepartlist *npl = ADDRESS(c, struct namepartlist);
585   struct namepart *np;
586   int n;
587
588   for(n = 0; n < npl->n; ++n) {
589     np = &npl->s[n];
590     xfree(np->part);
591     pcre_free(np->re);                  /* ...whatever pcre_free is set to. */
592     xfree(np->res);
593     xfree(np->replace);
594     xfree(np->context);
595   }
596   xfree(npl->s);
597 }
598
599 static void free_transformlist(struct config *c,
600                                const struct conf *whoami) {
601   struct transformlist *tl = ADDRESS(c, struct transformlist);
602   struct transform *t;
603   int n;
604
605   for(n = 0; n < tl->n; ++n) {
606     t = &tl->t[n];
607     xfree(t->type);
608     pcre_free(t->re);                   /* ...whatever pcre_free is set to. */
609     xfree(t->replace);
610     xfree(t->context);
611   }
612   xfree(tl->t);
613 }
614 #endif
615
616 static void free_netaddress(struct config *c,
617                             const struct conf *whoami) {
618   struct netaddress *na = ADDRESS(c, struct netaddress);
619
620   xfree(na->address);
621 }
622
623 /* configuration types */
624
625 static const struct conftype
626   type_signal = { set_signal, free_none },
627   type_collections = { set_collections, free_collectionlist },
628   type_boolean = { set_boolean, free_none },
629   type_string = { set_string, free_string },
630   type_stringlist = { set_stringlist, free_stringlist },
631   type_integer = { set_integer, free_none },
632   type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
633   type_string_accum = { set_string_accum, free_stringlist },
634   type_sample_format = { set_sample_format, free_none },
635 #if HAVE_PCRE_H
636   type_namepart = { set_namepart, free_namepartlist },
637   type_transform = { set_transform, free_transformlist },
638 #endif
639   type_netaddress = { set_netaddress, free_netaddress },
640   type_rights = { set_rights, free_string };
641
642 /* specific validation routine */
643
644 /** @brief Perform a test on a filename
645  * @param test Test function to call on mode bits
646  * @param what Type of file sought
647  *
648  * If @p test returns 0 then the file is not a @p what and an error
649  * is reported and -1 is returned.
650  */
651 #define VALIDATE_FILE(test, what) do {                  \
652   struct stat sb;                                       \
653   int n;                                                \
654                                                         \
655   for(n = 0; n < nvec; ++n) {                           \
656     if(stat(vec[n], &sb) < 0) {                         \
657       disorder_error(errno, "%s:%d: %s",                \
658                      cs->path, cs->line, vec[n]);       \
659       return -1;                                        \
660     }                                                   \
661     if(!test(sb.st_mode)) {                             \
662       disorder_error(0, "%s:%d: %s is not a %s",        \
663                      cs->path, cs->line, vec[n], what); \
664       return -1;                                        \
665     }                                                   \
666   }                                                     \
667 } while(0)
668
669 /** @brief Validate an absolute path
670  * @param cs Configuration state
671  * @param nvec Length of (proposed) new value
672  * @param vec Elements of new value
673  * @return 0 on success, non-0 on error
674  */
675 static int validate_isabspath(const struct config_state *cs,
676                               int nvec, char **vec) {
677   int n;
678
679   for(n = 0; n < nvec; ++n)
680     if(vec[n][0] != '/') {
681       disorder_error(0, "%s:%d: %s: not an absolute path", 
682                      cs->path, cs->line, vec[n]);
683       return -1;
684     }
685   return 0;
686 }
687
688 /** @brief Validate an existing directory
689  * @param cs Configuration state
690  * @param nvec Length of (proposed) new value
691  * @param vec Elements of new value
692  * @return 0 on success, non-0 on error
693  */
694 static int validate_isdir(const struct config_state *cs,
695                           int nvec, char **vec) {
696   VALIDATE_FILE(S_ISDIR, "directory");
697   return 0;
698 }
699
700 /** @brief Validate an existing regular file
701  * @param cs Configuration state
702  * @param nvec Length of (proposed) new value
703  * @param vec Elements of new value
704  * @return 0 on success, non-0 on error
705  */
706 static int validate_isreg(const struct config_state *cs,
707                           int nvec, char **vec) {
708   VALIDATE_FILE(S_ISREG, "regular file");
709   return 0;
710 }
711
712 /** @brief Validate a player pattern
713  * @param cs Configuration state
714  * @param nvec Length of (proposed) new value
715  * @param vec Elements of new value
716  * @return 0 on success, non-0 on error
717  */
718 static int validate_player(const struct config_state *cs,
719                            int nvec,
720                            char attribute((unused)) **vec) {
721   if(nvec && nvec < 2) {
722     disorder_error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
723                    cs->path, cs->line);
724     return -1;
725   }
726   return 0;
727 }
728
729 /** @brief Validate a track length pattern
730  * @param cs Configuration state
731  * @param nvec Length of (proposed) new value
732  * @param vec Elements of new value
733  * @return 0 on success, non-0 on error
734  */
735 static int validate_tracklength(const struct config_state *cs,
736                                 int nvec,
737                                 char attribute((unused)) **vec) {
738   if(nvec && nvec < 2) {
739     disorder_error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
740                    cs->path, cs->line);
741     return -1;
742   }
743   return 0;
744 }
745
746 /** @brief Validate a non-negative (@c long) integer
747  * @param cs Configuration state
748  * @param nvec Length of (proposed) new value
749  * @param vec Elements of new value
750  * @return 0 on success, non-0 on error
751  */
752 static int validate_non_negative(const struct config_state *cs,
753                                  int nvec, char **vec) {
754   long n;
755
756   if(nvec < 1) {
757     disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
758     return -1;
759   }
760   if(nvec > 1) {
761     disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
762     return -1;
763   }
764   if(xstrtol(&n, vec[0], 0, 0)) {
765     disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
766     return -1;
767   }
768   if(n < 0) {
769     disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
770     return -1;
771   }
772   return 0;
773 }
774
775 /** @brief Validate a positive (@c long) integer
776  * @param cs Configuration state
777  * @param nvec Length of (proposed) new value
778  * @param vec Elements of new value
779  * @return 0 on success, non-0 on error
780  */
781 static int validate_positive(const struct config_state *cs,
782                           int nvec, char **vec) {
783   long n;
784
785   if(nvec < 1) {
786     disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
787     return -1;
788   }
789   if(nvec > 1) {
790     disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
791     return -1;
792   }
793   if(xstrtol(&n, vec[0], 0, 0)) {
794     disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
795     return -1;
796   }
797   if(n <= 0) {
798     disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
799     return -1;
800   }
801   return 0;
802 }
803
804 /** @brief Validate a system username
805  * @param cs Configuration state
806  * @param nvec Length of (proposed) new value
807  * @param vec Elements of new value
808  * @return 0 on success, non-0 on error
809  */
810 static int validate_isauser(const struct config_state *cs,
811                             int attribute((unused)) nvec,
812                             char **vec) {
813   if(!getpwnam(vec[0])) {
814     disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
815     return -1;
816   }
817   return 0;
818 }
819
820 /** @brief Validate a sample format string
821  * @param cs Configuration state
822  * @param nvec Length of (proposed) new value
823  * @param vec Elements of new value
824  * @return 0 on success, non-0 on error
825  */
826 static int validate_sample_format(const struct config_state *cs,
827                                   int attribute((unused)) nvec,
828                                   char **vec) {
829   return parse_sample_format(cs, 0, nvec, vec);
830 }
831
832 /** @brief Validate anything
833  * @param cs Configuration state
834  * @param nvec Length of (proposed) new value
835  * @param vec Elements of new value
836  * @return 0
837  */
838 static int validate_any(const struct config_state attribute((unused)) *cs,
839                         int attribute((unused)) nvec,
840                         char attribute((unused)) **vec) {
841   return 0;
842 }
843
844 /** @brief Validate a URL
845  * @param cs Configuration state
846  * @param nvec Length of (proposed) new value
847  * @param vec Elements of new value
848  * @return 0 on success, non-0 on error
849  *
850  * Rather cursory.
851  */
852 static int validate_url(const struct config_state attribute((unused)) *cs,
853                         int attribute((unused)) nvec,
854                         char **vec) {
855   const char *s;
856   int n;
857   /* absoluteURI   = scheme ":" ( hier_part | opaque_part )
858      scheme        = alpha *( alpha | digit | "+" | "-" | "." ) */
859   s = vec[0];
860   n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
861                  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
862                  "0123456789"));
863   if(s[n] != ':') {
864     disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
865     return -1;
866   }
867   if(!strncmp(s, "http:", 5)
868      || !strncmp(s, "https:", 6)) {
869     s += n + 1;
870     /* we only do a rather cursory check */
871     if(strncmp(s, "//", 2)) {
872       disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
873       return -1;
874     }
875   }
876   return 0;
877 }
878
879 /** @brief Validate an alias pattern
880  * @param cs Configuration state
881  * @param nvec Length of (proposed) new value
882  * @param vec Elements of new value
883  * @return 0 on success, non-0 on error
884  */
885 static int validate_alias(const struct config_state *cs,
886                           int nvec,
887                           char **vec) {
888   const char *s;
889   int in_brackets = 0, c;
890
891   if(nvec < 1) {
892     disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
893     return -1;
894   }
895   if(nvec > 1) {
896     disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
897     return -1;
898   }
899   s = vec[0];
900   while((c = (unsigned char)*s++)) {
901     if(in_brackets) {
902       if(c == '}')
903         in_brackets = 0;
904       else if(!isalnum(c)) {
905         disorder_error(0, "%s:%d: invalid part name in alias expansion in '%s'",
906                        cs->path, cs->line, vec[0]);
907           return -1;
908       }
909     } else {
910       if(c == '{') {
911         in_brackets = 1;
912         if(*s == '/')
913           ++s;
914       } else if(c == '\\') {
915         if(!(c = (unsigned char)*s++)) {
916           disorder_error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
917                          cs->path, cs->line, vec[0]);
918           return -1;
919         } else if(c != '\\' && c != '{') {
920           disorder_error(0, "%s:%d: invalid escape in alias expansion in '%s'",
921                          cs->path, cs->line, vec[0]);
922           return -1;
923         }
924       }
925     }
926     ++s;
927   }
928   if(in_brackets) {
929     disorder_error(0,
930                    "%s:%d: unterminated part name in alias expansion in '%s'",
931                    cs->path, cs->line, vec[0]);
932     return -1;
933   }
934   return 0;
935 }
936
937 /** @brief Validate a hash algorithm name
938  * @param cs Configuration state
939  * @param nvec Length of (proposed) new value
940  * @param vec Elements of new value
941  * @return 0 on success, non-0 on error
942  */
943 static int validate_algo(const struct config_state attribute((unused)) *cs,
944                          int nvec,
945                          char **vec) {
946   if(nvec != 1) {
947     disorder_error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
948     return -1;
949   }
950   if(!valid_authhash(vec[0])) {
951     disorder_error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
952     return -1;
953   }
954   return 0;
955 }
956
957 /** @brief Validate a playback backend name
958  * @param cs Configuration state
959  * @param nvec Length of (proposed) new value
960  * @param vec Elements of new value
961  * @return 0 on success, non-0 on error
962  */
963 static int validate_backend(const struct config_state attribute((unused)) *cs,
964                             int nvec,
965                             char **vec) {
966   int n;
967   if(nvec != 1) {
968     disorder_error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
969     return -1;
970   }
971   if(!strcmp(vec[0], "network")) {
972     disorder_error(0, "'api network' is deprecated; use 'api rtp'");
973     return 0;
974   }
975   if(config_uaudio_apis) {
976     for(n = 0; config_uaudio_apis[n]; ++n)
977       if(!strcmp(vec[0], config_uaudio_apis[n]->name))
978         return 0;
979     disorder_error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
980     return -1;
981   }
982   /* In non-server processes we have no idea what's valid */
983   return 0;
984 }
985
986 /** @brief Validate a pause mode string
987  * @param cs Configuration state
988  * @param nvec Length of (proposed) new value
989  * @param vec Elements of new value
990  * @return 0 on success, non-0 on error
991  */
992 static int validate_pausemode(const struct config_state attribute((unused)) *cs,
993                               int nvec,
994                               char **vec) {
995   if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
996     return 0;
997   disorder_error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
998   return -1;
999 }
1000
1001 /** @brief Validate a destination network address
1002  * @param cs Configuration state
1003  * @param nvec Length of (proposed) new value
1004  * @param vec Elements of new value
1005  * @return 0 on success, non-0 on error
1006  *
1007  * By a destination address, it is meant that it must not be a wildcard
1008  * address.
1009  */
1010 static int validate_destaddr(const struct config_state attribute((unused)) *cs,
1011                              int nvec,
1012                              char **vec) {
1013   struct netaddress na[1];
1014
1015   if(netaddress_parse(na, nvec, vec)) {
1016     disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
1017     return -1;
1018   }
1019   if(!na->address) {
1020     disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
1021     return -1;
1022   }
1023   xfree(na->address);
1024   return 0;
1025 }
1026
1027 /** @brief Item name and and offset */
1028 #define C(x) #x, offsetof(struct config, x)
1029 /** @brief Item name and and offset */
1030 #define C2(x,y) #x, offsetof(struct config, y)
1031
1032 /** @brief All configuration items */
1033 static const struct conf conf[] = {
1034   { C(alias),            &type_string,           validate_alias },
1035   { C(api),              &type_string,           validate_backend },
1036   { C(authorization_algorithm), &type_string,    validate_algo },
1037   { C(broadcast),        &type_netaddress,       validate_destaddr },
1038   { C(broadcast_from),   &type_netaddress,       validate_any },
1039   { C(channel),          &type_string,           validate_any },
1040   { C(checkpoint_kbyte), &type_integer,          validate_non_negative },
1041   { C(checkpoint_min),   &type_integer,          validate_non_negative },
1042   { C(collection),       &type_collections,      validate_any },
1043   { C(connect),          &type_netaddress,       validate_destaddr },
1044   { C(cookie_key_lifetime),  &type_integer,      validate_positive },
1045   { C(cookie_login_lifetime),  &type_integer,    validate_positive },
1046   { C(dbversion),        &type_integer,          validate_positive },
1047   { C(default_rights),   &type_rights,           validate_any },
1048   { C(device),           &type_string,           validate_any },
1049   { C(history),          &type_integer,          validate_positive },
1050   { C(home),             &type_string,           validate_isabspath },
1051   { C(listen),           &type_netaddress,       validate_any },
1052   { C(mail_sender),      &type_string,           validate_any },
1053   { C(mixer),            &type_string,           validate_any },
1054   { C(mount_rescan),     &type_boolean,          validate_any },
1055   { C(multicast_loop),   &type_boolean,          validate_any },
1056   { C(multicast_ttl),    &type_integer,          validate_non_negative },
1057 #if HAVE_PCRE_H
1058   { C(namepart),         &type_namepart,         validate_any },
1059 #endif
1060   { C(new_bias),         &type_integer,          validate_positive },
1061   { C(new_bias_age),     &type_integer,          validate_positive },
1062   { C(new_max),          &type_integer,          validate_positive },
1063   { C2(nice, nice_rescan), &type_integer,        validate_non_negative },
1064   { C(nice_rescan),      &type_integer,          validate_non_negative },
1065   { C(nice_server),      &type_integer,          validate_any },
1066   { C(nice_speaker),     &type_integer,          validate_any },
1067   { C(noticed_history),  &type_integer,          validate_positive },
1068   { C(password),         &type_string,           validate_any },
1069   { C(pause_mode),       &type_string,           validate_pausemode },
1070   { C(player),           &type_stringlist_accum, validate_player },
1071   { C(playlist_lock_timeout), &type_integer,     validate_positive },
1072   { C(playlist_max) ,    &type_integer,          validate_positive },
1073   { C(plugins),          &type_string_accum,     validate_isdir },
1074   { C(queue_pad),        &type_integer,          validate_positive },
1075   { C(refresh),          &type_integer,          validate_positive },
1076   { C(refresh_min),      &type_integer,          validate_non_negative },
1077   { C(reminder_interval), &type_integer,         validate_positive },
1078   { C(remote_userman),   &type_boolean,          validate_any },
1079   { C(replay_min),       &type_integer,          validate_non_negative },
1080   { C(rtp_delay_threshold), &type_integer,       validate_positive },
1081   { C(rtp_mode),         &type_string,           validate_any },
1082   { C(rtp_verbose),      &type_boolean,          validate_any },
1083   { C(sample_format),    &type_sample_format,    validate_sample_format },
1084   { C(scratch),          &type_string_accum,     validate_isreg },
1085   { C(sendmail),         &type_string,           validate_isabspath },
1086   { C(short_display),    &type_integer,          validate_positive },
1087   { C(signal),           &type_signal,           validate_any },
1088   { C(smtp_server),      &type_string,           validate_any },
1089   { C(sox_generation),   &type_integer,          validate_non_negative },
1090   { C2(speaker_backend, api),  &type_string,     validate_backend },
1091   { C(speaker_command),  &type_string,           validate_any },
1092   { C(stopword),         &type_string_accum,     validate_any },
1093   { C(templates),        &type_string_accum,     validate_isdir },
1094   { C(tracklength),      &type_stringlist_accum, validate_tracklength },
1095 #if HAVE_PCRE_H
1096   { C(transform),        &type_transform,        validate_any },
1097 #endif
1098   { C(url),              &type_string,           validate_url },
1099   { C(user),             &type_string,           validate_isauser },
1100   { C(username),         &type_string,           validate_any },
1101 };
1102
1103 /** @brief Find a configuration item's definition by key */
1104 static const struct conf *find(const char *key) {
1105   int n;
1106
1107   if((n = TABLE_FIND(conf, name, key)) < 0)
1108     return 0;
1109   return &conf[n];
1110 }
1111
1112 /** @brief Set a new configuration value
1113  * @param cs Configuration state
1114  * @param nvec Length of @p vec
1115  * @param vec Name and new value
1116  * @return 0 on success, non-0 on error.
1117  *
1118  * @c vec[0] is the name, the rest is the value.
1119  */
1120 static int config_set(const struct config_state *cs,
1121                       int nvec, char **vec) {
1122   const struct conf *which;
1123
1124   D(("config_set %s", vec[0]));
1125   if(!(which = find(vec[0]))) {
1126     disorder_error(0, "%s:%d: unknown configuration key '%s'",
1127           cs->path, cs->line, vec[0]);
1128     return -1;
1129   }
1130   return (which->validate(cs, nvec - 1, vec + 1)
1131           || which->type->set(cs, which, nvec - 1, vec + 1));
1132 }
1133
1134 /** @brief Set a configuration item from parameters
1135  * @param cs Configuration state
1136  * @param which Item to set
1137  * @param ... Value as strings, terminated by (char *)NULL
1138  * @return 0 on success, non-0 on error
1139  */
1140 static int config_set_args(const struct config_state *cs,
1141                            const char *which, ...) {
1142   va_list ap;
1143   struct vector v[1];
1144   char *s;
1145   int rc;
1146
1147   vector_init(v);
1148   vector_append(v, (char *)which);
1149   va_start(ap, which);
1150   while((s = va_arg(ap, char *)))
1151     vector_append(v, s);
1152   va_end(ap);
1153   vector_terminate(v);
1154   rc = config_set(cs, v->nvec, v->vec);
1155   xfree(v->vec);
1156   return rc;
1157 }
1158
1159 /** @brief Error callback used by config_include()
1160  * @param msg Error message
1161  * @param u User data (@ref config_state)
1162  */
1163 static void config_error(const char *msg, void *u) {
1164   const struct config_state *cs = u;
1165
1166   disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
1167 }
1168
1169 /** @brief Include a file by name
1170  * @param c Configuration to update
1171  * @param path Path to read
1172  * @return 0 on success, non-0 on error
1173  */
1174 static int config_include(struct config *c, const char *path) {
1175   FILE *fp;
1176   char *buffer, *inputbuffer, **vec;
1177   int n, ret = 0;
1178   struct config_state cs;
1179
1180   cs.path = path;
1181   cs.line = 0;
1182   cs.config = c;
1183   D(("%s: reading configuration", path));
1184   if(!(fp = fopen(path, "r"))) {
1185     disorder_error(errno, "error opening %s", path);
1186     return -1;
1187   }
1188   while(!inputline(path, fp, &inputbuffer, '\n')) {
1189     ++cs.line;
1190     if(!(buffer = mb2utf8(inputbuffer))) {
1191       disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1192       ret = -1;
1193       xfree(inputbuffer);
1194       continue;
1195     }
1196     xfree(inputbuffer);
1197     if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1198                      config_error, &cs))) {
1199       ret = -1;
1200       xfree(buffer);
1201       continue;
1202     }
1203     if(n) {
1204       /* 'include' is special-cased */
1205       if(!strcmp(vec[0], "include")) {
1206         if(n != 2) {
1207           disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1208           ret = -1;
1209         } else
1210           config_include(c, vec[1]);
1211       } else
1212         ret |= config_set(&cs, n, vec);
1213     }
1214     for(n = 0; vec[n]; ++n) xfree(vec[n]);
1215     xfree(vec);
1216     xfree(buffer);
1217   }
1218   if(ferror(fp)) {
1219     disorder_error(errno, "error reading %s", path);
1220     ret = -1;
1221   }
1222   fclose(fp);
1223   return ret;
1224 }
1225
1226 /** @brief Default stopword setting */
1227 static const char *const default_stopwords[] = {
1228   "stopword",
1229
1230   "01",
1231   "02",
1232   "03",
1233   "04",
1234   "05",
1235   "06",
1236   "07",
1237   "08",
1238   "09",
1239   "1",
1240   "10",
1241   "11",
1242   "12",
1243   "13",
1244   "14",
1245   "15",
1246   "16",
1247   "17",
1248   "18",
1249   "19",
1250   "2",
1251   "20",
1252   "21",
1253   "22",
1254   "23",
1255   "24",
1256   "25",
1257   "26",
1258   "27",
1259   "28",
1260   "29",
1261   "3",
1262   "30",
1263   "4",
1264   "5",
1265   "6",
1266   "7",
1267   "8",
1268   "9",
1269   "a",
1270   "am",
1271   "an",
1272   "and",
1273   "as",
1274   "for",
1275   "i",
1276   "im",
1277   "in",
1278   "is",
1279   "of",
1280   "on",
1281   "the",
1282   "to",
1283   "too",
1284   "we",
1285 };
1286 #define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1287
1288 /** @brief Default player patterns */
1289 static const char *const default_players[] = {
1290   "*.ogg",
1291   "*.flac",
1292   "*.mp3",
1293   "*.wav",
1294 };
1295 #define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1296
1297 /** @brief Make a new default configuration
1298  * @return New configuration
1299  */
1300 static struct config *config_default(void) {
1301   struct config *c = xmalloc(sizeof *c);
1302   const char *logname;
1303   struct passwd *pw;
1304   struct config_state cs;
1305   size_t n;
1306
1307   cs.path = "<internal>";
1308   cs.line = 0;
1309   cs.config = c;
1310   /* Strings had better be xstrdup'd as they will get freed at some point. */
1311   c->history = 60;
1312   c->home = xstrdup(pkgstatedir);
1313   if(!(pw = getpwuid(getuid())))
1314     disorder_fatal(0, "cannot determine our username");
1315   logname = pw->pw_name;
1316   c->username = xstrdup(logname);
1317   c->refresh = 15;
1318   c->refresh_min = 1;
1319   c->signal = SIGKILL;
1320   c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1321   c->device = xstrdup("default");
1322   c->nice_rescan = 10;
1323   c->speaker_command = 0;
1324   c->sample_format.bits = 16;
1325   c->sample_format.rate = 44100;
1326   c->sample_format.channels = 2;
1327   c->sample_format.endian = ENDIAN_NATIVE;
1328   c->queue_pad = 10;
1329   c->replay_min = 8 * 3600;
1330   c->api = NULL;
1331   c->multicast_ttl = 1;
1332   c->multicast_loop = 1;
1333   c->authorization_algorithm = xstrdup("sha1");
1334   c->noticed_history = 31;
1335   c->short_display = 32;
1336   c->mixer = 0;
1337   c->channel = 0;
1338   c->dbversion = 2;
1339   c->cookie_login_lifetime = 86400;
1340   c->cookie_key_lifetime = 86400 * 7;
1341   if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1342     c->sendmail = xstrdup(sendmail_binary);
1343   c->smtp_server = xstrdup("127.0.0.1");
1344   c->new_max = 100;
1345   c->reminder_interval = 600;           /* 10m */
1346   c->new_bias_age = 7 * 86400;          /* 1 week */
1347   c->new_bias = 4500000;                /* 50 times the base weight */
1348   c->sox_generation = DEFAULT_SOX_GENERATION;
1349   c->playlist_max = INT_MAX;            /* effectively no limit */
1350   c->playlist_lock_timeout = 10;        /* 10s */
1351   c->mount_rescan = 1;
1352   /* Default stopwords */
1353   if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1354     exit(1);
1355   /* Default player configuration */
1356   for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1357     if(config_set_args(&cs, "player",
1358                        default_players[n], "execraw", "disorder-decode", (char *)0))
1359       exit(1);
1360     if(config_set_args(&cs, "tracklength",
1361                        default_players[n], "disorder-tracklength", (char *)0))
1362       exit(1);
1363   }
1364   c->broadcast.af = -1;
1365   c->broadcast_from.af = -1;
1366   c->listen.af = -1;
1367   c->connect.af = -1;
1368   c->rtp_mode = xstrdup("auto");
1369   return c;
1370 }
1371
1372 /** @brief Construct a filename
1373  * @param c Configuration
1374  * @param name Base filename
1375  * @return Full filename
1376  *
1377  * Usually use config_get_file() instead.
1378  */
1379 char *config_get_file2(struct config *c, const char *name) {
1380   char *s;
1381
1382   byte_xasprintf(&s, "%s/%s", c->home, name);
1383   return s;
1384 }
1385
1386 /** @brief Set the default configuration file */
1387 static void set_configfile(void) {
1388   if(!configfile)
1389     byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1390 }
1391
1392 /** @brief Free a configuration object
1393  * @param c Configuration to free
1394  *
1395  * @p c is indeterminate after this function is called.
1396  */
1397 void config_free(struct config *c) {
1398   int n;
1399
1400   if(c) {
1401     for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1402       conf[n].type->free(c, &conf[n]);
1403     for(n = 0; n < c->nparts; ++n)
1404       xfree(c->parts[n]);
1405     xfree(c->parts);
1406     xfree(c);
1407   }
1408 }
1409
1410 /** @brief Set post-parse defaults
1411  * @param c Configuration to update
1412  * @param server True when running in the server
1413  *
1414  * If @p server is set then certain parts of the configuration are more
1415  * strictly validated.
1416  */
1417 static void config_postdefaults(struct config *c,
1418                                 int server) {
1419   struct config_state cs;
1420 #if HAVE_PCRE_H
1421   const struct conf *whoami;
1422   int n;
1423 #endif
1424
1425 #if HAVE_PCRE_H
1426   static const char *namepart[][4] = {
1427     { "title",  "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1428     { "title",  "/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort" },
1429     { "album",  "/([^/]+)/[^/]+$",                    "$1", "*" },
1430     { "artist", "/([^/]+)/[^/]+/[^/]+$",              "$1", "*" },
1431     { "ext",    "(\\.[a-zA-Z0-9]+)$",                 "$1", "*" },
1432   };
1433 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1434
1435   static const char *transform[][5] = {
1436     { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1437     { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort", "" },
1438     { "dir",   "^.*/([^/]+)$",                          "$1", "*", "" },
1439     { "dir",   "^(the) ([^/]*)",                        "$2, $1", "sort", "i", },
1440     { "dir",   "[[:punct:]]",                           "", "sort", "g", }
1441   };
1442 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1443 #endif
1444
1445   cs.path = "<internal>";
1446   cs.line = 0;
1447   cs.config = c;
1448 #if HAVE_PCRE_H
1449   if(!c->namepart.n) {
1450     whoami = find("namepart");
1451     for(n = 0; n < NNAMEPART; ++n)
1452       set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1453   }
1454   if(!c->transform.n) {
1455     whoami = find("transform");
1456     for(n = 0; n < NTRANSFORM; ++n)
1457       set_transform(&cs, whoami, 5, (char **)transform[n]);
1458   }
1459 #endif
1460   if(!c->api) {
1461     if(c->speaker_command)
1462       c->api = xstrdup("command");
1463     else if(c->broadcast.af != -1)
1464       c->api = xstrdup("rtp");
1465     else if(config_uaudio_apis)
1466       c->api = xstrdup(uaudio_default(config_uaudio_apis,
1467                                       UAUDIO_API_SERVER)->name);
1468     else
1469       c->api = xstrdup("<none>");
1470   }
1471   if(!strcmp(c->api, "network"))
1472     c->api = xstrdup("rtp");
1473   if(server) {
1474     if(!strcmp(c->api, "command") && !c->speaker_command)
1475       disorder_fatal(0, "'api command' but speaker_command is not set");
1476     if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1477       disorder_fatal(0, "'api rtp' but broadcast is not set");
1478   }
1479   /* Override sample format */
1480   if(!strcmp(c->api, "rtp")) {
1481     c->sample_format.rate = 44100;
1482     c->sample_format.channels = 2;
1483     c->sample_format.bits = 16;
1484     c->sample_format.endian = ENDIAN_NATIVE;
1485   }
1486   if(!strcmp(c->api, "coreaudio")) {
1487     c->sample_format.rate = 44100;
1488     c->sample_format.channels = 2;
1489     c->sample_format.bits = 16;
1490     c->sample_format.endian = ENDIAN_NATIVE;
1491   }
1492   if(!c->default_rights) {
1493     rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1494                                      |RIGHT_MOVE__MASK
1495                                      |RIGHT_SCRATCH__MASK
1496                                      |RIGHT_REMOVE__MASK);
1497     r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
1498     c->default_rights = rights_string(r);
1499   }
1500 }
1501
1502 /** @brief (Re-)read the config file
1503  * @param server If set, do extra checking
1504  * @param oldconfig Old configuration for compatibility check
1505  * @return 0 on success, non-0 on error
1506  *
1507  * If @p oldconfig is set, then certain compatibility checks are done between
1508  * the old and new configurations.
1509  */
1510 int config_read(int server,
1511                 const struct config *oldconfig) {
1512   struct config *c;
1513   char *privconf;
1514   struct passwd *pw;
1515
1516   set_configfile();
1517   c = config_default();
1518   /* standalone Disobedience installs might not have a global config file */
1519   if(access(configfile, F_OK) == 0)
1520     if(config_include(c, configfile))
1521       return -1;
1522   /* if we can read the private config file, do */
1523   if((privconf = config_private())
1524      && access(privconf, R_OK) == 0
1525      && config_include(c, privconf))
1526     return -1;
1527   xfree(privconf);
1528   /* if there's a per-user system config file for this user, read it */
1529   if(config_per_user) {
1530     if(!(pw = getpwuid(getuid())))
1531       disorder_fatal(0, "cannot determine our username");
1532     if((privconf = config_usersysconf(pw))
1533        && access(privconf, F_OK) == 0
1534        && config_include(c, privconf))
1535       return -1;
1536     xfree(privconf);
1537     /* if we have a password file, read it */
1538     if((privconf = config_userconf(0, pw))
1539        && access(privconf, F_OK) == 0
1540        && config_include(c, privconf))
1541       return -1;
1542     xfree(privconf);
1543   }
1544   /* install default namepart and transform settings */
1545   config_postdefaults(c, server);
1546   if(oldconfig)  {
1547     int failed = 0;
1548     if(strcmp(c->home, oldconfig->home)) {
1549       disorder_error(0, "'home' cannot be changed without a restart");
1550       failed = 1;
1551     }
1552     if(strcmp(c->alias, oldconfig->alias)) {
1553       disorder_error(0, "'alias' cannot be changed without a restart");
1554       failed = 1;
1555     }
1556     if(strcmp(c->user, oldconfig->user)) {
1557       disorder_error(0, "'user' cannot be changed without a restart");
1558       failed = 1;
1559     }
1560     if(c->nice_speaker != oldconfig->nice_speaker) {
1561       disorder_error(0, "'nice_speaker' cannot be changed without a restart");
1562       /* ...but we accept the new config anyway */
1563     }
1564     if(c->nice_server != oldconfig->nice_server) {
1565       disorder_error(0, "'nice_server' cannot be changed without a restart");
1566       /* ...but we accept the new config anyway */
1567     }
1568 #if HAVE_PCRE_H
1569     if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1570       disorder_error(0, "'namepart' settings cannot be changed without a restart");
1571       failed = 1;
1572     }
1573 #endif
1574     if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1575       disorder_error(0, "'stopword' settings cannot be changed without a restart");
1576       failed = 1;
1577     }
1578     if(failed) {
1579       disorder_error(0, "not installing incompatible new configuration");
1580       return -1;
1581     }
1582   }
1583   /* everything is good so we shall use the new config */
1584   config_free(config);
1585   /* warn about obsolete directives */
1586   config = c;
1587   return 0;
1588 }
1589
1590 /** @brief Return the path to the private configuration file */
1591 char *config_private(void) {
1592   char *s;
1593
1594   set_configfile();
1595   byte_xasprintf(&s, "%s.private", configfile);
1596   return s;
1597 }
1598
1599 /** @brief Return the path to user's personal configuration file */
1600 char *config_userconf(const char *home, const struct passwd *pw) {
1601   char *s;
1602
1603   if(!home && !pw && !(pw = getpwuid(getuid())))
1604     disorder_fatal(0, "cannot determine our username");
1605   byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1606   return s;
1607 }
1608
1609 /** @brief Return the path to user-specific system configuration */
1610 char *config_usersysconf(const struct passwd *pw) {
1611   char *s;
1612
1613   set_configfile();
1614   if(!strchr(pw->pw_name, '/')) {
1615     byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1616     return s;
1617   } else
1618     return 0;
1619 }
1620
1621 /** @brief Get a filename within the home directory
1622  * @param name Relative name
1623  * @return Full path
1624  */
1625 char *config_get_file(const char *name) {
1626   return config_get_file2(config, name);
1627 }
1628
1629 /** @brief Order two stringlists
1630  * @param a First stringlist
1631  * @param b Second stringlist
1632  * @return <0, 0 or >0 if a<b, a=b or a>b
1633  */
1634 static int stringlist_compare(const struct stringlist *a,
1635                               const struct stringlist *b) {
1636   int n = 0, c;
1637
1638   while(n < a->n && n < b->n) {
1639     if((c = strcmp(a->s[n], b->s[n])))
1640       return c;
1641     ++n;
1642   }
1643   if(a->n < b->n)
1644     return -1;
1645   else if(a->n > b->n)
1646     return 1;
1647   else
1648     return 0;
1649 }
1650
1651 #if HAVE_PCRE_H
1652 /** @brief Order two namepart definitions
1653  * @param a First namepart definition
1654  * @param b Second namepart definition
1655  * @return <0, 0 or >0 if a<b, a=b or a>b
1656  */
1657 static int namepart_compare(const struct namepart *a,
1658                             const struct namepart *b) {
1659   int c;
1660
1661   if((c = strcmp(a->part, b->part)))
1662     return c;
1663   if((c = strcmp(a->res, b->res)))
1664     return c;
1665   if((c = strcmp(a->replace, b->replace)))
1666     return c;
1667   if((c = strcmp(a->context, b->context)))
1668     return c;
1669   if(a->reflags > b->reflags)
1670     return 1;
1671   if(a->reflags < b->reflags)
1672     return -1;
1673   return 0;
1674 }
1675
1676 /** @brief Order two lists of namepart definitions
1677  * @param a First list of namepart definitions
1678  * @param b Second list of namepart definitions
1679  * @return <0, 0 or >0 if a<b, a=b or a>b
1680  */
1681 static int namepartlist_compare(const struct namepartlist *a,
1682                                 const struct namepartlist *b) {
1683   int n = 0, c;
1684
1685   while(n < a->n && n < b->n) {
1686     if((c = namepart_compare(&a->s[n], &b->s[n])))
1687       return c;
1688     ++n;
1689   }
1690   if(a->n > b->n)
1691     return 1;
1692   else if(a->n < b->n)
1693     return -1;
1694   else
1695     return 0;
1696 }
1697 #endif
1698
1699 /** @brief Verify configuration table.
1700  * @return The number of problems found
1701 */
1702 int config_verify(void) {
1703   int fails = 0;
1704   size_t n;
1705   for(n = 1; n < sizeof conf / sizeof *conf; ++n)
1706     if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1707       fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1708       ++fails;
1709     }
1710   return fails;
1711 }
1712
1713 /*
1714 Local Variables:
1715 c-basic-offset:2
1716 comment-column:40
1717 fill-column:79
1718 End:
1719 */