]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/conf-parser.c
0ae499814e29b55525bb22908ad8ddd0e612f2b8
[thirdparty/systemd.git] / src / shared / conf-parser.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <limits.h>
5 #include <stdint.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <sys/types.h>
9
10 #include "alloc-util.h"
11 #include "conf-files.h"
12 #include "conf-parser.h"
13 #include "def.h"
14 #include "dns-domain.h"
15 #include "escape.h"
16 #include "ether-addr-util.h"
17 #include "extract-word.h"
18 #include "fd-util.h"
19 #include "fileio.h"
20 #include "fs-util.h"
21 #include "hostname-util.h"
22 #include "in-addr-util.h"
23 #include "log.h"
24 #include "macro.h"
25 #include "missing_network.h"
26 #include "nulstr-util.h"
27 #include "parse-util.h"
28 #include "path-util.h"
29 #include "percent-util.h"
30 #include "process-util.h"
31 #include "rlimit-util.h"
32 #include "sd-id128.h"
33 #include "set.h"
34 #include "signal-util.h"
35 #include "socket-util.h"
36 #include "string-util.h"
37 #include "strv.h"
38 #include "syslog-util.h"
39 #include "time-util.h"
40 #include "utf8.h"
41
42 int config_item_table_lookup(
43 const void *table,
44 const char *section,
45 const char *lvalue,
46 ConfigParserCallback *ret_func,
47 int *ret_ltype,
48 void **ret_data,
49 void *userdata) {
50
51 const ConfigTableItem *t;
52
53 assert(table);
54 assert(lvalue);
55 assert(ret_func);
56 assert(ret_ltype);
57 assert(ret_data);
58
59 for (t = table; t->lvalue; t++) {
60
61 if (!streq(lvalue, t->lvalue))
62 continue;
63
64 if (!streq_ptr(section, t->section))
65 continue;
66
67 *ret_func = t->parse;
68 *ret_ltype = t->ltype;
69 *ret_data = t->data;
70 return 1;
71 }
72
73 *ret_func = NULL;
74 *ret_ltype = 0;
75 *ret_data = NULL;
76 return 0;
77 }
78
79 int config_item_perf_lookup(
80 const void *table,
81 const char *section,
82 const char *lvalue,
83 ConfigParserCallback *ret_func,
84 int *ret_ltype,
85 void **ret_data,
86 void *userdata) {
87
88 ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table;
89 const ConfigPerfItem *p;
90
91 assert(table);
92 assert(lvalue);
93 assert(ret_func);
94 assert(ret_ltype);
95 assert(ret_data);
96
97 if (section) {
98 const char *key;
99
100 key = strjoina(section, ".", lvalue);
101 p = lookup(key, strlen(key));
102 } else
103 p = lookup(lvalue, strlen(lvalue));
104 if (!p) {
105 *ret_func = NULL;
106 *ret_ltype = 0;
107 *ret_data = NULL;
108 return 0;
109 }
110
111 *ret_func = p->parse;
112 *ret_ltype = p->ltype;
113 *ret_data = (uint8_t*) userdata + p->offset;
114 return 1;
115 }
116
117 /* Run the user supplied parser for an assignment */
118 static int next_assignment(
119 const char *unit,
120 const char *filename,
121 unsigned line,
122 ConfigItemLookup lookup,
123 const void *table,
124 const char *section,
125 unsigned section_line,
126 const char *lvalue,
127 const char *rvalue,
128 ConfigParseFlags flags,
129 void *userdata) {
130
131 ConfigParserCallback func = NULL;
132 int ltype = 0;
133 void *data = NULL;
134 int r;
135
136 assert(filename);
137 assert(line > 0);
138 assert(lookup);
139 assert(lvalue);
140 assert(rvalue);
141
142 r = lookup(table, section, lvalue, &func, &ltype, &data, userdata);
143 if (r < 0)
144 return r;
145 if (r > 0) {
146 if (!func)
147 return 0;
148
149 return func(unit, filename, line, section, section_line,
150 lvalue, ltype, rvalue, data, userdata);
151 }
152
153 /* Warn about unknown non-extension fields. */
154 if (!(flags & CONFIG_PARSE_RELAXED) && !startswith(lvalue, "X-"))
155 log_syntax(unit, LOG_WARNING, filename, line, 0,
156 "Unknown key name '%s' in section '%s', ignoring.", lvalue, section);
157
158 return 0;
159 }
160
161 /* Parse a single logical line */
162 static int parse_line(
163 const char* unit,
164 const char *filename,
165 unsigned line,
166 const char *sections,
167 ConfigItemLookup lookup,
168 const void *table,
169 ConfigParseFlags flags,
170 char **section,
171 unsigned *section_line,
172 bool *section_ignored,
173 char *l, /* is modified */
174 void *userdata) {
175
176 char *e;
177
178 assert(filename);
179 assert(line > 0);
180 assert(lookup);
181 assert(l);
182
183 l = strstrip(l);
184 if (isempty(l))
185 return 0;
186
187 if (l[0] == '\n')
188 return 0;
189
190 if (!utf8_is_valid(l))
191 return log_syntax_invalid_utf8(unit, LOG_WARNING, filename, line, l);
192
193 if (l[0] == '[') {
194 _cleanup_free_ char *n = NULL;
195 size_t k;
196
197 k = strlen(l);
198 assert(k > 0);
199
200 if (l[k-1] != ']')
201 return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EBADMSG), "Invalid section header '%s'", l);
202
203 n = strndup(l+1, k-2);
204 if (!n)
205 return log_oom();
206
207 if (!string_is_safe(n))
208 return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EBADMSG), "Bad characters in section header '%s'", l);
209
210 if (sections && !nulstr_contains(sections, n)) {
211 bool ignore;
212 const char *t;
213
214 ignore = (flags & CONFIG_PARSE_RELAXED) || startswith(n, "X-");
215
216 if (!ignore)
217 NULSTR_FOREACH(t, sections)
218 if (streq_ptr(n, startswith(t, "-"))) { /* Ignore sections prefixed with "-" in valid section list */
219 ignore = true;
220 break;
221 }
222
223 if (!ignore)
224 log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown section '%s'. Ignoring.", n);
225
226 *section = mfree(*section);
227 *section_line = 0;
228 *section_ignored = true;
229 } else {
230 free_and_replace(*section, n);
231 *section_line = line;
232 *section_ignored = false;
233 }
234
235 return 0;
236 }
237
238 if (sections && !*section) {
239 if (!(flags & CONFIG_PARSE_RELAXED) && !*section_ignored)
240 log_syntax(unit, LOG_WARNING, filename, line, 0, "Assignment outside of section. Ignoring.");
241
242 return 0;
243 }
244
245 e = strchr(l, '=');
246 if (!e)
247 return log_syntax(unit, LOG_WARNING, filename, line, 0,
248 "Missing '=', ignoring line.");
249 if (e == l)
250 return log_syntax(unit, LOG_WARNING, filename, line, 0,
251 "Missing key name before '=', ignoring line.");
252
253 *e = 0;
254 e++;
255
256 return next_assignment(unit,
257 filename,
258 line,
259 lookup,
260 table,
261 *section,
262 *section_line,
263 strstrip(l),
264 strstrip(e),
265 flags,
266 userdata);
267 }
268
269 /* Go through the file and parse each line */
270 int config_parse(
271 const char *unit,
272 const char *filename,
273 FILE *f,
274 const char *sections,
275 ConfigItemLookup lookup,
276 const void *table,
277 ConfigParseFlags flags,
278 void *userdata,
279 struct stat *ret_stat) {
280
281 _cleanup_free_ char *section = NULL, *continuation = NULL;
282 _cleanup_fclose_ FILE *ours = NULL;
283 unsigned line = 0, section_line = 0;
284 bool section_ignored = false, bom_seen = false;
285 struct stat st;
286 int r, fd;
287
288 assert(filename);
289 assert(lookup);
290
291 if (!f) {
292 f = ours = fopen(filename, "re");
293 if (!f) {
294 /* Only log on request, except for ENOENT,
295 * since we return 0 to the caller. */
296 if ((flags & CONFIG_PARSE_WARN) || errno == ENOENT)
297 log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
298 "Failed to open configuration file '%s': %m", filename);
299
300 if (errno == ENOENT) {
301 if (ret_stat)
302 *ret_stat = (struct stat) {};
303
304 return 0;
305 }
306
307 return -errno;
308 }
309 }
310
311 fd = fileno(f);
312 if (fd >= 0) { /* stream might not have an fd, let's be careful hence */
313
314 if (fstat(fd, &st) < 0)
315 return log_full_errno(FLAGS_SET(flags, CONFIG_PARSE_WARN) ? LOG_ERR : LOG_DEBUG, errno,
316 "Failed to fstat(%s): %m", filename);
317
318 (void) stat_warn_permissions(filename, &st);
319 } else
320 st = (struct stat) {};
321
322 for (;;) {
323 _cleanup_free_ char *buf = NULL;
324 bool escaped = false;
325 char *l, *p, *e;
326
327 r = read_line(f, LONG_LINE_MAX, &buf);
328 if (r == 0)
329 break;
330 if (r == -ENOBUFS) {
331 if (flags & CONFIG_PARSE_WARN)
332 log_error_errno(r, "%s:%u: Line too long", filename, line);
333
334 return r;
335 }
336 if (r < 0) {
337 if (FLAGS_SET(flags, CONFIG_PARSE_WARN))
338 log_error_errno(r, "%s:%u: Error while reading configuration file: %m", filename, line);
339
340 return r;
341 }
342
343 line++;
344
345 l = skip_leading_chars(buf, WHITESPACE);
346 if (*l != '\0' && strchr(COMMENTS, *l))
347 continue;
348
349 l = buf;
350 if (!bom_seen) {
351 char *q;
352
353 q = startswith(buf, UTF8_BYTE_ORDER_MARK);
354 if (q) {
355 l = q;
356 bom_seen = true;
357 }
358 }
359
360 if (continuation) {
361 if (strlen(continuation) + strlen(l) > LONG_LINE_MAX) {
362 if (flags & CONFIG_PARSE_WARN)
363 log_error("%s:%u: Continuation line too long", filename, line);
364 return -ENOBUFS;
365 }
366
367 if (!strextend(&continuation, l)) {
368 if (flags & CONFIG_PARSE_WARN)
369 log_oom();
370 return -ENOMEM;
371 }
372
373 p = continuation;
374 } else
375 p = l;
376
377 for (e = p; *e; e++) {
378 if (escaped)
379 escaped = false;
380 else if (*e == '\\')
381 escaped = true;
382 }
383
384 if (escaped) {
385 *(e-1) = ' ';
386
387 if (!continuation) {
388 continuation = strdup(l);
389 if (!continuation) {
390 if (flags & CONFIG_PARSE_WARN)
391 log_oom();
392 return -ENOMEM;
393 }
394 }
395
396 continue;
397 }
398
399 r = parse_line(unit,
400 filename,
401 line,
402 sections,
403 lookup,
404 table,
405 flags,
406 &section,
407 &section_line,
408 &section_ignored,
409 p,
410 userdata);
411 if (r < 0) {
412 if (flags & CONFIG_PARSE_WARN)
413 log_warning_errno(r, "%s:%u: Failed to parse file: %m", filename, line);
414 return r;
415 }
416
417 continuation = mfree(continuation);
418 }
419
420 if (continuation) {
421 r = parse_line(unit,
422 filename,
423 ++line,
424 sections,
425 lookup,
426 table,
427 flags,
428 &section,
429 &section_line,
430 &section_ignored,
431 continuation,
432 userdata);
433 if (r < 0) {
434 if (flags & CONFIG_PARSE_WARN)
435 log_warning_errno(r, "%s:%u: Failed to parse file: %m", filename, line);
436 return r;
437 }
438 }
439
440 if (ret_stat)
441 *ret_stat = st;
442
443 return 1;
444 }
445
446 static int hashmap_put_stats_by_path(Hashmap **stats_by_path, const char *path, const struct stat *st) {
447 _cleanup_free_ struct stat *st_copy = NULL;
448 _cleanup_free_ char *path_copy = NULL;
449 int r;
450
451 assert(stats_by_path);
452 assert(path);
453 assert(st);
454
455 r = hashmap_ensure_allocated(stats_by_path, &path_hash_ops_free_free);
456 if (r < 0)
457 return r;
458
459 st_copy = newdup(struct stat, st, 1);
460 if (!st_copy)
461 return -ENOMEM;
462
463 path_copy = strdup(path);
464 if (!path)
465 return -ENOMEM;
466
467 r = hashmap_put(*stats_by_path, path_copy, st_copy);
468 if (r < 0)
469 return r;
470
471 assert(r > 0);
472 TAKE_PTR(path_copy);
473 TAKE_PTR(st_copy);
474 return 0;
475 }
476
477 static int config_parse_many_files(
478 const char* const* conf_files,
479 char **files,
480 const char *sections,
481 ConfigItemLookup lookup,
482 const void *table,
483 ConfigParseFlags flags,
484 void *userdata,
485 Hashmap **ret_stats_by_path) {
486
487 _cleanup_hashmap_free_ Hashmap *stats_by_path = NULL;
488 struct stat st;
489 char **fn;
490 int r;
491
492 if (ret_stats_by_path) {
493 stats_by_path = hashmap_new(&path_hash_ops_free_free);
494 if (!stats_by_path)
495 return -ENOMEM;
496 }
497
498 /* First read the first found main config file. */
499 STRV_FOREACH(fn, (char**) conf_files) {
500 r = config_parse(NULL, *fn, NULL, sections, lookup, table, flags, userdata, &st);
501 if (r < 0)
502 return r;
503 if (r == 0)
504 continue;
505
506 if (ret_stats_by_path) {
507 r = hashmap_put_stats_by_path(&stats_by_path, *fn, &st);
508 if (r < 0)
509 return r;
510 }
511
512 break;
513 }
514
515 /* Then read all the drop-ins. */
516 STRV_FOREACH(fn, files) {
517 r = config_parse(NULL, *fn, NULL, sections, lookup, table, flags, userdata, &st);
518 if (r < 0)
519 return r;
520 if (r == 0)
521 continue;
522
523 if (ret_stats_by_path) {
524 r = hashmap_put_stats_by_path(&stats_by_path, *fn, &st);
525 if (r < 0)
526 return r;
527 }
528 }
529
530 if (ret_stats_by_path)
531 *ret_stats_by_path = TAKE_PTR(stats_by_path);
532
533 return 0;
534 }
535
536 /* Parse each config file in the directories specified as nulstr. */
537 int config_parse_many_nulstr(
538 const char *conf_file,
539 const char *conf_file_dirs,
540 const char *sections,
541 ConfigItemLookup lookup,
542 const void *table,
543 ConfigParseFlags flags,
544 void *userdata,
545 Hashmap **ret_stats_by_path) {
546
547 _cleanup_strv_free_ char **files = NULL;
548 int r;
549
550 r = conf_files_list_nulstr(&files, ".conf", NULL, 0, conf_file_dirs);
551 if (r < 0)
552 return r;
553
554 return config_parse_many_files(STRV_MAKE_CONST(conf_file),
555 files, sections, lookup, table, flags, userdata,
556 ret_stats_by_path);
557 }
558
559 /* Parse each config file in the directories specified as strv. */
560 int config_parse_many(
561 const char* const* conf_files,
562 const char* const* conf_file_dirs,
563 const char *dropin_dirname,
564 const char *sections,
565 ConfigItemLookup lookup,
566 const void *table,
567 ConfigParseFlags flags,
568 void *userdata,
569 Hashmap **ret_stats_by_path) {
570
571 _cleanup_strv_free_ char **dropin_dirs = NULL;
572 _cleanup_strv_free_ char **files = NULL;
573 const char *suffix;
574 int r;
575
576 suffix = strjoina("/", dropin_dirname);
577 r = strv_extend_strv_concat(&dropin_dirs, (char**) conf_file_dirs, suffix);
578 if (r < 0)
579 return r;
580
581 r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char* const*) dropin_dirs);
582 if (r < 0)
583 return r;
584
585 return config_parse_many_files(conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path);
586 }
587
588 static void config_section_hash_func(const ConfigSection *c, struct siphash *state) {
589 siphash24_compress_string(c->filename, state);
590 siphash24_compress(&c->line, sizeof(c->line), state);
591 }
592
593 static int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) {
594 int r;
595
596 r = strcmp(x->filename, y->filename);
597 if (r != 0)
598 return r;
599
600 return CMP(x->line, y->line);
601 }
602
603 DEFINE_HASH_OPS(config_section_hash_ops, ConfigSection, config_section_hash_func, config_section_compare_func);
604
605 int config_section_new(const char *filename, unsigned line, ConfigSection **s) {
606 ConfigSection *cs;
607
608 cs = malloc0(offsetof(ConfigSection, filename) + strlen(filename) + 1);
609 if (!cs)
610 return -ENOMEM;
611
612 strcpy(cs->filename, filename);
613 cs->line = line;
614
615 *s = TAKE_PTR(cs);
616
617 return 0;
618 }
619
620 unsigned hashmap_find_free_section_line(Hashmap *hashmap) {
621 ConfigSection *cs;
622 unsigned n = 0;
623 void *entry;
624
625 HASHMAP_FOREACH_KEY(entry, cs, hashmap)
626 if (n < cs->line)
627 n = cs->line;
628
629 return n + 1;
630 }
631
632 #define DEFINE_PARSER(type, vartype, conv_func) \
633 DEFINE_CONFIG_PARSE_PTR(config_parse_##type, conv_func, vartype, "Failed to parse " #type " value")
634
635 DEFINE_PARSER(int, int, safe_atoi);
636 DEFINE_PARSER(long, long, safe_atoli);
637 DEFINE_PARSER(uint8, uint8_t, safe_atou8);
638 DEFINE_PARSER(uint16, uint16_t, safe_atou16);
639 DEFINE_PARSER(uint32, uint32_t, safe_atou32);
640 DEFINE_PARSER(int32, int32_t, safe_atoi32);
641 DEFINE_PARSER(uint64, uint64_t, safe_atou64);
642 DEFINE_PARSER(unsigned, unsigned, safe_atou);
643 DEFINE_PARSER(double, double, safe_atod);
644 DEFINE_PARSER(nsec, nsec_t, parse_nsec);
645 DEFINE_PARSER(sec, usec_t, parse_sec);
646 DEFINE_PARSER(sec_def_infinity, usec_t, parse_sec_def_infinity);
647 DEFINE_PARSER(mode, mode_t, parse_mode);
648 DEFINE_PARSER(pid, pid_t, parse_pid);
649
650 int config_parse_iec_size(
651 const char* unit,
652 const char *filename,
653 unsigned line,
654 const char *section,
655 unsigned section_line,
656 const char *lvalue,
657 int ltype,
658 const char *rvalue,
659 void *data,
660 void *userdata) {
661
662 size_t *sz = data;
663 uint64_t v;
664 int r;
665
666 assert(filename);
667 assert(lvalue);
668 assert(rvalue);
669 assert(data);
670
671 r = parse_size(rvalue, 1024, &v);
672 if (r >= 0 && (uint64_t) (size_t) v != v)
673 r = -ERANGE;
674 if (r < 0) {
675 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue);
676 return 0;
677 }
678
679 *sz = (size_t) v;
680 return 0;
681 }
682
683 int config_parse_si_uint64(
684 const char* unit,
685 const char *filename,
686 unsigned line,
687 const char *section,
688 unsigned section_line,
689 const char *lvalue,
690 int ltype,
691 const char *rvalue,
692 void *data,
693 void *userdata) {
694
695 uint64_t *sz = data;
696 int r;
697
698 assert(filename);
699 assert(lvalue);
700 assert(rvalue);
701 assert(data);
702
703 r = parse_size(rvalue, 1000, sz);
704 if (r < 0)
705 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue);
706
707 return 0;
708 }
709
710 int config_parse_iec_uint64(
711 const char* unit,
712 const char *filename,
713 unsigned line,
714 const char *section,
715 unsigned section_line,
716 const char *lvalue,
717 int ltype,
718 const char *rvalue,
719 void *data,
720 void *userdata) {
721
722 uint64_t *bytes = data;
723 int r;
724
725 assert(filename);
726 assert(lvalue);
727 assert(rvalue);
728 assert(data);
729
730 r = parse_size(rvalue, 1024, bytes);
731 if (r < 0)
732 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
733
734 return 0;
735 }
736
737 int config_parse_iec_uint64_infinity(
738 const char* unit,
739 const char *filename,
740 unsigned line,
741 const char *section,
742 unsigned section_line,
743 const char *lvalue,
744 int ltype,
745 const char *rvalue,
746 void *data,
747 void *userdata) {
748
749 uint64_t *bytes = data;
750
751 assert(rvalue);
752 assert(data);
753
754 if (streq(rvalue, "infinity")) {
755 *bytes = UINT64_MAX;
756 return 0;
757 }
758
759 return config_parse_iec_uint64(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata);
760 }
761
762 int config_parse_bool(
763 const char* unit,
764 const char *filename,
765 unsigned line,
766 const char *section,
767 unsigned section_line,
768 const char *lvalue,
769 int ltype,
770 const char *rvalue,
771 void *data,
772 void *userdata) {
773
774 int k;
775 bool *b = data;
776 bool fatal = ltype;
777
778 assert(filename);
779 assert(lvalue);
780 assert(rvalue);
781 assert(data);
782
783 k = parse_boolean(rvalue);
784 if (k < 0) {
785 log_syntax(unit, fatal ? LOG_ERR : LOG_WARNING, filename, line, k,
786 "Failed to parse boolean value%s: %s",
787 fatal ? "" : ", ignoring", rvalue);
788 return fatal ? -ENOEXEC : 0;
789 }
790
791 *b = k;
792 return 0;
793 }
794
795 int config_parse_id128(
796 const char *unit,
797 const char *filename,
798 unsigned line,
799 const char *section,
800 unsigned section_line,
801 const char *lvalue,
802 int ltype,
803 const char *rvalue,
804 void *data,
805 void *userdata) {
806
807 sd_id128_t t, *result = data;
808 int r;
809
810 assert(filename);
811 assert(lvalue);
812 assert(rvalue);
813
814 r = sd_id128_from_string(rvalue, &t);
815 if (r < 0) {
816 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse 128bit ID/UUID, ignoring: %s", rvalue);
817 return 0;
818 }
819
820 if (sd_id128_is_null(t)) {
821 log_syntax(unit, LOG_WARNING, filename, line, 0, "128bit ID/UUID is all 0, ignoring: %s", rvalue);
822 return 0;
823 }
824
825 *result = t;
826 return 0;
827 }
828
829 int config_parse_tristate(
830 const char* unit,
831 const char *filename,
832 unsigned line,
833 const char *section,
834 unsigned section_line,
835 const char *lvalue,
836 int ltype,
837 const char *rvalue,
838 void *data,
839 void *userdata) {
840
841 int k, *t = data;
842
843 assert(filename);
844 assert(lvalue);
845 assert(rvalue);
846 assert(data);
847
848 /* A tristate is pretty much a boolean, except that it can also take an empty string,
849 * indicating "uninitialized", much like NULL is for a pointer type. */
850
851 if (isempty(rvalue)) {
852 *t = -1;
853 return 0;
854 }
855
856 k = parse_boolean(rvalue);
857 if (k < 0) {
858 log_syntax(unit, LOG_WARNING, filename, line, k,
859 "Failed to parse boolean value for %s=, ignoring: %s", lvalue, rvalue);
860 return 0;
861 }
862
863 *t = k;
864 return 0;
865 }
866
867 int config_parse_string(
868 const char *unit,
869 const char *filename,
870 unsigned line,
871 const char *section,
872 unsigned section_line,
873 const char *lvalue,
874 int ltype,
875 const char *rvalue,
876 void *data,
877 void *userdata) {
878
879 char **s = ASSERT_PTR(data);
880
881 assert(filename);
882 assert(lvalue);
883 assert(rvalue);
884
885 if (isempty(rvalue)) {
886 *s = mfree(*s);
887 return 0;
888 }
889
890 if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) {
891 _cleanup_free_ char *escaped = NULL;
892
893 escaped = cescape(rvalue);
894 log_syntax(unit, LOG_WARNING, filename, line, 0,
895 "Specified string contains unsafe characters, ignoring: %s", strna(escaped));
896 return 0;
897 }
898
899 if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_ASCII) && !ascii_is_valid(rvalue)) {
900 _cleanup_free_ char *escaped = NULL;
901
902 escaped = cescape(rvalue);
903 log_syntax(unit, LOG_WARNING, filename, line, 0,
904 "Specified string contains invalid ASCII characters, ignoring: %s", strna(escaped));
905 return 0;
906 }
907
908 return free_and_strdup_warn(s, empty_to_null(rvalue));
909 }
910
911 int config_parse_dns_name(
912 const char *unit,
913 const char *filename,
914 unsigned line,
915 const char *section,
916 unsigned section_line,
917 const char *lvalue,
918 int ltype,
919 const char *rvalue,
920 void *data,
921 void *userdata) {
922
923 char **hostname = ASSERT_PTR(data);
924 int r;
925
926 assert(filename);
927 assert(lvalue);
928 assert(rvalue);
929
930 if (isempty(rvalue)) {
931 *hostname = mfree(*hostname);
932 return 0;
933 }
934
935 r = dns_name_is_valid(rvalue);
936 if (r < 0) {
937 log_syntax(unit, LOG_WARNING, filename, line, r,
938 "Failed to check validity of DNS domain name '%s', ignoring assignment: %m", rvalue);
939 return 0;
940 }
941 if (r == 0) {
942 log_syntax(unit, LOG_WARNING, filename, line, 0,
943 "Specified invalid DNS domain name, ignoring assignment: %s", rvalue);
944 return 0;
945 }
946
947 return free_and_strdup_warn(hostname, rvalue);
948 }
949
950 int config_parse_hostname(
951 const char *unit,
952 const char *filename,
953 unsigned line,
954 const char *section,
955 unsigned section_line,
956 const char *lvalue,
957 int ltype,
958 const char *rvalue,
959 void *data,
960 void *userdata) {
961
962 char **hostname = ASSERT_PTR(data);
963
964 assert(filename);
965 assert(lvalue);
966 assert(rvalue);
967
968 if (isempty(rvalue)) {
969 *hostname = mfree(*hostname);
970 return 0;
971 }
972
973 if (!hostname_is_valid(rvalue, 0)) {
974 log_syntax(unit, LOG_WARNING, filename, line, 0,
975 "Specified invalid hostname, ignoring assignment: %s", rvalue);
976 return 0;
977 }
978
979 return config_parse_dns_name(unit, filename, line, section, section_line,
980 lvalue, ltype, rvalue, data, userdata);
981 }
982
983 int config_parse_path(
984 const char *unit,
985 const char *filename,
986 unsigned line,
987 const char *section,
988 unsigned section_line,
989 const char *lvalue,
990 int ltype,
991 const char *rvalue,
992 void *data,
993 void *userdata) {
994
995 _cleanup_free_ char *n = NULL;
996 bool fatal = ltype;
997 char **s = data;
998 int r;
999
1000 assert(filename);
1001 assert(lvalue);
1002 assert(rvalue);
1003 assert(data);
1004
1005 if (isempty(rvalue))
1006 goto finalize;
1007
1008 n = strdup(rvalue);
1009 if (!n)
1010 return log_oom();
1011
1012 r = path_simplify_and_warn(n, PATH_CHECK_ABSOLUTE | (fatal ? PATH_CHECK_FATAL : 0), unit, filename, line, lvalue);
1013 if (r < 0)
1014 return fatal ? -ENOEXEC : 0;
1015
1016 finalize:
1017 return free_and_replace(*s, n);
1018 }
1019
1020 int config_parse_strv(
1021 const char *unit,
1022 const char *filename,
1023 unsigned line,
1024 const char *section,
1025 unsigned section_line,
1026 const char *lvalue,
1027 int ltype,
1028 const char *rvalue,
1029 void *data,
1030 void *userdata) {
1031
1032 char ***sv = data;
1033 int r;
1034
1035 assert(filename);
1036 assert(lvalue);
1037 assert(rvalue);
1038 assert(data);
1039
1040 if (isempty(rvalue)) {
1041 *sv = strv_free(*sv);
1042 return 0;
1043 }
1044
1045 for (const char *p = rvalue;;) {
1046 char *word = NULL;
1047
1048 r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
1049 if (r == 0)
1050 return 0;
1051 if (r == -ENOMEM)
1052 return log_oom();
1053 if (r < 0) {
1054 log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
1055 return 0;
1056 }
1057
1058 r = strv_consume(sv, word);
1059 if (r < 0)
1060 return log_oom();
1061 }
1062 }
1063
1064 int config_parse_warn_compat(
1065 const char *unit,
1066 const char *filename,
1067 unsigned line,
1068 const char *section,
1069 unsigned section_line,
1070 const char *lvalue,
1071 int ltype,
1072 const char *rvalue,
1073 void *data,
1074 void *userdata) {
1075
1076 Disabled reason = ltype;
1077
1078 switch(reason) {
1079
1080 case DISABLED_CONFIGURATION:
1081 log_syntax(unit, LOG_DEBUG, filename, line, 0,
1082 "Support for option %s= has been disabled at compile time and it is ignored", lvalue);
1083 break;
1084
1085 case DISABLED_LEGACY:
1086 log_syntax(unit, LOG_INFO, filename, line, 0,
1087 "Support for option %s= has been removed and it is ignored", lvalue);
1088 break;
1089
1090 case DISABLED_EXPERIMENTAL:
1091 log_syntax(unit, LOG_INFO, filename, line, 0,
1092 "Support for option %s= has not yet been enabled and it is ignored", lvalue);
1093 break;
1094 }
1095
1096 return 0;
1097 }
1098
1099 int config_parse_log_facility(
1100 const char *unit,
1101 const char *filename,
1102 unsigned line,
1103 const char *section,
1104 unsigned section_line,
1105 const char *lvalue,
1106 int ltype,
1107 const char *rvalue,
1108 void *data,
1109 void *userdata) {
1110
1111 int *o = data, x;
1112
1113 assert(filename);
1114 assert(lvalue);
1115 assert(rvalue);
1116 assert(data);
1117
1118 x = log_facility_unshifted_from_string(rvalue);
1119 if (x < 0) {
1120 log_syntax(unit, LOG_WARNING, filename, line, x, "Failed to parse log facility, ignoring: %s", rvalue);
1121 return 0;
1122 }
1123
1124 *o = (x << 3) | LOG_PRI(*o);
1125
1126 return 0;
1127 }
1128
1129 int config_parse_log_level(
1130 const char *unit,
1131 const char *filename,
1132 unsigned line,
1133 const char *section,
1134 unsigned section_line,
1135 const char *lvalue,
1136 int ltype,
1137 const char *rvalue,
1138 void *data,
1139 void *userdata) {
1140
1141 int *o = data, x;
1142
1143 assert(filename);
1144 assert(lvalue);
1145 assert(rvalue);
1146 assert(data);
1147
1148 x = log_level_from_string(rvalue);
1149 if (x < 0) {
1150 log_syntax(unit, LOG_WARNING, filename, line, x, "Failed to parse log level, ignoring: %s", rvalue);
1151 return 0;
1152 }
1153
1154 if (*o < 0) /* if it wasn't initialized so far, assume zero facility */
1155 *o = x;
1156 else
1157 *o = (*o & LOG_FACMASK) | x;
1158
1159 return 0;
1160 }
1161
1162 int config_parse_signal(
1163 const char *unit,
1164 const char *filename,
1165 unsigned line,
1166 const char *section,
1167 unsigned section_line,
1168 const char *lvalue,
1169 int ltype,
1170 const char *rvalue,
1171 void *data,
1172 void *userdata) {
1173
1174 int *sig = data, r;
1175
1176 assert(filename);
1177 assert(lvalue);
1178 assert(rvalue);
1179 assert(sig);
1180
1181 r = signal_from_string(rvalue);
1182 if (r <= 0) {
1183 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse signal name, ignoring: %s", rvalue);
1184 return 0;
1185 }
1186
1187 *sig = r;
1188 return 0;
1189 }
1190
1191 int config_parse_personality(
1192 const char *unit,
1193 const char *filename,
1194 unsigned line,
1195 const char *section,
1196 unsigned section_line,
1197 const char *lvalue,
1198 int ltype,
1199 const char *rvalue,
1200 void *data,
1201 void *userdata) {
1202
1203 unsigned long *personality = data, p;
1204
1205 assert(filename);
1206 assert(lvalue);
1207 assert(rvalue);
1208 assert(personality);
1209
1210 if (isempty(rvalue))
1211 p = PERSONALITY_INVALID;
1212 else {
1213 p = personality_from_string(rvalue);
1214 if (p == PERSONALITY_INVALID) {
1215 log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse personality, ignoring: %s", rvalue);
1216 return 0;
1217 }
1218 }
1219
1220 *personality = p;
1221 return 0;
1222 }
1223
1224 int config_parse_ifname(
1225 const char *unit,
1226 const char *filename,
1227 unsigned line,
1228 const char *section,
1229 unsigned section_line,
1230 const char *lvalue,
1231 int ltype,
1232 const char *rvalue,
1233 void *data,
1234 void *userdata) {
1235
1236 char **s = data;
1237 int r;
1238
1239 assert(filename);
1240 assert(lvalue);
1241 assert(rvalue);
1242 assert(data);
1243
1244 if (isempty(rvalue)) {
1245 *s = mfree(*s);
1246 return 0;
1247 }
1248
1249 if (!ifname_valid(rvalue)) {
1250 log_syntax(unit, LOG_WARNING, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue);
1251 return 0;
1252 }
1253
1254 r = free_and_strdup(s, rvalue);
1255 if (r < 0)
1256 return log_oom();
1257
1258 return 0;
1259 }
1260
1261 int config_parse_ifnames(
1262 const char *unit,
1263 const char *filename,
1264 unsigned line,
1265 const char *section,
1266 unsigned section_line,
1267 const char *lvalue,
1268 int ltype,
1269 const char *rvalue,
1270 void *data,
1271 void *userdata) {
1272
1273 _cleanup_strv_free_ char **names = NULL;
1274 char ***s = data;
1275 int r;
1276
1277 assert(filename);
1278 assert(lvalue);
1279 assert(rvalue);
1280 assert(data);
1281
1282 if (isempty(rvalue)) {
1283 *s = strv_free(*s);
1284 return 0;
1285 }
1286
1287 for (const char *p = rvalue;;) {
1288 _cleanup_free_ char *word = NULL;
1289
1290 r = extract_first_word(&p, &word, NULL, 0);
1291 if (r == -ENOMEM)
1292 return log_oom();
1293 if (r < 0) {
1294 log_syntax(unit, LOG_WARNING, filename, line, r,
1295 "Failed to extract interface name, ignoring assignment: %s",
1296 rvalue);
1297 return 0;
1298 }
1299 if (r == 0)
1300 break;
1301
1302 if (!ifname_valid_full(word, ltype)) {
1303 log_syntax(unit, LOG_WARNING, filename, line, 0,
1304 "Interface name is not valid or too long, ignoring assignment: %s",
1305 word);
1306 continue;
1307 }
1308
1309 r = strv_consume(&names, TAKE_PTR(word));
1310 if (r < 0)
1311 return log_oom();
1312 }
1313
1314 r = strv_extend_strv(s, names, true);
1315 if (r < 0)
1316 return log_oom();
1317
1318 return 0;
1319 }
1320
1321 int config_parse_ip_port(
1322 const char *unit,
1323 const char *filename,
1324 unsigned line,
1325 const char *section,
1326 unsigned section_line,
1327 const char *lvalue,
1328 int ltype,
1329 const char *rvalue,
1330 void *data,
1331 void *userdata) {
1332
1333 uint16_t *s = data;
1334 uint16_t port;
1335 int r;
1336
1337 assert(filename);
1338 assert(lvalue);
1339 assert(rvalue);
1340 assert(data);
1341
1342 if (isempty(rvalue)) {
1343 *s = 0;
1344 return 0;
1345 }
1346
1347 r = parse_ip_port(rvalue, &port);
1348 if (r < 0) {
1349 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse port '%s'.", rvalue);
1350 return 0;
1351 }
1352
1353 *s = port;
1354
1355 return 0;
1356 }
1357
1358 int config_parse_mtu(
1359 const char *unit,
1360 const char *filename,
1361 unsigned line,
1362 const char *section,
1363 unsigned section_line,
1364 const char *lvalue,
1365 int ltype,
1366 const char *rvalue,
1367 void *data,
1368 void *userdata) {
1369
1370 uint32_t *mtu = data;
1371 int r;
1372
1373 assert(rvalue);
1374 assert(mtu);
1375
1376 r = parse_mtu(ltype, rvalue, mtu);
1377 if (r == -ERANGE) {
1378 log_syntax(unit, LOG_WARNING, filename, line, r,
1379 "Maximum transfer unit (MTU) value out of range. Permitted range is %" PRIu32 "…%" PRIu32 ", ignoring: %s",
1380 (uint32_t) (ltype == AF_INET6 ? IPV6_MIN_MTU : IPV4_MIN_MTU), (uint32_t) UINT32_MAX,
1381 rvalue);
1382 return 0;
1383 }
1384 if (r < 0) {
1385 log_syntax(unit, LOG_WARNING, filename, line, r,
1386 "Failed to parse MTU value '%s', ignoring: %m", rvalue);
1387 return 0;
1388 }
1389
1390 return 0;
1391 }
1392
1393 int config_parse_rlimit(
1394 const char *unit,
1395 const char *filename,
1396 unsigned line,
1397 const char *section,
1398 unsigned section_line,
1399 const char *lvalue,
1400 int ltype,
1401 const char *rvalue,
1402 void *data,
1403 void *userdata) {
1404
1405 struct rlimit **rl = data, d = {};
1406 int r;
1407
1408 assert(rvalue);
1409 assert(rl);
1410
1411 r = rlimit_parse(ltype, rvalue, &d);
1412 if (r == -EILSEQ) {
1413 log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue);
1414 return 0;
1415 }
1416 if (r < 0) {
1417 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue);
1418 return 0;
1419 }
1420
1421 if (rl[ltype])
1422 *rl[ltype] = d;
1423 else {
1424 rl[ltype] = newdup(struct rlimit, &d, 1);
1425 if (!rl[ltype])
1426 return log_oom();
1427 }
1428
1429 return 0;
1430 }
1431
1432 int config_parse_permille(
1433 const char* unit,
1434 const char *filename,
1435 unsigned line,
1436 const char *section,
1437 unsigned section_line,
1438 const char *lvalue,
1439 int ltype,
1440 const char *rvalue,
1441 void *data,
1442 void *userdata) {
1443
1444 unsigned *permille = data;
1445 int r;
1446
1447 assert(filename);
1448 assert(lvalue);
1449 assert(rvalue);
1450 assert(permille);
1451
1452 r = parse_permille(rvalue);
1453 if (r < 0) {
1454 log_syntax(unit, LOG_WARNING, filename, line, r,
1455 "Failed to parse permille value, ignoring: %s", rvalue);
1456 return 0;
1457 }
1458
1459 *permille = (unsigned) r;
1460
1461 return 0;
1462 }
1463
1464 int config_parse_vlanprotocol(
1465 const char* unit,
1466 const char *filename,
1467 unsigned line,
1468 const char *section,
1469 unsigned section_line,
1470 const char *lvalue,
1471 int ltype,
1472 const char *rvalue,
1473 void *data,
1474 void *userdata) {
1475
1476 int *vlan_protocol = data;
1477
1478 assert(filename);
1479 assert(lvalue);
1480
1481 if (isempty(rvalue)) {
1482 *vlan_protocol = -1;
1483 return 0;
1484 }
1485
1486 if (STR_IN_SET(rvalue, "802.1ad", "802.1AD"))
1487 *vlan_protocol = ETH_P_8021AD;
1488 else if (STR_IN_SET(rvalue, "802.1q", "802.1Q"))
1489 *vlan_protocol = ETH_P_8021Q;
1490 else {
1491 log_syntax(unit, LOG_WARNING, filename, line, 0,
1492 "Failed to parse VLAN protocol value, ignoring: %s", rvalue);
1493 return 0;
1494 }
1495
1496 return 0;
1497 }
1498
1499 int config_parse_hw_addr(
1500 const char *unit,
1501 const char *filename,
1502 unsigned line,
1503 const char *section,
1504 unsigned section_line,
1505 const char *lvalue,
1506 int ltype,
1507 const char *rvalue,
1508 void *data,
1509 void *userdata) {
1510
1511 struct hw_addr_data a, *hwaddr = data;
1512 int r;
1513
1514 assert(filename);
1515 assert(lvalue);
1516 assert(rvalue);
1517 assert(data);
1518
1519 if (isempty(rvalue)) {
1520 *hwaddr = HW_ADDR_NULL;
1521 return 0;
1522 }
1523
1524 r = parse_hw_addr_full(rvalue, ltype, &a);
1525 if (r < 0) {
1526 log_syntax(unit, LOG_WARNING, filename, line, r,
1527 "Not a valid hardware address, ignoring assignment: %s", rvalue);
1528 return 0;
1529 }
1530
1531 *hwaddr = a;
1532 return 0;
1533 }
1534
1535 int config_parse_hw_addrs(
1536 const char *unit,
1537 const char *filename,
1538 unsigned line,
1539 const char *section,
1540 unsigned section_line,
1541 const char *lvalue,
1542 int ltype,
1543 const char *rvalue,
1544 void *data,
1545 void *userdata) {
1546
1547 Set **hwaddrs = data;
1548 int r;
1549
1550 assert(filename);
1551 assert(lvalue);
1552 assert(rvalue);
1553 assert(data);
1554
1555 if (isempty(rvalue)) {
1556 /* Empty assignment resets the list */
1557 *hwaddrs = set_free(*hwaddrs);
1558 return 0;
1559 }
1560
1561 for (const char *p = rvalue;;) {
1562 _cleanup_free_ char *word = NULL;
1563 _cleanup_free_ struct hw_addr_data *n = NULL;
1564
1565 r = extract_first_word(&p, &word, NULL, 0);
1566 if (r == 0)
1567 return 0;
1568 if (r == -ENOMEM)
1569 return log_oom();
1570 if (r < 0) {
1571 log_syntax(unit, LOG_WARNING, filename, line, r,
1572 "Invalid syntax, ignoring: %s", rvalue);
1573 return 0;
1574 }
1575
1576 n = new(struct hw_addr_data, 1);
1577 if (!n)
1578 return log_oom();
1579
1580 r = parse_hw_addr_full(word, ltype, n);
1581 if (r < 0) {
1582 log_syntax(unit, LOG_WARNING, filename, line, r,
1583 "Not a valid hardware address, ignoring: %s", word);
1584 continue;
1585 }
1586
1587 r = set_ensure_consume(hwaddrs, &hw_addr_hash_ops_free, TAKE_PTR(n));
1588 if (r < 0)
1589 return log_oom();
1590 }
1591 }
1592
1593 int config_parse_ether_addr(
1594 const char *unit,
1595 const char *filename,
1596 unsigned line,
1597 const char *section,
1598 unsigned section_line,
1599 const char *lvalue,
1600 int ltype,
1601 const char *rvalue,
1602 void *data,
1603 void *userdata) {
1604
1605 _cleanup_free_ struct ether_addr *n = NULL;
1606 struct ether_addr **hwaddr = data;
1607 int r;
1608
1609 assert(filename);
1610 assert(lvalue);
1611 assert(rvalue);
1612 assert(data);
1613
1614 if (isempty(rvalue)) {
1615 *hwaddr = mfree(*hwaddr);
1616 return 0;
1617 }
1618
1619 n = new0(struct ether_addr, 1);
1620 if (!n)
1621 return log_oom();
1622
1623 r = parse_ether_addr(rvalue, n);
1624 if (r < 0) {
1625 log_syntax(unit, LOG_WARNING, filename, line, r,
1626 "Not a valid MAC address, ignoring assignment: %s", rvalue);
1627 return 0;
1628 }
1629
1630 free_and_replace(*hwaddr, n);
1631
1632 return 0;
1633 }
1634
1635 int config_parse_ether_addrs(
1636 const char *unit,
1637 const char *filename,
1638 unsigned line,
1639 const char *section,
1640 unsigned section_line,
1641 const char *lvalue,
1642 int ltype,
1643 const char *rvalue,
1644 void *data,
1645 void *userdata) {
1646
1647 Set **hwaddrs = data;
1648 int r;
1649
1650 assert(filename);
1651 assert(lvalue);
1652 assert(rvalue);
1653 assert(data);
1654
1655 if (isempty(rvalue)) {
1656 /* Empty assignment resets the list */
1657 *hwaddrs = set_free(*hwaddrs);
1658 return 0;
1659 }
1660
1661 for (const char *p = rvalue;;) {
1662 _cleanup_free_ char *word = NULL;
1663 _cleanup_free_ struct ether_addr *n = NULL;
1664
1665 r = extract_first_word(&p, &word, NULL, 0);
1666 if (r == 0)
1667 return 0;
1668 if (r == -ENOMEM)
1669 return log_oom();
1670 if (r < 0) {
1671 log_syntax(unit, LOG_WARNING, filename, line, r,
1672 "Invalid syntax, ignoring: %s", rvalue);
1673 return 0;
1674 }
1675
1676 n = new(struct ether_addr, 1);
1677 if (!n)
1678 return log_oom();
1679
1680 r = parse_ether_addr(word, n);
1681 if (r < 0) {
1682 log_syntax(unit, LOG_WARNING, filename, line, r,
1683 "Not a valid MAC address, ignoring: %s", word);
1684 continue;
1685 }
1686
1687 r = set_ensure_consume(hwaddrs, &ether_addr_hash_ops_free, TAKE_PTR(n));
1688 if (r < 0)
1689 return log_oom();
1690 }
1691 }
1692
1693 int config_parse_in_addr_non_null(
1694 const char *unit,
1695 const char *filename,
1696 unsigned line,
1697 const char *section,
1698 unsigned section_line,
1699 const char *lvalue,
1700 int ltype,
1701 const char *rvalue,
1702 void *data,
1703 void *userdata) {
1704
1705 /* data must be a pointer to struct in_addr or in6_addr, and the type is determined by ltype. */
1706 struct in_addr *ipv4 = data;
1707 struct in6_addr *ipv6 = data;
1708 union in_addr_union a;
1709 int r;
1710
1711 assert(filename);
1712 assert(lvalue);
1713 assert(rvalue);
1714 assert(data);
1715 assert(IN_SET(ltype, AF_INET, AF_INET6));
1716
1717 if (isempty(rvalue)) {
1718 if (ltype == AF_INET)
1719 *ipv4 = (struct in_addr) {};
1720 else
1721 *ipv6 = (struct in6_addr) {};
1722 return 0;
1723 }
1724
1725 r = in_addr_from_string(ltype, rvalue, &a);
1726 if (r < 0) {
1727 log_syntax(unit, LOG_WARNING, filename, line, r,
1728 "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
1729 return 0;
1730 }
1731
1732 if (!in_addr_is_set(ltype, &a)) {
1733 log_syntax(unit, LOG_WARNING, filename, line, 0,
1734 "%s= cannot be the ANY address, ignoring: %s", lvalue, rvalue);
1735 return 0;
1736 }
1737
1738 if (ltype == AF_INET)
1739 *ipv4 = a.in;
1740 else
1741 *ipv6 = a.in6;
1742 return 0;
1743 }
1744
1745 DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value");
1746 DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value");