]> git.proxmox.com Git - mirror_lxcfs.git/blobdiff - bindings.c
bindings: caller_may_see_dir()
[mirror_lxcfs.git] / bindings.c
index 5394b44aa21cb2687fde41b3769207e98bd94d94..fb905c19c62daa8736e6d495e2b5dc1c1d6e7200 100644 (file)
@@ -40,6 +40,7 @@ enum {
        LXC_TYPE_PROC_UPTIME,
        LXC_TYPE_PROC_STAT,
        LXC_TYPE_PROC_DISKSTATS,
+       LXC_TYPE_PROC_SWAPS,
 };
 
 struct file_info {
@@ -306,11 +307,11 @@ static void append_line(char **contents, size_t *len, char *line, ssize_t linele
        *len = newlen;
 }
 
-static char *slurp_file(const char *from)
+static char *slurp_file(const char *from, int fd)
 {
        char *line = NULL;
        char *contents = NULL;
-       FILE *f = fopen(from, "r");
+       FILE *f = fdopen(fd, "r");
        size_t len = 0, fulllen = 0;
        ssize_t linelen;
 
@@ -349,12 +350,6 @@ static bool write_string(const char *fnam, const char *string)
        return true;
 }
 
-/*
- * hierarchies, i.e. 'cpu,cpuacct'
- */
-char **hierarchies;
-int num_hierarchies;
-
 struct cgfs_files {
        char *name;
        uint32_t uid, gid;
@@ -374,7 +369,7 @@ static bool store_hierarchy(char *stridx, char *h)
                }
                hierarchies = tmp;
        }
-       
+
        hierarchies[num_hierarchies++] = must_copy_string(h);
        return true;
 }
@@ -383,7 +378,7 @@ static void print_subsystems(void)
 {
        int i;
 
-       fprintf(stderr, "hierarchies:");
+       fprintf(stderr, "hierarchies:\n");
        for (i = 0; i < num_hierarchies; i++) {
                if (hierarchies[i])
                        fprintf(stderr, " %d: %s\n", i, hierarchies[i]);
@@ -410,17 +405,25 @@ static bool in_comma_list(const char *needle, const char *haystack)
 }
 
 /* do we need to do any massaging here?  I'm not sure... */
-static char *find_mounted_controller(const char *controller)
+/* Return the mounted controller and store the corresponding open file descriptor
+ * referring to the controller mountpoint in the private lxcfs namespace in
+ * @cfd.
+ */
+static char *find_mounted_controller(const char *controller, int *cfd)
 {
        int i;
 
        for (i = 0; i < num_hierarchies; i++) {
                if (!hierarchies[i])
                        continue;
-               if (strcmp(hierarchies[i], controller) == 0)
+               if (strcmp(hierarchies[i], controller) == 0) {
+                       *cfd = fd_hierarchies[i];
                        return hierarchies[i];
-               if (in_comma_list(controller, hierarchies[i]))
+               }
+               if (in_comma_list(controller, hierarchies[i])) {
+                       *cfd = fd_hierarchies[i];
                        return hierarchies[i];
+               }
        }
 
        return NULL;
@@ -429,16 +432,17 @@ static char *find_mounted_controller(const char *controller)
 bool cgfs_set_value(const char *controller, const char *cgroup, const char *file,
                const char *value)
 {
+       int cfd;
        size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cgroup / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
+       /* BASEDIR / tmpc / cgroup / file \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, file);
-       
+       snprintf(fnam, len, "%s/%s/%s/%s", BASEDIR, tmpc, cgroup, file);
+
        return write_string(fnam, value);
 }
 
@@ -480,15 +484,16 @@ static void chown_all_cgroup_files(const char *dirname, uid_t uid, gid_t gid)
 
 int cgfs_create(const char *controller, const char *cg, uid_t uid, gid_t gid)
 {
+       int cfd;
        size_t len;
-       char *dirnam, *tmpc = find_mounted_controller(controller);
+       char *dirnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return -EINVAL;
-       /* basedir / tmpc / cg \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cg) + 3;
+       /* BASEDIR / tmpc / cg \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cg) + 3;
        dirnam = alloca(len);
-       snprintf(dirnam, len, "%s/%s/%s", basedir,tmpc, cg);
+       snprintf(dirnam, len, "%s/%s/%s", BASEDIR,tmpc, cg);
 
        if (mkdir(dirnam, 0755) < 0)
                return -errno;
@@ -570,29 +575,31 @@ static bool recursive_rmdir(const char *dirname)
 
 bool cgfs_remove(const char *controller, const char *cg)
 {
+       int cfd;
        size_t len;
-       char *dirnam, *tmpc = find_mounted_controller(controller);
+       char *dirnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cg \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cg) + 3;
+       /* BASEDIR / tmpc / cg \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cg) + 3;
        dirnam = alloca(len);
-       snprintf(dirnam, len, "%s/%s/%s", basedir,tmpc, cg);
+       snprintf(dirnam, len, "%s/%s/%s", BASEDIR,tmpc, cg);
        return recursive_rmdir(dirnam);
 }
 
 bool cgfs_chmod_file(const char *controller, const char *file, mode_t mode)
 {
+       int cfd;
        size_t len;
-       char *pathname, *tmpc = find_mounted_controller(controller);
+       char *pathname, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(file) + 3;
+       /* BASEDIR / tmpc / file \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(file) + 3;
        pathname = alloca(len);
-       snprintf(pathname, len, "%s/%s/%s", basedir, tmpc, file);
+       snprintf(pathname, len, "%s/%s/%s", BASEDIR, tmpc, file);
        if (chmod(pathname, mode) < 0)
                return false;
        return true;
@@ -616,15 +623,16 @@ static int chown_tasks_files(const char *dirname, uid_t uid, gid_t gid)
 
 int cgfs_chown_file(const char *controller, const char *file, uid_t uid, gid_t gid)
 {
+       int cfd;
        size_t len;
-       char *pathname, *tmpc = find_mounted_controller(controller);
+       char *pathname, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return -EINVAL;
-       /* basedir / tmpc / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(file) + 3;
+       /* BASEDIR / tmpc / file \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(file) + 3;
        pathname = alloca(len);
-       snprintf(pathname, len, "%s/%s/%s", basedir, tmpc, file);
+       snprintf(pathname, len, "%s/%s/%s", BASEDIR, tmpc, file);
        if (chown(pathname, uid, gid) < 0)
                return -errno;
 
@@ -637,91 +645,108 @@ int cgfs_chown_file(const char *controller, const char *file, uid_t uid, gid_t g
 
 FILE *open_pids_file(const char *controller, const char *cgroup)
 {
+       int cfd;
        size_t len;
-       char *pathname, *tmpc = find_mounted_controller(controller);
+       char *pathname, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return NULL;
-       /* basedir / tmpc / cgroup / "cgroup.procs" \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 4 + strlen("cgroup.procs");
+       /* BASEDIR / tmpc / cgroup / "cgroup.procs" \0 */
+       len = strlen(BASEDIR) + strlen(tmpc) + strlen(cgroup) + 4 + strlen("cgroup.procs");
        pathname = alloca(len);
-       snprintf(pathname, len, "%s/%s/%s/cgroup.procs", basedir, tmpc, cgroup);
+       snprintf(pathname, len, "%s/%s/%s/cgroup.procs", BASEDIR, tmpc, cgroup);
        return fopen(pathname, "w");
 }
 
-bool cgfs_list_children(const char *controller, const char *cgroup, char ***list)
+static bool cgfs_iterate_cgroup(const char *controller, const char *cgroup, bool directories,
+                                void ***list, size_t typesize,
+                                void* (*iterator)(const char*, const char*, const char*))
 {
+       int cfd, fd, ret;
        size_t len;
-       char *dirname, *tmpc = find_mounted_controller(controller);
+       char *cg, *tmpc;
        char pathname[MAXPATHLEN];
-       size_t sz = 0, asz = BATCH_SIZE;
-       struct dirent dirent, *direntp;
+       size_t sz = 0, asz = 0;
+       struct dirent *dirent;
        DIR *dir;
-       int ret;
-
-       do {
-               *list = malloc(asz * sizeof(char *));
-       } while (!*list);
-       (*list)[0] = NULL;
 
+       tmpc = find_mounted_controller(controller, &cfd);
+       *list = NULL;
        if (!tmpc)
                return false;
 
-       /* basedir / tmpc / cgroup \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
-       dirname = alloca(len);
-       snprintf(dirname, len, "%s/%s/%s", basedir, tmpc, cgroup);
+       /* Make sure we pass a relative path to openat(). */
+       len = strlen(cgroup) + 1 /* . */ + 1 /* \0 */;
+       cg = alloca(len);
+       ret = snprintf(cg, len, "%s%s", *cgroup == '/' ? "." : "", cgroup);
+       if (ret < 0 || (size_t)ret >= len) {
+               fprintf(stderr, "%s: pathname too long under %s\n", __func__, cgroup);
+               return false;
+       }
 
-       dir = opendir(dirname);
+       fd = openat(cfd, cg, O_DIRECTORY);
+       if (fd < 0)
+               return false;
+
+       dir = fdopendir(fd);
        if (!dir)
                return false;
 
-       while (!readdir_r(dir, &dirent, &direntp)) {
+       while ((dirent = readdir(dir))) {
                struct stat mystat;
-               int rc;
-
-               if (!direntp)
-                       break;
 
-               if (!strcmp(direntp->d_name, ".") ||
-                   !strcmp(direntp->d_name, ".."))
+               if (!strcmp(dirent->d_name, ".") ||
+                   !strcmp(dirent->d_name, ".."))
                        continue;
 
-               rc = snprintf(pathname, MAXPATHLEN, "%s/%s", dirname, direntp->d_name);
-               if (rc < 0 || rc >= MAXPATHLEN) {
-                       fprintf(stderr, "%s: pathname too long under %s\n", __func__, dirname);
+               ret = snprintf(pathname, MAXPATHLEN, "%s/%s", cg, dirent->d_name);
+               if (ret < 0 || ret >= MAXPATHLEN) {
+                       fprintf(stderr, "%s: pathname too long under %s\n", __func__, cg);
                        continue;
                }
 
-               ret = lstat(pathname, &mystat);
+               ret = fstatat(cfd, pathname, &mystat, AT_SYMLINK_NOFOLLOW);
                if (ret) {
                        fprintf(stderr, "%s: failed to stat %s: %s\n", __func__, pathname, strerror(errno));
                        continue;
                }
-               if (!S_ISDIR(mystat.st_mode))
+               if ((!directories && !S_ISREG(mystat.st_mode)) ||
+                   (directories && !S_ISDIR(mystat.st_mode)))
                        continue;
 
                if (sz+2 >= asz) {
-                       char **tmp;
+                       void **tmp;
                        asz += BATCH_SIZE;
                        do {
-                               tmp = realloc(*list, asz * sizeof(char *));
+                               tmp = realloc(*list, asz * typesize);
                        } while  (!tmp);
                        *list = tmp;
                }
-               do {
-                       (*list)[sz] = strdup(direntp->d_name);
-               } while (!(*list)[sz]);
+               (*list)[sz] = (*iterator)(controller, cg, dirent->d_name);
                (*list)[sz+1] = NULL;
                sz++;
        }
        if (closedir(dir) < 0) {
-               fprintf(stderr, "%s: failed closedir for %s: %s\n", __func__, dirname, strerror(errno));
+               fprintf(stderr, "%s: failed closedir for %s: %s\n", __func__, cgroup, strerror(errno));
                return false;
        }
        return true;
 }
 
+static void *make_children_list_entry(const char *controller, const char *cgroup, const char *dir_entry)
+{
+       char *dup;
+       do {
+               dup = strdup(dir_entry);
+       } while (!dup);
+       return dup;
+}
+
+bool cgfs_list_children(const char *controller, const char *cgroup, char ***list)
+{
+       return cgfs_iterate_cgroup(controller, cgroup, true, (void***)list, sizeof(*list), &make_children_list_entry);
+}
+
 void free_key(struct cgfs_files *k)
 {
        if (!k)
@@ -744,27 +769,34 @@ void free_keys(struct cgfs_files **keys)
 
 bool cgfs_get_value(const char *controller, const char *cgroup, const char *file, char **value)
 {
+       int ret, fd, cfd;
        size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cgroup / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(file) + 4;
+       /* . + /cgroup + / + file + \0 */
+       len = strlen(cgroup) + strlen(file) + 3;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, file);
+       ret = snprintf(fnam, len, "%s%s/%s", *cgroup == '/' ? "." : "", cgroup, file);
+       if (ret < 0 || (size_t)ret >= len)
+               return NULL;
+
+       fd = openat(cfd, fnam, O_RDONLY);
+       if (fd < 0)
+               return NULL;
 
-       *value = slurp_file(fnam);
+       *value = slurp_file(fnam, fd);
        return *value != NULL;
 }
 
 struct cgfs_files *cgfs_get_key(const char *controller, const char *cgroup, const char *file)
 {
+       int ret, cfd;
        size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
        struct stat sb;
        struct cgfs_files *newkey;
-       int ret;
 
        if (!tmpc)
                return false;
@@ -775,15 +807,15 @@ struct cgfs_files *cgfs_get_key(const char *controller, const char *cgroup, cons
        if (file && index(file, '/'))
                return NULL;
 
-       /* basedir / tmpc / cgroup / file \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
+       /* . + /cgroup + / + file + \0 */
+       len = strlen(cgroup) + 3;
        if (file)
                len += strlen(file) + 1;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s%s%s", basedir, tmpc, cgroup,
-               file ? "/" : "", file ? file : "");
+       snprintf(fnam, len, "%s%s%s%s", *cgroup == '/' ? "." : "", cgroup,
+                file ? "/" : "", file ? file : "");
 
-       ret = stat(fnam, &sb);
+       ret = fstatat(cfd, fnam, &sb, 0);
        if (ret < 0)
                return NULL;
 
@@ -803,92 +835,39 @@ struct cgfs_files *cgfs_get_key(const char *controller, const char *cgroup, cons
        return newkey;
 }
 
-bool cgfs_list_keys(const char *controller, const char *cgroup, struct cgfs_files ***keys)
+static void *make_key_list_entry(const char *controller, const char *cgroup, const char *dir_entry)
 {
-       size_t len;
-       char *dirname, *tmpc = find_mounted_controller(controller);
-       char pathname[MAXPATHLEN];
-       size_t sz = 0, asz = 0;
-       struct dirent dirent, *direntp;
-       DIR *dir;
-       int ret;
-
-       *keys = NULL;
-       if (!tmpc)
-               return false;
-
-       /* basedir / tmpc / cgroup \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + 3;
-       dirname = alloca(len);
-       snprintf(dirname, len, "%s/%s/%s", basedir, tmpc, cgroup);
-
-       dir = opendir(dirname);
-       if (!dir)
-               return false;
-
-       while (!readdir_r(dir, &dirent, &direntp)) {
-               struct stat mystat;
-               int rc;
-
-               if (!direntp)
-                       break;
-
-               if (!strcmp(direntp->d_name, ".") ||
-                   !strcmp(direntp->d_name, ".."))
-                       continue;
-
-               rc = snprintf(pathname, MAXPATHLEN, "%s/%s", dirname, direntp->d_name);
-               if (rc < 0 || rc >= MAXPATHLEN) {
-                       fprintf(stderr, "%s: pathname too long under %s\n", __func__, dirname);
-                       continue;
-               }
-
-               ret = lstat(pathname, &mystat);
-               if (ret) {
-                       fprintf(stderr, "%s: failed to stat %s: %s\n", __func__, pathname, strerror(errno));
-                       continue;
-               }
-               if (!S_ISREG(mystat.st_mode))
-                       continue;
-
-               if (sz+2 >= asz) {
-                       struct cgfs_files **tmp;
-                       asz += BATCH_SIZE;
-                       do {
-                               tmp = realloc(*keys, asz * sizeof(struct cgfs_files *));
-                       } while  (!tmp);
-                       *keys = tmp;
-               }
-               (*keys)[sz] = cgfs_get_key(controller, cgroup, direntp->d_name);
-               (*keys)[sz+1] = NULL;
-               if (!(*keys)[sz]) {
-                       fprintf(stderr, "%s: Error getting files under %s:%s\n",
-                               __func__, controller, cgroup);
-                       continue;
-               }
-               sz++;
+       struct cgfs_files *entry = cgfs_get_key(controller, cgroup, dir_entry);
+       if (!entry) {
+               fprintf(stderr, "%s: Error getting files under %s:%s\n",
+                       __func__, controller, cgroup);
        }
-       if (closedir(dir) < 0) {
-               fprintf(stderr, "%s: failed closedir for %s: %s\n", __func__, dirname, strerror(errno));
-               return false;
-       }
-       return true;
+       return entry;
+}
+
+bool cgfs_list_keys(const char *controller, const char *cgroup, struct cgfs_files ***keys)
+{
+       return cgfs_iterate_cgroup(controller, cgroup, false, (void***)keys, sizeof(*keys), &make_key_list_entry);
 }
 
 bool is_child_cgroup(const char *controller, const char *cgroup, const char *f)
-{      size_t len;
-       char *fnam, *tmpc = find_mounted_controller(controller);
+{
+       int cfd;
+       size_t len;
+       char *fnam, *tmpc = find_mounted_controller(controller, &cfd);
        int ret;
        struct stat sb;
 
        if (!tmpc)
                return false;
-       /* basedir / tmpc / cgroup / f \0 */
-       len = strlen(basedir) + strlen(tmpc) + strlen(cgroup) + strlen(f) + 4;
+       /* . + /cgroup + / + f + \0 */
+       len = strlen(cgroup) + strlen(f) + 3;
        fnam = alloca(len);
-       snprintf(fnam, len, "%s/%s/%s/%s", basedir, tmpc, cgroup, f);
+       ret = snprintf(fnam, len, "%s%s/%s", *cgroup == '/' ? "." : "", cgroup, f);
+       if (ret < 0 || (size_t)ret >= len)
+               return false;
 
-       ret = stat(fnam, &sb);
+       ret = fstatat(cfd, fnam, &sb, 0);
        if (ret < 0 || !S_ISDIR(sb.st_mode))
                return false;
        return true;
@@ -900,19 +879,25 @@ bool is_child_cgroup(const char *controller, const char *cgroup, const char *f)
 static bool recv_creds(int sock, struct ucred *cred, char *v);
 static int wait_for_pid(pid_t pid);
 static int send_creds(int sock, struct ucred *cred, char v, bool pingfirst);
+static int send_creds_clone_wrapper(void *arg);
 
 /*
- * fork a task which switches to @task's namespace and writes '1'.
+ * clone a task which switches to @task's namespace and writes '1'.
  * over a unix sock so we can read the task's reaper's pid in our
  * namespace
+ *
+ * Note: glibc's fork() does not respect pidns, which can lead to failed
+ * assertions inside glibc (and thus failed forks) if the child's pid in
+ * the pidns and the parent pid outside are identical. Using clone prevents
+ * this issue.
  */
 static void write_task_init_pid_exit(int sock, pid_t target)
 {
-       struct ucred cred;
        char fnam[100];
        pid_t pid;
-       char v;
        int fd, ret;
+       size_t stack_size = sysconf(_SC_PAGESIZE);
+       void *stack = alloca(stack_size);
 
        ret = snprintf(fnam, sizeof(fnam), "/proc/%d/ns/pid", (int)target);
        if (ret < 0 || ret >= sizeof(fnam))
@@ -928,7 +913,7 @@ static void write_task_init_pid_exit(int sock, pid_t target)
                close(fd);
                _exit(1);
        }
-       pid = fork();
+       pid = clone(send_creds_clone_wrapper, stack + stack_size, SIGCHLD, &sock);
        if (pid < 0)
                _exit(1);
        if (pid != 0) {
@@ -936,6 +921,12 @@ static void write_task_init_pid_exit(int sock, pid_t target)
                        _exit(1);
                _exit(0);
        }
+}
+
+static int send_creds_clone_wrapper(void *arg) {
+       struct ucred cred;
+       char v;
+       int sock = *(int *)arg;
 
        /* we are the child */
        cred.uid = 0;
@@ -943,8 +934,8 @@ static void write_task_init_pid_exit(int sock, pid_t target)
        cred.pid = 1;
        v = '1';
        if (send_creds(sock, &cred, v, true) != SEND_CREDS_OK)
-               _exit(1);
-       _exit(0);
+               return 1;
+       return 0;
 }
 
 static pid_t get_init_pid_for_task(pid_t task)
@@ -1052,9 +1043,8 @@ static void must_strcat_pid(char **src, size_t *sz, size_t *asz, pid_t pid)
                *src = tmp;
                *asz += BUF_RESERVE_SIZE;
        }
-       memcpy((*src) +*sz , tmp, tmplen);
+       memcpy((*src) +*sz , tmp, tmplen+1); /* include the \0 */
        *sz += tmplen;
-       (*src)[*sz] = '\0';
 }
 
 /*
@@ -1213,13 +1203,14 @@ static void stripnewline(char *x)
 
 static char *get_pid_cgroup(pid_t pid, const char *contrl)
 {
+       int cfd;
        char fnam[PROCLEN];
        FILE *f;
        char *answer = NULL;
        char *line = NULL;
        size_t len = 0;
        int ret;
-       const char *h = find_mounted_controller(contrl);
+       const char *h = find_mounted_controller(contrl, &cfd);
        if (!h)
                return NULL;
 
@@ -1332,10 +1323,18 @@ static bool caller_is_in_ancestor(pid_t pid, const char *contrl, const char *cg,
        prune_init_slice(c2);
 
        /*
-        * callers pass in '/' for root cgroup, otherwise they pass
-        * in a cgroup without leading '/'
+        * callers pass in '/' or './' (openat()) for root cgroup, otherwise
+        * they pass in a cgroup without leading '/'
+        *
+        * The original line here was:
+        *      linecmp = *cg == '/' ? c2 : c2+1;
+        * TODO: I'm not sure why you'd want to increment when *cg != '/'?
+        *       Serge, do you know?
         */
-       linecmp = *cg == '/' ? c2 : c2+1;
+       if (*cg == '/' || !strncmp(cg, "./", 2))
+               linecmp = c2;
+       else
+               linecmp = c2 + 1;
        if (strncmp(linecmp, cg, strlen(linecmp)) != 0) {
                if (nextcg) {
                        *nextcg = get_next_cgroup_dir(linecmp, cg);
@@ -1358,7 +1357,7 @@ static bool caller_may_see_dir(pid_t pid, const char *contrl, const char *cg)
        char *c2, *task_cg;
        size_t target_len, task_len;
 
-       if (strcmp(cg, "/") == 0)
+       if (strcmp(cg, "/") == 0 || strcmp(cg, "./") == 0)
                return true;
 
        c2 = get_pid_cgroup(pid, contrl);
@@ -1696,10 +1695,12 @@ int cg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset
                ret = 0;
                goto out;
        }
-       for (i = 0; clist[i]; i++) {
-               if (filler(buf, clist[i], NULL, 0) != 0) {
-                       ret = -EIO;
-                       goto out;
+       if (clist) {
+               for (i = 0; clist[i]; i++) {
+                       if (filler(buf, clist[i], NULL, 0) != 0) {
+                               ret = -EIO;
+                               goto out;
+                       }
                }
        }
        ret = 0;
@@ -1714,22 +1715,29 @@ out:
        return ret;
 }
 
-static void do_release_file_info(struct file_info *f)
+static void do_release_file_info(struct fuse_file_info *fi)
 {
+       struct file_info *f = (struct file_info *)fi->fh;
+
        if (!f)
                return;
+
+       fi->fh = 0;
+
        free(f->controller);
+       f->controller = NULL;
        free(f->cgroup);
+       f->cgroup = NULL;
        free(f->file);
+       f->file = NULL;
        free(f->buf);
+       f->buf = NULL;
        free(f);
 }
 
 int cg_releasedir(const char *path, struct fuse_file_info *fi)
 {
-       struct file_info *d = (struct file_info *)fi->fh;
-
-       do_release_file_info(d);
+       do_release_file_info(fi);
        return 0;
 }
 
@@ -1776,7 +1784,6 @@ int cg_open(const char *path, struct fuse_file_info *fi)
                goto out;
        }
        if (!fc_may_access(fc, controller, path1, path2, fi->flags)) {
-               // should never get here
                ret = -EACCES;
                goto out;
        }
@@ -1802,11 +1809,69 @@ out:
        return ret;
 }
 
-int cg_release(const char *path, struct fuse_file_info *fi)
+int cg_access(const char *path, int mode)
 {
-       struct file_info *f = (struct file_info *)fi->fh;
+       const char *cgroup;
+       char *last = NULL, *path1, *path2, * cgdir = NULL, *controller;
+       struct cgfs_files *k = NULL;
+       struct fuse_context *fc = fuse_get_context();
+       int ret;
+
+       if (!fc)
+               return -EIO;
+
+       controller = pick_controller_from_path(fc, path);
+       if (!controller)
+               return -EIO;
+       cgroup = find_cgroup_in_path(path);
+       if (!cgroup) {
+               // access("/sys/fs/cgroup/systemd", mode) - rx allowed, w not
+               if ((mode & W_OK) == 0)
+                       return 0;
+               return -EACCES;
+       }
 
-       do_release_file_info(f);
+       get_cgdir_and_path(cgroup, &cgdir, &last);
+       if (!last) {
+               path1 = "/";
+               path2 = cgdir;
+       } else {
+               path1 = cgdir;
+               path2 = last;
+       }
+
+       k = cgfs_get_key(controller, path1, path2);
+       if (!k) {
+               if ((mode & W_OK) == 0)
+                       ret = 0;
+               else
+                       ret = -EACCES;
+               goto out;
+       }
+       free_key(k);
+
+       pid_t initpid = lookup_initpid_in_store(fc->pid);
+       if (initpid <= 0)
+               initpid = fc->pid;
+       if (!caller_may_see_dir(initpid, controller, path1)) {
+               ret = -ENOENT;
+               goto out;
+       }
+       if (!fc_may_access(fc, controller, path1, path2, mode)) {
+               ret = -EACCES;
+               goto out;
+       }
+
+       ret = 0;
+
+out:
+       free(cgdir);
+       return ret;
+}
+
+int cg_release(const char *path, struct fuse_file_info *fi)
+{
+       do_release_file_info(fi);
        return 0;
 }
 
@@ -1971,31 +2036,61 @@ static bool recv_creds(int sock, struct ucred *cred, char *v)
        return true;
 }
 
+struct pid_ns_clone_args {
+       int *cpipe;
+       int sock;
+       pid_t tpid;
+       int (*wrapped) (int, pid_t); // pid_from_ns or pid_to_ns
+};
+
+/*
+ * pid_ns_clone_wrapper - wraps pid_to_ns or pid_from_ns for usage
+ * with clone(). This simply writes '1' as ACK back to the parent
+ * before calling the actual wrapped function.
+ */
+static int pid_ns_clone_wrapper(void *arg) {
+       struct pid_ns_clone_args* args = (struct pid_ns_clone_args *) arg;
+       char b = '1';
+
+       close(args->cpipe[0]);
+       if (write(args->cpipe[1], &b, sizeof(char)) < 0) {
+               fprintf(stderr, "%s (child): error on write: %s\n",
+                       __func__, strerror(errno));
+       }
+       close(args->cpipe[1]);
+       return args->wrapped(args->sock, args->tpid);
+}
 
 /*
  * pid_to_ns - reads pids from a ucred over a socket, then writes the
  * int value back over the socket.  This shifts the pid from the
  * sender's pidns into tpid's pidns.
  */
-static void pid_to_ns(int sock, pid_t tpid)
+static int pid_to_ns(int sock, pid_t tpid)
 {
        char v = '0';
        struct ucred cred;
 
        while (recv_creds(sock, &cred, &v)) {
                if (v == '1')
-                       _exit(0);
+                       return 0;
                if (write(sock, &cred.pid, sizeof(pid_t)) != sizeof(pid_t))
-                       _exit(1);
+                       return 1;
        }
-       _exit(0);
+       return 0;
 }
 
+
 /*
  * pid_to_ns_wrapper: when you setns into a pidns, you yourself remain
- * in your old pidns.  Only children which you fork will be in the target
- * pidns.  So the pid_to_ns_wrapper does the setns, then forks a child to
- * actually convert pids
+ * in your old pidns.  Only children which you clone will be in the target
+ * pidns.  So the pid_to_ns_wrapper does the setns, then clones a child to
+ * actually convert pids.
+ *
+ * Note: glibc's fork() does not respect pidns, which can lead to failed
+ * assertions inside glibc (and thus failed forks) if the child's pid in
+ * the pidns and the parent pid outside are identical. Using clone prevents
+ * this issue.
  */
 static void pid_to_ns_wrapper(int sock, pid_t tpid)
 {
@@ -2017,21 +2112,19 @@ static void pid_to_ns_wrapper(int sock, pid_t tpid)
        if (pipe(cpipe) < 0)
                _exit(1);
 
-       cpid = fork();
+       struct pid_ns_clone_args args = {
+               .cpipe = cpipe,
+               .sock = sock,
+               .tpid = tpid,
+               .wrapped = &pid_to_ns
+       };
+       size_t stack_size = sysconf(_SC_PAGESIZE);
+       void *stack = alloca(stack_size);
+
+       cpid = clone(pid_ns_clone_wrapper, stack + stack_size, SIGCHLD, &args);
        if (cpid < 0)
                _exit(1);
 
-       if (!cpid) {
-               char b = '1';
-               close(cpipe[0]);
-               if (write(cpipe[1], &b, sizeof(char)) < 0) {
-                       fprintf(stderr, "%s (child): erorr on write: %s\n",
-                               __func__, strerror(errno));
-               }
-               close(cpipe[1]);
-               pid_to_ns(sock, tpid);
-               _exit(1); // not reached
-       }
        // give the child 1 second to be done forking and
        // write its ack
        if (!wait_for_sock(cpipe[0], 1))
@@ -2166,7 +2259,7 @@ int cg_read(const char *path, char *buf, size_t size, off_t offset,
        free_key(k);
 
 
-       if (!fc_may_access(fc, f->controller, f->cgroup, f->file, O_RDONLY)) { // should never get here
+       if (!fc_may_access(fc, f->controller, f->cgroup, f->file, O_RDONLY)) {
                ret = -EACCES;
                goto out;
        }
@@ -2203,7 +2296,7 @@ out:
        return ret;
 }
 
-static void pid_from_ns(int sock, pid_t tpid)
+static int pid_from_ns(int sock, pid_t tpid)
 {
        pid_t vpid;
        struct ucred cred;
@@ -2215,12 +2308,12 @@ static void pid_from_ns(int sock, pid_t tpid)
        while (1) {
                if (!wait_for_sock(sock, 2)) {
                        fprintf(stderr, "%s: timeout reading from parent\n", __func__);
-                       _exit(1);
+                       return 1;
                }
                if ((ret = read(sock, &vpid, sizeof(pid_t))) != sizeof(pid_t)) {
                        fprintf(stderr, "%s: bad read from parent: %s\n",
                                __func__, strerror(errno));
-                       _exit(1);
+                       return 1;
                }
                if (vpid == -1) // done
                        break;
@@ -2230,10 +2323,10 @@ static void pid_from_ns(int sock, pid_t tpid)
                        v = '1';
                        cred.pid = getpid();
                        if (send_creds(sock, &cred, v, false) != SEND_CREDS_OK)
-                               _exit(1);
+                               return 1;
                }
        }
-       _exit(0);
+       return 0;
 }
 
 static void pid_from_ns_wrapper(int sock, pid_t tpid)
@@ -2256,40 +2349,30 @@ static void pid_from_ns_wrapper(int sock, pid_t tpid)
        if (pipe(cpipe) < 0)
                _exit(1);
 
-loop:
-       cpid = fork();
+       struct pid_ns_clone_args args = {
+               .cpipe = cpipe,
+               .sock = sock,
+               .tpid = tpid,
+               .wrapped = &pid_from_ns
+       };
+       size_t stack_size = sysconf(_SC_PAGESIZE);
+       void *stack = alloca(stack_size);
 
+       cpid = clone(pid_ns_clone_wrapper, stack + stack_size, SIGCHLD, &args);
        if (cpid < 0)
                _exit(1);
 
-       if (!cpid) {
-               char b = '1';
-               close(cpipe[0]);
-               if (write(cpipe[1], &b, sizeof(char)) < 0) {
-                       fprintf(stderr, "%s (child): erorr on write: %s\n",
-                               __func__, strerror(errno));
-               }
-               close(cpipe[1]);
-               pid_from_ns(sock, tpid);
-       }
-
        // give the child 1 second to be done forking and
        // write its ack
        if (!wait_for_sock(cpipe[0], 1))
-               goto again;
+               _exit(1);
        ret = read(cpipe[0], &v, 1);
-       if (ret != sizeof(char) || v != '1') {
-               goto again;
-       }
+       if (ret != sizeof(char) || v != '1')
+               _exit(1);
 
        if (!wait_for_pid(cpid))
                _exit(1);
        _exit(0);
-
-again:
-       kill(cpid, SIGKILL);
-       wait_for_pid(cpid);
-       goto loop;
 }
 
 /*
@@ -2815,7 +2898,7 @@ static int read_file(const char *path, char *buf, size_t size,
                return 0;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l = snprintf(cache, cache_size, "%s", line);
+               ssize_t l = snprintf(cache, cache_size, "%s", line);
                if (l < 0) {
                        perror("Error writing to cache");
                        rv = 0;
@@ -2832,7 +2915,8 @@ static int read_file(const char *path, char *buf, size_t size,
        }
 
        d->size = total_len;
-       if (total_len > size ) total_len = size;
+       if (total_len > size)
+               total_len = size;
 
        /* read from off 0 */
        memcpy(buf, d->buf, total_len);
@@ -2911,6 +2995,7 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
        cg = get_pid_cgroup(initpid, "memory");
        if (!cg)
                return read_file("/proc/meminfo", buf, size, d);
+       prune_init_slice(cg);
 
        memlimit = get_min_memlimit(cg);
        if (!cgfs_get_value("memory", cg, "memory.usage_in_bytes", &memusage_str))
@@ -2952,7 +3037,7 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                goto err;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
+               ssize_t l;
                char *printme, lbuf[100];
 
                memset(lbuf, 0, 100);
@@ -2972,9 +3057,12 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                        snprintf(lbuf, 100, "SwapTotal:      %8lu kB\n", memswlimit - memlimit);
                        printme = lbuf;
                } else if (startswith(line, "SwapFree:") && memswlimit > 0 && memswusage > 0) {
-                       snprintf(lbuf, 100, "SwapFree:       %8lu kB\n", 
+                       snprintf(lbuf, 100, "SwapFree:       %8lu kB\n",
                                (memswlimit - memlimit) - (memswusage - memusage));
                        printme = lbuf;
+               } else if (startswith(line, "Slab:")) {
+                       snprintf(lbuf, 100, "Slab:        %8lu kB\n", 0UL);
+                       printme = lbuf;
                } else if (startswith(line, "Buffers:")) {
                        snprintf(lbuf, 100, "Buffers:        %8lu kB\n", 0UL);
                        printme = lbuf;
@@ -3070,8 +3158,8 @@ static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
        char *cpuset = NULL;
        char *line = NULL;
        size_t linelen = 0, total_len = 0, rv = 0;
-       bool am_printing = false;
-       int curcpu = -1;
+       bool am_printing = false, firstline = true, is_s390x = false;
+       int curcpu = -1, cpu;
        char *cache = d->buf;
        size_t cache_size = d->buflen;
        FILE *f = NULL;
@@ -3093,6 +3181,7 @@ static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
        cg = get_pid_cgroup(initpid, "cpuset");
        if (!cg)
                return read_file("proc/cpuinfo", buf, size, d);
+       prune_init_slice(cg);
 
        cpuset = get_cpuset(cg);
        if (!cpuset)
@@ -3103,7 +3192,17 @@ static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
                goto err;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
+               ssize_t l;
+               if (firstline) {
+                       firstline = false;
+                       if (strstr(line, "IBM/S390") != NULL) {
+                               is_s390x = true;
+                               am_printing = true;
+                               continue;
+                       }
+               }
+               if (strncmp(line, "# processors:", 12) == 0)
+                       continue;
                if (is_processor_line(line)) {
                        am_printing = cpuline_in_cpuset(line, cpuset);
                        if (am_printing) {
@@ -3124,6 +3223,31 @@ static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
                                total_len += l;
                        }
                        continue;
+               } else if (is_s390x && sscanf(line, "processor %d:", &cpu) == 1) {
+                       char *p;
+                       if (!cpu_in_cpuset(cpu, cpuset))
+                               continue;
+                       curcpu ++;
+                       p = strchr(line, ':');
+                       if (!p || !*p)
+                               goto err;
+                       p++;
+                       l = snprintf(cache, cache_size, "processor %d:%s", curcpu, p);
+                       if (l < 0) {
+                               perror("Error writing to cache");
+                               rv = 0;
+                               goto err;
+                       }
+                       if (l >= cache_size) {
+                               fprintf(stderr, "Internal error: truncated write to cache\n");
+                               rv = 0;
+                               goto err;
+                       }
+                       cache += l;
+                       cache_size -= l;
+                       total_len += l;
+                       continue;
+
                }
                if (am_printing) {
                        l = snprintf(cache, cache_size, "%s", line);
@@ -3143,6 +3267,38 @@ static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
                }
        }
 
+       if (is_s390x) {
+               char *origcache = d->buf;
+               ssize_t l;
+               do {
+                       d->buf = malloc(d->buflen);
+               } while (!d->buf);
+               cache = d->buf;
+               cache_size = d->buflen;
+               total_len = 0;
+               l = snprintf(cache, cache_size, "vendor_id       : IBM/S390\n");
+               if (l < 0 || l >= cache_size) {
+                       free(origcache);
+                       goto err;
+               }
+               cache_size -= l;
+               cache += l;
+               total_len += l;
+               l = snprintf(cache, cache_size, "# processors    : %d\n", curcpu + 1);
+               if (l < 0 || l >= cache_size) {
+                       free(origcache);
+                       goto err;
+               }
+               cache_size -= l;
+               cache += l;
+               total_len += l;
+               l = snprintf(cache, cache_size, "%s", origcache);
+               free(origcache);
+               if (l < 0 || l >= cache_size)
+                       goto err;
+               total_len += l;
+       }
+
        d->cached = 1;
        d->size = total_len;
        if (total_len > size ) total_len = size;
@@ -3196,6 +3352,7 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
        cg = get_pid_cgroup(initpid, "cpuset");
        if (!cg)
                return read_file("/proc/stat", buf, size, d);
+       prune_init_slice(cg);
 
        cpuset = get_cpuset(cg);
        if (!cpuset)
@@ -3212,7 +3369,7 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
        }
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
+               ssize_t l;
                int cpu;
                char cpu_char[10]; /* That's a lot of cores */
                char *c;
@@ -3340,6 +3497,7 @@ static unsigned long get_reaper_busy(pid_t task)
        cgroup = get_pid_cgroup(initpid, "cpuacct");
        if (!cgroup)
                goto out;
+       prune_init_slice(cgroup);
        if (!cgfs_get_value("cpuacct", cgroup, "cpuacct.usage", &usage_str))
                goto out;
        usage = strtoul(usage_str, NULL, 10);
@@ -3383,7 +3541,7 @@ static int proc_uptime_read(char *buf, size_t size, off_t offset,
        long int reaperage = getreaperage(fc->pid);
        unsigned long int busytime = get_reaper_busy(fc->pid), idletime;
        char *cache = d->buf;
-       size_t total_len = 0;
+       ssize_t total_len = 0;
 
 #if RELOADTEST
        iwashere();
@@ -3459,16 +3617,17 @@ static int proc_diskstats_read(char *buf, size_t size, off_t offset,
        cg = get_pid_cgroup(initpid, "blkio");
        if (!cg)
                return read_file("/proc/diskstats", buf, size, d);
+       prune_init_slice(cg);
 
-       if (!cgfs_get_value("blkio", cg, "blkio.io_serviced", &io_serviced_str))
+       if (!cgfs_get_value("blkio", cg, "blkio.io_serviced_recursive", &io_serviced_str))
                goto err;
-       if (!cgfs_get_value("blkio", cg, "blkio.io_merged", &io_merged_str))
+       if (!cgfs_get_value("blkio", cg, "blkio.io_merged_recursive", &io_merged_str))
                goto err;
-       if (!cgfs_get_value("blkio", cg, "blkio.io_service_bytes", &io_service_bytes_str))
+       if (!cgfs_get_value("blkio", cg, "blkio.io_service_bytes_recursive", &io_service_bytes_str))
                goto err;
-       if (!cgfs_get_value("blkio", cg, "blkio.io_wait_time", &io_wait_time_str))
+       if (!cgfs_get_value("blkio", cg, "blkio.io_wait_time_recursive", &io_wait_time_str))
                goto err;
-       if (!cgfs_get_value("blkio", cg, "blkio.io_service_time", &io_service_time_str))
+       if (!cgfs_get_value("blkio", cg, "blkio.io_service_time_recursive", &io_service_time_str))
                goto err;
 
 
@@ -3477,48 +3636,46 @@ static int proc_diskstats_read(char *buf, size_t size, off_t offset,
                goto err;
 
        while (getline(&line, &linelen, f) != -1) {
-               size_t l;
-               char *printme, lbuf[256];
+               ssize_t l;
+               char lbuf[256];
 
                i = sscanf(line, "%u %u %71s", &major, &minor, dev_name);
-               if(i == 3){
-                       get_blkio_io_value(io_serviced_str, major, minor, "Read", &read);
-                       get_blkio_io_value(io_serviced_str, major, minor, "Write", &write);
-                       get_blkio_io_value(io_merged_str, major, minor, "Read", &read_merged);
-                       get_blkio_io_value(io_merged_str, major, minor, "Write", &write_merged);
-                       get_blkio_io_value(io_service_bytes_str, major, minor, "Read", &read_sectors);
-                       read_sectors = read_sectors/512;
-                       get_blkio_io_value(io_service_bytes_str, major, minor, "Write", &write_sectors);
-                       write_sectors = write_sectors/512;
-
-                       get_blkio_io_value(io_service_time_str, major, minor, "Read", &rd_svctm);
-                       rd_svctm = rd_svctm/1000000;
-                       get_blkio_io_value(io_wait_time_str, major, minor, "Read", &rd_wait);
-                       rd_wait = rd_wait/1000000;
-                       read_ticks = rd_svctm + rd_wait;
-
-                       get_blkio_io_value(io_service_time_str, major, minor, "Write", &wr_svctm);
-                       wr_svctm =  wr_svctm/1000000;
-                       get_blkio_io_value(io_wait_time_str, major, minor, "Write", &wr_wait);
-                       wr_wait =  wr_wait/1000000;
-                       write_ticks = wr_svctm + wr_wait;
-
-                       get_blkio_io_value(io_service_time_str, major, minor, "Total", &tot_ticks);
-                       tot_ticks =  tot_ticks/1000000;
-               }else{
+               if (i != 3)
                        continue;
-               }
+
+               get_blkio_io_value(io_serviced_str, major, minor, "Read", &read);
+               get_blkio_io_value(io_serviced_str, major, minor, "Write", &write);
+               get_blkio_io_value(io_merged_str, major, minor, "Read", &read_merged);
+               get_blkio_io_value(io_merged_str, major, minor, "Write", &write_merged);
+               get_blkio_io_value(io_service_bytes_str, major, minor, "Read", &read_sectors);
+               read_sectors = read_sectors/512;
+               get_blkio_io_value(io_service_bytes_str, major, minor, "Write", &write_sectors);
+               write_sectors = write_sectors/512;
+
+               get_blkio_io_value(io_service_time_str, major, minor, "Read", &rd_svctm);
+               rd_svctm = rd_svctm/1000000;
+               get_blkio_io_value(io_wait_time_str, major, minor, "Read", &rd_wait);
+               rd_wait = rd_wait/1000000;
+               read_ticks = rd_svctm + rd_wait;
+
+               get_blkio_io_value(io_service_time_str, major, minor, "Write", &wr_svctm);
+               wr_svctm =  wr_svctm/1000000;
+               get_blkio_io_value(io_wait_time_str, major, minor, "Write", &wr_wait);
+               wr_wait =  wr_wait/1000000;
+               write_ticks = wr_svctm + wr_wait;
+
+               get_blkio_io_value(io_service_time_str, major, minor, "Total", &tot_ticks);
+               tot_ticks =  tot_ticks/1000000;
 
                memset(lbuf, 0, 256);
-               if (read || write || read_merged || write_merged || read_sectors || write_sectors || read_ticks || write_ticks) {
+               if (read || write || read_merged || write_merged || read_sectors || write_sectors || read_ticks || write_ticks)
                        snprintf(lbuf, 256, "%u       %u %s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
                                major, minor, dev_name, read, read_merged, read_sectors, read_ticks,
                                write, write_merged, write_sectors, write_ticks, ios_pgr, tot_ticks, rq_ticks);
-                       printme = lbuf;
-               } else
+               else
                        continue;
 
-               l = snprintf(cache, cache_size, "%s", printme);
+               l = snprintf(cache, cache_size, "%s", lbuf);
                if (l < 0) {
                        perror("Error writing to fuse buf");
                        rv = 0;
@@ -3553,6 +3710,122 @@ err:
        return rv;
 }
 
+static int proc_swaps_read(char *buf, size_t size, off_t offset,
+               struct fuse_file_info *fi)
+{
+       struct fuse_context *fc = fuse_get_context();
+       struct file_info *d = (struct file_info *)fi->fh;
+       char *cg = NULL;
+       char *memswlimit_str = NULL, *memlimit_str = NULL, *memusage_str = NULL, *memswusage_str = NULL,
+             *memswlimit_default_str = NULL, *memswusage_default_str = NULL;
+       unsigned long memswlimit = 0, memlimit = 0, memusage = 0, memswusage = 0, swap_total = 0, swap_free = 0;
+       ssize_t total_len = 0, rv = 0;
+       ssize_t l = 0;
+       char *cache = d->buf;
+
+       if (offset) {
+               if (offset > d->size)
+                       return -EINVAL;
+               if (!d->cached)
+                       return 0;
+               int left = d->size - offset;
+               total_len = left > size ? size: left;
+               memcpy(buf, cache + offset, total_len);
+               return total_len;
+       }
+
+       pid_t initpid = lookup_initpid_in_store(fc->pid);
+       if (initpid <= 0)
+               initpid = fc->pid;
+       cg = get_pid_cgroup(initpid, "memory");
+       if (!cg)
+               return read_file("/proc/swaps", buf, size, d);
+       prune_init_slice(cg);
+
+       if (!cgfs_get_value("memory", cg, "memory.limit_in_bytes", &memlimit_str))
+               goto err;
+
+       if (!cgfs_get_value("memory", cg, "memory.usage_in_bytes", &memusage_str))
+               goto err;
+
+       memlimit = strtoul(memlimit_str, NULL, 10);
+       memusage = strtoul(memusage_str, NULL, 10);
+
+       if (cgfs_get_value("memory", cg, "memory.memsw.usage_in_bytes", &memswusage_str) &&
+           cgfs_get_value("memory", cg, "memory.memsw.limit_in_bytes", &memswlimit_str)) {
+
+                /* If swap accounting is turned on, then default value is assumed to be that of cgroup / */
+                if (!cgfs_get_value("memory", "/", "memory.memsw.limit_in_bytes", &memswlimit_default_str))
+                    goto err;
+                if (!cgfs_get_value("memory", "/", "memory.memsw.usage_in_bytes", &memswusage_default_str))
+                    goto err;
+
+               memswlimit = strtoul(memswlimit_str, NULL, 10);
+               memswusage = strtoul(memswusage_str, NULL, 10);
+
+                if (!strcmp(memswlimit_str, memswlimit_default_str))
+                    memswlimit = 0;
+                if (!strcmp(memswusage_str, memswusage_default_str))
+                    memswusage = 0;
+
+               swap_total = (memswlimit - memlimit) / 1024;
+               swap_free = (memswusage - memusage) / 1024;
+       }
+
+       total_len = snprintf(d->buf, d->size, "Filename\t\t\t\tType\t\tSize\tUsed\tPriority\n");
+
+       /* When no mem + swap limit is specified or swapaccount=0*/
+       if (!memswlimit) {
+               char *line = NULL;
+               size_t linelen = 0;
+               FILE *f = fopen("/proc/meminfo", "r");
+
+               if (!f)
+                       goto err;
+
+               while (getline(&line, &linelen, f) != -1) {
+                       if (startswith(line, "SwapTotal:")) {
+                               sscanf(line, "SwapTotal:      %8lu kB", &swap_total);
+                       } else if (startswith(line, "SwapFree:")) {
+                               sscanf(line, "SwapFree:      %8lu kB", &swap_free);
+                       }
+               }
+
+               free(line);
+               fclose(f);
+       }
+
+       if (swap_total > 0) {
+               l = snprintf(d->buf + total_len, d->size - total_len,
+                               "none%*svirtual\t\t%lu\t%lu\t0\n", 36, " ",
+                               swap_total, swap_free);
+               total_len += l;
+       }
+
+       if (total_len < 0 || l < 0) {
+               perror("Error writing to cache");
+               rv = 0;
+               goto err;
+       }
+
+       d->cached = 1;
+       d->size = (int)total_len;
+
+       if (total_len > size) total_len = size;
+       memcpy(buf, d->buf, total_len);
+       rv = total_len;
+
+err:
+       free(cg);
+       free(memswlimit_str);
+       free(memlimit_str);
+       free(memusage_str);
+       free(memswusage_str);
+       free(memswusage_default_str);
+       free(memswlimit_default_str);
+       return rv;
+}
+
 static off_t get_procfile_size(const char *which)
 {
        FILE *f = fopen(which, "r");
@@ -3588,7 +3861,8 @@ int proc_getattr(const char *path, struct stat *sb)
                        strcmp(path, "/proc/cpuinfo") == 0 ||
                        strcmp(path, "/proc/uptime") == 0 ||
                        strcmp(path, "/proc/stat") == 0 ||
-                       strcmp(path, "/proc/diskstats") == 0) {
+                       strcmp(path, "/proc/diskstats") == 0 ||
+                       strcmp(path, "/proc/swaps") == 0) {
                sb->st_size = 0;
                sb->st_mode = S_IFREG | 00444;
                sb->st_nlink = 1;
@@ -3605,7 +3879,8 @@ int proc_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offs
                                filler(buf, "meminfo", NULL, 0) != 0 ||
                                filler(buf, "stat", NULL, 0) != 0 ||
                                filler(buf, "uptime", NULL, 0) != 0 ||
-                               filler(buf, "diskstats", NULL, 0) != 0)
+                               filler(buf, "diskstats", NULL, 0) != 0 ||
+                               filler(buf, "swaps", NULL, 0) != 0)
                return -EINVAL;
        return 0;
 }
@@ -3625,6 +3900,8 @@ int proc_open(const char *path, struct fuse_file_info *fi)
                type = LXC_TYPE_PROC_STAT;
        else if (strcmp(path, "/proc/diskstats") == 0)
                type = LXC_TYPE_PROC_DISKSTATS;
+       else if (strcmp(path, "/proc/swaps") == 0)
+               type = LXC_TYPE_PROC_SWAPS;
        if (type == -1)
                return -ENOENT;
 
@@ -3647,11 +3924,17 @@ int proc_open(const char *path, struct fuse_file_info *fi)
        return 0;
 }
 
-int proc_release(const char *path, struct fuse_file_info *fi)
+int proc_access(const char *path, int mask)
 {
-       struct file_info *f = (struct file_info *)fi->fh;
+       /* these are all read-only */
+       if ((mask & ~R_OK) != 0)
+               return -EACCES;
+       return 0;
+}
 
-       do_release_file_info(f);
+int proc_release(const char *path, struct fuse_file_info *fi)
+{
+       do_release_file_info(fi);
        return 0;
 }
 
@@ -3671,6 +3954,8 @@ int proc_read(const char *path, char *buf, size_t size, off_t offset,
                return proc_stat_read(buf, size, offset, fi);
        case LXC_TYPE_PROC_DISKSTATS:
                return proc_diskstats_read(buf, size, offset, fi);
+       case LXC_TYPE_PROC_SWAPS:
+               return proc_swaps_read(buf, size, offset, fi);
        default:
                return -EINVAL;
        }
@@ -3699,6 +3984,14 @@ static void __attribute__((constructor)) collect_subsystems(void)
                        goto out;
                *p2 = '\0';
 
+               /* With cgroupv2 /proc/self/cgroup can contain entries of the
+                * form: 0::/ This will cause lxcfs to fail the cgroup mounts
+                * because it parses out the empty string "" and later on passes
+                * it to mount(). Let's skip such entries.
+                */
+               if (!strcmp(p, ""))
+                       continue;
+
                if (!store_hierarchy(line, p))
                        goto out;
        }