]>
git.proxmox.com Git - mirror_lxcfs.git/blob - pam/pam_cgfs.c
b823cda1cfbea364c8ec816f633814717c61d89c
3 * Copyright © 2016 Canonical, Inc
4 * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
5 * Author: Christian Brauner <christian.brauner@ubuntu.com>
7 * When a user logs in, this pam module will create cgroups which the user may
8 * administer. It handles both pure cgroupfs v1 and pure cgroupfs v2, as well as
9 * mixed mounts, where some controllers are mounted in a standard cgroupfs v1
10 * hierarchy location (/sys/fs/cgroup/<controller>) and others are in the
11 * cgroupfs v2 hierarchy.
12 * Writeable cgroups are either created for all controllers or, if specified,
13 * for any controllers listed on the command line.
14 * The cgroup created will be "user/$user/0" for the first session,
15 * "user/$user/1" for the second, etc.
17 * Systems with a systemd init system are treated specially, both with respect
18 * to cgroupfs v1 and cgroupfs v2. For both, cgroupfs v1 and cgroupfs v2, We
19 * check whether systemd already placed us in a cgroup it created:
21 * user.slice/user-uid.slice/session-n.scope
23 * by checking whether uid == our uid. If it did, we simply chown the last
24 * part (session-n.scope). If it did not we create a cgroup as outlined above
25 * (user/$user/n) and chown it to our uid.
26 * The same holds for cgroupfs v2 where this assumptions becomes crucial:
27 * We __have to__ be placed in our under the cgroup systemd created for us on
28 * login, otherwise things like starting an xserver or similar will not work.
30 * All requested cgroups must be mounted under /sys/fs/cgroup/$controller,
31 * no messing around with finding mountpoints.
33 * See COPYING file for details.
46 #include <linux/unistd.h>
47 #include <sys/mount.h>
48 #include <sys/param.h>
50 #include <sys/types.h>
53 #define PAM_SM_SESSION
54 #include <security/_pam_macros.h>
55 #include <security/pam_modules.h>
59 #ifndef CGROUP_SUPER_MAGIC
60 #define CGROUP_SUPER_MAGIC 0x27e0eb
63 #ifndef CGROUP2_SUPER_MAGIC
64 #define CGROUP2_SUPER_MAGIC 0x63677270
67 static enum cg_mount_mode
{
72 CGROUP_UNINITIALIZED
= 3,
73 } cg_mount_mode
= CGROUP_UNINITIALIZED
;
75 /* Common helper prototypes. */
76 static void append_line(char **dest
, size_t oldlen
, char *new, size_t newlen
);
77 static int append_null_to_list(void ***list
);
78 static void batch_realloc(char **mem
, size_t oldlen
, size_t newlen
);
79 static char *copy_to_eol(char *s
);
80 static bool file_exists(const char *f
);
81 static void free_string_list(char **list
);
82 static char *get_mountpoint(char *line
);
83 static bool get_uid_gid(const char *user
, uid_t
*uid
, gid_t
*gid
);
84 static int handle_login(const char *user
, uid_t uid
, gid_t gid
);
85 /* __typeof__ should be safe to use with all compilers. */
86 typedef __typeof__(((struct statfs
*)NULL
)->f_type
) fs_type_magic
;
87 static bool has_fs_type(const struct statfs
*fs
, fs_type_magic magic_val
);
88 static bool is_lxcfs(const char *line
);
89 static bool is_cgv1(char *line
);
90 static bool is_cgv2(char *line
);
91 static bool mkdir_p(const char *root
, char *path
);
92 static void *must_alloc(size_t sz
);
93 static void must_add_to_list(char ***clist
, char *entry
);
94 static void must_append_controller(char **klist
, char **nlist
, char ***clist
,
96 static void must_append_string(char ***list
, char *entry
);
97 static char *must_copy_string(const char *entry
);
98 static char *must_make_path(const char *first
, ...) __attribute__((sentinel
));
99 static void *must_realloc(void *orig
, size_t sz
);
100 static void mysyslog(int err
, const char *format
, ...) __attribute__((sentinel
));
101 static char *read_file(char *fnam
);
102 static int recursive_rmdir(char *dirname
);
103 static bool string_in_list(char **list
, const char *entry
);
104 static void trim(char *s
);
105 static bool write_int(char *path
, int v
);
107 /* cgroupfs prototypes. */
108 static void cg_mark_to_make_rw(const char *cstring
);
109 static bool cg_systemd_under_user_slice_1(const char *in
, uid_t uid
);
110 static bool cg_systemd_under_user_slice_2(const char *base_cgroup
,
111 const char *init_cgroup
, uid_t uid
);
112 static bool cg_systemd_created_user_slice(const char *base_cgroup
,
113 const char *init_cgroup
,
114 const char *in
, uid_t uid
);
115 static bool cg_systemd_chown_existing_cgroup(const char *mountpoint
,
116 const char *base_cgroup
, uid_t uid
,
118 bool systemd_user_slice
);
119 static int cg_get_version_of_mntpt(const char *path
);
120 static bool cg_enter(const char *cgroup
);
121 static void cg_escape(void);
122 static bool cg_init(uid_t uid
, gid_t gid
);
123 static void cg_systemd_prune_init_scope(char *cg
);
124 static void cg_prune_empty_cgroups(const char *user
);
125 static bool is_lxcfs(const char *line
);
127 /* cgroupfs v1 prototypes. */
128 struct cgv1_hierarchy
{
134 bool create_rw_cgroup
;
135 bool systemd_user_slice
;
138 static struct cgv1_hierarchy
**cgv1_hierarchies
;
140 static void cgv1_add_controller(char **clist
, char *mountpoint
,
141 char *base_cgroup
, char *init_cgroup
);
142 static bool cgv1_controller_in_clist(char *cgline
, char *c
);
143 static bool cgv1_controller_lists_intersect(char **l1
, char **l2
);
144 static bool cgv1_controller_list_is_dup(struct cgv1_hierarchy
**hlist
,
146 static bool cgv1_create(const char *cgroup
, uid_t uid
, gid_t gid
,
148 static bool cgv1_create_one(struct cgv1_hierarchy
*h
, const char *cgroup
,
149 uid_t uid
, gid_t gid
, bool *existed
);
150 static bool cgv1_enter(const char *cgroup
);
151 static void cgv1_escape(void);
152 static bool cgv1_get_controllers(char ***klist
, char ***nlist
);
153 static char *cgv1_get_current_cgroup(char *basecginfo
, char *controller
);
154 static char **cgv1_get_proc_mountinfo_controllers(char **klist
, char **nlist
,
156 static bool cgv1_init(uid_t uid
, gid_t gid
);
157 static void cgv1_mark_to_make_rw(char **clist
);
158 static char *cgv1_must_prefix_named(char *entry
);
159 static bool cgv1_prune_empty_cgroups(const char *user
);
160 static bool cgv1_remove_one(struct cgv1_hierarchy
*h
, const char *cgroup
);
161 static bool is_cgv1(char *line
);
163 /* cgroupfs v2 prototypes. */
164 struct cgv2_hierarchy
{
170 bool create_rw_cgroup
;
171 bool systemd_user_slice
;
174 /* Actually this should only be a single hierarchy. But for the sake of
175 * parallelism and because the layout of the cgroupfs v2 is still somewhat
176 * changing, we'll leave it as an array of structs.
178 static struct cgv2_hierarchy
**cgv2_hierarchies
;
180 static void cgv2_add_controller(char **clist
, char *mountpoint
,
181 char *base_cgroup
, char *init_cgroup
,
182 bool systemd_user_slice
);
183 static bool cgv2_create(const char *cgroup
, uid_t uid
, gid_t gid
,
185 static bool cgv2_enter(const char *cgroup
);
186 static void cgv2_escape(void);
187 static char *cgv2_get_current_cgroup(int pid
);
188 static bool cgv2_init(uid_t uid
, gid_t gid
);
189 static void cgv2_mark_to_make_rw(char **clist
);
190 static bool cgv2_prune_empty_cgroups(const char *user
);
191 static bool cgv2_remove(const char *cgroup
);
192 static bool is_cgv2(char *line
);
194 /* Common helper functions. */
195 static void mysyslog(int err
, const char *format
, ...)
199 va_start(args
, format
);
200 openlog("PAM-CGFS", LOG_CONS
|LOG_PID
, LOG_AUTH
);
201 vsyslog(err
, format
, args
);
206 /* realloc() pointer; do not fail. */
207 static void *must_realloc(void *orig
, size_t sz
)
212 ret
= realloc(orig
, sz
);
218 /* realloc() pointer in batch sizes; do not fail. */
219 #define BATCH_SIZE 50
220 static void batch_realloc(char **mem
, size_t oldlen
, size_t newlen
)
222 int newbatches
= (newlen
/ BATCH_SIZE
) + 1;
223 int oldbatches
= (oldlen
/ BATCH_SIZE
) + 1;
225 if (!*mem
|| newbatches
> oldbatches
)
226 *mem
= must_realloc(*mem
, newbatches
* BATCH_SIZE
);
229 /* Append lines as is to pointer; do not fail. */
230 static void append_line(char **dest
, size_t oldlen
, char *new, size_t newlen
)
232 size_t full
= oldlen
+ newlen
;
234 batch_realloc(dest
, oldlen
, full
+ 1);
236 memcpy(*dest
+ oldlen
, new, newlen
+ 1);
239 /* Read in whole file and return allocated pointer. */
240 static char *read_file(char *fnam
)
244 char *line
= NULL
, *buf
= NULL
;
245 size_t len
= 0, fulllen
= 0;
247 f
= fopen(fnam
, "r");
251 while ((linelen
= getline(&line
, &len
, f
)) != -1) {
252 append_line(&buf
, fulllen
, line
, linelen
);
262 /* Given a pointer to a null-terminated array of pointers, realloc to add one
263 * entry, and point the new entry to NULL. Do not fail. Return the index to the
264 * second-to-last entry - that is, the one which is now available for use
265 * (keeping the list null-terminated).
267 static int append_null_to_list(void ***list
)
272 for (; (*list
)[newentry
]; newentry
++) {
276 *list
= must_realloc(*list
, (newentry
+ 2) * sizeof(void **));
277 (*list
)[newentry
+ 1] = NULL
;
282 /* Make allocated copy of string; do not fail. */
283 static char *must_copy_string(const char *entry
)
297 /* Append new entry to null-terminated array of pointer; make sure that array of
298 * pointers will still be null-terminated.
300 static void must_append_string(char ***list
, char *entry
)
305 newentry
= append_null_to_list((void ***)list
);
306 copy
= must_copy_string(entry
);
307 (*list
)[newentry
] = copy
;
310 /* Remove newlines from string. */
311 static void trim(char *s
)
313 size_t len
= strlen(s
);
315 while (s
[len
- 1] == '\n')
319 /* Allocate pointer; do not fail. */
320 static void *must_alloc(size_t sz
)
322 return must_realloc(NULL
, sz
);
325 /* Make allocated copy of string. End of string is taken to be '\n'. */
326 static char *copy_to_eol(char *s
)
328 char *newline
, *sret
;
331 newline
= strchr(s
, '\n');
336 sret
= must_alloc(len
+ 1);
337 memcpy(sret
, s
, len
);
343 /* Check if given entry under /proc/<pid>/mountinfo is a fuse.lxcfs mount. */
344 static bool is_lxcfs(const char *line
)
346 char *p
= strstr(line
, " - ");
350 return strncmp(p
, " - fuse.lxcfs ", 14) == 0;
353 /* Check if given entry under /proc/<pid>/mountinfo is a cgroupfs v1 mount. */
354 static bool is_cgv1(char *line
)
356 char *p
= strstr(line
, " - ");
360 return strncmp(p
, " - cgroup ", 10) == 0;
363 /* Check if given entry under /proc/<pid>/mountinfo is a cgroupfs v2 mount. */
364 static bool is_cgv2(char *line
)
366 char *p
= strstr(line
, " - ");
370 return strncmp(p
, " - cgroup2 ", 11) == 0;
373 /* Given a null-terminated array of strings, check whether @entry is one of the
376 static bool string_in_list(char **list
, const char *entry
)
380 for (it
= list
; it
&& *it
; it
++)
381 if (strcmp(*it
, entry
) == 0)
387 /* Free null-terminated array of strings. */
388 static void free_string_list(char **list
)
392 for (it
= list
; it
&& *it
; it
++)
397 /* Concatenate all passed-in strings into one path. Do not fail. If any piece
398 * is not prefixed with '/', add a '/'. Does not remove duplicate '///' from the
401 static char *must_make_path(const char *first
, ...)
407 full_len
= strlen(first
);
409 dest
= must_copy_string(first
);
411 va_start(args
, first
);
412 while ((cur
= va_arg(args
, char *)) != NULL
) {
413 full_len
+= strlen(cur
);
418 dest
= must_realloc(dest
, full_len
+ 1);
430 /* Write single integer to file. */
431 static bool write_int(char *path
, int v
)
436 f
= fopen(path
, "w");
440 if (fprintf(f
, "%d\n", v
) < 0)
449 /* Check if a given file exists. */
450 static bool file_exists(const char *f
)
454 return stat(f
, &statbuf
) == 0;
457 /* Create directory and (if necessary) its parents. */
458 static bool mkdir_p(const char *root
, char *path
)
462 if (strlen(path
) < strlen(root
))
465 if (strlen(path
) == strlen(root
))
468 b
= path
+ strlen(root
) + 1;
470 while (*b
&& (*b
== '/'))
476 while (*e
&& *e
!= '/')
483 if (file_exists(path
))
486 if (mkdir(path
, 0755) < 0) {
487 lxcfs_debug("Failed to create %s: %m.\n", path
);
502 /* Recursively remove directory and its parents. */
503 static int recursive_rmdir(char *dirname
)
505 struct dirent
*direntp
;
509 dir
= opendir(dirname
);
513 while ((direntp
= readdir(dir
))) {
520 if (!strcmp(direntp
->d_name
, ".") ||
521 !strcmp(direntp
->d_name
, ".."))
524 pathname
= must_make_path(dirname
, direntp
->d_name
, NULL
);
526 if (lstat(pathname
, &st
)) {
528 lxcfs_debug("Failed to stat %s.\n", pathname
);
533 if (!S_ISDIR(st
.st_mode
))
536 if (recursive_rmdir(pathname
) < 0)
542 if (rmdir(dirname
) < 0) {
544 lxcfs_debug("Failed to delete %s: %m.\n", dirname
);
548 if (closedir(dir
) < 0) {
550 lxcfs_debug("Failed to delete %s: %m.\n", dirname
);
557 /* Add new entry to null-terminated array of pointers. Make sure array is still
560 static void must_add_to_list(char ***clist
, char *entry
)
564 newentry
= append_null_to_list((void ***)clist
);
565 (*clist
)[newentry
] = must_copy_string(entry
);
568 /* Get mountpoint from a /proc/<pid>/mountinfo line. */
569 static char *get_mountpoint(char *line
)
577 for (i
= 0; i
< 4; i
++) {
589 sret
= must_alloc(len
+ 1);
590 memcpy(sret
, p
, len
);
596 /* Create list of cgroupfs v1 controller found under /proc/self/cgroup. Skips
597 * the 0::/some/path cgroupfs v2 hierarchy listed. Splits controllers into
598 * kernel controllers (@klist) and named controllers (@nlist).
600 static bool cgv1_get_controllers(char ***klist
, char ***nlist
)
606 f
= fopen("/proc/self/cgroup", "r");
610 while (getline(&line
, &len
, f
) != -1) {
612 char *saveptr
= NULL
;
614 p
= strchr(line
, ':');
624 /* Skip the v2 hierarchy. */
628 for (tok
= strtok_r(p
, ",", &saveptr
); tok
;
629 tok
= strtok_r(NULL
, ",", &saveptr
)) {
630 if (strncmp(tok
, "name=", 5) == 0)
631 must_append_string(nlist
, tok
);
633 must_append_string(klist
, tok
);
643 /* Get list of controllers for cgroupfs v2 hierarchy by looking at
644 * cgroup.controllers and/or cgroup.subtree_control of a given (parent) cgroup.
645 static bool cgv2_get_controllers(char ***klist)
651 /* Get current cgroup from /proc/self/cgroup for the cgroupfs v2 hierarchy. */
652 static char *cgv2_get_current_cgroup(int pid
)
656 char *current_cgroup
;
658 /* The largest integer that can fit into long int is 2^64. This is a
659 * 20-digit number. */
660 #define __PIDLEN /* /proc */ 5 + /* /pid-to-str */ 21 + /* /cgroup */ 7 + /* \0 */ 1
663 ret
= snprintf(path
, __PIDLEN
, "/proc/%d/cgroup", pid
);
664 if (ret
< 0 || ret
>= __PIDLEN
)
667 cgroups_v2
= read_file(path
);
671 current_cgroup
= strstr(cgroups_v2
, "0::/");
675 current_cgroup
= current_cgroup
+ 3;
676 copy
= copy_to_eol(current_cgroup
);
688 /* Given two null-terminated lists of strings, return true if any string is in
691 static bool cgv1_controller_lists_intersect(char **l1
, char **l2
)
698 for (it
= l1
; it
&& *it
; it
++)
699 if (string_in_list(l2
, *it
))
705 /* For a null-terminated list of controllers @clist, return true if any of those
706 * controllers is already listed the null-terminated list of hierarchies @hlist.
707 * Realistically, if one is present, all must be present.
709 static bool cgv1_controller_list_is_dup(struct cgv1_hierarchy
**hlist
, char **clist
)
711 struct cgv1_hierarchy
**it
;
713 for (it
= hlist
; it
&& *it
; it
++)
714 if ((*it
)->controllers
)
715 if (cgv1_controller_lists_intersect((*it
)->controllers
, clist
))
721 /* Set boolean to mark controllers under which we are supposed create a
724 static void cgv1_mark_to_make_rw(char **clist
)
726 struct cgv1_hierarchy
**it
;
728 for (it
= cgv1_hierarchies
; it
&& *it
; it
++)
729 if ((*it
)->controllers
)
730 if (cgv1_controller_lists_intersect((*it
)->controllers
, clist
))
731 (*it
)->create_rw_cgroup
= true;
734 /* Set boolean to mark whether we are supposed to create a writeable cgroup in
735 * the cgroupfs v2 hierarchy.
737 static void cgv2_mark_to_make_rw(char **clist
)
739 if (string_in_list(clist
, "unified"))
740 if (cgv2_hierarchies
)
741 (*cgv2_hierarchies
)->create_rw_cgroup
= true;
744 /* Wrapper around cgv{1,2}_mark_to_make_rw(). */
745 static void cg_mark_to_make_rw(const char *cstring
)
748 char *saveptr
= NULL
;
751 copy
= must_copy_string(cstring
);
753 for (tok
= strtok_r(copy
, ",", &saveptr
); tok
;
754 tok
= strtok_r(NULL
, ",", &saveptr
))
755 must_add_to_list(&clist
, tok
);
759 cgv1_mark_to_make_rw(clist
);
760 cgv2_mark_to_make_rw(clist
);
762 free_string_list(clist
);
765 /* Prefix any named controllers with "name=", e.g. "name=systemd". */
766 static char *cgv1_must_prefix_named(char *entry
)
773 s
= must_alloc(len
+ 6);
775 ret
= snprintf(s
, len
+ 6, "name=%s", entry
);
776 if (ret
< 0 || (size_t)ret
>= (len
+ 6))
782 /* Append kernel controller in @klist or named controller in @nlist to @clist */
783 static void must_append_controller(char **klist
, char **nlist
, char ***clist
, char *entry
)
788 if (string_in_list(klist
, entry
) && string_in_list(nlist
, entry
))
791 newentry
= append_null_to_list((void ***)clist
);
793 if (strncmp(entry
, "name=", 5) == 0)
794 copy
= must_copy_string(entry
);
795 else if (string_in_list(klist
, entry
))
796 copy
= must_copy_string(entry
);
798 copy
= cgv1_must_prefix_named(entry
);
800 (*clist
)[newentry
] = copy
;
803 /* Get the controllers from a mountinfo line. There are other ways we could get
804 * this info. For lxcfs, field 3 is /cgroup/controller-list. For cgroupfs, we
805 * could parse the mount options. But we simply assume that the mountpoint must
806 * be /sys/fs/cgroup/controller-list
808 static char **cgv1_get_proc_mountinfo_controllers(char **klist
, char **nlist
, char *line
)
812 char *saveptr
= NULL
;
817 for (i
= 0; i
< 4; i
++) {
826 if (strncmp(p
, "/sys/fs/cgroup/", 15) != 0)
836 for (tok
= strtok_r(p
, ",", &saveptr
); tok
;
837 tok
= strtok_r(NULL
, ",", &saveptr
))
838 must_append_controller(klist
, nlist
, &aret
, tok
);
843 /* Check if a cgroupfs v2 controller is present in the string @cgline. */
844 static bool cgv1_controller_in_clist(char *cgline
, char *c
)
847 char *tok
, *eol
, *tmp
;
848 char *saveptr
= NULL
;
850 eol
= strchr(cgline
, ':');
855 tmp
= alloca(len
+ 1);
856 memcpy(tmp
, cgline
, len
);
859 for (tok
= strtok_r(tmp
, ",", &saveptr
); tok
;
860 tok
= strtok_r(NULL
, ",", &saveptr
)) {
861 if (strcmp(tok
, c
) == 0)
867 /* Get current cgroup from the /proc/<pid>/cgroup file passed in via @basecginfo
868 * of a given cgv1 controller passed in via @controller.
870 static char *cgv1_get_current_cgroup(char *basecginfo
, char *controller
)
882 if (cgv1_controller_in_clist(p
, controller
)) {
888 return copy_to_eol(p
);
900 /* Remove /init.scope from string @cg. This will mostly affect systemd-based
903 #define INIT_SCOPE "/init.scope"
904 static void cg_systemd_prune_init_scope(char *cg
)
911 point
= cg
+ strlen(cg
) - strlen(INIT_SCOPE
);
915 if (strcmp(point
, INIT_SCOPE
) == 0) {
923 /* Add new info about a mounted cgroupfs v1 hierarchy. Includes the controllers
924 * mounted into that hierarchy (e.g. cpu,cpuacct), the mountpoint of that
925 * hierarchy (/sys/fs/cgroup/<controller>, the base cgroup of the current
926 * process gathered from /proc/self/cgroup, and the init cgroup of PID1 gathered
927 * from /proc/1/cgroup.
929 static void cgv1_add_controller(char **clist
, char *mountpoint
, char *base_cgroup
, char *init_cgroup
)
931 struct cgv1_hierarchy
*new;
934 new = must_alloc(sizeof(*new));
935 new->controllers
= clist
;
936 new->mountpoint
= mountpoint
;
937 new->base_cgroup
= base_cgroup
;
938 new->fullcgpath
= NULL
;
939 new->create_rw_cgroup
= false;
940 new->init_cgroup
= init_cgroup
;
941 new->systemd_user_slice
= false;
943 newentry
= append_null_to_list((void ***)&cgv1_hierarchies
);
944 cgv1_hierarchies
[newentry
] = new;
947 /* Add new info about the mounted cgroupfs v2 hierarchy. Can (but doesn't
948 * currently) include the controllers mounted into the hierarchy (e.g. memory,
949 * pids, blkio), the mountpoint of that hierarchy (Should usually be
950 * /sys/fs/cgroup but some init systems seems to think it might be a good idea
951 * to also mount empty cgroupfs v2 hierarchies at /sys/fs/cgroup/systemd.), the
952 * base cgroup of the current process gathered from /proc/self/cgroup, and the
953 * init cgroup of PID1 gathered from /proc/1/cgroup.
955 static void cgv2_add_controller(char **clist
, char *mountpoint
, char *base_cgroup
, char *init_cgroup
, bool systemd_user_slice
)
957 struct cgv2_hierarchy
*new;
960 new = must_alloc(sizeof(*new));
961 new->controllers
= clist
;
962 new->mountpoint
= mountpoint
;
963 new->base_cgroup
= base_cgroup
;
964 new->fullcgpath
= NULL
;
965 new->create_rw_cgroup
= false;
966 new->init_cgroup
= init_cgroup
;
967 new->systemd_user_slice
= systemd_user_slice
;
969 newentry
= append_null_to_list((void ***)&cgv2_hierarchies
);
970 cgv2_hierarchies
[newentry
] = new;
973 /* In Ubuntu 14.04, the paths created for us were
974 * '/user/$uid.user/$something.session' This can be merged better with
975 * systemd_created_slice_for_us(), but keeping it separate makes it easier to
976 * reason about the correctness.
978 static bool cg_systemd_under_user_slice_1(const char *in
, uid_t uid
)
986 copy
= must_copy_string(in
);
987 if (strlen(copy
) < strlen("/user/1.user/1.session"))
989 p
= copy
+ strlen(copy
) - 1;
991 /* skip any trailing '/' (shouldn't be any, but be sure) */
992 while (p
>= copy
&& *p
== '/')
997 /* Get last path element */
998 while (p
>= copy
&& *p
!= '/')
1002 /* make sure it is something.session */
1003 len
= strlen(p
+ 1);
1004 if (len
< strlen("1.session") ||
1005 strncmp(p
+ 1 + len
- 8, ".session", 8) != 0)
1008 /* ok last path piece checks out, now check the second to last */
1010 while (p
>= copy
&& *(--p
) != '/')
1012 if (sscanf(p
+ 1, "%d.user/", &id
) != 1)
1025 /* So long as our path relative to init starts with /user.slice/user-$uid.slice,
1026 * assume it belongs to $uid and chown it
1028 static bool cg_systemd_under_user_slice_2(const char *base_cgroup
,
1029 const char *init_cgroup
, uid_t uid
)
1033 size_t curlen
, initlen
;
1035 curlen
= strlen(base_cgroup
);
1036 initlen
= strlen(init_cgroup
);
1037 if (curlen
<= initlen
)
1040 if (strncmp(base_cgroup
, init_cgroup
, initlen
) != 0)
1043 ret
= snprintf(buf
, 100, "/user.slice/user-%d.slice/", (int)uid
);
1044 if (ret
< 0 || ret
>= 100)
1048 initlen
= 0; // skip the '/'
1050 return strncmp(base_cgroup
+ initlen
, buf
, strlen(buf
)) == 0;
1053 /* The systemd-created path is: user-$uid.slice/session-c$session.scope. If that
1054 * is not the end of our systemd path, then we're not part of the PAM call that
1055 * created that path.
1057 * The last piece is chowned to $uid, the user- part not.
1058 * Note: If the user creates paths that look like what we're looking for to
1060 * - they fool us, we create new cgroups, and they get auto-logged-out.
1061 * - they fool a root sudo, systemd cgroup is not changed but chowned, and they
1062 * lose ownership of their cgroups
1064 static bool cg_systemd_created_user_slice(const char *base_cgroup
,
1065 const char *init_cgroup
,
1066 const char *in
, uid_t uid
)
1074 copy
= must_copy_string(in
);
1076 /* An old version of systemd has already created a cgroup for us. */
1077 if (cg_systemd_under_user_slice_1(in
, uid
))
1080 /* A new version of systemd has already created a cgroup for us. */
1081 if (cg_systemd_under_user_slice_2(base_cgroup
, init_cgroup
, uid
))
1084 if (strlen(copy
) < strlen("/user-0.slice/session-0.scope"))
1087 p
= copy
+ strlen(copy
) - 1;
1088 /* Skip any trailing '/' (shouldn't be any, but be sure). */
1089 while (p
>= copy
&& *p
== '/')
1095 /* Get last path element */
1096 while (p
>= copy
&& *p
!= '/')
1102 /* Make sure it is session-something.scope. */
1103 len
= strlen(p
+ 1);
1104 if (strncmp(p
+ 1, "session-", strlen("session-")) != 0 ||
1105 strncmp(p
+ 1 + len
- 6, ".scope", 6) != 0)
1108 /* Ok last path piece checks out, now check the second to last. */
1110 while (p
>= copy
&& *(--p
) != '/')
1113 if (sscanf(p
+ 1, "user-%d.slice/", &id
) != 1)
1126 /* Chown existing cgroup that systemd has already created for us. */
1127 static bool cg_systemd_chown_existing_cgroup(const char *mountpoint
,
1128 const char *base_cgroup
, uid_t uid
,
1129 gid_t gid
, bool systemd_user_slice
)
1133 if (!systemd_user_slice
)
1136 path
= must_make_path(mountpoint
, base_cgroup
, NULL
);
1138 /* A cgroup within name=systemd has already been created. So we only
1141 if (chown(path
, uid
, gid
) < 0)
1142 mysyslog(LOG_WARNING
, "Failed to chown %s to %d:%d: %m.\n",
1143 path
, (int)uid
, (int)gid
, NULL
);
1149 /* Detect and store information about cgroupfs v1 hierarchies. */
1150 static bool cgv1_init(uid_t uid
, gid_t gid
)
1153 struct cgv1_hierarchy
**it
;
1156 char **klist
= NULL
, **nlist
= NULL
;
1159 basecginfo
= read_file("/proc/self/cgroup");
1163 f
= fopen("/proc/self/mountinfo", "r");
1169 cgv1_get_controllers(&klist
, &nlist
);
1171 while (getline(&line
, &len
, f
) != -1) {
1172 char **controller_list
= NULL
;
1173 char *mountpoint
, *base_cgroup
;
1175 if (is_lxcfs(line
) || !is_cgv1(line
))
1178 controller_list
= cgv1_get_proc_mountinfo_controllers(klist
, nlist
, line
);
1179 if (!controller_list
)
1182 if (cgv1_controller_list_is_dup(cgv1_hierarchies
,
1184 free(controller_list
);
1188 mountpoint
= get_mountpoint(line
);
1190 free_string_list(controller_list
);
1194 base_cgroup
= cgv1_get_current_cgroup(basecginfo
, controller_list
[0]);
1196 free_string_list(controller_list
);
1201 lxcfs_debug("Detected cgroupfs v1 controller \"%s\" with "
1202 "mountpoint \"%s\" and cgroup \"%s\".\n",
1203 controller_list
[0], mountpoint
, base_cgroup
);
1204 cgv1_add_controller(controller_list
, mountpoint
, base_cgroup
,
1207 free_string_list(klist
);
1208 free_string_list(nlist
);
1213 /* Retrieve init cgroup path for all controllers. */
1214 basecginfo
= read_file("/proc/1/cgroup");
1218 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1219 if ((*it
)->controllers
) {
1220 char *init_cgroup
, *user_slice
;
1221 /* We've already stored the controller and received its
1222 * current cgroup. If we now fail to retrieve its init
1223 * cgroup, we should probably fail.
1225 init_cgroup
= cgv1_get_current_cgroup(basecginfo
, (*it
)->controllers
[0]);
1230 cg_systemd_prune_init_scope(init_cgroup
);
1231 (*it
)->init_cgroup
= init_cgroup
;
1232 lxcfs_debug("cgroupfs v1 controller \"%s\" has init "
1234 (*(*it
)->controllers
), init_cgroup
);
1235 /* Check whether systemd has already created a cgroup
1238 user_slice
= must_make_path((*it
)->mountpoint
, (*it
)->base_cgroup
, NULL
);
1239 if (cg_systemd_created_user_slice((*it
)->base_cgroup
, (*it
)->init_cgroup
, user_slice
, uid
))
1240 (*it
)->systemd_user_slice
= true;
1248 /* __typeof__ should be safe to use with all compilers. */
1249 typedef __typeof__(((struct statfs
*)NULL
)->f_type
) fs_type_magic
;
1250 /* Check whether given mountpoint has mount type specified via @magic_val. */
1251 static bool has_fs_type(const struct statfs
*fs
, fs_type_magic magic_val
)
1253 return (fs
->f_type
== (fs_type_magic
)magic_val
);
1256 /* Check whether @path is a cgroupfs v1 or cgroupfs v2 mount. Returns -1 if
1257 * statfs fails. If @path is null /sys/fs/cgroup is checked.
1259 static int cg_get_version_of_mntpt(const char *path
)
1265 ret
= statfs(path
, &sb
);
1267 ret
= statfs("/sys/fs/cgroup", &sb
);
1272 if (has_fs_type(&sb
, CGROUP_SUPER_MAGIC
))
1274 else if (has_fs_type(&sb
, CGROUP2_SUPER_MAGIC
))
1280 /* Detect and store information about the cgroupfs v2 hierarchy. Currently only
1281 * deals with the empty v2 hierachy as we do not retrieve enabled controllers.
1283 static bool cgv2_init(uid_t uid
, gid_t gid
)
1288 char *current_cgroup
= NULL
, *init_cgroup
= NULL
;
1292 current_cgroup
= cgv2_get_current_cgroup(getpid());
1293 if (!current_cgroup
) {
1294 /* No v2 hierarchy present. We're done. */
1299 init_cgroup
= cgv2_get_current_cgroup(1);
1301 /* If we're here and didn't fail already above, then something's
1302 * certainly wrong, so error this time.
1306 cg_systemd_prune_init_scope(init_cgroup
);
1308 /* Check if the v2 hierarchy is mounted at its standard location.
1309 * If so we can skip the rest of the work here. Although the unified
1310 * hierarchy can be mounted multiple times, each of those mountpoints
1311 * will expose identical information.
1313 if (cg_get_version_of_mntpt("/sys/fs/cgroup") == 2) {
1315 bool has_user_slice
= false;
1317 mountpoint
= must_copy_string("/sys/fs/cgroup");
1321 user_slice
= must_make_path(mountpoint
, current_cgroup
, NULL
);
1322 if (cg_systemd_created_user_slice(current_cgroup
, init_cgroup
, user_slice
, uid
))
1323 has_user_slice
= true;
1326 cgv2_add_controller(NULL
, mountpoint
, current_cgroup
, init_cgroup
, has_user_slice
);
1332 f
= fopen("/proc/self/mountinfo", "r");
1336 /* we support simple cgroup mounts and lxcfs mounts */
1337 while (getline(&line
, &len
, f
) != -1) {
1339 bool has_user_slice
= false;
1343 mountpoint
= get_mountpoint(line
);
1347 user_slice
= must_make_path(mountpoint
, current_cgroup
, NULL
);
1348 if (cg_systemd_created_user_slice(current_cgroup
, init_cgroup
, user_slice
, uid
))
1349 has_user_slice
= true;
1352 cgv2_add_controller(NULL
, mountpoint
, current_cgroup
, init_cgroup
, has_user_slice
);
1353 /* Although the unified hierarchy can be mounted multiple times,
1354 * each of those mountpoints will expose identical information.
1355 * So let the first mountpoint we find, win.
1360 lxcfs_debug("Detected cgroupfs v2 hierarchy at mountpoint \"%s\" with "
1361 "current cgroup \"%s\" and init cgroup \"%s\".\n",
1362 mountpoint
, current_cgroup
, init_cgroup
);
1372 /* Detect and store information about mounted cgroupfs v1 hierarchies and the
1373 * cgroupfs v2 hierarchy.
1374 * Detect whether we are on a pure cgroupfs v1, cgroupfs v2, or mixed system,
1375 * where some controllers are mounted into their standard cgroupfs v1 locations
1376 * (/sys/fs/cgroup/<controller>) and others are mounted into the cgroupfs v2
1377 * hierarchy (/sys/fs/cgroup).
1379 static bool cg_init(uid_t uid
, gid_t gid
)
1381 if (!cgv1_init(uid
, gid
))
1384 if (!cgv2_init(uid
, gid
))
1387 if (cgv1_hierarchies
&& cgv2_hierarchies
) {
1388 cg_mount_mode
= CGROUP_MIXED
;
1389 lxcfs_debug("%s\n", "Detected cgroupfs v1 and v2 hierarchies.");
1390 } else if (cgv1_hierarchies
&& !cgv2_hierarchies
) {
1391 cg_mount_mode
= CGROUP_PURE_V1
;
1392 lxcfs_debug("%s\n", "Detected cgroupfs v1 hierarchies.");
1393 } else if (cgv2_hierarchies
&& !cgv1_hierarchies
) {
1394 cg_mount_mode
= CGROUP_PURE_V2
;
1395 lxcfs_debug("%s\n", "Detected cgroupfs v2 hierarchies.");
1397 cg_mount_mode
= CGROUP_UNKNOWN
;
1398 mysyslog(LOG_ERR
, "Could not detect cgroupfs hierarchy.\n", NULL
);
1401 if (cg_mount_mode
== CGROUP_UNKNOWN
)
1407 /* Try to move/migrate us into @cgroup in a cgroupfs v1 hierarchy. */
1408 static bool cgv1_enter(const char *cgroup
)
1410 struct cgv1_hierarchy
**it
;
1412 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1414 bool entered
= false;
1416 if (!(*it
)->controllers
|| !(*it
)->mountpoint
||
1417 !(*it
)->init_cgroup
|| !(*it
)->create_rw_cgroup
)
1420 for (controller
= (*it
)->controllers
; controller
&& *controller
;
1424 if ((*it
)->systemd_user_slice
)
1427 path
= must_make_path((*it
)->mountpoint
,
1432 if (!file_exists(path
)) {
1434 path
= must_make_path((*it
)->mountpoint
,
1440 lxcfs_debug("Attempting to enter cgroupfs v1 hierarchy in \"%s\" cgroup.\n", path
);
1441 entered
= write_int(path
, (int)getpid());
1446 lxcfs_debug("Failed to enter cgroupfs v1 hierarchy in \"%s\" cgroup.\n", path
);
1456 /* Try to move/migrate us into @cgroup in the cgroupfs v2 hierarchy. */
1457 static bool cgv2_enter(const char *cgroup
)
1459 struct cgv2_hierarchy
*v2
;
1461 bool entered
= false;
1463 if (!cgv2_hierarchies
)
1466 v2
= *cgv2_hierarchies
;
1468 if (!v2
->mountpoint
|| !v2
->base_cgroup
)
1471 if (!v2
->create_rw_cgroup
|| v2
->systemd_user_slice
)
1474 path
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, cgroup
,
1475 "/cgroup.procs", NULL
);
1476 lxcfs_debug("Attempting to enter cgroupfs v2 hierarchy in cgroup \"%s\".\n", path
);
1477 entered
= write_int(path
, (int)getpid());
1479 lxcfs_debug("Failed to enter cgroupfs v2 hierarchy in cgroup \"%s\".\n", path
);
1489 /* Wrapper around cgv{1,2}_enter(). */
1490 static bool cg_enter(const char *cgroup
)
1492 if (!cgv1_enter(cgroup
)) {
1493 mysyslog(LOG_WARNING
, "cgroupfs v1: Failed to enter cgroups.\n", NULL
);
1497 if (!cgv2_enter(cgroup
)) {
1498 mysyslog(LOG_WARNING
, "cgroupfs v2: Failed to enter cgroups.\n", NULL
);
1505 /* Escape to root cgroup in all detected cgroupfs v1 hierarchies. */
1506 static void cgv1_escape(void)
1508 if (!cgv1_enter("/"))
1509 mysyslog(LOG_WARNING
, "cgroupfs v1: Failed to escape to init's cgroup.\n", NULL
);
1512 /* Escape to root cgroup in the cgroupfs v2 hierarchy. */
1513 static void cgv2_escape(void)
1515 if (!cgv2_enter("/"))
1516 mysyslog(LOG_WARNING
, "cgroupfs v2: Failed to escape to init's cgroup.\n", NULL
);
1519 /* Wrapper around cgv{1,2}_escape(). */
1520 static void cg_escape(void)
1526 /* Get uid and gid for @user. */
1527 static bool get_uid_gid(const char *user
, uid_t
*uid
, gid_t
*gid
)
1529 struct passwd
*pwent
;
1531 pwent
= getpwnam(user
);
1535 *uid
= pwent
->pw_uid
;
1536 *gid
= pwent
->pw_gid
;
1541 /* Create and chown @cgroup for all given controllers in a cgroupfs v1 hierarchy
1542 * (For example, create @cgroup for the cpu and cpuacct controller mounted into
1543 * /sys/fs/cgroup/cpu,cpuacct). Check if the path already exists and report back
1544 * to the caller in @existed.
1546 #define __PAM_CGFS_USER "/user/"
1547 #define __PAM_CGFS_USER_LEN 6
1548 static bool cgv1_create_one(struct cgv1_hierarchy
*h
, const char *cgroup
, uid_t uid
, gid_t gid
, bool *existed
)
1550 char *clean_base_cgroup
, *path
;
1552 struct cgv1_hierarchy
*it
;
1553 bool created
= false;
1556 for (controller
= it
->controllers
; controller
&& *controller
;
1559 /* If systemd has already created a cgroup for us, keep using
1562 if (cg_systemd_chown_existing_cgroup(it
->mountpoint
,
1563 it
->base_cgroup
, uid
, gid
,
1564 it
->systemd_user_slice
)) {
1568 /* We need to make sure that we do not create an endless chain
1569 * of sub-cgroups. So we check if we have already logged in
1570 * somehow (sudo -i, su, etc.) and have created a
1571 * /user/PAM_user/idx cgroup. If so, we skip that part. For most
1572 * cgroups this is unnecessary since we use the init_cgroup
1573 * anyway, but for controllers which have an existing systemd
1574 * cgroup that does not match the current uid, this is pretty
1577 if (strncmp(it
->base_cgroup
, __PAM_CGFS_USER
, __PAM_CGFS_USER_LEN
) == 0) {
1578 free(it
->base_cgroup
);
1579 it
->base_cgroup
= must_copy_string("/");
1582 strstr(it
->base_cgroup
, __PAM_CGFS_USER
);
1583 if (clean_base_cgroup
)
1584 *clean_base_cgroup
= '\0';
1587 path
= must_make_path(it
->mountpoint
, it
->init_cgroup
, cgroup
, NULL
);
1588 lxcfs_debug("Constructing path: %s.\n", path
);
1589 if (file_exists(path
)) {
1591 lxcfs_debug("%s existed.\n", path
);
1595 created
= mkdir_p(it
->mountpoint
, path
);
1600 if (chown(path
, uid
, gid
) < 0)
1601 lxcfs_debug("Failed to chown %s to %d:%d: %m.\n", path
,
1602 (int)uid
, (int)gid
);
1613 /* Try to remove @cgroup for all given controllers in a cgroupfs v1 hierarchy
1614 * (For example, try to remove @cgroup for the cpu and cpuacct controller
1615 * mounted into /sys/fs/cgroup/cpu,cpuacct). Ignores failures.
1617 static bool cgv1_remove_one(struct cgv1_hierarchy
*h
, const char *cgroup
)
1622 /* Better safe than sorry. */
1623 if (!h
->controllers
)
1626 /* Cgroups created by systemd for us which we re-use won't be removed
1627 * here, since we're using init_cgroup + cgroup as path instead of
1628 * base_cgroup + cgroup.
1630 path
= must_make_path(h
->mountpoint
, h
->init_cgroup
, cgroup
, NULL
);
1631 (void)recursive_rmdir(path
);
1637 /* Try to remove @cgroup the cgroupfs v2 hierarchy. */
1638 static bool cgv2_remove(const char *cgroup
)
1640 struct cgv2_hierarchy
*v2
;
1643 if (!cgv2_hierarchies
)
1646 v2
= *cgv2_hierarchies
;
1648 /* If we reused an already existing cgroup, don't bother trying to
1649 * remove (a potentially wrong)/the path.
1650 * Cgroups created by systemd for us which we re-use would be removed
1651 * here, since we're using base_cgroup + cgroup as path.
1653 if (v2
->systemd_user_slice
)
1656 path
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, cgroup
, NULL
);
1657 (void)recursive_rmdir(path
);
1663 /* Create @cgroup in all detected cgroupfs v1 hierarchy. If the creation fails
1664 * for any cgroupfs v1 hierarchy, remove all we have created so far. Report
1665 * back, to the caller if the creation failed due to @cgroup already existing
1668 static bool cgv1_create(const char *cgroup
, uid_t uid
, gid_t gid
, bool *existed
)
1670 struct cgv1_hierarchy
**it
, **rev_it
;
1671 bool all_created
= true;
1673 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1674 if (!(*it
)->controllers
|| !(*it
)->mountpoint
||
1675 !(*it
)->init_cgroup
|| !(*it
)->create_rw_cgroup
)
1678 if (!cgv1_create_one(*it
, cgroup
, uid
, gid
, existed
)) {
1679 all_created
= false;
1687 for (rev_it
= cgv1_hierarchies
; rev_it
&& *rev_it
&& (*rev_it
!= *it
);
1689 cgv1_remove_one(*rev_it
, cgroup
);
1694 /* Create @cgroup in the cgroupfs v2 hierarchy. Report back, to the caller if
1695 * the creation failed due to @cgroup already existing via @existed.
1697 static bool cgv2_create(const char *cgroup
, uid_t uid
, gid_t gid
, bool *existed
)
1699 char *clean_base_cgroup
;
1701 struct cgv2_hierarchy
*v2
;
1702 bool created
= false;
1704 if (!cgv2_hierarchies
|| !(*cgv2_hierarchies
)->create_rw_cgroup
)
1707 v2
= *cgv2_hierarchies
;
1709 /* We can't be placed under init's cgroup for the v2 hierarchy. We need
1710 * to be placed under our current cgroup.
1712 if (cg_systemd_chown_existing_cgroup(v2
->mountpoint
,
1713 v2
->base_cgroup
, uid
, gid
,
1714 v2
->systemd_user_slice
))
1717 /* We need to make sure that we do not create an endless chaing of
1718 * sub-cgroups. So we check if we have already logged in somehow (sudo
1719 * -i, su, etc.) and have created a /user/PAM_user/idx cgroup. If so, we
1722 if (strncmp(v2
->base_cgroup
, __PAM_CGFS_USER
, __PAM_CGFS_USER_LEN
) == 0) {
1723 free(v2
->base_cgroup
);
1724 v2
->base_cgroup
= must_copy_string("/");
1726 clean_base_cgroup
= strstr(v2
->base_cgroup
, __PAM_CGFS_USER
);
1727 if (clean_base_cgroup
)
1728 *clean_base_cgroup
= '\0';
1731 path
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, cgroup
, NULL
);
1732 lxcfs_debug("Constructing path \"%s\".\n", path
);
1733 if (file_exists(path
)) {
1735 lxcfs_debug("%s existed.\n", path
);
1740 created
= mkdir_p(v2
->mountpoint
, path
);
1746 if (chown(path
, uid
, gid
) < 0)
1747 mysyslog(LOG_WARNING
, "Failed to chown %s to %d:%d: %m.\n",
1748 path
, (int)uid
, (int)gid
, NULL
);
1754 /* Create writeable cgroups for @user at login. Details can be found in the
1755 * preamble/license at the top of this file.
1757 static int handle_login(const char *user
, uid_t uid
, gid_t gid
)
1761 char cg
[MAXPATHLEN
];
1766 ret
= snprintf(cg
, MAXPATHLEN
, "/user/%s/%d", user
, idx
);
1767 if (ret
< 0 || ret
>= MAXPATHLEN
) {
1768 mysyslog(LOG_ERR
, "Username too long.\n", NULL
);
1769 return PAM_SESSION_ERR
;
1773 if (!cgv2_create(cg
, uid
, gid
, &existed
)) {
1779 mysyslog(LOG_ERR
, "Failed to create a cgroup for user %s.\n", user
, NULL
);
1780 return PAM_SESSION_ERR
;
1784 if (!cgv1_create(cg
, uid
, gid
, &existed
)) {
1790 mysyslog(LOG_ERR
, "Failed to create a cgroup for user %s.\n", user
, NULL
);
1791 return PAM_SESSION_ERR
;
1794 if (!cg_enter(cg
)) {
1795 mysyslog( LOG_ERR
, "Failed to enter user cgroup %s for user %s.\n", cg
, user
, NULL
);
1796 return PAM_SESSION_ERR
;
1804 /* Try to prune cgroups we created and that now are empty from all cgroupfs v1
1807 static bool cgv1_prune_empty_cgroups(const char *user
)
1809 bool controller_removed
= true;
1810 bool all_removed
= true;
1811 struct cgv1_hierarchy
**it
;
1813 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1815 char *path_base
, *path_init
;
1818 if (!(*it
)->controllers
|| !(*it
)->mountpoint
||
1819 !(*it
)->init_cgroup
|| !(*it
)->create_rw_cgroup
)
1822 for (controller
= (*it
)->controllers
; controller
&& *controller
;
1824 bool path_base_rm
, path_init_rm
;
1826 path_base
= must_make_path((*it
)->mountpoint
, (*it
)->base_cgroup
, "/user", user
, NULL
);
1827 lxcfs_debug("cgroupfs v1: Trying to prune \"%s\".\n", path_base
);
1828 ret
= recursive_rmdir(path_base
);
1829 if (ret
== -ENOENT
|| ret
>= 0)
1830 path_base_rm
= true;
1832 path_base_rm
= false;
1835 path_init
= must_make_path((*it
)->mountpoint
, (*it
)->init_cgroup
, "/user", user
, NULL
);
1836 lxcfs_debug("cgroupfs v1: Trying to prune \"%s\".\n", path_init
);
1837 ret
= recursive_rmdir(path_init
);
1838 if (ret
== -ENOENT
|| ret
>= 0)
1839 path_init_rm
= true;
1841 path_init_rm
= false;
1844 if (!path_base_rm
&& !path_init_rm
) {
1845 controller_removed
= false;
1849 controller_removed
= true;
1852 if (!controller_removed
)
1853 all_removed
= false;
1859 /* Try to prune cgroup we created and that now is empty from the cgroupfs v2
1862 static bool cgv2_prune_empty_cgroups(const char *user
)
1865 struct cgv2_hierarchy
*v2
;
1866 char *path_base
, *path_init
;
1867 bool path_base_rm
, path_init_rm
;
1869 if (!cgv2_hierarchies
)
1872 v2
= *cgv2_hierarchies
;
1874 path_base
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, "/user", user
, NULL
);
1875 lxcfs_debug("cgroupfs v2: Trying to prune \"%s\".\n", path_base
);
1876 ret
= recursive_rmdir(path_base
);
1877 if (ret
== -ENOENT
|| ret
>= 0)
1878 path_base_rm
= true;
1880 path_base_rm
= false;
1883 path_init
= must_make_path(v2
->mountpoint
, v2
->init_cgroup
, "/user", user
, NULL
);
1884 lxcfs_debug("cgroupfs v2: Trying to prune \"%s\".\n", path_init
);
1885 ret
= recursive_rmdir(path_init
);
1886 if (ret
== -ENOENT
|| ret
>= 0)
1887 path_init_rm
= true;
1889 path_init_rm
= false;
1892 if (!path_base_rm
&& !path_init_rm
)
1898 /* Wrapper around cgv{1,2}_prune_empty_cgroups(). */
1899 static void cg_prune_empty_cgroups(const char *user
)
1901 (void)cgv1_prune_empty_cgroups(user
);
1902 (void)cgv2_prune_empty_cgroups(user
);
1905 /* Free allocated information for detected cgroupfs v1 hierarchies. */
1906 static void cgv1_free_hierarchies(void)
1908 struct cgv1_hierarchy
**it
;
1910 if (!cgv1_hierarchies
)
1913 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1914 if ((*it
)->controllers
) {
1916 for (tmp
= (*it
)->controllers
; tmp
&& *tmp
; tmp
++)
1919 free((*it
)->controllers
);
1921 free((*it
)->mountpoint
);
1922 free((*it
)->base_cgroup
);
1923 free((*it
)->fullcgpath
);
1924 free((*it
)->init_cgroup
);
1926 free(cgv1_hierarchies
);
1929 /* Free allocated information for the detected cgroupfs v2 hierarchy. */
1930 static void cgv2_free_hierarchies(void)
1932 struct cgv2_hierarchy
**it
;
1934 if (!cgv2_hierarchies
)
1937 for (it
= cgv2_hierarchies
; it
&& *it
; it
++) {
1938 if ((*it
)->controllers
) {
1940 for (tmp
= (*it
)->controllers
; tmp
&& *tmp
; tmp
++)
1943 free((*it
)->controllers
);
1945 free((*it
)->mountpoint
);
1946 free((*it
)->base_cgroup
);
1947 free((*it
)->fullcgpath
);
1948 free((*it
)->init_cgroup
);
1950 free(cgv2_hierarchies
);
1953 /* Wrapper around cgv{1,2}_free_hierarchies(). */
1954 static void cg_exit(void)
1956 cgv1_free_hierarchies();
1957 cgv2_free_hierarchies();
1960 int pam_sm_open_session(pam_handle_t
*pamh
, int flags
, int argc
,
1966 const char *PAM_user
= NULL
;
1968 ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
1969 if (ret
!= PAM_SUCCESS
) {
1970 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n", NULL
);
1971 return PAM_SESSION_ERR
;
1974 if (!get_uid_gid(PAM_user
, &uid
, &gid
)) {
1975 mysyslog(LOG_ERR
, "Failed to get uid and gid for %s.\n", PAM_user
, NULL
);
1976 return PAM_SESSION_ERR
;
1979 if (!cg_init(uid
, gid
)) {
1980 mysyslog(LOG_ERR
, "Failed to get list of controllers\n", NULL
);
1981 return PAM_SESSION_ERR
;
1984 /* Try to prune cgroups, that are actually empty but were still marked
1985 * as busy by the kernel so we couldn't remove them on session close.
1987 cg_prune_empty_cgroups(PAM_user
);
1989 if (cg_mount_mode
== CGROUP_UNKNOWN
)
1990 return PAM_SESSION_ERR
;
1992 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
1993 cg_mark_to_make_rw(argv
[1]);
1995 return handle_login(PAM_user
, uid
, gid
);
1998 int pam_sm_close_session(pam_handle_t
*pamh
, int flags
, int argc
,
2004 const char *PAM_user
= NULL
;
2006 ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
2007 if (ret
!= PAM_SUCCESS
) {
2008 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n", NULL
);
2009 return PAM_SESSION_ERR
;
2012 if (!get_uid_gid(PAM_user
, &uid
, &gid
)) {
2013 mysyslog(LOG_ERR
, "Failed to get uid and gid for %s.\n", PAM_user
, NULL
);
2014 return PAM_SESSION_ERR
;
2017 if (cg_mount_mode
== CGROUP_UNINITIALIZED
) {
2018 if (!cg_init(uid
, gid
))
2019 mysyslog(LOG_ERR
, "Failed to get list of controllers\n", NULL
);
2021 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
2022 cg_mark_to_make_rw(argv
[1]);
2025 cg_prune_empty_cgroups(PAM_user
);