]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bus-unit-util.c
core: add RootImage= setting for using a specific image file as root directory for...
[thirdparty/systemd.git] / src / shared / bus-unit-util.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2016 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include "alloc-util.h"
21 #include "bus-internal.h"
22 #include "bus-unit-util.h"
23 #include "bus-util.h"
24 #include "cgroup-util.h"
25 #include "env-util.h"
26 #include "escape.h"
27 #include "hashmap.h"
28 #include "list.h"
29 #include "locale-util.h"
30 #include "mount-util.h"
31 #include "nsflags.h"
32 #include "parse-util.h"
33 #include "path-util.h"
34 #include "process-util.h"
35 #include "rlimit-util.h"
36 #include "signal-util.h"
37 #include "string-util.h"
38 #include "syslog-util.h"
39 #include "terminal-util.h"
40 #include "utf8.h"
41 #include "util.h"
42
43 int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) {
44 assert(message);
45 assert(u);
46
47 u->machine = NULL;
48
49 return sd_bus_message_read(
50 message,
51 "(ssssssouso)",
52 &u->id,
53 &u->description,
54 &u->load_state,
55 &u->active_state,
56 &u->sub_state,
57 &u->following,
58 &u->unit_path,
59 &u->job_id,
60 &u->job_type,
61 &u->job_path);
62 }
63
64 int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) {
65 const char *eq, *field;
66 UnitDependency dep;
67 int r, rl;
68
69 assert(m);
70 assert(assignment);
71
72 eq = strchr(assignment, '=');
73 if (!eq) {
74 log_error("Not an assignment: %s", assignment);
75 return -EINVAL;
76 }
77
78 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
79 if (r < 0)
80 return bus_log_create_error(r);
81
82 field = strndupa(assignment, eq - assignment);
83 eq++;
84
85 if (streq(field, "CPUQuota")) {
86
87 if (isempty(eq))
88 r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY);
89 else {
90 r = parse_percent_unbounded(eq);
91 if (r <= 0) {
92 log_error_errno(r, "CPU quota '%s' invalid.", eq);
93 return -EINVAL;
94 }
95
96 r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) r * USEC_PER_SEC / 100U);
97 }
98
99 goto finish;
100
101 } else if (streq(field, "EnvironmentFile")) {
102
103 r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1,
104 eq[0] == '-' ? eq + 1 : eq,
105 eq[0] == '-');
106 goto finish;
107
108 } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
109 char *n;
110 usec_t t;
111 size_t l;
112
113 r = parse_sec(eq, &t);
114 if (r < 0)
115 return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
116
117 l = strlen(field);
118 n = newa(char, l + 2);
119 if (!n)
120 return log_oom();
121
122 /* Change suffix Sec → USec */
123 strcpy(mempcpy(n, field, l - 3), "USec");
124 r = sd_bus_message_append(m, "sv", n, "t", t);
125 goto finish;
126
127 } else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
128 uint64_t bytes;
129
130 if (isempty(eq) || streq(eq, "infinity"))
131 bytes = CGROUP_LIMIT_MAX;
132 else {
133 r = parse_percent(eq);
134 if (r >= 0) {
135 char *n;
136
137 /* When this is a percentage we'll convert this into a relative value in the range
138 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
139 * ones). This way the physical memory size can be determined server-side */
140
141 n = strjoina(field, "Scale");
142 r = sd_bus_message_append(m, "sv", n, "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
143 goto finish;
144
145 } else {
146 r = parse_size(eq, 1024, &bytes);
147 if (r < 0)
148 return log_error_errno(r, "Failed to parse bytes specification %s", assignment);
149 }
150 }
151
152 r = sd_bus_message_append(m, "sv", field, "t", bytes);
153 goto finish;
154 } else if (streq(field, "TasksMax")) {
155 uint64_t t;
156
157 if (isempty(eq) || streq(eq, "infinity"))
158 t = (uint64_t) -1;
159 else {
160 r = parse_percent(eq);
161 if (r >= 0) {
162 r = sd_bus_message_append(m, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
163 goto finish;
164 } else {
165 r = safe_atou64(eq, &t);
166 if (r < 0)
167 return log_error_errno(r, "Failed to parse maximum tasks specification %s", assignment);
168 }
169
170 }
171
172 r = sd_bus_message_append(m, "sv", "TasksMax", "t", t);
173 goto finish;
174 }
175
176 r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
177 if (r < 0)
178 return bus_log_create_error(r);
179
180 rl = rlimit_from_string(field);
181 if (rl >= 0) {
182 const char *sn;
183 struct rlimit l;
184
185 r = rlimit_parse(rl, eq, &l);
186 if (r < 0)
187 return log_error_errno(r, "Failed to parse resource limit: %s", eq);
188
189 r = sd_bus_message_append(m, "v", "t", l.rlim_max);
190 if (r < 0)
191 return bus_log_create_error(r);
192
193 r = sd_bus_message_close_container(m);
194 if (r < 0)
195 return bus_log_create_error(r);
196
197 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
198 if (r < 0)
199 return bus_log_create_error(r);
200
201 sn = strjoina(field, "Soft");
202 r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur);
203
204 } else if (STR_IN_SET(field,
205 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
206 "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
207 "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
208 "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges",
209 "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
210 "RestrictRealtime", "DynamicUser", "RemoveIPC", "ProtectKernelTunables",
211 "ProtectKernelModules", "ProtectControlGroups", "MountAPIVFS")) {
212
213 r = parse_boolean(eq);
214 if (r < 0)
215 return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment);
216
217 r = sd_bus_message_append(m, "v", "b", r);
218
219 } else if (STR_IN_SET(field, "CPUWeight", "StartupCPUWeight")) {
220 uint64_t u;
221
222 r = cg_weight_parse(eq, &u);
223 if (r < 0) {
224 log_error("Failed to parse %s value %s.", field, eq);
225 return -EINVAL;
226 }
227
228 r = sd_bus_message_append(m, "v", "t", u);
229
230 } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) {
231 uint64_t u;
232
233 r = cg_cpu_shares_parse(eq, &u);
234 if (r < 0) {
235 log_error("Failed to parse %s value %s.", field, eq);
236 return -EINVAL;
237 }
238
239 r = sd_bus_message_append(m, "v", "t", u);
240
241 } else if (STR_IN_SET(field, "IOWeight", "StartupIOWeight")) {
242 uint64_t u;
243
244 r = cg_weight_parse(eq, &u);
245 if (r < 0) {
246 log_error("Failed to parse %s value %s.", field, eq);
247 return -EINVAL;
248 }
249
250 r = sd_bus_message_append(m, "v", "t", u);
251
252 } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) {
253 uint64_t u;
254
255 r = cg_blkio_weight_parse(eq, &u);
256 if (r < 0) {
257 log_error("Failed to parse %s value %s.", field, eq);
258 return -EINVAL;
259 }
260
261 r = sd_bus_message_append(m, "v", "t", u);
262
263 } else if (STR_IN_SET(field,
264 "User", "Group", "DevicePolicy", "KillMode",
265 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
266 "StandardInput", "StandardOutput", "StandardError",
267 "Description", "Slice", "Type", "WorkingDirectory",
268 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
269 "ProtectHome", "SELinuxContext", "Restart", "RootImage"))
270 r = sd_bus_message_append(m, "v", "s", eq);
271
272 else if (streq(field, "SyslogLevel")) {
273 int level;
274
275 level = log_level_from_string(eq);
276 if (level < 0) {
277 log_error("Failed to parse %s value %s.", field, eq);
278 return -EINVAL;
279 }
280
281 r = sd_bus_message_append(m, "v", "i", level);
282
283 } else if (streq(field, "SyslogFacility")) {
284 int facility;
285
286 facility = log_facility_unshifted_from_string(eq);
287 if (facility < 0) {
288 log_error("Failed to parse %s value %s.", field, eq);
289 return -EINVAL;
290 }
291
292 r = sd_bus_message_append(m, "v", "i", facility);
293
294 } else if (streq(field, "DeviceAllow")) {
295
296 if (isempty(eq))
297 r = sd_bus_message_append(m, "v", "a(ss)", 0);
298 else {
299 const char *path, *rwm, *e;
300
301 e = strchr(eq, ' ');
302 if (e) {
303 path = strndupa(eq, e - eq);
304 rwm = e+1;
305 } else {
306 path = eq;
307 rwm = "";
308 }
309
310 if (!is_deviceallow_pattern(path)) {
311 log_error("%s is not a device file in /dev.", path);
312 return -EINVAL;
313 }
314
315 r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm);
316 }
317
318 } else if (cgroup_io_limit_type_from_string(field) >= 0 || STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
319
320 if (isempty(eq))
321 r = sd_bus_message_append(m, "v", "a(st)", 0);
322 else {
323 const char *path, *bandwidth, *e;
324 uint64_t bytes;
325
326 e = strchr(eq, ' ');
327 if (e) {
328 path = strndupa(eq, e - eq);
329 bandwidth = e+1;
330 } else {
331 log_error("Failed to parse %s value %s.", field, eq);
332 return -EINVAL;
333 }
334
335 if (!path_startswith(path, "/dev")) {
336 log_error("%s is not a device file in /dev.", path);
337 return -EINVAL;
338 }
339
340 if (streq(bandwidth, "infinity")) {
341 bytes = CGROUP_LIMIT_MAX;
342 } else {
343 r = parse_size(bandwidth, 1000, &bytes);
344 if (r < 0) {
345 log_error("Failed to parse byte value %s.", bandwidth);
346 return -EINVAL;
347 }
348 }
349
350 r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes);
351 }
352
353 } else if (STR_IN_SET(field, "IODeviceWeight", "BlockIODeviceWeight")) {
354
355 if (isempty(eq))
356 r = sd_bus_message_append(m, "v", "a(st)", 0);
357 else {
358 const char *path, *weight, *e;
359 uint64_t u;
360
361 e = strchr(eq, ' ');
362 if (e) {
363 path = strndupa(eq, e - eq);
364 weight = e+1;
365 } else {
366 log_error("Failed to parse %s value %s.", field, eq);
367 return -EINVAL;
368 }
369
370 if (!path_startswith(path, "/dev")) {
371 log_error("%s is not a device file in /dev.", path);
372 return -EINVAL;
373 }
374
375 r = safe_atou64(weight, &u);
376 if (r < 0) {
377 log_error("Failed to parse %s value %s.", field, weight);
378 return -EINVAL;
379 }
380 r = sd_bus_message_append(m, "v", "a(st)", 1, path, u);
381 }
382
383 } else if (streq(field, "Nice")) {
384 int n;
385
386 r = parse_nice(eq, &n);
387 if (r < 0)
388 return log_error_errno(r, "Failed to parse nice value: %s", eq);
389
390 r = sd_bus_message_append(m, "v", "i", (int32_t) n);
391
392 } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) {
393 const char *p;
394
395 r = sd_bus_message_open_container(m, 'v', "as");
396 if (r < 0)
397 return bus_log_create_error(r);
398
399 r = sd_bus_message_open_container(m, 'a', "s");
400 if (r < 0)
401 return bus_log_create_error(r);
402
403 for (p = eq;;) {
404 _cleanup_free_ char *word = NULL;
405
406 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
407 if (r < 0) {
408 log_error("Failed to parse Environment value %s", eq);
409 return -EINVAL;
410 }
411 if (r == 0)
412 break;
413
414 if (streq(field, "Environment")) {
415 if (!env_assignment_is_valid(word)) {
416 log_error("Invalid environment assignment: %s", word);
417 return -EINVAL;
418 }
419 } else { /* PassEnvironment */
420 if (!env_name_is_valid(word)) {
421 log_error("Invalid environment variable name: %s", word);
422 return -EINVAL;
423 }
424 }
425
426 r = sd_bus_message_append_basic(m, 's', word);
427 if (r < 0)
428 return bus_log_create_error(r);
429 }
430
431 r = sd_bus_message_close_container(m);
432 if (r < 0)
433 return bus_log_create_error(r);
434
435 r = sd_bus_message_close_container(m);
436
437 } else if (streq(field, "KillSignal")) {
438 int sig;
439
440 sig = signal_from_string_try_harder(eq);
441 if (sig < 0) {
442 log_error("Failed to parse %s value %s.", field, eq);
443 return -EINVAL;
444 }
445
446 r = sd_bus_message_append(m, "v", "i", sig);
447
448 } else if (streq(field, "TimerSlackNSec")) {
449 nsec_t n;
450
451 r = parse_nsec(eq, &n);
452 if (r < 0) {
453 log_error("Failed to parse %s value %s", field, eq);
454 return -EINVAL;
455 }
456
457 r = sd_bus_message_append(m, "v", "t", n);
458 } else if (streq(field, "OOMScoreAdjust")) {
459 int oa;
460
461 r = safe_atoi(eq, &oa);
462 if (r < 0) {
463 log_error("Failed to parse %s value %s", field, eq);
464 return -EINVAL;
465 }
466
467 if (!oom_score_adjust_is_valid(oa)) {
468 log_error("OOM score adjust value out of range");
469 return -EINVAL;
470 }
471
472 r = sd_bus_message_append(m, "v", "i", oa);
473 } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
474 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
475 const char *p;
476
477 r = sd_bus_message_open_container(m, 'v', "as");
478 if (r < 0)
479 return bus_log_create_error(r);
480
481 r = sd_bus_message_open_container(m, 'a', "s");
482 if (r < 0)
483 return bus_log_create_error(r);
484
485 for (p = eq;;) {
486 _cleanup_free_ char *word = NULL;
487 size_t offset;
488
489 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
490 if (r < 0) {
491 log_error("Failed to parse %s value %s", field, eq);
492 return -EINVAL;
493 }
494 if (r == 0)
495 break;
496
497 if (!utf8_is_valid(word)) {
498 log_error("Failed to parse %s value %s", field, eq);
499 return -EINVAL;
500 }
501
502 offset = word[0] == '-';
503 offset += word[offset] == '+';
504
505 if (!path_is_absolute(word + offset)) {
506 log_error("Failed to parse %s value %s", field, eq);
507 return -EINVAL;
508 }
509
510 path_kill_slashes(word + offset);
511
512 r = sd_bus_message_append_basic(m, 's', word);
513 if (r < 0)
514 return bus_log_create_error(r);
515 }
516
517 r = sd_bus_message_close_container(m);
518 if (r < 0)
519 return bus_log_create_error(r);
520
521 r = sd_bus_message_close_container(m);
522
523 } else if (streq(field, "RuntimeDirectory")) {
524 const char *p;
525
526 r = sd_bus_message_open_container(m, 'v', "as");
527 if (r < 0)
528 return bus_log_create_error(r);
529
530 r = sd_bus_message_open_container(m, 'a', "s");
531 if (r < 0)
532 return bus_log_create_error(r);
533
534 for (p = eq;;) {
535 _cleanup_free_ char *word = NULL;
536
537 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
538 if (r < 0)
539 return log_error_errno(r, "Failed to parse %s value %s", field, eq);
540
541 if (r == 0)
542 break;
543
544 r = sd_bus_message_append_basic(m, 's', word);
545 if (r < 0)
546 return bus_log_create_error(r);
547 }
548
549 r = sd_bus_message_close_container(m);
550 if (r < 0)
551 return bus_log_create_error(r);
552
553 r = sd_bus_message_close_container(m);
554
555 } else if (streq(field, "RestrictNamespaces")) {
556 bool invert = false;
557 uint64_t flags = 0;
558
559 if (eq[0] == '~') {
560 invert = true;
561 eq++;
562 }
563
564 r = parse_boolean(eq);
565 if (r > 0)
566 flags = 0;
567 else if (r == 0)
568 flags = NAMESPACE_FLAGS_ALL;
569 else {
570 r = namespace_flag_from_string_many(eq, &flags);
571 if (r < 0)
572 return log_error_errno(r, "Failed to parse %s value %s.", field, eq);
573 }
574
575 if (invert)
576 flags = (~flags) & NAMESPACE_FLAGS_ALL;
577
578 r = sd_bus_message_append(m, "v", "t", flags);
579 } else if ((dep = unit_dependency_from_string(field)) >= 0)
580 r = sd_bus_message_append(m, "v", "as", 1, eq);
581 else if (streq(field, "MountFlags")) {
582 unsigned long f;
583
584 r = mount_propagation_flags_from_string(eq, &f);
585 if (r < 0)
586 return log_error_errno(r, "Failed to parse mount propagation flags: %s", eq);
587
588 r = sd_bus_message_append(m, "v", "t", f);
589 } else if (STR_IN_SET(field, "BindPaths", "BindReadOnlyPaths")) {
590 const char *p = eq;
591
592 r = sd_bus_message_open_container(m, 'v', "a(ssbt)");
593 if (r < 0)
594 return r;
595
596 r = sd_bus_message_open_container(m, 'a', "(ssbt)");
597 if (r < 0)
598 return r;
599
600 for (;;) {
601 _cleanup_free_ char *source = NULL, *destination = NULL;
602 char *s = NULL, *d = NULL;
603 bool ignore_enoent = false;
604 uint64_t flags = MS_REC;
605
606 r = extract_first_word(&p, &source, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
607 if (r < 0)
608 return log_error_errno(r, "Failed to parse argument: %m");
609 if (r == 0)
610 break;
611
612 s = source;
613 if (s[0] == '-') {
614 ignore_enoent = true;
615 s++;
616 }
617
618 if (p && p[-1] == ':') {
619 r = extract_first_word(&p, &destination, ":" WHITESPACE, EXTRACT_QUOTES|EXTRACT_DONT_COALESCE_SEPARATORS);
620 if (r < 0)
621 return log_error_errno(r, "Failed to parse argument: %m");
622 if (r == 0) {
623 log_error("Missing argument after ':': %s", eq);
624 return -EINVAL;
625 }
626
627 d = destination;
628
629 if (p && p[-1] == ':') {
630 _cleanup_free_ char *options = NULL;
631
632 r = extract_first_word(&p, &options, NULL, EXTRACT_QUOTES);
633 if (r < 0)
634 return log_error_errno(r, "Failed to parse argument: %m");
635
636 if (isempty(options) || streq(options, "rbind"))
637 flags = MS_REC;
638 else if (streq(options, "norbind"))
639 flags = 0;
640 else {
641 log_error("Unknown options: %s", eq);
642 return -EINVAL;
643 }
644 }
645 } else
646 d = s;
647
648
649 r = sd_bus_message_append(m, "(ssbt)", s, d, ignore_enoent, flags);
650 if (r < 0)
651 return r;
652 }
653
654 r = sd_bus_message_close_container(m);
655 if (r < 0)
656 return r;
657
658 r = sd_bus_message_close_container(m);
659 } else {
660 log_error("Unknown assignment %s.", assignment);
661 return -EINVAL;
662 }
663
664 finish:
665 if (r < 0)
666 return bus_log_create_error(r);
667
668 r = sd_bus_message_close_container(m);
669 if (r < 0)
670 return bus_log_create_error(r);
671
672 return 0;
673 }
674
675 int bus_append_unit_property_assignment_many(sd_bus_message *m, char **l) {
676 char **i;
677 int r;
678
679 assert(m);
680
681 STRV_FOREACH(i, l) {
682 r = bus_append_unit_property_assignment(m, *i);
683 if (r < 0)
684 return r;
685 }
686
687 return 0;
688 }
689
690 typedef struct BusWaitForJobs {
691 sd_bus *bus;
692 Set *jobs;
693
694 char *name;
695 char *result;
696
697 sd_bus_slot *slot_job_removed;
698 sd_bus_slot *slot_disconnected;
699 } BusWaitForJobs;
700
701 static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
702 assert(m);
703
704 log_error("Warning! D-Bus connection terminated.");
705 sd_bus_close(sd_bus_message_get_bus(m));
706
707 return 0;
708 }
709
710 static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
711 const char *path, *unit, *result;
712 BusWaitForJobs *d = userdata;
713 uint32_t id;
714 char *found;
715 int r;
716
717 assert(m);
718 assert(d);
719
720 r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result);
721 if (r < 0) {
722 bus_log_parse_error(r);
723 return 0;
724 }
725
726 found = set_remove(d->jobs, (char*) path);
727 if (!found)
728 return 0;
729
730 free(found);
731
732 if (!isempty(result))
733 d->result = strdup(result);
734
735 if (!isempty(unit))
736 d->name = strdup(unit);
737
738 return 0;
739 }
740
741 void bus_wait_for_jobs_free(BusWaitForJobs *d) {
742 if (!d)
743 return;
744
745 set_free_free(d->jobs);
746
747 sd_bus_slot_unref(d->slot_disconnected);
748 sd_bus_slot_unref(d->slot_job_removed);
749
750 sd_bus_unref(d->bus);
751
752 free(d->name);
753 free(d->result);
754
755 free(d);
756 }
757
758 int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
759 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL;
760 int r;
761
762 assert(bus);
763 assert(ret);
764
765 d = new0(BusWaitForJobs, 1);
766 if (!d)
767 return -ENOMEM;
768
769 d->bus = sd_bus_ref(bus);
770
771 /* When we are a bus client we match by sender. Direct
772 * connections OTOH have no initialized sender field, and
773 * hence we ignore the sender then */
774 r = sd_bus_add_match(
775 bus,
776 &d->slot_job_removed,
777 bus->bus_client ?
778 "type='signal',"
779 "sender='org.freedesktop.systemd1',"
780 "interface='org.freedesktop.systemd1.Manager',"
781 "member='JobRemoved',"
782 "path='/org/freedesktop/systemd1'" :
783 "type='signal',"
784 "interface='org.freedesktop.systemd1.Manager',"
785 "member='JobRemoved',"
786 "path='/org/freedesktop/systemd1'",
787 match_job_removed, d);
788 if (r < 0)
789 return r;
790
791 r = sd_bus_add_match(
792 bus,
793 &d->slot_disconnected,
794 "type='signal',"
795 "sender='org.freedesktop.DBus.Local',"
796 "interface='org.freedesktop.DBus.Local',"
797 "member='Disconnected'",
798 match_disconnected, d);
799 if (r < 0)
800 return r;
801
802 *ret = d;
803 d = NULL;
804
805 return 0;
806 }
807
808 static int bus_process_wait(sd_bus *bus) {
809 int r;
810
811 for (;;) {
812 r = sd_bus_process(bus, NULL);
813 if (r < 0)
814 return r;
815 if (r > 0)
816 return 0;
817
818 r = sd_bus_wait(bus, (uint64_t) -1);
819 if (r < 0)
820 return r;
821 }
822 }
823
824 static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
825 _cleanup_free_ char *dbus_path = NULL;
826
827 assert(d);
828 assert(d->name);
829 assert(result);
830
831 dbus_path = unit_dbus_path_from_name(d->name);
832 if (!dbus_path)
833 return -ENOMEM;
834
835 return sd_bus_get_property_string(d->bus,
836 "org.freedesktop.systemd1",
837 dbus_path,
838 "org.freedesktop.systemd1.Service",
839 "Result",
840 NULL,
841 result);
842 }
843
844 static const struct {
845 const char *result, *explanation;
846 } explanations [] = {
847 { "resources", "of unavailable resources or another system error" },
848 { "protocol", "the service did not take the steps required by its unit configuration" },
849 { "timeout", "a timeout was exceeded" },
850 { "exit-code", "the control process exited with error code" },
851 { "signal", "a fatal signal was delivered to the control process" },
852 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
853 { "watchdog", "the service failed to send watchdog ping" },
854 { "start-limit", "start of the service was attempted too often" }
855 };
856
857 static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) {
858 _cleanup_free_ char *service_shell_quoted = NULL;
859 const char *systemctl = "systemctl", *journalctl = "journalctl";
860
861 assert(service);
862
863 service_shell_quoted = shell_maybe_quote(service);
864
865 if (extra_args && extra_args[1]) {
866 _cleanup_free_ char *t;
867
868 t = strv_join((char**) extra_args, " ");
869 systemctl = strjoina("systemctl ", t ? : "<args>");
870 journalctl = strjoina("journalctl ", t ? : "<args>");
871 }
872
873 if (!isempty(result)) {
874 unsigned i;
875
876 for (i = 0; i < ELEMENTSOF(explanations); ++i)
877 if (streq(result, explanations[i].result))
878 break;
879
880 if (i < ELEMENTSOF(explanations)) {
881 log_error("Job for %s failed because %s.\n"
882 "See \"%s status %s\" and \"%s -xe\" for details.\n",
883 service,
884 explanations[i].explanation,
885 systemctl,
886 service_shell_quoted ?: "<service>",
887 journalctl);
888 goto finish;
889 }
890 }
891
892 log_error("Job for %s failed.\n"
893 "See \"%s status %s\" and \"%s -xe\" for details.\n",
894 service,
895 systemctl,
896 service_shell_quoted ?: "<service>",
897 journalctl);
898
899 finish:
900 /* For some results maybe additional explanation is required */
901 if (streq_ptr(result, "start-limit"))
902 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
903 "followed by \"%1$s start %2$s\" again.",
904 systemctl,
905 service_shell_quoted ?: "<service>");
906 }
907
908 static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
909 int r = 0;
910
911 assert(d->result);
912
913 if (!quiet) {
914 if (streq(d->result, "canceled"))
915 log_error("Job for %s canceled.", strna(d->name));
916 else if (streq(d->result, "timeout"))
917 log_error("Job for %s timed out.", strna(d->name));
918 else if (streq(d->result, "dependency"))
919 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
920 else if (streq(d->result, "invalid"))
921 log_error("%s is not active, cannot reload.", strna(d->name));
922 else if (streq(d->result, "assert"))
923 log_error("Assertion failed on job for %s.", strna(d->name));
924 else if (streq(d->result, "unsupported"))
925 log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
926 else if (streq(d->result, "collected"))
927 log_error("Queued job for %s was garbage collected.", strna(d->name));
928 else if (!streq(d->result, "done") && !streq(d->result, "skipped")) {
929 if (d->name) {
930 int q;
931 _cleanup_free_ char *result = NULL;
932
933 q = bus_job_get_service_result(d, &result);
934 if (q < 0)
935 log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name);
936
937 log_job_error_with_service_result(d->name, result, extra_args);
938 } else
939 log_error("Job failed. See \"journalctl -xe\" for details.");
940 }
941 }
942
943 if (STR_IN_SET(d->result, "canceled", "collected"))
944 r = -ECANCELED;
945 else if (streq(d->result, "timeout"))
946 r = -ETIME;
947 else if (streq(d->result, "dependency"))
948 r = -EIO;
949 else if (streq(d->result, "invalid"))
950 r = -ENOEXEC;
951 else if (streq(d->result, "assert"))
952 r = -EPROTO;
953 else if (streq(d->result, "unsupported"))
954 r = -EOPNOTSUPP;
955 else if (!streq(d->result, "done") && !streq(d->result, "skipped"))
956 r = -EIO;
957
958 return r;
959 }
960
961 int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
962 int r = 0;
963
964 assert(d);
965
966 while (!set_isempty(d->jobs)) {
967 int q;
968
969 q = bus_process_wait(d->bus);
970 if (q < 0)
971 return log_error_errno(q, "Failed to wait for response: %m");
972
973 if (d->result) {
974 q = check_wait_response(d, quiet, extra_args);
975 /* Return the first error as it is most likely to be
976 * meaningful. */
977 if (q < 0 && r == 0)
978 r = q;
979
980 log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name));
981 }
982
983 d->name = mfree(d->name);
984 d->result = mfree(d->result);
985 }
986
987 return r;
988 }
989
990 int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) {
991 int r;
992
993 assert(d);
994
995 r = set_ensure_allocated(&d->jobs, &string_hash_ops);
996 if (r < 0)
997 return r;
998
999 return set_put_strdup(d->jobs, path);
1000 }
1001
1002 int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) {
1003 int r;
1004
1005 r = bus_wait_for_jobs_add(d, path);
1006 if (r < 0)
1007 return log_oom();
1008
1009 return bus_wait_for_jobs(d, quiet, NULL);
1010 }
1011
1012 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) {
1013 const char *type, *path, *source;
1014 int r;
1015
1016 /* changes is dereferenced when calling unit_file_dump_changes() later,
1017 * so we have to make sure this is not NULL. */
1018 assert(changes);
1019 assert(n_changes);
1020
1021 r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
1022 if (r < 0)
1023 return bus_log_parse_error(r);
1024
1025 while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) {
1026 /* We expect only "success" changes to be sent over the bus.
1027 Hence, reject anything negative. */
1028 UnitFileChangeType ch = unit_file_change_type_from_string(type);
1029
1030 if (ch < 0) {
1031 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type, path);
1032 continue;
1033 }
1034
1035 r = unit_file_changes_add(changes, n_changes, ch, path, source);
1036 if (r < 0)
1037 return r;
1038 }
1039 if (r < 0)
1040 return bus_log_parse_error(r);
1041
1042 r = sd_bus_message_exit_container(m);
1043 if (r < 0)
1044 return bus_log_parse_error(r);
1045
1046 unit_file_dump_changes(0, NULL, *changes, *n_changes, false);
1047 return 0;
1048 }
1049
1050 struct CGroupInfo {
1051 char *cgroup_path;
1052 bool is_const; /* If false, cgroup_path should be free()'d */
1053
1054 Hashmap *pids; /* PID → process name */
1055 bool done;
1056
1057 struct CGroupInfo *parent;
1058 LIST_FIELDS(struct CGroupInfo, siblings);
1059 LIST_HEAD(struct CGroupInfo, children);
1060 size_t n_children;
1061 };
1062
1063 static bool IS_ROOT(const char *p) {
1064 return isempty(p) || streq(p, "/");
1065 }
1066
1067 static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) {
1068 struct CGroupInfo *parent = NULL, *cg;
1069 int r;
1070
1071 assert(cgroups);
1072 assert(ret);
1073
1074 if (IS_ROOT(path))
1075 path = "/";
1076
1077 cg = hashmap_get(cgroups, path);
1078 if (cg) {
1079 *ret = cg;
1080 return 0;
1081 }
1082
1083 if (!IS_ROOT(path)) {
1084 const char *e, *pp;
1085
1086 e = strrchr(path, '/');
1087 if (!e)
1088 return -EINVAL;
1089
1090 pp = strndupa(path, e - path);
1091 if (!pp)
1092 return -ENOMEM;
1093
1094 r = add_cgroup(cgroups, pp, false, &parent);
1095 if (r < 0)
1096 return r;
1097 }
1098
1099 cg = new0(struct CGroupInfo, 1);
1100 if (!cg)
1101 return -ENOMEM;
1102
1103 if (is_const)
1104 cg->cgroup_path = (char*) path;
1105 else {
1106 cg->cgroup_path = strdup(path);
1107 if (!cg->cgroup_path) {
1108 free(cg);
1109 return -ENOMEM;
1110 }
1111 }
1112
1113 cg->is_const = is_const;
1114 cg->parent = parent;
1115
1116 r = hashmap_put(cgroups, cg->cgroup_path, cg);
1117 if (r < 0) {
1118 if (!is_const)
1119 free(cg->cgroup_path);
1120 free(cg);
1121 return r;
1122 }
1123
1124 if (parent) {
1125 LIST_PREPEND(siblings, parent->children, cg);
1126 parent->n_children++;
1127 }
1128
1129 *ret = cg;
1130 return 1;
1131 }
1132
1133 static int add_process(
1134 Hashmap *cgroups,
1135 const char *path,
1136 pid_t pid,
1137 const char *name) {
1138
1139 struct CGroupInfo *cg;
1140 int r;
1141
1142 assert(cgroups);
1143 assert(name);
1144 assert(pid > 0);
1145
1146 r = add_cgroup(cgroups, path, true, &cg);
1147 if (r < 0)
1148 return r;
1149
1150 r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops);
1151 if (r < 0)
1152 return r;
1153
1154 return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name);
1155 }
1156
1157 static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) {
1158 assert(cgroups);
1159 assert(cg);
1160
1161 while (cg->children)
1162 remove_cgroup(cgroups, cg->children);
1163
1164 hashmap_remove(cgroups, cg->cgroup_path);
1165
1166 if (!cg->is_const)
1167 free(cg->cgroup_path);
1168
1169 hashmap_free(cg->pids);
1170
1171 if (cg->parent)
1172 LIST_REMOVE(siblings, cg->parent->children, cg);
1173
1174 free(cg);
1175 }
1176
1177 static int cgroup_info_compare_func(const void *a, const void *b) {
1178 const struct CGroupInfo *x = *(const struct CGroupInfo* const*) a, *y = *(const struct CGroupInfo* const*) b;
1179
1180 assert(x);
1181 assert(y);
1182
1183 return strcmp(x->cgroup_path, y->cgroup_path);
1184 }
1185
1186 static int dump_processes(
1187 Hashmap *cgroups,
1188 const char *cgroup_path,
1189 const char *prefix,
1190 unsigned n_columns,
1191 OutputFlags flags) {
1192
1193 struct CGroupInfo *cg;
1194 int r;
1195
1196 assert(prefix);
1197
1198 if (IS_ROOT(cgroup_path))
1199 cgroup_path = "/";
1200
1201 cg = hashmap_get(cgroups, cgroup_path);
1202 if (!cg)
1203 return 0;
1204
1205 if (!hashmap_isempty(cg->pids)) {
1206 const char *name;
1207 size_t n = 0, i;
1208 pid_t *pids;
1209 void *pidp;
1210 Iterator j;
1211 int width;
1212
1213 /* Order processes by their PID */
1214 pids = newa(pid_t, hashmap_size(cg->pids));
1215
1216 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j)
1217 pids[n++] = PTR_TO_PID(pidp);
1218
1219 assert(n == hashmap_size(cg->pids));
1220 qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
1221
1222 width = DECIMAL_STR_WIDTH(pids[n-1]);
1223
1224 for (i = 0; i < n; i++) {
1225 _cleanup_free_ char *e = NULL;
1226 const char *special;
1227 bool more;
1228
1229 name = hashmap_get(cg->pids, PID_TO_PTR(pids[i]));
1230 assert(name);
1231
1232 if (n_columns != 0) {
1233 unsigned k;
1234
1235 k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
1236
1237 e = ellipsize(name, k, 100);
1238 if (e)
1239 name = e;
1240 }
1241
1242 more = i+1 < n || cg->children;
1243 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
1244
1245 fprintf(stdout, "%s%s%*"PID_PRI" %s\n",
1246 prefix,
1247 special,
1248 width, pids[i],
1249 name);
1250 }
1251 }
1252
1253 if (cg->children) {
1254 struct CGroupInfo **children, *child;
1255 size_t n = 0, i;
1256
1257 /* Order subcgroups by their name */
1258 children = newa(struct CGroupInfo*, cg->n_children);
1259 LIST_FOREACH(siblings, child, cg->children)
1260 children[n++] = child;
1261 assert(n == cg->n_children);
1262 qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func);
1263
1264 if (n_columns != 0)
1265 n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
1266
1267 for (i = 0; i < n; i++) {
1268 _cleanup_free_ char *pp = NULL;
1269 const char *name, *special;
1270 bool more;
1271
1272 child = children[i];
1273
1274 name = strrchr(child->cgroup_path, '/');
1275 if (!name)
1276 return -EINVAL;
1277 name++;
1278
1279 more = i+1 < n;
1280 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
1281
1282 fputs(prefix, stdout);
1283 fputs(special, stdout);
1284 fputs(name, stdout);
1285 fputc('\n', stdout);
1286
1287 special = special_glyph(more ? TREE_VERTICAL : TREE_SPACE);
1288
1289 pp = strappend(prefix, special);
1290 if (!pp)
1291 return -ENOMEM;
1292
1293 r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags);
1294 if (r < 0)
1295 return r;
1296 }
1297 }
1298
1299 cg->done = true;
1300 return 0;
1301 }
1302
1303 static int dump_extra_processes(
1304 Hashmap *cgroups,
1305 const char *prefix,
1306 unsigned n_columns,
1307 OutputFlags flags) {
1308
1309 _cleanup_free_ pid_t *pids = NULL;
1310 _cleanup_hashmap_free_ Hashmap *names = NULL;
1311 struct CGroupInfo *cg;
1312 size_t n_allocated = 0, n = 0, k;
1313 Iterator i;
1314 int width, r;
1315
1316 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1317 * combined, sorted, linear list. */
1318
1319 HASHMAP_FOREACH(cg, cgroups, i) {
1320 const char *name;
1321 void *pidp;
1322 Iterator j;
1323
1324 if (cg->done)
1325 continue;
1326
1327 if (hashmap_isempty(cg->pids))
1328 continue;
1329
1330 r = hashmap_ensure_allocated(&names, &trivial_hash_ops);
1331 if (r < 0)
1332 return r;
1333
1334 if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids)))
1335 return -ENOMEM;
1336
1337 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) {
1338 pids[n++] = PTR_TO_PID(pidp);
1339
1340 r = hashmap_put(names, pidp, (void*) name);
1341 if (r < 0)
1342 return r;
1343 }
1344 }
1345
1346 if (n == 0)
1347 return 0;
1348
1349 qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
1350 width = DECIMAL_STR_WIDTH(pids[n-1]);
1351
1352 for (k = 0; k < n; k++) {
1353 _cleanup_free_ char *e = NULL;
1354 const char *name;
1355
1356 name = hashmap_get(names, PID_TO_PTR(pids[k]));
1357 assert(name);
1358
1359 if (n_columns != 0) {
1360 unsigned z;
1361
1362 z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
1363
1364 e = ellipsize(name, z, 100);
1365 if (e)
1366 name = e;
1367 }
1368
1369 fprintf(stdout, "%s%s %*" PID_PRI " %s\n",
1370 prefix,
1371 special_glyph(TRIANGULAR_BULLET),
1372 width, pids[k],
1373 name);
1374 }
1375
1376 return 0;
1377 }
1378
1379 int unit_show_processes(
1380 sd_bus *bus,
1381 const char *unit,
1382 const char *cgroup_path,
1383 const char *prefix,
1384 unsigned n_columns,
1385 OutputFlags flags,
1386 sd_bus_error *error) {
1387
1388 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1389 Hashmap *cgroups = NULL;
1390 struct CGroupInfo *cg;
1391 int r;
1392
1393 assert(bus);
1394 assert(unit);
1395
1396 if (flags & OUTPUT_FULL_WIDTH)
1397 n_columns = 0;
1398 else if (n_columns <= 0)
1399 n_columns = columns();
1400
1401 prefix = strempty(prefix);
1402
1403 r = sd_bus_call_method(
1404 bus,
1405 "org.freedesktop.systemd1",
1406 "/org/freedesktop/systemd1",
1407 "org.freedesktop.systemd1.Manager",
1408 "GetUnitProcesses",
1409 error,
1410 &reply,
1411 "s",
1412 unit);
1413 if (r < 0)
1414 return r;
1415
1416 cgroups = hashmap_new(&string_hash_ops);
1417 if (!cgroups)
1418 return -ENOMEM;
1419
1420 r = sd_bus_message_enter_container(reply, 'a', "(sus)");
1421 if (r < 0)
1422 goto finish;
1423
1424 for (;;) {
1425 const char *path = NULL, *name = NULL;
1426 uint32_t pid;
1427
1428 r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name);
1429 if (r < 0)
1430 goto finish;
1431 if (r == 0)
1432 break;
1433
1434 r = add_process(cgroups, path, pid, name);
1435 if (r < 0)
1436 goto finish;
1437 }
1438
1439 r = sd_bus_message_exit_container(reply);
1440 if (r < 0)
1441 goto finish;
1442
1443 r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags);
1444 if (r < 0)
1445 goto finish;
1446
1447 r = dump_extra_processes(cgroups, prefix, n_columns, flags);
1448
1449 finish:
1450 while ((cg = hashmap_first(cgroups)))
1451 remove_cgroup(cgroups, cg);
1452
1453 hashmap_free(cgroups);
1454
1455 return r;
1456 }