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