]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bus-unit-util.c
Merge pull request #4618 from grawity/signal-direction
[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 p = eq;
402
403 for (;;) {
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 p = eq;
486
487 for (;;) {
488 _cleanup_free_ char *word = NULL;
489 int offset;
490
491 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
492 if (r < 0) {
493 log_error("Failed to parse %s value %s", field, eq);
494 return -EINVAL;
495 }
496 if (r == 0)
497 break;
498
499 if (!utf8_is_valid(word)) {
500 log_error("Failed to parse %s value %s", field, eq);
501 return -EINVAL;
502 }
503
504 offset = word[0] == '-';
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 p = eq;
535
536 for (;;) {
537 _cleanup_free_ char *word = NULL;
538
539 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
540 if (r < 0)
541 return log_error_errno(r, "Failed to parse %s value %s", field, eq);
542
543 if (r == 0)
544 break;
545
546 r = sd_bus_message_append_basic(m, 's', word);
547 if (r < 0)
548 return bus_log_create_error(r);
549 }
550
551 r = sd_bus_message_close_container(m);
552 if (r < 0)
553 return bus_log_create_error(r);
554
555 r = sd_bus_message_close_container(m);
556
557 } else if (streq(field, "RestrictNamespaces")) {
558 bool invert = false;
559 uint64_t flags = 0;
560
561 if (eq[0] == '~') {
562 invert = true;
563 eq++;
564 }
565
566 r = parse_boolean(eq);
567 if (r > 0)
568 flags = 0;
569 else if (r == 0)
570 flags = NAMESPACE_FLAGS_ALL;
571 else {
572 r = namespace_flag_from_string_many(eq, &flags);
573 if (r < 0)
574 return log_error_errno(r, "Failed to parse %s value %s.", field, eq);
575 }
576
577 if (invert)
578 flags = (~flags) & NAMESPACE_FLAGS_ALL;
579
580 r = sd_bus_message_append(m, "v", "t", flags);
581 } else {
582 log_error("Unknown assignment %s.", assignment);
583 return -EINVAL;
584 }
585
586 finish:
587 if (r < 0)
588 return bus_log_create_error(r);
589
590 r = sd_bus_message_close_container(m);
591 if (r < 0)
592 return bus_log_create_error(r);
593
594 return 0;
595 }
596
597 int bus_append_unit_property_assignment_many(sd_bus_message *m, char **l) {
598 char **i;
599 int r;
600
601 assert(m);
602
603 STRV_FOREACH(i, l) {
604 r = bus_append_unit_property_assignment(m, *i);
605 if (r < 0)
606 return r;
607 }
608
609 return 0;
610 }
611
612 typedef struct BusWaitForJobs {
613 sd_bus *bus;
614 Set *jobs;
615
616 char *name;
617 char *result;
618
619 sd_bus_slot *slot_job_removed;
620 sd_bus_slot *slot_disconnected;
621 } BusWaitForJobs;
622
623 static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
624 assert(m);
625
626 log_error("Warning! D-Bus connection terminated.");
627 sd_bus_close(sd_bus_message_get_bus(m));
628
629 return 0;
630 }
631
632 static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
633 const char *path, *unit, *result;
634 BusWaitForJobs *d = userdata;
635 uint32_t id;
636 char *found;
637 int r;
638
639 assert(m);
640 assert(d);
641
642 r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result);
643 if (r < 0) {
644 bus_log_parse_error(r);
645 return 0;
646 }
647
648 found = set_remove(d->jobs, (char*) path);
649 if (!found)
650 return 0;
651
652 free(found);
653
654 if (!isempty(result))
655 d->result = strdup(result);
656
657 if (!isempty(unit))
658 d->name = strdup(unit);
659
660 return 0;
661 }
662
663 void bus_wait_for_jobs_free(BusWaitForJobs *d) {
664 if (!d)
665 return;
666
667 set_free_free(d->jobs);
668
669 sd_bus_slot_unref(d->slot_disconnected);
670 sd_bus_slot_unref(d->slot_job_removed);
671
672 sd_bus_unref(d->bus);
673
674 free(d->name);
675 free(d->result);
676
677 free(d);
678 }
679
680 int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
681 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL;
682 int r;
683
684 assert(bus);
685 assert(ret);
686
687 d = new0(BusWaitForJobs, 1);
688 if (!d)
689 return -ENOMEM;
690
691 d->bus = sd_bus_ref(bus);
692
693 /* When we are a bus client we match by sender. Direct
694 * connections OTOH have no initialized sender field, and
695 * hence we ignore the sender then */
696 r = sd_bus_add_match(
697 bus,
698 &d->slot_job_removed,
699 bus->bus_client ?
700 "type='signal',"
701 "sender='org.freedesktop.systemd1',"
702 "interface='org.freedesktop.systemd1.Manager',"
703 "member='JobRemoved',"
704 "path='/org/freedesktop/systemd1'" :
705 "type='signal',"
706 "interface='org.freedesktop.systemd1.Manager',"
707 "member='JobRemoved',"
708 "path='/org/freedesktop/systemd1'",
709 match_job_removed, d);
710 if (r < 0)
711 return r;
712
713 r = sd_bus_add_match(
714 bus,
715 &d->slot_disconnected,
716 "type='signal',"
717 "sender='org.freedesktop.DBus.Local',"
718 "interface='org.freedesktop.DBus.Local',"
719 "member='Disconnected'",
720 match_disconnected, d);
721 if (r < 0)
722 return r;
723
724 *ret = d;
725 d = NULL;
726
727 return 0;
728 }
729
730 static int bus_process_wait(sd_bus *bus) {
731 int r;
732
733 for (;;) {
734 r = sd_bus_process(bus, NULL);
735 if (r < 0)
736 return r;
737 if (r > 0)
738 return 0;
739
740 r = sd_bus_wait(bus, (uint64_t) -1);
741 if (r < 0)
742 return r;
743 }
744 }
745
746 static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
747 _cleanup_free_ char *dbus_path = NULL;
748
749 assert(d);
750 assert(d->name);
751 assert(result);
752
753 dbus_path = unit_dbus_path_from_name(d->name);
754 if (!dbus_path)
755 return -ENOMEM;
756
757 return sd_bus_get_property_string(d->bus,
758 "org.freedesktop.systemd1",
759 dbus_path,
760 "org.freedesktop.systemd1.Service",
761 "Result",
762 NULL,
763 result);
764 }
765
766 static const struct {
767 const char *result, *explanation;
768 } explanations [] = {
769 { "resources", "of unavailable resources or another system error" },
770 { "timeout", "a timeout was exceeded" },
771 { "exit-code", "the control process exited with error code" },
772 { "signal", "a fatal signal was delivered to the control process" },
773 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
774 { "watchdog", "the service failed to send watchdog ping" },
775 { "start-limit", "start of the service was attempted too often" }
776 };
777
778 static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) {
779 _cleanup_free_ char *service_shell_quoted = NULL;
780 const char *systemctl = "systemctl", *journalctl = "journalctl";
781
782 assert(service);
783
784 service_shell_quoted = shell_maybe_quote(service);
785
786 if (extra_args && extra_args[1]) {
787 _cleanup_free_ char *t;
788
789 t = strv_join((char**) extra_args, " ");
790 systemctl = strjoina("systemctl ", t ? : "<args>");
791 journalctl = strjoina("journalctl ", t ? : "<args>");
792 }
793
794 if (!isempty(result)) {
795 unsigned i;
796
797 for (i = 0; i < ELEMENTSOF(explanations); ++i)
798 if (streq(result, explanations[i].result))
799 break;
800
801 if (i < ELEMENTSOF(explanations)) {
802 log_error("Job for %s failed because %s.\n"
803 "See \"%s status %s\" and \"%s -xe\" for details.\n",
804 service,
805 explanations[i].explanation,
806 systemctl,
807 service_shell_quoted ?: "<service>",
808 journalctl);
809 goto finish;
810 }
811 }
812
813 log_error("Job for %s failed.\n"
814 "See \"%s status %s\" and \"%s -xe\" for details.\n",
815 service,
816 systemctl,
817 service_shell_quoted ?: "<service>",
818 journalctl);
819
820 finish:
821 /* For some results maybe additional explanation is required */
822 if (streq_ptr(result, "start-limit"))
823 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
824 "followed by \"%1$s start %2$s\" again.",
825 systemctl,
826 service_shell_quoted ?: "<service>");
827 }
828
829 static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
830 int r = 0;
831
832 assert(d->result);
833
834 if (!quiet) {
835 if (streq(d->result, "canceled"))
836 log_error("Job for %s canceled.", strna(d->name));
837 else if (streq(d->result, "timeout"))
838 log_error("Job for %s timed out.", strna(d->name));
839 else if (streq(d->result, "dependency"))
840 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
841 else if (streq(d->result, "invalid"))
842 log_error("%s is not active, cannot reload.", strna(d->name));
843 else if (streq(d->result, "assert"))
844 log_error("Assertion failed on job for %s.", strna(d->name));
845 else if (streq(d->result, "unsupported"))
846 log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
847 else if (!streq(d->result, "done") && !streq(d->result, "skipped")) {
848 if (d->name) {
849 int q;
850 _cleanup_free_ char *result = NULL;
851
852 q = bus_job_get_service_result(d, &result);
853 if (q < 0)
854 log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name);
855
856 log_job_error_with_service_result(d->name, result, extra_args);
857 } else
858 log_error("Job failed. See \"journalctl -xe\" for details.");
859 }
860 }
861
862 if (streq(d->result, "canceled"))
863 r = -ECANCELED;
864 else if (streq(d->result, "timeout"))
865 r = -ETIME;
866 else if (streq(d->result, "dependency"))
867 r = -EIO;
868 else if (streq(d->result, "invalid"))
869 r = -ENOEXEC;
870 else if (streq(d->result, "assert"))
871 r = -EPROTO;
872 else if (streq(d->result, "unsupported"))
873 r = -EOPNOTSUPP;
874 else if (!streq(d->result, "done") && !streq(d->result, "skipped"))
875 r = -EIO;
876
877 return r;
878 }
879
880 int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
881 int r = 0;
882
883 assert(d);
884
885 while (!set_isempty(d->jobs)) {
886 int q;
887
888 q = bus_process_wait(d->bus);
889 if (q < 0)
890 return log_error_errno(q, "Failed to wait for response: %m");
891
892 if (d->result) {
893 q = check_wait_response(d, quiet, extra_args);
894 /* Return the first error as it is most likely to be
895 * meaningful. */
896 if (q < 0 && r == 0)
897 r = q;
898
899 log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name));
900 }
901
902 d->name = mfree(d->name);
903 d->result = mfree(d->result);
904 }
905
906 return r;
907 }
908
909 int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) {
910 int r;
911
912 assert(d);
913
914 r = set_ensure_allocated(&d->jobs, &string_hash_ops);
915 if (r < 0)
916 return r;
917
918 return set_put_strdup(d->jobs, path);
919 }
920
921 int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) {
922 int r;
923
924 r = bus_wait_for_jobs_add(d, path);
925 if (r < 0)
926 return log_oom();
927
928 return bus_wait_for_jobs(d, quiet, NULL);
929 }
930
931 int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) {
932 const char *type, *path, *source;
933 int r;
934
935 /* changes is dereferenced when calling unit_file_dump_changes() later,
936 * so we have to make sure this is not NULL. */
937 assert(changes);
938 assert(n_changes);
939
940 r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)");
941 if (r < 0)
942 return bus_log_parse_error(r);
943
944 while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) {
945 /* We expect only "success" changes to be sent over the bus.
946 Hence, reject anything negative. */
947 UnitFileChangeType ch = unit_file_change_type_from_string(type);
948
949 if (ch < 0) {
950 log_notice("Manager reported unknown change type \"%s\" for path \"%s\", ignoring.", type, path);
951 continue;
952 }
953
954 r = unit_file_changes_add(changes, n_changes, ch, path, source);
955 if (r < 0)
956 return r;
957 }
958 if (r < 0)
959 return bus_log_parse_error(r);
960
961 r = sd_bus_message_exit_container(m);
962 if (r < 0)
963 return bus_log_parse_error(r);
964
965 unit_file_dump_changes(0, NULL, *changes, *n_changes, false);
966 return 0;
967 }
968
969 struct CGroupInfo {
970 char *cgroup_path;
971 bool is_const; /* If false, cgroup_path should be free()'d */
972
973 Hashmap *pids; /* PID → process name */
974 bool done;
975
976 struct CGroupInfo *parent;
977 LIST_FIELDS(struct CGroupInfo, siblings);
978 LIST_HEAD(struct CGroupInfo, children);
979 size_t n_children;
980 };
981
982 static bool IS_ROOT(const char *p) {
983 return isempty(p) || streq(p, "/");
984 }
985
986 static int add_cgroup(Hashmap *cgroups, const char *path, bool is_const, struct CGroupInfo **ret) {
987 struct CGroupInfo *parent = NULL, *cg;
988 int r;
989
990 assert(cgroups);
991 assert(ret);
992
993 if (IS_ROOT(path))
994 path = "/";
995
996 cg = hashmap_get(cgroups, path);
997 if (cg) {
998 *ret = cg;
999 return 0;
1000 }
1001
1002 if (!IS_ROOT(path)) {
1003 const char *e, *pp;
1004
1005 e = strrchr(path, '/');
1006 if (!e)
1007 return -EINVAL;
1008
1009 pp = strndupa(path, e - path);
1010 if (!pp)
1011 return -ENOMEM;
1012
1013 r = add_cgroup(cgroups, pp, false, &parent);
1014 if (r < 0)
1015 return r;
1016 }
1017
1018 cg = new0(struct CGroupInfo, 1);
1019 if (!cg)
1020 return -ENOMEM;
1021
1022 if (is_const)
1023 cg->cgroup_path = (char*) path;
1024 else {
1025 cg->cgroup_path = strdup(path);
1026 if (!cg->cgroup_path) {
1027 free(cg);
1028 return -ENOMEM;
1029 }
1030 }
1031
1032 cg->is_const = is_const;
1033 cg->parent = parent;
1034
1035 r = hashmap_put(cgroups, cg->cgroup_path, cg);
1036 if (r < 0) {
1037 if (!is_const)
1038 free(cg->cgroup_path);
1039 free(cg);
1040 return r;
1041 }
1042
1043 if (parent) {
1044 LIST_PREPEND(siblings, parent->children, cg);
1045 parent->n_children++;
1046 }
1047
1048 *ret = cg;
1049 return 1;
1050 }
1051
1052 static int add_process(
1053 Hashmap *cgroups,
1054 const char *path,
1055 pid_t pid,
1056 const char *name) {
1057
1058 struct CGroupInfo *cg;
1059 int r;
1060
1061 assert(cgroups);
1062 assert(name);
1063 assert(pid > 0);
1064
1065 r = add_cgroup(cgroups, path, true, &cg);
1066 if (r < 0)
1067 return r;
1068
1069 r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops);
1070 if (r < 0)
1071 return r;
1072
1073 return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name);
1074 }
1075
1076 static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) {
1077 assert(cgroups);
1078 assert(cg);
1079
1080 while (cg->children)
1081 remove_cgroup(cgroups, cg->children);
1082
1083 hashmap_remove(cgroups, cg->cgroup_path);
1084
1085 if (!cg->is_const)
1086 free(cg->cgroup_path);
1087
1088 hashmap_free(cg->pids);
1089
1090 if (cg->parent)
1091 LIST_REMOVE(siblings, cg->parent->children, cg);
1092
1093 free(cg);
1094 }
1095
1096 static int cgroup_info_compare_func(const void *a, const void *b) {
1097 const struct CGroupInfo *x = *(const struct CGroupInfo* const*) a, *y = *(const struct CGroupInfo* const*) b;
1098
1099 assert(x);
1100 assert(y);
1101
1102 return strcmp(x->cgroup_path, y->cgroup_path);
1103 }
1104
1105 static int dump_processes(
1106 Hashmap *cgroups,
1107 const char *cgroup_path,
1108 const char *prefix,
1109 unsigned n_columns,
1110 OutputFlags flags) {
1111
1112 struct CGroupInfo *cg;
1113 int r;
1114
1115 assert(prefix);
1116
1117 if (IS_ROOT(cgroup_path))
1118 cgroup_path = "/";
1119
1120 cg = hashmap_get(cgroups, cgroup_path);
1121 if (!cg)
1122 return 0;
1123
1124 if (!hashmap_isempty(cg->pids)) {
1125 const char *name;
1126 size_t n = 0, i;
1127 pid_t *pids;
1128 void *pidp;
1129 Iterator j;
1130 int width;
1131
1132 /* Order processes by their PID */
1133 pids = newa(pid_t, hashmap_size(cg->pids));
1134
1135 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j)
1136 pids[n++] = PTR_TO_PID(pidp);
1137
1138 assert(n == hashmap_size(cg->pids));
1139 qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
1140
1141 width = DECIMAL_STR_WIDTH(pids[n-1]);
1142
1143 for (i = 0; i < n; i++) {
1144 _cleanup_free_ char *e = NULL;
1145 const char *special;
1146 bool more;
1147
1148 name = hashmap_get(cg->pids, PID_TO_PTR(pids[i]));
1149 assert(name);
1150
1151 if (n_columns != 0) {
1152 unsigned k;
1153
1154 k = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
1155
1156 e = ellipsize(name, k, 100);
1157 if (e)
1158 name = e;
1159 }
1160
1161 more = i+1 < n || cg->children;
1162 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
1163
1164 fprintf(stdout, "%s%s%*"PID_PRI" %s\n",
1165 prefix,
1166 special,
1167 width, pids[i],
1168 name);
1169 }
1170 }
1171
1172 if (cg->children) {
1173 struct CGroupInfo **children, *child;
1174 size_t n = 0, i;
1175
1176 /* Order subcgroups by their name */
1177 children = newa(struct CGroupInfo*, cg->n_children);
1178 LIST_FOREACH(siblings, child, cg->children)
1179 children[n++] = child;
1180 assert(n == cg->n_children);
1181 qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func);
1182
1183 if (n_columns != 0)
1184 n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
1185
1186 for (i = 0; i < n; i++) {
1187 _cleanup_free_ char *pp = NULL;
1188 const char *name, *special;
1189 bool more;
1190
1191 child = children[i];
1192
1193 name = strrchr(child->cgroup_path, '/');
1194 if (!name)
1195 return -EINVAL;
1196 name++;
1197
1198 more = i+1 < n;
1199 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
1200
1201 fputs(prefix, stdout);
1202 fputs(special, stdout);
1203 fputs(name, stdout);
1204 fputc('\n', stdout);
1205
1206 special = special_glyph(more ? TREE_VERTICAL : TREE_SPACE);
1207
1208 pp = strappend(prefix, special);
1209 if (!pp)
1210 return -ENOMEM;
1211
1212 r = dump_processes(cgroups, child->cgroup_path, pp, n_columns, flags);
1213 if (r < 0)
1214 return r;
1215 }
1216 }
1217
1218 cg->done = true;
1219 return 0;
1220 }
1221
1222 static int dump_extra_processes(
1223 Hashmap *cgroups,
1224 const char *prefix,
1225 unsigned n_columns,
1226 OutputFlags flags) {
1227
1228 _cleanup_free_ pid_t *pids = NULL;
1229 _cleanup_hashmap_free_ Hashmap *names = NULL;
1230 struct CGroupInfo *cg;
1231 size_t n_allocated = 0, n = 0, k;
1232 Iterator i;
1233 int width, r;
1234
1235 /* Prints the extra processes, i.e. those that are in cgroups we haven't displayed yet. We show them as
1236 * combined, sorted, linear list. */
1237
1238 HASHMAP_FOREACH(cg, cgroups, i) {
1239 const char *name;
1240 void *pidp;
1241 Iterator j;
1242
1243 if (cg->done)
1244 continue;
1245
1246 if (hashmap_isempty(cg->pids))
1247 continue;
1248
1249 r = hashmap_ensure_allocated(&names, &trivial_hash_ops);
1250 if (r < 0)
1251 return r;
1252
1253 if (!GREEDY_REALLOC(pids, n_allocated, n + hashmap_size(cg->pids)))
1254 return -ENOMEM;
1255
1256 HASHMAP_FOREACH_KEY(name, pidp, cg->pids, j) {
1257 pids[n++] = PTR_TO_PID(pidp);
1258
1259 r = hashmap_put(names, pidp, (void*) name);
1260 if (r < 0)
1261 return r;
1262 }
1263 }
1264
1265 if (n == 0)
1266 return 0;
1267
1268 qsort_safe(pids, n, sizeof(pid_t), pid_compare_func);
1269 width = DECIMAL_STR_WIDTH(pids[n-1]);
1270
1271 for (k = 0; k < n; k++) {
1272 _cleanup_free_ char *e = NULL;
1273 const char *name;
1274
1275 name = hashmap_get(names, PID_TO_PTR(pids[k]));
1276 assert(name);
1277
1278 if (n_columns != 0) {
1279 unsigned z;
1280
1281 z = MAX(LESS_BY(n_columns, 2U + width + 1U), 20U);
1282
1283 e = ellipsize(name, z, 100);
1284 if (e)
1285 name = e;
1286 }
1287
1288 fprintf(stdout, "%s%s %*" PID_PRI " %s\n",
1289 prefix,
1290 special_glyph(TRIANGULAR_BULLET),
1291 width, pids[k],
1292 name);
1293 }
1294
1295 return 0;
1296 }
1297
1298 int unit_show_processes(
1299 sd_bus *bus,
1300 const char *unit,
1301 const char *cgroup_path,
1302 const char *prefix,
1303 unsigned n_columns,
1304 OutputFlags flags,
1305 sd_bus_error *error) {
1306
1307 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1308 Hashmap *cgroups = NULL;
1309 struct CGroupInfo *cg;
1310 int r;
1311
1312 assert(bus);
1313 assert(unit);
1314
1315 if (flags & OUTPUT_FULL_WIDTH)
1316 n_columns = 0;
1317 else if (n_columns <= 0)
1318 n_columns = columns();
1319
1320 prefix = strempty(prefix);
1321
1322 r = sd_bus_call_method(
1323 bus,
1324 "org.freedesktop.systemd1",
1325 "/org/freedesktop/systemd1",
1326 "org.freedesktop.systemd1.Manager",
1327 "GetUnitProcesses",
1328 error,
1329 &reply,
1330 "s",
1331 unit);
1332 if (r < 0)
1333 return r;
1334
1335 cgroups = hashmap_new(&string_hash_ops);
1336 if (!cgroups)
1337 return -ENOMEM;
1338
1339 r = sd_bus_message_enter_container(reply, 'a', "(sus)");
1340 if (r < 0)
1341 goto finish;
1342
1343 for (;;) {
1344 const char *path = NULL, *name = NULL;
1345 uint32_t pid;
1346
1347 r = sd_bus_message_read(reply, "(sus)", &path, &pid, &name);
1348 if (r < 0)
1349 goto finish;
1350 if (r == 0)
1351 break;
1352
1353 r = add_process(cgroups, path, pid, name);
1354 if (r < 0)
1355 goto finish;
1356 }
1357
1358 r = sd_bus_message_exit_container(reply);
1359 if (r < 0)
1360 goto finish;
1361
1362 r = dump_processes(cgroups, cgroup_path, prefix, n_columns, flags);
1363 if (r < 0)
1364 goto finish;
1365
1366 r = dump_extra_processes(cgroups, prefix, n_columns, flags);
1367
1368 finish:
1369 while ((cg = hashmap_first(cgroups)))
1370 remove_cgroup(cgroups, cg);
1371
1372 hashmap_free(cgroups);
1373
1374 return r;
1375 }