]>
Commit | Line | Data |
---|---|---|
d6c9574f | 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
8c6db833 LP |
2 | |
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2010 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
5430f7f2 LP |
9 | under the terms of the GNU Lesser General Public License as published by |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
8c6db833 LP |
11 | (at your option) any later version. |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
5430f7f2 | 16 | Lesser General Public License for more details. |
8c6db833 | 17 | |
5430f7f2 | 18 | You should have received a copy of the GNU Lesser General Public License |
8c6db833 LP |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. |
20 | ***/ | |
21 | ||
22 | #include <errno.h> | |
23 | #include <unistd.h> | |
24 | #include <signal.h> | |
25 | #include <string.h> | |
26 | #include <stdlib.h> | |
35d2e7ec | 27 | #include <dirent.h> |
672c48cc LP |
28 | #include <sys/stat.h> |
29 | #include <sys/types.h> | |
e27796a0 | 30 | #include <ftw.h> |
8c6db833 LP |
31 | |
32 | #include "cgroup-util.h" | |
33 | #include "log.h" | |
34 | #include "set.h" | |
35 | #include "macro.h" | |
36 | #include "util.h" | |
9eb977db | 37 | #include "path-util.h" |
b59e2465 | 38 | #include "strv.h" |
ef1673d1 | 39 | #include "unit-name.h" |
a5c32cff | 40 | #include "fileio.h" |
9444b1f2 | 41 | #include "special.h" |
8c6db833 | 42 | |
c6c18be3 | 43 | int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { |
7027ff61 | 44 | _cleanup_free_ char *fs = NULL; |
c6c18be3 | 45 | FILE *f; |
7027ff61 | 46 | int r; |
c6c18be3 | 47 | |
c6c18be3 LP |
48 | assert(_f); |
49 | ||
c3175a7f LP |
50 | r = cg_get_path(controller, path, "cgroup.procs", &fs); |
51 | if (r < 0) | |
c6c18be3 LP |
52 | return r; |
53 | ||
54 | f = fopen(fs, "re"); | |
c6c18be3 LP |
55 | if (!f) |
56 | return -errno; | |
57 | ||
58 | *_f = f; | |
59 | return 0; | |
60 | } | |
61 | ||
c6c18be3 LP |
62 | int cg_read_pid(FILE *f, pid_t *_pid) { |
63 | unsigned long ul; | |
64 | ||
65 | /* Note that the cgroup.procs might contain duplicates! See | |
66 | * cgroups.txt for details. */ | |
67 | ||
7027ff61 LP |
68 | assert(f); |
69 | assert(_pid); | |
70 | ||
c6c18be3 LP |
71 | errno = 0; |
72 | if (fscanf(f, "%lu", &ul) != 1) { | |
73 | ||
74 | if (feof(f)) | |
75 | return 0; | |
76 | ||
77 | return errno ? -errno : -EIO; | |
78 | } | |
79 | ||
80 | if (ul <= 0) | |
81 | return -EIO; | |
82 | ||
83 | *_pid = (pid_t) ul; | |
84 | return 1; | |
85 | } | |
86 | ||
35d2e7ec | 87 | int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { |
7027ff61 | 88 | _cleanup_free_ char *fs = NULL; |
35d2e7ec LP |
89 | int r; |
90 | DIR *d; | |
91 | ||
35d2e7ec LP |
92 | assert(_d); |
93 | ||
94 | /* This is not recursive! */ | |
95 | ||
c3175a7f LP |
96 | r = cg_get_path(controller, path, NULL, &fs); |
97 | if (r < 0) | |
35d2e7ec LP |
98 | return r; |
99 | ||
100 | d = opendir(fs); | |
35d2e7ec LP |
101 | if (!d) |
102 | return -errno; | |
103 | ||
104 | *_d = d; | |
105 | return 0; | |
106 | } | |
107 | ||
108 | int cg_read_subgroup(DIR *d, char **fn) { | |
109 | struct dirent *de; | |
110 | ||
111 | assert(d); | |
7027ff61 | 112 | assert(fn); |
35d2e7ec | 113 | |
7027ff61 | 114 | FOREACH_DIRENT(de, d, return -errno) { |
35d2e7ec LP |
115 | char *b; |
116 | ||
117 | if (de->d_type != DT_DIR) | |
118 | continue; | |
119 | ||
120 | if (streq(de->d_name, ".") || | |
121 | streq(de->d_name, "..")) | |
122 | continue; | |
123 | ||
7027ff61 LP |
124 | b = strdup(de->d_name); |
125 | if (!b) | |
35d2e7ec LP |
126 | return -ENOMEM; |
127 | ||
128 | *fn = b; | |
129 | return 1; | |
130 | } | |
131 | ||
35d2e7ec LP |
132 | return 0; |
133 | } | |
134 | ||
ad293f5a | 135 | int cg_rmdir(const char *controller, const char *path, bool honour_sticky) { |
7027ff61 | 136 | _cleanup_free_ char *p = NULL; |
35d2e7ec LP |
137 | int r; |
138 | ||
ad293f5a LP |
139 | r = cg_get_path(controller, path, NULL, &p); |
140 | if (r < 0) | |
35d2e7ec LP |
141 | return r; |
142 | ||
ad293f5a | 143 | if (honour_sticky) { |
b043cd0b | 144 | char *fn; |
ad293f5a | 145 | |
b043cd0b LP |
146 | /* If the sticky bit is set on cgroup.procs, don't |
147 | * remove the directory */ | |
ad293f5a | 148 | |
b043cd0b LP |
149 | fn = strappend(p, "/cgroup.procs"); |
150 | if (!fn) | |
ad293f5a | 151 | return -ENOMEM; |
ad293f5a | 152 | |
b043cd0b LP |
153 | r = file_is_priv_sticky(fn); |
154 | free(fn); | |
155 | ||
156 | if (r > 0) | |
157 | return 0; | |
158 | ||
159 | /* Compatibility ... */ | |
160 | fn = strappend(p, "/tasks"); | |
161 | if (!fn) | |
162 | return -ENOMEM; | |
163 | ||
164 | r = file_is_priv_sticky(fn); | |
165 | free(fn); | |
ad293f5a | 166 | |
7027ff61 | 167 | if (r > 0) |
ad293f5a | 168 | return 0; |
ad293f5a LP |
169 | } |
170 | ||
35d2e7ec | 171 | r = rmdir(p); |
7027ff61 LP |
172 | if (r < 0 && errno != ENOENT) |
173 | return -errno; | |
35d2e7ec | 174 | |
7027ff61 | 175 | return 0; |
35d2e7ec LP |
176 | } |
177 | ||
430c18ed | 178 | int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) { |
7027ff61 | 179 | _cleanup_set_free_ Set *allocated_set = NULL; |
35d2e7ec | 180 | bool done = false; |
8c6db833 | 181 | int r, ret = 0; |
35d2e7ec | 182 | pid_t my_pid; |
8c6db833 | 183 | |
8c6db833 LP |
184 | assert(sig >= 0); |
185 | ||
186 | /* This goes through the tasks list and kills them all. This | |
187 | * is repeated until no further processes are added to the | |
188 | * tasks list, to properly handle forking processes */ | |
189 | ||
7027ff61 LP |
190 | if (!s) { |
191 | s = allocated_set = set_new(trivial_hash_func, trivial_compare_func); | |
192 | if (!s) | |
ca949c9d | 193 | return -ENOMEM; |
7027ff61 | 194 | } |
8c6db833 LP |
195 | |
196 | my_pid = getpid(); | |
197 | ||
198 | do { | |
7027ff61 | 199 | _cleanup_fclose_ FILE *f = NULL; |
0b172489 | 200 | pid_t pid = 0; |
8c6db833 LP |
201 | done = true; |
202 | ||
7027ff61 LP |
203 | r = cg_enumerate_processes(controller, path, &f); |
204 | if (r < 0) { | |
4c633005 | 205 | if (ret >= 0 && r != -ENOENT) |
7027ff61 | 206 | return r; |
35d2e7ec | 207 | |
7027ff61 | 208 | return ret; |
35d2e7ec | 209 | } |
c6c18be3 LP |
210 | |
211 | while ((r = cg_read_pid(f, &pid)) > 0) { | |
8c6db833 | 212 | |
7027ff61 | 213 | if (ignore_self && pid == my_pid) |
c6c18be3 | 214 | continue; |
8c6db833 | 215 | |
c6c18be3 LP |
216 | if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid)) |
217 | continue; | |
8c6db833 LP |
218 | |
219 | /* If we haven't killed this process yet, kill | |
220 | * it */ | |
4c633005 LP |
221 | if (kill(pid, sig) < 0) { |
222 | if (ret >= 0 && errno != ESRCH) | |
8c6db833 | 223 | ret = -errno; |
430c18ed LP |
224 | } else if (ret == 0) { |
225 | ||
226 | if (sigcont) | |
227 | kill(pid, SIGCONT); | |
228 | ||
35d2e7ec | 229 | ret = 1; |
430c18ed | 230 | } |
8c6db833 | 231 | |
8c6db833 LP |
232 | done = false; |
233 | ||
7027ff61 LP |
234 | r = set_put(s, LONG_TO_PTR(pid)); |
235 | if (r < 0) { | |
35d2e7ec | 236 | if (ret >= 0) |
7027ff61 | 237 | return r; |
35d2e7ec | 238 | |
7027ff61 | 239 | return ret; |
35d2e7ec LP |
240 | } |
241 | } | |
242 | ||
243 | if (r < 0) { | |
244 | if (ret >= 0) | |
7027ff61 | 245 | return r; |
35d2e7ec | 246 | |
7027ff61 | 247 | return ret; |
8c6db833 LP |
248 | } |
249 | ||
8c6db833 LP |
250 | /* To avoid racing against processes which fork |
251 | * quicker than we can kill them we repeat this until | |
252 | * no new pids need to be killed. */ | |
253 | ||
35d2e7ec | 254 | } while (!done); |
8c6db833 | 255 | |
35d2e7ec | 256 | return ret; |
8c6db833 LP |
257 | } |
258 | ||
430c18ed | 259 | int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) { |
7027ff61 LP |
260 | _cleanup_set_free_ Set *allocated_set = NULL; |
261 | _cleanup_closedir_ DIR *d = NULL; | |
35d2e7ec | 262 | int r, ret = 0; |
35d2e7ec | 263 | char *fn; |
8c6db833 LP |
264 | |
265 | assert(path); | |
8c6db833 LP |
266 | assert(sig >= 0); |
267 | ||
7027ff61 LP |
268 | if (!s) { |
269 | s = allocated_set = set_new(trivial_hash_func, trivial_compare_func); | |
270 | if (!s) | |
ca949c9d | 271 | return -ENOMEM; |
7027ff61 | 272 | } |
ca949c9d | 273 | |
430c18ed | 274 | ret = cg_kill(controller, path, sig, sigcont, ignore_self, s); |
8c6db833 | 275 | |
7027ff61 LP |
276 | r = cg_enumerate_subgroups(controller, path, &d); |
277 | if (r < 0) { | |
4c633005 | 278 | if (ret >= 0 && r != -ENOENT) |
7027ff61 | 279 | return r; |
8c6db833 | 280 | |
7027ff61 | 281 | return ret; |
35d2e7ec | 282 | } |
8c6db833 | 283 | |
35d2e7ec | 284 | while ((r = cg_read_subgroup(d, &fn)) > 0) { |
7027ff61 | 285 | _cleanup_free_ char *p = NULL; |
8c6db833 | 286 | |
7027ff61 | 287 | p = strjoin(path, "/", fn, NULL); |
35d2e7ec | 288 | free(fn); |
7027ff61 LP |
289 | if (!p) |
290 | return -ENOMEM; | |
8c6db833 | 291 | |
430c18ed | 292 | r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s); |
7027ff61 | 293 | if (ret >= 0 && r != 0) |
35d2e7ec | 294 | ret = r; |
8c6db833 LP |
295 | } |
296 | ||
7027ff61 | 297 | if (ret >= 0 && r < 0) |
35d2e7ec LP |
298 | ret = r; |
299 | ||
7027ff61 LP |
300 | if (rem) { |
301 | r = cg_rmdir(controller, path, true); | |
302 | if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) | |
303 | return r; | |
304 | } | |
ca949c9d | 305 | |
8c6db833 LP |
306 | return ret; |
307 | } | |
308 | ||
35d2e7ec | 309 | int cg_kill_recursive_and_wait(const char *controller, const char *path, bool rem) { |
8c6db833 LP |
310 | unsigned i; |
311 | ||
312 | assert(path); | |
8c6db833 LP |
313 | |
314 | /* This safely kills all processes; first it sends a SIGTERM, | |
9f452741 LP |
315 | * then checks 8 times after 200ms whether the group is now |
316 | * empty, then kills everything that is left with SIGKILL and | |
317 | * finally checks 5 times after 200ms each whether the group | |
318 | * is finally empty. */ | |
8c6db833 | 319 | |
9f452741 | 320 | for (i = 0; i < 15; i++) { |
1d0ae74a | 321 | int sig, r; |
8c6db833 LP |
322 | |
323 | if (i <= 0) | |
324 | sig = SIGTERM; | |
9f452741 | 325 | else if (i == 9) |
8c6db833 LP |
326 | sig = SIGKILL; |
327 | else | |
328 | sig = 0; | |
329 | ||
7027ff61 LP |
330 | r = cg_kill_recursive(controller, path, sig, true, true, rem, NULL); |
331 | if (r <= 0) | |
8c6db833 LP |
332 | return r; |
333 | ||
9f452741 | 334 | usleep(200 * USEC_PER_MSEC); |
8c6db833 LP |
335 | } |
336 | ||
337 | return 0; | |
338 | } | |
339 | ||
246aa6dd | 340 | int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) { |
35d2e7ec | 341 | bool done = false; |
246aa6dd | 342 | _cleanup_set_free_ Set *s = NULL; |
8c6db833 LP |
343 | int r, ret = 0; |
344 | pid_t my_pid; | |
345 | ||
246aa6dd LP |
346 | assert(cfrom); |
347 | assert(pfrom); | |
348 | assert(cto); | |
349 | assert(pto); | |
8c6db833 | 350 | |
246aa6dd LP |
351 | s = set_new(trivial_hash_func, trivial_compare_func); |
352 | if (!s) | |
35d2e7ec LP |
353 | return -ENOMEM; |
354 | ||
8c6db833 LP |
355 | my_pid = getpid(); |
356 | ||
357 | do { | |
7027ff61 | 358 | _cleanup_fclose_ FILE *f = NULL; |
0b172489 | 359 | pid_t pid = 0; |
8c6db833 LP |
360 | done = true; |
361 | ||
b043cd0b | 362 | r = cg_enumerate_processes(cfrom, pfrom, &f); |
246aa6dd | 363 | if (r < 0) { |
4c633005 | 364 | if (ret >= 0 && r != -ENOENT) |
7027ff61 | 365 | return r; |
35d2e7ec | 366 | |
246aa6dd | 367 | return ret; |
35d2e7ec | 368 | } |
c6c18be3 LP |
369 | |
370 | while ((r = cg_read_pid(f, &pid)) > 0) { | |
8c6db833 | 371 | |
35d2e7ec LP |
372 | /* This might do weird stuff if we aren't a |
373 | * single-threaded program. However, we | |
374 | * luckily know we are not */ | |
7027ff61 | 375 | if (ignore_self && pid == my_pid) |
c6c18be3 | 376 | continue; |
8c6db833 | 377 | |
35d2e7ec LP |
378 | if (set_get(s, LONG_TO_PTR(pid)) == LONG_TO_PTR(pid)) |
379 | continue; | |
380 | ||
246aa6dd LP |
381 | r = cg_attach(cto, pto, pid); |
382 | if (r < 0) { | |
4c633005 | 383 | if (ret >= 0 && r != -ESRCH) |
35d2e7ec LP |
384 | ret = r; |
385 | } else if (ret == 0) | |
386 | ret = 1; | |
8c6db833 | 387 | |
8c6db833 | 388 | done = false; |
35d2e7ec | 389 | |
246aa6dd LP |
390 | r = set_put(s, LONG_TO_PTR(pid)); |
391 | if (r < 0) { | |
35d2e7ec | 392 | if (ret >= 0) |
7027ff61 | 393 | return r; |
35d2e7ec | 394 | |
246aa6dd | 395 | return ret; |
35d2e7ec LP |
396 | } |
397 | } | |
398 | ||
399 | if (r < 0) { | |
400 | if (ret >= 0) | |
7027ff61 | 401 | return r; |
35d2e7ec | 402 | |
246aa6dd | 403 | return ret; |
8c6db833 | 404 | } |
35d2e7ec | 405 | } while (!done); |
8c6db833 | 406 | |
35d2e7ec | 407 | return ret; |
8c6db833 LP |
408 | } |
409 | ||
246aa6dd | 410 | int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem) { |
246aa6dd | 411 | _cleanup_closedir_ DIR *d = NULL; |
7027ff61 | 412 | int r, ret = 0; |
35d2e7ec | 413 | char *fn; |
8c6db833 | 414 | |
246aa6dd LP |
415 | assert(cfrom); |
416 | assert(pfrom); | |
417 | assert(cto); | |
418 | assert(pto); | |
8c6db833 | 419 | |
246aa6dd | 420 | ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self); |
8c6db833 | 421 | |
246aa6dd LP |
422 | r = cg_enumerate_subgroups(cfrom, pfrom, &d); |
423 | if (r < 0) { | |
4c633005 | 424 | if (ret >= 0 && r != -ENOENT) |
7027ff61 LP |
425 | return r; |
426 | ||
246aa6dd | 427 | return ret; |
35d2e7ec LP |
428 | } |
429 | ||
430 | while ((r = cg_read_subgroup(d, &fn)) > 0) { | |
246aa6dd | 431 | _cleanup_free_ char *p = NULL; |
8c6db833 | 432 | |
246aa6dd | 433 | p = strjoin(pfrom, "/", fn, NULL); |
35d2e7ec | 434 | free(fn); |
246aa6dd | 435 | if (!p) { |
35d2e7ec | 436 | if (ret >= 0) |
7027ff61 | 437 | return -ENOMEM; |
35d2e7ec | 438 | |
246aa6dd | 439 | return ret; |
8c6db833 LP |
440 | } |
441 | ||
246aa6dd | 442 | r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem); |
35d2e7ec LP |
443 | if (r != 0 && ret >= 0) |
444 | ret = r; | |
8c6db833 LP |
445 | } |
446 | ||
35d2e7ec LP |
447 | if (r < 0 && ret >= 0) |
448 | ret = r; | |
449 | ||
246aa6dd LP |
450 | if (rem) { |
451 | r = cg_rmdir(cfrom, pfrom, true); | |
452 | if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) | |
453 | return r; | |
454 | } | |
8c6db833 LP |
455 | |
456 | return ret; | |
457 | } | |
458 | ||
3474ae3c LP |
459 | static const char *normalize_controller(const char *controller) { |
460 | ||
7027ff61 LP |
461 | assert(controller); |
462 | ||
3474ae3c LP |
463 | if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) |
464 | return "systemd"; | |
465 | else if (startswith(controller, "name=")) | |
466 | return controller + 5; | |
467 | else | |
468 | return controller; | |
469 | } | |
470 | ||
471 | static int join_path(const char *controller, const char *path, const char *suffix, char **fs) { | |
018ef268 | 472 | char *t = NULL; |
3474ae3c | 473 | |
9444b1f2 LP |
474 | if (!isempty(controller)) { |
475 | if (!isempty(path) && !isempty(suffix)) | |
b7def684 | 476 | t = strjoin("/sys/fs/cgroup/", controller, "/", path, "/", suffix, NULL); |
9444b1f2 | 477 | else if (!isempty(path)) |
b7def684 | 478 | t = strjoin("/sys/fs/cgroup/", controller, "/", path, NULL); |
9444b1f2 | 479 | else if (!isempty(suffix)) |
b7def684 | 480 | t = strjoin("/sys/fs/cgroup/", controller, "/", suffix, NULL); |
c3175a7f | 481 | else |
7027ff61 | 482 | t = strappend("/sys/fs/cgroup/", controller); |
c3175a7f | 483 | } else { |
9444b1f2 | 484 | if (!isempty(path) && !isempty(suffix)) |
b7def684 | 485 | t = strjoin(path, "/", suffix, NULL); |
9444b1f2 | 486 | else if (!isempty(path)) |
c3175a7f | 487 | t = strdup(path); |
7027ff61 LP |
488 | else |
489 | return -EINVAL; | |
c3175a7f | 490 | } |
3474ae3c LP |
491 | |
492 | if (!t) | |
493 | return -ENOMEM; | |
494 | ||
495 | path_kill_slashes(t); | |
496 | ||
497 | *fs = t; | |
498 | return 0; | |
499 | } | |
500 | ||
8c6db833 | 501 | int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { |
dbd821ac | 502 | const char *p; |
0ac10822 | 503 | static __thread bool good = false; |
8c6db833 | 504 | |
dbd821ac LP |
505 | assert(fs); |
506 | ||
78edb35a LP |
507 | if (controller && !cg_controller_is_valid(controller, true)) |
508 | return -EINVAL; | |
509 | ||
3bfc7184 | 510 | if (_unlikely_(!good)) { |
70132bd0 LP |
511 | int r; |
512 | ||
0c85a4f3 | 513 | r = path_is_mount_point("/sys/fs/cgroup", false); |
70132bd0 LP |
514 | if (r <= 0) |
515 | return r < 0 ? r : -ENOENT; | |
516 | ||
517 | /* Cache this to save a few stat()s */ | |
518 | good = true; | |
519 | } | |
520 | ||
c3175a7f | 521 | p = controller ? normalize_controller(controller) : NULL; |
7027ff61 | 522 | |
3474ae3c LP |
523 | return join_path(p, path, suffix, fs); |
524 | } | |
dbd821ac | 525 | |
7027ff61 | 526 | static int check_hierarchy(const char *p) { |
37099707 LP |
527 | char *cc; |
528 | ||
529 | assert(p); | |
530 | ||
531 | /* Check if this controller actually really exists */ | |
532 | cc = alloca(sizeof("/sys/fs/cgroup/") + strlen(p)); | |
533 | strcpy(stpcpy(cc, "/sys/fs/cgroup/"), p); | |
534 | if (access(cc, F_OK) < 0) | |
535 | return -errno; | |
536 | ||
537 | return 0; | |
538 | } | |
539 | ||
3474ae3c LP |
540 | int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) { |
541 | const char *p; | |
37099707 | 542 | int r; |
dbd821ac | 543 | |
3474ae3c | 544 | assert(fs); |
70132bd0 | 545 | |
78edb35a | 546 | if (!cg_controller_is_valid(controller, true)) |
3474ae3c | 547 | return -EINVAL; |
70132bd0 | 548 | |
37099707 | 549 | /* Normalize the controller syntax */ |
3474ae3c | 550 | p = normalize_controller(controller); |
8c6db833 | 551 | |
3474ae3c | 552 | /* Check if this controller actually really exists */ |
7027ff61 | 553 | r = check_hierarchy(p); |
37099707 LP |
554 | if (r < 0) |
555 | return r; | |
3474ae3c LP |
556 | |
557 | return join_path(p, path, suffix, fs); | |
8c6db833 LP |
558 | } |
559 | ||
e27796a0 LP |
560 | static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { |
561 | char *p; | |
562 | bool is_sticky; | |
563 | ||
564 | if (typeflag != FTW_DP) | |
565 | return 0; | |
566 | ||
567 | if (ftwbuf->level < 1) | |
568 | return 0; | |
569 | ||
b043cd0b LP |
570 | p = strappend(path, "/cgroup.procs"); |
571 | if (!p) { | |
572 | errno = ENOMEM; | |
573 | return 1; | |
574 | } | |
575 | ||
576 | is_sticky = file_is_priv_sticky(p) > 0; | |
577 | free(p); | |
578 | ||
579 | if (is_sticky) | |
580 | return 0; | |
581 | ||
582 | /* Compatibility */ | |
e27796a0 LP |
583 | p = strappend(path, "/tasks"); |
584 | if (!p) { | |
585 | errno = ENOMEM; | |
586 | return 1; | |
587 | } | |
588 | ||
8d53b453 | 589 | is_sticky = file_is_priv_sticky(p) > 0; |
e27796a0 LP |
590 | free(p); |
591 | ||
592 | if (is_sticky) | |
593 | return 0; | |
594 | ||
595 | rmdir(path); | |
596 | return 0; | |
597 | } | |
598 | ||
8c6db833 | 599 | int cg_trim(const char *controller, const char *path, bool delete_root) { |
7027ff61 | 600 | _cleanup_free_ char *fs = NULL; |
e27796a0 | 601 | int r = 0; |
8c6db833 | 602 | |
8c6db833 LP |
603 | assert(path); |
604 | ||
e27796a0 LP |
605 | r = cg_get_path(controller, path, NULL, &fs); |
606 | if (r < 0) | |
8c6db833 LP |
607 | return r; |
608 | ||
e27796a0 | 609 | errno = 0; |
7027ff61 | 610 | if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) |
e27796a0 LP |
611 | r = errno ? -errno : -EIO; |
612 | ||
613 | if (delete_root) { | |
614 | bool is_sticky; | |
615 | char *p; | |
616 | ||
b043cd0b | 617 | p = strappend(fs, "/cgroup.procs"); |
7027ff61 | 618 | if (!p) |
e27796a0 | 619 | return -ENOMEM; |
e27796a0 | 620 | |
8d53b453 | 621 | is_sticky = file_is_priv_sticky(p) > 0; |
e27796a0 LP |
622 | free(p); |
623 | ||
b043cd0b LP |
624 | if (!is_sticky) { |
625 | p = strappend(fs, "/tasks"); | |
626 | if (!p) | |
627 | return -ENOMEM; | |
628 | ||
629 | is_sticky = file_is_priv_sticky(p) > 0; | |
630 | free(p); | |
631 | } | |
632 | ||
e27796a0 | 633 | if (!is_sticky) |
7027ff61 LP |
634 | if (rmdir(fs) < 0 && errno != ENOENT && r == 0) |
635 | return -errno; | |
e27796a0 LP |
636 | } |
637 | ||
e27796a0 | 638 | return r; |
8c6db833 LP |
639 | } |
640 | ||
641 | int cg_delete(const char *controller, const char *path) { | |
7027ff61 | 642 | _cleanup_free_ char *parent = NULL; |
8c6db833 LP |
643 | int r; |
644 | ||
8c6db833 LP |
645 | assert(path); |
646 | ||
7027ff61 LP |
647 | r = path_get_parent(path, &parent); |
648 | if (r < 0) | |
35d2e7ec | 649 | return r; |
8c6db833 | 650 | |
246aa6dd | 651 | r = cg_migrate_recursive(controller, path, controller, parent, false, true); |
4c633005 | 652 | return r == -ENOENT ? 0 : r; |
8c6db833 LP |
653 | } |
654 | ||
8c6db833 | 655 | int cg_attach(const char *controller, const char *path, pid_t pid) { |
574d5f2d LP |
656 | _cleanup_free_ char *fs = NULL; |
657 | char c[DECIMAL_STR_MAX(pid_t) + 2]; | |
8c6db833 LP |
658 | int r; |
659 | ||
8c6db833 LP |
660 | assert(path); |
661 | assert(pid >= 0); | |
662 | ||
b043cd0b | 663 | r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs); |
3474ae3c | 664 | if (r < 0) |
c6c18be3 | 665 | return r; |
8c6db833 LP |
666 | |
667 | if (pid == 0) | |
668 | pid = getpid(); | |
669 | ||
c6c18be3 | 670 | snprintf(c, sizeof(c), "%lu\n", (unsigned long) pid); |
8c6db833 | 671 | |
574d5f2d | 672 | return write_string_file(fs, c); |
8c6db833 LP |
673 | } |
674 | ||
2d76d14e LP |
675 | int cg_set_group_access( |
676 | const char *controller, | |
677 | const char *path, | |
678 | mode_t mode, | |
679 | uid_t uid, | |
680 | gid_t gid) { | |
681 | ||
574d5f2d | 682 | _cleanup_free_ char *fs = NULL; |
8c6db833 LP |
683 | int r; |
684 | ||
8c6db833 LP |
685 | assert(path); |
686 | ||
8d53b453 LP |
687 | if (mode != (mode_t) -1) |
688 | mode &= 0777; | |
689 | ||
690 | r = cg_get_path(controller, path, NULL, &fs); | |
691 | if (r < 0) | |
8c6db833 LP |
692 | return r; |
693 | ||
574d5f2d | 694 | return chmod_and_chown(fs, mode, uid, gid); |
8c6db833 LP |
695 | } |
696 | ||
974efc46 LP |
697 | int cg_set_task_access( |
698 | const char *controller, | |
699 | const char *path, | |
700 | mode_t mode, | |
701 | uid_t uid, | |
702 | gid_t gid, | |
703 | int sticky) { | |
704 | ||
705 | _cleanup_free_ char *fs = NULL, *procs = NULL; | |
8c6db833 LP |
706 | int r; |
707 | ||
8c6db833 LP |
708 | assert(path); |
709 | ||
8d53b453 LP |
710 | if (mode == (mode_t) -1 && uid == (uid_t) -1 && gid == (gid_t) -1 && sticky < 0) |
711 | return 0; | |
712 | ||
713 | if (mode != (mode_t) -1) | |
714 | mode &= 0666; | |
715 | ||
b043cd0b | 716 | r = cg_get_path(controller, path, "cgroup.procs", &fs); |
8d53b453 | 717 | if (r < 0) |
8c6db833 LP |
718 | return r; |
719 | ||
8d53b453 LP |
720 | if (sticky >= 0 && mode != (mode_t) -1) |
721 | /* Both mode and sticky param are passed */ | |
722 | mode |= (sticky ? S_ISVTX : 0); | |
723 | else if ((sticky >= 0 && mode == (mode_t) -1) || | |
724 | (mode != (mode_t) -1 && sticky < 0)) { | |
725 | struct stat st; | |
726 | ||
727 | /* Only one param is passed, hence read the current | |
728 | * mode from the file itself */ | |
729 | ||
730 | r = lstat(fs, &st); | |
974efc46 | 731 | if (r < 0) |
8d53b453 | 732 | return -errno; |
8d53b453 LP |
733 | |
734 | if (mode == (mode_t) -1) | |
735 | /* No mode set, we just shall set the sticky bit */ | |
736 | mode = (st.st_mode & ~S_ISVTX) | (sticky ? S_ISVTX : 0); | |
737 | else | |
738 | /* Only mode set, leave sticky bit untouched */ | |
739 | mode = (st.st_mode & ~0777) | mode; | |
740 | } | |
741 | ||
8c6db833 | 742 | r = chmod_and_chown(fs, mode, uid, gid); |
974efc46 LP |
743 | if (r < 0) |
744 | return r; | |
8c6db833 | 745 | |
b043cd0b LP |
746 | /* Compatibility, Always keep values for "tasks" in sync with |
747 | * "cgroup.procs" */ | |
748 | r = cg_get_path(controller, path, "tasks", &procs); | |
974efc46 LP |
749 | if (r < 0) |
750 | return r; | |
751 | ||
752 | return chmod_and_chown(procs, mode, uid, gid); | |
8c6db833 LP |
753 | } |
754 | ||
7027ff61 | 755 | int cg_pid_get_path(const char *controller, pid_t pid, char **path) { |
7027ff61 LP |
756 | _cleanup_fclose_ FILE *f = NULL; |
757 | char line[LINE_MAX]; | |
8af8afd6 | 758 | const char *fs; |
c6c18be3 | 759 | size_t cs; |
8c6db833 | 760 | |
8c6db833 | 761 | assert(path); |
c6c18be3 | 762 | assert(pid >= 0); |
8c6db833 | 763 | |
8af8afd6 LP |
764 | if (controller) { |
765 | if (!cg_controller_is_valid(controller, true)) | |
766 | return -EINVAL; | |
78edb35a | 767 | |
8af8afd6 LP |
768 | controller = normalize_controller(controller); |
769 | } else | |
7027ff61 LP |
770 | controller = SYSTEMD_CGROUP_CONTROLLER; |
771 | ||
c6c18be3 | 772 | if (pid == 0) |
8af8afd6 LP |
773 | fs = "/proc/self/cgroup"; |
774 | else | |
775 | fs = procfs_file_alloca(pid, "cgroup"); | |
8c6db833 | 776 | |
c6c18be3 | 777 | f = fopen(fs, "re"); |
4c633005 LP |
778 | if (!f) |
779 | return errno == ENOENT ? -ESRCH : -errno; | |
780 | ||
c6c18be3 LP |
781 | cs = strlen(controller); |
782 | ||
7027ff61 | 783 | FOREACH_LINE(line, f, return -errno) { |
8af8afd6 LP |
784 | char *l, *p, *w, *e; |
785 | size_t k; | |
786 | char *state; | |
787 | bool found = false; | |
c6c18be3 LP |
788 | |
789 | truncate_nl(line); | |
790 | ||
7027ff61 LP |
791 | l = strchr(line, ':'); |
792 | if (!l) | |
c6c18be3 LP |
793 | continue; |
794 | ||
795 | l++; | |
8af8afd6 LP |
796 | e = strchr(l, ':'); |
797 | if (!e) | |
c6c18be3 LP |
798 | continue; |
799 | ||
8af8afd6 LP |
800 | *e = 0; |
801 | ||
802 | FOREACH_WORD_SEPARATOR(w, k, l, ",", state) { | |
803 | ||
804 | if (k == cs && memcmp(w, controller, cs) == 0) { | |
805 | found = true; | |
806 | break; | |
807 | } | |
808 | ||
809 | if (k == 5 + cs && | |
810 | memcmp(w, "name=", 5) == 0 && | |
811 | memcmp(w+5, controller, cs) == 0) { | |
812 | found = true; | |
813 | break; | |
814 | } | |
815 | } | |
816 | ||
817 | if (!found) | |
c6c18be3 LP |
818 | continue; |
819 | ||
8af8afd6 | 820 | p = strdup(e + 1); |
7027ff61 LP |
821 | if (!p) |
822 | return -ENOMEM; | |
c6c18be3 LP |
823 | |
824 | *path = p; | |
7027ff61 | 825 | return 0; |
c6c18be3 LP |
826 | } |
827 | ||
7027ff61 | 828 | return -ENOENT; |
8c6db833 LP |
829 | } |
830 | ||
831 | int cg_install_release_agent(const char *controller, const char *agent) { | |
7027ff61 LP |
832 | _cleanup_free_ char *fs = NULL, *contents = NULL; |
833 | char *sc; | |
8c6db833 LP |
834 | int r; |
835 | ||
8c6db833 LP |
836 | assert(agent); |
837 | ||
7027ff61 LP |
838 | r = cg_get_path(controller, NULL, "release_agent", &fs); |
839 | if (r < 0) | |
c6c18be3 | 840 | return r; |
8c6db833 | 841 | |
7027ff61 LP |
842 | r = read_one_line_file(fs, &contents); |
843 | if (r < 0) | |
844 | return r; | |
8c6db833 LP |
845 | |
846 | sc = strstrip(contents); | |
8c6db833 | 847 | if (sc[0] == 0) { |
7027ff61 | 848 | r = write_string_file(fs, agent); |
574d5f2d | 849 | if (r < 0) |
7027ff61 LP |
850 | return r; |
851 | } else if (!streq(sc, agent)) | |
852 | return -EEXIST; | |
8c6db833 | 853 | |
c6c18be3 LP |
854 | free(fs); |
855 | fs = NULL; | |
7027ff61 LP |
856 | r = cg_get_path(controller, NULL, "notify_on_release", &fs); |
857 | if (r < 0) | |
858 | return r; | |
8c6db833 LP |
859 | |
860 | free(contents); | |
861 | contents = NULL; | |
7027ff61 LP |
862 | r = read_one_line_file(fs, &contents); |
863 | if (r < 0) | |
864 | return r; | |
8c6db833 LP |
865 | |
866 | sc = strstrip(contents); | |
8c6db833 | 867 | if (streq(sc, "0")) { |
7027ff61 LP |
868 | r = write_string_file(fs, "1"); |
869 | if (r < 0) | |
870 | return r; | |
c6c18be3 | 871 | |
7027ff61 LP |
872 | return 1; |
873 | } | |
8c6db833 | 874 | |
7027ff61 LP |
875 | if (!streq(sc, "1")) |
876 | return -EIO; | |
8c6db833 | 877 | |
7027ff61 | 878 | return 0; |
8c6db833 LP |
879 | } |
880 | ||
881 | int cg_is_empty(const char *controller, const char *path, bool ignore_self) { | |
7027ff61 | 882 | _cleanup_fclose_ FILE *f = NULL; |
c3175a7f | 883 | pid_t pid = 0, self_pid; |
c6c18be3 | 884 | bool found = false; |
7027ff61 | 885 | int r; |
8c6db833 | 886 | |
8c6db833 LP |
887 | assert(path); |
888 | ||
b043cd0b | 889 | r = cg_enumerate_processes(controller, path, &f); |
c3175a7f | 890 | if (r < 0) |
4c633005 | 891 | return r == -ENOENT ? 1 : r; |
8c6db833 | 892 | |
c3175a7f LP |
893 | self_pid = getpid(); |
894 | ||
c6c18be3 | 895 | while ((r = cg_read_pid(f, &pid)) > 0) { |
8c6db833 | 896 | |
c3175a7f | 897 | if (ignore_self && pid == self_pid) |
c6c18be3 | 898 | continue; |
8c6db833 | 899 | |
c6c18be3 LP |
900 | found = true; |
901 | break; | |
8c6db833 LP |
902 | } |
903 | ||
c6c18be3 LP |
904 | if (r < 0) |
905 | return r; | |
8c6db833 | 906 | |
c6c18be3 | 907 | return !found; |
8c6db833 LP |
908 | } |
909 | ||
b08121d0 | 910 | int cg_is_empty_by_spec(const char *spec, bool ignore_self) { |
b08121d0 | 911 | _cleanup_free_ char *controller = NULL, *path = NULL; |
7027ff61 | 912 | int r; |
b08121d0 LP |
913 | |
914 | assert(spec); | |
915 | ||
916 | r = cg_split_spec(spec, &controller, &path); | |
917 | if (r < 0) | |
918 | return r; | |
919 | ||
920 | return cg_is_empty(controller, path, ignore_self); | |
921 | } | |
922 | ||
8c6db833 | 923 | int cg_is_empty_recursive(const char *controller, const char *path, bool ignore_self) { |
7027ff61 | 924 | _cleanup_closedir_ DIR *d = NULL; |
35d2e7ec | 925 | char *fn; |
7027ff61 | 926 | int r; |
8c6db833 | 927 | |
8c6db833 LP |
928 | assert(path); |
929 | ||
c3175a7f LP |
930 | r = cg_is_empty(controller, path, ignore_self); |
931 | if (r <= 0) | |
35d2e7ec LP |
932 | return r; |
933 | ||
c3175a7f LP |
934 | r = cg_enumerate_subgroups(controller, path, &d); |
935 | if (r < 0) | |
4c633005 | 936 | return r == -ENOENT ? 1 : r; |
8c6db833 | 937 | |
35d2e7ec | 938 | while ((r = cg_read_subgroup(d, &fn)) > 0) { |
7027ff61 | 939 | _cleanup_free_ char *p = NULL; |
8c6db833 | 940 | |
7027ff61 | 941 | p = strjoin(path, "/", fn, NULL); |
35d2e7ec | 942 | free(fn); |
7027ff61 LP |
943 | if (!p) |
944 | return -ENOMEM; | |
8c6db833 | 945 | |
35d2e7ec | 946 | r = cg_is_empty_recursive(controller, p, ignore_self); |
35d2e7ec | 947 | if (r <= 0) |
7027ff61 | 948 | return r; |
35d2e7ec LP |
949 | } |
950 | ||
7027ff61 LP |
951 | if (r < 0) |
952 | return r; | |
35d2e7ec | 953 | |
7027ff61 | 954 | return 1; |
35d2e7ec LP |
955 | } |
956 | ||
957 | int cg_split_spec(const char *spec, char **controller, char **path) { | |
958 | const char *e; | |
959 | char *t = NULL, *u = NULL; | |
5954c074 | 960 | _cleanup_free_ char *v = NULL; |
35d2e7ec LP |
961 | |
962 | assert(spec); | |
35d2e7ec LP |
963 | |
964 | if (*spec == '/') { | |
e884315e LP |
965 | if (!path_is_safe(spec)) |
966 | return -EINVAL; | |
35d2e7ec LP |
967 | |
968 | if (path) { | |
246aa6dd LP |
969 | t = strdup(spec); |
970 | if (!t) | |
35d2e7ec LP |
971 | return -ENOMEM; |
972 | ||
5954c074 | 973 | path_kill_slashes(t); |
35d2e7ec | 974 | *path = t; |
8c6db833 LP |
975 | } |
976 | ||
35d2e7ec LP |
977 | if (controller) |
978 | *controller = NULL; | |
979 | ||
980 | return 0; | |
8c6db833 LP |
981 | } |
982 | ||
246aa6dd LP |
983 | e = strchr(spec, ':'); |
984 | if (!e) { | |
78edb35a | 985 | if (!cg_controller_is_valid(spec, true)) |
35d2e7ec LP |
986 | return -EINVAL; |
987 | ||
988 | if (controller) { | |
5954c074 | 989 | t = strdup(normalize_controller(spec)); |
246aa6dd | 990 | if (!t) |
35d2e7ec LP |
991 | return -ENOMEM; |
992 | ||
993 | *controller = t; | |
994 | } | |
995 | ||
996 | if (path) | |
997 | *path = NULL; | |
998 | ||
999 | return 0; | |
8c6db833 LP |
1000 | } |
1001 | ||
5954c074 LP |
1002 | v = strndup(spec, e-spec); |
1003 | if (!v) | |
1004 | return -ENOMEM; | |
1005 | t = strdup(normalize_controller(v)); | |
e884315e LP |
1006 | if (!t) |
1007 | return -ENOMEM; | |
78edb35a | 1008 | if (!cg_controller_is_valid(t, true)) { |
e884315e | 1009 | free(t); |
35d2e7ec | 1010 | return -EINVAL; |
246aa6dd LP |
1011 | } |
1012 | ||
e884315e LP |
1013 | u = strdup(e+1); |
1014 | if (!u) { | |
1015 | free(t); | |
1016 | return -ENOMEM; | |
1017 | } | |
5954c074 LP |
1018 | if (!path_is_safe(u) || |
1019 | !path_is_absolute(u)) { | |
e884315e LP |
1020 | free(t); |
1021 | free(u); | |
1022 | return -EINVAL; | |
246aa6dd | 1023 | } |
35d2e7ec | 1024 | |
5954c074 LP |
1025 | path_kill_slashes(u); |
1026 | ||
35d2e7ec LP |
1027 | if (controller) |
1028 | *controller = t; | |
e884315e LP |
1029 | else |
1030 | free(t); | |
35d2e7ec LP |
1031 | |
1032 | if (path) | |
1033 | *path = u; | |
e884315e LP |
1034 | else |
1035 | free(u); | |
35d2e7ec LP |
1036 | |
1037 | return 0; | |
8c6db833 | 1038 | } |
c6c18be3 | 1039 | |
35d2e7ec | 1040 | int cg_join_spec(const char *controller, const char *path, char **spec) { |
7027ff61 LP |
1041 | char *s; |
1042 | ||
35d2e7ec | 1043 | assert(path); |
c6c18be3 | 1044 | |
7027ff61 LP |
1045 | if (!controller) |
1046 | controller = "systemd"; | |
78edb35a LP |
1047 | else { |
1048 | if (!cg_controller_is_valid(controller, true)) | |
1049 | return -EINVAL; | |
1050 | ||
1051 | controller = normalize_controller(controller); | |
1052 | } | |
35d2e7ec | 1053 | |
7027ff61 LP |
1054 | if (!path_is_absolute(path)) |
1055 | return -EINVAL; | |
1056 | ||
7027ff61 LP |
1057 | s = strjoin(controller, ":", path, NULL); |
1058 | if (!s) | |
35d2e7ec | 1059 | return -ENOMEM; |
c6c18be3 | 1060 | |
5954c074 LP |
1061 | path_kill_slashes(s + strlen(controller) + 1); |
1062 | ||
7027ff61 | 1063 | *spec = s; |
c6c18be3 LP |
1064 | return 0; |
1065 | } | |
35d2e7ec | 1066 | |
7027ff61 | 1067 | int cg_mangle_path(const char *path, char **result) { |
78edb35a LP |
1068 | _cleanup_free_ char *c = NULL, *p = NULL; |
1069 | char *t; | |
35d2e7ec LP |
1070 | int r; |
1071 | ||
1072 | assert(path); | |
1073 | assert(result); | |
1074 | ||
1075 | /* First check if it already is a filesystem path */ | |
7027ff61 | 1076 | if (path_startswith(path, "/sys/fs/cgroup")) { |
35d2e7ec | 1077 | |
b69d29ce LP |
1078 | t = strdup(path); |
1079 | if (!t) | |
35d2e7ec LP |
1080 | return -ENOMEM; |
1081 | ||
5954c074 | 1082 | path_kill_slashes(t); |
35d2e7ec LP |
1083 | *result = t; |
1084 | return 0; | |
1085 | } | |
1086 | ||
1087 | /* Otherwise treat it as cg spec */ | |
b69d29ce LP |
1088 | r = cg_split_spec(path, &c, &p); |
1089 | if (r < 0) | |
35d2e7ec LP |
1090 | return r; |
1091 | ||
78edb35a | 1092 | return cg_get_path(c ? c : SYSTEMD_CGROUP_CONTROLLER, p ? p : "/", NULL, result); |
35d2e7ec | 1093 | } |
1f73f0f1 | 1094 | |
7027ff61 | 1095 | int cg_get_root_path(char **path) { |
9444b1f2 | 1096 | char *p, *e; |
7027ff61 LP |
1097 | int r; |
1098 | ||
1099 | assert(path); | |
1100 | ||
9444b1f2 | 1101 | r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); |
7027ff61 LP |
1102 | if (r < 0) |
1103 | return r; | |
1104 | ||
9444b1f2 LP |
1105 | e = endswith(p, "/" SPECIAL_SYSTEM_SLICE); |
1106 | if (e) | |
7027ff61 LP |
1107 | *e = 0; |
1108 | ||
1f73f0f1 LP |
1109 | *path = p; |
1110 | return 0; | |
1111 | } | |
b59e2465 LP |
1112 | |
1113 | char **cg_shorten_controllers(char **controllers) { | |
1114 | char **f, **t; | |
1115 | ||
b59e2465 LP |
1116 | if (!controllers) |
1117 | return controllers; | |
1118 | ||
1119 | for (f = controllers, t = controllers; *f; f++) { | |
37099707 | 1120 | const char *p; |
7027ff61 LP |
1121 | int r; |
1122 | ||
1123 | p = normalize_controller(*f); | |
b59e2465 | 1124 | |
78edb35a LP |
1125 | if (streq(p, "systemd")) { |
1126 | free(*f); | |
1127 | continue; | |
1128 | } | |
1129 | ||
1130 | if (!cg_controller_is_valid(p, true)) { | |
1131 | log_warning("Controller %s is not valid, removing from controllers list.", p); | |
b59e2465 LP |
1132 | free(*f); |
1133 | continue; | |
1134 | } | |
1135 | ||
7027ff61 | 1136 | r = check_hierarchy(p); |
37099707 | 1137 | if (r < 0) { |
78edb35a | 1138 | log_debug("Controller %s is not available, removing from controllers list.", p); |
b59e2465 LP |
1139 | free(*f); |
1140 | continue; | |
1141 | } | |
1142 | ||
1143 | *(t++) = *f; | |
1144 | } | |
1145 | ||
1146 | *t = NULL; | |
6c03089c | 1147 | return strv_uniq(controllers); |
b59e2465 | 1148 | } |
ba1261bc | 1149 | |
7027ff61 LP |
1150 | int cg_pid_get_path_shifted(pid_t pid, char **root, char **cgroup) { |
1151 | _cleanup_free_ char *cg_root = NULL; | |
1152 | char *cg_process, *p; | |
ba1261bc LP |
1153 | int r; |
1154 | ||
7027ff61 | 1155 | r = cg_get_root_path(&cg_root); |
ba1261bc LP |
1156 | if (r < 0) |
1157 | return r; | |
1158 | ||
7027ff61 LP |
1159 | r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cg_process); |
1160 | if (r < 0) | |
ba1261bc | 1161 | return r; |
ba1261bc | 1162 | |
7027ff61 LP |
1163 | p = path_startswith(cg_process, cg_root); |
1164 | if (p) | |
1165 | p--; | |
1166 | else | |
1167 | p = cg_process; | |
ba1261bc LP |
1168 | |
1169 | if (cgroup) { | |
1170 | char* c; | |
1171 | ||
1172 | c = strdup(p); | |
1173 | if (!c) { | |
1174 | free(cg_process); | |
1175 | return -ENOMEM; | |
1176 | } | |
1177 | ||
1178 | *cgroup = c; | |
1179 | } | |
1180 | ||
1181 | if (root) { | |
1182 | cg_process[p-cg_process] = 0; | |
1183 | *root = cg_process; | |
1184 | } else | |
1185 | free(cg_process); | |
1186 | ||
1187 | return 0; | |
1188 | } | |
1189 | ||
7027ff61 | 1190 | int cg_path_decode_unit(const char *cgroup, char **unit){ |
6c03089c | 1191 | char *p, *e, *c, *s, *k; |
ef1673d1 MT |
1192 | |
1193 | assert(cgroup); | |
6c03089c | 1194 | assert(unit); |
ef1673d1 | 1195 | |
6c03089c LP |
1196 | e = strchrnul(cgroup, '/'); |
1197 | c = strndupa(cgroup, e - cgroup); | |
ae018d9b | 1198 | c = cg_unescape(c); |
ef1673d1 | 1199 | |
6c03089c LP |
1200 | /* Could this be a valid unit name? */ |
1201 | if (!unit_name_is_valid(c, true)) | |
1202 | return -EINVAL; | |
ef1673d1 | 1203 | |
6c03089c LP |
1204 | if (!unit_name_is_template(c)) |
1205 | s = strdup(c); | |
1206 | else { | |
1207 | if (*e != '/') | |
96cde13a | 1208 | return -EINVAL; |
ef1673d1 | 1209 | |
6c03089c | 1210 | e += strspn(e, "/"); |
ae018d9b | 1211 | |
6c03089c | 1212 | p = strchrnul(e, '/'); |
ae018d9b LP |
1213 | k = strndupa(e, p - e); |
1214 | k = cg_unescape(k); | |
ef1673d1 | 1215 | |
ae018d9b | 1216 | if (!unit_name_is_valid(k, false)) |
6c03089c LP |
1217 | return -EINVAL; |
1218 | ||
ae018d9b | 1219 | s = strdup(k); |
ef1673d1 MT |
1220 | } |
1221 | ||
6c03089c LP |
1222 | if (!s) |
1223 | return -ENOMEM; | |
1224 | ||
1225 | *unit = s; | |
ef1673d1 MT |
1226 | return 0; |
1227 | } | |
1228 | ||
9444b1f2 LP |
1229 | static const char *skip_slices(const char *p) { |
1230 | size_t n; | |
1231 | ||
1232 | /* Skips over all slice assignments */ | |
1233 | ||
1234 | for (;;) { | |
1235 | p += strspn(p, "/"); | |
1236 | ||
1237 | n = strcspn(p, "/"); | |
1238 | if (n <= 6 || memcmp(p + n - 6, ".slice", 6) != 0) | |
1239 | return p; | |
1240 | ||
1241 | p += n; | |
1242 | } | |
1243 | } | |
1244 | ||
6c03089c LP |
1245 | int cg_path_get_unit(const char *path, char **unit) { |
1246 | const char *e; | |
1247 | ||
1248 | assert(path); | |
1249 | assert(unit); | |
1250 | ||
9444b1f2 | 1251 | e = skip_slices(path); |
6c03089c | 1252 | |
7027ff61 | 1253 | return cg_path_decode_unit(e, unit); |
6c03089c LP |
1254 | } |
1255 | ||
1256 | int cg_pid_get_unit(pid_t pid, char **unit) { | |
7fd1b19b | 1257 | _cleanup_free_ char *cgroup = NULL; |
ba1261bc | 1258 | int r; |
ba1261bc | 1259 | |
ef1673d1 MT |
1260 | assert(unit); |
1261 | ||
7027ff61 | 1262 | r = cg_pid_get_path_shifted(pid, NULL, &cgroup); |
ef1673d1 MT |
1263 | if (r < 0) |
1264 | return r; | |
1265 | ||
6c03089c LP |
1266 | return cg_path_get_unit(cgroup, unit); |
1267 | } | |
ef1673d1 | 1268 | |
9444b1f2 LP |
1269 | static const char *skip_user(const char *p) { |
1270 | size_t n; | |
ef1673d1 | 1271 | |
9444b1f2 LP |
1272 | assert(p); |
1273 | ||
1274 | p += strspn(p, "/"); | |
1275 | ||
1276 | n = strcspn(p, "/"); | |
1277 | if (n <= 5 || memcmp(p + n - 5, ".user", 5) != 0) | |
1278 | return p; | |
1279 | ||
1280 | p += n; | |
1281 | p += strspn(p, "/"); | |
1282 | ||
1283 | return p; | |
1284 | } | |
1285 | ||
1286 | static const char *skip_session(const char *p) { | |
1287 | size_t n; | |
1288 | ||
1289 | assert(p); | |
1290 | ||
1291 | p += strspn(p, "/"); | |
1292 | ||
1293 | n = strcspn(p, "/"); | |
1294 | if (n <= 8 || memcmp(p + n - 8, ".session", 8) != 0) | |
6c03089c | 1295 | return NULL; |
ef1673d1 | 1296 | |
9444b1f2 LP |
1297 | p += n; |
1298 | p += strspn(p, "/"); | |
1299 | ||
1300 | return p; | |
1301 | } | |
1302 | ||
1303 | static const char *skip_systemd_label(const char *p) { | |
1304 | size_t n; | |
1305 | ||
1306 | assert(p); | |
1307 | ||
1308 | p += strspn(p, "/"); | |
1309 | ||
1310 | n = strcspn(p, "/"); | |
1311 | if (n < 8 || memcmp(p, "systemd-", 8) != 0) | |
1312 | return p; | |
1313 | ||
1314 | p += n; | |
1315 | p += strspn(p, "/"); | |
1316 | ||
1317 | return p; | |
ef1673d1 MT |
1318 | } |
1319 | ||
6c03089c LP |
1320 | int cg_path_get_user_unit(const char *path, char **unit) { |
1321 | const char *e; | |
ef1673d1 | 1322 | |
6c03089c | 1323 | assert(path); |
ba1261bc LP |
1324 | assert(unit); |
1325 | ||
6c03089c LP |
1326 | /* We always have to parse the path from the beginning as unit |
1327 | * cgroups might have arbitrary child cgroups and we shouldn't get | |
1328 | * confused by those */ | |
ba1261bc | 1329 | |
9444b1f2 LP |
1330 | /* Skip slices, if there are any */ |
1331 | e = skip_slices(path); | |
ba1261bc | 1332 | |
9444b1f2 LP |
1333 | /* Skip the user name, if there is one */ |
1334 | e = skip_user(e); | |
ba1261bc | 1335 | |
9444b1f2 LP |
1336 | /* Skip the session ID, require that there is one */ |
1337 | e = skip_session(e); | |
6c03089c LP |
1338 | if (!e) |
1339 | return -ENOENT; | |
1340 | ||
9444b1f2 LP |
1341 | /* Skip the systemd cgroup, if there is one */ |
1342 | e = skip_systemd_label(e); | |
6c03089c | 1343 | |
7027ff61 | 1344 | return cg_path_decode_unit(e, unit); |
ef1673d1 | 1345 | } |
ba1261bc | 1346 | |
ef1673d1 | 1347 | int cg_pid_get_user_unit(pid_t pid, char **unit) { |
7fd1b19b | 1348 | _cleanup_free_ char *cgroup = NULL; |
6c03089c LP |
1349 | int r; |
1350 | ||
1351 | assert(unit); | |
1352 | ||
7027ff61 | 1353 | r = cg_pid_get_path_shifted(pid, NULL, &cgroup); |
6c03089c LP |
1354 | if (r < 0) |
1355 | return r; | |
1356 | ||
1357 | return cg_path_get_user_unit(cgroup, unit); | |
ba1261bc | 1358 | } |
e884315e | 1359 | |
7027ff61 | 1360 | int cg_path_get_machine_name(const char *path, char **machine) { |
9444b1f2 | 1361 | const char *e, *n, *x; |
ae018d9b | 1362 | char *s, *r; |
7027ff61 LP |
1363 | |
1364 | assert(path); | |
1365 | assert(machine); | |
1366 | ||
9444b1f2 LP |
1367 | /* Skip slices, if there are any */ |
1368 | e = skip_slices(path); | |
7027ff61 LP |
1369 | |
1370 | n = strchrnul(e, '/'); | |
1371 | if (e == n) | |
1372 | return -ENOENT; | |
1373 | ||
ae018d9b | 1374 | s = strndupa(e, n - e); |
9444b1f2 LP |
1375 | s = cg_unescape(s); |
1376 | ||
1377 | x = endswith(s, ".machine"); | |
1378 | if (!x) | |
1379 | return -ENOENT; | |
7027ff61 | 1380 | |
9444b1f2 | 1381 | r = strndup(s, x - s); |
ae018d9b LP |
1382 | if (!r) |
1383 | return -ENOMEM; | |
aff38e74 | 1384 | |
ae018d9b | 1385 | *machine = r; |
7027ff61 LP |
1386 | return 0; |
1387 | } | |
1388 | ||
1389 | int cg_pid_get_machine_name(pid_t pid, char **machine) { | |
7fd1b19b | 1390 | _cleanup_free_ char *cgroup = NULL; |
7027ff61 LP |
1391 | int r; |
1392 | ||
1393 | assert(machine); | |
1394 | ||
1395 | r = cg_pid_get_path_shifted(pid, NULL, &cgroup); | |
1396 | if (r < 0) | |
1397 | return r; | |
1398 | ||
1399 | return cg_path_get_machine_name(cgroup, machine); | |
1400 | } | |
1401 | ||
1402 | int cg_path_get_session(const char *path, char **session) { | |
1403 | const char *e, *n; | |
1404 | char *s; | |
1405 | ||
1406 | assert(path); | |
1407 | assert(session); | |
1408 | ||
9444b1f2 LP |
1409 | /* Skip slices, if there are any */ |
1410 | e = skip_slices(path); | |
7027ff61 | 1411 | |
9444b1f2 LP |
1412 | /* Skip the user name, if there is one */ |
1413 | e = skip_user(e); | |
7027ff61 LP |
1414 | |
1415 | n = strchrnul(e, '/'); | |
ae018d9b | 1416 | if (n - e < 8) |
7027ff61 | 1417 | return -ENOENT; |
ae018d9b | 1418 | if (memcmp(n - 8, ".session", 8) != 0) |
7027ff61 LP |
1419 | return -ENOENT; |
1420 | ||
ae018d9b | 1421 | s = strndup(e, n - e - 8); |
7027ff61 LP |
1422 | if (!s) |
1423 | return -ENOMEM; | |
1424 | ||
1425 | *session = s; | |
1426 | return 0; | |
1427 | } | |
1428 | ||
1429 | int cg_pid_get_session(pid_t pid, char **session) { | |
7fd1b19b | 1430 | _cleanup_free_ char *cgroup = NULL; |
7027ff61 LP |
1431 | int r; |
1432 | ||
1433 | assert(session); | |
1434 | ||
1435 | r = cg_pid_get_path_shifted(pid, NULL, &cgroup); | |
1436 | if (r < 0) | |
1437 | return r; | |
1438 | ||
1439 | return cg_path_get_session(cgroup, session); | |
1440 | } | |
1441 | ||
ae018d9b LP |
1442 | int cg_path_get_owner_uid(const char *path, uid_t *uid) { |
1443 | const char *e, *n; | |
1444 | char *s; | |
1445 | ||
1446 | assert(path); | |
1447 | assert(uid); | |
1448 | ||
9444b1f2 LP |
1449 | /* Skip slices, if there are any */ |
1450 | e = skip_slices(path); | |
ae018d9b LP |
1451 | |
1452 | n = strchrnul(e, '/'); | |
1453 | if (n - e < 5) | |
1454 | return -ENOENT; | |
1455 | if (memcmp(n - 5, ".user", 5) != 0) | |
1456 | return -ENOENT; | |
1457 | ||
1458 | s = strndupa(e, n - e - 5); | |
1459 | if (!s) | |
1460 | return -ENOMEM; | |
1461 | ||
1462 | return parse_uid(s, uid); | |
1463 | } | |
1464 | ||
1465 | int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) { | |
1466 | _cleanup_free_ char *cgroup = NULL; | |
1467 | int r; | |
1468 | ||
1469 | assert(uid); | |
1470 | ||
1471 | r = cg_pid_get_path_shifted(pid, NULL, &cgroup); | |
1472 | if (r < 0) | |
1473 | return r; | |
1474 | ||
1475 | return cg_path_get_owner_uid(cgroup, uid); | |
1476 | } | |
1477 | ||
e884315e LP |
1478 | int cg_controller_from_attr(const char *attr, char **controller) { |
1479 | const char *dot; | |
1480 | char *c; | |
1481 | ||
1482 | assert(attr); | |
1483 | assert(controller); | |
1484 | ||
1485 | if (!filename_is_safe(attr)) | |
1486 | return -EINVAL; | |
1487 | ||
1488 | dot = strchr(attr, '.'); | |
1489 | if (!dot) { | |
1490 | *controller = NULL; | |
1491 | return 0; | |
1492 | } | |
1493 | ||
1494 | c = strndup(attr, dot - attr); | |
1495 | if (!c) | |
1496 | return -ENOMEM; | |
1497 | ||
78edb35a | 1498 | if (!cg_controller_is_valid(c, false)) { |
e884315e LP |
1499 | free(c); |
1500 | return -EINVAL; | |
1501 | } | |
1502 | ||
1503 | *controller = c; | |
1504 | return 1; | |
1505 | } | |
ae018d9b LP |
1506 | |
1507 | char *cg_escape(const char *p) { | |
1508 | bool need_prefix = false; | |
1509 | ||
1510 | /* This implements very minimal escaping for names to be used | |
1511 | * as file names in the cgroup tree: any name which might | |
1512 | * conflict with a kernel name or is prefixed with '_' is | |
1513 | * prefixed with a '_'. That way, when reading cgroup names it | |
1514 | * is sufficient to remove a single prefixing underscore if | |
1515 | * there is one. */ | |
1516 | ||
1517 | /* The return value of this function (unlike cg_unescape()) | |
1518 | * needs free()! */ | |
1519 | ||
a0ab5665 LP |
1520 | if (p[0] == 0 || |
1521 | p[0] == '_' || | |
1522 | p[0] == '.' || | |
1523 | streq(p, "notify_on_release") || | |
1524 | streq(p, "release_agent") || | |
1525 | streq(p, "tasks")) | |
ae018d9b LP |
1526 | need_prefix = true; |
1527 | else { | |
1528 | const char *dot; | |
1529 | ||
1530 | dot = strrchr(p, '.'); | |
1531 | if (dot) { | |
1532 | ||
1533 | if (dot - p == 6 && memcmp(p, "cgroup", 6) == 0) | |
1534 | need_prefix = true; | |
1535 | else { | |
1536 | char *n; | |
1537 | ||
1538 | n = strndupa(p, dot - p); | |
1539 | ||
1540 | if (check_hierarchy(n) >= 0) | |
1541 | need_prefix = true; | |
1542 | } | |
1543 | } | |
1544 | } | |
1545 | ||
1546 | if (need_prefix) | |
1547 | return strappend("_", p); | |
1548 | else | |
1549 | return strdup(p); | |
1550 | } | |
1551 | ||
1552 | char *cg_unescape(const char *p) { | |
1553 | assert(p); | |
1554 | ||
1555 | /* The return value of this function (unlike cg_escape()) | |
1556 | * doesn't need free()! */ | |
1557 | ||
1558 | if (p[0] == '_') | |
1559 | return (char*) p+1; | |
1560 | ||
1561 | return (char*) p; | |
1562 | } | |
78edb35a LP |
1563 | |
1564 | #define CONTROLLER_VALID \ | |
1565 | "0123456789" \ | |
1566 | "abcdefghijklmnopqrstuvwxyz" \ | |
1567 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ | |
1568 | "_" | |
1569 | ||
1570 | bool cg_controller_is_valid(const char *p, bool allow_named) { | |
1571 | const char *t, *s; | |
1572 | ||
1573 | if (!p) | |
1574 | return false; | |
1575 | ||
1576 | if (allow_named) { | |
1577 | s = startswith(p, "name="); | |
1578 | if (s) | |
1579 | p = s; | |
1580 | } | |
1581 | ||
1582 | if (*p == 0 || *p == '_') | |
1583 | return false; | |
1584 | ||
1585 | for (t = p; *t; t++) | |
1586 | if (!strchr(CONTROLLER_VALID, *t)) | |
1587 | return false; | |
1588 | ||
1589 | if (t - p > FILENAME_MAX) | |
1590 | return false; | |
1591 | ||
1592 | return true; | |
1593 | } | |
a016b922 LP |
1594 | |
1595 | int cg_slice_to_path(const char *unit, char **ret) { | |
1596 | _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL; | |
1597 | const char *dash; | |
1598 | ||
1599 | assert(unit); | |
1600 | assert(ret); | |
1601 | ||
1602 | if (!unit_name_is_valid(unit, false)) | |
1603 | return -EINVAL; | |
1604 | ||
1605 | if (!endswith(unit, ".slice")) | |
1606 | return -EINVAL; | |
1607 | ||
1608 | p = unit_name_to_prefix(unit); | |
1609 | if (!p) | |
1610 | return -ENOMEM; | |
1611 | ||
1612 | dash = strchr(p, '-'); | |
1613 | while (dash) { | |
1614 | _cleanup_free_ char *escaped = NULL; | |
1615 | char n[dash - p + sizeof(".slice")]; | |
1616 | ||
1617 | strcpy(stpncpy(n, p, dash - p), ".slice"); | |
1618 | ||
1619 | if (!unit_name_is_valid(n, false)) | |
1620 | return -EINVAL; | |
1621 | ||
1622 | escaped = cg_escape(n); | |
1623 | if (!escaped) | |
1624 | return -ENOMEM; | |
1625 | ||
1626 | if (!strextend(&s, escaped, "/", NULL)) | |
1627 | return -ENOMEM; | |
1628 | ||
1629 | dash = strchr(dash+1, '-'); | |
1630 | } | |
1631 | ||
1632 | e = cg_escape(unit); | |
1633 | if (!e) | |
1634 | return -ENOMEM; | |
1635 | ||
1636 | if (!strextend(&s, e, NULL)) | |
1637 | return -ENOMEM; | |
1638 | ||
1639 | *ret = s; | |
1640 | s = NULL; | |
1641 | ||
1642 | return 0; | |
1643 | } |