2 * This file is part of DisOrder.
3 * Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
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.
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.
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
20 /** @file lib/configuration.c
21 * @brief Configuration file support
31 #include <sys/types.h>
41 #include "configuration.h"
47 #include "inputline.h"
55 /** @brief Path to config file
57 * set_configfile() sets the deafult if it is null.
61 /** @brief Config file parser state */
63 /** @brief Filename */
65 /** @brief Line number */
67 /** @brief Configuration object under construction */
68 struct config *config;
71 /** @brief Current configuration */
72 struct config *config;
74 /** @brief One configuration item */
76 /** @brief Name as it appears in the config file */
78 /** @brief Offset in @ref config structure */
80 /** @brief Pointer to item type */
81 const struct conftype *type;
82 /** @brief Pointer to item-specific validation routine */
83 int (*validate)(const struct config_state *cs,
84 int nvec, char **vec);
87 /** @brief Type of a configuration item */
89 /** @brief Pointer to function to set item */
90 int (*set)(const struct config_state *cs,
91 const struct conf *whoami,
92 int nvec, char **vec);
93 /** @brief Pointer to function to free item */
94 void (*free)(struct config *c, const struct conf *whoami);
97 /** @brief Compute the address of an item */
98 #define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
99 /** @brief Return the value of an item */
100 #define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
102 static int set_signal(const struct config_state *cs,
103 const struct conf *whoami,
104 int nvec, char **vec) {
108 error(0, "%s:%d: '%s' requires one argument",
109 cs->path, cs->line, whoami->name);
112 if((n = find_signal(vec[0])) == -1) {
113 error(0, "%s:%d: unknown signal '%s'",
114 cs->path, cs->line, vec[0]);
117 VALUE(cs->config, int) = n;
121 static int set_collections(const struct config_state *cs,
122 const struct conf *whoami,
123 int nvec, char **vec) {
124 struct collectionlist *cl;
127 error(0, "%s:%d: '%s' requires three arguments",
128 cs->path, cs->line, whoami->name);
131 if(vec[2][0] != '/') {
132 error(0, "%s:%d: collection root must start with '/'",
136 if(vec[2][1] && vec[2][strlen(vec[2])-1] == '/') {
137 error(0, "%s:%d: collection root must not end with '/'",
141 cl = ADDRESS(cs->config, struct collectionlist);
143 cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
144 cl->s[cl->n - 1].module = xstrdup(vec[0]);
145 cl->s[cl->n - 1].encoding = xstrdup(vec[1]);
146 cl->s[cl->n - 1].root = xstrdup(vec[2]);
150 static int set_boolean(const struct config_state *cs,
151 const struct conf *whoami,
152 int nvec, char **vec) {
156 error(0, "%s:%d: '%s' takes only one argument",
157 cs->path, cs->line, whoami->name);
160 if(!strcmp(vec[0], "yes")) state = 1;
161 else if(!strcmp(vec[0], "no")) state = 0;
163 error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
164 cs->path, cs->line, whoami->name);
167 VALUE(cs->config, int) = state;
171 static int set_string(const struct config_state *cs,
172 const struct conf *whoami,
173 int nvec, char **vec) {
175 error(0, "%s:%d: '%s' takes only one argument",
176 cs->path, cs->line, whoami->name);
179 VALUE(cs->config, char *) = xstrdup(vec[0]);
183 static int set_stringlist(const struct config_state *cs,
184 const struct conf *whoami,
185 int nvec, char **vec) {
187 struct stringlist *sl;
189 sl = ADDRESS(cs->config, struct stringlist);
191 for(n = 0; n < nvec; ++n) {
193 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
194 sl->s[sl->n - 1] = xstrdup(vec[n]);
199 static int set_integer(const struct config_state *cs,
200 const struct conf *whoami,
201 int nvec, char **vec) {
205 error(0, "%s:%d: '%s' takes only one argument",
206 cs->path, cs->line, whoami->name);
209 if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
210 error(errno, "%s:%d: converting integer", cs->path, cs->line);
214 error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
220 static int set_stringlist_accum(const struct config_state *cs,
221 const struct conf *whoami,
222 int nvec, char **vec) {
224 struct stringlist *s;
225 struct stringlistlist *sll;
227 sll = ADDRESS(cs->config, struct stringlistlist);
229 sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
230 s = &sll->s[sll->n - 1];
232 s->s = xmalloc((nvec + 1) * sizeof (char *));
233 for(n = 0; n < nvec; ++n)
234 s->s[n] = xstrdup(vec[n]);
238 static int set_string_accum(const struct config_state *cs,
239 const struct conf *whoami,
240 int nvec, char **vec) {
242 struct stringlist *sl;
244 sl = ADDRESS(cs->config, struct stringlist);
245 for(n = 0; n < nvec; ++n) {
247 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
248 sl->s[sl->n - 1] = xstrdup(vec[n]);
253 static int set_restrict(const struct config_state *cs,
254 const struct conf *whoami,
255 int nvec, char **vec) {
259 static const struct restriction {
263 { "remove", RESTRICT_REMOVE },
264 { "scratch", RESTRICT_SCRATCH },
265 { "move", RESTRICT_MOVE },
268 for(n = 0; n < nvec; ++n) {
269 if((i = TABLE_FIND(restrictions, struct restriction, name, vec[n])) < 0) {
270 error(0, "%s:%d: invalid restriction '%s'",
271 cs->path, cs->line, vec[n]);
274 r |= restrictions[i].bit;
276 VALUE(cs->config, unsigned) = r;
280 static int parse_sample_format(const struct config_state *cs,
281 struct stream_header *format,
282 int nvec, char **vec) {
287 error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
290 if(xstrtol(&t, p, &p, 0)) {
291 error(errno, "%s:%d: converting bits-per-sample", cs->path, cs->line);
294 if(t != 8 && t != 16) {
295 error(0, "%s:%d: bad bite-per-sample (%ld)", cs->path, cs->line, t);
298 if(format) format->bits = t;
300 case 'l': case 'L': t = ENDIAN_LITTLE; p++; break;
301 case 'b': case 'B': t = ENDIAN_BIG; p++; break;
302 default: t = ENDIAN_NATIVE; break;
304 if(format) format->endian = t;
306 error(errno, "%s:%d: expected `/' after bits-per-sample",
311 if(xstrtol(&t, p, &p, 0)) {
312 error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
315 if(t < 1 || t > INT_MAX) {
316 error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
319 if(format) format->rate = t;
321 error(0, "%s:%d: expected `/' after sample-rate",
326 if(xstrtol(&t, p, &p, 0)) {
327 error(errno, "%s:%d: converting channels", cs->path, cs->line);
331 error(0, "%s:%d: silly number (%ld) of channels", cs->path, cs->line, t);
334 if(format) format->channels = t;
336 error(0, "%s:%d: junk after channels", cs->path, cs->line);
342 static int set_sample_format(const struct config_state *cs,
343 const struct conf *whoami,
344 int nvec, char **vec) {
345 return parse_sample_format(cs, ADDRESS(cs->config, struct stream_header),
349 static int set_namepart(const struct config_state *cs,
350 const struct conf *whoami,
351 int nvec, char **vec) {
352 struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
359 error(0, "%s:%d: namepart needs at least 3 arguments", cs->path, cs->line);
363 error(0, "%s:%d: namepart needs at most 5 arguments", cs->path, cs->line);
366 reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
367 if(!(re = pcre_compile(vec[1],
369 |regsub_compile_options(reflags),
370 &errstr, &erroffset, 0))) {
371 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
372 cs->path, cs->line, vec[1], errstr, erroffset);
375 npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
376 npl->s[npl->n].part = xstrdup(vec[0]);
377 npl->s[npl->n].re = re;
378 npl->s[npl->n].replace = xstrdup(vec[2]);
379 npl->s[npl->n].context = xstrdup(vec[3]);
380 npl->s[npl->n].reflags = reflags;
382 /* XXX a bit of a bodge; relies on there being very few parts. */
383 for(n = 0; (n < cs->config->nparts
384 && strcmp(cs->config->parts[n], vec[0])); ++n)
386 if(n >= cs->config->nparts) {
387 cs->config->parts = xrealloc(cs->config->parts,
388 (cs->config->nparts + 1) * sizeof (char *));
389 cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
394 static int set_transform(const struct config_state *cs,
395 const struct conf *whoami,
396 int nvec, char **vec) {
397 struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
404 error(0, "%s:%d: transform needs at least 3 arguments", cs->path, cs->line);
408 error(0, "%s:%d: transform needs at most 5 arguments", cs->path, cs->line);
411 reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
412 if(!(re = pcre_compile(vec[1],
414 |regsub_compile_options(reflags),
415 &errstr, &erroffset, 0))) {
416 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
417 cs->path, cs->line, vec[1], errstr, erroffset);
420 tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
421 tl->t[tl->n].type = xstrdup(vec[0]);
422 tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
423 tl->t[tl->n].re = re;
424 tl->t[tl->n].replace = xstrdup(vec[2]);
425 tl->t[tl->n].flags = reflags;
430 static int set_backend(const struct config_state *cs,
431 const struct conf *whoami,
432 int nvec, char **vec) {
433 int *const valuep = ADDRESS(cs->config, int);
436 error(0, "%s:%d: '%s' requires one argument",
437 cs->path, cs->line, whoami->name);
440 if(!strcmp(vec[0], "alsa")) {
442 *valuep = BACKEND_ALSA;
444 error(0, "%s:%d: ALSA is not available on this platform",
448 } else if(!strcmp(vec[0], "command"))
449 *valuep = BACKEND_COMMAND;
450 else if(!strcmp(vec[0], "network"))
451 *valuep = BACKEND_NETWORK;
453 error(0, "%s:%d: invalid '%s' value '%s'",
454 cs->path, cs->line, whoami->name, vec[0]);
462 static void free_none(struct config attribute((unused)) *c,
463 const struct conf attribute((unused)) *whoami) {
466 static void free_string(struct config *c,
467 const struct conf *whoami) {
468 xfree(VALUE(c, char *));
471 static void free_stringlist(struct config *c,
472 const struct conf *whoami) {
474 struct stringlist *sl = ADDRESS(c, struct stringlist);
476 for(n = 0; n < sl->n; ++n)
481 static void free_stringlistlist(struct config *c,
482 const struct conf *whoami) {
484 struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
485 struct stringlist *sl;
487 for(n = 0; n < sll->n; ++n) {
489 for(m = 0; m < sl->n; ++m)
496 static void free_collectionlist(struct config *c,
497 const struct conf *whoami) {
498 struct collectionlist *cll = ADDRESS(c, struct collectionlist);
499 struct collection *cl;
502 for(n = 0; n < cll->n; ++n) {
511 static void free_namepartlist(struct config *c,
512 const struct conf *whoami) {
513 struct namepartlist *npl = ADDRESS(c, struct namepartlist);
517 for(n = 0; n < npl->n; ++n) {
520 pcre_free(np->re); /* ...whatever pcre_free is set to. */
527 static void free_transformlist(struct config *c,
528 const struct conf *whoami) {
529 struct transformlist *tl = ADDRESS(c, struct transformlist);
533 for(n = 0; n < tl->n; ++n) {
536 pcre_free(t->re); /* ...whatever pcre_free is set to. */
543 /* configuration types */
545 static const struct conftype
546 type_signal = { set_signal, free_none },
547 type_collections = { set_collections, free_collectionlist },
548 type_boolean = { set_boolean, free_none },
549 type_string = { set_string, free_string },
550 type_stringlist = { set_stringlist, free_stringlist },
551 type_integer = { set_integer, free_none },
552 type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
553 type_string_accum = { set_string_accum, free_stringlist },
554 type_sample_format = { set_sample_format, free_none },
555 type_restrict = { set_restrict, free_none },
556 type_namepart = { set_namepart, free_namepartlist },
557 type_transform = { set_transform, free_transformlist },
558 type_backend = { set_backend, free_none };
560 /* specific validation routine */
562 #define VALIDATE_FILE(test, what) do { \
566 for(n = 0; n < nvec; ++n) { \
567 if(stat(vec[n], &sb) < 0) { \
568 error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]); \
571 if(!test(sb.st_mode)) { \
572 error(0, "%s:%d: %s is not a %s", \
573 cs->path, cs->line, vec[n], what); \
579 static int validate_isdir(const struct config_state *cs,
580 int nvec, char **vec) {
581 VALIDATE_FILE(S_ISDIR, "directory");
585 static int validate_isreg(const struct config_state *cs,
586 int nvec, char **vec) {
587 VALIDATE_FILE(S_ISREG, "regular file");
591 static int validate_ischr(const struct config_state *cs,
592 int nvec, char **vec) {
593 VALIDATE_FILE(S_ISCHR, "character device");
597 static int validate_player(const struct config_state *cs,
599 char attribute((unused)) **vec) {
601 error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
608 static int validate_allow(const struct config_state *cs,
610 char attribute((unused)) **vec) {
612 error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
618 static int validate_non_negative(const struct config_state *cs,
619 int nvec, char **vec) {
623 error(0, "%s:%d: missing argument", cs->path, cs->line);
627 error(0, "%s:%d: too many arguments", cs->path, cs->line);
630 if(xstrtol(&n, vec[0], 0, 0)) {
631 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
635 error(0, "%s:%d: must not be negative", cs->path, cs->line);
641 static int validate_positive(const struct config_state *cs,
642 int nvec, char **vec) {
646 error(0, "%s:%d: missing argument", cs->path, cs->line);
650 error(0, "%s:%d: too many arguments", cs->path, cs->line);
653 if(xstrtol(&n, vec[0], 0, 0)) {
654 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
658 error(0, "%s:%d: must be positive", cs->path, cs->line);
664 static int validate_isauser(const struct config_state *cs,
665 int attribute((unused)) nvec,
669 if(!(pw = getpwnam(vec[0]))) {
670 error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
676 static int validate_sample_format(const struct config_state *cs,
677 int attribute((unused)) nvec,
679 return parse_sample_format(cs, 0, nvec, vec);
682 static int validate_channel(const struct config_state *cs,
683 int attribute((unused)) nvec,
685 if(mixer_channel(vec[0]) == -1) {
686 error(0, "%s:%d: invalid channel '%s'", cs->path, cs->line, vec[0]);
692 static int validate_any(const struct config_state attribute((unused)) *cs,
693 int attribute((unused)) nvec,
694 char attribute((unused)) **vec) {
698 static int validate_url(const struct config_state attribute((unused)) *cs,
699 int attribute((unused)) nvec,
703 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
704 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
706 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
707 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
710 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
713 if(!strncmp(s, "http:", 5)
714 || !strncmp(s, "https:", 6)) {
716 /* we only do a rather cursory check */
717 if(strncmp(s, "//", 2)) {
718 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
725 static int validate_alias(const struct config_state *cs,
729 int in_brackets = 0, c;
732 error(0, "%s:%d: missing argument", cs->path, cs->line);
736 error(0, "%s:%d: too many arguments", cs->path, cs->line);
740 while((c = (unsigned char)*s++)) {
744 else if(!isalnum(c)) {
745 error(0, "%s:%d: invalid part name in alias expansion in '%s'",
746 cs->path, cs->line, vec[0]);
754 } else if(c == '\\') {
755 if(!(c = (unsigned char)*s++)) {
756 error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
757 cs->path, cs->line, vec[0]);
759 } else if(c != '\\' && c != '{') {
760 error(0, "%s:%d: invalid escape in alias expansion in '%s'",
761 cs->path, cs->line, vec[0]);
769 error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
770 cs->path, cs->line, vec[0]);
776 static int validate_addrport(const struct config_state attribute((unused)) *cs,
778 char attribute((unused)) **vec) {
781 error(0, "%s:%d: missing address",
785 error(0, "%s:%d: missing port name/number",
791 error(0, "%s:%d: expected ADDRESS PORT",
797 static int validate_port(const struct config_state attribute((unused)) *cs,
799 char attribute((unused)) **vec) {
802 error(0, "%s:%d: missing address",
809 error(0, "%s:%d: expected [ADDRESS] PORT",
815 /** @brief Item name and and offset */
816 #define C(x) #x, offsetof(struct config, x)
817 /** @brief Item name and and offset */
818 #define C2(x,y) #x, offsetof(struct config, y)
820 /** @brief All configuration items */
821 static const struct conf conf[] = {
822 { C(alias), &type_string, validate_alias },
823 { C(allow), &type_stringlist_accum, validate_allow },
824 { C(broadcast), &type_stringlist, validate_addrport },
825 { C(broadcast_from), &type_stringlist, validate_addrport },
826 { C(channel), &type_string, validate_channel },
827 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
828 { C(checkpoint_min), &type_integer, validate_non_negative },
829 { C(collection), &type_collections, validate_any },
830 { C(connect), &type_stringlist, validate_addrport },
831 { C(device), &type_string, validate_any },
832 { C(gap), &type_integer, validate_non_negative },
833 { C(history), &type_integer, validate_positive },
834 { C(home), &type_string, validate_isdir },
835 { C(listen), &type_stringlist, validate_port },
836 { C(lock), &type_boolean, validate_any },
837 { C(mixer), &type_string, validate_ischr },
838 { C(multicast_ttl), &type_integer, validate_non_negative },
839 { C(namepart), &type_namepart, validate_any },
840 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
841 { C(nice_rescan), &type_integer, validate_non_negative },
842 { C(nice_server), &type_integer, validate_any },
843 { C(nice_speaker), &type_integer, validate_any },
844 { C(password), &type_string, validate_any },
845 { C(player), &type_stringlist_accum, validate_player },
846 { C(plugins), &type_string_accum, validate_isdir },
847 { C(prefsync), &type_integer, validate_positive },
848 { C(queue_pad), &type_integer, validate_positive },
849 { C(refresh), &type_integer, validate_positive },
850 { C2(restrict, restrictions), &type_restrict, validate_any },
851 { C(sample_format), &type_sample_format, validate_sample_format },
852 { C(scratch), &type_string_accum, validate_isreg },
853 { C(signal), &type_signal, validate_any },
854 { C(sox_generation), &type_integer, validate_non_negative },
855 { C(speaker_backend), &type_backend, validate_any },
856 { C(speaker_command), &type_string, validate_any },
857 { C(stopword), &type_string_accum, validate_any },
858 { C(templates), &type_string_accum, validate_isdir },
859 { C(transform), &type_transform, validate_any },
860 { C(trust), &type_string_accum, validate_any },
861 { C(url), &type_string, validate_url },
862 { C(user), &type_string, validate_isauser },
863 { C(username), &type_string, validate_any },
866 /** @brief Find a configuration item's definition by key */
867 static const struct conf *find(const char *key) {
870 if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
875 /** @brief Set a new configuration value */
876 static int config_set(const struct config_state *cs,
877 int nvec, char **vec) {
878 const struct conf *which;
880 D(("config_set %s", vec[0]));
881 if(!(which = find(vec[0]))) {
882 error(0, "%s:%d: unknown configuration key '%s'",
883 cs->path, cs->line, vec[0]);
886 return (which->validate(cs, nvec - 1, vec + 1)
887 || which->type->set(cs, which, nvec - 1, vec + 1));
890 /** @brief Error callback used by config_include() */
891 static void config_error(const char *msg, void *u) {
892 const struct config_state *cs = u;
894 error(0, "%s:%d: %s", cs->path, cs->line, msg);
897 /** @brief Include a file by name */
898 static int config_include(struct config *c, const char *path) {
900 char *buffer, *inputbuffer, **vec;
902 struct config_state cs;
907 D(("%s: reading configuration", path));
908 if(!(fp = fopen(path, "r"))) {
909 error(errno, "error opening %s", path);
912 while(!inputline(path, fp, &inputbuffer, '\n')) {
914 if(!(buffer = mb2utf8(inputbuffer))) {
915 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
921 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
922 config_error, &cs))) {
928 if(!strcmp(vec[0], "include")) {
930 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
933 config_include(c, vec[1]);
935 ret |= config_set(&cs, n, vec);
937 for(n = 0; vec[n]; ++n) xfree(vec[n]);
942 error(errno, "error reading %s", path);
949 /** @brief Make a new default configuration */
950 static struct config *config_default(void) {
951 struct config *c = xmalloc(sizeof *c);
955 /* Strings had better be xstrdup'd as they will get freed at some point. */
958 c->home = xstrdup(pkgstatedir);
959 if(!(pw = getpwuid(getuid())))
960 fatal(0, "cannot determine our username");
961 logname = pw->pw_name;
962 c->username = xstrdup(logname);
966 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
968 c->device = xstrdup("default");
970 c->speaker_command = 0;
971 c->sample_format.bits = 16;
972 c->sample_format.rate = 44100;
973 c->sample_format.channels = 2;
974 c->sample_format.endian = ENDIAN_NATIVE;
976 c->speaker_backend = -1;
977 c->multicast_ttl = 1;
981 static char *get_file(struct config *c, const char *name) {
984 byte_xasprintf(&s, "%s/%s", c->home, name);
988 /** @brief Set the default configuration file */
989 static void set_configfile(void) {
991 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
994 /** @brief Free a configuration object */
995 static void config_free(struct config *c) {
999 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1000 conf[n].type->free(c, &conf[n]);
1001 for(n = 0; n < c->nparts; ++n)
1008 /** @brief Set post-parse defaults */
1009 static void config_postdefaults(struct config *c) {
1010 struct config_state cs;
1011 const struct conf *whoami;
1014 static const char *namepart[][4] = {
1015 { "title", "/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1016 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1017 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1018 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1019 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1021 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1023 static const char *transform[][5] = {
1024 { "track", "^.*/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1025 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1026 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1027 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1028 { "dir", "[[:punct:]]", "", "sort", "g", }
1030 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1032 cs.path = "<internal>";
1035 if(!c->namepart.n) {
1036 whoami = find("namepart");
1037 for(n = 0; n < NNAMEPART; ++n)
1038 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1040 if(!c->transform.n) {
1041 whoami = find("transform");
1042 for(n = 0; n < NTRANSFORM; ++n)
1043 set_transform(&cs, whoami, 5, (char **)transform[n]);
1045 if(c->speaker_backend == -1) {
1046 if(c->speaker_command)
1047 c->speaker_backend = BACKEND_COMMAND;
1048 else if(c->broadcast.n)
1049 c->speaker_backend = BACKEND_NETWORK;
1052 c->speaker_backend = BACKEND_ALSA;
1054 c->speaker_backend = BACKEND_COMMAND;
1058 if(c->speaker_backend == BACKEND_COMMAND && !c->speaker_command)
1059 fatal(0, "speaker_backend is command but speaker_command is not set");
1060 if(c->speaker_backend == BACKEND_NETWORK && !c->broadcast.n)
1061 fatal(0, "speaker_backend is network but broadcast is not set");
1062 if(c->speaker_backend) {
1063 /* Override sample format */
1064 c->sample_format.rate = 44100;
1065 c->sample_format.channels = 2;
1066 c->sample_format.bits = 16;
1067 c->sample_format.endian = ENDIAN_BIG;
1071 /** @brief (Re-)read the config file */
1078 c = config_default();
1079 /* standalone Disobedience installs might not have a global config file */
1080 if(access(configfile, F_OK) == 0)
1081 if(config_include(c, configfile))
1083 /* if we can read the private config file, do */
1084 if((privconf = config_private())
1085 && access(privconf, R_OK) == 0
1086 && config_include(c, privconf))
1089 /* if there's a per-user system config file for this user, read it */
1090 if(!(pw = getpwuid(getuid())))
1091 fatal(0, "cannot determine our username");
1092 if((privconf = config_usersysconf(pw))
1093 && access(privconf, F_OK) == 0
1094 && config_include(c, privconf))
1097 /* if we have a password file, read it */
1098 if((privconf = config_userconf(getenv("HOME"), pw))
1099 && access(privconf, F_OK) == 0
1100 && config_include(c, privconf))
1103 /* install default namepart and transform settings */
1104 config_postdefaults(c);
1105 /* everything is good so we shall use the new config */
1106 config_free(config);
1111 /** @brief Return the path to the private configuration file */
1112 char *config_private(void) {
1116 byte_xasprintf(&s, "%s.private", configfile);
1120 /** @brief Return the path to user's personal configuration file */
1121 char *config_userconf(const char *home, const struct passwd *pw) {
1124 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1128 /** @brief Return the path to user-specific system configuration */
1129 char *config_usersysconf(const struct passwd *pw) {
1133 if(!strchr(pw->pw_name, '/')) {
1134 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1140 char *config_get_file(const char *name) {
1141 return get_file(config, name);