]> git.proxmox.com Git - mirror_lxcfs.git/blame - pam/pam_cgfs.c
Merge pull request #205 from bmiklautz/uptime_read
[mirror_lxcfs.git] / pam / pam_cgfs.c
CommitLineData
df54106a
SH
1/* pam-cgfs
2 *
3 * Copyright © 2016 Canonical, Inc
4 * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
e65cfafc 5 * Author: Christian Brauner <christian.brauner@ubuntu.com>
df54106a 6 *
e65cfafc
CB
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.
df54106a
SH
14 * The cgroup created will be "user/$user/0" for the first session,
15 * "user/$user/1" for the second, etc.
16 *
e65cfafc
CB
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:
20 *
21 * user.slice/user-uid.slice/session-n.scope
22 *
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.
edd25678 29 *
df54106a
SH
30 * All requested cgroups must be mounted under /sys/fs/cgroup/$controller,
31 * no messing around with finding mountpoints.
32 *
33 * See COPYING file for details.
34 */
35
04742595 36#include <ctype.h>
e65cfafc
CB
37#include <dirent.h>
38#include <errno.h>
04742595 39#include <fcntl.h>
e65cfafc
CB
40#include <pwd.h>
41#include <stdarg.h>
42#include <stdbool.h>
04742595 43#include <stdint.h>
df54106a
SH
44#include <stdio.h>
45#include <stdlib.h>
e65cfafc 46#include <string.h>
df54106a 47#include <syslog.h>
e65cfafc
CB
48#include <unistd.h>
49#include <linux/unistd.h>
df54106a 50#include <sys/mount.h>
df54106a 51#include <sys/param.h>
e65cfafc
CB
52#include <sys/stat.h>
53#include <sys/types.h>
54#include <sys/vfs.h>
df54106a
SH
55
56#define PAM_SM_SESSION
57#include <security/_pam_macros.h>
58#include <security/pam_modules.h>
59
e65cfafc
CB
60#include "macro.h"
61
62#ifndef CGROUP_SUPER_MAGIC
63#define CGROUP_SUPER_MAGIC 0x27e0eb
64#endif
df54106a 65
e65cfafc
CB
66#ifndef CGROUP2_SUPER_MAGIC
67#define CGROUP2_SUPER_MAGIC 0x63677270
68#endif
df54106a 69
04742595
CB
70/* Taken over modified from the kernel sources. */
71#define NBITS 32 /* bits in uint32_t */
72#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
73#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, NBITS)
74
e65cfafc
CB
75static enum cg_mount_mode {
76 CGROUP_UNKNOWN = -1,
77 CGROUP_MIXED = 0,
78 CGROUP_PURE_V1 = 1,
79 CGROUP_PURE_V2 = 2,
80 CGROUP_UNINITIALIZED = 3,
81} cg_mount_mode = CGROUP_UNINITIALIZED;
82
7c029b0f 83/* Common helper functions. Most of these have been taken from LXC. */
e65cfafc
CB
84static void append_line(char **dest, size_t oldlen, char *new, size_t newlen);
85static int append_null_to_list(void ***list);
86static void batch_realloc(char **mem, size_t oldlen, size_t newlen);
04742595
CB
87static inline void clear_bit(unsigned bit, uint32_t *bitarr)
88{
89 bitarr[bit / NBITS] &= ~(1 << (bit % NBITS));
90}
e65cfafc
CB
91static char *copy_to_eol(char *s);
92static bool file_exists(const char *f);
93static void free_string_list(char **list);
94static char *get_mountpoint(char *line);
95static bool get_uid_gid(const char *user, uid_t *uid, gid_t *gid);
96static int handle_login(const char *user, uid_t uid, gid_t gid);
04742595
CB
97static inline bool is_set(unsigned bit, uint32_t *bitarr)
98{
99 return (bitarr[bit / NBITS] & (1 << (bit % NBITS))) != 0;
100}
e65cfafc
CB
101/* __typeof__ should be safe to use with all compilers. */
102typedef __typeof__(((struct statfs *)NULL)->f_type) fs_type_magic;
103static bool has_fs_type(const struct statfs *fs, fs_type_magic magic_val);
104static bool is_lxcfs(const char *line);
105static bool is_cgv1(char *line);
106static bool is_cgv2(char *line);
107static bool mkdir_p(const char *root, char *path);
108static void *must_alloc(size_t sz);
109static void must_add_to_list(char ***clist, char *entry);
110static void must_append_controller(char **klist, char **nlist, char ***clist,
111 char *entry);
112static void must_append_string(char ***list, char *entry);
113static char *must_copy_string(const char *entry);
114static char *must_make_path(const char *first, ...) __attribute__((sentinel));
115static void *must_realloc(void *orig, size_t sz);
116static void mysyslog(int err, const char *format, ...) __attribute__((sentinel));
117static char *read_file(char *fnam);
04742595 118static int read_from_file(const char *filename, void* buf, size_t count);
e65cfafc 119static int recursive_rmdir(char *dirname);
04742595
CB
120static inline void set_bit(unsigned bit, uint32_t *bitarr)
121{
122 bitarr[bit / NBITS] |= (1 << (bit % NBITS));
123}
e65cfafc 124static bool string_in_list(char **list, const char *entry);
808fd1ef 125static char *string_join(const char *sep, const char **parts, bool use_as_prefix);
e65cfafc
CB
126static void trim(char *s);
127static bool write_int(char *path, int v);
808fd1ef
CB
128static ssize_t write_nointr(int fd, const void* buf, size_t count);
129static int write_to_file(const char *filename, const void *buf, size_t count,
130 bool add_newline);
e65cfafc
CB
131
132/* cgroupfs prototypes. */
04742595
CB
133static bool cg_belongs_to_uid_gid(const char *path, uid_t uid, gid_t gid);
134static uint32_t *cg_cpumask(char *buf, size_t nbits);
135static bool cg_copy_parent_file(char *path, char *file);
136static char *cg_cpumask_to_cpulist(uint32_t *bitarr, size_t nbits);
137static bool cg_enter(const char *cgroup);
138static void cg_escape(void);
139static bool cg_filter_and_set_cpus(char *path, bool am_initialized);
140static ssize_t cg_get_max_cpus(char *cpulist);
141static int cg_get_version_of_mntpt(const char *path);
142static bool cg_init(uid_t uid, gid_t gid);
e65cfafc 143static void cg_mark_to_make_rw(const char *cstring);
04742595 144static void cg_prune_empty_cgroups(const char *user);
e65cfafc
CB
145static bool cg_systemd_created_user_slice(const char *base_cgroup,
146 const char *init_cgroup,
147 const char *in, uid_t uid);
148static bool cg_systemd_chown_existing_cgroup(const char *mountpoint,
149 const char *base_cgroup, uid_t uid,
150 gid_t gid,
151 bool systemd_user_slice);
04742595
CB
152static bool cg_systemd_under_user_slice_1(const char *in, uid_t uid);
153static bool cg_systemd_under_user_slice_2(const char *base_cgroup,
154 const char *init_cgroup, uid_t uid);
e65cfafc 155static void cg_systemd_prune_init_scope(char *cg);
e65cfafc
CB
156static bool is_lxcfs(const char *line);
157
158/* cgroupfs v1 prototypes. */
159struct cgv1_hierarchy {
160 char **controllers;
161 char *mountpoint;
162 char *base_cgroup;
163 char *fullcgpath;
164 char *init_cgroup;
165 bool create_rw_cgroup;
166 bool systemd_user_slice;
167};
168
169static struct cgv1_hierarchy **cgv1_hierarchies;
170
171static void cgv1_add_controller(char **clist, char *mountpoint,
172 char *base_cgroup, char *init_cgroup);
173static bool cgv1_controller_in_clist(char *cgline, char *c);
174static bool cgv1_controller_lists_intersect(char **l1, char **l2);
175static bool cgv1_controller_list_is_dup(struct cgv1_hierarchy **hlist,
176 char **clist);
177static bool cgv1_create(const char *cgroup, uid_t uid, gid_t gid,
178 bool *existed);
179static bool cgv1_create_one(struct cgv1_hierarchy *h, const char *cgroup,
180 uid_t uid, gid_t gid, bool *existed);
181static bool cgv1_enter(const char *cgroup);
182static void cgv1_escape(void);
183static bool cgv1_get_controllers(char ***klist, char ***nlist);
184static char *cgv1_get_current_cgroup(char *basecginfo, char *controller);
185static char **cgv1_get_proc_mountinfo_controllers(char **klist, char **nlist,
186 char *line);
04742595
CB
187static bool cgv1_handle_cpuset_hierarchy(struct cgv1_hierarchy *h,
188 const char *cgroup);
189static bool cgv1_handle_root_cpuset_hierarchy(struct cgv1_hierarchy *h);
e65cfafc
CB
190static bool cgv1_init(uid_t uid, gid_t gid);
191static void cgv1_mark_to_make_rw(char **clist);
192static char *cgv1_must_prefix_named(char *entry);
193static bool cgv1_prune_empty_cgroups(const char *user);
194static bool cgv1_remove_one(struct cgv1_hierarchy *h, const char *cgroup);
195static bool is_cgv1(char *line);
196
197/* cgroupfs v2 prototypes. */
198struct cgv2_hierarchy {
199 char **controllers;
200 char *mountpoint;
201 char *base_cgroup;
202 char *fullcgpath;
203 char *init_cgroup;
204 bool create_rw_cgroup;
205 bool systemd_user_slice;
206};
207
208/* Actually this should only be a single hierarchy. But for the sake of
209 * parallelism and because the layout of the cgroupfs v2 is still somewhat
210 * changing, we'll leave it as an array of structs.
211 */
212static struct cgv2_hierarchy **cgv2_hierarchies;
213
214static void cgv2_add_controller(char **clist, char *mountpoint,
215 char *base_cgroup, char *init_cgroup,
216 bool systemd_user_slice);
217static bool cgv2_create(const char *cgroup, uid_t uid, gid_t gid,
218 bool *existed);
219static bool cgv2_enter(const char *cgroup);
220static void cgv2_escape(void);
221static char *cgv2_get_current_cgroup(int pid);
222static bool cgv2_init(uid_t uid, gid_t gid);
223static void cgv2_mark_to_make_rw(char **clist);
224static bool cgv2_prune_empty_cgroups(const char *user);
225static bool cgv2_remove(const char *cgroup);
226static bool is_cgv2(char *line);
227
7c029b0f 228/* Common helper functions. Most of these have been taken from LXC. */
df54106a
SH
229static void mysyslog(int err, const char *format, ...)
230{
231 va_list args;
232
233 va_start(args, format);
04742595 234 openlog("PAM-CGFS", LOG_CONS | LOG_PID, LOG_AUTH);
df54106a
SH
235 vsyslog(err, format, args);
236 va_end(args);
237 closelog();
238}
239
e65cfafc
CB
240/* realloc() pointer; do not fail. */
241static void *must_realloc(void *orig, size_t sz)
242{
243 void *ret;
df54106a 244
e65cfafc
CB
245 do {
246 ret = realloc(orig, sz);
247 } while (!ret);
248
249 return ret;
250}
251
252/* realloc() pointer in batch sizes; do not fail. */
253#define BATCH_SIZE 50
254static void batch_realloc(char **mem, size_t oldlen, size_t newlen)
df54106a 255{
e65cfafc
CB
256 int newbatches = (newlen / BATCH_SIZE) + 1;
257 int oldbatches = (oldlen / BATCH_SIZE) + 1;
258
259 if (!*mem || newbatches > oldbatches)
260 *mem = must_realloc(*mem, newbatches * BATCH_SIZE);
261}
262
263/* Append lines as is to pointer; do not fail. */
264static void append_line(char **dest, size_t oldlen, char *new, size_t newlen)
265{
266 size_t full = oldlen + newlen;
267
268 batch_realloc(dest, oldlen, full + 1);
269
270 memcpy(*dest + oldlen, new, newlen + 1);
271}
272
273/* Read in whole file and return allocated pointer. */
274static char *read_file(char *fnam)
275{
276 FILE *f;
277 int linelen;
278 char *line = NULL, *buf = NULL;
279 size_t len = 0, fulllen = 0;
280
281 f = fopen(fnam, "r");
282 if (!f)
283 return NULL;
284
285 while ((linelen = getline(&line, &len, f)) != -1) {
286 append_line(&buf, fulllen, line, linelen);
287 fulllen += linelen;
288 }
289
290 fclose(f);
291 free(line);
292
293 return buf;
294}
295
296/* Given a pointer to a null-terminated array of pointers, realloc to add one
297 * entry, and point the new entry to NULL. Do not fail. Return the index to the
298 * second-to-last entry - that is, the one which is now available for use
299 * (keeping the list null-terminated).
300 */
301static int append_null_to_list(void ***list)
302{
303 int newentry = 0;
304
305 if (*list)
306 for (; (*list)[newentry]; newentry++) {
307 ;
308 }
309
310 *list = must_realloc(*list, (newentry + 2) * sizeof(void **));
311 (*list)[newentry + 1] = NULL;
312
313 return newentry;
314}
315
316/* Make allocated copy of string; do not fail. */
317static char *must_copy_string(const char *entry)
318{
319 char *ret;
320
321 if (!entry)
322 return NULL;
df54106a
SH
323
324 do {
e65cfafc
CB
325 ret = strdup(entry);
326 } while (!ret);
df54106a 327
e65cfafc
CB
328 return ret;
329}
330
331/* Append new entry to null-terminated array of pointer; make sure that array of
332 * pointers will still be null-terminated.
333 */
334static void must_append_string(char ***list, char *entry)
335{
336 int newentry;
337 char *copy;
338
339 newentry = append_null_to_list((void ***)list);
340 copy = must_copy_string(entry);
341 (*list)[newentry] = copy;
342}
343
344/* Remove newlines from string. */
345static void trim(char *s)
346{
347 size_t len = strlen(s);
348
fa00d59a 349 while ((len > 0) && s[len - 1] == '\n')
e65cfafc
CB
350 s[--len] = '\0';
351}
352
353/* Allocate pointer; do not fail. */
354static void *must_alloc(size_t sz)
355{
356 return must_realloc(NULL, sz);
357}
358
359/* Make allocated copy of string. End of string is taken to be '\n'. */
360static char *copy_to_eol(char *s)
361{
362 char *newline, *sret;
363 size_t len;
364
365 newline = strchr(s, '\n');
366 if (!newline)
367 return NULL;
368
369 len = newline - s;
370 sret = must_alloc(len + 1);
371 memcpy(sret, s, len);
372 sret[len] = '\0';
373
374 return sret;
375}
376
377/* Check if given entry under /proc/<pid>/mountinfo is a fuse.lxcfs mount. */
378static bool is_lxcfs(const char *line)
379{
380 char *p = strstr(line, " - ");
381 if (!p)
382 return false;
383
384 return strncmp(p, " - fuse.lxcfs ", 14) == 0;
385}
386
387/* Check if given entry under /proc/<pid>/mountinfo is a cgroupfs v1 mount. */
388static bool is_cgv1(char *line)
389{
390 char *p = strstr(line, " - ");
391 if (!p)
392 return false;
393
394 return strncmp(p, " - cgroup ", 10) == 0;
395}
396
397/* Check if given entry under /proc/<pid>/mountinfo is a cgroupfs v2 mount. */
398static bool is_cgv2(char *line)
399{
400 char *p = strstr(line, " - ");
401 if (!p)
402 return false;
403
404 return strncmp(p, " - cgroup2 ", 11) == 0;
405}
406
407/* Given a null-terminated array of strings, check whether @entry is one of the
408 * strings
409 */
410static bool string_in_list(char **list, const char *entry)
411{
412 char **it;
413
414 for (it = list; it && *it; it++)
415 if (strcmp(*it, entry) == 0)
416 return true;
417
418 return false;
419}
420
421/* Free null-terminated array of strings. */
422static void free_string_list(char **list)
423{
424 char **it;
425
426 for (it = list; it && *it; it++)
427 free(*it);
428 free(list);
429}
430
431/* Concatenate all passed-in strings into one path. Do not fail. If any piece
432 * is not prefixed with '/', add a '/'. Does not remove duplicate '///' from the
433 * created path.
434 */
435static char *must_make_path(const char *first, ...)
436{
437 va_list args;
438 char *cur, *dest;
439 size_t full_len;
440
441 full_len = strlen(first);
442
443 dest = must_copy_string(first);
df54106a 444
e65cfafc 445 va_start(args, first);
df54106a 446 while ((cur = va_arg(args, char *)) != NULL) {
e65cfafc
CB
447 full_len += strlen(cur);
448
449 if (cur[0] != '/')
450 full_len++;
451
452 dest = must_realloc(dest, full_len + 1);
453
454 if (cur[0] != '/')
455 strcat(dest, "/");
456
df54106a 457 strcat(dest, cur);
df54106a
SH
458 }
459 va_end(args);
460
461 return dest;
462}
463
e65cfafc
CB
464/* Write single integer to file. */
465static bool write_int(char *path, int v)
df54106a 466{
e65cfafc
CB
467 FILE *f;
468 bool ret = true;
469
470 f = fopen(path, "w");
471 if (!f)
472 return false;
df54106a 473
e65cfafc
CB
474 if (fprintf(f, "%d\n", v) < 0)
475 ret = false;
476
477 if (fclose(f) != 0)
478 ret = false;
479
480 return ret;
df54106a
SH
481}
482
e65cfafc
CB
483/* Check if a given file exists. */
484static bool file_exists(const char *f)
df54106a 485{
e65cfafc 486 struct stat statbuf;
df54106a 487
e65cfafc 488 return stat(f, &statbuf) == 0;
df54106a
SH
489}
490
e65cfafc 491/* Create directory and (if necessary) its parents. */
df54106a
SH
492static bool mkdir_p(const char *root, char *path)
493{
494 char *b, orig, *e;
495
496 if (strlen(path) < strlen(root))
497 return false;
e65cfafc 498
df54106a
SH
499 if (strlen(path) == strlen(root))
500 return true;
501
502 b = path + strlen(root) + 1;
e65cfafc
CB
503 while (true) {
504 while (*b && (*b == '/'))
df54106a
SH
505 b++;
506 if (!*b)
507 return true;
e65cfafc 508
df54106a
SH
509 e = b + 1;
510 while (*e && *e != '/')
511 e++;
e65cfafc 512
df54106a
SH
513 orig = *e;
514 if (orig)
515 *e = '\0';
e65cfafc
CB
516
517 if (file_exists(path))
df54106a 518 goto next;
e65cfafc 519
df54106a 520 if (mkdir(path, 0755) < 0) {
b36273fa 521 lxcfs_debug("Failed to create %s: %s.\n", path, strerror(errno));
df54106a
SH
522 return false;
523 }
e65cfafc
CB
524
525 next:
df54106a
SH
526 if (!orig)
527 return true;
e65cfafc 528
df54106a
SH
529 *e = orig;
530 b = e + 1;
531 }
78a2a9f3 532
e65cfafc 533 return false;
df54106a
SH
534}
535
e65cfafc
CB
536/* Recursively remove directory and its parents. */
537static int recursive_rmdir(char *dirname)
538{
539 struct dirent *direntp;
540 DIR *dir;
541 int r = 0;
df54106a 542
e65cfafc
CB
543 dir = opendir(dirname);
544 if (!dir)
545 return -ENOENT;
df54106a 546
e65cfafc
CB
547 while ((direntp = readdir(dir))) {
548 struct stat st;
549 char *pathname;
550
551 if (!direntp)
552 break;
553
554 if (!strcmp(direntp->d_name, ".") ||
555 !strcmp(direntp->d_name, ".."))
556 continue;
557
558 pathname = must_make_path(dirname, direntp->d_name, NULL);
559
560 if (lstat(pathname, &st)) {
561 if (!r)
562 lxcfs_debug("Failed to stat %s.\n", pathname);
563 r = -1;
564 goto next;
edd25678 565 }
e65cfafc
CB
566
567 if (!S_ISDIR(st.st_mode))
568 goto next;
569
570 if (recursive_rmdir(pathname) < 0)
571 r = -1;
572next:
573 free(pathname);
2be80971 574 }
e65cfafc
CB
575
576 if (rmdir(dirname) < 0) {
577 if (!r)
b36273fa 578 lxcfs_debug("Failed to delete %s: %s.\n", dirname, strerror(errno));
e65cfafc
CB
579 r = -1;
580 }
581
582 if (closedir(dir) < 0) {
583 if (!r)
b36273fa 584 lxcfs_debug("Failed to delete %s: %s.\n", dirname, strerror(errno));
e65cfafc
CB
585 r = -1;
586 }
587
588 return r;
589}
590
591/* Add new entry to null-terminated array of pointers. Make sure array is still
592 * null-terminated.
593 */
594static void must_add_to_list(char ***clist, char *entry)
595{
596 int newentry;
597
598 newentry = append_null_to_list((void ***)clist);
599 (*clist)[newentry] = must_copy_string(entry);
2be80971
SH
600}
601
e65cfafc
CB
602/* Get mountpoint from a /proc/<pid>/mountinfo line. */
603static char *get_mountpoint(char *line)
df54106a
SH
604{
605 int i;
e65cfafc
CB
606 char *p, *sret, *p2;
607 size_t len;
608
609 p = line;
610
611 for (i = 0; i < 4; i++) {
612 p = strchr(p, ' ');
613 if (!p)
614 return NULL;
615 p++;
616 }
df54106a 617
e65cfafc
CB
618 p2 = strchr(p, ' ');
619 if (p2)
620 *p2 = '\0';
621
622 len = strlen(p);
623 sret = must_alloc(len + 1);
624 memcpy(sret, p, len);
625 sret[len] = '\0';
626
627 return sret;
628}
629
630/* Create list of cgroupfs v1 controller found under /proc/self/cgroup. Skips
631 * the 0::/some/path cgroupfs v2 hierarchy listed. Splits controllers into
632 * kernel controllers (@klist) and named controllers (@nlist).
633 */
634static bool cgv1_get_controllers(char ***klist, char ***nlist)
635{
636 FILE *f;
637 char *line = NULL;
638 size_t len = 0;
639
640 f = fopen("/proc/self/cgroup", "r");
641 if (!f)
642 return false;
643
644 while (getline(&line, &len, f) != -1) {
645 char *p, *p2, *tok;
646 char *saveptr = NULL;
647
648 p = strchr(line, ':');
649 if (!p)
df54106a 650 continue;
e65cfafc
CB
651 p++;
652
653 p2 = strchr(p, ':');
654 if (!p2)
655 continue;
656 *p2 = '\0';
657
658 /* Skip the v2 hierarchy. */
659 if ((p2 - p) == 0)
df54106a 660 continue;
e65cfafc
CB
661
662 for (tok = strtok_r(p, ",", &saveptr); tok;
663 tok = strtok_r(NULL, ",", &saveptr)) {
664 if (strncmp(tok, "name=", 5) == 0)
665 must_append_string(nlist, tok);
666 else
667 must_append_string(klist, tok);
df54106a
SH
668 }
669 }
e65cfafc
CB
670
671 free(line);
672 fclose(f);
673
674 return true;
df54106a
SH
675}
676
e65cfafc
CB
677/* Get list of controllers for cgroupfs v2 hierarchy by looking at
678 * cgroup.controllers and/or cgroup.subtree_control of a given (parent) cgroup.
679static bool cgv2_get_controllers(char ***klist)
df54106a 680{
e65cfafc
CB
681 return -ENOSYS;
682}
683*/
78a2a9f3 684
e65cfafc
CB
685/* Get current cgroup from /proc/self/cgroup for the cgroupfs v2 hierarchy. */
686static char *cgv2_get_current_cgroup(int pid)
687{
688 int ret;
689 char *cgroups_v2;
690 char *current_cgroup;
691 char *copy = NULL;
692 /* The largest integer that can fit into long int is 2^64. This is a
693 * 20-digit number. */
694#define __PIDLEN /* /proc */ 5 + /* /pid-to-str */ 21 + /* /cgroup */ 7 + /* \0 */ 1
695 char path[__PIDLEN];
696
697 ret = snprintf(path, __PIDLEN, "/proc/%d/cgroup", pid);
698 if (ret < 0 || ret >= __PIDLEN)
699 return NULL;
700
701 cgroups_v2 = read_file(path);
702 if (!cgroups_v2)
703 return NULL;
704
705 current_cgroup = strstr(cgroups_v2, "0::/");
706 if (!current_cgroup)
707 goto cleanup_on_err;
708
709 current_cgroup = current_cgroup + 3;
710 copy = copy_to_eol(current_cgroup);
711 if (!copy)
712 goto cleanup_on_err;
713
714cleanup_on_err:
715 free(cgroups_v2);
716 if (copy)
717 trim(copy);
718
719 return copy;
df54106a
SH
720}
721
e65cfafc
CB
722/* Given two null-terminated lists of strings, return true if any string is in
723 * both.
724 */
725static bool cgv1_controller_lists_intersect(char **l1, char **l2)
df54106a 726{
e65cfafc 727 char **it;
df54106a 728
e65cfafc
CB
729 if (!l2)
730 return false;
731
732 for (it = l1; it && *it; it++)
733 if (string_in_list(l2, *it))
734 return true;
735
736 return false;
df54106a
SH
737}
738
e65cfafc
CB
739/* For a null-terminated list of controllers @clist, return true if any of those
740 * controllers is already listed the null-terminated list of hierarchies @hlist.
741 * Realistically, if one is present, all must be present.
742 */
743static bool cgv1_controller_list_is_dup(struct cgv1_hierarchy **hlist, char **clist)
df54106a 744{
e65cfafc
CB
745 struct cgv1_hierarchy **it;
746
747 for (it = hlist; it && *it; it++)
748 if ((*it)->controllers)
749 if (cgv1_controller_lists_intersect((*it)->controllers, clist))
750 return true;
df54106a 751 return false;
e65cfafc
CB
752
753}
754
755/* Set boolean to mark controllers under which we are supposed create a
756 * writeable cgroup.
757 */
758static void cgv1_mark_to_make_rw(char **clist)
759{
760 struct cgv1_hierarchy **it;
761
762 for (it = cgv1_hierarchies; it && *it; it++)
763 if ((*it)->controllers)
764 if (cgv1_controller_lists_intersect((*it)->controllers, clist))
765 (*it)->create_rw_cgroup = true;
766}
767
768/* Set boolean to mark whether we are supposed to create a writeable cgroup in
769 * the cgroupfs v2 hierarchy.
770 */
771static void cgv2_mark_to_make_rw(char **clist)
772{
773 if (string_in_list(clist, "unified"))
774 if (cgv2_hierarchies)
775 (*cgv2_hierarchies)->create_rw_cgroup = true;
df54106a
SH
776}
777
e65cfafc
CB
778/* Wrapper around cgv{1,2}_mark_to_make_rw(). */
779static void cg_mark_to_make_rw(const char *cstring)
df54106a 780{
e65cfafc
CB
781 char *copy, *tok;
782 char *saveptr = NULL;
783 char **clist = NULL;
df54106a 784
e65cfafc
CB
785 copy = must_copy_string(cstring);
786
787 for (tok = strtok_r(copy, ",", &saveptr); tok;
788 tok = strtok_r(NULL, ",", &saveptr))
789 must_add_to_list(&clist, tok);
790
791 free(copy);
792
793 cgv1_mark_to_make_rw(clist);
794 cgv2_mark_to_make_rw(clist);
795
796 free_string_list(clist);
797}
798
799/* Prefix any named controllers with "name=", e.g. "name=systemd". */
800static char *cgv1_must_prefix_named(char *entry)
801{
802 char *s;
803 int ret;
804 size_t len;
805
806 len = strlen(entry);
807 s = must_alloc(len + 6);
808
809 ret = snprintf(s, len + 6, "name=%s", entry);
810 if (ret < 0 || (size_t)ret >= (len + 6))
811 return NULL;
812
813 return s;
814}
815
816/* Append kernel controller in @klist or named controller in @nlist to @clist */
817static void must_append_controller(char **klist, char **nlist, char ***clist, char *entry)
818{
819 int newentry;
820 char *copy;
821
822 if (string_in_list(klist, entry) && string_in_list(nlist, entry))
823 return;
824
825 newentry = append_null_to_list((void ***)clist);
826
827 if (strncmp(entry, "name=", 5) == 0)
828 copy = must_copy_string(entry);
829 else if (string_in_list(klist, entry))
830 copy = must_copy_string(entry);
831 else
832 copy = cgv1_must_prefix_named(entry);
833
834 (*clist)[newentry] = copy;
835}
836
837/* Get the controllers from a mountinfo line. There are other ways we could get
838 * this info. For lxcfs, field 3 is /cgroup/controller-list. For cgroupfs, we
839 * could parse the mount options. But we simply assume that the mountpoint must
840 * be /sys/fs/cgroup/controller-list
841 */
842static char **cgv1_get_proc_mountinfo_controllers(char **klist, char **nlist, char *line)
843{
844 int i;
845 char *p, *p2, *tok;
846 char *saveptr = NULL;
847 char **aret = NULL;
848
849 p = line;
850
851 for (i = 0; i < 4; i++) {
852 p = strchr(p, ' ');
853 if (!p)
854 return NULL;
855 p++;
856 }
857 if (!p)
858 return NULL;
859
860 if (strncmp(p, "/sys/fs/cgroup/", 15) != 0)
861 return NULL;
862
863 p += 15;
864
865 p2 = strchr(p, ' ');
866 if (!p2)
867 return NULL;
868 *p2 = '\0';
869
870 for (tok = strtok_r(p, ",", &saveptr); tok;
871 tok = strtok_r(NULL, ",", &saveptr))
872 must_append_controller(klist, nlist, &aret, tok);
873
874 return aret;
875}
876
877/* Check if a cgroupfs v2 controller is present in the string @cgline. */
878static bool cgv1_controller_in_clist(char *cgline, char *c)
879{
880 size_t len;
881 char *tok, *eol, *tmp;
882 char *saveptr = NULL;
883
884 eol = strchr(cgline, ':');
885 if (!eol)
886 return false;
887
888 len = eol - cgline;
889 tmp = alloca(len + 1);
890 memcpy(tmp, cgline, len);
891 tmp[len] = '\0';
892
893 for (tok = strtok_r(tmp, ",", &saveptr); tok;
894 tok = strtok_r(NULL, ",", &saveptr)) {
895 if (strcmp(tok, c) == 0)
df54106a
SH
896 return true;
897 }
898 return false;
899}
900
e65cfafc
CB
901/* Get current cgroup from the /proc/<pid>/cgroup file passed in via @basecginfo
902 * of a given cgv1 controller passed in via @controller.
df54106a 903 */
e65cfafc 904static char *cgv1_get_current_cgroup(char *basecginfo, char *controller)
df54106a 905{
e65cfafc
CB
906 char *p;
907
908 p = basecginfo;
909
910 while (true) {
911 p = strchr(p, ':');
912 if (!p)
913 return NULL;
914 p++;
915
916 if (cgv1_controller_in_clist(p, controller)) {
917 p = strchr(p, ':');
918 if (!p)
919 return NULL;
920 p++;
921
922 return copy_to_eol(p);
923 }
924
925 p = strchr(p, '\n');
926 if (!p)
927 return NULL;
928 p++;
df54106a 929 }
e65cfafc
CB
930
931 return NULL;
df54106a
SH
932}
933
e65cfafc
CB
934/* Remove /init.scope from string @cg. This will mostly affect systemd-based
935 * systems.
936 */
df54106a 937#define INIT_SCOPE "/init.scope"
e65cfafc 938static void cg_systemd_prune_init_scope(char *cg)
df54106a
SH
939{
940 char *point;
941
942 if (!cg)
943 return;
944
e65cfafc
CB
945 point = cg + strlen(cg) - strlen(INIT_SCOPE);
946 if (point < cg)
df54106a 947 return;
826297d7 948
df54106a
SH
949 if (strcmp(point, INIT_SCOPE) == 0) {
950 if (point == cg)
e65cfafc 951 *(point + 1) = '\0';
df54106a
SH
952 else
953 *point = '\0';
954 }
955}
956
e65cfafc
CB
957/* Add new info about a mounted cgroupfs v1 hierarchy. Includes the controllers
958 * mounted into that hierarchy (e.g. cpu,cpuacct), the mountpoint of that
959 * hierarchy (/sys/fs/cgroup/<controller>, the base cgroup of the current
960 * process gathered from /proc/self/cgroup, and the init cgroup of PID1 gathered
961 * from /proc/1/cgroup.
962 */
963static void cgv1_add_controller(char **clist, char *mountpoint, char *base_cgroup, char *init_cgroup)
964{
965 struct cgv1_hierarchy *new;
966 int newentry;
967
968 new = must_alloc(sizeof(*new));
969 new->controllers = clist;
970 new->mountpoint = mountpoint;
971 new->base_cgroup = base_cgroup;
972 new->fullcgpath = NULL;
973 new->create_rw_cgroup = false;
974 new->init_cgroup = init_cgroup;
975 new->systemd_user_slice = false;
976
977 newentry = append_null_to_list((void ***)&cgv1_hierarchies);
978 cgv1_hierarchies[newentry] = new;
979}
980
981/* Add new info about the mounted cgroupfs v2 hierarchy. Can (but doesn't
982 * currently) include the controllers mounted into the hierarchy (e.g. memory,
983 * pids, blkio), the mountpoint of that hierarchy (Should usually be
984 * /sys/fs/cgroup but some init systems seems to think it might be a good idea
985 * to also mount empty cgroupfs v2 hierarchies at /sys/fs/cgroup/systemd.), the
986 * base cgroup of the current process gathered from /proc/self/cgroup, and the
987 * init cgroup of PID1 gathered from /proc/1/cgroup.
988 */
989static void cgv2_add_controller(char **clist, char *mountpoint, char *base_cgroup, char *init_cgroup, bool systemd_user_slice)
990{
991 struct cgv2_hierarchy *new;
992 int newentry;
993
994 new = must_alloc(sizeof(*new));
995 new->controllers = clist;
996 new->mountpoint = mountpoint;
997 new->base_cgroup = base_cgroup;
998 new->fullcgpath = NULL;
999 new->create_rw_cgroup = false;
1000 new->init_cgroup = init_cgroup;
1001 new->systemd_user_slice = systemd_user_slice;
1002
1003 newentry = append_null_to_list((void ***)&cgv2_hierarchies);
1004 cgv2_hierarchies[newentry] = new;
1005}
1006
1007/* In Ubuntu 14.04, the paths created for us were
1008 * '/user/$uid.user/$something.session' This can be merged better with
1009 * systemd_created_slice_for_us(), but keeping it separate makes it easier to
1010 * reason about the correctness.
1011 */
1012static bool cg_systemd_under_user_slice_1(const char *in, uid_t uid)
1013{
1014 char *p;
1015 size_t len;
1016 int id;
1017 char *copy = NULL;
1018 bool bret = false;
1019
1020 copy = must_copy_string(in);
1021 if (strlen(copy) < strlen("/user/1.user/1.session"))
1022 goto cleanup;
1023 p = copy + strlen(copy) - 1;
1024
1025 /* skip any trailing '/' (shouldn't be any, but be sure) */
1026 while (p >= copy && *p == '/')
1027 *(p--) = '\0';
1028 if (p < copy)
1029 goto cleanup;
1030
1031 /* Get last path element */
1032 while (p >= copy && *p != '/')
1033 p--;
1034 if (p < copy)
1035 goto cleanup;
1036 /* make sure it is something.session */
1037 len = strlen(p + 1);
1038 if (len < strlen("1.session") ||
1039 strncmp(p + 1 + len - 8, ".session", 8) != 0)
1040 goto cleanup;
1041
1042 /* ok last path piece checks out, now check the second to last */
1043 *(p + 1) = '\0';
1044 while (p >= copy && *(--p) != '/')
1045 ;
1046 if (sscanf(p + 1, "%d.user/", &id) != 1)
1047 goto cleanup;
1048
1049 if (id != (int)uid)
1050 goto cleanup;
1051
1052 bret = true;
1053
1054cleanup:
1055 free(copy);
1056 return bret;
1057}
1058
1059/* So long as our path relative to init starts with /user.slice/user-$uid.slice,
1060 * assume it belongs to $uid and chown it
1061 */
1062static bool cg_systemd_under_user_slice_2(const char *base_cgroup,
1063 const char *init_cgroup, uid_t uid)
1064{
1065 int ret;
1066 char buf[100];
1067 size_t curlen, initlen;
1068
1069 curlen = strlen(base_cgroup);
1070 initlen = strlen(init_cgroup);
1071 if (curlen <= initlen)
1072 return false;
1073
1074 if (strncmp(base_cgroup, init_cgroup, initlen) != 0)
1075 return false;
1076
1077 ret = snprintf(buf, 100, "/user.slice/user-%d.slice/", (int)uid);
1078 if (ret < 0 || ret >= 100)
1079 return false;
1080
1081 if (initlen == 1)
1082 initlen = 0; // skip the '/'
1083
1084 return strncmp(base_cgroup + initlen, buf, strlen(buf)) == 0;
1085}
1086
1087/* The systemd-created path is: user-$uid.slice/session-c$session.scope. If that
1088 * is not the end of our systemd path, then we're not part of the PAM call that
1089 * created that path.
1090 *
1091 * The last piece is chowned to $uid, the user- part not.
1092 * Note: If the user creates paths that look like what we're looking for to
1093 * 'fool' us, either
1094 * - they fool us, we create new cgroups, and they get auto-logged-out.
1095 * - they fool a root sudo, systemd cgroup is not changed but chowned, and they
1096 * lose ownership of their cgroups
1097 */
1098static bool cg_systemd_created_user_slice(const char *base_cgroup,
1099 const char *init_cgroup,
1100 const char *in, uid_t uid)
1101{
1102 char *p;
1103 size_t len;
1104 int id;
1105 char *copy = NULL;
1106 bool bret = false;
1107
1108 copy = must_copy_string(in);
1109
1110 /* An old version of systemd has already created a cgroup for us. */
1111 if (cg_systemd_under_user_slice_1(in, uid))
1112 goto succeed;
1113
1114 /* A new version of systemd has already created a cgroup for us. */
1115 if (cg_systemd_under_user_slice_2(base_cgroup, init_cgroup, uid))
1116 goto succeed;
1117
1118 if (strlen(copy) < strlen("/user-0.slice/session-0.scope"))
1119 goto cleanup;
1120
1121 p = copy + strlen(copy) - 1;
1122 /* Skip any trailing '/' (shouldn't be any, but be sure). */
1123 while (p >= copy && *p == '/')
1124 *(p--) = '\0';
1125
1126 if (p < copy)
1127 goto cleanup;
1128
1129 /* Get last path element */
1130 while (p >= copy && *p != '/')
1131 p--;
1132
1133 if (p < copy)
1134 goto cleanup;
1135
1136 /* Make sure it is session-something.scope. */
1137 len = strlen(p + 1);
1138 if (strncmp(p + 1, "session-", strlen("session-")) != 0 ||
1139 strncmp(p + 1 + len - 6, ".scope", 6) != 0)
1140 goto cleanup;
1141
1142 /* Ok last path piece checks out, now check the second to last. */
1143 *(p + 1) = '\0';
1144 while (p >= copy && *(--p) != '/')
1145 ;
1146
1147 if (sscanf(p + 1, "user-%d.slice/", &id) != 1)
1148 goto cleanup;
1149
1150 if (id != (int)uid)
1151 goto cleanup;
1152
1153succeed:
1154 bret = true;
1155cleanup:
1156 free(copy);
1157 return bret;
1158}
1159
1160/* Chown existing cgroup that systemd has already created for us. */
1161static bool cg_systemd_chown_existing_cgroup(const char *mountpoint,
1162 const char *base_cgroup, uid_t uid,
1163 gid_t gid, bool systemd_user_slice)
1164{
1165 char *path;
1166
1167 if (!systemd_user_slice)
1168 return false;
1169
1170 path = must_make_path(mountpoint, base_cgroup, NULL);
1171
1172 /* A cgroup within name=systemd has already been created. So we only
1173 * need to chown it.
1174 */
1175 if (chown(path, uid, gid) < 0)
b36273fa
CB
1176 mysyslog(LOG_WARNING, "Failed to chown %s to %d:%d: %s.\n",
1177 path, (int)uid, (int)gid, strerror(errno), NULL);
1178 lxcfs_debug("Chowned %s to %d:%d.\n", path, (int)uid, (int)gid);
e65cfafc
CB
1179
1180 free(path);
1181 return true;
1182}
1183
1184/* Detect and store information about cgroupfs v1 hierarchies. */
1185static bool cgv1_init(uid_t uid, gid_t gid)
df54106a
SH
1186{
1187 FILE *f;
e65cfafc
CB
1188 struct cgv1_hierarchy **it;
1189 char *basecginfo;
df54106a 1190 char *line = NULL;
e65cfafc 1191 char **klist = NULL, **nlist = NULL;
df54106a 1192 size_t len = 0;
df54106a 1193
e65cfafc
CB
1194 basecginfo = read_file("/proc/self/cgroup");
1195 if (!basecginfo)
1196 return false;
1197
1198 f = fopen("/proc/self/mountinfo", "r");
a6e9ec7d
CB
1199 if (!f) {
1200 free(basecginfo);
df54106a 1201 return false;
a6e9ec7d 1202 }
e65cfafc
CB
1203
1204 cgv1_get_controllers(&klist, &nlist);
1205
df54106a 1206 while (getline(&line, &len, f) != -1) {
e65cfafc
CB
1207 char **controller_list = NULL;
1208 char *mountpoint, *base_cgroup;
1209
a6e9ec7d 1210 if (is_lxcfs(line) || !is_cgv1(line))
e65cfafc
CB
1211 continue;
1212
1213 controller_list = cgv1_get_proc_mountinfo_controllers(klist, nlist, line);
1214 if (!controller_list)
1215 continue;
1216
1217 if (cgv1_controller_list_is_dup(cgv1_hierarchies,
1218 controller_list)) {
1219 free(controller_list);
1220 continue;
df54106a 1221 }
e65cfafc
CB
1222
1223 mountpoint = get_mountpoint(line);
1224 if (!mountpoint) {
1225 free_string_list(controller_list);
1226 continue;
df54106a 1227 }
e65cfafc
CB
1228
1229 base_cgroup = cgv1_get_current_cgroup(basecginfo, controller_list[0]);
1230 if (!base_cgroup) {
1231 free_string_list(controller_list);
1232 free(mountpoint);
1233 continue;
edd25678 1234 }
e65cfafc
CB
1235 trim(base_cgroup);
1236 lxcfs_debug("Detected cgroupfs v1 controller \"%s\" with "
1237 "mountpoint \"%s\" and cgroup \"%s\".\n",
1238 controller_list[0], mountpoint, base_cgroup);
1239 cgv1_add_controller(controller_list, mountpoint, base_cgroup,
1240 NULL);
df54106a 1241 }
e65cfafc
CB
1242 free_string_list(klist);
1243 free_string_list(nlist);
1244 free(basecginfo);
df54106a 1245 fclose(f);
c65c5956 1246 free(line);
df54106a 1247
e65cfafc
CB
1248 /* Retrieve init cgroup path for all controllers. */
1249 basecginfo = read_file("/proc/1/cgroup");
1250 if (!basecginfo)
1251 return false;
df54106a 1252
e65cfafc
CB
1253 for (it = cgv1_hierarchies; it && *it; it++) {
1254 if ((*it)->controllers) {
1255 char *init_cgroup, *user_slice;
a6e9ec7d
CB
1256 /* We've already stored the controller and received its
1257 * current cgroup. If we now fail to retrieve its init
1258 * cgroup, we should probably fail.
1259 */
e65cfafc 1260 init_cgroup = cgv1_get_current_cgroup(basecginfo, (*it)->controllers[0]);
a6e9ec7d
CB
1261 if (!init_cgroup) {
1262 free(basecginfo);
1263 return false;
1264 }
e65cfafc
CB
1265 cg_systemd_prune_init_scope(init_cgroup);
1266 (*it)->init_cgroup = init_cgroup;
1267 lxcfs_debug("cgroupfs v1 controller \"%s\" has init "
1268 "cgroup \"%s\".\n",
1269 (*(*it)->controllers), init_cgroup);
1270 /* Check whether systemd has already created a cgroup
1271 * for us.
1272 */
1273 user_slice = must_make_path((*it)->mountpoint, (*it)->base_cgroup, NULL);
1274 if (cg_systemd_created_user_slice((*it)->base_cgroup, (*it)->init_cgroup, user_slice, uid))
1275 (*it)->systemd_user_slice = true;
df54106a
SH
1276 }
1277 }
e65cfafc
CB
1278 free(basecginfo);
1279
1280 return true;
df54106a 1281}
e65cfafc
CB
1282
1283/* __typeof__ should be safe to use with all compilers. */
1284typedef __typeof__(((struct statfs *)NULL)->f_type) fs_type_magic;
1285/* Check whether given mountpoint has mount type specified via @magic_val. */
1286static bool has_fs_type(const struct statfs *fs, fs_type_magic magic_val)
1287{
1288 return (fs->f_type == (fs_type_magic)magic_val);
1289}
1290
1291/* Check whether @path is a cgroupfs v1 or cgroupfs v2 mount. Returns -1 if
1292 * statfs fails. If @path is null /sys/fs/cgroup is checked.
df54106a 1293 */
e65cfafc 1294static int cg_get_version_of_mntpt(const char *path)
df54106a 1295{
e65cfafc
CB
1296 int ret;
1297 struct statfs sb;
1298
1299 if (path)
1300 ret = statfs(path, &sb);
1301 else
1302 ret = statfs("/sys/fs/cgroup", &sb);
1303
1304 if (ret < 0)
1305 return -1;
1306
1307 if (has_fs_type(&sb, CGROUP_SUPER_MAGIC))
1308 return 1;
1309 else if (has_fs_type(&sb, CGROUP2_SUPER_MAGIC))
1310 return 2;
1311
1312 return 0;
1313}
1314
1315/* Detect and store information about the cgroupfs v2 hierarchy. Currently only
1316 * deals with the empty v2 hierachy as we do not retrieve enabled controllers.
1317 */
1318static bool cgv2_init(uid_t uid, gid_t gid)
1319{
1320 char *mountpoint;
e65cfafc
CB
1321 FILE *f = NULL;
1322 char *current_cgroup = NULL, *init_cgroup = NULL;
1323 char * line = NULL;
df54106a 1324 size_t len = 0;
e4992b3e 1325 int ret = false;
df54106a 1326
e65cfafc
CB
1327 current_cgroup = cgv2_get_current_cgroup(getpid());
1328 if (!current_cgroup) {
1329 /* No v2 hierarchy present. We're done. */
e4992b3e 1330 ret = true;
e65cfafc
CB
1331 goto cleanup;
1332 }
1333
1334 init_cgroup = cgv2_get_current_cgroup(1);
1335 if (!init_cgroup) {
1336 /* If we're here and didn't fail already above, then something's
1337 * certainly wrong, so error this time.
1338 */
1339 goto cleanup;
1340 }
1341 cg_systemd_prune_init_scope(init_cgroup);
1342
1343 /* Check if the v2 hierarchy is mounted at its standard location.
1344 * If so we can skip the rest of the work here. Although the unified
1345 * hierarchy can be mounted multiple times, each of those mountpoints
1346 * will expose identical information.
1347 */
1348 if (cg_get_version_of_mntpt("/sys/fs/cgroup") == 2) {
1349 char *user_slice;
1350 bool has_user_slice = false;
1351
1352 mountpoint = must_copy_string("/sys/fs/cgroup");
1353 if (!mountpoint)
1354 goto cleanup;
1355
1356 user_slice = must_make_path(mountpoint, current_cgroup, NULL);
1357 if (cg_systemd_created_user_slice(current_cgroup, init_cgroup, user_slice, uid))
1358 has_user_slice = true;
1359 free(user_slice);
1360
1361 cgv2_add_controller(NULL, mountpoint, current_cgroup, init_cgroup, has_user_slice);
1362
e4992b3e 1363 ret = true;
e65cfafc
CB
1364 goto cleanup;
1365 }
1366
1367 f = fopen("/proc/self/mountinfo", "r");
df54106a 1368 if (!f)
e4992b3e 1369 goto cleanup;
e65cfafc
CB
1370
1371 /* we support simple cgroup mounts and lxcfs mounts */
df54106a 1372 while (getline(&line, &len, f) != -1) {
e65cfafc
CB
1373 char *user_slice;
1374 bool has_user_slice = false;
1375 if (!is_cgv2(line))
1376 continue;
1377
1378 mountpoint = get_mountpoint(line);
1379 if (!mountpoint)
1380 continue;
df54106a 1381
e65cfafc
CB
1382 user_slice = must_make_path(mountpoint, current_cgroup, NULL);
1383 if (cg_systemd_created_user_slice(current_cgroup, init_cgroup, user_slice, uid))
1384 has_user_slice = true;
1385 free(user_slice);
df54106a 1386
e65cfafc
CB
1387 cgv2_add_controller(NULL, mountpoint, current_cgroup, init_cgroup, has_user_slice);
1388 /* Although the unified hierarchy can be mounted multiple times,
1389 * each of those mountpoints will expose identical information.
1390 * So let the first mountpoint we find, win.
1391 */
ca2003d4 1392 ret = true;
e65cfafc 1393 break;
df54106a
SH
1394 }
1395
e65cfafc
CB
1396 lxcfs_debug("Detected cgroupfs v2 hierarchy at mountpoint \"%s\" with "
1397 "current cgroup \"%s\" and init cgroup \"%s\".\n",
1398 mountpoint, current_cgroup, init_cgroup);
df54106a 1399
e65cfafc
CB
1400cleanup:
1401 if (f)
1402 fclose(f);
1403 free(line);
df54106a 1404
e4992b3e 1405 return ret;
df54106a
SH
1406}
1407
e65cfafc
CB
1408/* Detect and store information about mounted cgroupfs v1 hierarchies and the
1409 * cgroupfs v2 hierarchy.
1410 * Detect whether we are on a pure cgroupfs v1, cgroupfs v2, or mixed system,
1411 * where some controllers are mounted into their standard cgroupfs v1 locations
1412 * (/sys/fs/cgroup/<controller>) and others are mounted into the cgroupfs v2
1413 * hierarchy (/sys/fs/cgroup).
4deb6092 1414 */
e65cfafc 1415static bool cg_init(uid_t uid, gid_t gid)
4deb6092 1416{
e65cfafc 1417 if (!cgv1_init(uid, gid))
4deb6092
SH
1418 return false;
1419
e65cfafc 1420 if (!cgv2_init(uid, gid))
4deb6092
SH
1421 return false;
1422
e65cfafc
CB
1423 if (cgv1_hierarchies && cgv2_hierarchies) {
1424 cg_mount_mode = CGROUP_MIXED;
1425 lxcfs_debug("%s\n", "Detected cgroupfs v1 and v2 hierarchies.");
1426 } else if (cgv1_hierarchies && !cgv2_hierarchies) {
1427 cg_mount_mode = CGROUP_PURE_V1;
1428 lxcfs_debug("%s\n", "Detected cgroupfs v1 hierarchies.");
1429 } else if (cgv2_hierarchies && !cgv1_hierarchies) {
1430 cg_mount_mode = CGROUP_PURE_V2;
1431 lxcfs_debug("%s\n", "Detected cgroupfs v2 hierarchies.");
1432 } else {
1433 cg_mount_mode = CGROUP_UNKNOWN;
1434 mysyslog(LOG_ERR, "Could not detect cgroupfs hierarchy.\n", NULL);
1435 }
4deb6092 1436
e65cfafc 1437 if (cg_mount_mode == CGROUP_UNKNOWN)
4deb6092
SH
1438 return false;
1439
1440 return true;
1441}
1442
e65cfafc
CB
1443/* Try to move/migrate us into @cgroup in a cgroupfs v1 hierarchy. */
1444static bool cgv1_enter(const char *cgroup)
475a859c 1445{
e65cfafc
CB
1446 struct cgv1_hierarchy **it;
1447
1448 for (it = cgv1_hierarchies; it && *it; it++) {
e65cfafc
CB
1449 char **controller;
1450 bool entered = false;
1451
1452 if (!(*it)->controllers || !(*it)->mountpoint ||
1453 !(*it)->init_cgroup || !(*it)->create_rw_cgroup)
1454 continue;
1455
1456 for (controller = (*it)->controllers; controller && *controller;
1457 controller++) {
a6e9ec7d 1458 char *path;
e65cfafc 1459
a403c004
CB
1460 /* We've already been placed in a user slice, so we
1461 * don't need to enter the cgroup again.
1462 */
1463 if ((*it)->systemd_user_slice) {
1464 entered = true;
1465 break;
1466 }
e65cfafc
CB
1467
1468 path = must_make_path((*it)->mountpoint,
1469 (*it)->init_cgroup,
1470 cgroup,
1471 "/cgroup.procs",
1472 NULL);
1473 if (!file_exists(path)) {
1474 free(path);
1475 path = must_make_path((*it)->mountpoint,
1476 (*it)->init_cgroup,
1477 cgroup,
1478 "/tasks",
1479 NULL);
1480 }
1481 lxcfs_debug("Attempting to enter cgroupfs v1 hierarchy in \"%s\" cgroup.\n", path);
1482 entered = write_int(path, (int)getpid());
a6e9ec7d
CB
1483 if (entered) {
1484 free(path);
e65cfafc 1485 break;
a6e9ec7d 1486 }
e65cfafc 1487 lxcfs_debug("Failed to enter cgroupfs v1 hierarchy in \"%s\" cgroup.\n", path);
a6e9ec7d 1488 free(path);
e65cfafc
CB
1489 }
1490 if (!entered)
1491 return false;
1492 }
475a859c 1493
e65cfafc 1494 return true;
475a859c
SH
1495}
1496
e65cfafc
CB
1497/* Try to move/migrate us into @cgroup in the cgroupfs v2 hierarchy. */
1498static bool cgv2_enter(const char *cgroup)
78a2a9f3 1499{
e65cfafc
CB
1500 struct cgv2_hierarchy *v2;
1501 char *path;
1502 bool entered = false;
78a2a9f3 1503
e65cfafc 1504 if (!cgv2_hierarchies)
4deb6092
SH
1505 return true;
1506
e65cfafc 1507 v2 = *cgv2_hierarchies;
475a859c 1508
e65cfafc 1509 if (!v2->mountpoint || !v2->base_cgroup)
78a2a9f3
SH
1510 return false;
1511
e65cfafc
CB
1512 if (!v2->create_rw_cgroup || v2->systemd_user_slice)
1513 return true;
78a2a9f3 1514
423a3b4f 1515 path = must_make_path(v2->mountpoint, v2->base_cgroup, cgroup, "/cgroup.procs", NULL);
e65cfafc
CB
1516 lxcfs_debug("Attempting to enter cgroupfs v2 hierarchy in cgroup \"%s\".\n", path);
1517 entered = write_int(path, (int)getpid());
1518 if (!entered) {
1519 lxcfs_debug("Failed to enter cgroupfs v2 hierarchy in cgroup \"%s\".\n", path);
1520 free(path);
78a2a9f3 1521 return false;
e65cfafc 1522 }
78a2a9f3 1523
e65cfafc 1524 free(path);
78a2a9f3
SH
1525
1526 return true;
1527}
edd25678 1528
e65cfafc
CB
1529/* Wrapper around cgv{1,2}_enter(). */
1530static bool cg_enter(const char *cgroup)
1531{
1532 if (!cgv1_enter(cgroup)) {
1533 mysyslog(LOG_WARNING, "cgroupfs v1: Failed to enter cgroups.\n", NULL);
edd25678 1534 return false;
e65cfafc 1535 }
edd25678 1536
e65cfafc
CB
1537 if (!cgv2_enter(cgroup)) {
1538 mysyslog(LOG_WARNING, "cgroupfs v2: Failed to enter cgroups.\n", NULL);
78a2a9f3
SH
1539 return false;
1540 }
1541
edd25678
SH
1542 return true;
1543}
1544
e65cfafc
CB
1545/* Escape to root cgroup in all detected cgroupfs v1 hierarchies. */
1546static void cgv1_escape(void)
df54106a 1547{
04742595
CB
1548 struct cgv1_hierarchy **it;
1549
1550 /* In case systemd hasn't already placed us in a user slice for the
1551 * cpuset v1 controller we will reside in the root cgroup. This means
1552 * that cgroup.clone_children will not have been initialized for us so
1553 * we need to do it.
1554 */
1555 for (it = cgv1_hierarchies; it && *it; it++)
1556 if (!cgv1_handle_root_cpuset_hierarchy(*it))
1557 mysyslog(LOG_WARNING, "cgroupfs v1: Failed to initialize cpuset.\n", NULL);
1558
e65cfafc
CB
1559 if (!cgv1_enter("/"))
1560 mysyslog(LOG_WARNING, "cgroupfs v1: Failed to escape to init's cgroup.\n", NULL);
1561}
edd25678 1562
e65cfafc
CB
1563/* Escape to root cgroup in the cgroupfs v2 hierarchy. */
1564static void cgv2_escape(void)
1565{
1566 if (!cgv2_enter("/"))
1567 mysyslog(LOG_WARNING, "cgroupfs v2: Failed to escape to init's cgroup.\n", NULL);
1568}
edd25678 1569
e65cfafc
CB
1570/* Wrapper around cgv{1,2}_escape(). */
1571static void cg_escape(void)
1572{
1573 cgv1_escape();
1574 cgv2_escape();
df54106a
SH
1575}
1576
e65cfafc
CB
1577/* Get uid and gid for @user. */
1578static bool get_uid_gid(const char *user, uid_t *uid, gid_t *gid)
df54106a 1579{
e65cfafc 1580 struct passwd *pwent;
df54106a 1581
e65cfafc
CB
1582 pwent = getpwnam(user);
1583 if (!pwent)
1584 return false;
df54106a 1585
e65cfafc
CB
1586 *uid = pwent->pw_uid;
1587 *gid = pwent->pw_gid;
df54106a 1588
e65cfafc 1589 return true;
df54106a
SH
1590}
1591
3145d497
CB
1592/* Check if cgroup belongs to our uid and gid. If so, reuse it. */
1593static bool cg_belongs_to_uid_gid(const char *path, uid_t uid, gid_t gid)
1594{
1595 struct stat statbuf;
1596
1597 if (stat(path, &statbuf) < 0)
1598 return false;
1599
1600 if (!(statbuf.st_uid == uid) || !(statbuf.st_gid == gid))
1601 return false;
1602
1603 return true;
1604}
1605
04742595
CB
1606/* Create cpumask from cpulist aka turn:
1607 *
1608 * 0,2-3
1609 *
1610 * into bit array
1611 *
1612 * 1 0 1 1
1613 */
1614static uint32_t *cg_cpumask(char *buf, size_t nbits)
1615{
1616 char *token;
1617 char *saveptr = NULL;
1618 size_t arrlen = BITS_TO_LONGS(nbits);
1619 uint32_t *bitarr = calloc(arrlen, sizeof(uint32_t));
1620 if (!bitarr)
1621 return NULL;
1622
1623 for (; (token = strtok_r(buf, ",", &saveptr)); buf = NULL) {
1624 errno = 0;
1625 unsigned start = strtoul(token, NULL, 0);
1626 unsigned end = start;
1627
1628 char *range = strchr(token, '-');
1629 if (range)
1630 end = strtoul(range + 1, NULL, 0);
1631 if (!(start <= end)) {
1632 free(bitarr);
1633 return NULL;
1634 }
1635
1636 if (end >= nbits) {
1637 free(bitarr);
1638 return NULL;
1639 }
1640
1641 while (start <= end)
1642 set_bit(start++, bitarr);
1643 }
1644
1645 return bitarr;
1646}
1647
808fd1ef 1648static char *string_join(const char *sep, const char **parts, bool use_as_prefix)
04742595
CB
1649{
1650 char *result;
1651 char **p;
1652 size_t sep_len = strlen(sep);
1653 size_t result_len = use_as_prefix * sep_len;
1654
bfd723ff
CB
1655 if (!parts)
1656 return NULL;
1657
04742595
CB
1658 /* calculate new string length */
1659 for (p = (char **)parts; *p; p++)
1660 result_len += (p > (char **)parts) * sep_len + strlen(*p);
1661
3acf9e94 1662 result = calloc(result_len + 1, sizeof(char));
04742595
CB
1663 if (!result)
1664 return NULL;
1665
1666 if (use_as_prefix)
1667 strcpy(result, sep);
1668 for (p = (char **)parts; *p; p++) {
1669 if (p > (char **)parts)
1670 strcat(result, sep);
1671 strcat(result, *p);
1672 }
1673
1674 return result;
1675}
1676
1677/* The largest integer that can fit into long int is 2^64. This is a
1678 * 20-digit number.
1679 */
1680#define __IN_TO_STR_LEN 21
1681/* Turn cpumask into simple, comma-separated cpulist. */
1682static char *cg_cpumask_to_cpulist(uint32_t *bitarr, size_t nbits)
1683{
1684 size_t i;
1685 int ret;
1686 char numstr[__IN_TO_STR_LEN] = {0};
1687 char **cpulist = NULL;
1688
1689 for (i = 0; i <= nbits; i++) {
1690 if (is_set(i, bitarr)) {
1691 ret = snprintf(numstr, __IN_TO_STR_LEN, "%zu", i);
1692 if (ret < 0 || (size_t)ret >= __IN_TO_STR_LEN) {
1693 free_string_list(cpulist);
1694 return NULL;
1695 }
1696 must_append_string(&cpulist, numstr);
1697 }
1698 }
1699 return string_join(",", (const char **)cpulist, false);
1700}
1701
1702static ssize_t cg_get_max_cpus(char *cpulist)
1703{
1704 char *c1, *c2;
1705 char *maxcpus = cpulist;
1706 size_t cpus = 0;
1707
1708 c1 = strrchr(maxcpus, ',');
1709 if (c1)
1710 c1++;
1711
1712 c2 = strrchr(maxcpus, '-');
1713 if (c2)
1714 c2++;
1715
1716 if (!c1 && !c2)
1717 c1 = maxcpus;
04742595
CB
1718 else if (c1 < c2)
1719 c1 = c2;
04742595
CB
1720
1721 /* If the above logic is correct, c1 should always hold a valid string
1722 * here.
1723 */
1724
1725 errno = 0;
1726 cpus = strtoul(c1, NULL, 0);
1727 if (errno != 0)
1728 return -1;
1729
1730 return cpus;
1731}
1732
808fd1ef 1733static ssize_t write_nointr(int fd, const void* buf, size_t count)
04742595
CB
1734{
1735 ssize_t ret;
1736again:
1737 ret = write(fd, buf, count);
1738 if (ret < 0 && errno == EINTR)
1739 goto again;
1740 return ret;
1741}
1742
808fd1ef 1743static int write_to_file(const char *filename, const void* buf, size_t count, bool add_newline)
04742595
CB
1744{
1745 int fd, saved_errno;
1746 ssize_t ret;
1747
1748 fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0666);
1749 if (fd < 0)
1750 return -1;
1751 ret = write_nointr(fd, buf, count);
1752 if (ret < 0)
1753 goto out_error;
1754 if ((size_t)ret != count)
1755 goto out_error;
1756 if (add_newline) {
1757 ret = write_nointr(fd, "\n", 1);
1758 if (ret != 1)
1759 goto out_error;
1760 }
1761 close(fd);
1762 return 0;
1763
1764out_error:
1765 saved_errno = errno;
1766 close(fd);
1767 errno = saved_errno;
1768 return -1;
1769}
1770
808fd1ef 1771#define __ISOL_CPUS "/sys/devices/system/cpu/isolated"
04742595
CB
1772static bool cg_filter_and_set_cpus(char *path, bool am_initialized)
1773{
1774 char *lastslash, *fpath, oldv;
1775 int ret;
1776 ssize_t i;
1777
1778 ssize_t maxposs = 0, maxisol = 0;
1779 char *cpulist = NULL, *posscpus = NULL, *isolcpus = NULL;
1780 uint32_t *possmask = NULL, *isolmask = NULL;
808fd1ef 1781 bool bret = false, flipped_bit = false;
04742595
CB
1782
1783 lastslash = strrchr(path, '/');
1784 if (!lastslash) { // bug... this shouldn't be possible
808fd1ef 1785 lxcfs_debug("Invalid path: %s.\n", path);
04742595
CB
1786 return bret;
1787 }
1788 oldv = *lastslash;
1789 *lastslash = '\0';
1790 fpath = must_make_path(path, "cpuset.cpus", NULL);
1791 posscpus = read_file(fpath);
808fd1ef
CB
1792 if (!posscpus) {
1793 lxcfs_debug("Could not read file: %s.\n", fpath);
1794 goto on_error;
1795 }
04742595
CB
1796
1797 /* Get maximum number of cpus found in possible cpuset. */
1798 maxposs = cg_get_max_cpus(posscpus);
1799 if (maxposs < 0)
808fd1ef 1800 goto on_error;
04742595 1801
b25412f5
CB
1802 if (!file_exists(__ISOL_CPUS)) {
1803 /* This system doesn't expose isolated cpus. */
1804 lxcfs_debug("%s", "Path: "__ISOL_CPUS" to read isolated cpus from does not exist.\n");
1805 cpulist = posscpus;
1806 /* No isolated cpus but we weren't already initialized by
1807 * someone. We should simply copy the parents cpuset.cpus
1808 * values.
1809 */
1810 if (!am_initialized) {
1811 lxcfs_debug("%s", "Copying cpuset of parent cgroup.\n");
1812 goto copy_parent;
1813 }
1814 /* No isolated cpus but we were already initialized by someone.
1815 * Nothing more to do for us.
1816 */
1817 goto on_success;
1818 }
1819
808fd1ef
CB
1820 isolcpus = read_file(__ISOL_CPUS);
1821 if (!isolcpus) {
1822 lxcfs_debug("%s", "Could not read file "__ISOL_CPUS"\n");
1823 goto on_error;
1824 }
04742595 1825 if (!isdigit(isolcpus[0])) {
808fd1ef 1826 lxcfs_debug("%s", "No isolated cpus detected.\n");
04742595
CB
1827 cpulist = posscpus;
1828 /* No isolated cpus but we weren't already initialized by
1829 * someone. We should simply copy the parents cpuset.cpus
1830 * values.
1831 */
808fd1ef
CB
1832 if (!am_initialized) {
1833 lxcfs_debug("%s", "Copying cpuset of parent cgroup.\n");
04742595 1834 goto copy_parent;
808fd1ef 1835 }
04742595
CB
1836 /* No isolated cpus but we were already initialized by someone.
1837 * Nothing more to do for us.
1838 */
808fd1ef 1839 goto on_success;
04742595
CB
1840 }
1841
1842 /* Get maximum number of cpus found in isolated cpuset. */
1843 maxisol = cg_get_max_cpus(isolcpus);
1844 if (maxisol < 0)
808fd1ef 1845 goto on_error;
04742595
CB
1846
1847 if (maxposs < maxisol)
1848 maxposs = maxisol;
1849 maxposs++;
1850
1851 possmask = cg_cpumask(posscpus, maxposs);
808fd1ef
CB
1852 if (!possmask) {
1853 lxcfs_debug("%s", "Could not create cpumask for all possible cpus.\n");
1854 goto on_error;
1855 }
04742595
CB
1856
1857 isolmask = cg_cpumask(isolcpus, maxposs);
808fd1ef
CB
1858 if (!isolmask) {
1859 lxcfs_debug("%s", "Could not create cpumask for all isolated cpus.\n");
1860 goto on_error;
1861 }
04742595
CB
1862
1863 for (i = 0; i <= maxposs; i++) {
1864 if (is_set(i, isolmask) && is_set(i, possmask)) {
808fd1ef 1865 flipped_bit = true;
04742595
CB
1866 clear_bit(i, possmask);
1867 }
1868 }
1869
808fd1ef
CB
1870 if (!flipped_bit) {
1871 lxcfs_debug("%s", "No isolated cpus present in cpuset.\n");
1872 goto on_success;
1873 }
1874 lxcfs_debug("%s", "Removed isolated cpus from cpuset.\n");
1875
04742595 1876 cpulist = cg_cpumask_to_cpulist(possmask, maxposs);
808fd1ef
CB
1877 if (!cpulist) {
1878 lxcfs_debug("%s", "Could not create cpu list.\n");
1879 goto on_error;
1880 }
04742595
CB
1881
1882copy_parent:
1883 *lastslash = oldv;
1884 fpath = must_make_path(path, "cpuset.cpus", NULL);
808fd1ef
CB
1885 ret = write_to_file(fpath, cpulist, strlen(cpulist), false);
1886 if (ret < 0) {
1887 lxcfs_debug("Could not write cpu list to: %s.\n", fpath);
1888 goto on_error;
1889 }
04742595 1890
808fd1ef
CB
1891on_success:
1892 bret = true;
1893
1894on_error:
04742595
CB
1895 free(fpath);
1896
1897 free(isolcpus);
1898 free(isolmask);
1899
1900 if (posscpus != cpulist)
1901 free(posscpus);
1902 free(possmask);
1903
1904 free(cpulist);
1905 return bret;
1906}
1907
1908int read_from_file(const char *filename, void* buf, size_t count)
1909{
1910 int fd = -1, saved_errno;
1911 ssize_t ret;
1912
1913 fd = open(filename, O_RDONLY | O_CLOEXEC);
1914 if (fd < 0)
1915 return -1;
1916
1917 if (!buf || !count) {
1918 char buf2[100];
1919 size_t count2 = 0;
1920 while ((ret = read(fd, buf2, 100)) > 0)
1921 count2 += ret;
1922 if (ret >= 0)
1923 ret = count2;
1924 } else {
1925 memset(buf, 0, count);
1926 ret = read(fd, buf, count);
1927 }
1928
1929 if (ret < 0)
1930 lxcfs_debug("read %s: %s", filename, strerror(errno));
1931
1932 saved_errno = errno;
1933 close(fd);
1934 errno = saved_errno;
1935 return ret;
1936}
1937
1938/* Copy contents of parent(@path)/@file to @path/@file */
1939static bool cg_copy_parent_file(char *path, char *file)
1940{
1941 char *lastslash, *value = NULL, *fpath, oldv;
1942 int len = 0;
1943 int ret;
1944
1945 lastslash = strrchr(path, '/');
1946 if (!lastslash) { // bug... this shouldn't be possible
1947 lxcfs_debug("cgfsng:copy_parent_file: bad path %s", path);
1948 return false;
1949 }
1950 oldv = *lastslash;
1951 *lastslash = '\0';
1952 fpath = must_make_path(path, file, NULL);
1953 len = read_from_file(fpath, NULL, 0);
1954 if (len <= 0)
1955 goto bad;
1956 value = must_alloc(len + 1);
1957 if (read_from_file(fpath, value, len) != len)
1958 goto bad;
1959 free(fpath);
1960 *lastslash = oldv;
1961 fpath = must_make_path(path, file, NULL);
808fd1ef 1962 ret = write_to_file(fpath, value, len, false);
04742595
CB
1963 if (ret < 0)
1964 lxcfs_debug("Unable to write %s to %s", value, fpath);
1965 free(fpath);
1966 free(value);
1967 return ret >= 0;
1968
1969bad:
1970 lxcfs_debug("Error reading '%s'", fpath);
1971 free(fpath);
1972 free(value);
1973 return false;
1974}
1975
1976/* In case systemd hasn't already placed us in a user slice for the cpuset v1
1977 * controller we will reside in the root cgroup. This means that
1978 * cgroup.clone_children will not have been initialized for us so we need to do
1979 * it.
1980 */
1981static bool cgv1_handle_root_cpuset_hierarchy(struct cgv1_hierarchy *h)
1982{
1983 char *clonechildrenpath, v;
1984
1985 if (!string_in_list(h->controllers, "cpuset"))
1986 return true;
1987
1988 clonechildrenpath = must_make_path(h->mountpoint, "cgroup.clone_children", NULL);
1989
1990 if (read_from_file(clonechildrenpath, &v, 1) < 0) {
1991 lxcfs_debug("Failed to read '%s'", clonechildrenpath);
1992 free(clonechildrenpath);
1993 return false;
1994 }
1995
1996 if (v == '1') { /* already set for us by someone else */
1997 free(clonechildrenpath);
1998 return true;
1999 }
2000
808fd1ef 2001 if (write_to_file(clonechildrenpath, "1", 1, false) < 0) {
04742595
CB
2002 /* Set clone_children so children inherit our settings */
2003 lxcfs_debug("Failed to write 1 to %s", clonechildrenpath);
2004 free(clonechildrenpath);
2005 return false;
2006 }
2007 free(clonechildrenpath);
2008 return true;
2009}
2010
2011/*
2012 * Initialize the cpuset hierarchy in first directory of @gname and
2013 * set cgroup.clone_children so that children inherit settings.
2014 * Since the h->base_path is populated by init or ourselves, we know
2015 * it is already initialized.
2016 */
2017static bool cgv1_handle_cpuset_hierarchy(struct cgv1_hierarchy *h,
2018 const char *cgroup)
2019{
2020 char *cgpath, *clonechildrenpath, v, *slash;
2021
2022 if (!string_in_list(h->controllers, "cpuset"))
2023 return true;
2024
2025 if (*cgroup == '/')
2026 cgroup++;
2027 slash = strchr(cgroup, '/');
2028 if (slash)
2029 *slash = '\0';
2030
2031 cgpath = must_make_path(h->mountpoint, h->base_cgroup, cgroup, NULL);
2032 if (slash)
2033 *slash = '/';
2034 if (mkdir(cgpath, 0755) < 0 && errno != EEXIST) {
2035 lxcfs_debug("Failed to create '%s'", cgpath);
2036 free(cgpath);
2037 return false;
2038 }
2039 clonechildrenpath = must_make_path(cgpath, "cgroup.clone_children", NULL);
2040 if (!file_exists(clonechildrenpath)) { /* unified hierarchy doesn't have clone_children */
2041 free(clonechildrenpath);
2042 free(cgpath);
2043 return true;
2044 }
2045 if (read_from_file(clonechildrenpath, &v, 1) < 0) {
2046 lxcfs_debug("Failed to read '%s'", clonechildrenpath);
2047 free(clonechildrenpath);
2048 free(cgpath);
2049 return false;
2050 }
2051
2052 /* Make sure any isolated cpus are removed from cpuset.cpus. */
7c029b0f
CB
2053 if (!cg_filter_and_set_cpus(cgpath, v == '1')) {
2054 lxcfs_debug("%s", "Failed to remove isolated cpus.\n");
2055 free(clonechildrenpath);
2056 free(cgpath);
04742595 2057 return false;
7c029b0f 2058 }
04742595
CB
2059
2060 if (v == '1') { /* already set for us by someone else */
808fd1ef 2061 lxcfs_debug("%s", "\"cgroup.clone_children\" was already set to \"1\".\n");
04742595
CB
2062 free(clonechildrenpath);
2063 free(cgpath);
2064 return true;
2065 }
2066
2067 /* copy parent's settings */
2068 if (!cg_copy_parent_file(cgpath, "cpuset.mems")) {
808fd1ef 2069 lxcfs_debug("%s", "Failed to copy \"cpuset.mems\" settings.\n");
04742595
CB
2070 free(cgpath);
2071 free(clonechildrenpath);
2072 return false;
2073 }
2074 free(cgpath);
2075
808fd1ef 2076 if (write_to_file(clonechildrenpath, "1", 1, false) < 0) {
04742595
CB
2077 /* Set clone_children so children inherit our settings */
2078 lxcfs_debug("Failed to write 1 to %s", clonechildrenpath);
2079 free(clonechildrenpath);
2080 return false;
2081 }
2082 free(clonechildrenpath);
2083 return true;
2084}
2085
e65cfafc
CB
2086/* Create and chown @cgroup for all given controllers in a cgroupfs v1 hierarchy
2087 * (For example, create @cgroup for the cpu and cpuacct controller mounted into
2088 * /sys/fs/cgroup/cpu,cpuacct). Check if the path already exists and report back
2089 * to the caller in @existed.
df54106a 2090 */
e65cfafc
CB
2091#define __PAM_CGFS_USER "/user/"
2092#define __PAM_CGFS_USER_LEN 6
2093static bool cgv1_create_one(struct cgv1_hierarchy *h, const char *cgroup, uid_t uid, gid_t gid, bool *existed)
df54106a 2094{
e65cfafc
CB
2095 char *clean_base_cgroup, *path;
2096 char **controller;
2097 struct cgv1_hierarchy *it;
2098 bool created = false;
2099
a403c004 2100 *existed = false;
e65cfafc
CB
2101 it = h;
2102 for (controller = it->controllers; controller && *controller;
2103 controller++) {
04742595
CB
2104 if (!cgv1_handle_cpuset_hierarchy(it, cgroup))
2105 return false;
2106
e65cfafc
CB
2107 /* If systemd has already created a cgroup for us, keep using
2108 * it.
2109 */
2110 if (cg_systemd_chown_existing_cgroup(it->mountpoint,
2111 it->base_cgroup, uid, gid,
2112 it->systemd_user_slice)) {
2113 return true;
2114 }
2115
2116 /* We need to make sure that we do not create an endless chain
2117 * of sub-cgroups. So we check if we have already logged in
2118 * somehow (sudo -i, su, etc.) and have created a
2119 * /user/PAM_user/idx cgroup. If so, we skip that part. For most
2120 * cgroups this is unnecessary since we use the init_cgroup
2121 * anyway, but for controllers which have an existing systemd
2122 * cgroup that does not match the current uid, this is pretty
2123 * useful.
2124 */
2125 if (strncmp(it->base_cgroup, __PAM_CGFS_USER, __PAM_CGFS_USER_LEN) == 0) {
2126 free(it->base_cgroup);
2127 it->base_cgroup = must_copy_string("/");
2128 } else {
2129 clean_base_cgroup =
2130 strstr(it->base_cgroup, __PAM_CGFS_USER);
2131 if (clean_base_cgroup)
2132 *clean_base_cgroup = '\0';
2133 }
df54106a 2134
e65cfafc
CB
2135 path = must_make_path(it->mountpoint, it->init_cgroup, cgroup, NULL);
2136 lxcfs_debug("Constructing path: %s.\n", path);
2137 if (file_exists(path)) {
3145d497 2138 bool our_cg = cg_belongs_to_uid_gid(path, uid, gid);
b36273fa 2139 lxcfs_debug("%s existed and does %shave our uid: %d and gid: %d.\n", path, our_cg ? "" : "not ", uid, gid);
e65cfafc 2140 free(path);
3145d497
CB
2141 if (our_cg)
2142 *existed = false;
2143 else
2144 *existed = true;
2145 return our_cg;
e65cfafc
CB
2146 }
2147 created = mkdir_p(it->mountpoint, path);
2148 if (!created) {
df54106a 2149 free(path);
e65cfafc 2150 continue;
df54106a 2151 }
e65cfafc 2152 if (chown(path, uid, gid) < 0)
b36273fa
CB
2153 mysyslog(LOG_WARNING,
2154 "Failed to chown %s to %d:%d: %s.\n", path,
2155 (int)uid, (int)gid, strerror(errno), NULL);
2156 lxcfs_debug("Chowned %s to %d:%d.\n", path, (int)uid, (int)gid);
e65cfafc
CB
2157 free(path);
2158 break;
df54106a 2159 }
e65cfafc 2160
eb42e790 2161 return created;
df54106a
SH
2162}
2163
e65cfafc
CB
2164/* Try to remove @cgroup for all given controllers in a cgroupfs v1 hierarchy
2165 * (For example, try to remove @cgroup for the cpu and cpuacct controller
2166 * mounted into /sys/fs/cgroup/cpu,cpuacct). Ignores failures.
2167 */
2168static bool cgv1_remove_one(struct cgv1_hierarchy *h, const char *cgroup)
df54106a 2169{
df54106a 2170
e65cfafc 2171 char *path;
df54106a 2172
e65cfafc
CB
2173 /* Better safe than sorry. */
2174 if (!h->controllers)
2175 return true;
df54106a 2176
e65cfafc
CB
2177 /* Cgroups created by systemd for us which we re-use won't be removed
2178 * here, since we're using init_cgroup + cgroup as path instead of
2179 * base_cgroup + cgroup.
2180 */
2181 path = must_make_path(h->mountpoint, h->init_cgroup, cgroup, NULL);
2182 (void)recursive_rmdir(path);
2183 free(path);
df54106a
SH
2184
2185 return true;
2186}
2187
e65cfafc
CB
2188/* Try to remove @cgroup the cgroupfs v2 hierarchy. */
2189static bool cgv2_remove(const char *cgroup)
df54106a 2190{
e65cfafc
CB
2191 struct cgv2_hierarchy *v2;
2192 char *path;
e9597a70 2193
e65cfafc
CB
2194 if (!cgv2_hierarchies)
2195 return true;
2196
2197 v2 = *cgv2_hierarchies;
2198
2199 /* If we reused an already existing cgroup, don't bother trying to
2200 * remove (a potentially wrong)/the path.
2201 * Cgroups created by systemd for us which we re-use would be removed
2202 * here, since we're using base_cgroup + cgroup as path.
2203 */
2204 if (v2->systemd_user_slice)
2205 return true;
2206
2207 path = must_make_path(v2->mountpoint, v2->base_cgroup, cgroup, NULL);
2208 (void)recursive_rmdir(path);
2209 free(path);
2210
2211 return true;
df54106a
SH
2212}
2213
e65cfafc
CB
2214/* Create @cgroup in all detected cgroupfs v1 hierarchy. If the creation fails
2215 * for any cgroupfs v1 hierarchy, remove all we have created so far. Report
2216 * back, to the caller if the creation failed due to @cgroup already existing
2217 * via @existed.
2218 */
2219static bool cgv1_create(const char *cgroup, uid_t uid, gid_t gid, bool *existed)
df54106a 2220{
e65cfafc
CB
2221 struct cgv1_hierarchy **it, **rev_it;
2222 bool all_created = true;
df54106a 2223
e65cfafc
CB
2224 for (it = cgv1_hierarchies; it && *it; it++) {
2225 if (!(*it)->controllers || !(*it)->mountpoint ||
2226 !(*it)->init_cgroup || !(*it)->create_rw_cgroup)
2227 continue;
2228
2229 if (!cgv1_create_one(*it, cgroup, uid, gid, existed)) {
2230 all_created = false;
2231 break;
df54106a 2232 }
df54106a
SH
2233 }
2234
e65cfafc
CB
2235 if (all_created)
2236 return true;
2237
2238 for (rev_it = cgv1_hierarchies; rev_it && *rev_it && (*rev_it != *it);
2239 rev_it++)
2240 cgv1_remove_one(*rev_it, cgroup);
2241
df54106a
SH
2242 return false;
2243}
2244
e65cfafc
CB
2245/* Create @cgroup in the cgroupfs v2 hierarchy. Report back, to the caller if
2246 * the creation failed due to @cgroup already existing via @existed.
2247 */
2248static bool cgv2_create(const char *cgroup, uid_t uid, gid_t gid, bool *existed)
df54106a 2249{
e65cfafc
CB
2250 char *clean_base_cgroup;
2251 char *path;
2252 struct cgv2_hierarchy *v2;
cb083287 2253 bool our_cg = false, created = false;
df54106a 2254
a403c004
CB
2255 *existed = false;
2256
e65cfafc
CB
2257 if (!cgv2_hierarchies || !(*cgv2_hierarchies)->create_rw_cgroup)
2258 return true;
df54106a 2259
e65cfafc 2260 v2 = *cgv2_hierarchies;
df54106a 2261
e65cfafc
CB
2262 /* We can't be placed under init's cgroup for the v2 hierarchy. We need
2263 * to be placed under our current cgroup.
2264 */
cb083287
CB
2265 if (cg_systemd_chown_existing_cgroup(v2->mountpoint, v2->base_cgroup,
2266 uid, gid, v2->systemd_user_slice))
2267 goto chown_cgroup_procs_file;
edd25678 2268
423a3b4f 2269 /* We need to make sure that we do not create an endless chain of
e65cfafc
CB
2270 * sub-cgroups. So we check if we have already logged in somehow (sudo
2271 * -i, su, etc.) and have created a /user/PAM_user/idx cgroup. If so, we
2272 * skip that part.
2273 */
2274 if (strncmp(v2->base_cgroup, __PAM_CGFS_USER, __PAM_CGFS_USER_LEN) == 0) {
2275 free(v2->base_cgroup);
2276 v2->base_cgroup = must_copy_string("/");
2277 } else {
2278 clean_base_cgroup = strstr(v2->base_cgroup, __PAM_CGFS_USER);
2279 if (clean_base_cgroup)
2280 *clean_base_cgroup = '\0';
df54106a
SH
2281 }
2282
e65cfafc
CB
2283 path = must_make_path(v2->mountpoint, v2->base_cgroup, cgroup, NULL);
2284 lxcfs_debug("Constructing path \"%s\".\n", path);
2285 if (file_exists(path)) {
cb083287
CB
2286 our_cg = cg_belongs_to_uid_gid(path, uid, gid);
2287 lxcfs_debug(
2288 "%s existed and does %shave our uid: %d and gid: %d.\n",
2289 path, our_cg ? "" : "not ", uid, gid);
e65cfafc 2290 free(path);
cb083287 2291 if (our_cg) {
3145d497 2292 *existed = false;
cb083287
CB
2293 goto chown_cgroup_procs_file;
2294 } else {
3145d497 2295 *existed = true;
cb083287
CB
2296 return false;
2297 }
df54106a 2298 }
df54106a 2299
e65cfafc
CB
2300 created = mkdir_p(v2->mountpoint, path);
2301 if (!created) {
2302 free(path);
df54106a 2303 return false;
e65cfafc
CB
2304 }
2305
cb083287 2306 /* chown cgroup to user */
e65cfafc 2307 if (chown(path, uid, gid) < 0)
b36273fa
CB
2308 mysyslog(LOG_WARNING, "Failed to chown %s to %d:%d: %s.\n",
2309 path, (int)uid, (int)gid, strerror(errno), NULL);
cb083287
CB
2310 else
2311 lxcfs_debug("Chowned %s to %d:%d.\n", path, (int)uid, (int)gid);
2312 free(path);
2313
2314chown_cgroup_procs_file:
2315 /* chown cgroup.procs to user */
2316 if (v2->systemd_user_slice)
2317 path = must_make_path(v2->mountpoint, v2->base_cgroup,
2318 "/cgroup.procs", NULL);
2319 else
2320 path = must_make_path(v2->mountpoint, v2->base_cgroup, cgroup,
2321 "/cgroup.procs", NULL);
2322 if (chown(path, uid, gid) < 0)
2323 mysyslog(LOG_WARNING, "Failed to chown %s to %d:%d: %s.\n",
2324 path, (int)uid, (int)gid, strerror(errno), NULL);
2325 else
2326 lxcfs_debug("Chowned %s to %d:%d.\n", path, (int)uid, (int)gid);
e65cfafc 2327 free(path);
df54106a
SH
2328
2329 return true;
2330}
2331
e65cfafc
CB
2332/* Create writeable cgroups for @user at login. Details can be found in the
2333 * preamble/license at the top of this file.
2334 */
2335static int handle_login(const char *user, uid_t uid, gid_t gid)
df54106a
SH
2336{
2337 int idx = 0, ret;
2338 bool existed;
df54106a 2339 char cg[MAXPATHLEN];
78a2a9f3 2340
e65cfafc 2341 cg_escape();
df54106a
SH
2342
2343 while (idx >= 0) {
2344 ret = snprintf(cg, MAXPATHLEN, "/user/%s/%d", user, idx);
2345 if (ret < 0 || ret >= MAXPATHLEN) {
e65cfafc
CB
2346 mysyslog(LOG_ERR, "Username too long.\n", NULL);
2347 return PAM_SESSION_ERR;
2348 }
2349
2350 existed = false;
2351 if (!cgv2_create(cg, uid, gid, &existed)) {
2352 if (existed) {
2353 cgv2_remove(cg);
2354 idx++;
2355 continue;
2356 }
2357 mysyslog(LOG_ERR, "Failed to create a cgroup for user %s.\n", user, NULL);
df54106a
SH
2358 return PAM_SESSION_ERR;
2359 }
2360
56ee748c 2361 existed = false;
e65cfafc 2362 if (!cgv1_create(cg, uid, gid, &existed)) {
56ee748c 2363 if (existed) {
e65cfafc 2364 cgv2_remove(cg);
56ee748c
SH
2365 idx++;
2366 continue;
2367 }
e65cfafc 2368 mysyslog(LOG_ERR, "Failed to create a cgroup for user %s.\n", user, NULL);
df54106a
SH
2369 return PAM_SESSION_ERR;
2370 }
2371
e65cfafc
CB
2372 if (!cg_enter(cg)) {
2373 mysyslog( LOG_ERR, "Failed to enter user cgroup %s for user %s.\n", cg, user, NULL);
df54106a
SH
2374 return PAM_SESSION_ERR;
2375 }
2376 break;
2377 }
2378
2379 return PAM_SUCCESS;
2380}
2381
e65cfafc
CB
2382/* Try to prune cgroups we created and that now are empty from all cgroupfs v1
2383 * hierarchies.
2384 */
2385static bool cgv1_prune_empty_cgroups(const char *user)
2386{
2387 bool controller_removed = true;
2388 bool all_removed = true;
2389 struct cgv1_hierarchy **it;
2390
2391 for (it = cgv1_hierarchies; it && *it; it++) {
2392 int ret;
2393 char *path_base, *path_init;
2394 char **controller;
2395
2396 if (!(*it)->controllers || !(*it)->mountpoint ||
2397 !(*it)->init_cgroup || !(*it)->create_rw_cgroup)
2398 continue;
2399
2400 for (controller = (*it)->controllers; controller && *controller;
2401 controller++) {
2402 bool path_base_rm, path_init_rm;
2403
2404 path_base = must_make_path((*it)->mountpoint, (*it)->base_cgroup, "/user", user, NULL);
2405 lxcfs_debug("cgroupfs v1: Trying to prune \"%s\".\n", path_base);
2406 ret = recursive_rmdir(path_base);
2407 if (ret == -ENOENT || ret >= 0)
2408 path_base_rm = true;
2409 else
2410 path_base_rm = false;
2411 free(path_base);
2412
2413 path_init = must_make_path((*it)->mountpoint, (*it)->init_cgroup, "/user", user, NULL);
2414 lxcfs_debug("cgroupfs v1: Trying to prune \"%s\".\n", path_init);
2415 ret = recursive_rmdir(path_init);
2416 if (ret == -ENOENT || ret >= 0)
2417 path_init_rm = true;
2418 else
2419 path_init_rm = false;
2420 free(path_init);
2421
2422 if (!path_base_rm && !path_init_rm) {
2423 controller_removed = false;
2424 continue;
2425 }
2426
2427 controller_removed = true;
2428 break;
2429 }
2430 if (!controller_removed)
2431 all_removed = false;
2432 }
2433
2434 return all_removed;
2435}
2436
2437/* Try to prune cgroup we created and that now is empty from the cgroupfs v2
2438 * hierarchy.
2439 */
2440static bool cgv2_prune_empty_cgroups(const char *user)
df54106a 2441{
df54106a 2442 int ret;
e65cfafc
CB
2443 struct cgv2_hierarchy *v2;
2444 char *path_base, *path_init;
2445 bool path_base_rm, path_init_rm;
df54106a 2446
e65cfafc
CB
2447 if (!cgv2_hierarchies)
2448 return true;
2449
2450 v2 = *cgv2_hierarchies;
2451
2452 path_base = must_make_path(v2->mountpoint, v2->base_cgroup, "/user", user, NULL);
2453 lxcfs_debug("cgroupfs v2: Trying to prune \"%s\".\n", path_base);
2454 ret = recursive_rmdir(path_base);
2455 if (ret == -ENOENT || ret >= 0)
2456 path_base_rm = true;
2457 else
2458 path_base_rm = false;
2459 free(path_base);
2460
2461 path_init = must_make_path(v2->mountpoint, v2->init_cgroup, "/user", user, NULL);
2462 lxcfs_debug("cgroupfs v2: Trying to prune \"%s\".\n", path_init);
2463 ret = recursive_rmdir(path_init);
2464 if (ret == -ENOENT || ret >= 0)
2465 path_init_rm = true;
2466 else
2467 path_init_rm = false;
2468 free(path_init);
2469
2470 if (!path_base_rm && !path_init_rm)
2471 return false;
2472
2473 return true;
2474}
2475
2476/* Wrapper around cgv{1,2}_prune_empty_cgroups(). */
2477static void cg_prune_empty_cgroups(const char *user)
2478{
2479 (void)cgv1_prune_empty_cgroups(user);
2480 (void)cgv2_prune_empty_cgroups(user);
2481}
2482
2483/* Free allocated information for detected cgroupfs v1 hierarchies. */
2484static void cgv1_free_hierarchies(void)
2485{
2486 struct cgv1_hierarchy **it;
2487
2488 if (!cgv1_hierarchies)
2489 return;
2490
2491 for (it = cgv1_hierarchies; it && *it; it++) {
2492 if ((*it)->controllers) {
2493 char **tmp;
2494 for (tmp = (*it)->controllers; tmp && *tmp; tmp++)
2495 free(*tmp);
2496
2497 free((*it)->controllers);
2498 }
2499 free((*it)->mountpoint);
2500 free((*it)->base_cgroup);
2501 free((*it)->fullcgpath);
2502 free((*it)->init_cgroup);
df54106a 2503 }
e65cfafc
CB
2504 free(cgv1_hierarchies);
2505}
df54106a 2506
e65cfafc
CB
2507/* Free allocated information for the detected cgroupfs v2 hierarchy. */
2508static void cgv2_free_hierarchies(void)
2509{
2510 struct cgv2_hierarchy **it;
2511
2512 if (!cgv2_hierarchies)
2513 return;
2514
2515 for (it = cgv2_hierarchies; it && *it; it++) {
2516 if ((*it)->controllers) {
2517 char **tmp;
2518 for (tmp = (*it)->controllers; tmp && *tmp; tmp++)
2519 free(*tmp);
2520
2521 free((*it)->controllers);
2522 }
2523 free((*it)->mountpoint);
2524 free((*it)->base_cgroup);
2525 free((*it)->fullcgpath);
2526 free((*it)->init_cgroup);
2527 }
2528 free(cgv2_hierarchies);
2529}
2530
2531/* Wrapper around cgv{1,2}_free_hierarchies(). */
2532static void cg_exit(void)
2533{
2534 cgv1_free_hierarchies();
2535 cgv2_free_hierarchies();
2536}
2537
2538int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
2539 const char **argv)
2540{
2541 int ret;
2542 uid_t uid = 0;
2543 gid_t gid = 0;
2544 const char *PAM_user = NULL;
df54106a
SH
2545
2546 ret = pam_get_user(pamh, &PAM_user, NULL);
2547 if (ret != PAM_SUCCESS) {
e65cfafc 2548 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n", NULL);
df54106a
SH
2549 return PAM_SESSION_ERR;
2550 }
2551
e65cfafc
CB
2552 if (!get_uid_gid(PAM_user, &uid, &gid)) {
2553 mysyslog(LOG_ERR, "Failed to get uid and gid for %s.\n", PAM_user, NULL);
2554 return PAM_SESSION_ERR;
2555 }
df54106a 2556
e65cfafc
CB
2557 if (!cg_init(uid, gid)) {
2558 mysyslog(LOG_ERR, "Failed to get list of controllers\n", NULL);
2559 return PAM_SESSION_ERR;
df54106a 2560 }
df54106a 2561
e65cfafc
CB
2562 /* Try to prune cgroups, that are actually empty but were still marked
2563 * as busy by the kernel so we couldn't remove them on session close.
2564 */
2565 cg_prune_empty_cgroups(PAM_user);
2566
2567 if (cg_mount_mode == CGROUP_UNKNOWN)
2568 return PAM_SESSION_ERR;
2569
2570 if (argc > 1 && strcmp(argv[0], "-c") == 0)
2571 cg_mark_to_make_rw(argv[1]);
df54106a 2572
e65cfafc 2573 return handle_login(PAM_user, uid, gid);
df54106a
SH
2574}
2575
2576int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
2577 const char **argv)
2578{
e65cfafc
CB
2579 int ret;
2580 uid_t uid = 0;
2581 gid_t gid = 0;
df54106a 2582 const char *PAM_user = NULL;
df54106a 2583
e65cfafc 2584 ret = pam_get_user(pamh, &PAM_user, NULL);
df54106a 2585 if (ret != PAM_SUCCESS) {
e65cfafc
CB
2586 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n", NULL);
2587 return PAM_SESSION_ERR;
2588 }
2589
2590 if (!get_uid_gid(PAM_user, &uid, &gid)) {
2591 mysyslog(LOG_ERR, "Failed to get uid and gid for %s.\n", PAM_user, NULL);
df54106a
SH
2592 return PAM_SESSION_ERR;
2593 }
2594
e65cfafc
CB
2595 if (cg_mount_mode == CGROUP_UNINITIALIZED) {
2596 if (!cg_init(uid, gid))
2597 mysyslog(LOG_ERR, "Failed to get list of controllers\n", NULL);
2598
df54106a 2599 if (argc > 1 && strcmp(argv[0], "-c") == 0)
e65cfafc 2600 cg_mark_to_make_rw(argv[1]);
df54106a
SH
2601 }
2602
e65cfafc
CB
2603 cg_prune_empty_cgroups(PAM_user);
2604 cg_exit();
2605
df54106a
SH
2606 return PAM_SUCCESS;
2607}