chiark / gitweb /
Horrible bodge to wait for root user to be created before attempting to
[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(plugins), &type_string_accum, validate_isdir },
951 { C(prefsync), &type_integer, validate_positive },
952 { C(queue_pad), &type_integer, validate_positive },
953 { C(replay_min), &type_integer, validate_non_negative },
954 { C(refresh), &type_integer, validate_positive },
955 { C(reminder_interval), &type_integer, validate_positive },
956 { C(remote_userman), &type_boolean, validate_any },
957 { C2(restrict, restrictions), &type_restrict, validate_any },
958 { C(rtp_delay_threshold), &type_integer, validate_positive },
959 { C(sample_format), &type_sample_format, validate_sample_format },
960 { C(scratch), &type_string_accum, validate_isreg },
961 { C(sendmail), &type_string, validate_isabspath },
962 { C(short_display), &type_integer, validate_positive },
963 { C(signal), &type_signal, validate_any },
964 { C(smtp_server), &type_string, validate_any },
965 { C(sox_generation), &type_integer, validate_non_negative },
966 { C2(speaker_backend, api), &type_string, validate_backend },
967 { C(speaker_command), &type_string, validate_any },
968 { C(stopword), &type_string_accum, validate_any },
969 { C(templates), &type_string_accum, validate_isdir },
970 { C(tracklength), &type_stringlist_accum, validate_tracklength },
971 { C(transform), &type_transform, validate_any },
972 { C(trust), &type_string_accum, validate_any },
973 { C(url), &type_string, validate_url },
974 { C(user), &type_string, validate_isauser },
975 { C(username), &type_string, validate_any },
976};
977
978/** @brief Find a configuration item's definition by key */
979static const struct conf *find(const char *key) {
980 int n;
981
982 if((n = TABLE_FIND(conf, name, key)) < 0)
983 return 0;
984 return &conf[n];
985}
986
987/** @brief Set a new configuration value */
988static int config_set(const struct config_state *cs,
989 int nvec, char **vec) {
990 const struct conf *which;
991
992 D(("config_set %s", vec[0]));
993 if(!(which = find(vec[0]))) {
994 error(0, "%s:%d: unknown configuration key '%s'",
995 cs->path, cs->line, vec[0]);
996 return -1;
997 }
998 return (which->validate(cs, nvec - 1, vec + 1)
999 || which->type->set(cs, which, nvec - 1, vec + 1));
1000}
1001
1002static int config_set_args(const struct config_state *cs,
1003 const char *which, ...) {
1004 va_list ap;
1005 struct vector v[1];
1006 char *s;
1007
1008 vector_init(v);
1009 vector_append(v, (char *)which);
1010 va_start(ap, which);
1011 while((s = va_arg(ap, char *)))
1012 vector_append(v, s);
1013 va_end(ap);
1014 vector_terminate(v);
1015 return config_set(cs, v->nvec, v->vec);
1016}
1017
1018/** @brief Error callback used by config_include() */
1019static void config_error(const char *msg, void *u) {
1020 const struct config_state *cs = u;
1021
1022 error(0, "%s:%d: %s", cs->path, cs->line, msg);
1023}
1024
1025/** @brief Include a file by name */
1026static int config_include(struct config *c, const char *path) {
1027 FILE *fp;
1028 char *buffer, *inputbuffer, **vec;
1029 int n, ret = 0;
1030 struct config_state cs;
1031
1032 cs.path = path;
1033 cs.line = 0;
1034 cs.config = c;
1035 D(("%s: reading configuration", path));
1036 if(!(fp = fopen(path, "r"))) {
1037 error(errno, "error opening %s", path);
1038 return -1;
1039 }
1040 while(!inputline(path, fp, &inputbuffer, '\n')) {
1041 ++cs.line;
1042 if(!(buffer = mb2utf8(inputbuffer))) {
1043 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1044 ret = -1;
1045 xfree(inputbuffer);
1046 continue;
1047 }
1048 xfree(inputbuffer);
1049 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1050 config_error, &cs))) {
1051 ret = -1;
1052 xfree(buffer);
1053 continue;
1054 }
1055 if(n) {
1056 if(!strcmp(vec[0], "include")) {
1057 if(n != 2) {
1058 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1059 ret = -1;
1060 } else
1061 config_include(c, vec[1]);
1062 } else
1063 ret |= config_set(&cs, n, vec);
1064 }
1065 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1066 xfree(vec);
1067 xfree(buffer);
1068 }
1069 if(ferror(fp)) {
1070 error(errno, "error reading %s", path);
1071 ret = -1;
1072 }
1073 fclose(fp);
1074 return ret;
1075}
1076
1077static const char *const default_stopwords[] = {
1078 "stopword",
1079
1080 "01",
1081 "02",
1082 "03",
1083 "04",
1084 "05",
1085 "06",
1086 "07",
1087 "08",
1088 "09",
1089 "1",
1090 "10",
1091 "11",
1092 "12",
1093 "13",
1094 "14",
1095 "15",
1096 "16",
1097 "17",
1098 "18",
1099 "19",
1100 "2",
1101 "20",
1102 "21",
1103 "22",
1104 "23",
1105 "24",
1106 "25",
1107 "26",
1108 "27",
1109 "28",
1110 "29",
1111 "3",
1112 "30",
1113 "4",
1114 "5",
1115 "6",
1116 "7",
1117 "8",
1118 "9",
1119 "a",
1120 "am",
1121 "an",
1122 "and",
1123 "as",
1124 "for",
1125 "i",
1126 "im",
1127 "in",
1128 "is",
1129 "of",
1130 "on",
1131 "the",
1132 "to",
1133 "too",
1134 "we",
1135};
1136#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1137
1138static const char *const default_players[] = {
1139 "*.ogg",
1140 "*.flac",
1141 "*.mp3",
1142 "*.wav",
1143};
1144#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1145
1146/** @brief Make a new default configuration */
1147static struct config *config_default(void) {
1148 struct config *c = xmalloc(sizeof *c);
1149 const char *logname;
1150 struct passwd *pw;
1151 struct config_state cs;
1152 size_t n;
1153
1154 cs.path = "<internal>";
1155 cs.line = 0;
1156 cs.config = c;
1157 /* Strings had better be xstrdup'd as they will get freed at some point. */
1158 c->gap = 0;
1159 c->history = 60;
1160 c->home = xstrdup(pkgstatedir);
1161 if(!(pw = getpwuid(getuid())))
1162 fatal(0, "cannot determine our username");
1163 logname = pw->pw_name;
1164 c->username = xstrdup(logname);
1165 c->refresh = 15;
1166 c->prefsync = 0;
1167 c->signal = SIGKILL;
1168 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1169 c->lock = 1;
1170 c->device = xstrdup("default");
1171 c->nice_rescan = 10;
1172 c->speaker_command = 0;
1173 c->sample_format.bits = 16;
1174 c->sample_format.rate = 44100;
1175 c->sample_format.channels = 2;
1176 c->sample_format.endian = ENDIAN_NATIVE;
1177 c->queue_pad = 10;
1178 c->replay_min = 8 * 3600;
1179 c->api = NULL;
1180 c->multicast_ttl = 1;
1181 c->multicast_loop = 1;
1182 c->authorization_algorithm = xstrdup("sha1");
1183 c->noticed_history = 31;
1184 c->short_display = 32;
1185 c->mixer = 0;
1186 c->channel = 0;
1187 c->dbversion = 2;
1188 c->cookie_login_lifetime = 86400;
1189 c->cookie_key_lifetime = 86400 * 7;
1190 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1191 c->sendmail = xstrdup(sendmail_binary);
1192 c->smtp_server = xstrdup("127.0.0.1");
1193 c->new_max = 100;
1194 c->reminder_interval = 600; /* 10m */
1195 c->new_bias_age = 7 * 86400; /* 1 week */
1196 c->new_bias = 4500000; /* 50 times the base weight */
1197 c->sox_generation = DEFAULT_SOX_GENERATION;
1198 /* Default stopwords */
1199 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1200 exit(1);
1201 /* Default player configuration */
1202 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1203 if(config_set_args(&cs, "player",
1204 default_players[n], "execraw", "disorder-decode", (char *)0))
1205 exit(1);
1206 if(config_set_args(&cs, "tracklength",
1207 default_players[n], "disorder-tracklength", (char *)0))
1208 exit(1);
1209 }
1210 c->broadcast.af = -1;
1211 c->broadcast_from.af = -1;
1212 c->listen.af = -1;
1213 c->connect.af = -1;
1214 return c;
1215}
1216
1217char *config_get_file2(struct config *c, const char *name) {
1218 char *s;
1219
1220 byte_xasprintf(&s, "%s/%s", c->home, name);
1221 return s;
1222}
1223
1224/** @brief Set the default configuration file */
1225static void set_configfile(void) {
1226 if(!configfile)
1227 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1228}
1229
1230/** @brief Free a configuration object */
1231static void config_free(struct config *c) {
1232 int n;
1233
1234 if(c) {
1235 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1236 conf[n].type->free(c, &conf[n]);
1237 for(n = 0; n < c->nparts; ++n)
1238 xfree(c->parts[n]);
1239 xfree(c->parts);
1240 xfree(c);
1241 }
1242}
1243
1244/** @brief Set post-parse defaults */
1245static void config_postdefaults(struct config *c,
1246 int server) {
1247 struct config_state cs;
1248 const struct conf *whoami;
1249 int n;
1250
1251 static const char *namepart[][4] = {
1252 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1253 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1254 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1255 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1256 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1257 };
1258#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1259
1260 static const char *transform[][5] = {
1261 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1262 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1263 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1264 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1265 { "dir", "[[:punct:]]", "", "sort", "g", }
1266 };
1267#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1268
1269 cs.path = "<internal>";
1270 cs.line = 0;
1271 cs.config = c;
1272 if(!c->namepart.n) {
1273 whoami = find("namepart");
1274 for(n = 0; n < NNAMEPART; ++n)
1275 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1276 }
1277 if(!c->transform.n) {
1278 whoami = find("transform");
1279 for(n = 0; n < NTRANSFORM; ++n)
1280 set_transform(&cs, whoami, 5, (char **)transform[n]);
1281 }
1282 if(!c->api) {
1283 if(c->speaker_command)
1284 c->api = xstrdup("command");
1285 else if(c->broadcast.af != -1)
1286 c->api = xstrdup("rtp");
1287 else if(config_uaudio_apis)
1288 c->api = xstrdup(config_uaudio_apis[0]->name);
1289 else
1290 c->api = xstrdup("<none>");
1291 }
1292 if(!strcmp(c->api, "network"))
1293 c->api = xstrdup("rtp");
1294 if(server) {
1295 if(!strcmp(c->api, "command") && !c->speaker_command)
1296 fatal(0, "'api command' but speaker_command is not set");
1297 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1298 fatal(0, "'api rtp' but broadcast is not set");
1299 }
1300 /* Override sample format */
1301 if(!strcmp(c->api, "rtp")) {
1302 c->sample_format.rate = 44100;
1303 c->sample_format.channels = 2;
1304 c->sample_format.bits = 16;
1305 c->sample_format.endian = ENDIAN_NATIVE;
1306 }
1307 if(!strcmp(c->api, "coreaudio")) {
1308 c->sample_format.rate = 44100;
1309 c->sample_format.channels = 2;
1310 c->sample_format.bits = 16;
1311 c->sample_format.endian = ENDIAN_NATIVE;
1312 }
1313 if(!c->default_rights) {
1314 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1315 |RIGHT_MOVE__MASK
1316 |RIGHT_SCRATCH__MASK
1317 |RIGHT_REMOVE__MASK);
1318 /* The idea is to approximate the meaning of the old 'restrict' directive
1319 * in the default rights if they are not overridden. */
1320 if(c->restrictions & RESTRICT_SCRATCH)
1321 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1322 else
1323 r |= RIGHT_SCRATCH_ANY;
1324 if(!(c->restrictions & RESTRICT_MOVE))
1325 r |= RIGHT_MOVE_ANY;
1326 if(c->restrictions & RESTRICT_REMOVE)
1327 r |= RIGHT_REMOVE_MINE;
1328 else
1329 r |= RIGHT_REMOVE_ANY;
1330 c->default_rights = rights_string(r);
1331 }
1332}
1333
1334/** @brief (Re-)read the config file
1335 * @param server If set, do extra checking
1336 * @param oldconfig Old configuration for compatibility check
1337 * @return 0 on success, non-0 on error
1338 *
1339 * If @p oldconfig is set, then certain compatibility checks are done between
1340 * the old and new configurations.
1341 */
1342int config_read(int server,
1343 const struct config *oldconfig) {
1344 struct config *c;
1345 char *privconf;
1346 struct passwd *pw;
1347
1348 set_configfile();
1349 c = config_default();
1350 /* standalone Disobedience installs might not have a global config file */
1351 if(access(configfile, F_OK) == 0)
1352 if(config_include(c, configfile))
1353 return -1;
1354 /* if we can read the private config file, do */
1355 if((privconf = config_private())
1356 && access(privconf, R_OK) == 0
1357 && config_include(c, privconf))
1358 return -1;
1359 xfree(privconf);
1360 /* if there's a per-user system config file for this user, read it */
1361 if(config_per_user) {
1362 if(!(pw = getpwuid(getuid())))
1363 fatal(0, "cannot determine our username");
1364 if((privconf = config_usersysconf(pw))
1365 && access(privconf, F_OK) == 0
1366 && config_include(c, privconf))
1367 return -1;
1368 xfree(privconf);
1369 /* if we have a password file, read it */
1370 if((privconf = config_userconf(0, pw))
1371 && access(privconf, F_OK) == 0
1372 && config_include(c, privconf))
1373 return -1;
1374 xfree(privconf);
1375 }
1376 /* install default namepart and transform settings */
1377 config_postdefaults(c, server);
1378 if(oldconfig) {
1379 int failed = 0;
1380 if(strcmp(c->home, oldconfig->home)) {
1381 error(0, "'home' cannot be changed without a restart");
1382 failed = 1;
1383 }
1384 if(strcmp(c->alias, oldconfig->alias)) {
1385 error(0, "'alias' cannot be changed without a restart");
1386 failed = 1;
1387 }
1388 if(strcmp(c->user, oldconfig->user)) {
1389 error(0, "'user' cannot be changed without a restart");
1390 failed = 1;
1391 }
1392 if(c->nice_speaker != oldconfig->nice_speaker) {
1393 error(0, "'nice_speaker' cannot be changed without a restart");
1394 /* ...but we accept the new config anyway */
1395 }
1396 if(c->nice_server != oldconfig->nice_server) {
1397 error(0, "'nice_server' cannot be changed without a restart");
1398 /* ...but we accept the new config anyway */
1399 }
1400 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1401 error(0, "'namepart' settings cannot be changed without a restart");
1402 failed = 1;
1403 }
1404 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1405 error(0, "'stopword' settings cannot be changed without a restart");
1406 failed = 1;
1407 }
1408 if(failed) {
1409 error(0, "not installing incompatible new configuration");
1410 return -1;
1411 }
1412 }
1413 /* everything is good so we shall use the new config */
1414 config_free(config);
1415 /* warn about obsolete directives */
1416 if(c->restrictions)
1417 error(0, "'restrict' will be removed in a future version");
1418 if(c->allow.n)
1419 error(0, "'allow' will be removed in a future version");
1420 if(c->trust.n)
1421 error(0, "'trust' will be removed in a future version");
1422 if(!c->lock)
1423 error(0, "'lock' will be removed in a future version");
1424 if(c->gap)
1425 error(0, "'gap' will be removed in a future version");
1426 if(c->prefsync)
1427 error(0, "'prefsync' will be removed in a future version");
1428 config = c;
1429 return 0;
1430}
1431
1432/** @brief Return the path to the private configuration file */
1433char *config_private(void) {
1434 char *s;
1435
1436 set_configfile();
1437 byte_xasprintf(&s, "%s.private", configfile);
1438 return s;
1439}
1440
1441/** @brief Return the path to user's personal configuration file */
1442char *config_userconf(const char *home, const struct passwd *pw) {
1443 char *s;
1444
1445 if(!home && !pw && !(pw = getpwuid(getuid())))
1446 fatal(0, "cannot determine our username");
1447 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1448 return s;
1449}
1450
1451/** @brief Return the path to user-specific system configuration */
1452char *config_usersysconf(const struct passwd *pw) {
1453 char *s;
1454
1455 set_configfile();
1456 if(!strchr(pw->pw_name, '/')) {
1457 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1458 return s;
1459 } else
1460 return 0;
1461}
1462
1463char *config_get_file(const char *name) {
1464 return config_get_file2(config, name);
1465}
1466
1467static int stringlist_compare(const struct stringlist *a,
1468 const struct stringlist *b) {
1469 int n = 0, c;
1470
1471 while(n < a->n && n < b->n) {
1472 if((c = strcmp(a->s[n], b->s[n])))
1473 return c;
1474 ++n;
1475 }
1476 if(a->n < b->n)
1477 return -1;
1478 else if(a->n > b->n)
1479 return 1;
1480 else
1481 return 0;
1482}
1483
1484static int namepart_compare(const struct namepart *a,
1485 const struct namepart *b) {
1486 int c;
1487
1488 if((c = strcmp(a->part, b->part)))
1489 return c;
1490 if((c = strcmp(a->res, b->res)))
1491 return c;
1492 if((c = strcmp(a->replace, b->replace)))
1493 return c;
1494 if((c = strcmp(a->context, b->context)))
1495 return c;
1496 if(a->reflags > b->reflags)
1497 return 1;
1498 if(a->reflags < b->reflags)
1499 return -1;
1500 return 0;
1501}
1502
1503static int namepartlist_compare(const struct namepartlist *a,
1504 const struct namepartlist *b) {
1505 int n = 0, c;
1506
1507 while(n < a->n && n < b->n) {
1508 if((c = namepart_compare(&a->s[n], &b->s[n])))
1509 return c;
1510 ++n;
1511 }
1512 if(a->n > b->n)
1513 return 1;
1514 else if(a->n < b->n)
1515 return -1;
1516 else
1517 return 0;
1518}
1519
1520/*
1521Local Variables:
1522c-basic-offset:2
1523comment-column:40
1524fill-column:79
1525End:
1526*/