chiark / gitweb /
Import from Arch revision:
[disorder] / lib / configuration.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2004, 2005, 2006 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20
21 #include <config.h>
22 #include "types.h"
23
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <errno.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <ctype.h>
32 #include <stddef.h>
33 #include <pwd.h>
34 #include <langinfo.h>
35 #include <pcre.h>
36 #include <signal.h>
37
38 #include "configuration.h"
39 #include "mem.h"
40 #include "log.h"
41 #include "split.h"
42 #include "syscalls.h"
43 #include "table.h"
44 #include "inputline.h"
45 #include "charset.h"
46 #include "defs.h"
47 #include "mixer.h"
48 #include "printf.h"
49 #include "regsub.h"
50 #include "signame.h"
51
52 char *configfile;
53
54 struct config_state {
55   const char *path;
56   int line;
57   struct config *config;
58 };
59
60 struct config *config;
61
62 struct conf {
63   const char *name;
64   size_t offset;
65   const struct conftype *type;
66   int (*validate)(const struct config_state *cs,
67                   int nvec, char **vec);
68 };
69
70 struct conftype {
71   int (*set)(const struct config_state *cs,
72              const struct conf *whoami,
73              int nvec, char **vec);
74   void (*free)(struct config *c, const struct conf *whoami);
75 };
76
77 #define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
78 #define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
79
80 static int set_signal(const struct config_state *cs,
81                       const struct conf *whoami,
82                       int nvec, char **vec) {
83   int n;
84   
85   if(nvec != 1) {
86     error(0, "%s:%d: '%s' requires one argument",
87           cs->path, cs->line, whoami->name);
88     return -1;
89   }
90   if((n = find_signal(vec[0])) == -1) {
91     error(0, "%s:%d: unknown signal '%s'",
92           cs->path, cs->line, vec[0]);
93     return -1;
94   }
95   VALUE(cs->config, int) = n;
96   return 0;
97 }
98
99 static int set_collections(const struct config_state *cs,
100                            const struct conf *whoami,
101                            int nvec, char **vec) {
102   struct collectionlist *cl;
103   
104   if(nvec != 3) {
105     error(0, "%s:%d: '%s' requires three arguments",
106           cs->path, cs->line, whoami->name);
107     return -1;
108   }
109   if(vec[2][0] != '/') {
110     error(0, "%s:%d: collection root must start with '/'",
111           cs->path, cs->line);
112     return -1;
113   }
114   if(vec[2][1] && vec[2][strlen(vec[2])-1] == '/') {
115     error(0, "%s:%d: collection root must not end with '/'",
116           cs->path, cs->line);
117     return -1;
118   }
119   cl = ADDRESS(cs->config, struct collectionlist);
120   ++cl->n;
121   cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
122   cl->s[cl->n - 1].module = xstrdup(vec[0]);
123   cl->s[cl->n - 1].encoding = xstrdup(vec[1]);
124   cl->s[cl->n - 1].root = xstrdup(vec[2]);
125   return 0;
126 }
127
128 static int set_boolean(const struct config_state *cs,
129                        const struct conf *whoami,
130                        int nvec, char **vec) {
131   int state;
132   
133   if(nvec != 1) {
134     error(0, "%s:%d: '%s' takes only one argument",
135           cs->path, cs->line, whoami->name);
136     return -1;
137   }
138   if(!strcmp(vec[0], "yes")) state = 1;
139   else if(!strcmp(vec[0], "no")) state = 0;
140   else {
141     error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
142           cs->path, cs->line, whoami->name);
143     return -1;
144   }
145   VALUE(cs->config, int) = state;
146   return 0;
147 }
148
149 static int set_string(const struct config_state *cs,
150                       const struct conf *whoami,
151                       int nvec, char **vec) {
152   if(nvec != 1) {
153     error(0, "%s:%d: '%s' takes only one argument",
154           cs->path, cs->line, whoami->name);
155     return -1;
156   }
157   VALUE(cs->config, char *) = xstrdup(vec[0]);
158   return 0;
159 }
160
161 static int set_stringlist(const struct config_state *cs,
162                           const struct conf *whoami,
163                           int nvec, char **vec) {
164   int n;
165   struct stringlist *sl;
166
167   sl = ADDRESS(cs->config, struct stringlist);
168   sl->n = 0;
169   for(n = 0; n < nvec; ++n) {
170     sl->n++;
171     sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
172     sl->s[sl->n - 1] = xstrdup(vec[n]);
173   }
174   return 0;
175 }
176
177 static int set_integer(const struct config_state *cs,
178                        const struct conf *whoami,
179                        int nvec, char **vec) {
180   char *e;
181
182   if(nvec != 1) {
183     error(0, "%s:%d: '%s' takes only one argument",
184           cs->path, cs->line, whoami->name);
185     return -1;
186   }
187   if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
188     error(errno, "%s:%d: converting integer", cs->path, cs->line);
189     return -1;
190   }
191   if(*e) {
192     error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
193     return -1;
194   }
195   return 0;
196 }
197
198 static int set_stringlist_accum(const struct config_state *cs,
199                                 const struct conf *whoami,
200                                 int nvec, char **vec) {
201   int n;
202   struct stringlist *s;
203   struct stringlistlist *sll;
204
205   sll = ADDRESS(cs->config, struct stringlistlist);
206   sll->n++;
207   sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
208   s = &sll->s[sll->n - 1];
209   s->n = nvec;
210   s->s = xmalloc((nvec + 1) * sizeof (char *));
211   for(n = 0; n < nvec; ++n)
212     s->s[n] = xstrdup(vec[n]);
213   return 0;
214 }
215
216 static int set_string_accum(const struct config_state *cs,
217                             const struct conf *whoami,
218                             int nvec, char **vec) {
219   int n;
220   struct stringlist *sl;
221
222   sl = ADDRESS(cs->config, struct stringlist);
223   for(n = 0; n < nvec; ++n) {
224     sl->n++;
225     sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
226     sl->s[sl->n - 1] = xstrdup(vec[n]);
227   }
228   return 0;
229 }
230
231 static int set_restrict(const struct config_state *cs,
232                         const struct conf *whoami,
233                         int nvec, char **vec) {
234   unsigned r = 0;
235   int n, i;
236   
237   static const struct restriction {
238     const char *name;
239     unsigned bit;
240   } restrictions[] = {
241     { "remove", RESTRICT_REMOVE },
242     { "scratch", RESTRICT_SCRATCH },
243     { "move", RESTRICT_MOVE },
244   };
245
246   for(n = 0; n < nvec; ++n) {
247     if((i = TABLE_FIND(restrictions, struct restriction, name, vec[n])) < 0) {
248       error(0, "%s:%d: invalid restriction '%s'",
249             cs->path, cs->line, vec[n]);
250       return -1;
251     }
252     r |= restrictions[i].bit;
253   }
254   VALUE(cs->config, unsigned) = r;
255   return 0;
256 }
257
258 static int set_namepart(const struct config_state *cs,
259                         const struct conf *whoami,
260                         int nvec, char **vec) {
261   struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
262   unsigned reflags;
263   const char *errstr;
264   int erroffset, n;
265   pcre *re;
266
267   if(nvec < 3) {
268     error(0, "%s:%d: namepart needs at least 3 arguments", cs->path, cs->line);
269     return -1;
270   }
271   if(nvec > 5) {
272     error(0, "%s:%d: namepart needs at most 5 arguments", cs->path, cs->line);
273     return -1;
274   }
275   reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
276   if(!(re = pcre_compile(vec[1],
277                          PCRE_UTF8
278                          |regsub_compile_options(reflags),
279                          &errstr, &erroffset, 0))) {
280     error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
281           cs->path, cs->line, vec[1], errstr, erroffset);
282     return -1;
283   }
284   npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
285   npl->s[npl->n].part = xstrdup(vec[0]);
286   npl->s[npl->n].re = re;
287   npl->s[npl->n].replace = xstrdup(vec[2]);
288   npl->s[npl->n].context = xstrdup(vec[3]);
289   npl->s[npl->n].reflags = reflags;
290   ++npl->n;
291   /* XXX a bit of a bodge; relies on there being very few parts. */
292   for(n = 0; (n < cs->config->nparts
293               && strcmp(cs->config->parts[n], vec[0])); ++n)
294     ;
295   if(n >= cs->config->nparts) {
296     cs->config->parts = xrealloc(cs->config->parts,
297                                  (cs->config->nparts + 1) * sizeof (char *));
298     cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
299   }
300   return 0;
301 }
302
303 static int set_transform(const struct config_state *cs,
304                          const struct conf *whoami,
305                          int nvec, char **vec) {
306   struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
307   pcre *re;
308   unsigned reflags;
309   const char *errstr;
310   int erroffset;
311
312   if(nvec < 3) {
313     error(0, "%s:%d: transform needs at least 3 arguments", cs->path, cs->line);
314     return -1;
315   }
316   if(nvec > 5) {
317     error(0, "%s:%d: transform needs at most 5 arguments", cs->path, cs->line);
318     return -1;
319   }
320   reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
321   if(!(re = pcre_compile(vec[1],
322                          PCRE_UTF8
323                          |regsub_compile_options(reflags),
324                          &errstr, &erroffset, 0))) {
325     error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
326           cs->path, cs->line, vec[1], errstr, erroffset);
327     return -1;
328   }
329   tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
330   tl->t[tl->n].type = xstrdup(vec[0]);
331   tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
332   tl->t[tl->n].re = re;
333   tl->t[tl->n].replace = xstrdup(vec[2]);
334   tl->t[tl->n].flags = reflags;
335   ++tl->n;
336   return 0;
337 }
338
339 /* free functions */
340
341 static void free_none(struct config attribute((unused)) *c,
342                       const struct conf attribute((unused)) *whoami) {
343 }
344
345 static void free_string(struct config *c,
346                         const struct conf *whoami) {
347   xfree(VALUE(c, char *));
348 }
349
350 static void free_stringlist(struct config *c,
351                             const struct conf *whoami) {
352   int n;
353   struct stringlist *sl = ADDRESS(c, struct stringlist);
354
355   for(n = 0; n < sl->n; ++n)
356     xfree(sl->s[n]);
357   xfree(sl->s);
358 }
359
360 static void free_stringlistlist(struct config *c,
361                                 const struct conf *whoami) {
362   int n, m;
363   struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
364   struct stringlist *sl;
365
366   for(n = 0; n < sll->n; ++n) {
367     sl = &sll->s[n];
368     for(m = 0; m < sl->n; ++m)
369       xfree(sl->s[m]);
370     xfree(sl->s);
371   }
372   xfree(sll->s);
373 }
374
375 static void free_collectionlist(struct config *c,
376                                 const struct conf *whoami) {
377   struct collectionlist *cll = ADDRESS(c, struct collectionlist);
378   struct collection *cl;
379   int n;
380
381   for(n = 0; n < cll->n; ++n) {
382     cl = &cll->s[n];
383     xfree(cl->module);
384     xfree(cl->encoding);
385     xfree(cl->root);
386   }
387   xfree(cll->s);
388 }
389
390 static void free_namepartlist(struct config *c,
391                               const struct conf *whoami) {
392   struct namepartlist *npl = ADDRESS(c, struct namepartlist);
393   struct namepart *np;
394   int n;
395
396   for(n = 0; n < npl->n; ++n) {
397     np = &npl->s[n];
398     xfree(np->part);
399     pcre_free(np->re);                  /* ...whatever pcre_free is set to. */
400     xfree(np->replace);
401     xfree(np->context);
402   }
403   xfree(npl->s);
404 }
405
406 static void free_transformlist(struct config *c,
407                                const struct conf *whoami) {
408   struct transformlist *tl = ADDRESS(c, struct transformlist);
409   struct transform *t;
410   int n;
411
412   for(n = 0; n < tl->n; ++n) {
413     t = &tl->t[n];
414     xfree(t->type);
415     pcre_free(t->re);                   /* ...whatever pcre_free is set to. */
416     xfree(t->replace);
417     xfree(t->context);
418   }
419   xfree(tl->t);
420 }
421
422 /* configuration types */
423
424 static const struct conftype
425   type_signal = { set_signal, free_none },
426   type_collections = { set_collections, free_collectionlist },
427   type_boolean = { set_boolean, free_none },
428   type_string = { set_string, free_string },
429   type_stringlist = { set_stringlist, free_stringlist },
430   type_integer = { set_integer, free_none },
431   type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
432   type_string_accum = { set_string_accum, free_stringlist },
433   type_restrict = { set_restrict, free_none },
434   type_namepart = { set_namepart, free_namepartlist },
435   type_transform = { set_transform, free_transformlist };
436
437 /* specific validation routine */
438
439 #define VALIDATE_FILE(test, what) do {                          \
440   struct stat sb;                                               \
441   int n;                                                        \
442                                                                 \
443   for(n = 0; n < nvec; ++n) {                                   \
444     if(stat(vec[n], &sb) < 0) {                                 \
445       error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]);    \
446       return -1;                                                \
447     }                                                           \
448     if(!test(sb.st_mode)) {                                     \
449       error(0, "%s:%d: %s is not a %s",                         \
450             cs->path, cs->line, vec[n], what);                  \
451       return -1;                                                \
452     }                                                           \
453   }                                                             \
454 } while(0)
455
456 static int validate_isdir(const struct config_state *cs,
457                           int nvec, char **vec) {
458   VALIDATE_FILE(S_ISDIR, "directory");
459   return 0;
460 }
461
462 static int validate_isreg(const struct config_state *cs,
463                           int nvec, char **vec) {
464   VALIDATE_FILE(S_ISREG, "regular file");
465   return 0;
466 }
467
468 static int validate_ischr(const struct config_state *cs,
469                           int nvec, char **vec) {
470   VALIDATE_FILE(S_ISCHR, "character device");
471   return 0;
472 }
473
474 static int validate_player(const struct config_state *cs,
475                            int nvec,
476                            char attribute((unused)) **vec) {
477   if(nvec < 2) {
478     error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
479           cs->path, cs->line);
480     return -1;
481   }
482   return 0;
483 }
484
485 static int validate_allow(const struct config_state *cs,
486                           int nvec,
487                           char attribute((unused)) **vec) {
488   if(nvec != 2) {
489     error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
490     return -1;
491   }
492   return 0;
493 }
494
495 static int validate_non_negative(const struct config_state *cs,
496                                  int nvec, char **vec) {
497   long n;
498
499   if(nvec < 1) {
500     error(0, "%s:%d: missing argument", cs->path, cs->line);
501     return -1;
502   }
503   if(nvec > 1) {
504     error(0, "%s:%d: too many arguments", cs->path, cs->line);
505     return -1;
506   }
507   if(xstrtol(&n, vec[0], 0, 0)) {
508     error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
509     return -1;
510   }
511   if(n < 0) {
512     error(0, "%s:%d: must not be negative", cs->path, cs->line);
513     return -1;
514   }
515   return 0;
516 }
517
518 static int validate_positive(const struct config_state *cs,
519                           int nvec, char **vec) {
520   long n;
521
522   if(nvec < 1) {
523     error(0, "%s:%d: missing argument", cs->path, cs->line);
524     return -1;
525   }
526   if(nvec > 1) {
527     error(0, "%s:%d: too many arguments", cs->path, cs->line);
528     return -1;
529   }
530   if(xstrtol(&n, vec[0], 0, 0)) {
531     error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
532     return -1;
533   }
534   if(n <= 0) {
535     error(0, "%s:%d: must be positive", cs->path, cs->line);
536     return -1;
537   }
538   return 0;
539 }
540
541 static int validate_isauser(const struct config_state *cs,
542                             int attribute((unused)) nvec,
543                             char **vec) {
544   struct passwd *pw;
545
546   if(!(pw = getpwnam(vec[0]))) {
547     error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
548     return -1;
549   }
550   return 0;
551 }
552
553 static int validate_channel(const struct config_state *cs,
554                             int attribute((unused)) nvec,
555                             char **vec) {
556   if(mixer_channel(vec[0]) == -1) {
557     error(0, "%s:%d: invalid channel '%s'", cs->path, cs->line, vec[0]);
558     return -1;
559   }
560   return 0;
561 }
562
563 static int validate_any(const struct config_state attribute((unused)) *cs,
564                         int attribute((unused)) nvec,
565                         char attribute((unused)) **vec) {
566   return 0;
567 }
568
569 static int validate_url(const struct config_state attribute((unused)) *cs,
570                         int attribute((unused)) nvec,
571                         char **vec) {
572   const char *s;
573   int n;
574   /* absoluteURI   = scheme ":" ( hier_part | opaque_part )
575      scheme        = alpha *( alpha | digit | "+" | "-" | "." ) */
576   s = vec[0];
577   n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
578                  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
579                  "0123456789"));
580   if(s[n] != ':') {
581     error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
582     return -1;
583   }
584   if(!strncmp(s, "http:", 5)
585      || !strncmp(s, "https:", 6)) {
586     s += n + 1;
587     /* we only do a rather cursory check */
588     if(strncmp(s, "//", 2)) {
589       error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
590       return -1;
591     }
592   }
593   return 0;
594 }
595
596 static int validate_alias(const struct config_state *cs,
597                           int nvec,
598                           char **vec) {
599   const char *s;
600   int in_brackets = 0, c;
601
602   if(nvec < 1) {
603     error(0, "%s:%d: missing argument", cs->path, cs->line);
604     return -1;
605   }
606   if(nvec > 1) {
607     error(0, "%s:%d: too many arguments", cs->path, cs->line);
608     return -1;
609   }
610   s = vec[0];
611   while((c = (unsigned char)*s++)) {
612     if(in_brackets) {
613       if(c == '}')
614         in_brackets = 0;
615       else if(!isalnum(c)) {
616         error(0, "%s:%d: invalid part name in alias expansion in '%s'",
617               cs->path, cs->line, vec[0]);
618           return -1;
619       }
620     } else {
621       if(c == '{') {
622         in_brackets = 1;
623         if(*s == '/')
624           ++s;
625       } else if(c == '\\') {
626         if(!(c = (unsigned char)*s++)) {
627           error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
628                 cs->path, cs->line, vec[0]);
629           return -1;
630         } else if(c != '\\' && c != '{') {
631           error(0, "%s:%d: invalid escape in alias expansion in '%s'",
632                 cs->path, cs->line, vec[0]);
633           return -1;
634         }
635       }
636     }
637     ++s;
638   }
639   if(in_brackets) {
640     error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
641           cs->path, cs->line, vec[0]);
642     return -1;
643   }
644   return 0;
645 }
646
647 /* configuration table */
648
649 #define C(x) #x, offsetof(struct config, x)
650 #define C2(x,y) #x, offsetof(struct config, y)
651
652 static const struct conf conf[] = {
653   { C(alias),            &type_string,           validate_alias },
654   { C(allow),            &type_stringlist_accum, validate_allow },
655   { C(channel),          &type_string,           validate_channel },
656   { C(checkpoint_kbyte), &type_integer,          validate_non_negative },
657   { C(checkpoint_min),   &type_integer,          validate_non_negative },
658   { C(collection),       &type_collections,      validate_any },
659   { C(connect),          &type_stringlist,       validate_any },
660   { C(device),           &type_string,           validate_any },
661   { C(gap),              &type_integer,          validate_non_negative },
662   { C(history),          &type_integer,          validate_positive },
663   { C(home),             &type_string,           validate_isdir },
664   { C(listen),           &type_stringlist,       validate_any },
665   { C(lock),             &type_boolean,          validate_any },
666   { C(mixer),            &type_string,           validate_ischr },
667   { C(namepart),         &type_namepart,         validate_any },
668   { C2(nice, nice_rescan), &type_integer,        validate_non_negative },
669   { C(nice_rescan),      &type_integer,          validate_non_negative },
670   { C(nice_server),      &type_integer,          validate_any },
671   { C(nice_speaker),     &type_integer,          validate_any },
672   { C(password),         &type_string,           validate_any },
673   { C(player),           &type_stringlist_accum, validate_player },
674   { C(plugins),          &type_string_accum,     validate_isdir },
675   { C(prefsync),         &type_integer,          validate_positive },
676   { C(refresh),          &type_integer,          validate_positive },
677   { C2(restrict, restrictions),         &type_restrict,         validate_any },
678   { C(scratch),          &type_string_accum,     validate_isreg },
679   { C(signal),           &type_signal,           validate_any },
680   { C(stopword),         &type_string_accum,     validate_any },
681   { C(templates),        &type_string_accum,     validate_isdir },
682   { C(transform),        &type_transform,        validate_any },
683   { C(trust),            &type_string_accum,     validate_any },
684   { C(url),              &type_string,           validate_url },
685   { C(user),             &type_string,           validate_isauser },
686   { C(username),         &type_string,           validate_any },
687 };
688
689 /* find a configuration item's definition by key */
690 static const struct conf *find(const char *key) {
691   int n;
692
693   if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
694     return 0;
695   return &conf[n];
696 }
697
698 /* set a new configuration value */
699 static int config_set(const struct config_state *cs,
700                       int nvec, char **vec) {
701   const struct conf *which;
702
703   D(("config_set %s", vec[0]));
704   if(!(which = find(vec[0]))) {
705     error(0, "%s:%d: unknown configuration key '%s'",
706           cs->path, cs->line, vec[0]);
707     return -1;
708   }
709   return (which->validate(cs, nvec - 1, vec + 1)
710           || which->type->set(cs, which, nvec - 1, vec + 1));
711 }
712
713 static void config_error(const char *msg, void *u) {
714   const struct config_state *cs = u;
715
716   error(0, "%s:%d: %s", cs->path, cs->line, msg);
717 }
718
719 /* include a file by name */
720 static int config_include(struct config *c, const char *path) {
721   FILE *fp;
722   char *buffer, *inputbuffer, **vec;
723   int n, ret = 0;
724   struct config_state cs;
725
726   cs.path = path;
727   cs.line = 0;
728   cs.config = c;
729   D(("%s: reading configuration", path));
730   if(!(fp = fopen(path, "r"))) {
731     error(errno, "error opening %s", path);
732     return -1;
733   }
734   while(!inputline(path, fp, &inputbuffer, '\n')) {
735     ++cs.line;
736     if(!(buffer = mb2utf8(inputbuffer))) {
737       error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
738       ret = -1;
739       xfree(inputbuffer);
740       continue;
741     }
742     xfree(inputbuffer);
743     if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
744                      config_error, &cs))) {
745       ret = -1;
746       xfree(buffer);
747       continue;
748     }
749     if(n) {
750       if(!strcmp(vec[0], "include")) {
751         if(n != 2) {
752           error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
753           ret = -1;
754         } else
755           config_include(c, vec[1]);
756       } else
757         ret |= config_set(&cs, n, vec);
758     }
759     for(n = 0; vec[n]; ++n) xfree(vec[n]);
760     xfree(vec);
761     xfree(buffer);
762   }
763   if(ferror(fp)) {
764     error(errno, "error reading %s", path);
765     ret = -1;
766   }
767   fclose(fp);
768   return ret;
769 }
770
771 /* make a new default config */
772 static struct config *config_default(void) {
773   struct config *c = xmalloc(sizeof *c);
774   const char *logname;
775   struct passwd *pw;
776
777   /* Strings had better be xstrdup'd as they will get freed at some point. */
778   c->gap = 2;
779   c->history = 60;
780   c->home = xstrdup(pkgstatedir);
781   if(!(pw = getpwuid(getuid())))
782     fatal(0, "cannot determine our username");
783   logname = pw->pw_name;
784   c->username = xstrdup(logname);
785   c->refresh = 15;
786   c->prefsync = 3600;
787   c->signal = SIGKILL;
788   c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
789   c->lock = 1;
790   c->device = xstrdup("default");
791   c->nice_rescan = 10;
792   return c;
793 }
794
795 static char *get_file(struct config *c, const char *name) {
796   char *s;
797
798   byte_xasprintf(&s, "%s/%s", c->home, name);
799   return s;
800 }
801
802 static void set_configfile(void) {
803   if(!configfile)
804     byte_xasprintf(&configfile, "%s/config", pkgconfdir);
805 }
806
807 /* free the config file */
808 static void config_free(struct config *c) {
809   int n;
810
811   if(c) {
812     for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
813       conf[n].type->free(c, &conf[n]);
814     for(n = 0; n < c->nparts; ++n)
815       xfree(c->parts[n]);
816     xfree(c->parts);
817     xfree(c);
818   }
819 }
820
821 static void config_postdefaults(struct config *c) {
822   struct config_state cs;
823   const struct conf *whoami;
824   int n;
825
826   static const char *namepart[][4] = {
827     { "title",  "/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
828     { "title",  "/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort" },
829     { "album",  "/([^/]+)/[^/]+$",                    "$1", "*" },
830     { "artist", "/([^/]+)/[^/]+/[^/]+$",              "$1", "*" },
831     { "ext",    "(\\.[a-zA-Z0-9]+)$",                 "$1", "*" },
832   };
833 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
834
835   static const char *transform[][5] = {
836     { "track", "^.*/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
837     { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort", "" },
838     { "dir",   "^.*/([^/]+)$",                          "$1", "*", "" },
839     { "dir",   "^(the) ([^/]*)",                        "$2, $1", "sort", "i", },
840     { "dir",   "[[:punct:]]",                           "", "sort", "g", }
841   };
842 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
843
844   cs.path = "<internal>";
845   cs.line = 0;
846   cs.config = c;
847   if(!c->namepart.n) {
848     whoami = find("namepart");
849     for(n = 0; n < NNAMEPART; ++n)
850       set_namepart(&cs, whoami, 4, (char **)namepart[n]);
851   }
852   if(!c->transform.n) {
853     whoami = find("transform");
854     for(n = 0; n < NTRANSFORM; ++n)
855       set_transform(&cs, whoami, 5, (char **)transform[n]);
856   }
857 }
858
859 /* re-read the config file */
860 int config_read() {
861   struct config *c;
862   char *privconf;
863   struct passwd *pw;
864
865   set_configfile();
866   c = config_default();
867   if(config_include(c, configfile))
868     return -1;
869   /* if we can read the private config file, do */
870   if((privconf = config_private())
871      && access(privconf, R_OK) == 0
872      && config_include(c, privconf))
873     return -1;
874   xfree(privconf);
875   /* if there's a per-user system config file for this user, read it */
876   if(!(pw = getpwuid(getuid())))
877     fatal(0, "cannot determine our username");
878   if((privconf = config_usersysconf(pw))
879      && access(privconf, F_OK) == 0
880      && config_include(c, privconf))
881       return -1;
882   xfree(privconf);
883   /* if we have a password file, read it */
884   if((privconf = config_userconf(getenv("HOME"), pw))
885      && access(privconf, F_OK) == 0
886      && config_include(c, privconf))
887     return -1;
888   xfree(privconf);
889   /* install default namepart and transform settings */
890   config_postdefaults(c);
891   /* everything is good so we shall use the new config */
892   config_free(config);
893   config = c;
894   return 0;
895 }
896
897 char *config_private(void) {
898   char *s;
899
900   set_configfile();
901   byte_xasprintf(&s, "%s.private", configfile);
902   return s;
903 }
904
905 char *config_userconf(const char *home, const struct passwd *pw) {
906   char *s;
907
908   byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
909   return s;
910 }
911
912 char *config_usersysconf(const struct passwd *pw ) {
913   char *s;
914
915   set_configfile();
916   if(!strchr(pw->pw_name, '/')) {
917     byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
918     return s;
919   } else
920     return 0;
921 }
922
923 char *config_get_file(const char *name) {
924   return get_file(config, name);
925 }
926
927 /*
928 Local Variables:
929 c-basic-offset:2
930 comment-column:40
931 fill-column:79
932 End:
933 */
934 /* arch-tag:ede19ed49dca6136ba864ed0a4988b34 */