chiark / gitweb /
cgroup: get rid of MemorySoftLimit=
[elogind.git] / src / core / cgroup.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
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
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
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <fcntl.h>
23
24 #include "path-util.h"
25 #include "special.h"
26 #include "cgroup-util.h"
27 #include "cgroup.h"
28
29 void cgroup_context_init(CGroupContext *c) {
30         assert(c);
31
32         /* Initialize everything to the kernel defaults, assuming the
33          * structure is preinitialized to 0 */
34
35         c->cpu_shares = 1024;
36         c->memory_limit = (uint64_t) -1;
37         c->blockio_weight = 1000;
38 }
39
40 void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) {
41         assert(c);
42         assert(a);
43
44         LIST_REMOVE(CGroupDeviceAllow, device_allow, c->device_allow, a);
45         free(a->path);
46         free(a);
47 }
48
49 void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w) {
50         assert(c);
51         assert(w);
52
53         LIST_REMOVE(CGroupBlockIODeviceWeight, device_weights, c->blockio_device_weights, w);
54         free(w->path);
55         free(w);
56 }
57
58 void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) {
59         assert(c);
60         assert(b);
61
62         LIST_REMOVE(CGroupBlockIODeviceBandwidth, device_bandwidths, c->blockio_device_bandwidths, b);
63         free(b->path);
64         free(b);
65 }
66
67 void cgroup_context_done(CGroupContext *c) {
68         assert(c);
69
70         while (c->blockio_device_weights)
71                 cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights);
72
73         while (c->blockio_device_bandwidths)
74                 cgroup_context_free_blockio_device_bandwidth(c, c->blockio_device_bandwidths);
75
76         while (c->device_allow)
77                 cgroup_context_free_device_allow(c, c->device_allow);
78 }
79
80 void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
81         CGroupBlockIODeviceBandwidth *b;
82         CGroupBlockIODeviceWeight *w;
83         CGroupDeviceAllow *a;
84
85         assert(c);
86         assert(f);
87
88         prefix = strempty(prefix);
89
90         fprintf(f,
91                 "%sCPUAccounting=%s\n"
92                 "%sBlockIOAccounting=%s\n"
93                 "%sMemoryAccounting=%s\n"
94                 "%sCPUShares=%lu\n"
95                 "%sBlockIOWeight=%lu\n"
96                 "%sMemoryLimit=%" PRIu64 "\n"
97                 "%sDevicePolicy=%s\n",
98                 prefix, yes_no(c->cpu_accounting),
99                 prefix, yes_no(c->blockio_accounting),
100                 prefix, yes_no(c->memory_accounting),
101                 prefix, c->cpu_shares,
102                 prefix, c->blockio_weight,
103                 prefix, c->memory_limit,
104                 prefix, cgroup_device_policy_to_string(c->device_policy));
105
106         LIST_FOREACH(device_allow, a, c->device_allow)
107                 fprintf(f,
108                         "%sDeviceAllow=%s %s%s%s\n",
109                         prefix,
110                         a->path,
111                         a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : "");
112
113         LIST_FOREACH(device_weights, w, c->blockio_device_weights)
114                 fprintf(f,
115                         "%sBlockIODeviceWeight=%s %lu",
116                         prefix,
117                         w->path,
118                         w->weight);
119
120         LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) {
121                 char buf[FORMAT_BYTES_MAX];
122
123                 fprintf(f,
124                         "%s%s=%s %s\n",
125                         prefix,
126                         b->read ? "BlockIOReadBandwidth" : "BlockIOWriteBandwidth",
127                         b->path,
128                         format_bytes(buf, sizeof(buf), b->bandwidth));
129         }
130 }
131
132 static int lookup_blkio_device(const char *p, dev_t *dev) {
133         struct stat st;
134         int r;
135
136         assert(p);
137         assert(dev);
138
139         r = stat(p, &st);
140         if (r < 0) {
141                 log_warning("Couldn't stat device %s: %m", p);
142                 return -errno;
143         }
144
145         if (S_ISBLK(st.st_mode))
146                 *dev = st.st_rdev;
147         else if (major(st.st_dev) != 0) {
148                 /* If this is not a device node then find the block
149                  * device this file is stored on */
150                 *dev = st.st_dev;
151
152                 /* If this is a partition, try to get the originating
153                  * block device */
154                 block_get_whole_disk(*dev, dev);
155         } else {
156                 log_warning("%s is not a block device and file system block device cannot be determined or is not local.", p);
157                 return -ENODEV;
158         }
159
160         return 0;
161 }
162
163 static int whitelist_device(const char *path, const char *node, const char *acc) {
164         char buf[2+DECIMAL_STR_MAX(dev_t)*2+2+4];
165         struct stat st;
166         int r;
167
168         assert(path);
169         assert(acc);
170
171         if (stat(node, &st) < 0) {
172                 log_warning("Couldn't stat device %s", node);
173                 return -errno;
174         }
175
176         if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
177                 log_warning("%s is not a device.", node);
178                 return -ENODEV;
179         }
180
181         sprintf(buf,
182                 "%c %u:%u %s",
183                 S_ISCHR(st.st_mode) ? 'c' : 'b',
184                 major(st.st_rdev), minor(st.st_rdev),
185                 acc);
186
187         r = cg_set_attribute("devices", path, "devices.allow", buf);
188         if (r < 0)
189                 log_warning("Failed to set devices.allow on %s: %s", path, strerror(-r));
190
191         return r;
192 }
193
194 void cgroup_context_apply(CGroupContext *c, CGroupControllerMask mask, const char *path) {
195         int r;
196
197         assert(c);
198         assert(path);
199
200         if (mask == 0)
201                 return;
202
203         if (mask & CGROUP_CPU) {
204                 char buf[DECIMAL_STR_MAX(unsigned long) + 1];
205
206                 sprintf(buf, "%lu\n", c->cpu_shares);
207                 r = cg_set_attribute("cpu", path, "cpu.shares", buf);
208                 if (r < 0)
209                         log_warning("Failed to set cpu.shares on %s: %s", path, strerror(-r));
210         }
211
212         if (mask & CGROUP_BLKIO) {
213                 char buf[MAX3(DECIMAL_STR_MAX(unsigned long)+1,
214                               DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(unsigned long)*1,
215                               DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1)];
216                 CGroupBlockIODeviceWeight *w;
217                 CGroupBlockIODeviceBandwidth *b;
218
219                 sprintf(buf, "%lu\n", c->blockio_weight);
220                 r = cg_set_attribute("blkio", path, "blkio.weight", buf);
221                 if (r < 0)
222                         log_warning("Failed to set blkio.weight on %s: %s", path, strerror(-r));
223
224                 /* FIXME: no way to reset this list */
225                 LIST_FOREACH(device_weights, w, c->blockio_device_weights) {
226                         dev_t dev;
227
228                         r = lookup_blkio_device(w->path, &dev);
229                         if (r < 0)
230                                 continue;
231
232                         sprintf(buf, "%u:%u %lu", major(dev), minor(dev), w->weight);
233                         r = cg_set_attribute("blkio", path, "blkio.weight_device", buf);
234                         if (r < 0)
235                                 log_error("Failed to set blkio.weight_device on %s: %s", path, strerror(-r));
236                 }
237
238                 /* FIXME: no way to reset this list */
239                 LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) {
240                         const char *a;
241                         dev_t dev;
242
243                         r = lookup_blkio_device(b->path, &dev);
244                         if (r < 0)
245                                 continue;
246
247                         a = b->read ? "blkio.throttle.read_bps_device" : "blkio.throttle.write_bps_device";
248
249                         sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), b->bandwidth);
250                         r = cg_set_attribute("blkio", path, a, buf);
251                         if (r < 0)
252                                 log_error("Failed to set %s on %s: %s", a, path, strerror(-r));
253                 }
254         }
255
256         if (mask & CGROUP_MEMORY) {
257                 char buf[DECIMAL_STR_MAX(uint64_t) + 1];
258                 if (c->memory_limit != (uint64_t) -1) {
259                         sprintf(buf, "%" PRIu64 "\n", c->memory_limit);
260                         r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf);
261                 } else
262                         r = cg_set_attribute("memory", path, "memory.limit_in_bytes", "-1");
263
264                 if (r < 0)
265                         log_error("Failed to set memory.limit_in_bytes on %s: %s", path, strerror(-r));
266         }
267
268         if (mask & CGROUP_DEVICE) {
269                 CGroupDeviceAllow *a;
270
271                 if (c->device_allow || c->device_policy != CGROUP_AUTO)
272                         r = cg_set_attribute("devices", path, "devices.deny", "a");
273                 else
274                         r = cg_set_attribute("devices", path, "devices.allow", "a");
275                 if (r < 0)
276                         log_error("Failed to reset devices.list on %s: %s", path, strerror(-r));
277
278                 if (c->device_policy == CGROUP_CLOSED ||
279                     (c->device_policy == CGROUP_AUTO && c->device_allow)) {
280                         static const char auto_devices[] =
281                                 "/dev/null\0" "rw\0"
282                                 "/dev/zero\0" "rw\0"
283                                 "/dev/full\0" "rw\0"
284                                 "/dev/random\0" "rw\0"
285                                 "/dev/urandom\0" "rw\0";
286
287                         const char *x, *y;
288
289                         NULSTR_FOREACH_PAIR(x, y, auto_devices)
290                                 whitelist_device(path, x, y);
291                 }
292
293                 LIST_FOREACH(device_allow, a, c->device_allow) {
294                         char acc[4];
295                         unsigned k = 0;
296
297                         if (a->r)
298                                 acc[k++] = 'r';
299                         if (a->w)
300                                 acc[k++] = 'w';
301                         if (a->m)
302                                 acc[k++] = 'm';
303
304                         if (k == 0)
305                                 continue;
306
307                         acc[k++] = 0;
308                         whitelist_device(path, a->path, acc);
309                 }
310         }
311 }
312
313 CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) {
314         CGroupControllerMask mask = 0;
315
316         /* Figure out which controllers we need */
317
318         if (c->cpu_accounting || c->cpu_shares != 1024)
319                 mask |= CGROUP_CPUACCT | CGROUP_CPU;
320
321         if (c->blockio_accounting ||
322             c->blockio_weight != 1000 ||
323             c->blockio_device_weights ||
324             c->blockio_device_bandwidths)
325                 mask |= CGROUP_BLKIO;
326
327         if (c->memory_accounting ||
328             c->memory_limit != (uint64_t) -1)
329                 mask |= CGROUP_MEMORY;
330
331         if (c->device_allow || c->device_policy != CGROUP_AUTO)
332                 mask |= CGROUP_DEVICE;
333
334         return mask;
335 }
336
337 static CGroupControllerMask unit_get_cgroup_mask(Unit *u) {
338         CGroupContext *c;
339
340         c = unit_get_cgroup_context(u);
341         if (!c)
342                 return 0;
343
344         return cgroup_context_get_mask(c);
345 }
346
347 static CGroupControllerMask unit_get_members_mask(Unit *u) {
348         CGroupControllerMask mask = 0;
349         Unit *m;
350         Iterator i;
351
352         assert(u);
353
354         SET_FOREACH(m, u->dependencies[UNIT_BEFORE], i) {
355
356                 if (UNIT_DEREF(m->slice) != u)
357                         continue;
358
359                 mask |= unit_get_cgroup_mask(m) | unit_get_members_mask(m);
360         }
361
362         return mask;
363 }
364
365 static CGroupControllerMask unit_get_siblings_mask(Unit *u) {
366         assert(u);
367
368         if (!UNIT_ISSET(u->slice))
369                 return 0;
370
371         /* Sibling propagation is only relevant for weight-based
372          * controllers, so let's mask out everything else */
373         return unit_get_members_mask(UNIT_DEREF(u->slice)) &
374                 (CGROUP_CPU|CGROUP_BLKIO|CGROUP_CPUACCT);
375 }
376
377 static int unit_create_cgroups(Unit *u, CGroupControllerMask mask) {
378         char *path = NULL;
379         int r;
380         bool is_in_hash = false;
381
382         assert(u);
383
384         path = unit_default_cgroup_path(u);
385         if (!path)
386                 return -ENOMEM;
387
388         r = hashmap_put(u->manager->cgroup_unit, path, u);
389         if (r == 0)
390                 is_in_hash = true;
391
392         if (r < 0) {
393                 log_error("cgroup %s exists already: %s", path, strerror(-r));
394                 free(path);
395                 return r;
396         }
397
398         /* First, create our own group */
399         r = cg_create_with_mask(mask, path);
400         if (r < 0)
401                 log_error("Failed to create cgroup %s: %s", path, strerror(-r));
402
403         /* Then, possibly move things over */
404         if (u->cgroup_path && !streq(path, u->cgroup_path)) {
405                 r = cg_migrate_with_mask(mask, u->cgroup_path, path);
406                 if (r < 0)
407                         log_error("Failed to migrate cgroup %s: %s", path, strerror(-r));
408         }
409
410         if (!is_in_hash) {
411                 /* And remember the new data */
412                 free(u->cgroup_path);
413                 u->cgroup_path = path;
414         }
415
416         u->cgroup_realized = true;
417         u->cgroup_mask = mask;
418
419         return 0;
420 }
421
422 static int unit_realize_cgroup_now(Unit *u) {
423         CGroupControllerMask mask;
424
425         assert(u);
426
427         if (u->in_cgroup_queue) {
428                 LIST_REMOVE(Unit, cgroup_queue, u->manager->cgroup_queue, u);
429                 u->in_cgroup_queue = false;
430         }
431
432         mask = unit_get_cgroup_mask(u) | unit_get_members_mask(u) | unit_get_siblings_mask(u);
433         mask &= u->manager->cgroup_supported;
434
435         if (u->cgroup_realized &&
436             u->cgroup_mask == mask)
437                 return 0;
438
439         /* First, realize parents */
440         if (UNIT_ISSET(u->slice))
441                 unit_realize_cgroup_now(UNIT_DEREF(u->slice));
442
443         /* And then do the real work */
444         return unit_create_cgroups(u, mask);
445 }
446
447 static void unit_add_to_cgroup_queue(Unit *u) {
448
449         if (u->in_cgroup_queue)
450                 return;
451
452         LIST_PREPEND(Unit, cgroup_queue, u->manager->cgroup_queue, u);
453         u->in_cgroup_queue = true;
454 }
455
456 unsigned manager_dispatch_cgroup_queue(Manager *m) {
457         Unit *i;
458         unsigned n = 0;
459
460         while ((i = m->cgroup_queue)) {
461                 assert(i->in_cgroup_queue);
462
463                 if (unit_realize_cgroup_now(i) >= 0)
464                         cgroup_context_apply(unit_get_cgroup_context(i), i->cgroup_mask, i->cgroup_path);
465
466                 n++;
467         }
468
469         return n;
470 }
471
472 static void unit_queue_siblings(Unit *u) {
473         Unit *slice;
474
475         /* This adds the siblings of the specified unit and the
476          * siblings of all parent units to the cgroup queue. (But
477          * neither the specified unit itself nor the parents.) */
478
479         while ((slice = UNIT_DEREF(u->slice))) {
480                 Iterator i;
481                 Unit *m;
482
483                 SET_FOREACH(m, slice->dependencies[UNIT_BEFORE], i) {
484                         if (m == u)
485                                 continue;
486
487                         if (UNIT_DEREF(m->slice) != slice)
488                                 continue;
489
490                         unit_add_to_cgroup_queue(m);
491                 }
492
493                 u = slice;
494         }
495 }
496
497 int unit_realize_cgroup(Unit *u) {
498         CGroupContext *c;
499         int r;
500
501         assert(u);
502
503         c = unit_get_cgroup_context(u);
504         if (!c)
505                 return 0;
506
507         /* So, here's the deal: when realizing the cgroups for this
508          * unit, we need to first create all parents, but there's more
509          * actually: for the weight-based controllers we also need to
510          * make sure that all our siblings (i.e. units that are in the
511          * same slice as we are) have cgroup too. Otherwise things
512          * would become very uneven as each of their processes would
513          * get as much resources as all our group together. This call
514          * will synchronously create the parent cgroups, but will
515          * defer work on the siblings to the next event loop
516          * iteration. */
517
518         /* Add all sibling slices to the cgroup queue. */
519         unit_queue_siblings(u);
520
521         /* And realize this one now */
522         r = unit_realize_cgroup_now(u);
523
524         /* And apply the values */
525         if (r >= 0)
526                 cgroup_context_apply(c, u->cgroup_mask, u->cgroup_path);
527
528         return r;
529 }
530
531 void unit_destroy_cgroup(Unit *u) {
532         int r;
533
534         assert(u);
535
536         if (!u->cgroup_path)
537                 return;
538
539         r = cg_trim_with_mask(u->cgroup_mask, u->cgroup_path, !unit_has_name(u, SPECIAL_ROOT_SLICE));
540         if (r < 0)
541                 log_debug("Failed to destroy cgroup %s: %s", u->cgroup_path, strerror(-r));
542
543         hashmap_remove(u->manager->cgroup_unit, u->cgroup_path);
544
545         free(u->cgroup_path);
546         u->cgroup_path = NULL;
547         u->cgroup_realized = false;
548         u->cgroup_mask = 0;
549
550 }
551
552 pid_t unit_search_main_pid(Unit *u) {
553         _cleanup_fclose_ FILE *f = NULL;
554         pid_t pid = 0, npid, mypid;
555
556         assert(u);
557
558         if (!u->cgroup_path)
559                 return 0;
560
561         if (cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &f) < 0)
562                 return 0;
563
564         mypid = getpid();
565         while (cg_read_pid(f, &npid) > 0)  {
566                 pid_t ppid;
567
568                 if (npid == pid)
569                         continue;
570
571                 /* Ignore processes that aren't our kids */
572                 if (get_parent_of_pid(npid, &ppid) >= 0 && ppid != mypid)
573                         continue;
574
575                 if (pid != 0) {
576                         /* Dang, there's more than one daemonized PID
577                         in this group, so we don't know what process
578                         is the main process. */
579                         pid = 0;
580                         break;
581                 }
582
583                 pid = npid;
584         }
585
586         return pid;
587 }
588
589 int manager_setup_cgroup(Manager *m) {
590         _cleanup_free_ char *path = NULL;
591         int r;
592         char *e, *a;
593
594         assert(m);
595
596         /* 0. Be nice to Ingo Molnar #628004 */
597         if (path_is_mount_point("/sys/fs/cgroup/systemd", false) <= 0) {
598                 log_warning("No control group support available, not creating root group.");
599                 return 0;
600         }
601
602         /* 1. Determine hierarchy */
603         free(m->cgroup_root);
604         m->cgroup_root = NULL;
605
606         r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &m->cgroup_root);
607         if (r < 0) {
608                 log_error("Cannot determine cgroup we are running in: %s", strerror(-r));
609                 return r;
610         }
611
612         /* Already in /system.slice? If so, let's cut this off again */
613         if (m->running_as == SYSTEMD_SYSTEM) {
614                 e = endswith(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE);
615                 if (e)
616                         *e = 0;
617         }
618
619         /* And make sure to store away the root value without trailing
620          * slash, even for the root dir, so that we can easily prepend
621          * it everywhere. */
622         if (streq(m->cgroup_root, "/"))
623                 m->cgroup_root[0] = 0;
624
625         /* 2. Show data */
626         r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, NULL, &path);
627         if (r < 0) {
628                 log_error("Cannot find cgroup mount point: %s", strerror(-r));
629                 return r;
630         }
631
632         log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path);
633
634         /* 3. Install agent */
635         if (m->running_as == SYSTEMD_SYSTEM) {
636                 r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH);
637                 if (r < 0)
638                         log_warning("Failed to install release agent, ignoring: %s", strerror(-r));
639                 else if (r > 0)
640                         log_debug("Installed release agent.");
641                 else
642                         log_debug("Release agent already installed.");
643         }
644
645         /* 4. Realize the system slice and put us in there */
646         if (m->running_as == SYSTEMD_SYSTEM) {
647                 a = strappenda(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE);
648                 r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, a, 0);
649         } else
650                 r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, 0);
651         if (r < 0) {
652                 log_error("Failed to create root cgroup hierarchy: %s", strerror(-r));
653                 return r;
654         }
655
656         /* 5. And pin it, so that it cannot be unmounted */
657         if (m->pin_cgroupfs_fd >= 0)
658                 close_nointr_nofail(m->pin_cgroupfs_fd);
659
660         m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK);
661         if (r < 0) {
662                 log_error("Failed to open pin file: %m");
663                 return -errno;
664         }
665
666         /* 6. Figure out which controllers are supported */
667         m->cgroup_supported = cg_mask_supported();
668
669         return 0;
670 }
671
672 void manager_shutdown_cgroup(Manager *m, bool delete) {
673         assert(m);
674
675         /* We can't really delete the group, since we are in it. But
676          * let's trim it. */
677         if (delete && m->cgroup_root)
678                 cg_trim(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, false);
679
680         if (m->pin_cgroupfs_fd >= 0) {
681                 close_nointr_nofail(m->pin_cgroupfs_fd);
682                 m->pin_cgroupfs_fd = -1;
683         }
684
685         free(m->cgroup_root);
686         m->cgroup_root = NULL;
687 }
688
689 Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) {
690         char *p;
691         Unit *u;
692
693         assert(m);
694         assert(cgroup);
695
696         u = hashmap_get(m->cgroup_unit, cgroup);
697         if (u)
698                 return u;
699
700         p = strdupa(cgroup);
701         for (;;) {
702                 char *e;
703
704                 e = strrchr(p, '/');
705                 if (e == p || !e)
706                         return NULL;
707
708                 *e = 0;
709
710                 u = hashmap_get(m->cgroup_unit, p);
711                 if (u)
712                         return u;
713         }
714 }
715
716 Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) {
717         _cleanup_free_ char *cgroup = NULL;
718         int r;
719
720         assert(m);
721
722         if (pid <= 1)
723                 return NULL;
724
725         r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup);
726         if (r < 0)
727                 return NULL;
728
729         return manager_get_unit_by_cgroup(m, cgroup);
730 }
731
732 int manager_notify_cgroup_empty(Manager *m, const char *cgroup) {
733         Unit *u;
734         int r;
735
736         assert(m);
737         assert(cgroup);
738
739         u = manager_get_unit_by_cgroup(m, cgroup);
740         if (u) {
741                 r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, true);
742                 if (r > 0) {
743                         if (UNIT_VTABLE(u)->notify_cgroup_empty)
744                                 UNIT_VTABLE(u)->notify_cgroup_empty(u);
745
746                         unit_add_to_gc_queue(u);
747                 }
748         }
749
750         return 0;
751 }
752
753 static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
754         [CGROUP_AUTO] = "auto",
755         [CGROUP_CLOSED] = "closed",
756         [CGROUP_STRICT] = "strict",
757 };
758
759 DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy);