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
28 #include <sys/types.h>
38 #include "configuration.h"
44 #include "inputline.h"
52 /** @brief Path to config file
54 * set_configfile() sets the deafult if it is null.
58 /** @brief Config file parser state */
60 /** @brief Filename */
62 /** @brief Line number */
64 /** @brief Configuration object under construction */
65 struct config *config;
68 /** @brief Current configuration */
69 struct config *config;
71 /** @brief One configuration item */
73 /** @brief Name as it appears in the config file */
75 /** @brief Offset in @ref config structure */
77 /** @brief Pointer to item type */
78 const struct conftype *type;
79 /** @brief Pointer to item-specific validation routine */
80 int (*validate)(const struct config_state *cs,
81 int nvec, char **vec);
84 /** @brief Type of a configuration item */
86 /** @brief Pointer to function to set item */
87 int (*set)(const struct config_state *cs,
88 const struct conf *whoami,
89 int nvec, char **vec);
90 /** @brief Pointer to function to free item */
91 void (*free)(struct config *c, const struct conf *whoami);
94 /** @brief Compute the address of an item */
95 #define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
96 /** @brief Return the value of an item */
97 #define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
99 static int set_signal(const struct config_state *cs,
100 const struct conf *whoami,
101 int nvec, char **vec) {
105 error(0, "%s:%d: '%s' requires one argument",
106 cs->path, cs->line, whoami->name);
109 if((n = find_signal(vec[0])) == -1) {
110 error(0, "%s:%d: unknown signal '%s'",
111 cs->path, cs->line, vec[0]);
114 VALUE(cs->config, int) = n;
118 static int set_collections(const struct config_state *cs,
119 const struct conf *whoami,
120 int nvec, char **vec) {
121 struct collectionlist *cl;
124 error(0, "%s:%d: '%s' requires three arguments",
125 cs->path, cs->line, whoami->name);
128 if(vec[2][0] != '/') {
129 error(0, "%s:%d: collection root must start with '/'",
133 if(vec[2][1] && vec[2][strlen(vec[2])-1] == '/') {
134 error(0, "%s:%d: collection root must not end with '/'",
138 cl = ADDRESS(cs->config, struct collectionlist);
140 cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
141 cl->s[cl->n - 1].module = xstrdup(vec[0]);
142 cl->s[cl->n - 1].encoding = xstrdup(vec[1]);
143 cl->s[cl->n - 1].root = xstrdup(vec[2]);
147 static int set_boolean(const struct config_state *cs,
148 const struct conf *whoami,
149 int nvec, char **vec) {
153 error(0, "%s:%d: '%s' takes only one argument",
154 cs->path, cs->line, whoami->name);
157 if(!strcmp(vec[0], "yes")) state = 1;
158 else if(!strcmp(vec[0], "no")) state = 0;
160 error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
161 cs->path, cs->line, whoami->name);
164 VALUE(cs->config, int) = state;
168 static int set_string(const struct config_state *cs,
169 const struct conf *whoami,
170 int nvec, char **vec) {
172 error(0, "%s:%d: '%s' takes only one argument",
173 cs->path, cs->line, whoami->name);
176 VALUE(cs->config, char *) = xstrdup(vec[0]);
180 static int set_stringlist(const struct config_state *cs,
181 const struct conf *whoami,
182 int nvec, char **vec) {
184 struct stringlist *sl;
186 sl = ADDRESS(cs->config, struct stringlist);
188 for(n = 0; n < nvec; ++n) {
190 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
191 sl->s[sl->n - 1] = xstrdup(vec[n]);
196 static int set_integer(const struct config_state *cs,
197 const struct conf *whoami,
198 int nvec, char **vec) {
202 error(0, "%s:%d: '%s' takes only one argument",
203 cs->path, cs->line, whoami->name);
206 if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
207 error(errno, "%s:%d: converting integer", cs->path, cs->line);
211 error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
217 static int set_stringlist_accum(const struct config_state *cs,
218 const struct conf *whoami,
219 int nvec, char **vec) {
221 struct stringlist *s;
222 struct stringlistlist *sll;
224 sll = ADDRESS(cs->config, struct stringlistlist);
226 sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
227 s = &sll->s[sll->n - 1];
229 s->s = xmalloc((nvec + 1) * sizeof (char *));
230 for(n = 0; n < nvec; ++n)
231 s->s[n] = xstrdup(vec[n]);
235 static int set_string_accum(const struct config_state *cs,
236 const struct conf *whoami,
237 int nvec, char **vec) {
239 struct stringlist *sl;
241 sl = ADDRESS(cs->config, struct stringlist);
242 for(n = 0; n < nvec; ++n) {
244 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
245 sl->s[sl->n - 1] = xstrdup(vec[n]);
250 static int set_restrict(const struct config_state *cs,
251 const struct conf *whoami,
252 int nvec, char **vec) {
256 static const struct restriction {
260 { "remove", RESTRICT_REMOVE },
261 { "scratch", RESTRICT_SCRATCH },
262 { "move", RESTRICT_MOVE },
265 for(n = 0; n < nvec; ++n) {
266 if((i = TABLE_FIND(restrictions, struct restriction, name, vec[n])) < 0) {
267 error(0, "%s:%d: invalid restriction '%s'",
268 cs->path, cs->line, vec[n]);
271 r |= restrictions[i].bit;
273 VALUE(cs->config, unsigned) = r;
277 static int parse_sample_format(const struct config_state *cs,
278 ao_sample_format *ao,
279 int nvec, char **vec) {
284 error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
287 if (xstrtol(&t, p, &p, 0)) {
288 error(errno, "%s:%d: converting bits-per-sample", cs->path, cs->line);
291 if (t != 8 && t != 16) {
292 error(0, "%s:%d: bad bite-per-sample (%ld)", cs->path, cs->line, t);
295 if (ao) ao->bits = t;
297 case 'l': case 'L': t = AO_FMT_LITTLE; p++; break;
298 case 'b': case 'B': t = AO_FMT_BIG; p++; break;
299 default: t = AO_FMT_NATIVE; break;
301 if (ao) ao->byte_format = t;
303 error(errno, "%s:%d: expected `/' after bits-per-sample",
308 if (xstrtol(&t, p, &p, 0)) {
309 error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
312 if (t < 1 || t > INT_MAX) {
313 error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
316 if (ao) ao->rate = t;
318 error(0, "%s:%d: expected `/' after sample-rate",
323 if (xstrtol(&t, p, &p, 0)) {
324 error(errno, "%s:%d: converting channels", cs->path, cs->line);
327 if (t < 1 || t > 8) {
328 error(0, "%s:%d: silly number (%ld) of channels", cs->path, cs->line, t);
331 if (ao) ao->channels = t;
333 error(0, "%s:%d: junk after channels", cs->path, cs->line);
339 static int set_sample_format(const struct config_state *cs,
340 const struct conf *whoami,
341 int nvec, char **vec) {
342 return parse_sample_format(cs, ADDRESS(cs->config, ao_sample_format),
346 static int set_namepart(const struct config_state *cs,
347 const struct conf *whoami,
348 int nvec, char **vec) {
349 struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
356 error(0, "%s:%d: namepart needs at least 3 arguments", cs->path, cs->line);
360 error(0, "%s:%d: namepart needs at most 5 arguments", cs->path, cs->line);
363 reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
364 if(!(re = pcre_compile(vec[1],
366 |regsub_compile_options(reflags),
367 &errstr, &erroffset, 0))) {
368 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
369 cs->path, cs->line, vec[1], errstr, erroffset);
372 npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
373 npl->s[npl->n].part = xstrdup(vec[0]);
374 npl->s[npl->n].re = re;
375 npl->s[npl->n].replace = xstrdup(vec[2]);
376 npl->s[npl->n].context = xstrdup(vec[3]);
377 npl->s[npl->n].reflags = reflags;
379 /* XXX a bit of a bodge; relies on there being very few parts. */
380 for(n = 0; (n < cs->config->nparts
381 && strcmp(cs->config->parts[n], vec[0])); ++n)
383 if(n >= cs->config->nparts) {
384 cs->config->parts = xrealloc(cs->config->parts,
385 (cs->config->nparts + 1) * sizeof (char *));
386 cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
391 static int set_transform(const struct config_state *cs,
392 const struct conf *whoami,
393 int nvec, char **vec) {
394 struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
401 error(0, "%s:%d: transform needs at least 3 arguments", cs->path, cs->line);
405 error(0, "%s:%d: transform needs at most 5 arguments", cs->path, cs->line);
408 reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
409 if(!(re = pcre_compile(vec[1],
411 |regsub_compile_options(reflags),
412 &errstr, &erroffset, 0))) {
413 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
414 cs->path, cs->line, vec[1], errstr, erroffset);
417 tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
418 tl->t[tl->n].type = xstrdup(vec[0]);
419 tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
420 tl->t[tl->n].re = re;
421 tl->t[tl->n].replace = xstrdup(vec[2]);
422 tl->t[tl->n].flags = reflags;
427 static int set_backend(const struct config_state *cs,
428 const struct conf *whoami,
429 int nvec, char **vec) {
430 int *const valuep = ADDRESS(cs->config, int);
433 error(0, "%s:%d: '%s' requires one argument",
434 cs->path, cs->line, whoami->name);
437 if(!strcmp(vec[0], "alsa")) {
439 *valuep = BACKEND_ALSA;
441 error(0, "%s:%d: ALSA is not available on this platform",
445 } else if(!strcmp(vec[0], "command"))
446 *valuep = BACKEND_COMMAND;
447 else if(!strcmp(vec[0], "network"))
448 *valuep = BACKEND_NETWORK;
450 error(0, "%s:%d: invalid '%s' value '%s'",
451 cs->path, cs->line, whoami->name, vec[0]);
459 static void free_none(struct config attribute((unused)) *c,
460 const struct conf attribute((unused)) *whoami) {
463 static void free_string(struct config *c,
464 const struct conf *whoami) {
465 xfree(VALUE(c, char *));
468 static void free_stringlist(struct config *c,
469 const struct conf *whoami) {
471 struct stringlist *sl = ADDRESS(c, struct stringlist);
473 for(n = 0; n < sl->n; ++n)
478 static void free_stringlistlist(struct config *c,
479 const struct conf *whoami) {
481 struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
482 struct stringlist *sl;
484 for(n = 0; n < sll->n; ++n) {
486 for(m = 0; m < sl->n; ++m)
493 static void free_collectionlist(struct config *c,
494 const struct conf *whoami) {
495 struct collectionlist *cll = ADDRESS(c, struct collectionlist);
496 struct collection *cl;
499 for(n = 0; n < cll->n; ++n) {
508 static void free_namepartlist(struct config *c,
509 const struct conf *whoami) {
510 struct namepartlist *npl = ADDRESS(c, struct namepartlist);
514 for(n = 0; n < npl->n; ++n) {
517 pcre_free(np->re); /* ...whatever pcre_free is set to. */
524 static void free_transformlist(struct config *c,
525 const struct conf *whoami) {
526 struct transformlist *tl = ADDRESS(c, struct transformlist);
530 for(n = 0; n < tl->n; ++n) {
533 pcre_free(t->re); /* ...whatever pcre_free is set to. */
540 /* configuration types */
542 static const struct conftype
543 type_signal = { set_signal, free_none },
544 type_collections = { set_collections, free_collectionlist },
545 type_boolean = { set_boolean, free_none },
546 type_string = { set_string, free_string },
547 type_stringlist = { set_stringlist, free_stringlist },
548 type_integer = { set_integer, free_none },
549 type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
550 type_string_accum = { set_string_accum, free_stringlist },
551 type_sample_format = { set_sample_format, free_none },
552 type_restrict = { set_restrict, free_none },
553 type_namepart = { set_namepart, free_namepartlist },
554 type_transform = { set_transform, free_transformlist },
555 type_backend = { set_backend, free_none };
557 /* specific validation routine */
559 #define VALIDATE_FILE(test, what) do { \
563 for(n = 0; n < nvec; ++n) { \
564 if(stat(vec[n], &sb) < 0) { \
565 error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]); \
568 if(!test(sb.st_mode)) { \
569 error(0, "%s:%d: %s is not a %s", \
570 cs->path, cs->line, vec[n], what); \
576 static int validate_isdir(const struct config_state *cs,
577 int nvec, char **vec) {
578 VALIDATE_FILE(S_ISDIR, "directory");
582 static int validate_isreg(const struct config_state *cs,
583 int nvec, char **vec) {
584 VALIDATE_FILE(S_ISREG, "regular file");
588 static int validate_ischr(const struct config_state *cs,
589 int nvec, char **vec) {
590 VALIDATE_FILE(S_ISCHR, "character device");
594 static int validate_player(const struct config_state *cs,
596 char attribute((unused)) **vec) {
598 error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
605 static int validate_allow(const struct config_state *cs,
607 char attribute((unused)) **vec) {
609 error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
615 static int validate_non_negative(const struct config_state *cs,
616 int nvec, char **vec) {
620 error(0, "%s:%d: missing argument", cs->path, cs->line);
624 error(0, "%s:%d: too many arguments", cs->path, cs->line);
627 if(xstrtol(&n, vec[0], 0, 0)) {
628 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
632 error(0, "%s:%d: must not be negative", cs->path, cs->line);
638 static int validate_positive(const struct config_state *cs,
639 int nvec, char **vec) {
643 error(0, "%s:%d: missing argument", cs->path, cs->line);
647 error(0, "%s:%d: too many arguments", cs->path, cs->line);
650 if(xstrtol(&n, vec[0], 0, 0)) {
651 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
655 error(0, "%s:%d: must be positive", cs->path, cs->line);
661 static int validate_isauser(const struct config_state *cs,
662 int attribute((unused)) nvec,
666 if(!(pw = getpwnam(vec[0]))) {
667 error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
673 static int validate_sample_format(const struct config_state *cs,
674 int attribute((unused)) nvec,
676 return parse_sample_format(cs, 0, nvec, vec);
679 static int validate_channel(const struct config_state *cs,
680 int attribute((unused)) nvec,
682 if(mixer_channel(vec[0]) == -1) {
683 error(0, "%s:%d: invalid channel '%s'", cs->path, cs->line, vec[0]);
689 static int validate_any(const struct config_state attribute((unused)) *cs,
690 int attribute((unused)) nvec,
691 char attribute((unused)) **vec) {
695 static int validate_url(const struct config_state attribute((unused)) *cs,
696 int attribute((unused)) nvec,
700 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
701 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
703 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
704 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
707 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
710 if(!strncmp(s, "http:", 5)
711 || !strncmp(s, "https:", 6)) {
713 /* we only do a rather cursory check */
714 if(strncmp(s, "//", 2)) {
715 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
722 static int validate_alias(const struct config_state *cs,
726 int in_brackets = 0, c;
729 error(0, "%s:%d: missing argument", cs->path, cs->line);
733 error(0, "%s:%d: too many arguments", cs->path, cs->line);
737 while((c = (unsigned char)*s++)) {
741 else if(!isalnum(c)) {
742 error(0, "%s:%d: invalid part name in alias expansion in '%s'",
743 cs->path, cs->line, vec[0]);
751 } else if(c == '\\') {
752 if(!(c = (unsigned char)*s++)) {
753 error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
754 cs->path, cs->line, vec[0]);
756 } else if(c != '\\' && c != '{') {
757 error(0, "%s:%d: invalid escape in alias expansion in '%s'",
758 cs->path, cs->line, vec[0]);
766 error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
767 cs->path, cs->line, vec[0]);
773 static int validate_addrport(const struct config_state attribute((unused)) *cs,
775 char attribute((unused)) **vec) {
778 error(0, "%s:%d: missing address",
782 error(0, "%s:%d: missing port name/number",
788 error(0, "%s:%d: expected ADDRESS PORT",
794 static int validate_address(const struct config_state attribute((unused)) *cs,
796 char attribute((unused)) **vec) {
799 error(0, "%s:%d: missing address",
806 error(0, "%s:%d: expected ADDRESS PORT",
812 /** @brief Item name and and offset */
813 #define C(x) #x, offsetof(struct config, x)
814 /** @brief Item name and and offset */
815 #define C2(x,y) #x, offsetof(struct config, y)
817 /** @brief All configuration items */
818 static const struct conf conf[] = {
819 { C(alias), &type_string, validate_alias },
820 { C(allow), &type_stringlist_accum, validate_allow },
821 { C(broadcast), &type_stringlist, validate_addrport },
822 { C(broadcast_from), &type_stringlist, validate_address },
823 { C(channel), &type_string, validate_channel },
824 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
825 { C(checkpoint_min), &type_integer, validate_non_negative },
826 { C(collection), &type_collections, validate_any },
827 { C(connect), &type_stringlist, validate_addrport },
828 { C(device), &type_string, validate_any },
829 { C(gap), &type_integer, validate_non_negative },
830 { C(history), &type_integer, validate_positive },
831 { C(home), &type_string, validate_isdir },
832 { C(listen), &type_stringlist, validate_addrport },
833 { C(lock), &type_boolean, validate_any },
834 { C(mixer), &type_string, validate_ischr },
835 { C(namepart), &type_namepart, validate_any },
836 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
837 { C(nice_rescan), &type_integer, validate_non_negative },
838 { C(nice_server), &type_integer, validate_any },
839 { C(nice_speaker), &type_integer, validate_any },
840 { C(password), &type_string, validate_any },
841 { C(player), &type_stringlist_accum, validate_player },
842 { C(plugins), &type_string_accum, validate_isdir },
843 { C(prefsync), &type_integer, validate_positive },
844 { C(queue_pad), &type_integer, validate_positive },
845 { C(refresh), &type_integer, validate_positive },
846 { C2(restrict, restrictions), &type_restrict, validate_any },
847 { C(sample_format), &type_sample_format, validate_sample_format },
848 { C(scratch), &type_string_accum, validate_isreg },
849 { C(signal), &type_signal, validate_any },
850 { C(sox_generation), &type_integer, validate_non_negative },
851 { C(speaker_backend), &type_backend, validate_any },
852 { C(speaker_command), &type_string, validate_any },
853 { C(stopword), &type_string_accum, validate_any },
854 { C(templates), &type_string_accum, validate_isdir },
855 { C(transform), &type_transform, validate_any },
856 { C(trust), &type_string_accum, validate_any },
857 { C(url), &type_string, validate_url },
858 { C(user), &type_string, validate_isauser },
859 { C(username), &type_string, validate_any },
862 /** @brief Find a configuration item's definition by key */
863 static const struct conf *find(const char *key) {
866 if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
871 /** @brief Set a new configuration value */
872 static int config_set(const struct config_state *cs,
873 int nvec, char **vec) {
874 const struct conf *which;
876 D(("config_set %s", vec[0]));
877 if(!(which = find(vec[0]))) {
878 error(0, "%s:%d: unknown configuration key '%s'",
879 cs->path, cs->line, vec[0]);
882 return (which->validate(cs, nvec - 1, vec + 1)
883 || which->type->set(cs, which, nvec - 1, vec + 1));
886 static void config_error(const char *msg, void *u) {
887 const struct config_state *cs = u;
889 error(0, "%s:%d: %s", cs->path, cs->line, msg);
892 /** @brief Include a file by name */
893 static int config_include(struct config *c, const char *path) {
895 char *buffer, *inputbuffer, **vec;
897 struct config_state cs;
902 D(("%s: reading configuration", path));
903 if(!(fp = fopen(path, "r"))) {
904 error(errno, "error opening %s", path);
907 while(!inputline(path, fp, &inputbuffer, '\n')) {
909 if(!(buffer = mb2utf8(inputbuffer))) {
910 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
916 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
917 config_error, &cs))) {
923 if(!strcmp(vec[0], "include")) {
925 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
928 config_include(c, vec[1]);
930 ret |= config_set(&cs, n, vec);
932 for(n = 0; vec[n]; ++n) xfree(vec[n]);
937 error(errno, "error reading %s", path);
944 /** @brief Make a new default configuration */
945 static struct config *config_default(void) {
946 struct config *c = xmalloc(sizeof *c);
950 /* Strings had better be xstrdup'd as they will get freed at some point. */
953 c->home = xstrdup(pkgstatedir);
954 if(!(pw = getpwuid(getuid())))
955 fatal(0, "cannot determine our username");
956 logname = pw->pw_name;
957 c->username = xstrdup(logname);
961 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
963 c->device = xstrdup("default");
965 c->speaker_command = 0;
966 c->sample_format.bits = 16;
967 c->sample_format.rate = 44100;
968 c->sample_format.channels = 2;
969 c->sample_format.byte_format = AO_FMT_NATIVE;
971 c->speaker_backend = -1;
975 static char *get_file(struct config *c, const char *name) {
978 byte_xasprintf(&s, "%s/%s", c->home, name);
982 /** @brief Set the default configuration file */
983 static void set_configfile(void) {
985 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
988 /** @brief Free a configuration object */
989 static void config_free(struct config *c) {
993 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
994 conf[n].type->free(c, &conf[n]);
995 for(n = 0; n < c->nparts; ++n)
1002 /** @brief Set post-parse defaults */
1003 static void config_postdefaults(struct config *c) {
1004 struct config_state cs;
1005 const struct conf *whoami;
1008 static const char *namepart[][4] = {
1009 { "title", "/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1010 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1011 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1012 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1013 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1015 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1017 static const char *transform[][5] = {
1018 { "track", "^.*/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1019 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1020 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1021 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1022 { "dir", "[[:punct:]]", "", "sort", "g", }
1024 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1026 cs.path = "<internal>";
1029 if(!c->namepart.n) {
1030 whoami = find("namepart");
1031 for(n = 0; n < NNAMEPART; ++n)
1032 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1034 if(!c->transform.n) {
1035 whoami = find("transform");
1036 for(n = 0; n < NTRANSFORM; ++n)
1037 set_transform(&cs, whoami, 5, (char **)transform[n]);
1039 if(c->speaker_backend == -1) {
1040 if(c->speaker_command)
1041 c->speaker_backend = BACKEND_COMMAND;
1042 else if(c->broadcast.n)
1043 c->speaker_backend = BACKEND_NETWORK;
1046 c->speaker_backend = BACKEND_ALSA;
1048 c->speaker_backend = BACKEND_COMMAND;
1052 if(c->speaker_backend == BACKEND_COMMAND && !c->speaker_command)
1053 fatal(0, "speaker_backend is command but speaker_command is not set");
1054 if(c->speaker_backend == BACKEND_NETWORK && !c->broadcast.n)
1055 fatal(0, "speaker_backend is network but broadcast is not set");
1058 /** @brief (Re-)read the config file */
1065 c = config_default();
1066 /* standalone Disobedience installs might not have a global config file */
1067 if(access(configfile, F_OK) == 0)
1068 if(config_include(c, configfile))
1070 /* if we can read the private config file, do */
1071 if((privconf = config_private())
1072 && access(privconf, R_OK) == 0
1073 && config_include(c, privconf))
1076 /* if there's a per-user system config file for this user, read it */
1077 if(!(pw = getpwuid(getuid())))
1078 fatal(0, "cannot determine our username");
1079 if((privconf = config_usersysconf(pw))
1080 && access(privconf, F_OK) == 0
1081 && config_include(c, privconf))
1084 /* if we have a password file, read it */
1085 if((privconf = config_userconf(getenv("HOME"), pw))
1086 && access(privconf, F_OK) == 0
1087 && config_include(c, privconf))
1090 /* install default namepart and transform settings */
1091 config_postdefaults(c);
1092 /* everything is good so we shall use the new config */
1093 config_free(config);
1098 /** @brief Return the path to the private configuration file */
1099 char *config_private(void) {
1103 byte_xasprintf(&s, "%s.private", configfile);
1107 /** @brief Return the path to user's personal configuration file */
1108 char *config_userconf(const char *home, const struct passwd *pw) {
1111 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1115 /** @brief Return the path to user-specific system configuration */
1116 char *config_usersysconf(const struct passwd *pw) {
1120 if(!strchr(pw->pw_name, '/')) {
1121 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1127 char *config_get_file(const char *name) {
1128 return get_file(config, name);