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