]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/bus-unit-util.c
Merge pull request #4877 from evverx/fix-machine-id
[thirdparty/systemd.git] / src / shared / bus-unit-util.c
CommitLineData
291d565a
LP
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"
add00535 30#include "nsflags.h"
291d565a
LP
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
20b16441
LP
42int 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
63int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) {
64 const char *eq, *field;
289188ca 65 UnitDependency dep;
20b16441
LP
66 int r, rl;
67
68 assert(m);
69 assert(assignment);
70
71 eq = strchr(assignment, '=');
72 if (!eq) {
73 log_error("Not an assignment: %s", assignment);
74 return -EINVAL;
75 }
76
77 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
78 if (r < 0)
79 return bus_log_create_error(r);
80
81 field = strndupa(assignment, eq - assignment);
82 eq++;
83
84 if (streq(field, "CPUQuota")) {
85
86 if (isempty(eq))
87 r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY);
9184ca48 88 else {
5124866d 89 r = parse_percent_unbounded(eq);
9184ca48
LP
90 if (r <= 0) {
91 log_error_errno(r, "CPU quota '%s' invalid.", eq);
20b16441
LP
92 return -EINVAL;
93 }
94
9184ca48 95 r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) r * USEC_PER_SEC / 100U);
20b16441
LP
96 }
97
98 goto finish;
99
100 } else if (streq(field, "EnvironmentFile")) {
101
102 r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1,
103 eq[0] == '-' ? eq + 1 : eq,
104 eq[0] == '-');
105 goto finish;
106
107 } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) {
108 char *n;
109 usec_t t;
110 size_t l;
9184ca48 111
20b16441
LP
112 r = parse_sec(eq, &t);
113 if (r < 0)
114 return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
115
116 l = strlen(field);
117 n = newa(char, l + 2);
118 if (!n)
119 return log_oom();
120
121 /* Change suffix Sec → USec */
122 strcpy(mempcpy(n, field, l - 3), "USec");
123 r = sd_bus_message_append(m, "sv", n, "t", t);
124 goto finish;
d58d600e
LP
125
126 } else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
127 uint64_t bytes;
128
129 if (isempty(eq) || streq(eq, "infinity"))
130 bytes = CGROUP_LIMIT_MAX;
131 else {
132 r = parse_percent(eq);
133 if (r >= 0) {
134 char *n;
135
136 /* When this is a percentage we'll convert this into a relative value in the range
f7903e8d 137 * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
d58d600e
LP
138 * ones). This way the physical memory size can be determined server-side */
139
f7903e8d 140 n = strjoina(field, "Scale");
d58d600e
LP
141 r = sd_bus_message_append(m, "sv", n, "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
142 goto finish;
143
144 } else {
145 r = parse_size(eq, 1024, &bytes);
146 if (r < 0)
147 return log_error_errno(r, "Failed to parse bytes specification %s", assignment);
148 }
149 }
150
151 r = sd_bus_message_append(m, "sv", field, "t", bytes);
152 goto finish;
83f8e808
LP
153 } else if (streq(field, "TasksMax")) {
154 uint64_t t;
155
156 if (isempty(eq) || streq(eq, "infinity"))
157 t = (uint64_t) -1;
158 else {
159 r = parse_percent(eq);
160 if (r >= 0) {
161 r = sd_bus_message_append(m, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
162 goto finish;
163 } else {
164 r = safe_atou64(eq, &t);
165 if (r < 0)
166 return log_error_errno(r, "Failed to parse maximum tasks specification %s", assignment);
167 }
168
169 }
170
171 r = sd_bus_message_append(m, "sv", "TasksMax", "t", t);
172 goto finish;
20b16441
LP
173 }
174
175 r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
176 if (r < 0)
177 return bus_log_create_error(r);
178
179 rl = rlimit_from_string(field);
180 if (rl >= 0) {
181 const char *sn;
182 struct rlimit l;
183
184 r = rlimit_parse(rl, eq, &l);
185 if (r < 0)
186 return log_error_errno(r, "Failed to parse resource limit: %s", eq);
187
188 r = sd_bus_message_append(m, "v", "t", l.rlim_max);
189 if (r < 0)
190 return bus_log_create_error(r);
191
192 r = sd_bus_message_close_container(m);
193 if (r < 0)
194 return bus_log_create_error(r);
195
196 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
197 if (r < 0)
198 return bus_log_create_error(r);
199
200 sn = strjoina(field, "Soft");
201 r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur);
202
203 } else if (STR_IN_SET(field,
29206d46
LP
204 "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
205 "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
206 "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
d251207d 207 "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers", "NoNewPrivileges",
29206d46 208 "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
502d704e
DH
209 "RestrictRealtime", "DynamicUser", "RemoveIPC", "ProtectKernelTunables",
210 "ProtectKernelModules", "ProtectControlGroups")) {
20b16441
LP
211
212 r = parse_boolean(eq);
213 if (r < 0)
214 return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment);
215
216 r = sd_bus_message_append(m, "v", "b", r);
217
66ebf6c0
TH
218 } else if (STR_IN_SET(field, "CPUWeight", "StartupCPUWeight")) {
219 uint64_t u;
220
221 r = cg_weight_parse(eq, &u);
222 if (r < 0) {
223 log_error("Failed to parse %s value %s.", field, eq);
224 return -EINVAL;
225 }
226
227 r = sd_bus_message_append(m, "v", "t", u);
228
20b16441
LP
229 } else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) {
230 uint64_t u;
231
232 r = cg_cpu_shares_parse(eq, &u);
233 if (r < 0) {
234 log_error("Failed to parse %s value %s.", field, eq);
235 return -EINVAL;
236 }
237
238 r = sd_bus_message_append(m, "v", "t", u);
239
13c31542
TH
240 } else if (STR_IN_SET(field, "IOWeight", "StartupIOWeight")) {
241 uint64_t u;
242
243 r = cg_weight_parse(eq, &u);
244 if (r < 0) {
245 log_error("Failed to parse %s value %s.", field, eq);
246 return -EINVAL;
247 }
248
249 r = sd_bus_message_append(m, "v", "t", u);
250
20b16441
LP
251 } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) {
252 uint64_t u;
253
3fdf9ad7 254 r = cg_blkio_weight_parse(eq, &u);
20b16441
LP
255 if (r < 0) {
256 log_error("Failed to parse %s value %s.", field, eq);
257 return -EINVAL;
258 }
259
260 r = sd_bus_message_append(m, "v", "t", u);
261
262 } else if (STR_IN_SET(field,
263 "User", "Group", "DevicePolicy", "KillMode",
264 "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
265 "StandardInput", "StandardOutput", "StandardError",
266 "Description", "Slice", "Type", "WorkingDirectory",
267 "RootDirectory", "SyslogIdentifier", "ProtectSystem",
186ad4b1 268 "ProtectHome", "SELinuxContext"))
20b16441
LP
269 r = sd_bus_message_append(m, "v", "s", eq);
270
271 else if (streq(field, "SyslogLevel")) {
272 int level;
273
274 level = log_level_from_string(eq);
275 if (level < 0) {
276 log_error("Failed to parse %s value %s.", field, eq);
277 return -EINVAL;
278 }
279
280 r = sd_bus_message_append(m, "v", "i", level);
281
282 } else if (streq(field, "SyslogFacility")) {
283 int facility;
284
285 facility = log_facility_unshifted_from_string(eq);
286 if (facility < 0) {
287 log_error("Failed to parse %s value %s.", field, eq);
288 return -EINVAL;
289 }
290
291 r = sd_bus_message_append(m, "v", "i", facility);
292
293 } else if (streq(field, "DeviceAllow")) {
294
295 if (isempty(eq))
296 r = sd_bus_message_append(m, "v", "a(ss)", 0);
297 else {
298 const char *path, *rwm, *e;
299
300 e = strchr(eq, ' ');
301 if (e) {
302 path = strndupa(eq, e - eq);
303 rwm = e+1;
304 } else {
305 path = eq;
306 rwm = "";
307 }
308
3ccb8862 309 if (!is_deviceallow_pattern(path)) {
20b16441
LP
310 log_error("%s is not a device file in /dev.", path);
311 return -EINVAL;
312 }
313
314 r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm);
315 }
316
9be57249 317 } else if (cgroup_io_limit_type_from_string(field) >= 0 || STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
20b16441
LP
318
319 if (isempty(eq))
320 r = sd_bus_message_append(m, "v", "a(st)", 0);
321 else {
322 const char *path, *bandwidth, *e;
323 uint64_t bytes;
324
325 e = strchr(eq, ' ');
326 if (e) {
327 path = strndupa(eq, e - eq);
328 bandwidth = e+1;
329 } else {
330 log_error("Failed to parse %s value %s.", field, eq);
331 return -EINVAL;
332 }
333
334 if (!path_startswith(path, "/dev")) {
335 log_error("%s is not a device file in /dev.", path);
336 return -EINVAL;
337 }
338
e57c9ce1 339 if (streq(bandwidth, "infinity")) {
13c31542
TH
340 bytes = CGROUP_LIMIT_MAX;
341 } else {
342 r = parse_size(bandwidth, 1000, &bytes);
343 if (r < 0) {
344 log_error("Failed to parse byte value %s.", bandwidth);
345 return -EINVAL;
346 }
20b16441
LP
347 }
348
349 r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes);
350 }
351
13c31542 352 } else if (STR_IN_SET(field, "IODeviceWeight", "BlockIODeviceWeight")) {
20b16441
LP
353
354 if (isempty(eq))
355 r = sd_bus_message_append(m, "v", "a(st)", 0);
356 else {
357 const char *path, *weight, *e;
358 uint64_t u;
359
360 e = strchr(eq, ' ');
361 if (e) {
362 path = strndupa(eq, e - eq);
363 weight = e+1;
364 } else {
365 log_error("Failed to parse %s value %s.", field, eq);
366 return -EINVAL;
367 }
368
369 if (!path_startswith(path, "/dev")) {
370 log_error("%s is not a device file in /dev.", path);
371 return -EINVAL;
372 }
373
374 r = safe_atou64(weight, &u);
375 if (r < 0) {
376 log_error("Failed to parse %s value %s.", field, weight);
377 return -EINVAL;
378 }
096a4d53 379 r = sd_bus_message_append(m, "v", "a(st)", 1, path, u);
20b16441
LP
380 }
381
382 } else if (streq(field, "Nice")) {
41bf0590 383 int n;
20b16441 384
41bf0590
LP
385 r = parse_nice(eq, &n);
386 if (r < 0)
387 return log_error_errno(r, "Failed to parse nice value: %s", eq);
20b16441 388
41bf0590 389 r = sd_bus_message_append(m, "v", "i", (int32_t) n);
20b16441
LP
390
391 } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) {
392 const char *p;
393
394 r = sd_bus_message_open_container(m, 'v', "as");
395 if (r < 0)
396 return bus_log_create_error(r);
397
398 r = sd_bus_message_open_container(m, 'a', "s");
399 if (r < 0)
400 return bus_log_create_error(r);
401
c58bd76a 402 for (p = eq;;) {
20b16441
LP
403 _cleanup_free_ char *word = NULL;
404
405 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
406 if (r < 0) {
407 log_error("Failed to parse Environment value %s", eq);
408 return -EINVAL;
409 }
410 if (r == 0)
411 break;
412
413 if (streq(field, "Environment")) {
414 if (!env_assignment_is_valid(word)) {
415 log_error("Invalid environment assignment: %s", word);
416 return -EINVAL;
417 }
418 } else { /* PassEnvironment */
419 if (!env_name_is_valid(word)) {
420 log_error("Invalid environment variable name: %s", word);
421 return -EINVAL;
422 }
423 }
424
425 r = sd_bus_message_append_basic(m, 's', word);
426 if (r < 0)
427 return bus_log_create_error(r);
428 }
429
430 r = sd_bus_message_close_container(m);
431 if (r < 0)
432 return bus_log_create_error(r);
433
434 r = sd_bus_message_close_container(m);
435
436 } else if (streq(field, "KillSignal")) {
437 int sig;
438
439 sig = signal_from_string_try_harder(eq);
440 if (sig < 0) {
441 log_error("Failed to parse %s value %s.", field, eq);
442 return -EINVAL;
443 }
444
445 r = sd_bus_message_append(m, "v", "i", sig);
446
447 } else if (streq(field, "TimerSlackNSec")) {
448 nsec_t n;
449
450 r = parse_nsec(eq, &n);
451 if (r < 0) {
452 log_error("Failed to parse %s value %s", field, eq);
453 return -EINVAL;
454 }
455
456 r = sd_bus_message_append(m, "v", "t", n);
457 } else if (streq(field, "OOMScoreAdjust")) {
458 int oa;
459
460 r = safe_atoi(eq, &oa);
461 if (r < 0) {
462 log_error("Failed to parse %s value %s", field, eq);
463 return -EINVAL;
464 }
465
466 if (!oom_score_adjust_is_valid(oa)) {
467 log_error("OOM score adjust value out of range");
468 return -EINVAL;
469 }
470
471 r = sd_bus_message_append(m, "v", "i", oa);
2a624c36
AP
472 } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
473 "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
20b16441
LP
474 const char *p;
475
476 r = sd_bus_message_open_container(m, 'v', "as");
477 if (r < 0)
478 return bus_log_create_error(r);
479
480 r = sd_bus_message_open_container(m, 'a', "s");
481 if (r < 0)
482 return bus_log_create_error(r);
483
c58bd76a 484 for (p = eq;;) {
20b16441
LP
485 _cleanup_free_ char *word = NULL;
486 int offset;
487
488 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
489 if (r < 0) {
490 log_error("Failed to parse %s value %s", field, eq);
491 return -EINVAL;
492 }
493 if (r == 0)
494 break;
495
496 if (!utf8_is_valid(word)) {
497 log_error("Failed to parse %s value %s", field, eq);
498 return -EINVAL;
499 }
500
501 offset = word[0] == '-';
502 if (!path_is_absolute(word + offset)) {
503 log_error("Failed to parse %s value %s", field, eq);
504 return -EINVAL;
505 }
506
507 path_kill_slashes(word + offset);
508
509 r = sd_bus_message_append_basic(m, 's', word);
510 if (r < 0)
511 return bus_log_create_error(r);
512 }
513
514 r = sd_bus_message_close_container(m);
515 if (r < 0)
516 return bus_log_create_error(r);
517
518 r = sd_bus_message_close_container(m);
519
520 } else if (streq(field, "RuntimeDirectory")) {
521 const char *p;
522
523 r = sd_bus_message_open_container(m, 'v', "as");
524 if (r < 0)
525 return bus_log_create_error(r);
526
527 r = sd_bus_message_open_container(m, 'a', "s");
528 if (r < 0)
529 return bus_log_create_error(r);
530
c58bd76a 531 for (p = eq;;) {
20b16441
LP
532 _cleanup_free_ char *word = NULL;
533
534 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
535 if (r < 0)
536 return log_error_errno(r, "Failed to parse %s value %s", field, eq);
537
538 if (r == 0)
539 break;
540
541 r = sd_bus_message_append_basic(m, 's', word);
542 if (r < 0)
543 return bus_log_create_error(r);
544 }
545
546 r = sd_bus_message_close_container(m);
547 if (r < 0)
548 return bus_log_create_error(r);
549
550 r = sd_bus_message_close_container(m);
551
add00535
LP
552 } else if (streq(field, "RestrictNamespaces")) {
553 bool invert = false;
554 uint64_t flags = 0;
555
556 if (eq[0] == '~') {
557 invert = true;
558 eq++;
559 }
560
561 r = parse_boolean(eq);
562 if (r > 0)
563 flags = 0;
564 else if (r == 0)
565 flags = NAMESPACE_FLAGS_ALL;
566 else {
567 r = namespace_flag_from_string_many(eq, &flags);
568 if (r < 0)
569 return log_error_errno(r, "Failed to parse %s value %s.", field, eq);
570 }
571
572 if (invert)
573 flags = (~flags) & NAMESPACE_FLAGS_ALL;
574
575 r = sd_bus_message_append(m, "v", "t", flags);
289188ca
LP
576 } else if ((dep = unit_dependency_from_string(field)) >= 0)
577 r = sd_bus_message_append(m, "v", "as", 1, eq);
578 else {
20b16441
LP
579 log_error("Unknown assignment %s.", assignment);
580 return -EINVAL;
581 }
582
583finish:
584 if (r < 0)
585 return bus_log_create_error(r);
586
587 r = sd_bus_message_close_container(m);
588 if (r < 0)
589 return bus_log_create_error(r);
590
591 return 0;
592}
593
8673cf13
LP
594int bus_append_unit_property_assignment_many(sd_bus_message *m, char **l) {
595 char **i;
596 int r;
597
598 assert(m);
599
600 STRV_FOREACH(i, l) {
601 r = bus_append_unit_property_assignment(m, *i);
602 if (r < 0)
603 return r;
604 }
605
606 return 0;
607}
608
20b16441
LP
609typedef struct BusWaitForJobs {
610 sd_bus *bus;
611 Set *jobs;
612
613 char *name;
614 char *result;
615
616 sd_bus_slot *slot_job_removed;
617 sd_bus_slot *slot_disconnected;
618} BusWaitForJobs;
619
620static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
621 assert(m);
622
623 log_error("Warning! D-Bus connection terminated.");
624 sd_bus_close(sd_bus_message_get_bus(m));
625
626 return 0;
627}
628
629static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
630 const char *path, *unit, *result;
631 BusWaitForJobs *d = userdata;
632 uint32_t id;
633 char *found;
634 int r;
635
636 assert(m);
637 assert(d);
638
639 r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result);
640 if (r < 0) {
641 bus_log_parse_error(r);
642 return 0;
643 }
644
645 found = set_remove(d->jobs, (char*) path);
646 if (!found)
647 return 0;
648
649 free(found);
650
651 if (!isempty(result))
652 d->result = strdup(result);
653
654 if (!isempty(unit))
655 d->name = strdup(unit);
656
657 return 0;
658}
659
660void bus_wait_for_jobs_free(BusWaitForJobs *d) {
661 if (!d)
662 return;
663
664 set_free_free(d->jobs);
665
666 sd_bus_slot_unref(d->slot_disconnected);
667 sd_bus_slot_unref(d->slot_job_removed);
668
669 sd_bus_unref(d->bus);
670
671 free(d->name);
672 free(d->result);
673
674 free(d);
675}
676
677int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) {
678 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL;
679 int r;
680
681 assert(bus);
682 assert(ret);
683
684 d = new0(BusWaitForJobs, 1);
685 if (!d)
686 return -ENOMEM;
687
688 d->bus = sd_bus_ref(bus);
689
690 /* When we are a bus client we match by sender. Direct
691 * connections OTOH have no initialized sender field, and
692 * hence we ignore the sender then */
693 r = sd_bus_add_match(
694 bus,
695 &d->slot_job_removed,
696 bus->bus_client ?
697 "type='signal',"
698 "sender='org.freedesktop.systemd1',"
699 "interface='org.freedesktop.systemd1.Manager',"
700 "member='JobRemoved',"
701 "path='/org/freedesktop/systemd1'" :
702 "type='signal',"
703 "interface='org.freedesktop.systemd1.Manager',"
704 "member='JobRemoved',"
705 "path='/org/freedesktop/systemd1'",
706 match_job_removed, d);
707 if (r < 0)
708 return r;
709
710 r = sd_bus_add_match(
711 bus,
712 &d->slot_disconnected,
713 "type='signal',"
714 "sender='org.freedesktop.DBus.Local',"
715 "interface='org.freedesktop.DBus.Local',"
716 "member='Disconnected'",
717 match_disconnected, d);
718 if (r < 0)
719 return r;
720
721 *ret = d;
722 d = NULL;
723
724 return 0;
725}
726
727static int bus_process_wait(sd_bus *bus) {
728 int r;
729
730 for (;;) {
731 r = sd_bus_process(bus, NULL);
732 if (r < 0)
733 return r;
734 if (r > 0)
735 return 0;
736
737 r = sd_bus_wait(bus, (uint64_t) -1);
738 if (r < 0)
739 return r;
740 }
741}
742
743static int bus_job_get_service_result(BusWaitForJobs *d, char **result) {
744 _cleanup_free_ char *dbus_path = NULL;
745
746 assert(d);
747 assert(d->name);
748 assert(result);
749
750 dbus_path = unit_dbus_path_from_name(d->name);
751 if (!dbus_path)
752 return -ENOMEM;
753
754 return sd_bus_get_property_string(d->bus,
755 "org.freedesktop.systemd1",
756 dbus_path,
757 "org.freedesktop.systemd1.Service",
758 "Result",
759 NULL,
760 result);
761}
762
763static const struct {
764 const char *result, *explanation;
765} explanations [] = {
0b2de9d9 766 { "resources", "of unavailable resources or another system error" },
7ed0a4c5 767 { "protocol", "the service did not take the steps required by its unit configuration" },
20b16441
LP
768 { "timeout", "a timeout was exceeded" },
769 { "exit-code", "the control process exited with error code" },
770 { "signal", "a fatal signal was delivered to the control process" },
771 { "core-dump", "a fatal signal was delivered causing the control process to dump core" },
772 { "watchdog", "the service failed to send watchdog ping" },
773 { "start-limit", "start of the service was attempted too often" }
774};
775
776static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) {
777 _cleanup_free_ char *service_shell_quoted = NULL;
778 const char *systemctl = "systemctl", *journalctl = "journalctl";
779
780 assert(service);
781
782 service_shell_quoted = shell_maybe_quote(service);
783
784 if (extra_args && extra_args[1]) {
785 _cleanup_free_ char *t;
786
787 t = strv_join((char**) extra_args, " ");
788 systemctl = strjoina("systemctl ", t ? : "<args>");
789 journalctl = strjoina("journalctl ", t ? : "<args>");
790 }
791
792 if (!isempty(result)) {
793 unsigned i;
794
795 for (i = 0; i < ELEMENTSOF(explanations); ++i)
796 if (streq(result, explanations[i].result))
797 break;
798
799 if (i < ELEMENTSOF(explanations)) {
800 log_error("Job for %s failed because %s.\n"
801 "See \"%s status %s\" and \"%s -xe\" for details.\n",
802 service,
803 explanations[i].explanation,
804 systemctl,
805 service_shell_quoted ?: "<service>",
806 journalctl);
807 goto finish;
808 }
809 }
810
811 log_error("Job for %s failed.\n"
812 "See \"%s status %s\" and \"%s -xe\" for details.\n",
813 service,
814 systemctl,
815 service_shell_quoted ?: "<service>",
816 journalctl);
817
818finish:
819 /* For some results maybe additional explanation is required */
820 if (streq_ptr(result, "start-limit"))
821 log_info("To force a start use \"%1$s reset-failed %2$s\"\n"
822 "followed by \"%1$s start %2$s\" again.",
823 systemctl,
824 service_shell_quoted ?: "<service>");
825}
826
827static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) {
828 int r = 0;
829
830 assert(d->result);
831
832 if (!quiet) {
833 if (streq(d->result, "canceled"))
834 log_error("Job for %s canceled.", strna(d->name));
835 else if (streq(d->result, "timeout"))
836 log_error("Job for %s timed out.", strna(d->name));
837 else if (streq(d->result, "dependency"))
838 log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name));
839 else if (streq(d->result, "invalid"))
840 log_error("%s is not active, cannot reload.", strna(d->name));
841 else if (streq(d->result, "assert"))
842 log_error("Assertion failed on job for %s.", strna(d->name));
843 else if (streq(d->result, "unsupported"))
844 log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
c5a97ed1
LP
845 else if (streq(d->result, "collected"))
846 log_error("Queued job for %s was garbage collected.", strna(d->name));
20b16441
LP
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
c5a97ed1 862 if (STR_IN_SET(d->result, "canceled", "collected"))
20b16441
LP
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
880int 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
909int 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
921int 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
931int 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
acc0269c
CH
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
20b16441
LP
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
291d565a
LP
969struct 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
982static bool IS_ROOT(const char *p) {
983 return isempty(p) || streq(p, "/");
984}
985
986static 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
1052static 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
1076static 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
1096static 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
1105static 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;
323b7dc9 1162 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
291d565a
LP
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
7351ded5
IL
1183 if (n_columns != 0)
1184 n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
291d565a
LP
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;
323b7dc9 1199 special = special_glyph(more ? TREE_BRANCH : TREE_RIGHT);
291d565a
LP
1200
1201 fputs(prefix, stdout);
1202 fputs(special, stdout);
1203 fputs(name, stdout);
1204 fputc('\n', stdout);
1205
323b7dc9 1206 special = special_glyph(more ? TREE_VERTICAL : TREE_SPACE);
291d565a
LP
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
1222static 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,
323b7dc9 1290 special_glyph(TRIANGULAR_BULLET),
291d565a
LP
1291 width, pids[k],
1292 name);
1293 }
1294
1295 return 0;
1296}
1297
1298int 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
1368finish:
1369 while ((cg = hashmap_first(cgroups)))
1370 remove_cgroup(cgroups, cg);
1371
1372 hashmap_free(cgroups);
1373
1374 return r;
1375}