]>
Commit | Line | Data |
---|---|---|
3efd4195 LP |
1 | /*-*- Mode: C; c-basic-offset: 8 -*-*/ |
2 | ||
3 | #include <assert.h> | |
4 | #include <errno.h> | |
5 | #include <string.h> | |
034c6ed7 | 6 | #include <linux/oom.h> |
3efd4195 LP |
7 | |
8 | #include "name.h" | |
9 | #include "strv.h" | |
10 | #include "conf-parser.h" | |
11 | #include "load-fragment.h" | |
16354eff | 12 | #include "log.h" |
3efd4195 | 13 | |
42f4e3c4 | 14 | static int config_parse_deps( |
3efd4195 LP |
15 | const char *filename, |
16 | unsigned line, | |
17 | const char *section, | |
18 | const char *lvalue, | |
19 | const char *rvalue, | |
20 | void *data, | |
21 | void *userdata) { | |
22 | ||
44d8db9e | 23 | NameDependency d = PTR_TO_UINT(data); |
3efd4195 LP |
24 | Name *name = userdata; |
25 | char *w; | |
26 | size_t l; | |
27 | char *state; | |
28 | ||
29 | assert(filename); | |
30 | assert(lvalue); | |
31 | assert(rvalue); | |
3efd4195 LP |
32 | |
33 | FOREACH_WORD(w, &l, rvalue, state) { | |
34 | char *t; | |
35 | int r; | |
36 | Name *other; | |
37 | ||
38 | if (!(t = strndup(w, l))) | |
39 | return -ENOMEM; | |
40 | ||
41 | r = manager_load_name(name->meta.manager, t, &other); | |
42 | free(t); | |
43 | ||
44 | if (r < 0) | |
45 | return r; | |
46 | ||
44d8db9e | 47 | if ((r = name_add_dependency(name, d, other)) < 0) |
3efd4195 LP |
48 | return r; |
49 | } | |
50 | ||
51 | return 0; | |
52 | } | |
53 | ||
42f4e3c4 | 54 | static int config_parse_names( |
87d1515d LP |
55 | const char *filename, |
56 | unsigned line, | |
57 | const char *section, | |
58 | const char *lvalue, | |
59 | const char *rvalue, | |
60 | void *data, | |
61 | void *userdata) { | |
62 | ||
63 | Set **set = data; | |
64 | Name *name = userdata; | |
65 | char *w; | |
66 | size_t l; | |
67 | char *state; | |
68 | ||
69 | assert(filename); | |
70 | assert(lvalue); | |
71 | assert(rvalue); | |
72 | assert(data); | |
73 | ||
74 | FOREACH_WORD(w, &l, rvalue, state) { | |
75 | char *t; | |
76 | int r; | |
77 | Name *other; | |
78 | ||
79 | if (!(t = strndup(w, l))) | |
80 | return -ENOMEM; | |
81 | ||
82 | other = manager_get_name(name->meta.manager, t); | |
83 | ||
84 | if (other) { | |
85 | ||
86 | if (other != name) { | |
87 | ||
5cb5a6ff | 88 | if (other->meta.load_state != NAME_STUB) { |
87d1515d LP |
89 | free(t); |
90 | return -EEXIST; | |
91 | } | |
92 | ||
7fad411c | 93 | if ((r = name_merge(name, other)) < 0) { |
87d1515d LP |
94 | free(t); |
95 | return r; | |
96 | } | |
97 | } | |
98 | ||
99 | } else { | |
100 | ||
101 | if (!*set) | |
102 | if (!(*set = set_new(trivial_hash_func, trivial_compare_func))) { | |
103 | free(t); | |
104 | return -ENOMEM; | |
105 | } | |
106 | ||
107 | if ((r = set_put(*set, t)) < 0) { | |
108 | free(t); | |
109 | return r; | |
110 | } | |
11dd41ce LP |
111 | |
112 | t = NULL; | |
87d1515d LP |
113 | } |
114 | ||
115 | free(t); | |
116 | } | |
117 | ||
118 | return 0; | |
119 | } | |
120 | ||
42f4e3c4 LP |
121 | static int config_parse_listen( |
122 | const char *filename, | |
123 | unsigned line, | |
124 | const char *section, | |
125 | const char *lvalue, | |
126 | const char *rvalue, | |
127 | void *data, | |
128 | void *userdata) { | |
129 | ||
16354eff | 130 | int r; |
542563ba LP |
131 | SocketPort *p; |
132 | Socket *s; | |
16354eff | 133 | |
42f4e3c4 LP |
134 | assert(filename); |
135 | assert(lvalue); | |
136 | assert(rvalue); | |
137 | assert(data); | |
138 | ||
542563ba LP |
139 | s = (Socket*) data; |
140 | ||
141 | if (!(p = new0(SocketPort, 1))) | |
142 | return -ENOMEM; | |
143 | ||
144 | if (streq(lvalue, "ListenFIFO")) { | |
145 | p->type = SOCKET_FIFO; | |
146 | ||
147 | if (!(p->path = strdup(rvalue))) { | |
148 | free(p); | |
149 | return -ENOMEM; | |
150 | } | |
151 | } else { | |
152 | p->type = SOCKET_SOCKET; | |
153 | ||
154 | if ((r = socket_address_parse(&p->address, rvalue)) < 0) { | |
155 | log_error("[%s:%u] Failed to parse address value: %s", filename, line, rvalue); | |
156 | free(p); | |
157 | return r; | |
158 | } | |
159 | ||
160 | if (streq(lvalue, "ListenStream")) | |
161 | p->address.type = SOCK_STREAM; | |
162 | else if (streq(lvalue, "ListenDatagram")) | |
163 | p->address.type = SOCK_DGRAM; | |
164 | else { | |
165 | assert(streq(lvalue, "ListenSequentialPacket")); | |
166 | p->address.type = SOCK_SEQPACKET; | |
167 | } | |
168 | ||
169 | if (socket_address_family(&p->address) != AF_LOCAL && p->address.type == SOCK_SEQPACKET) { | |
170 | free(p); | |
171 | return -EPROTONOSUPPORT; | |
172 | } | |
16354eff LP |
173 | } |
174 | ||
542563ba | 175 | p->fd = -1; |
034c6ed7 | 176 | LIST_PREPEND(SocketPort, port, s->ports, p); |
542563ba | 177 | |
16354eff | 178 | return 0; |
42f4e3c4 LP |
179 | } |
180 | ||
034c6ed7 | 181 | static int config_parse_socket_bind( |
42f4e3c4 LP |
182 | const char *filename, |
183 | unsigned line, | |
184 | const char *section, | |
185 | const char *lvalue, | |
186 | const char *rvalue, | |
187 | void *data, | |
188 | void *userdata) { | |
189 | ||
542563ba LP |
190 | int r; |
191 | Socket *s; | |
42f4e3c4 LP |
192 | |
193 | assert(filename); | |
194 | assert(lvalue); | |
195 | assert(rvalue); | |
196 | assert(data); | |
197 | ||
542563ba LP |
198 | s = (Socket*) data; |
199 | ||
200 | if ((r = parse_boolean(rvalue)) < 0) { | |
201 | log_error("[%s:%u] Failed to parse bind IPv6 only value: %s", filename, line, rvalue); | |
202 | return r; | |
16354eff | 203 | } |
42f4e3c4 | 204 | |
542563ba LP |
205 | s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH; |
206 | ||
42f4e3c4 LP |
207 | return 0; |
208 | } | |
209 | ||
034c6ed7 LP |
210 | static int config_parse_nice( |
211 | const char *filename, | |
212 | unsigned line, | |
213 | const char *section, | |
214 | const char *lvalue, | |
215 | const char *rvalue, | |
216 | void *data, | |
217 | void *userdata) { | |
218 | ||
219 | int *i = data, priority, r; | |
220 | ||
221 | assert(filename); | |
222 | assert(lvalue); | |
223 | assert(rvalue); | |
224 | assert(data); | |
225 | ||
226 | if ((r = safe_atoi(rvalue, &priority)) < 0) { | |
227 | log_error("[%s:%u] Failed to parse nice priority: %s", filename, line, rvalue); | |
228 | return r; | |
229 | } | |
230 | ||
231 | if (priority < PRIO_MIN || priority >= PRIO_MAX) { | |
232 | log_error("[%s:%u] Nice priority out of range: %s", filename, line, rvalue); | |
233 | return -ERANGE; | |
234 | } | |
235 | ||
236 | *i = priority; | |
237 | return 0; | |
238 | } | |
239 | ||
240 | static int config_parse_oom_adjust( | |
241 | const char *filename, | |
242 | unsigned line, | |
243 | const char *section, | |
244 | const char *lvalue, | |
245 | const char *rvalue, | |
246 | void *data, | |
247 | void *userdata) { | |
248 | ||
249 | int *i = data, oa, r; | |
250 | ||
251 | assert(filename); | |
252 | assert(lvalue); | |
253 | assert(rvalue); | |
254 | assert(data); | |
255 | ||
256 | if ((r = safe_atoi(rvalue, &oa)) < 0) { | |
257 | log_error("[%s:%u] Failed to parse OOM adjust value: %s", filename, line, rvalue); | |
258 | return r; | |
259 | } | |
260 | ||
261 | if (oa < OOM_DISABLE || oa > OOM_ADJUST_MAX) { | |
262 | log_error("[%s:%u] OOM adjust value out of range: %s", filename, line, rvalue); | |
263 | return -ERANGE; | |
264 | } | |
265 | ||
266 | *i = oa; | |
267 | return 0; | |
268 | } | |
269 | ||
270 | static int config_parse_umask( | |
271 | const char *filename, | |
272 | unsigned line, | |
273 | const char *section, | |
274 | const char *lvalue, | |
275 | const char *rvalue, | |
276 | void *data, | |
277 | void *userdata) { | |
278 | ||
279 | mode_t *m = data; | |
280 | long l; | |
281 | char *x = NULL; | |
282 | ||
283 | assert(filename); | |
284 | assert(lvalue); | |
285 | assert(rvalue); | |
286 | assert(data); | |
287 | ||
288 | errno = 0; | |
289 | l = strtol(rvalue, &x, 8); | |
290 | if (!x || *x || errno) { | |
291 | log_error("[%s:%u] Failed to parse umask value: %s", filename, line, rvalue); | |
292 | return errno ? -errno : -EINVAL; | |
293 | } | |
294 | ||
295 | if (l < 0000 || l > 0777) { | |
296 | log_error("[%s:%u] umask value out of range: %s", filename, line, rvalue); | |
297 | return -ERANGE; | |
298 | } | |
299 | ||
300 | *m = (mode_t) l; | |
301 | return 0; | |
302 | } | |
303 | ||
304 | static int config_parse_exec( | |
305 | const char *filename, | |
306 | unsigned line, | |
307 | const char *section, | |
308 | const char *lvalue, | |
309 | const char *rvalue, | |
310 | void *data, | |
311 | void *userdata) { | |
312 | ||
313 | ExecCommand **e = data, *ee, *nce = NULL; | |
314 | char **n; | |
315 | char *w; | |
316 | unsigned k; | |
317 | size_t l; | |
318 | char *state; | |
319 | ||
320 | assert(filename); | |
321 | assert(lvalue); | |
322 | assert(rvalue); | |
323 | assert(data); | |
324 | ||
325 | k = 0; | |
326 | FOREACH_WORD_QUOTED(w, l, rvalue, state) | |
327 | k++; | |
328 | ||
329 | if (!(n = new(char*, k+1))) | |
330 | return -ENOMEM; | |
331 | ||
44d8db9e | 332 | k = 0; |
034c6ed7 LP |
333 | FOREACH_WORD_QUOTED(w, l, rvalue, state) |
334 | if (!(n[k++] = strndup(w, l))) | |
335 | goto fail; | |
336 | ||
337 | n[k] = NULL; | |
338 | ||
339 | if (!n[0] || n[0][0] != '/') { | |
340 | log_error("[%s:%u] Invalid executable path in command line: %s", filename, line, rvalue); | |
341 | strv_free(n); | |
342 | return -EINVAL; | |
343 | } | |
344 | ||
345 | if (!(nce = new0(ExecCommand, 1))) | |
346 | goto fail; | |
347 | ||
348 | nce->argv = n; | |
349 | if (!(nce->path = strdup(n[0]))) | |
350 | goto fail; | |
351 | ||
352 | if (*e) { | |
353 | /* It's kinda important that we keep the order here */ | |
354 | LIST_FIND_TAIL(ExecCommand, command, *e, ee); | |
355 | LIST_INSERT_AFTER(ExecCommand, command, *e, ee, nce); | |
356 | } else | |
357 | *e = nce; | |
358 | ||
359 | return 0; | |
360 | ||
361 | fail: | |
362 | for (; k > 0; k--) | |
363 | free(n[k-1]); | |
364 | free(n); | |
365 | ||
366 | free(nce); | |
367 | ||
368 | return -ENOMEM; | |
369 | } | |
370 | ||
371 | static int config_parse_usec( | |
372 | const char *filename, | |
373 | unsigned line, | |
374 | const char *section, | |
375 | const char *lvalue, | |
376 | const char *rvalue, | |
377 | void *data, | |
378 | void *userdata) { | |
379 | ||
380 | usec_t *usec = data; | |
381 | unsigned long long u; | |
382 | int r; | |
383 | ||
384 | assert(filename); | |
385 | assert(lvalue); | |
386 | assert(rvalue); | |
387 | assert(data); | |
388 | ||
389 | if ((r = safe_atollu(rvalue, &u)) < 0) { | |
390 | log_error("[%s:%u] Failed to parse time value: %s", filename, line, rvalue); | |
391 | return r; | |
392 | } | |
393 | ||
394 | /* We actually assume the user configures seconds. Later on we | |
395 | * might choose to support suffixes for time values, to | |
396 | * configure bigger or smaller units */ | |
397 | ||
398 | *usec = u * USEC_PER_SEC; | |
399 | ||
400 | return 0; | |
401 | } | |
402 | ||
403 | static int config_parse_service_type( | |
404 | const char *filename, | |
405 | unsigned line, | |
406 | const char *section, | |
407 | const char *lvalue, | |
408 | const char *rvalue, | |
409 | void *data, | |
410 | void *userdata) { | |
411 | ||
412 | Service *s = data; | |
413 | ||
414 | assert(filename); | |
415 | assert(lvalue); | |
416 | assert(rvalue); | |
417 | assert(data); | |
418 | ||
419 | if (streq(rvalue, "forking")) | |
420 | s->type = SERVICE_FORKING; | |
421 | else if (streq(rvalue, "simple")) | |
422 | s->type = SERVICE_SIMPLE; | |
423 | else { | |
424 | log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue); | |
425 | return -EBADMSG; | |
426 | } | |
427 | ||
428 | return 0; | |
429 | } | |
430 | ||
431 | static int config_parse_service_restart( | |
432 | const char *filename, | |
433 | unsigned line, | |
434 | const char *section, | |
435 | const char *lvalue, | |
436 | const char *rvalue, | |
437 | void *data, | |
438 | void *userdata) { | |
439 | ||
440 | Service *s = data; | |
441 | ||
442 | assert(filename); | |
443 | assert(lvalue); | |
444 | assert(rvalue); | |
445 | assert(data); | |
446 | ||
447 | if (streq(rvalue, "once")) | |
448 | s->restart = SERVICE_ONCE; | |
449 | else if (streq(rvalue, "on-success")) | |
450 | s->type = SERVICE_RESTART_ON_SUCCESS; | |
451 | else if (streq(rvalue, "always")) | |
452 | s->type = SERVICE_RESTART_ALWAYS; | |
453 | else { | |
454 | log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue); | |
455 | return -EBADMSG; | |
456 | } | |
457 | ||
458 | return 0; | |
459 | } | |
460 | ||
3efd4195 LP |
461 | int name_load_fragment(Name *n) { |
462 | ||
5cb5a6ff | 463 | static const char* const section_table[_NAME_TYPE_MAX] = { |
42f4e3c4 LP |
464 | [NAME_SERVICE] = "Service", |
465 | [NAME_TIMER] = "Timer", | |
466 | [NAME_SOCKET] = "Socket", | |
c22cbe26 | 467 | [NAME_TARGET] = "Target", |
42f4e3c4 LP |
468 | [NAME_DEVICE] = "Device", |
469 | [NAME_MOUNT] = "Mount", | |
470 | [NAME_AUTOMOUNT] = "Automount", | |
471 | [NAME_SNAPSHOT] = "Snapshot" | |
472 | }; | |
473 | ||
034c6ed7 LP |
474 | #define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \ |
475 | { "Directory", config_parse_path, &(context).directory, section }, \ | |
476 | { "User", config_parse_string, &(context).user, section }, \ | |
477 | { "Group", config_parse_string, &(context).group, section }, \ | |
478 | { "SupplementaryGroups", config_parse_strv, &(context).supplementary_groups, section }, \ | |
479 | { "Nice", config_parse_nice, &(context).nice, section }, \ | |
480 | { "OOMAdjust", config_parse_oom_adjust, &(context).oom_adjust, section }, \ | |
481 | { "UMask", config_parse_umask, &(context).umask, section }, \ | |
482 | { "Environment", config_parse_strv, &(context).environment, section } | |
483 | ||
3efd4195 | 484 | const ConfigItem items[] = { |
034c6ed7 LP |
485 | { "Names", config_parse_names, &n->meta.names, "Meta" }, |
486 | { "Description", config_parse_string, &n->meta.description, "Meta" }, | |
44d8db9e LP |
487 | { "Requires", config_parse_deps, UINT_TO_PTR(NAME_REQUIRES), "Meta" }, |
488 | { "SoftRequires", config_parse_deps, UINT_TO_PTR(NAME_SOFT_REQUIRES), "Meta" }, | |
489 | { "Wants", config_parse_deps, UINT_TO_PTR(NAME_WANTS), "Meta" }, | |
490 | { "Requisite", config_parse_deps, UINT_TO_PTR(NAME_REQUISITE), "Meta" }, | |
491 | { "SoftRequisite", config_parse_deps, UINT_TO_PTR(NAME_SOFT_REQUISITE), "Meta" }, | |
492 | { "Conflicts", config_parse_deps, UINT_TO_PTR(NAME_CONFLICTS), "Meta" }, | |
493 | { "Before", config_parse_deps, UINT_TO_PTR(NAME_BEFORE), "Meta" }, | |
494 | { "After", config_parse_deps, UINT_TO_PTR(NAME_AFTER), "Meta" }, | |
034c6ed7 LP |
495 | |
496 | { "PIDFile", config_parse_path, &n->service.pid_file, "Service" }, | |
497 | { "ExecStartPre", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START_PRE], "Service" }, | |
498 | { "ExecStart", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START], "Service" }, | |
499 | { "ExecStartPost", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START_POST], "Service" }, | |
500 | { "ExecReload", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_RELOAD], "Service" }, | |
501 | { "ExecStop", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_STOP], "Service" }, | |
502 | { "ExecStopPost", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_STOP_POST], "Service" }, | |
503 | { "RestartSec", config_parse_usec, &n->service.restart_usec, "Service" }, | |
504 | { "TimeoutSec", config_parse_usec, &n->service.timeout_usec, "Service" }, | |
505 | { "Type", config_parse_service_type, &n->service, "Service" }, | |
506 | { "Restart", config_parse_service_restart, &n->service, "Service" }, | |
507 | EXEC_CONTEXT_CONFIG_ITEMS(n->service.exec_context, "Service"), | |
508 | ||
509 | { "ListenStream", config_parse_listen, &n->socket, "Socket" }, | |
510 | { "ListenDatagram", config_parse_listen, &n->socket, "Socket" }, | |
511 | { "ListenSequentialPacket", config_parse_listen, &n->socket, "Socket" }, | |
512 | { "ListenFIFO", config_parse_listen, &n->socket, "Socket" }, | |
513 | { "BindIPv6Only", config_parse_socket_bind, &n->socket, "Socket" }, | |
514 | { "Backlog", config_parse_unsigned, &n->socket.backlog, "Socket" }, | |
515 | { "ExecStartPre", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_START_PRE], "Socket" }, | |
516 | { "ExecStartPost", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_START_POST], "Socket" }, | |
517 | { "ExecStopPre", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_STOP_PRE], "Socket" }, | |
518 | { "ExecStopPost", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_STOP_POST], "Socket" }, | |
519 | EXEC_CONTEXT_CONFIG_ITEMS(n->socket.exec_context, "Socket"), | |
520 | ||
521 | EXEC_CONTEXT_CONFIG_ITEMS(n->automount.exec_context, "Automount"), | |
522 | ||
3efd4195 LP |
523 | { NULL, NULL, NULL, NULL } |
524 | }; | |
525 | ||
034c6ed7 | 526 | #undef EXEC_CONTEXT_CONFIG_ITEMS |
42f4e3c4 | 527 | |
87d1515d | 528 | char *t; |
3efd4195 | 529 | int r; |
42f4e3c4 | 530 | const char *sections[3]; |
034c6ed7 | 531 | Iterator i; |
3efd4195 LP |
532 | |
533 | assert(n); | |
5cb5a6ff | 534 | assert(n->meta.load_state == NAME_STUB); |
3efd4195 | 535 | |
42f4e3c4 LP |
536 | sections[0] = "Meta"; |
537 | sections[1] = section_table[n->meta.type]; | |
538 | sections[2] = NULL; | |
539 | ||
034c6ed7 | 540 | SET_FOREACH(t, n->meta.names, i) { |
3efd4195 | 541 | |
034c6ed7 LP |
542 | /* Try to find a name we can load this with */ |
543 | if ((r = config_parse(t, sections, items, n)) == -ENOENT) | |
544 | continue; | |
3efd4195 | 545 | |
034c6ed7 LP |
546 | /* Yay, we succeeded! Now let's call this our identifier */ |
547 | if (r == 0) | |
548 | n->meta.id = t; | |
549 | ||
550 | return r; | |
551 | } | |
552 | ||
553 | return -ENOENT; | |
3efd4195 | 554 | } |