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