Parse 'PerProcess' section
[platform/core/system/resourced.git] / src / common / config-parser.c
1 /*
2  * resourced
3  *
4  * Copyright (c) 2013 Samsung Electronics Co., Ltd.
5  *
6  * Licensed under the Apache License, Version 2.0 (the License);
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <stdio.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <assert.h>
23 #include <limits.h>
24
25 #include "util.h"
26 #include "trace.h"
27 #include "config-parser.h"
28 #include "proc-common.h"
29
30 #define MAX_SECTION             64
31 #define CPU_INIT_PRIO   100
32
33 static GSList *app_conf_info_list;
34 static GSList *service_conf_info_list;
35
36 static struct proc_conf_info* process_exist(const char *name, enum proc_type proc_type)
37 {
38         GSList *iter;
39         bool found = false;
40         struct proc_conf_info *pci = NULL;
41
42         if (proc_type != APP_TYPE && proc_type != SERVICE_TYPE)
43                 return pci;
44
45         gslist_for_each_item(iter,
46                 proc_type == APP_TYPE ? app_conf_info_list : service_conf_info_list) {
47                 pci     = (struct proc_conf_info *)iter->data;
48                 if (!strncmp(pci->name, name, strlen(name)+1)) {
49                         found = true;
50                         break;
51                 }
52         }
53
54         if (!found)
55                 pci = NULL;
56
57         return pci;
58 }
59
60 static int vendor_config(struct parse_result *result, void *user_data)
61 {
62         int *config_type = (int *)user_data;
63         static struct proc_conf_info *pci = NULL;
64
65         if (!result || !user_data)
66                 return RESOURCED_ERROR_INVALID_PARAMETER;
67
68         if (strncmp(result->section, PER_PROCESS_SECTION_CONF, strlen(PER_PROCESS_SECTION_CONF)+1))
69                 return RESOURCED_ERROR_NONE;
70
71         if (!strncmp(result->name, SERVICE_NAME_CONF, strlen(SERVICE_NAME_CONF)+1) ||
72                 !strncmp(result->name, APP_NAME_CONF, strlen(APP_NAME_CONF)+1)) {
73                 pci = process_exist(result->value, result->name[0] == 'A' ? APP_TYPE : SERVICE_TYPE);
74                 if (pci == NULL) {
75                         pci = (struct proc_conf_info *)calloc(1, sizeof(struct proc_conf_info));
76                         if (pci == NULL) {
77                                 _E("Failed to allocate memory during parsing vendor configurations");
78                                 return RESOURCED_ERROR_OUT_OF_MEMORY;
79                         }
80                         if (result->name[0] == 'A') {
81                                 pci->proc_type = APP_TYPE;
82                                 app_conf_info_list = g_slist_prepend(app_conf_info_list, (gpointer)pci);
83                         }
84                         else {
85                                 pci->proc_type = SERVICE_TYPE;
86                                 service_conf_info_list = g_slist_prepend(service_conf_info_list, (gpointer)pci);
87                         }
88                         pci->mem_type = CGROUP_TOP;
89                         pci->cpu_type = CGROUP_TOP;
90                         pci->cpu_priority = CPU_INIT_PRIO;
91                         strncpy(pci->name, result->value, sizeof(pci->name)-1);
92                 }
93         }
94         else if (!strncmp(result->name, CPU_CGROUP_NAME_CONF, strlen(CPU_CGROUP_NAME_CONF)+1) &&
95                         *config_type == LIMITER_CONFIG) {
96                 if (!pci) {
97                         _E("process configuration information pointer should not be NULL");
98                         return RESOURCED_ERROR_FAIL;
99                 }
100
101                 if (!strncmp(result->value, CGROUP_VIP_VALUE_CONF,
102                         strlen(CGROUP_VIP_VALUE_CONF) +1)) {
103                         pci->cpu_type = CGROUP_VIP;
104                 }
105                 else if (!strncmp(result->value, CGROUP_HIGH_VALUE_CONF,
106                         strlen(CGROUP_HIGH_VALUE_CONF) +1)) {
107                         pci->cpu_type = CGROUP_HIGH;
108                 }
109                 else if (!strncmp(result->value, CGROUP_MEDIUM_VALUE_CONF,
110                         strlen(CGROUP_MEDIUM_VALUE_CONF) +1)) {
111                         pci->cpu_type = CGROUP_MEDIUM;
112                 }
113                 else if (!strncmp(result->value, CGROUP_LOW_VALUE_CONF,
114                         strlen(CGROUP_LOW_VALUE_CONF) +1)) {
115                         pci->cpu_type = CGROUP_LOW;
116                 }
117                 else {
118                         _E("invalid parameter (%s)", result->value);
119                         return RESOURCED_ERROR_INVALID_PARAMETER;
120                 }
121         }
122         else if (!strncmp(result->name, MEM_CGROUP_NAME_CONF, strlen(MEM_CGROUP_NAME_CONF)+1) &&
123                         *config_type == LIMITER_CONFIG) {
124                 if (!pci) {
125                         _E("process configuration information pointer should not be NULL");
126                         return RESOURCED_ERROR_FAIL;
127                 }
128
129                 if (!strncmp(result->value, CGROUP_VIP_VALUE_CONF,
130                         strlen(CGROUP_VIP_VALUE_CONF) +1)) {
131                         pci->mem_type = CGROUP_VIP;
132                 }
133                 else if (!strncmp(result->value, CGROUP_HIGH_VALUE_CONF,
134                         strlen(CGROUP_HIGH_VALUE_CONF) +1)) {
135                         pci->mem_type = CGROUP_HIGH;
136                 }
137                 else if (!strncmp(result->value, CGROUP_MEDIUM_VALUE_CONF,
138                         strlen(CGROUP_MEDIUM_VALUE_CONF) +1)) {
139                         pci->mem_type = CGROUP_MEDIUM;
140                 }
141                 else if (!strncmp(result->value, CGROUP_LOW_VALUE_CONF,
142                         strlen(CGROUP_LOW_VALUE_CONF) +1)) {
143                         pci->mem_type = CGROUP_LOW;
144                 }
145                 else {
146                         _E("invalid parameter (%s)", result->value);
147                         return RESOURCED_ERROR_INVALID_PARAMETER;
148                 }
149         }
150         else if (!strncmp(result->name, MEM_LIMIT_ACTION_NAME_CONF,
151                 strlen(MEM_LIMIT_ACTION_NAME_CONF)+1) && *config_type == LIMITER_CONFIG) {
152                 if (!pci) {
153                         _E("process configuration information pointer should not be NULL");
154                         return RESOURCED_ERROR_FAIL;
155                 }
156
157                 char *ptr = strchr(result->value, ',');
158                 if (ptr == NULL) {
159                         _E("Cannot find ',' in the string (%s)", result->value);
160                         return RESOURCED_ERROR_FAIL;
161                 }
162
163                 char *second_value = ptr + 1;
164                 char temp;
165
166                 if (result->value > (ptr - 2)) {
167                         _E("Size of string should be larger than 2");
168                         return RESOURCED_ERROR_FAIL;
169                 }
170
171                 if (*(ptr - 1) == 'B') {
172                         temp = *(ptr - 2);
173                         *(ptr - 2) = '\0';
174
175                         if (temp == 'G') {
176                                 pci->mem_action.memory = GBYTE_TO_MBYTE(atoi(result->value));
177                         }
178                         else if (temp == 'M') {
179                                 pci->mem_action.memory = atoi(result->value);
180                         }
181                         else if (temp == 'K') {
182                                 pci->mem_action.memory = KBYTE_TO_MBYTE(atoi(result->value));
183                         }
184                         else if (temp == ' ') {
185                                 pci->mem_action.memory = BYTE_TO_MBYTE(atoi(result->value));
186                         }
187                         else {
188                                 _E("Memory size unit should be GB or MB or KB or B");
189                                 return RESOURCED_ERROR_FAIL;
190                         }
191
192                         if (!strncmp(second_value, ACTION_BROADCAST_VALUE_CONF,
193                                                 strlen(ACTION_BROADCAST_VALUE_CONF)+1))
194                                 pci->mem_action.action = PROC_ACTION_BROADCAST;
195                         else if (!strncmp(second_value, ACTION_RECLAIM_VALUE_CONF,
196                                                 strlen(ACTION_RECLAIM_VALUE_CONF)+1))
197                                 pci->mem_action.action = PROC_ACTION_RECLAIM;
198                         else if (!strncmp(second_value, ACTION_KILL_VALUE_CONF,
199                                                 strlen(ACTION_KILL_VALUE_CONF)+1))
200                                 pci->mem_action.action = PROC_ACTION_KILL;
201                         else {
202                                 _E("action (%s) is not supported", second_value);
203                                 return RESOURCED_ERROR_FAIL;
204                         }
205                 }
206                 else {
207                         _E("Memory size unit should be XB");
208                         return RESOURCED_ERROR_FAIL;
209                 }
210         }
211         else if (!strncmp(result->name, CPU_PRIORITY_NAME_CONF, strlen(CPU_PRIORITY_NAME_CONF)+1) &&
212                         *config_type == LIMITER_CONFIG) {
213                 if (!pci) {
214                         _E("process configuration information pointer should not be NULL");
215                         return RESOURCED_ERROR_FAIL;
216                 }
217                 pci->cpu_priority = atoi(result->value);
218         }
219         else if (!strncmp(result->name, ACTION_ON_FAILURE_NAME_CONF,
220                                 strlen(ACTION_ON_FAILURE_NAME_CONF)+1) && *config_type == PROCESS_CONFIG) {
221                 if (!pci) {
222                         _E("process configuration information pointer should not be NULL");
223                         return RESOURCED_ERROR_FAIL;
224                 }
225
226                 if (!strncmp(result->value, ACTION_REBOOT_VALUE_CONF,
227                                         strlen(ACTION_REBOOT_VALUE_CONF) +1)) {
228                         pci->fail_action = PROC_ACTION_REBOOT;
229                 }
230                 else {
231                         _E("invalid parameter (%s)", result->value);
232                         return RESOURCED_ERROR_INVALID_PARAMETER;
233                 }
234         }
235         else if (!strncmp(result->name, WATCHDOG_ACTION_NAME_CONF,
236                                 strlen(WATCHDOG_ACTION_NAME_CONF)+1) && *config_type == PROCESS_CONFIG) {
237                 if (!pci) {
238                         _E("process configuration information pointer should not be NULL");
239                         return RESOURCED_ERROR_FAIL;
240                 }
241
242                 if (!strncmp(result->value, ACTION_IGNORE_VALUE_CONF,
243                                         strlen(ACTION_IGNORE_VALUE_CONF) +1)) {
244                         pci->watchdog_action = PROC_ACTION_IGNORE;
245                 }
246                 else if (!strncmp(result->value, ACTION_KILL_VALUE_CONF,
247                                         strlen(ACTION_KILL_VALUE_CONF) +1)) {
248                         pci->watchdog_action = PROC_ACTION_KILL;
249                 }
250                 else {
251                         _E("invalid parameter (%s)", result->value);
252                         return RESOURCED_ERROR_INVALID_PARAMETER;
253                 }
254         }
255         else {
256                 _E("Unknown configuration name (%s) and value (%s) on section (%s)",
257                                 result->name, result->value, result->section);
258         }
259
260         return RESOURCED_ERROR_NONE;
261 }
262
263 static void load_per_vendor_configs(const char *dir, int func(struct parse_result *result,
264                                                                 void *user_data), void *user_data)
265 {
266         int count;
267         int idx;
268         struct dirent **namelist;
269
270         if ((count = scandir(dir, &namelist, NULL, alphasort)) == -1) {
271                 _W("failed to opendir (%s)", dir);
272                 return;
273         }
274
275         for (idx = 0; idx < count; idx++) {
276                 char path[PATH_MAX] = {0, };
277
278                 if (!strstr(namelist[idx]->d_name, CONF_FILE_SUFFIX))
279                         continue;
280
281                 snprintf(path, sizeof(path), "%s/%s", dir, namelist[idx]->d_name);
282                 config_parse(path, func, user_data);
283                 free(namelist[idx]);
284         }
285
286         free(namelist);
287 }
288
289 void resourced_parse_vendor_configs(void)
290 {
291         int config_type;
292
293         config_type = LIMITER_CONFIG;
294         load_per_vendor_configs(CGROUP_VIP_LIST_DIR, vendor_config, &config_type);
295
296         config_type = PROCESS_CONFIG;
297         load_per_vendor_configs(PROC_CONF_DIR, vendor_config, &config_type);
298
299         //DEBUG
300         GSList *iter;
301         gslist_for_each_item(iter, service_conf_info_list) {
302                 struct proc_conf_info *pci = (struct proc_conf_info *)iter->data;
303                 _I("[CONFIG] name: %s", pci->name);
304                 _I("[CONFIG] mem cgroup: %d", pci->mem_type);
305                 _I("[CONFIG] cpu cgroup: %d", pci->cpu_type);
306                 _I("[CONFIG] cpu priority: %d", pci->cpu_priority);
307                 _I("[CONFIG] watchdog_flag: %x", pci->watchdog_action);
308                 _I("[CONFIG] fail_flag: %x", pci->fail_action);
309                 _I("[CONFIG] memory: %lu MB", pci->mem_action.memory);
310                 _I("[CONFIG] action: %d", pci->mem_action.action);
311         }
312
313         gslist_for_each_item(iter, app_conf_info_list) {
314                 struct proc_conf_info *pci = (struct proc_conf_info *)iter->data;
315                 _I("[CONFIG] name: %s", pci->name);
316                 _I("[CONFIG] mem cgroup: %d", pci->mem_type);
317                 _I("[CONFIG] cpu cgroup: %d", pci->cpu_type);
318                 _I("[CONFIG] cpu priority: %d", pci->cpu_priority);
319                 _I("[CONFIG] watchdog_flag: %x", pci->watchdog_action);
320                 _I("[CONFIG] fail_flag: %x", pci->fail_action);
321                 _I("[CONFIG] memory: %lu MB", pci->mem_action.memory);
322                 _I("[CONFIG] action: %d", pci->mem_action.action);
323         }
324 }
325
326 int config_parse(const char *file_name, int cb(struct parse_result *result,
327                         void *user_data), void *user_data)
328 {
329         FILE *f = NULL;
330         struct parse_result result;
331         /* use stack for parsing */
332         char line[LINE_MAX];
333         char section[MAX_SECTION];
334         char *start, *end, *name, *value;
335         int lineno = 0, ret = 0;
336
337         if (!file_name || !cb) {
338                 ret = -EINVAL;
339                 goto error;
340         }
341
342         /* open conf file */
343         f = fopen(file_name, "r");
344         if (!f) {
345                 _E("Failed to open file %s", file_name);
346                 ret = -EIO;
347                 goto error;
348         }
349
350         /* parsing line by line */
351         while (fgets(line, LINE_MAX, f) != NULL) {
352                 lineno++;
353
354                 start = line;
355                 truncate_nl(start);
356                 start = strstrip(start);
357
358                 if (*start == COMMENT) {
359                         continue;
360                 } else if (*start == '[') {
361                         /* parse section */
362                         end = strchr(start, ']');
363                         if (!end || *end != ']') {
364                                 ret = -EBADMSG;
365                                 goto error;
366                         }
367
368                         *end = '\0';
369                         strncpy(section, start + 1, sizeof(section)-1);
370                         section[MAX_SECTION-1] = '\0';
371                 } else if (*start) {
372                         /* parse name & value */
373                         end = strchr(start, '=');
374                         if (!end || *end != '=') {
375                                 ret = -EBADMSG;
376                                 goto error;
377                         }
378                         *end = '\0';
379                         name = strstrip(start);
380                         value = strstrip(end + 1);
381                         end = strchr(value, COMMENT);
382                         if (end && *end == COMMENT) {
383                                 *end = '\0';
384                                 value = strstrip(value);
385                         }
386
387                         result.section = section;
388                         result.name = name;
389                         result.value = value;
390                         /* callback with parse result */
391                         ret = cb(&result, user_data);
392                         if (ret < 0) {
393                                 ret = -EBADMSG;
394                                 goto error;
395                         }
396                 }
397         }
398         return 0;
399
400 error:
401         if (f)
402                 fclose(f);
403         _E("Failed to read %s:%d!", file_name, lineno);
404         return ret;
405 }
406
407 static int config_table_lookup(void *table,
408                                const char *section,
409                                const char *lvalue,
410                                ConfigParserCallback *func,
411                                int *ltype,
412                                void **data)
413 {
414         ConfigTableItem *t;
415
416         assert(table);
417         assert(lvalue);
418         assert(func);
419         assert(ltype);
420         assert(data);
421
422         for (t = table; t->lvalue; t++) {
423
424                 if (!streq(lvalue, t->lvalue))
425                         continue;
426
427                 if (!streq_ptr(section, t->section))
428                         continue;
429
430                 *func = t->cb;
431                 *ltype = t->ltype;
432                 *data = t->data;
433                 return 1;
434         }
435
436         return 0;
437 }
438
439 /* Run the user supplied parser for an assignment */
440 static int config_parse_table(const char *filename,
441                               unsigned line,
442                               void *table,
443                               const char *section,
444                               const char *lvalue,
445                               const char *rvalue)
446 {
447         ConfigParserCallback cb = NULL;
448         int ltype = 0;
449         void *data = NULL;
450         int r;
451
452         assert(filename);
453         assert(section);
454         assert(lvalue);
455         assert(rvalue);
456
457         r = config_table_lookup(table,
458                                 section,
459                                 lvalue,
460                                 &cb,
461                                 &ltype,
462                                 &data);
463         if (r <= 0)
464                 return r;
465
466         if (cb)
467                 return cb(filename,
468                           line,
469                           section,
470                           lvalue,
471                           ltype,
472                           rvalue,
473                           data);
474
475         return 0;
476 }
477
478 int config_parse_new(const char *filename, void *table)
479 {
480         _cleanup_fclose_ FILE *f = NULL;
481         char *sections[MAX_SECTION] = { 0 };
482         char *section = NULL, *n, *e, l[LINE_MAX];
483         size_t len;
484         int i, r, num_section = 0;
485         bool already;
486         unsigned line = 0;
487
488         assert(filename);
489
490         f = fopen(filename, "r");
491         if (!f) {
492                 const int saved_errno = errno;
493                 _E("Failed to open file %s", filename); // can modify errno
494                 return -saved_errno;
495         }
496
497         while (!feof(f)) {
498                 _cleanup_free_ char *lvalue = NULL, *rvalue = NULL;
499                 char *rs = NULL;
500
501                 if (fgets(l, LINE_MAX, f) == NULL) {
502                         if (feof(f))
503                                 break;
504
505                         _E("Failed to parse configuration file '%s': %m", filename);
506                         r = -errno;
507                         goto finish;
508                 }
509
510                 line++;
511                 truncate_nl(l);
512
513                 if (strchr(COMMENTS NEWLINE, *l))
514                         continue;
515
516                 if (*l == '[') {
517                         len = strlen(l);
518                         if (l[len-1] != ']') {
519                                 _E("Error: Invalid section header: %s", l);
520                                 r = -EBADMSG;
521                                 goto finish;
522                         }
523
524                         n = strndup(l+1, len-2);
525                         if (!n) {
526                                 r = -ENOMEM;
527                                 goto finish;
528                         }
529
530                         already = false;
531                         for (i = 0; i < num_section; i++) {
532                                 if (streq(n, sections[i])) {
533                                         section = sections[i];
534                                         already = true;
535                                         free(n);
536                                         break;
537                                 }
538                         }
539
540                         if (already)
541                                 continue;
542
543                         section = n;
544                         sections[num_section] = n;
545                         num_section++;
546                         if (num_section > MAX_SECTION) {
547                                 _E("Error: max number of section reached: %d", num_section);
548                                 r = -EOVERFLOW;
549                                 goto finish;
550                         }
551
552                         continue;
553                 }
554
555                 if (!section)
556                         continue;
557
558                 e = strchr(l, '=');
559                 if (e == NULL) {
560                         _D("Warning: config: no '=' character in line '%s'.", l);
561                         continue;
562                 }
563
564                 lvalue = strndup(l, e-l);
565                 if (!lvalue) {
566                         r = -ENOMEM;
567                         goto finish;
568                 }
569
570                 strstrip(lvalue);
571
572                 rs = strstrip(e+1);
573                 rvalue = strdup(rs);
574                 if (!rvalue) {
575                         r = -ENOMEM;
576                         goto finish;
577                 }
578
579                 strstrip(rvalue);
580
581                 r = config_parse_table(filename,
582                                        line,
583                                        table,
584                                        section,
585                                        lvalue,
586                                        rvalue);
587                 if (r < 0)
588                         goto finish;
589         }
590
591         r = 0;
592
593 finish:
594         for (i = 0; i < num_section; i++)
595                 if (sections[i])
596                         free(sections[i]);
597
598         return r;
599 }
600
601 int config_parse_dir(const char *dir, ConfigParseFunc fp, void *data)
602 {
603         _cleanup_closedir_ DIR *d = NULL;
604         struct dirent *de;
605
606         d = opendir(dir);
607         if (!d) {
608                 _E("Failed to open dir: %s", dir);
609                 return errno;
610         }
611
612         for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) {
613                 if (!de) {
614                         if (errno > 0)
615                                 return -errno;
616                         break;
617                 } else if (streq(de->d_name, ".") || streq(de->d_name, "..")) {
618                         continue;
619                 }
620
621                 _cleanup_free_ char *path = NULL;
622                 int r;
623
624                 if (de->d_type != DT_REG)
625                         continue;
626
627                 r = asprintf(&path, "%s/%s", dir, de->d_name);
628                 if (r < 0)
629                         return -ENOMEM;
630
631                 r = fp(path, data);
632                 /* Do not just break loop until parse all file of
633                  * dir. Just only put log */
634                 if (r < 0)
635                         _D("Failed to parse config: %s", de->d_name);
636         }
637
638         return 0;
639 }
640
641 int config_parse_bool(const char *filename,
642                       unsigned line,
643                       const char *section,
644                       const char *lvalue,
645                       int ltype,
646                       const char *rvalue,
647                       void *data)
648 {
649         int k;
650         bool *b = data;
651
652         assert(filename);
653         assert(lvalue);
654         assert(rvalue);
655         assert(data);
656
657         k = parse_boolean(rvalue);
658         if (k < 0) {
659                 _E("Failed to parse boolean value, ignoring: %s", rvalue);
660                 return 0;
661         }
662
663         *b = !!k;
664
665         return 0;
666 }
667
668 #define DEFINE_PARSER(type, vartype, conv_func)          \
669         int config_parse_##type(                                        \
670                                 const char *filename,                   \
671                                 unsigned line,                          \
672                                 const char *section,                    \
673                                 const char *lvalue,                     \
674                                 int ltype,                              \
675                                 const char *rvalue,                     \
676                                 void *data) {                           \
677                                                                                                                 \
678                 vartype *i = data;                                      \
679                                                                         \
680                 assert(filename);                                       \
681                 assert(lvalue);                                         \
682                 assert(rvalue);                                         \
683                 assert(data);                                           \
684                                                                         \
685                 *i = conv_func(rvalue);                          \
686                 return 0;                                               \
687                 }
688
689 DEFINE_PARSER(int, int, atoi)
690 DEFINE_PARSER(float, float, atof)
691 DEFINE_PARSER(long, long, atol)
692
693 int config_parse_string(const char *filename,
694                         unsigned line,
695                         const char *section,
696                         const char *lvalue,
697                         int ltype,
698                         const char *rvalue,
699                         void *data)
700 {
701         char **s = data, *n;
702
703         assert(filename);
704         assert(lvalue);
705         assert(rvalue);
706         assert(data);
707
708         if (is_empty(rvalue))
709                 n = NULL;
710         else {
711                 n = strdup(rvalue);
712                 if (!n)
713                         return -ENOMEM;
714         }
715
716         free(*s);
717         *s = n;
718
719         return 0;
720 }
721
722 int config_parse_bytes(const char *filename,
723                        unsigned line,
724                        const char *section,
725                        const char *lvalue,
726                        int ltype,
727                        const char *rvalue,
728                        void *data)
729 {
730         size_t *s = data;
731         int r;
732
733         assert(filename);
734         assert(lvalue);
735         assert(rvalue);
736         assert(data);
737
738         if (is_empty(rvalue))
739                 *s = 0;
740         else {
741                 r = parse_bytes(rvalue, s);
742                 if (r < 0)
743                         return r;
744         }
745
746         return 0;
747 }
748
749 int config_parse_strv(const char *filename,
750                       unsigned line,
751                       const char *section,
752                       const char *lvalue,
753                       int ltype,
754                       const char *rvalue,
755                       void *data)
756 {
757         char ***strv = data;
758         char **o = NULL, **v = NULL, **vv = NULL;
759         int r;
760
761         assert(filename);
762         assert(lvalue);
763         assert(rvalue);
764         assert(data);
765
766         if (is_empty(rvalue))
767                 return 0;
768
769         r = str_to_strv(rvalue, &v, WHITESPACE);
770         if (r < 0)
771                 return r;
772
773         o = *strv;
774
775         r = strv_attach(o, v, &vv, true);
776         if (r < 0)
777                 return r;
778
779         *strv = vv;
780
781         return 0;
782 }