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