chiark / gitweb /
Typos.
[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   type_namepart = { set_namepart, free_namepartlist },
636   type_transform = { set_transform, free_transformlist },
637   type_netaddress = { set_netaddress, free_netaddress },
638   type_rights = { set_rights, free_string };
639
640 /* specific validation routine */
641
642 /** @brief Perform a test on a filename
643  * @param test Test function to call on mode bits
644  * @param what Type of file sought
645  *
646  * If @p test returns 0 then the file is not a @p what and an error
647  * is reported and -1 is returned.
648  */
649 #define VALIDATE_FILE(test, what) do {                  \
650   struct stat sb;                                       \
651   int n;                                                \
652                                                         \
653   for(n = 0; n < nvec; ++n) {                           \
654     if(stat(vec[n], &sb) < 0) {                         \
655       disorder_error(errno, "%s:%d: %s",                \
656                      cs->path, cs->line, vec[n]);       \
657       return -1;                                        \
658     }                                                   \
659     if(!test(sb.st_mode)) {                             \
660       disorder_error(0, "%s:%d: %s is not a %s",        \
661                      cs->path, cs->line, vec[n], what); \
662       return -1;                                        \
663     }                                                   \
664   }                                                     \
665 } while(0)
666
667 /** @brief Validate an absolute path
668  * @param cs Configuration state
669  * @param nvec Length of (proposed) new value
670  * @param vec Elements of new value
671  * @return 0 on success, non-0 on error
672  */
673 static int validate_isabspath(const struct config_state *cs,
674                               int nvec, char **vec) {
675   int n;
676
677   for(n = 0; n < nvec; ++n)
678     if(vec[n][0] != '/') {
679       disorder_error(0, "%s:%d: %s: not an absolute path", 
680                      cs->path, cs->line, vec[n]);
681       return -1;
682     }
683   return 0;
684 }
685
686 /** @brief Validate an existing directory
687  * @param cs Configuration state
688  * @param nvec Length of (proposed) new value
689  * @param vec Elements of new value
690  * @return 0 on success, non-0 on error
691  */
692 static int validate_isdir(const struct config_state *cs,
693                           int nvec, char **vec) {
694   VALIDATE_FILE(S_ISDIR, "directory");
695   return 0;
696 }
697
698 /** @brief Validate an existing regular file
699  * @param cs Configuration state
700  * @param nvec Length of (proposed) new value
701  * @param vec Elements of new value
702  * @return 0 on success, non-0 on error
703  */
704 static int validate_isreg(const struct config_state *cs,
705                           int nvec, char **vec) {
706   VALIDATE_FILE(S_ISREG, "regular file");
707   return 0;
708 }
709
710 /** @brief Validate a player pattern
711  * @param cs Configuration state
712  * @param nvec Length of (proposed) new value
713  * @param vec Elements of new value
714  * @return 0 on success, non-0 on error
715  */
716 static int validate_player(const struct config_state *cs,
717                            int nvec,
718                            char attribute((unused)) **vec) {
719   if(nvec && nvec < 2) {
720     disorder_error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
721                    cs->path, cs->line);
722     return -1;
723   }
724   return 0;
725 }
726
727 /** @brief Validate a track length pattern
728  * @param cs Configuration state
729  * @param nvec Length of (proposed) new value
730  * @param vec Elements of new value
731  * @return 0 on success, non-0 on error
732  */
733 static int validate_tracklength(const struct config_state *cs,
734                                 int nvec,
735                                 char attribute((unused)) **vec) {
736   if(nvec && nvec < 2) {
737     disorder_error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
738                    cs->path, cs->line);
739     return -1;
740   }
741   return 0;
742 }
743
744 /** @brief Validate a non-negative (@c long) integer
745  * @param cs Configuration state
746  * @param nvec Length of (proposed) new value
747  * @param vec Elements of new value
748  * @return 0 on success, non-0 on error
749  */
750 static int validate_non_negative(const struct config_state *cs,
751                                  int nvec, char **vec) {
752   long n;
753
754   if(nvec < 1) {
755     disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
756     return -1;
757   }
758   if(nvec > 1) {
759     disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
760     return -1;
761   }
762   if(xstrtol(&n, vec[0], 0, 0)) {
763     disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
764     return -1;
765   }
766   if(n < 0) {
767     disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
768     return -1;
769   }
770   return 0;
771 }
772
773 /** @brief Validate a positive (@c long) integer
774  * @param cs Configuration state
775  * @param nvec Length of (proposed) new value
776  * @param vec Elements of new value
777  * @return 0 on success, non-0 on error
778  */
779 static int validate_positive(const struct config_state *cs,
780                           int nvec, char **vec) {
781   long n;
782
783   if(nvec < 1) {
784     disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
785     return -1;
786   }
787   if(nvec > 1) {
788     disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
789     return -1;
790   }
791   if(xstrtol(&n, vec[0], 0, 0)) {
792     disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
793     return -1;
794   }
795   if(n <= 0) {
796     disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
797     return -1;
798   }
799   return 0;
800 }
801
802 /** @brief Validate a system username
803  * @param cs Configuration state
804  * @param nvec Length of (proposed) new value
805  * @param vec Elements of new value
806  * @return 0 on success, non-0 on error
807  */
808 static int validate_isauser(const struct config_state *cs,
809                             int attribute((unused)) nvec,
810                             char **vec) {
811   if(!getpwnam(vec[0])) {
812     disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
813     return -1;
814   }
815   return 0;
816 }
817
818 /** @brief Validate a sample format string
819  * @param cs Configuration state
820  * @param nvec Length of (proposed) new value
821  * @param vec Elements of new value
822  * @return 0 on success, non-0 on error
823  */
824 static int validate_sample_format(const struct config_state *cs,
825                                   int attribute((unused)) nvec,
826                                   char **vec) {
827   return parse_sample_format(cs, 0, nvec, vec);
828 }
829
830 /** @brief Validate anything
831  * @param cs Configuration state
832  * @param nvec Length of (proposed) new value
833  * @param vec Elements of new value
834  * @return 0
835  */
836 static int validate_any(const struct config_state attribute((unused)) *cs,
837                         int attribute((unused)) nvec,
838                         char attribute((unused)) **vec) {
839   return 0;
840 }
841
842 /** @brief Validate a URL
843  * @param cs Configuration state
844  * @param nvec Length of (proposed) new value
845  * @param vec Elements of new value
846  * @return 0 on success, non-0 on error
847  *
848  * Rather cursory.
849  */
850 static int validate_url(const struct config_state attribute((unused)) *cs,
851                         int attribute((unused)) nvec,
852                         char **vec) {
853   const char *s;
854   int n;
855   /* absoluteURI   = scheme ":" ( hier_part | opaque_part )
856      scheme        = alpha *( alpha | digit | "+" | "-" | "." ) */
857   s = vec[0];
858   n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
859                  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
860                  "0123456789"));
861   if(s[n] != ':') {
862     disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
863     return -1;
864   }
865   if(!strncmp(s, "http:", 5)
866      || !strncmp(s, "https:", 6)) {
867     s += n + 1;
868     /* we only do a rather cursory check */
869     if(strncmp(s, "//", 2)) {
870       disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
871       return -1;
872     }
873   }
874   return 0;
875 }
876
877 /** @brief Validate an alias pattern
878  * @param cs Configuration state
879  * @param nvec Length of (proposed) new value
880  * @param vec Elements of new value
881  * @return 0 on success, non-0 on error
882  */
883 static int validate_alias(const struct config_state *cs,
884                           int nvec,
885                           char **vec) {
886   const char *s;
887   int in_brackets = 0, c;
888
889   if(nvec < 1) {
890     disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
891     return -1;
892   }
893   if(nvec > 1) {
894     disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
895     return -1;
896   }
897   s = vec[0];
898   while((c = (unsigned char)*s++)) {
899     if(in_brackets) {
900       if(c == '}')
901         in_brackets = 0;
902       else if(!isalnum(c)) {
903         disorder_error(0, "%s:%d: invalid part name in alias expansion in '%s'",
904                        cs->path, cs->line, vec[0]);
905           return -1;
906       }
907     } else {
908       if(c == '{') {
909         in_brackets = 1;
910         if(*s == '/')
911           ++s;
912       } else if(c == '\\') {
913         if(!(c = (unsigned char)*s++)) {
914           disorder_error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
915                          cs->path, cs->line, vec[0]);
916           return -1;
917         } else if(c != '\\' && c != '{') {
918           disorder_error(0, "%s:%d: invalid escape in alias expansion in '%s'",
919                          cs->path, cs->line, vec[0]);
920           return -1;
921         }
922       }
923     }
924     ++s;
925   }
926   if(in_brackets) {
927     disorder_error(0,
928                    "%s:%d: unterminated part name in alias expansion in '%s'",
929                    cs->path, cs->line, vec[0]);
930     return -1;
931   }
932   return 0;
933 }
934
935 /** @brief Validate a hash algorithm name
936  * @param cs Configuration state
937  * @param nvec Length of (proposed) new value
938  * @param vec Elements of new value
939  * @return 0 on success, non-0 on error
940  */
941 static int validate_algo(const struct config_state attribute((unused)) *cs,
942                          int nvec,
943                          char **vec) {
944   if(nvec != 1) {
945     disorder_error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
946     return -1;
947   }
948   if(!valid_authhash(vec[0])) {
949     disorder_error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
950     return -1;
951   }
952   return 0;
953 }
954
955 /** @brief Validate a playback backend name
956  * @param cs Configuration state
957  * @param nvec Length of (proposed) new value
958  * @param vec Elements of new value
959  * @return 0 on success, non-0 on error
960  */
961 static int validate_backend(const struct config_state attribute((unused)) *cs,
962                             int nvec,
963                             char **vec) {
964   int n;
965   if(nvec != 1) {
966     disorder_error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
967     return -1;
968   }
969   if(!strcmp(vec[0], "network")) {
970     disorder_error(0, "'api network' is deprecated; use 'api rtp'");
971     return 0;
972   }
973   if(config_uaudio_apis) {
974     for(n = 0; config_uaudio_apis[n]; ++n)
975       if(!strcmp(vec[0], config_uaudio_apis[n]->name))
976         return 0;
977     disorder_error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
978     return -1;
979   }
980   /* In non-server processes we have no idea what's valid */
981   return 0;
982 }
983
984 /** @brief Validate a pause mode string
985  * @param cs Configuration state
986  * @param nvec Length of (proposed) new value
987  * @param vec Elements of new value
988  * @return 0 on success, non-0 on error
989  */
990 static int validate_pausemode(const struct config_state attribute((unused)) *cs,
991                               int nvec,
992                               char **vec) {
993   if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
994     return 0;
995   disorder_error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
996   return -1;
997 }
998
999 /** @brief Validate a destination network address
1000  * @param cs Configuration state
1001  * @param nvec Length of (proposed) new value
1002  * @param vec Elements of new value
1003  * @return 0 on success, non-0 on error
1004  *
1005  * By a destination address, it is meant that it must not be a wildcard
1006  * address.
1007  */
1008 static int validate_destaddr(const struct config_state attribute((unused)) *cs,
1009                              int nvec,
1010                              char **vec) {
1011   struct netaddress na[1];
1012
1013   if(netaddress_parse(na, nvec, vec)) {
1014     disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
1015     return -1;
1016   }
1017   if(!na->address) {
1018     disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
1019     return -1;
1020   }
1021   xfree(na->address);
1022   return 0;
1023 }
1024
1025 /** @brief Item name and and offset */
1026 #define C(x) #x, offsetof(struct config, x)
1027 /** @brief Item name and and offset */
1028 #define C2(x,y) #x, offsetof(struct config, y)
1029
1030 /** @brief All configuration items */
1031 static const struct conf conf[] = {
1032   { C(alias),            &type_string,           validate_alias },
1033   { C(api),              &type_string,           validate_backend },
1034   { C(authorization_algorithm), &type_string,    validate_algo },
1035   { C(broadcast),        &type_netaddress,       validate_destaddr },
1036   { C(broadcast_from),   &type_netaddress,       validate_any },
1037   { C(channel),          &type_string,           validate_any },
1038   { C(checkpoint_kbyte), &type_integer,          validate_non_negative },
1039   { C(checkpoint_min),   &type_integer,          validate_non_negative },
1040   { C(collection),       &type_collections,      validate_any },
1041   { C(connect),          &type_netaddress,       validate_destaddr },
1042   { C(cookie_key_lifetime),  &type_integer,      validate_positive },
1043   { C(cookie_login_lifetime),  &type_integer,    validate_positive },
1044   { C(dbversion),        &type_integer,          validate_positive },
1045   { C(default_rights),   &type_rights,           validate_any },
1046   { C(device),           &type_string,           validate_any },
1047   { C(history),          &type_integer,          validate_positive },
1048   { C(home),             &type_string,           validate_isabspath },
1049   { C(listen),           &type_netaddress,       validate_any },
1050   { C(mail_sender),      &type_string,           validate_any },
1051   { C(mixer),            &type_string,           validate_any },
1052   { C(mount_rescan),     &type_boolean,          validate_any },
1053   { C(multicast_loop),   &type_boolean,          validate_any },
1054   { C(multicast_ttl),    &type_integer,          validate_non_negative },
1055 #if HAVE_PCRE_H
1056   { C(namepart),         &type_namepart,         validate_any },
1057 #endif
1058   { C(new_bias),         &type_integer,          validate_positive },
1059   { C(new_bias_age),     &type_integer,          validate_positive },
1060   { C(new_max),          &type_integer,          validate_positive },
1061   { C2(nice, nice_rescan), &type_integer,        validate_non_negative },
1062   { C(nice_rescan),      &type_integer,          validate_non_negative },
1063   { C(nice_server),      &type_integer,          validate_any },
1064   { C(nice_speaker),     &type_integer,          validate_any },
1065   { C(noticed_history),  &type_integer,          validate_positive },
1066   { C(password),         &type_string,           validate_any },
1067   { C(pause_mode),       &type_string,           validate_pausemode },
1068   { C(player),           &type_stringlist_accum, validate_player },
1069   { C(playlist_lock_timeout), &type_integer,     validate_positive },
1070   { C(playlist_max) ,    &type_integer,          validate_positive },
1071   { C(plugins),          &type_string_accum,     validate_isdir },
1072   { C(queue_pad),        &type_integer,          validate_positive },
1073   { C(refresh),          &type_integer,          validate_positive },
1074   { C(refresh_min),      &type_integer,          validate_non_negative },
1075   { C(reminder_interval), &type_integer,         validate_positive },
1076   { C(remote_userman),   &type_boolean,          validate_any },
1077   { C(replay_min),       &type_integer,          validate_non_negative },
1078   { C(rtp_delay_threshold), &type_integer,       validate_positive },
1079   { C(rtp_mode),         &type_string,           validate_any },
1080   { C(rtp_verbose),      &type_boolean,          validate_any },
1081   { C(sample_format),    &type_sample_format,    validate_sample_format },
1082   { C(scratch),          &type_string_accum,     validate_isreg },
1083   { C(sendmail),         &type_string,           validate_isabspath },
1084   { C(short_display),    &type_integer,          validate_positive },
1085   { C(signal),           &type_signal,           validate_any },
1086   { C(smtp_server),      &type_string,           validate_any },
1087   { C(sox_generation),   &type_integer,          validate_non_negative },
1088   { C2(speaker_backend, api),  &type_string,     validate_backend },
1089   { C(speaker_command),  &type_string,           validate_any },
1090   { C(stopword),         &type_string_accum,     validate_any },
1091   { C(templates),        &type_string_accum,     validate_isdir },
1092   { C(tracklength),      &type_stringlist_accum, validate_tracklength },
1093 #if HAVE_PCRE_H
1094   { C(transform),        &type_transform,        validate_any },
1095 #endif
1096   { C(url),              &type_string,           validate_url },
1097   { C(user),             &type_string,           validate_isauser },
1098   { C(username),         &type_string,           validate_any },
1099 };
1100
1101 /** @brief Find a configuration item's definition by key */
1102 static const struct conf *find(const char *key) {
1103   int n;
1104
1105   if((n = TABLE_FIND(conf, name, key)) < 0)
1106     return 0;
1107   return &conf[n];
1108 }
1109
1110 /** @brief Set a new configuration value
1111  * @param cs Configuration state
1112  * @param nvec Length of @p vec
1113  * @param vec Name and new value
1114  * @return 0 on success, non-0 on error.
1115  *
1116  * @c vec[0] is the name, the rest is the value.
1117  */
1118 static int config_set(const struct config_state *cs,
1119                       int nvec, char **vec) {
1120   const struct conf *which;
1121
1122   D(("config_set %s", vec[0]));
1123   if(!(which = find(vec[0]))) {
1124     disorder_error(0, "%s:%d: unknown configuration key '%s'",
1125           cs->path, cs->line, vec[0]);
1126     return -1;
1127   }
1128   return (which->validate(cs, nvec - 1, vec + 1)
1129           || which->type->set(cs, which, nvec - 1, vec + 1));
1130 }
1131
1132 /** @brief Set a configuration item from parameters
1133  * @param cs Configuration state
1134  * @param which Item to set
1135  * @param ... Value as strings, terminated by (char *)NULL
1136  * @return 0 on success, non-0 on error
1137  */
1138 static int config_set_args(const struct config_state *cs,
1139                            const char *which, ...) {
1140   va_list ap;
1141   struct vector v[1];
1142   char *s;
1143   int rc;
1144
1145   vector_init(v);
1146   vector_append(v, (char *)which);
1147   va_start(ap, which);
1148   while((s = va_arg(ap, char *)))
1149     vector_append(v, s);
1150   va_end(ap);
1151   vector_terminate(v);
1152   rc = config_set(cs, v->nvec, v->vec);
1153   xfree(v->vec);
1154   return rc;
1155 }
1156
1157 /** @brief Error callback used by config_include()
1158  * @param msg Error message
1159  * @param u User data (@ref config_state)
1160  */
1161 static void config_error(const char *msg, void *u) {
1162   const struct config_state *cs = u;
1163
1164   disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
1165 }
1166
1167 /** @brief Include a file by name
1168  * @param c Configuration to update
1169  * @param path Path to read
1170  * @return 0 on success, non-0 on error
1171  */
1172 static int config_include(struct config *c, const char *path) {
1173   FILE *fp;
1174   char *buffer, *inputbuffer, **vec;
1175   int n, ret = 0;
1176   struct config_state cs;
1177
1178   cs.path = path;
1179   cs.line = 0;
1180   cs.config = c;
1181   D(("%s: reading configuration", path));
1182   if(!(fp = fopen(path, "r"))) {
1183     disorder_error(errno, "error opening %s", path);
1184     return -1;
1185   }
1186   while(!inputline(path, fp, &inputbuffer, '\n')) {
1187     ++cs.line;
1188     if(!(buffer = mb2utf8(inputbuffer))) {
1189       disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1190       ret = -1;
1191       xfree(inputbuffer);
1192       continue;
1193     }
1194     xfree(inputbuffer);
1195     if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1196                      config_error, &cs))) {
1197       ret = -1;
1198       xfree(buffer);
1199       continue;
1200     }
1201     if(n) {
1202       /* 'include' is special-cased */
1203       if(!strcmp(vec[0], "include")) {
1204         if(n != 2) {
1205           disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1206           ret = -1;
1207         } else
1208           config_include(c, vec[1]);
1209       } else
1210         ret |= config_set(&cs, n, vec);
1211     }
1212     for(n = 0; vec[n]; ++n) xfree(vec[n]);
1213     xfree(vec);
1214     xfree(buffer);
1215   }
1216   if(ferror(fp)) {
1217     disorder_error(errno, "error reading %s", path);
1218     ret = -1;
1219   }
1220   fclose(fp);
1221   return ret;
1222 }
1223
1224 /** @brief Default stopword setting */
1225 static const char *const default_stopwords[] = {
1226   "stopword",
1227
1228   "01",
1229   "02",
1230   "03",
1231   "04",
1232   "05",
1233   "06",
1234   "07",
1235   "08",
1236   "09",
1237   "1",
1238   "10",
1239   "11",
1240   "12",
1241   "13",
1242   "14",
1243   "15",
1244   "16",
1245   "17",
1246   "18",
1247   "19",
1248   "2",
1249   "20",
1250   "21",
1251   "22",
1252   "23",
1253   "24",
1254   "25",
1255   "26",
1256   "27",
1257   "28",
1258   "29",
1259   "3",
1260   "30",
1261   "4",
1262   "5",
1263   "6",
1264   "7",
1265   "8",
1266   "9",
1267   "a",
1268   "am",
1269   "an",
1270   "and",
1271   "as",
1272   "for",
1273   "i",
1274   "im",
1275   "in",
1276   "is",
1277   "of",
1278   "on",
1279   "the",
1280   "to",
1281   "too",
1282   "we",
1283 };
1284 #define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1285
1286 /** @brief Default player patterns */
1287 static const char *const default_players[] = {
1288   "*.ogg",
1289   "*.flac",
1290   "*.mp3",
1291   "*.wav",
1292 };
1293 #define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1294
1295 /** @brief Make a new default configuration
1296  * @return New configuration
1297  */
1298 static struct config *config_default(void) {
1299   struct config *c = xmalloc(sizeof *c);
1300   const char *logname;
1301   struct passwd *pw;
1302   struct config_state cs;
1303   size_t n;
1304
1305   cs.path = "<internal>";
1306   cs.line = 0;
1307   cs.config = c;
1308   /* Strings had better be xstrdup'd as they will get freed at some point. */
1309   c->history = 60;
1310   c->home = xstrdup(pkgstatedir);
1311   if(!(pw = getpwuid(getuid())))
1312     disorder_fatal(0, "cannot determine our username");
1313   logname = pw->pw_name;
1314   c->username = xstrdup(logname);
1315   c->refresh = 15;
1316   c->refresh_min = 1;
1317   c->signal = SIGKILL;
1318   c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1319   c->device = xstrdup("default");
1320   c->nice_rescan = 10;
1321   c->speaker_command = 0;
1322   c->sample_format.bits = 16;
1323   c->sample_format.rate = 44100;
1324   c->sample_format.channels = 2;
1325   c->sample_format.endian = ENDIAN_NATIVE;
1326   c->queue_pad = 10;
1327   c->replay_min = 8 * 3600;
1328   c->api = NULL;
1329   c->multicast_ttl = 1;
1330   c->multicast_loop = 1;
1331   c->authorization_algorithm = xstrdup("sha1");
1332   c->noticed_history = 31;
1333   c->short_display = 32;
1334   c->mixer = 0;
1335   c->channel = 0;
1336   c->dbversion = 2;
1337   c->cookie_login_lifetime = 86400;
1338   c->cookie_key_lifetime = 86400 * 7;
1339   if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1340     c->sendmail = xstrdup(sendmail_binary);
1341   c->smtp_server = xstrdup("127.0.0.1");
1342   c->new_max = 100;
1343   c->reminder_interval = 600;           /* 10m */
1344   c->new_bias_age = 7 * 86400;          /* 1 week */
1345   c->new_bias = 4500000;                /* 50 times the base weight */
1346   c->sox_generation = DEFAULT_SOX_GENERATION;
1347   c->playlist_max = INT_MAX;            /* effectively no limit */
1348   c->playlist_lock_timeout = 10;        /* 10s */
1349   c->mount_rescan = 1;
1350   /* Default stopwords */
1351   if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1352     exit(1);
1353   /* Default player configuration */
1354   for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1355     if(config_set_args(&cs, "player",
1356                        default_players[n], "execraw", "disorder-decode", (char *)0))
1357       exit(1);
1358     if(config_set_args(&cs, "tracklength",
1359                        default_players[n], "disorder-tracklength", (char *)0))
1360       exit(1);
1361   }
1362   c->broadcast.af = -1;
1363   c->broadcast_from.af = -1;
1364   c->listen.af = -1;
1365   c->connect.af = -1;
1366   c->rtp_mode = xstrdup("auto");
1367   return c;
1368 }
1369
1370 /** @brief Construct a filename
1371  * @param c Configuration
1372  * @param name Base filename
1373  * @return Full filename
1374  *
1375  * Usually use config_get_file() instead.
1376  */
1377 char *config_get_file2(struct config *c, const char *name) {
1378   char *s;
1379
1380   byte_xasprintf(&s, "%s/%s", c->home, name);
1381   return s;
1382 }
1383
1384 /** @brief Set the default configuration file */
1385 static void set_configfile(void) {
1386   if(!configfile)
1387     byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1388 }
1389
1390 /** @brief Free a configuration object
1391  * @param c Configuration to free
1392  *
1393  * @p c is indeterminate after this function is called.
1394  */
1395 void config_free(struct config *c) {
1396   int n;
1397
1398   if(c) {
1399     for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1400       conf[n].type->free(c, &conf[n]);
1401     for(n = 0; n < c->nparts; ++n)
1402       xfree(c->parts[n]);
1403     xfree(c->parts);
1404     xfree(c);
1405   }
1406 }
1407
1408 /** @brief Set post-parse defaults
1409  * @param c Configuration to update
1410  * @param server True when running in the server
1411  *
1412  * If @p server is set then certain parts of the configuration are more
1413  * strictly validated.
1414  */
1415 static void config_postdefaults(struct config *c,
1416                                 int server) {
1417   struct config_state cs;
1418 #if HAVE_PCRE_H
1419   const struct conf *whoami;
1420   int n;
1421 #endif
1422
1423 #if HAVE_PCRE_H
1424   static const char *namepart[][4] = {
1425     { "title",  "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1426     { "title",  "/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort" },
1427     { "album",  "/([^/]+)/[^/]+$",                    "$1", "*" },
1428     { "artist", "/([^/]+)/[^/]+/[^/]+$",              "$1", "*" },
1429     { "ext",    "(\\.[a-zA-Z0-9]+)$",                 "$1", "*" },
1430   };
1431 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1432
1433   static const char *transform[][5] = {
1434     { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1435     { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort", "" },
1436     { "dir",   "^.*/([^/]+)$",                          "$1", "*", "" },
1437     { "dir",   "^(the) ([^/]*)",                        "$2, $1", "sort", "i", },
1438     { "dir",   "[[:punct:]]",                           "", "sort", "g", }
1439   };
1440 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1441 #endif
1442
1443   cs.path = "<internal>";
1444   cs.line = 0;
1445   cs.config = c;
1446 #if HAVE_PCRE_H
1447   if(!c->namepart.n) {
1448     whoami = find("namepart");
1449     for(n = 0; n < NNAMEPART; ++n)
1450       set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1451   }
1452   if(!c->transform.n) {
1453     whoami = find("transform");
1454     for(n = 0; n < NTRANSFORM; ++n)
1455       set_transform(&cs, whoami, 5, (char **)transform[n]);
1456   }
1457 #endif
1458   if(!c->api) {
1459     if(c->speaker_command)
1460       c->api = xstrdup("command");
1461     else if(c->broadcast.af != -1)
1462       c->api = xstrdup("rtp");
1463     else if(config_uaudio_apis)
1464       c->api = xstrdup(uaudio_default(config_uaudio_apis,
1465                                       UAUDIO_API_SERVER)->name);
1466     else
1467       c->api = xstrdup("<none>");
1468   }
1469   if(!strcmp(c->api, "network"))
1470     c->api = xstrdup("rtp");
1471   if(server) {
1472     if(!strcmp(c->api, "command") && !c->speaker_command)
1473       disorder_fatal(0, "'api command' but speaker_command is not set");
1474     if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1475       disorder_fatal(0, "'api rtp' but broadcast is not set");
1476   }
1477   /* Override sample format */
1478   if(!strcmp(c->api, "rtp")) {
1479     c->sample_format.rate = 44100;
1480     c->sample_format.channels = 2;
1481     c->sample_format.bits = 16;
1482     c->sample_format.endian = ENDIAN_NATIVE;
1483   }
1484   if(!strcmp(c->api, "coreaudio")) {
1485     c->sample_format.rate = 44100;
1486     c->sample_format.channels = 2;
1487     c->sample_format.bits = 16;
1488     c->sample_format.endian = ENDIAN_NATIVE;
1489   }
1490   if(!c->default_rights) {
1491     rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1492                                      |RIGHT_MOVE__MASK
1493                                      |RIGHT_SCRATCH__MASK
1494                                      |RIGHT_REMOVE__MASK);
1495     r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
1496     c->default_rights = rights_string(r);
1497   }
1498 }
1499
1500 /** @brief (Re-)read the config file
1501  * @param server If set, do extra checking
1502  * @param oldconfig Old configuration for compatibility check
1503  * @return 0 on success, non-0 on error
1504  *
1505  * If @p oldconfig is set, then certain compatibility checks are done between
1506  * the old and new configurations.
1507  */
1508 int config_read(int server,
1509                 const struct config *oldconfig) {
1510   struct config *c;
1511   char *privconf;
1512   struct passwd *pw;
1513
1514   set_configfile();
1515   c = config_default();
1516   /* standalone Disobedience installs might not have a global config file */
1517   if(access(configfile, F_OK) == 0)
1518     if(config_include(c, configfile))
1519       return -1;
1520   /* if we can read the private config file, do */
1521   if((privconf = config_private())
1522      && access(privconf, R_OK) == 0
1523      && config_include(c, privconf))
1524     return -1;
1525   xfree(privconf);
1526   /* if there's a per-user system config file for this user, read it */
1527   if(config_per_user) {
1528     if(!(pw = getpwuid(getuid())))
1529       disorder_fatal(0, "cannot determine our username");
1530     if((privconf = config_usersysconf(pw))
1531        && access(privconf, F_OK) == 0
1532        && config_include(c, privconf))
1533       return -1;
1534     xfree(privconf);
1535     /* if we have a password file, read it */
1536     if((privconf = config_userconf(0, pw))
1537        && access(privconf, F_OK) == 0
1538        && config_include(c, privconf))
1539       return -1;
1540     xfree(privconf);
1541   }
1542   /* install default namepart and transform settings */
1543   config_postdefaults(c, server);
1544   if(oldconfig)  {
1545     int failed = 0;
1546     if(strcmp(c->home, oldconfig->home)) {
1547       disorder_error(0, "'home' cannot be changed without a restart");
1548       failed = 1;
1549     }
1550     if(strcmp(c->alias, oldconfig->alias)) {
1551       disorder_error(0, "'alias' cannot be changed without a restart");
1552       failed = 1;
1553     }
1554     if(strcmp(c->user, oldconfig->user)) {
1555       disorder_error(0, "'user' cannot be changed without a restart");
1556       failed = 1;
1557     }
1558     if(c->nice_speaker != oldconfig->nice_speaker) {
1559       disorder_error(0, "'nice_speaker' cannot be changed without a restart");
1560       /* ...but we accept the new config anyway */
1561     }
1562     if(c->nice_server != oldconfig->nice_server) {
1563       disorder_error(0, "'nice_server' cannot be changed without a restart");
1564       /* ...but we accept the new config anyway */
1565     }
1566 #if HAVE_PCRE_H
1567     if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1568       disorder_error(0, "'namepart' settings cannot be changed without a restart");
1569       failed = 1;
1570     }
1571 #endif
1572     if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1573       disorder_error(0, "'stopword' settings cannot be changed without a restart");
1574       failed = 1;
1575     }
1576     if(failed) {
1577       disorder_error(0, "not installing incompatible new configuration");
1578       return -1;
1579     }
1580   }
1581   /* everything is good so we shall use the new config */
1582   config_free(config);
1583   /* warn about obsolete directives */
1584   config = c;
1585   return 0;
1586 }
1587
1588 /** @brief Return the path to the private configuration file */
1589 char *config_private(void) {
1590   char *s;
1591
1592   set_configfile();
1593   byte_xasprintf(&s, "%s.private", configfile);
1594   return s;
1595 }
1596
1597 /** @brief Return the path to user's personal configuration file */
1598 char *config_userconf(const char *home, const struct passwd *pw) {
1599   char *s;
1600
1601   if(!home && !pw && !(pw = getpwuid(getuid())))
1602     disorder_fatal(0, "cannot determine our username");
1603   byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1604   return s;
1605 }
1606
1607 /** @brief Return the path to user-specific system configuration */
1608 char *config_usersysconf(const struct passwd *pw) {
1609   char *s;
1610
1611   set_configfile();
1612   if(!strchr(pw->pw_name, '/')) {
1613     byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1614     return s;
1615   } else
1616     return 0;
1617 }
1618
1619 /** @brief Get a filename within the home directory
1620  * @param name Relative name
1621  * @return Full path
1622  */
1623 char *config_get_file(const char *name) {
1624   return config_get_file2(config, name);
1625 }
1626
1627 /** @brief Order two stringlists
1628  * @param a First stringlist
1629  * @param b Second stringlist
1630  * @return <0, 0 or >0 if a<b, a=b or a>b
1631  */
1632 static int stringlist_compare(const struct stringlist *a,
1633                               const struct stringlist *b) {
1634   int n = 0, c;
1635
1636   while(n < a->n && n < b->n) {
1637     if((c = strcmp(a->s[n], b->s[n])))
1638       return c;
1639     ++n;
1640   }
1641   if(a->n < b->n)
1642     return -1;
1643   else if(a->n > b->n)
1644     return 1;
1645   else
1646     return 0;
1647 }
1648
1649 #if HAVE_PCRE_H
1650 /** @brief Order two namepart definitions
1651  * @param a First namepart definition
1652  * @param b Second namepart definition
1653  * @return <0, 0 or >0 if a<b, a=b or a>b
1654  */
1655 static int namepart_compare(const struct namepart *a,
1656                             const struct namepart *b) {
1657   int c;
1658
1659   if((c = strcmp(a->part, b->part)))
1660     return c;
1661   if((c = strcmp(a->res, b->res)))
1662     return c;
1663   if((c = strcmp(a->replace, b->replace)))
1664     return c;
1665   if((c = strcmp(a->context, b->context)))
1666     return c;
1667   if(a->reflags > b->reflags)
1668     return 1;
1669   if(a->reflags < b->reflags)
1670     return -1;
1671   return 0;
1672 }
1673
1674 /** @brief Order two lists of namepart definitions
1675  * @param a First list of namepart definitions
1676  * @param b Second list of namepart definitions
1677  * @return <0, 0 or >0 if a<b, a=b or a>b
1678  */
1679 static int namepartlist_compare(const struct namepartlist *a,
1680                                 const struct namepartlist *b) {
1681   int n = 0, c;
1682
1683   while(n < a->n && n < b->n) {
1684     if((c = namepart_compare(&a->s[n], &b->s[n])))
1685       return c;
1686     ++n;
1687   }
1688   if(a->n > b->n)
1689     return 1;
1690   else if(a->n < b->n)
1691     return -1;
1692   else
1693     return 0;
1694 }
1695 #endif
1696
1697 /** @brief Verify configuration table.
1698  * @return The number of problems found
1699 */
1700 int config_verify(void) {
1701   int fails = 0;
1702   size_t n;
1703   for(n = 1; n < sizeof conf / sizeof *conf; ++n)
1704     if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1705       fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1706       ++fails;
1707     }
1708   return fails;
1709 }
1710
1711 /*
1712 Local Variables:
1713 c-basic-offset:2
1714 comment-column:40
1715 fill-column:79
1716 End:
1717 */