]>
git.proxmox.com Git - mirror_lxcfs.git/blob - pam/pam_cgfs.c
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
);
126 static bool cg_belongs_to_uid_gid(const char *path
, uid_t uid
, gid_t gid
);
128 /* cgroupfs v1 prototypes. */
129 struct cgv1_hierarchy
{
135 bool create_rw_cgroup
;
136 bool systemd_user_slice
;
139 static struct cgv1_hierarchy
**cgv1_hierarchies
;
141 static void cgv1_add_controller(char **clist
, char *mountpoint
,
142 char *base_cgroup
, char *init_cgroup
);
143 static bool cgv1_controller_in_clist(char *cgline
, char *c
);
144 static bool cgv1_controller_lists_intersect(char **l1
, char **l2
);
145 static bool cgv1_controller_list_is_dup(struct cgv1_hierarchy
**hlist
,
147 static bool cgv1_create(const char *cgroup
, uid_t uid
, gid_t gid
,
149 static bool cgv1_create_one(struct cgv1_hierarchy
*h
, const char *cgroup
,
150 uid_t uid
, gid_t gid
, bool *existed
);
151 static bool cgv1_enter(const char *cgroup
);
152 static void cgv1_escape(void);
153 static bool cgv1_get_controllers(char ***klist
, char ***nlist
);
154 static char *cgv1_get_current_cgroup(char *basecginfo
, char *controller
);
155 static char **cgv1_get_proc_mountinfo_controllers(char **klist
, char **nlist
,
157 static bool cgv1_init(uid_t uid
, gid_t gid
);
158 static void cgv1_mark_to_make_rw(char **clist
);
159 static char *cgv1_must_prefix_named(char *entry
);
160 static bool cgv1_prune_empty_cgroups(const char *user
);
161 static bool cgv1_remove_one(struct cgv1_hierarchy
*h
, const char *cgroup
);
162 static bool is_cgv1(char *line
);
164 /* cgroupfs v2 prototypes. */
165 struct cgv2_hierarchy
{
171 bool create_rw_cgroup
;
172 bool systemd_user_slice
;
175 /* Actually this should only be a single hierarchy. But for the sake of
176 * parallelism and because the layout of the cgroupfs v2 is still somewhat
177 * changing, we'll leave it as an array of structs.
179 static struct cgv2_hierarchy
**cgv2_hierarchies
;
181 static void cgv2_add_controller(char **clist
, char *mountpoint
,
182 char *base_cgroup
, char *init_cgroup
,
183 bool systemd_user_slice
);
184 static bool cgv2_create(const char *cgroup
, uid_t uid
, gid_t gid
,
186 static bool cgv2_enter(const char *cgroup
);
187 static void cgv2_escape(void);
188 static char *cgv2_get_current_cgroup(int pid
);
189 static bool cgv2_init(uid_t uid
, gid_t gid
);
190 static void cgv2_mark_to_make_rw(char **clist
);
191 static bool cgv2_prune_empty_cgroups(const char *user
);
192 static bool cgv2_remove(const char *cgroup
);
193 static bool is_cgv2(char *line
);
195 /* Common helper functions. */
196 static void mysyslog(int err
, const char *format
, ...)
200 va_start(args
, format
);
201 openlog("PAM-CGFS", LOG_CONS
|LOG_PID
, LOG_AUTH
);
202 vsyslog(err
, format
, args
);
207 /* realloc() pointer; do not fail. */
208 static void *must_realloc(void *orig
, size_t sz
)
213 ret
= realloc(orig
, sz
);
219 /* realloc() pointer in batch sizes; do not fail. */
220 #define BATCH_SIZE 50
221 static void batch_realloc(char **mem
, size_t oldlen
, size_t newlen
)
223 int newbatches
= (newlen
/ BATCH_SIZE
) + 1;
224 int oldbatches
= (oldlen
/ BATCH_SIZE
) + 1;
226 if (!*mem
|| newbatches
> oldbatches
)
227 *mem
= must_realloc(*mem
, newbatches
* BATCH_SIZE
);
230 /* Append lines as is to pointer; do not fail. */
231 static void append_line(char **dest
, size_t oldlen
, char *new, size_t newlen
)
233 size_t full
= oldlen
+ newlen
;
235 batch_realloc(dest
, oldlen
, full
+ 1);
237 memcpy(*dest
+ oldlen
, new, newlen
+ 1);
240 /* Read in whole file and return allocated pointer. */
241 static char *read_file(char *fnam
)
245 char *line
= NULL
, *buf
= NULL
;
246 size_t len
= 0, fulllen
= 0;
248 f
= fopen(fnam
, "r");
252 while ((linelen
= getline(&line
, &len
, f
)) != -1) {
253 append_line(&buf
, fulllen
, line
, linelen
);
263 /* Given a pointer to a null-terminated array of pointers, realloc to add one
264 * entry, and point the new entry to NULL. Do not fail. Return the index to the
265 * second-to-last entry - that is, the one which is now available for use
266 * (keeping the list null-terminated).
268 static int append_null_to_list(void ***list
)
273 for (; (*list
)[newentry
]; newentry
++) {
277 *list
= must_realloc(*list
, (newentry
+ 2) * sizeof(void **));
278 (*list
)[newentry
+ 1] = NULL
;
283 /* Make allocated copy of string; do not fail. */
284 static char *must_copy_string(const char *entry
)
298 /* Append new entry to null-terminated array of pointer; make sure that array of
299 * pointers will still be null-terminated.
301 static void must_append_string(char ***list
, char *entry
)
306 newentry
= append_null_to_list((void ***)list
);
307 copy
= must_copy_string(entry
);
308 (*list
)[newentry
] = copy
;
311 /* Remove newlines from string. */
312 static void trim(char *s
)
314 size_t len
= strlen(s
);
316 while (s
[len
- 1] == '\n')
320 /* Allocate pointer; do not fail. */
321 static void *must_alloc(size_t sz
)
323 return must_realloc(NULL
, sz
);
326 /* Make allocated copy of string. End of string is taken to be '\n'. */
327 static char *copy_to_eol(char *s
)
329 char *newline
, *sret
;
332 newline
= strchr(s
, '\n');
337 sret
= must_alloc(len
+ 1);
338 memcpy(sret
, s
, len
);
344 /* Check if given entry under /proc/<pid>/mountinfo is a fuse.lxcfs mount. */
345 static bool is_lxcfs(const char *line
)
347 char *p
= strstr(line
, " - ");
351 return strncmp(p
, " - fuse.lxcfs ", 14) == 0;
354 /* Check if given entry under /proc/<pid>/mountinfo is a cgroupfs v1 mount. */
355 static bool is_cgv1(char *line
)
357 char *p
= strstr(line
, " - ");
361 return strncmp(p
, " - cgroup ", 10) == 0;
364 /* Check if given entry under /proc/<pid>/mountinfo is a cgroupfs v2 mount. */
365 static bool is_cgv2(char *line
)
367 char *p
= strstr(line
, " - ");
371 return strncmp(p
, " - cgroup2 ", 11) == 0;
374 /* Given a null-terminated array of strings, check whether @entry is one of the
377 static bool string_in_list(char **list
, const char *entry
)
381 for (it
= list
; it
&& *it
; it
++)
382 if (strcmp(*it
, entry
) == 0)
388 /* Free null-terminated array of strings. */
389 static void free_string_list(char **list
)
393 for (it
= list
; it
&& *it
; it
++)
398 /* Concatenate all passed-in strings into one path. Do not fail. If any piece
399 * is not prefixed with '/', add a '/'. Does not remove duplicate '///' from the
402 static char *must_make_path(const char *first
, ...)
408 full_len
= strlen(first
);
410 dest
= must_copy_string(first
);
412 va_start(args
, first
);
413 while ((cur
= va_arg(args
, char *)) != NULL
) {
414 full_len
+= strlen(cur
);
419 dest
= must_realloc(dest
, full_len
+ 1);
431 /* Write single integer to file. */
432 static bool write_int(char *path
, int v
)
437 f
= fopen(path
, "w");
441 if (fprintf(f
, "%d\n", v
) < 0)
450 /* Check if a given file exists. */
451 static bool file_exists(const char *f
)
455 return stat(f
, &statbuf
) == 0;
458 /* Create directory and (if necessary) its parents. */
459 static bool mkdir_p(const char *root
, char *path
)
463 if (strlen(path
) < strlen(root
))
466 if (strlen(path
) == strlen(root
))
469 b
= path
+ strlen(root
) + 1;
471 while (*b
&& (*b
== '/'))
477 while (*e
&& *e
!= '/')
484 if (file_exists(path
))
487 if (mkdir(path
, 0755) < 0) {
488 lxcfs_debug("Failed to create %s: %m.\n", path
);
503 /* Recursively remove directory and its parents. */
504 static int recursive_rmdir(char *dirname
)
506 struct dirent
*direntp
;
510 dir
= opendir(dirname
);
514 while ((direntp
= readdir(dir
))) {
521 if (!strcmp(direntp
->d_name
, ".") ||
522 !strcmp(direntp
->d_name
, ".."))
525 pathname
= must_make_path(dirname
, direntp
->d_name
, NULL
);
527 if (lstat(pathname
, &st
)) {
529 lxcfs_debug("Failed to stat %s.\n", pathname
);
534 if (!S_ISDIR(st
.st_mode
))
537 if (recursive_rmdir(pathname
) < 0)
543 if (rmdir(dirname
) < 0) {
545 lxcfs_debug("Failed to delete %s: %m.\n", dirname
);
549 if (closedir(dir
) < 0) {
551 lxcfs_debug("Failed to delete %s: %m.\n", dirname
);
558 /* Add new entry to null-terminated array of pointers. Make sure array is still
561 static void must_add_to_list(char ***clist
, char *entry
)
565 newentry
= append_null_to_list((void ***)clist
);
566 (*clist
)[newentry
] = must_copy_string(entry
);
569 /* Get mountpoint from a /proc/<pid>/mountinfo line. */
570 static char *get_mountpoint(char *line
)
578 for (i
= 0; i
< 4; i
++) {
590 sret
= must_alloc(len
+ 1);
591 memcpy(sret
, p
, len
);
597 /* Create list of cgroupfs v1 controller found under /proc/self/cgroup. Skips
598 * the 0::/some/path cgroupfs v2 hierarchy listed. Splits controllers into
599 * kernel controllers (@klist) and named controllers (@nlist).
601 static bool cgv1_get_controllers(char ***klist
, char ***nlist
)
607 f
= fopen("/proc/self/cgroup", "r");
611 while (getline(&line
, &len
, f
) != -1) {
613 char *saveptr
= NULL
;
615 p
= strchr(line
, ':');
625 /* Skip the v2 hierarchy. */
629 for (tok
= strtok_r(p
, ",", &saveptr
); tok
;
630 tok
= strtok_r(NULL
, ",", &saveptr
)) {
631 if (strncmp(tok
, "name=", 5) == 0)
632 must_append_string(nlist
, tok
);
634 must_append_string(klist
, tok
);
644 /* Get list of controllers for cgroupfs v2 hierarchy by looking at
645 * cgroup.controllers and/or cgroup.subtree_control of a given (parent) cgroup.
646 static bool cgv2_get_controllers(char ***klist)
652 /* Get current cgroup from /proc/self/cgroup for the cgroupfs v2 hierarchy. */
653 static char *cgv2_get_current_cgroup(int pid
)
657 char *current_cgroup
;
659 /* The largest integer that can fit into long int is 2^64. This is a
660 * 20-digit number. */
661 #define __PIDLEN /* /proc */ 5 + /* /pid-to-str */ 21 + /* /cgroup */ 7 + /* \0 */ 1
664 ret
= snprintf(path
, __PIDLEN
, "/proc/%d/cgroup", pid
);
665 if (ret
< 0 || ret
>= __PIDLEN
)
668 cgroups_v2
= read_file(path
);
672 current_cgroup
= strstr(cgroups_v2
, "0::/");
676 current_cgroup
= current_cgroup
+ 3;
677 copy
= copy_to_eol(current_cgroup
);
689 /* Given two null-terminated lists of strings, return true if any string is in
692 static bool cgv1_controller_lists_intersect(char **l1
, char **l2
)
699 for (it
= l1
; it
&& *it
; it
++)
700 if (string_in_list(l2
, *it
))
706 /* For a null-terminated list of controllers @clist, return true if any of those
707 * controllers is already listed the null-terminated list of hierarchies @hlist.
708 * Realistically, if one is present, all must be present.
710 static bool cgv1_controller_list_is_dup(struct cgv1_hierarchy
**hlist
, char **clist
)
712 struct cgv1_hierarchy
**it
;
714 for (it
= hlist
; it
&& *it
; it
++)
715 if ((*it
)->controllers
)
716 if (cgv1_controller_lists_intersect((*it
)->controllers
, clist
))
722 /* Set boolean to mark controllers under which we are supposed create a
725 static void cgv1_mark_to_make_rw(char **clist
)
727 struct cgv1_hierarchy
**it
;
729 for (it
= cgv1_hierarchies
; it
&& *it
; it
++)
730 if ((*it
)->controllers
)
731 if (cgv1_controller_lists_intersect((*it
)->controllers
, clist
))
732 (*it
)->create_rw_cgroup
= true;
735 /* Set boolean to mark whether we are supposed to create a writeable cgroup in
736 * the cgroupfs v2 hierarchy.
738 static void cgv2_mark_to_make_rw(char **clist
)
740 if (string_in_list(clist
, "unified"))
741 if (cgv2_hierarchies
)
742 (*cgv2_hierarchies
)->create_rw_cgroup
= true;
745 /* Wrapper around cgv{1,2}_mark_to_make_rw(). */
746 static void cg_mark_to_make_rw(const char *cstring
)
749 char *saveptr
= NULL
;
752 copy
= must_copy_string(cstring
);
754 for (tok
= strtok_r(copy
, ",", &saveptr
); tok
;
755 tok
= strtok_r(NULL
, ",", &saveptr
))
756 must_add_to_list(&clist
, tok
);
760 cgv1_mark_to_make_rw(clist
);
761 cgv2_mark_to_make_rw(clist
);
763 free_string_list(clist
);
766 /* Prefix any named controllers with "name=", e.g. "name=systemd". */
767 static char *cgv1_must_prefix_named(char *entry
)
774 s
= must_alloc(len
+ 6);
776 ret
= snprintf(s
, len
+ 6, "name=%s", entry
);
777 if (ret
< 0 || (size_t)ret
>= (len
+ 6))
783 /* Append kernel controller in @klist or named controller in @nlist to @clist */
784 static void must_append_controller(char **klist
, char **nlist
, char ***clist
, char *entry
)
789 if (string_in_list(klist
, entry
) && string_in_list(nlist
, entry
))
792 newentry
= append_null_to_list((void ***)clist
);
794 if (strncmp(entry
, "name=", 5) == 0)
795 copy
= must_copy_string(entry
);
796 else if (string_in_list(klist
, entry
))
797 copy
= must_copy_string(entry
);
799 copy
= cgv1_must_prefix_named(entry
);
801 (*clist
)[newentry
] = copy
;
804 /* Get the controllers from a mountinfo line. There are other ways we could get
805 * this info. For lxcfs, field 3 is /cgroup/controller-list. For cgroupfs, we
806 * could parse the mount options. But we simply assume that the mountpoint must
807 * be /sys/fs/cgroup/controller-list
809 static char **cgv1_get_proc_mountinfo_controllers(char **klist
, char **nlist
, char *line
)
813 char *saveptr
= NULL
;
818 for (i
= 0; i
< 4; i
++) {
827 if (strncmp(p
, "/sys/fs/cgroup/", 15) != 0)
837 for (tok
= strtok_r(p
, ",", &saveptr
); tok
;
838 tok
= strtok_r(NULL
, ",", &saveptr
))
839 must_append_controller(klist
, nlist
, &aret
, tok
);
844 /* Check if a cgroupfs v2 controller is present in the string @cgline. */
845 static bool cgv1_controller_in_clist(char *cgline
, char *c
)
848 char *tok
, *eol
, *tmp
;
849 char *saveptr
= NULL
;
851 eol
= strchr(cgline
, ':');
856 tmp
= alloca(len
+ 1);
857 memcpy(tmp
, cgline
, len
);
860 for (tok
= strtok_r(tmp
, ",", &saveptr
); tok
;
861 tok
= strtok_r(NULL
, ",", &saveptr
)) {
862 if (strcmp(tok
, c
) == 0)
868 /* Get current cgroup from the /proc/<pid>/cgroup file passed in via @basecginfo
869 * of a given cgv1 controller passed in via @controller.
871 static char *cgv1_get_current_cgroup(char *basecginfo
, char *controller
)
883 if (cgv1_controller_in_clist(p
, controller
)) {
889 return copy_to_eol(p
);
901 /* Remove /init.scope from string @cg. This will mostly affect systemd-based
904 #define INIT_SCOPE "/init.scope"
905 static void cg_systemd_prune_init_scope(char *cg
)
912 point
= cg
+ strlen(cg
) - strlen(INIT_SCOPE
);
916 if (strcmp(point
, INIT_SCOPE
) == 0) {
924 /* Add new info about a mounted cgroupfs v1 hierarchy. Includes the controllers
925 * mounted into that hierarchy (e.g. cpu,cpuacct), the mountpoint of that
926 * hierarchy (/sys/fs/cgroup/<controller>, the base cgroup of the current
927 * process gathered from /proc/self/cgroup, and the init cgroup of PID1 gathered
928 * from /proc/1/cgroup.
930 static void cgv1_add_controller(char **clist
, char *mountpoint
, char *base_cgroup
, char *init_cgroup
)
932 struct cgv1_hierarchy
*new;
935 new = must_alloc(sizeof(*new));
936 new->controllers
= clist
;
937 new->mountpoint
= mountpoint
;
938 new->base_cgroup
= base_cgroup
;
939 new->fullcgpath
= NULL
;
940 new->create_rw_cgroup
= false;
941 new->init_cgroup
= init_cgroup
;
942 new->systemd_user_slice
= false;
944 newentry
= append_null_to_list((void ***)&cgv1_hierarchies
);
945 cgv1_hierarchies
[newentry
] = new;
948 /* Add new info about the mounted cgroupfs v2 hierarchy. Can (but doesn't
949 * currently) include the controllers mounted into the hierarchy (e.g. memory,
950 * pids, blkio), the mountpoint of that hierarchy (Should usually be
951 * /sys/fs/cgroup but some init systems seems to think it might be a good idea
952 * to also mount empty cgroupfs v2 hierarchies at /sys/fs/cgroup/systemd.), the
953 * base cgroup of the current process gathered from /proc/self/cgroup, and the
954 * init cgroup of PID1 gathered from /proc/1/cgroup.
956 static void cgv2_add_controller(char **clist
, char *mountpoint
, char *base_cgroup
, char *init_cgroup
, bool systemd_user_slice
)
958 struct cgv2_hierarchy
*new;
961 new = must_alloc(sizeof(*new));
962 new->controllers
= clist
;
963 new->mountpoint
= mountpoint
;
964 new->base_cgroup
= base_cgroup
;
965 new->fullcgpath
= NULL
;
966 new->create_rw_cgroup
= false;
967 new->init_cgroup
= init_cgroup
;
968 new->systemd_user_slice
= systemd_user_slice
;
970 newentry
= append_null_to_list((void ***)&cgv2_hierarchies
);
971 cgv2_hierarchies
[newentry
] = new;
974 /* In Ubuntu 14.04, the paths created for us were
975 * '/user/$uid.user/$something.session' This can be merged better with
976 * systemd_created_slice_for_us(), but keeping it separate makes it easier to
977 * reason about the correctness.
979 static bool cg_systemd_under_user_slice_1(const char *in
, uid_t uid
)
987 copy
= must_copy_string(in
);
988 if (strlen(copy
) < strlen("/user/1.user/1.session"))
990 p
= copy
+ strlen(copy
) - 1;
992 /* skip any trailing '/' (shouldn't be any, but be sure) */
993 while (p
>= copy
&& *p
== '/')
998 /* Get last path element */
999 while (p
>= copy
&& *p
!= '/')
1003 /* make sure it is something.session */
1004 len
= strlen(p
+ 1);
1005 if (len
< strlen("1.session") ||
1006 strncmp(p
+ 1 + len
- 8, ".session", 8) != 0)
1009 /* ok last path piece checks out, now check the second to last */
1011 while (p
>= copy
&& *(--p
) != '/')
1013 if (sscanf(p
+ 1, "%d.user/", &id
) != 1)
1026 /* So long as our path relative to init starts with /user.slice/user-$uid.slice,
1027 * assume it belongs to $uid and chown it
1029 static bool cg_systemd_under_user_slice_2(const char *base_cgroup
,
1030 const char *init_cgroup
, uid_t uid
)
1034 size_t curlen
, initlen
;
1036 curlen
= strlen(base_cgroup
);
1037 initlen
= strlen(init_cgroup
);
1038 if (curlen
<= initlen
)
1041 if (strncmp(base_cgroup
, init_cgroup
, initlen
) != 0)
1044 ret
= snprintf(buf
, 100, "/user.slice/user-%d.slice/", (int)uid
);
1045 if (ret
< 0 || ret
>= 100)
1049 initlen
= 0; // skip the '/'
1051 return strncmp(base_cgroup
+ initlen
, buf
, strlen(buf
)) == 0;
1054 /* The systemd-created path is: user-$uid.slice/session-c$session.scope. If that
1055 * is not the end of our systemd path, then we're not part of the PAM call that
1056 * created that path.
1058 * The last piece is chowned to $uid, the user- part not.
1059 * Note: If the user creates paths that look like what we're looking for to
1061 * - they fool us, we create new cgroups, and they get auto-logged-out.
1062 * - they fool a root sudo, systemd cgroup is not changed but chowned, and they
1063 * lose ownership of their cgroups
1065 static bool cg_systemd_created_user_slice(const char *base_cgroup
,
1066 const char *init_cgroup
,
1067 const char *in
, uid_t uid
)
1075 copy
= must_copy_string(in
);
1077 /* An old version of systemd has already created a cgroup for us. */
1078 if (cg_systemd_under_user_slice_1(in
, uid
))
1081 /* A new version of systemd has already created a cgroup for us. */
1082 if (cg_systemd_under_user_slice_2(base_cgroup
, init_cgroup
, uid
))
1085 if (strlen(copy
) < strlen("/user-0.slice/session-0.scope"))
1088 p
= copy
+ strlen(copy
) - 1;
1089 /* Skip any trailing '/' (shouldn't be any, but be sure). */
1090 while (p
>= copy
&& *p
== '/')
1096 /* Get last path element */
1097 while (p
>= copy
&& *p
!= '/')
1103 /* Make sure it is session-something.scope. */
1104 len
= strlen(p
+ 1);
1105 if (strncmp(p
+ 1, "session-", strlen("session-")) != 0 ||
1106 strncmp(p
+ 1 + len
- 6, ".scope", 6) != 0)
1109 /* Ok last path piece checks out, now check the second to last. */
1111 while (p
>= copy
&& *(--p
) != '/')
1114 if (sscanf(p
+ 1, "user-%d.slice/", &id
) != 1)
1127 /* Chown existing cgroup that systemd has already created for us. */
1128 static bool cg_systemd_chown_existing_cgroup(const char *mountpoint
,
1129 const char *base_cgroup
, uid_t uid
,
1130 gid_t gid
, bool systemd_user_slice
)
1134 if (!systemd_user_slice
)
1137 path
= must_make_path(mountpoint
, base_cgroup
, NULL
);
1139 /* A cgroup within name=systemd has already been created. So we only
1142 if (chown(path
, uid
, gid
) < 0)
1143 mysyslog(LOG_WARNING
, "Failed to chown %s to %d:%d: %m.\n",
1144 path
, (int)uid
, (int)gid
, NULL
);
1150 /* Detect and store information about cgroupfs v1 hierarchies. */
1151 static bool cgv1_init(uid_t uid
, gid_t gid
)
1154 struct cgv1_hierarchy
**it
;
1157 char **klist
= NULL
, **nlist
= NULL
;
1160 basecginfo
= read_file("/proc/self/cgroup");
1164 f
= fopen("/proc/self/mountinfo", "r");
1170 cgv1_get_controllers(&klist
, &nlist
);
1172 while (getline(&line
, &len
, f
) != -1) {
1173 char **controller_list
= NULL
;
1174 char *mountpoint
, *base_cgroup
;
1176 if (is_lxcfs(line
) || !is_cgv1(line
))
1179 controller_list
= cgv1_get_proc_mountinfo_controllers(klist
, nlist
, line
);
1180 if (!controller_list
)
1183 if (cgv1_controller_list_is_dup(cgv1_hierarchies
,
1185 free(controller_list
);
1189 mountpoint
= get_mountpoint(line
);
1191 free_string_list(controller_list
);
1195 base_cgroup
= cgv1_get_current_cgroup(basecginfo
, controller_list
[0]);
1197 free_string_list(controller_list
);
1202 lxcfs_debug("Detected cgroupfs v1 controller \"%s\" with "
1203 "mountpoint \"%s\" and cgroup \"%s\".\n",
1204 controller_list
[0], mountpoint
, base_cgroup
);
1205 cgv1_add_controller(controller_list
, mountpoint
, base_cgroup
,
1208 free_string_list(klist
);
1209 free_string_list(nlist
);
1214 /* Retrieve init cgroup path for all controllers. */
1215 basecginfo
= read_file("/proc/1/cgroup");
1219 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1220 if ((*it
)->controllers
) {
1221 char *init_cgroup
, *user_slice
;
1222 /* We've already stored the controller and received its
1223 * current cgroup. If we now fail to retrieve its init
1224 * cgroup, we should probably fail.
1226 init_cgroup
= cgv1_get_current_cgroup(basecginfo
, (*it
)->controllers
[0]);
1231 cg_systemd_prune_init_scope(init_cgroup
);
1232 (*it
)->init_cgroup
= init_cgroup
;
1233 lxcfs_debug("cgroupfs v1 controller \"%s\" has init "
1235 (*(*it
)->controllers
), init_cgroup
);
1236 /* Check whether systemd has already created a cgroup
1239 user_slice
= must_make_path((*it
)->mountpoint
, (*it
)->base_cgroup
, NULL
);
1240 if (cg_systemd_created_user_slice((*it
)->base_cgroup
, (*it
)->init_cgroup
, user_slice
, uid
))
1241 (*it
)->systemd_user_slice
= true;
1249 /* __typeof__ should be safe to use with all compilers. */
1250 typedef __typeof__(((struct statfs
*)NULL
)->f_type
) fs_type_magic
;
1251 /* Check whether given mountpoint has mount type specified via @magic_val. */
1252 static bool has_fs_type(const struct statfs
*fs
, fs_type_magic magic_val
)
1254 return (fs
->f_type
== (fs_type_magic
)magic_val
);
1257 /* Check whether @path is a cgroupfs v1 or cgroupfs v2 mount. Returns -1 if
1258 * statfs fails. If @path is null /sys/fs/cgroup is checked.
1260 static int cg_get_version_of_mntpt(const char *path
)
1266 ret
= statfs(path
, &sb
);
1268 ret
= statfs("/sys/fs/cgroup", &sb
);
1273 if (has_fs_type(&sb
, CGROUP_SUPER_MAGIC
))
1275 else if (has_fs_type(&sb
, CGROUP2_SUPER_MAGIC
))
1281 /* Detect and store information about the cgroupfs v2 hierarchy. Currently only
1282 * deals with the empty v2 hierachy as we do not retrieve enabled controllers.
1284 static bool cgv2_init(uid_t uid
, gid_t gid
)
1289 char *current_cgroup
= NULL
, *init_cgroup
= NULL
;
1293 current_cgroup
= cgv2_get_current_cgroup(getpid());
1294 if (!current_cgroup
) {
1295 /* No v2 hierarchy present. We're done. */
1300 init_cgroup
= cgv2_get_current_cgroup(1);
1302 /* If we're here and didn't fail already above, then something's
1303 * certainly wrong, so error this time.
1307 cg_systemd_prune_init_scope(init_cgroup
);
1309 /* Check if the v2 hierarchy is mounted at its standard location.
1310 * If so we can skip the rest of the work here. Although the unified
1311 * hierarchy can be mounted multiple times, each of those mountpoints
1312 * will expose identical information.
1314 if (cg_get_version_of_mntpt("/sys/fs/cgroup") == 2) {
1316 bool has_user_slice
= false;
1318 mountpoint
= must_copy_string("/sys/fs/cgroup");
1322 user_slice
= must_make_path(mountpoint
, current_cgroup
, NULL
);
1323 if (cg_systemd_created_user_slice(current_cgroup
, init_cgroup
, user_slice
, uid
))
1324 has_user_slice
= true;
1327 cgv2_add_controller(NULL
, mountpoint
, current_cgroup
, init_cgroup
, has_user_slice
);
1333 f
= fopen("/proc/self/mountinfo", "r");
1337 /* we support simple cgroup mounts and lxcfs mounts */
1338 while (getline(&line
, &len
, f
) != -1) {
1340 bool has_user_slice
= false;
1344 mountpoint
= get_mountpoint(line
);
1348 user_slice
= must_make_path(mountpoint
, current_cgroup
, NULL
);
1349 if (cg_systemd_created_user_slice(current_cgroup
, init_cgroup
, user_slice
, uid
))
1350 has_user_slice
= true;
1353 cgv2_add_controller(NULL
, mountpoint
, current_cgroup
, init_cgroup
, has_user_slice
);
1354 /* Although the unified hierarchy can be mounted multiple times,
1355 * each of those mountpoints will expose identical information.
1356 * So let the first mountpoint we find, win.
1361 lxcfs_debug("Detected cgroupfs v2 hierarchy at mountpoint \"%s\" with "
1362 "current cgroup \"%s\" and init cgroup \"%s\".\n",
1363 mountpoint
, current_cgroup
, init_cgroup
);
1373 /* Detect and store information about mounted cgroupfs v1 hierarchies and the
1374 * cgroupfs v2 hierarchy.
1375 * Detect whether we are on a pure cgroupfs v1, cgroupfs v2, or mixed system,
1376 * where some controllers are mounted into their standard cgroupfs v1 locations
1377 * (/sys/fs/cgroup/<controller>) and others are mounted into the cgroupfs v2
1378 * hierarchy (/sys/fs/cgroup).
1380 static bool cg_init(uid_t uid
, gid_t gid
)
1382 if (!cgv1_init(uid
, gid
))
1385 if (!cgv2_init(uid
, gid
))
1388 if (cgv1_hierarchies
&& cgv2_hierarchies
) {
1389 cg_mount_mode
= CGROUP_MIXED
;
1390 lxcfs_debug("%s\n", "Detected cgroupfs v1 and v2 hierarchies.");
1391 } else if (cgv1_hierarchies
&& !cgv2_hierarchies
) {
1392 cg_mount_mode
= CGROUP_PURE_V1
;
1393 lxcfs_debug("%s\n", "Detected cgroupfs v1 hierarchies.");
1394 } else if (cgv2_hierarchies
&& !cgv1_hierarchies
) {
1395 cg_mount_mode
= CGROUP_PURE_V2
;
1396 lxcfs_debug("%s\n", "Detected cgroupfs v2 hierarchies.");
1398 cg_mount_mode
= CGROUP_UNKNOWN
;
1399 mysyslog(LOG_ERR
, "Could not detect cgroupfs hierarchy.\n", NULL
);
1402 if (cg_mount_mode
== CGROUP_UNKNOWN
)
1408 /* Try to move/migrate us into @cgroup in a cgroupfs v1 hierarchy. */
1409 static bool cgv1_enter(const char *cgroup
)
1411 struct cgv1_hierarchy
**it
;
1413 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1415 bool entered
= false;
1417 if (!(*it
)->controllers
|| !(*it
)->mountpoint
||
1418 !(*it
)->init_cgroup
|| !(*it
)->create_rw_cgroup
)
1421 for (controller
= (*it
)->controllers
; controller
&& *controller
;
1425 /* We've already been placed in a user slice, so we
1426 * don't need to enter the cgroup again.
1428 if ((*it
)->systemd_user_slice
) {
1433 path
= must_make_path((*it
)->mountpoint
,
1438 if (!file_exists(path
)) {
1440 path
= must_make_path((*it
)->mountpoint
,
1446 lxcfs_debug("Attempting to enter cgroupfs v1 hierarchy in \"%s\" cgroup.\n", path
);
1447 entered
= write_int(path
, (int)getpid());
1452 lxcfs_debug("Failed to enter cgroupfs v1 hierarchy in \"%s\" cgroup.\n", path
);
1462 /* Try to move/migrate us into @cgroup in the cgroupfs v2 hierarchy. */
1463 static bool cgv2_enter(const char *cgroup
)
1465 struct cgv2_hierarchy
*v2
;
1467 bool entered
= false;
1469 if (!cgv2_hierarchies
)
1472 v2
= *cgv2_hierarchies
;
1474 if (!v2
->mountpoint
|| !v2
->base_cgroup
)
1477 if (!v2
->create_rw_cgroup
|| v2
->systemd_user_slice
)
1480 path
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, cgroup
,
1481 "/cgroup.procs", NULL
);
1482 lxcfs_debug("Attempting to enter cgroupfs v2 hierarchy in cgroup \"%s\".\n", path
);
1483 entered
= write_int(path
, (int)getpid());
1485 lxcfs_debug("Failed to enter cgroupfs v2 hierarchy in cgroup \"%s\".\n", path
);
1495 /* Wrapper around cgv{1,2}_enter(). */
1496 static bool cg_enter(const char *cgroup
)
1498 if (!cgv1_enter(cgroup
)) {
1499 mysyslog(LOG_WARNING
, "cgroupfs v1: Failed to enter cgroups.\n", NULL
);
1503 if (!cgv2_enter(cgroup
)) {
1504 mysyslog(LOG_WARNING
, "cgroupfs v2: Failed to enter cgroups.\n", NULL
);
1511 /* Escape to root cgroup in all detected cgroupfs v1 hierarchies. */
1512 static void cgv1_escape(void)
1514 if (!cgv1_enter("/"))
1515 mysyslog(LOG_WARNING
, "cgroupfs v1: Failed to escape to init's cgroup.\n", NULL
);
1518 /* Escape to root cgroup in the cgroupfs v2 hierarchy. */
1519 static void cgv2_escape(void)
1521 if (!cgv2_enter("/"))
1522 mysyslog(LOG_WARNING
, "cgroupfs v2: Failed to escape to init's cgroup.\n", NULL
);
1525 /* Wrapper around cgv{1,2}_escape(). */
1526 static void cg_escape(void)
1532 /* Get uid and gid for @user. */
1533 static bool get_uid_gid(const char *user
, uid_t
*uid
, gid_t
*gid
)
1535 struct passwd
*pwent
;
1537 pwent
= getpwnam(user
);
1541 *uid
= pwent
->pw_uid
;
1542 *gid
= pwent
->pw_gid
;
1547 /* Check if cgroup belongs to our uid and gid. If so, reuse it. */
1548 static bool cg_belongs_to_uid_gid(const char *path
, uid_t uid
, gid_t gid
)
1550 struct stat statbuf
;
1552 if (stat(path
, &statbuf
) < 0)
1555 if (!(statbuf
.st_uid
== uid
) || !(statbuf
.st_gid
== gid
))
1561 /* Create and chown @cgroup for all given controllers in a cgroupfs v1 hierarchy
1562 * (For example, create @cgroup for the cpu and cpuacct controller mounted into
1563 * /sys/fs/cgroup/cpu,cpuacct). Check if the path already exists and report back
1564 * to the caller in @existed.
1566 #define __PAM_CGFS_USER "/user/"
1567 #define __PAM_CGFS_USER_LEN 6
1568 static bool cgv1_create_one(struct cgv1_hierarchy
*h
, const char *cgroup
, uid_t uid
, gid_t gid
, bool *existed
)
1570 char *clean_base_cgroup
, *path
;
1572 struct cgv1_hierarchy
*it
;
1573 bool created
= false;
1577 for (controller
= it
->controllers
; controller
&& *controller
;
1580 /* If systemd has already created a cgroup for us, keep using
1583 if (cg_systemd_chown_existing_cgroup(it
->mountpoint
,
1584 it
->base_cgroup
, uid
, gid
,
1585 it
->systemd_user_slice
)) {
1589 /* We need to make sure that we do not create an endless chain
1590 * of sub-cgroups. So we check if we have already logged in
1591 * somehow (sudo -i, su, etc.) and have created a
1592 * /user/PAM_user/idx cgroup. If so, we skip that part. For most
1593 * cgroups this is unnecessary since we use the init_cgroup
1594 * anyway, but for controllers which have an existing systemd
1595 * cgroup that does not match the current uid, this is pretty
1598 if (strncmp(it
->base_cgroup
, __PAM_CGFS_USER
, __PAM_CGFS_USER_LEN
) == 0) {
1599 free(it
->base_cgroup
);
1600 it
->base_cgroup
= must_copy_string("/");
1603 strstr(it
->base_cgroup
, __PAM_CGFS_USER
);
1604 if (clean_base_cgroup
)
1605 *clean_base_cgroup
= '\0';
1608 path
= must_make_path(it
->mountpoint
, it
->init_cgroup
, cgroup
, NULL
);
1609 lxcfs_debug("Constructing path: %s.\n", path
);
1610 if (file_exists(path
)) {
1611 bool our_cg
= cg_belongs_to_uid_gid(path
, uid
, gid
);
1612 lxcfs_debug("%s existed and does %s have our uid and gid.\n", path
, our_cg
? "" : "not");
1620 created
= mkdir_p(it
->mountpoint
, path
);
1625 if (chown(path
, uid
, gid
) < 0)
1626 lxcfs_debug("Failed to chown %s to %d:%d: %m.\n", path
,
1627 (int)uid
, (int)gid
);
1638 /* Try to remove @cgroup for all given controllers in a cgroupfs v1 hierarchy
1639 * (For example, try to remove @cgroup for the cpu and cpuacct controller
1640 * mounted into /sys/fs/cgroup/cpu,cpuacct). Ignores failures.
1642 static bool cgv1_remove_one(struct cgv1_hierarchy
*h
, const char *cgroup
)
1647 /* Better safe than sorry. */
1648 if (!h
->controllers
)
1651 /* Cgroups created by systemd for us which we re-use won't be removed
1652 * here, since we're using init_cgroup + cgroup as path instead of
1653 * base_cgroup + cgroup.
1655 path
= must_make_path(h
->mountpoint
, h
->init_cgroup
, cgroup
, NULL
);
1656 (void)recursive_rmdir(path
);
1662 /* Try to remove @cgroup the cgroupfs v2 hierarchy. */
1663 static bool cgv2_remove(const char *cgroup
)
1665 struct cgv2_hierarchy
*v2
;
1668 if (!cgv2_hierarchies
)
1671 v2
= *cgv2_hierarchies
;
1673 /* If we reused an already existing cgroup, don't bother trying to
1674 * remove (a potentially wrong)/the path.
1675 * Cgroups created by systemd for us which we re-use would be removed
1676 * here, since we're using base_cgroup + cgroup as path.
1678 if (v2
->systemd_user_slice
)
1681 path
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, cgroup
, NULL
);
1682 (void)recursive_rmdir(path
);
1688 /* Create @cgroup in all detected cgroupfs v1 hierarchy. If the creation fails
1689 * for any cgroupfs v1 hierarchy, remove all we have created so far. Report
1690 * back, to the caller if the creation failed due to @cgroup already existing
1693 static bool cgv1_create(const char *cgroup
, uid_t uid
, gid_t gid
, bool *existed
)
1695 struct cgv1_hierarchy
**it
, **rev_it
;
1696 bool all_created
= true;
1698 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1699 if (!(*it
)->controllers
|| !(*it
)->mountpoint
||
1700 !(*it
)->init_cgroup
|| !(*it
)->create_rw_cgroup
)
1703 if (!cgv1_create_one(*it
, cgroup
, uid
, gid
, existed
)) {
1704 all_created
= false;
1712 for (rev_it
= cgv1_hierarchies
; rev_it
&& *rev_it
&& (*rev_it
!= *it
);
1714 cgv1_remove_one(*rev_it
, cgroup
);
1719 /* Create @cgroup in the cgroupfs v2 hierarchy. Report back, to the caller if
1720 * the creation failed due to @cgroup already existing via @existed.
1722 static bool cgv2_create(const char *cgroup
, uid_t uid
, gid_t gid
, bool *existed
)
1724 char *clean_base_cgroup
;
1726 struct cgv2_hierarchy
*v2
;
1727 bool created
= false;
1731 if (!cgv2_hierarchies
|| !(*cgv2_hierarchies
)->create_rw_cgroup
)
1734 v2
= *cgv2_hierarchies
;
1736 /* We can't be placed under init's cgroup for the v2 hierarchy. We need
1737 * to be placed under our current cgroup.
1739 if (cg_systemd_chown_existing_cgroup(v2
->mountpoint
,
1740 v2
->base_cgroup
, uid
, gid
,
1741 v2
->systemd_user_slice
))
1744 /* We need to make sure that we do not create an endless chaing of
1745 * sub-cgroups. So we check if we have already logged in somehow (sudo
1746 * -i, su, etc.) and have created a /user/PAM_user/idx cgroup. If so, we
1749 if (strncmp(v2
->base_cgroup
, __PAM_CGFS_USER
, __PAM_CGFS_USER_LEN
) == 0) {
1750 free(v2
->base_cgroup
);
1751 v2
->base_cgroup
= must_copy_string("/");
1753 clean_base_cgroup
= strstr(v2
->base_cgroup
, __PAM_CGFS_USER
);
1754 if (clean_base_cgroup
)
1755 *clean_base_cgroup
= '\0';
1758 path
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, cgroup
, NULL
);
1759 lxcfs_debug("Constructing path \"%s\".\n", path
);
1760 if (file_exists(path
)) {
1761 bool our_cg
= cg_belongs_to_uid_gid(path
, uid
, gid
);
1762 lxcfs_debug("%s existed and does %s have our uid and gid.\n", path
, our_cg
? "" : "not");
1771 created
= mkdir_p(v2
->mountpoint
, path
);
1777 if (chown(path
, uid
, gid
) < 0)
1778 mysyslog(LOG_WARNING
, "Failed to chown %s to %d:%d: %m.\n",
1779 path
, (int)uid
, (int)gid
, NULL
);
1785 /* Create writeable cgroups for @user at login. Details can be found in the
1786 * preamble/license at the top of this file.
1788 static int handle_login(const char *user
, uid_t uid
, gid_t gid
)
1792 char cg
[MAXPATHLEN
];
1797 ret
= snprintf(cg
, MAXPATHLEN
, "/user/%s/%d", user
, idx
);
1798 if (ret
< 0 || ret
>= MAXPATHLEN
) {
1799 mysyslog(LOG_ERR
, "Username too long.\n", NULL
);
1800 return PAM_SESSION_ERR
;
1804 if (!cgv2_create(cg
, uid
, gid
, &existed
)) {
1810 mysyslog(LOG_ERR
, "Failed to create a cgroup for user %s.\n", user
, NULL
);
1811 return PAM_SESSION_ERR
;
1815 if (!cgv1_create(cg
, uid
, gid
, &existed
)) {
1821 mysyslog(LOG_ERR
, "Failed to create a cgroup for user %s.\n", user
, NULL
);
1822 return PAM_SESSION_ERR
;
1825 if (!cg_enter(cg
)) {
1826 mysyslog( LOG_ERR
, "Failed to enter user cgroup %s for user %s.\n", cg
, user
, NULL
);
1827 return PAM_SESSION_ERR
;
1835 /* Try to prune cgroups we created and that now are empty from all cgroupfs v1
1838 static bool cgv1_prune_empty_cgroups(const char *user
)
1840 bool controller_removed
= true;
1841 bool all_removed
= true;
1842 struct cgv1_hierarchy
**it
;
1844 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1846 char *path_base
, *path_init
;
1849 if (!(*it
)->controllers
|| !(*it
)->mountpoint
||
1850 !(*it
)->init_cgroup
|| !(*it
)->create_rw_cgroup
)
1853 for (controller
= (*it
)->controllers
; controller
&& *controller
;
1855 bool path_base_rm
, path_init_rm
;
1857 path_base
= must_make_path((*it
)->mountpoint
, (*it
)->base_cgroup
, "/user", user
, NULL
);
1858 lxcfs_debug("cgroupfs v1: Trying to prune \"%s\".\n", path_base
);
1859 ret
= recursive_rmdir(path_base
);
1860 if (ret
== -ENOENT
|| ret
>= 0)
1861 path_base_rm
= true;
1863 path_base_rm
= false;
1866 path_init
= must_make_path((*it
)->mountpoint
, (*it
)->init_cgroup
, "/user", user
, NULL
);
1867 lxcfs_debug("cgroupfs v1: Trying to prune \"%s\".\n", path_init
);
1868 ret
= recursive_rmdir(path_init
);
1869 if (ret
== -ENOENT
|| ret
>= 0)
1870 path_init_rm
= true;
1872 path_init_rm
= false;
1875 if (!path_base_rm
&& !path_init_rm
) {
1876 controller_removed
= false;
1880 controller_removed
= true;
1883 if (!controller_removed
)
1884 all_removed
= false;
1890 /* Try to prune cgroup we created and that now is empty from the cgroupfs v2
1893 static bool cgv2_prune_empty_cgroups(const char *user
)
1896 struct cgv2_hierarchy
*v2
;
1897 char *path_base
, *path_init
;
1898 bool path_base_rm
, path_init_rm
;
1900 if (!cgv2_hierarchies
)
1903 v2
= *cgv2_hierarchies
;
1905 path_base
= must_make_path(v2
->mountpoint
, v2
->base_cgroup
, "/user", user
, NULL
);
1906 lxcfs_debug("cgroupfs v2: Trying to prune \"%s\".\n", path_base
);
1907 ret
= recursive_rmdir(path_base
);
1908 if (ret
== -ENOENT
|| ret
>= 0)
1909 path_base_rm
= true;
1911 path_base_rm
= false;
1914 path_init
= must_make_path(v2
->mountpoint
, v2
->init_cgroup
, "/user", user
, NULL
);
1915 lxcfs_debug("cgroupfs v2: Trying to prune \"%s\".\n", path_init
);
1916 ret
= recursive_rmdir(path_init
);
1917 if (ret
== -ENOENT
|| ret
>= 0)
1918 path_init_rm
= true;
1920 path_init_rm
= false;
1923 if (!path_base_rm
&& !path_init_rm
)
1929 /* Wrapper around cgv{1,2}_prune_empty_cgroups(). */
1930 static void cg_prune_empty_cgroups(const char *user
)
1932 (void)cgv1_prune_empty_cgroups(user
);
1933 (void)cgv2_prune_empty_cgroups(user
);
1936 /* Free allocated information for detected cgroupfs v1 hierarchies. */
1937 static void cgv1_free_hierarchies(void)
1939 struct cgv1_hierarchy
**it
;
1941 if (!cgv1_hierarchies
)
1944 for (it
= cgv1_hierarchies
; it
&& *it
; it
++) {
1945 if ((*it
)->controllers
) {
1947 for (tmp
= (*it
)->controllers
; tmp
&& *tmp
; tmp
++)
1950 free((*it
)->controllers
);
1952 free((*it
)->mountpoint
);
1953 free((*it
)->base_cgroup
);
1954 free((*it
)->fullcgpath
);
1955 free((*it
)->init_cgroup
);
1957 free(cgv1_hierarchies
);
1960 /* Free allocated information for the detected cgroupfs v2 hierarchy. */
1961 static void cgv2_free_hierarchies(void)
1963 struct cgv2_hierarchy
**it
;
1965 if (!cgv2_hierarchies
)
1968 for (it
= cgv2_hierarchies
; it
&& *it
; it
++) {
1969 if ((*it
)->controllers
) {
1971 for (tmp
= (*it
)->controllers
; tmp
&& *tmp
; tmp
++)
1974 free((*it
)->controllers
);
1976 free((*it
)->mountpoint
);
1977 free((*it
)->base_cgroup
);
1978 free((*it
)->fullcgpath
);
1979 free((*it
)->init_cgroup
);
1981 free(cgv2_hierarchies
);
1984 /* Wrapper around cgv{1,2}_free_hierarchies(). */
1985 static void cg_exit(void)
1987 cgv1_free_hierarchies();
1988 cgv2_free_hierarchies();
1991 int pam_sm_open_session(pam_handle_t
*pamh
, int flags
, int argc
,
1997 const char *PAM_user
= NULL
;
1999 ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
2000 if (ret
!= PAM_SUCCESS
) {
2001 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n", NULL
);
2002 return PAM_SESSION_ERR
;
2005 if (!get_uid_gid(PAM_user
, &uid
, &gid
)) {
2006 mysyslog(LOG_ERR
, "Failed to get uid and gid for %s.\n", PAM_user
, NULL
);
2007 return PAM_SESSION_ERR
;
2010 if (!cg_init(uid
, gid
)) {
2011 mysyslog(LOG_ERR
, "Failed to get list of controllers\n", NULL
);
2012 return PAM_SESSION_ERR
;
2015 /* Try to prune cgroups, that are actually empty but were still marked
2016 * as busy by the kernel so we couldn't remove them on session close.
2018 cg_prune_empty_cgroups(PAM_user
);
2020 if (cg_mount_mode
== CGROUP_UNKNOWN
)
2021 return PAM_SESSION_ERR
;
2023 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
2024 cg_mark_to_make_rw(argv
[1]);
2026 return handle_login(PAM_user
, uid
, gid
);
2029 int pam_sm_close_session(pam_handle_t
*pamh
, int flags
, int argc
,
2035 const char *PAM_user
= NULL
;
2037 ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
2038 if (ret
!= PAM_SUCCESS
) {
2039 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n", NULL
);
2040 return PAM_SESSION_ERR
;
2043 if (!get_uid_gid(PAM_user
, &uid
, &gid
)) {
2044 mysyslog(LOG_ERR
, "Failed to get uid and gid for %s.\n", PAM_user
, NULL
);
2045 return PAM_SESSION_ERR
;
2048 if (cg_mount_mode
== CGROUP_UNINITIALIZED
) {
2049 if (!cg_init(uid
, gid
))
2050 mysyslog(LOG_ERR
, "Failed to get list of controllers\n", NULL
);
2052 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
2053 cg_mark_to_make_rw(argv
[1]);
2056 cg_prune_empty_cgroups(PAM_user
);