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