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