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