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