]> git.proxmox.com Git - mirror_lxcfs.git/blobdiff - src/proc_fuse.c
tree-wide: set _GNU_SOURCE in meson.build
[mirror_lxcfs.git] / src / proc_fuse.c
index c3870fb27282119e11faaaa7d0af084f70c8ced0..4ac5d44b0a10c32033044f89627e3b303a8d51b4 100644 (file)
@@ -1,20 +1,10 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-
-#ifndef FUSE_USE_VERSION
-#define FUSE_USE_VERSION 26
-#endif
-
-#define _FILE_OFFSET_BITS 64
+#include "config.h"
 
-#define __STDC_FORMAT_MACROS
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <fuse.h>
 #include <inttypes.h>
 #include <libgen.h>
 #include <pthread.h>
 #include <sys/sysinfo.h>
 #include <sys/vfs.h>
 
+#include "proc_fuse.h"
+
 #include "bindings.h"
-#include "config.h"
 #include "cgroup_fuse.h"
 #include "cgroups/cgroup.h"
 #include "cgroups/cgroup_utils.h"
 #include "cpuset_parse.h"
+#include "lxcfs_fuse_compat.h"
 #include "memory_utils.h"
 #include "proc_loadavg.h"
 #include "proc_cpuview.h"
@@ -72,6 +64,23 @@ struct memory_stat {
        uint64_t total_unevictable;
 };
 
+static off_t get_procfile_size(const char *path)
+{
+       __do_fclose FILE *f = NULL;
+       __do_free char *line = NULL;
+       size_t len = 0;
+       ssize_t sz, answer = 0;
+
+       f = fopen(path, "re");
+       if (!f)
+               return 0;
+
+       while ((sz = getline(&line, &len, f)) != -1)
+               answer += sz;
+
+       return answer;
+}
+
 __lxcfs_fuse_ops int proc_getattr(const char *path, struct stat *sb)
 {
        struct timespec now;
@@ -94,8 +103,9 @@ __lxcfs_fuse_ops int proc_getattr(const char *path, struct stat *sb)
            strcmp(path, "/proc/stat")          == 0 ||
            strcmp(path, "/proc/diskstats")     == 0 ||
            strcmp(path, "/proc/swaps")         == 0 ||
-           strcmp(path, "/proc/loadavg")       == 0) {
-               sb->st_size = 0;
+           strcmp(path, "/proc/loadavg")       == 0 ||
+           strcmp(path, "/proc/slabinfo")      == 0) {
+               sb->st_size = get_procfile_size(path);
                sb->st_mode = S_IFREG | 00444;
                sb->st_nlink = 1;
                return 0;
@@ -108,37 +118,21 @@ __lxcfs_fuse_ops int proc_readdir(const char *path, void *buf,
                                  fuse_fill_dir_t filler, off_t offset,
                                  struct fuse_file_info *fi)
 {
-       if (filler(buf, ".",            NULL, 0) != 0 ||
-           filler(buf, "..",           NULL, 0) != 0 ||
-           filler(buf, "cpuinfo",      NULL, 0) != 0 ||
-           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, "swaps",        NULL, 0) != 0 ||
-           filler(buf, "loadavg",      NULL, 0) != 0)
+       if (DIR_FILLER(filler, buf, ".",                NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "..",               NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "cpuinfo",  NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "meminfo",  NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "stat",             NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "uptime",   NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "diskstats",        NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "swaps",    NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "loadavg",  NULL, 0) != 0 ||
+           DIR_FILLER(filler, buf, "slabinfo", NULL, 0) != 0)
                return -EINVAL;
 
        return 0;
 }
 
-static off_t get_procfile_size(const char *path)
-{
-       __do_fclose FILE *f = NULL;
-       __do_free char *line = NULL;
-       size_t len = 0;
-       ssize_t sz, answer = 0;
-
-       f = fopen(path, "re");
-       if (!f)
-               return 0;
-
-       while ((sz = getline(&line, &len, f)) != -1)
-               answer += sz;
-
-       return answer;
-}
-
 __lxcfs_fuse_ops int proc_open(const char *path, struct fuse_file_info *fi)
 {
        __do_free struct file_info *info = NULL;
@@ -158,23 +152,22 @@ __lxcfs_fuse_ops int proc_open(const char *path, struct fuse_file_info *fi)
                type = LXC_TYPE_PROC_SWAPS;
        else if (strcmp(path, "/proc/loadavg") == 0)
                type = LXC_TYPE_PROC_LOADAVG;
+       else if (strcmp(path, "/proc/slabinfo") == 0)
+               type = LXC_TYPE_PROC_SLABINFO;
        if (type == -1)
                return -ENOENT;
 
-       info = malloc(sizeof(*info));
+       info = zalloc(sizeof(*info));
        if (!info)
                return -ENOMEM;
 
-       memset(info, 0, sizeof(*info));
        info->type = type;
 
        info->buflen = get_procfile_size(path) + BUF_RESERVE_SIZE;
 
-       info->buf = malloc(info->buflen);
+       info->buf = zalloc(info->buflen);
        if (!info->buf)
                return -ENOMEM;
-
-       memset(info->buf, 0, info->buflen);
        /* set actual size to buffer size */
        info->size = info->buflen;
 
@@ -203,43 +196,102 @@ __lxcfs_fuse_ops int proc_release(const char *path, struct fuse_file_info *fi)
 static uint64_t get_memlimit(const char *cgroup, bool swap)
 {
        __do_free char *memlimit_str = NULL;
-       uint64_t memlimit = -1;
-       char *ptr;
+       uint64_t memlimit = 0;
        int ret;
 
        if (swap)
                ret = cgroup_ops->get_memory_swap_max(cgroup_ops, cgroup, &memlimit_str);
        else
                ret = cgroup_ops->get_memory_max(cgroup_ops, cgroup, &memlimit_str);
-       if (ret > 0) {
-               memlimit = strtoull(memlimit_str, &ptr, 10);
-               if (ptr == memlimit_str)
-                       memlimit = -1;
-       }
+       if (ret > 0 && memlimit_str[0] && safe_uint64(memlimit_str, &memlimit, 10) < 0)
+               lxcfs_error("Failed to convert memlimit %s", memlimit_str);
 
        return memlimit;
 }
 
+/*
+ * This function taken from glibc-2.32, as POSIX dirname("/some-dir") will
+ * return "/some-dir" as opposed to "/", which breaks `get_min_memlimit()`
+ */
+static char *gnu_dirname(char *path)
+{
+       static const char dot[] = ".";
+       char *last_slash;
+
+       /* Find last '/'.  */
+       last_slash = path != NULL ? strrchr(path, '/') : NULL;
+
+       if (last_slash != NULL && last_slash != path && last_slash[1] == '\0') {
+               /* Determine whether all remaining characters are slashes.  */
+               char *runp;
+
+               for (runp = last_slash; runp != path; --runp)
+                       if (runp[-1] != '/')
+                               break;
+
+               /* The '/' is the last character, we have to look further.  */
+               if (runp != path)
+                       last_slash = memrchr(path, '/', runp - path);
+       }
+
+       if (last_slash != NULL) {
+               /* Determine whether all remaining characters are slashes.  */
+               char *runp;
+
+               for (runp = last_slash; runp != path; --runp)
+                       if (runp[-1] != '/')
+                               break;
+
+               /* Terminate the path.  */
+               if (runp == path) {
+                       /*
+                        * The last slash is the first character in the string.
+                        * We have to return "/".  As a special case we have to
+                        * return "//" if there are exactly two slashes at the
+                        * beginning of the string.  See XBD 4.10 Path Name
+                        * Resolution for more information
+                        */
+                       if (last_slash == path + 1)
+                               ++last_slash;
+                       else
+                               last_slash = path + 1;
+               } else
+                       last_slash = runp;
+
+               last_slash[0] = '\0';
+       } else {
+               /*
+                * This assignment is ill-designed but the XPG specs require to
+                * return a string containing "." in any case no directory part
+                * is found and so a static and constant string is required.
+                */
+               path = (char *)dot;
+       }
+
+       return path;
+}
+
 static uint64_t get_min_memlimit(const char *cgroup, bool swap)
 {
        __do_free char *copy = NULL;
-       uint64_t memlimit = 0;
-       uint64_t retlimit;
+       uint64_t memlimit = 0, retlimit = 0;
 
        copy = strdup(cgroup);
        if (!copy)
                return log_error_errno(0, ENOMEM, "Failed to allocate memory");
 
        retlimit = get_memlimit(copy, swap);
-       if (retlimit == -1)
-               retlimit = 0;
 
-       while (strcmp(copy, "/") != 0) {
+       /*
+        * If the cgroup doesn't start with / (probably won't happen), dirname()
+        * will terminate with "" instead of "/"
+        */
+       while (*copy && strcmp(copy, "/") != 0) {
                char *it = copy;
 
-               it = dirname(it);
+               it = gnu_dirname(it);
                memlimit = get_memlimit(it, swap);
-               if (memlimit != -1 && memlimit < retlimit)
+               if (memlimit > 0 && memlimit < retlimit)
                        retlimit = memlimit;
        };
 
@@ -254,19 +306,25 @@ static inline bool startswith(const char *line, const char *pref)
 static int proc_swaps_read(char *buf, size_t size, off_t offset,
                           struct fuse_file_info *fi)
 {
-       __do_free char *cg = NULL, *memswlimit_str = NULL, *memusage_str = NULL,
-                      *memswusage_str = NULL;
+       __do_free char *cgroup = NULL, *memusage_str = NULL,
+                *memswusage_str = NULL, *memswpriority_str = NULL;
        struct fuse_context *fc = fuse_get_context();
+       bool wants_swap = lxcfs_has_opt(fuse_get_context()->private_data, LXCFS_SWAP_ON);
        struct file_info *d = INTTYPE_TO_PTR(fi->fh);
        uint64_t memswlimit = 0, memlimit = 0, memusage = 0, memswusage = 0,
-                swap_total = 0, swap_free = 0;
+                swtotal = 0, swusage = 0, memswpriority = 1,
+                hostswtotal = 0, hostswfree = 0;
        ssize_t total_len = 0;
        ssize_t l = 0;
        char *cache = d->buf;
        int ret;
+       __do_free char *line = NULL;
+       __do_free void *fopen_cache = NULL;
+       __do_fclose FILE *f = NULL;
+       size_t linelen = 0;
 
        if (offset) {
-               int left;
+               size_t left;
 
                if (offset > d->size)
                        return -EINVAL;
@@ -285,54 +343,75 @@ static int proc_swaps_read(char *buf, size_t size, off_t offset,
        if (initpid <= 1 || is_shared_pidns(initpid))
                initpid = fc->pid;
 
-       cg = get_pid_cgroup(initpid, "memory");
-       if (!cg)
+       cgroup = get_pid_cgroup(initpid, "memory");
+       if (!cgroup)
                return read_file_fuse("/proc/swaps", buf, size, d);
-       prune_init_slice(cg);
+       prune_init_slice(cgroup);
 
-       memlimit = get_min_memlimit(cg, false);
+       memlimit = get_min_memlimit(cgroup, false);
 
-       ret = cgroup_ops->get_memory_current(cgroup_ops, cg, &memusage_str);
+       ret = cgroup_ops->get_memory_current(cgroup_ops, cgroup, &memusage_str);
        if (ret < 0)
                return 0;
 
-       memusage = strtoull(memusage_str, NULL, 10);
+       if (safe_uint64(memusage_str, &memusage, 10) < 0)
+               lxcfs_error("Failed to convert memusage %s", memusage_str);
+
+       if (wants_swap) {
+               memswlimit = get_min_memlimit(cgroup, true);
+               if (memswlimit > 0) {
+                       ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cgroup, &memswusage_str);
+                       if (ret >= 0 && safe_uint64(memswusage_str, &memswusage, 10) == 0) {
+                               if (memlimit > memswlimit)
+                                       swtotal = 0;
+                               else
+                                       swtotal = (memswlimit - memlimit) / 1024;
+                               if (memusage > memswusage || swtotal == 0)
+                                       swusage = 0;
+                               else
+                                       swusage = (memswusage - memusage) / 1024;
+                       }
 
-       ret = cgroup_ops->get_memory_swap_max(cgroup_ops, cg, &memswlimit_str);
-       if (ret >= 0)
-               ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cg, &memswusage_str);
-       if (ret >= 0) {
-               memswlimit = get_min_memlimit(cg, true);
-               memswusage = strtoull(memswusage_str, NULL, 10);
-               swap_total = (memswlimit - memlimit) / 1024;
-               swap_free = (memswusage - memusage) / 1024;
+                       ret = cgroup_ops->get_memory_swappiness(cgroup_ops, cgroup, &memswpriority_str);
+                       if (ret >= 0)
+                               safe_uint64(memswpriority_str, &memswpriority, 10);
+               }
        }
 
        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) {
-               __do_free char *line = NULL;
-               __do_free void *fopen_cache = NULL;
-               __do_fclose FILE *f = NULL;
-               size_t linelen = 0;
+       /* Read host total and free values */
+       f = fopen_cached("/proc/meminfo", "re", &fopen_cache);
+       if (!f)
+               return 0;
 
-               f = fopen_cached("/proc/meminfo", "re", &fopen_cache);
-               if (!f)
-                       return 0;
+       while (getline(&line, &linelen, f) != -1) {
+               if (startswith(line, "SwapTotal:"))
+                       sscanf(line, "SwapTotal:      %8" PRIu64 " kB", &hostswtotal);
+               else if (startswith(line, "SwapFree:"))
+                       sscanf(line, "SwapFree:      %8" PRIu64 " kB", &hostswfree);
+       }
 
-               while (getline(&line, &linelen, f) != -1) {
-                       if (startswith(line, "SwapTotal:"))
-                               sscanf(line, "SwapTotal:      %8" PRIu64 " kB", &swap_total);
-                       else if (startswith(line, "SwapFree:"))
-                               sscanf(line, "SwapFree:      %8" PRIu64 " kB", &swap_free);
+       if (wants_swap) {
+               /* The total amount of swap is always reported to be the
+                  lesser of the RAM+SWAP limit or the SWAP device size.
+                  This is because the kernel can swap as much as it
+                  wants and not only up to swtotal. */
+               swtotal = memlimit / 1024 + swtotal;
+               if (hostswtotal < swtotal) {
+                       swtotal = hostswtotal;
+               }
+
+               /* When swappiness is 0, pretend we can't swap. */
+               if (memswpriority == 0) {
+                       swtotal = swusage;
                }
        }
 
-       if (swap_total > 0) {
+       if (swtotal > 0) {
                l = snprintf(d->buf + total_len, d->size - total_len,
                             "none%*svirtual\t\t%" PRIu64 "\t%" PRIu64 "\t0\n",
-                            36, " ", swap_total, swap_free);
+                            36, " ", swtotal, swusage);
                total_len += l;
        }
 
@@ -342,7 +421,7 @@ static int proc_swaps_read(char *buf, size_t size, off_t offset,
        d->cached = 1;
        d->size = (int)total_len;
 
-       if (total_len > size)
+       if ((size_t)total_len > size)
                total_len = size;
        memcpy(buf, d->buf, total_len);
 
@@ -373,6 +452,27 @@ static void get_blkio_io_value(char *str, unsigned major, unsigned minor,
        }
 }
 
+struct lxcfs_diskstats {
+       unsigned int major;             /*  1 - major number */
+       unsigned int minor;             /*  2 - minor mumber */
+       char dev_name[72];              /*  3 - device name */
+       uint64_t read;                  /*  4 - reads completed successfully */
+       uint64_t read_merged;           /*  5 - reads merged */
+       uint64_t read_sectors;          /*  6 - sectors read */
+       uint64_t read_ticks;            /*  7 - time spent reading (ms) */
+       uint64_t write;                 /*  8 - writes completed */
+       uint64_t write_merged;          /*  9 - writes merged */
+       uint64_t write_sectors;         /* 10 - sectors written */
+       uint64_t write_ticks;           /* 11 - time spent writing (ms) */
+       uint64_t ios_pgr;               /* 12 - I/Os currently in progress */
+       uint64_t total_ticks;           /* 13 - time spent doing I/Os (ms) */
+       uint64_t rq_ticks;              /* 14 - weighted time spent doing I/Os (ms) */
+       uint64_t discard;               /* 15 - discards completed successfully (4.18+) */
+       uint64_t discard_merged;        /* 16 - discards merged                 (4.18+) */
+       uint64_t discard_sectors;       /* 17 - sectors discarded               (4.18+) */
+       uint64_t discard_ticks;         /* 18 - time spent discarding           (4.18+) */
+};
+
 static int proc_diskstats_read(char *buf, size_t size, off_t offset,
                               struct fuse_file_info *fi)
 {
@@ -384,22 +484,18 @@ static int proc_diskstats_read(char *buf, size_t size, off_t offset,
        __do_fclose FILE *f = NULL;
        struct fuse_context *fc = fuse_get_context();
        struct file_info *d = INTTYPE_TO_PTR(fi->fh);
-       uint64_t read = 0, write = 0;
-       uint64_t read_merged = 0, write_merged = 0;
-       uint64_t read_sectors = 0, write_sectors = 0;
-       uint64_t read_ticks = 0, write_ticks = 0;
-       uint64_t ios_pgr = 0, tot_ticks = 0, rq_ticks = 0;
-       uint64_t rd_svctm = 0, wr_svctm = 0, rd_wait = 0, wr_wait = 0;
+       struct lxcfs_diskstats stats = {};
+       /* helper fields */
+       uint64_t read_service_time, write_service_time, discard_service_time, read_wait_time,
+           write_wait_time, discard_wait_time;
        char *cache = d->buf;
        size_t cache_size = d->buflen;
        size_t linelen = 0, total_len = 0;
-       unsigned int major = 0, minor = 0;
        int i = 0;
        int ret;
-       char dev_name[72];
 
        if (offset) {
-               int left;
+               size_t left;
 
                if (offset > d->size)
                        return -EINVAL;
@@ -461,46 +557,76 @@ static int proc_diskstats_read(char *buf, size_t size, off_t offset,
                ssize_t l;
                char lbuf[256];
 
-               i = sscanf(line, "%u %u %71s", &major, &minor, dev_name);
+               i = sscanf(line, "%u %u %71s", &stats.major, &stats.minor, stats.dev_name);
                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;
+               get_blkio_io_value(io_serviced_str, stats.major, stats.minor, "Read", &stats.read);
+               get_blkio_io_value(io_serviced_str, stats.major, stats.minor, "Write", &stats.write);
+               get_blkio_io_value(io_serviced_str, stats.major, stats.minor, "Discard", &stats.discard);
+
+               get_blkio_io_value(io_merged_str, stats.major, stats.minor, "Read", &stats.read_merged);
+               get_blkio_io_value(io_merged_str, stats.major, stats.minor, "Write", &stats.write_merged);
+               get_blkio_io_value(io_merged_str, stats.major, stats.minor, "Discard", &stats.discard_merged);
+
+               get_blkio_io_value(io_service_bytes_str, stats.major, stats.minor, "Read", &stats.read_sectors);
+               stats.read_sectors = stats.read_sectors / 512;
+               get_blkio_io_value(io_service_bytes_str, stats.major, stats.minor, "Write", &stats.write_sectors);
+               stats.write_sectors = stats.write_sectors / 512;
+               get_blkio_io_value(io_service_bytes_str, stats.major, stats.minor, "Discard", &stats.discard_sectors);
+               stats.discard_sectors = stats.discard_sectors / 512;
+
+               get_blkio_io_value(io_service_time_str, stats.major, stats.minor, "Read", &read_service_time);
+               read_service_time = read_service_time / 1000000;
+               get_blkio_io_value(io_wait_time_str, stats.major, stats.minor, "Read", &read_wait_time);
+               read_wait_time = read_wait_time / 1000000;
+               stats.read_ticks = read_service_time + read_wait_time;
+
+               get_blkio_io_value(io_service_time_str, stats.major, stats.minor, "Write", &write_service_time);
+               write_service_time = write_service_time / 1000000;
+               get_blkio_io_value(io_wait_time_str, stats.major, stats.minor, "Write", &write_wait_time);
+               write_wait_time = write_wait_time / 1000000;
+               stats.write_ticks = write_service_time + write_wait_time;
+
+               get_blkio_io_value(io_service_time_str, stats.major, stats.minor, "Discard", &discard_service_time);
+               discard_service_time = discard_service_time / 1000000;
+               get_blkio_io_value(io_wait_time_str, stats.major, stats.minor, "Discard", &discard_wait_time);
+               discard_wait_time = discard_wait_time / 1000000;
+               stats.discard_ticks = discard_service_time + discard_wait_time;
+
+               get_blkio_io_value(io_service_time_str, stats.major, stats.minor, "Total", &stats.total_ticks);
+               stats.total_ticks = stats.total_ticks / 1000000;
 
                memset(lbuf, 0, 256);
-               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);
+               if (stats.read || stats.write || stats.read_merged || stats.write_merged ||
+                   stats.read_sectors || stats.write_sectors || stats.read_ticks ||
+                   stats.write_ticks || stats.ios_pgr || stats.total_ticks || stats.rq_ticks ||
+                   stats.discard_merged || stats.discard_sectors || stats.discard_ticks)
+                       snprintf(lbuf, 256, "%u       %u %s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n",
+                               stats.major,
+                               stats.minor,
+                               stats.dev_name,
+                               stats.read,
+                               stats.read_merged,
+                               stats.read_sectors,
+                               stats.read_ticks,
+                               stats.write,
+                               stats.write_merged,
+                               stats.write_sectors,
+                               stats.write_ticks,
+                               stats.ios_pgr,
+                               stats.total_ticks,
+                               stats.rq_ticks,
+                               stats.discard_merged,
+                               stats.discard_sectors,
+                               stats.discard_ticks);
                else
                        continue;
 
                l = snprintf(cache, cache_size, "%s", lbuf);
                if (l < 0)
                        return log_error(0, "Failed to write cache");
-               if (l >= cache_size)
+               if ((size_t)l >= cache_size)
                        return log_error(0, "Write to cache was truncated");
 
                cache += l;
@@ -517,14 +643,15 @@ static int proc_diskstats_read(char *buf, size_t size, off_t offset,
        return total_len;
 }
 
-#if RELOADTEST
+#ifdef RELOADTEST
 static inline void iwashere(void)
 {
        mknod("/tmp/lxcfs-iwashere", S_IFREG, 0644);
 }
 #endif
 
-/* This function retrieves the busy time of a group of tasks by looking at
+/*
+ * This function retrieves the busy time of a group of tasks by looking at
  * cpuacct.usage. Unfortunately, this only makes sense when the container has
  * been given it's own cpuacct cgroup. If not, this function will take the busy
  * time of all other taks that do not actually belong to the container into
@@ -545,10 +672,13 @@ static double get_reaper_busy(pid_t task)
        if (!cgroup)
                return 0;
        prune_init_slice(cgroup);
+
        if (!cgroup_ops->get(cgroup_ops, "cpuacct", cgroup, "cpuacct.usage", &usage_str))
                return 0;
 
-       usage = strtoull(usage_str, NULL, 10);
+       if (safe_uint64(usage_str, &usage, 10) < 0)
+               lxcfs_error("Failed to convert usage %s", usage_str);
+
        return ((double)usage / 1000000000);
 }
 
@@ -558,38 +688,21 @@ static uint64_t get_reaper_start_time(pid_t pid)
        __do_fclose FILE *f = NULL;
        int ret;
        uint64_t starttime;
-       /* strlen("/proc/") = 6
-        * +
-        * LXCFS_NUMSTRLEN64
-        * +
-        * strlen("/stat") = 5
-        * +
-        * \0 = 1
-        * */
-#define __PROC_PID_STAT_LEN (6 + LXCFS_NUMSTRLEN64 + 5 + 1)
-       char path[__PROC_PID_STAT_LEN];
+       char path[STRLITERALLEN("/proc/") + LXCFS_NUMSTRLEN64 +
+                 STRLITERALLEN("/stat") + 1];
        pid_t qpid;
 
        qpid = lookup_initpid_in_store(pid);
-       if (qpid <= 0) {
-               /* Caller can check for EINVAL on 0. */
-               errno = EINVAL;
-               return 0;
-       }
+       if (qpid <= 0)
+               return ret_errno(EINVAL);
 
-       ret = snprintf(path, __PROC_PID_STAT_LEN, "/proc/%d/stat", qpid);
-       if (ret < 0 || ret >= __PROC_PID_STAT_LEN) {
-               /* Caller can check for EINVAL on 0. */
-               errno = EINVAL;
-               return 0;
-       }
+       ret = snprintf(path, sizeof(path), "/proc/%d/stat", qpid);
+       if (ret < 0 || (size_t)ret >= sizeof(path))
+               return ret_errno(EINVAL);
 
        f = fopen_cached(path, "re", &fopen_cache);
-       if (!f) {
-               /* Caller can check for EINVAL on 0. */
-               errno = EINVAL;
-               return 0;
-       }
+       if (!f)
+               return ret_errno(EINVAL);
 
        /* Note that the *scanf() argument supression requires that length
         * modifiers such as "l" are omitted. Otherwise some compilers will yell
@@ -620,7 +733,7 @@ static uint64_t get_reaper_start_time(pid_t pid)
                        "%" PRIu64, /* (22) starttime   %llu */
                     &starttime);
        if (ret != 1)
-               return ret_set_errno(0, EINVAL);
+               return ret_errno(EINVAL);
 
        return ret_set_errno(starttime, 0);
 }
@@ -632,11 +745,11 @@ static double get_reaper_start_time_in_sec(pid_t pid)
        double res = 0;
 
        clockticks = get_reaper_start_time(pid);
-       if (clockticks == 0 && errno == EINVAL)
+       if (clockticks <= 0)
                return log_debug(0, "Failed to retrieve start time of pid %d", pid);
 
        ret = sysconf(_SC_CLK_TCK);
-       if (ret < 0 && errno == EINVAL)
+       if (ret < 0)
                return log_debug(0, "Failed to determine number of clock ticks in a second");
 
        ticks_per_sec = (uint64_t)ret;
@@ -649,7 +762,8 @@ static double get_reaper_age(pid_t pid)
        uint64_t uptime_ms;
        double procstart, procage;
 
-       /* We need to substract the time the process has started since system
+       /*
+        * We need to substract the time the process has started since system
         * boot minus the time when the system has started to get the actual
         * reaper age.
         */
@@ -663,11 +777,6 @@ static double get_reaper_age(pid_t pid)
                if (ret < 0)
                        return 0;
 
-               /* We could make this more precise here by using the tv_nsec
-                * field in the timespec struct and convert it to milliseconds
-                * and then create a double for the seconds and milliseconds but
-                * that seems more work than it is worth.
-                */
                uptime_ms = (spec.tv_sec * 1000) + (spec.tv_nsec * 1e-6);
                procage = (uptime_ms - (procstart * 1000)) / 1000;
        }
@@ -685,24 +794,23 @@ static int proc_uptime_read(char *buf, size_t size, off_t offset,
 {
        struct fuse_context *fc = fuse_get_context();
        struct file_info *d = INTTYPE_TO_PTR(fi->fh);
-       double busytime = get_reaper_busy(fc->pid);
        char *cache = d->buf;
-       ssize_t total_len = 0;
-       double idletime, reaperage;
+       ssize_t total_len = 0, ret = 0;
+       double busytime, idletime, reaperage;
 
-#if RELOADTEST
+#ifdef RELOADTEST
        iwashere();
 #endif
 
        if (offset) {
-               int left;
-
-               if (!d->cached)
-                       return 0;
+               size_t left;
 
                if (offset > d->size)
                        return -EINVAL;
 
+               if (!d->cached)
+                       return 0;
+
                left = d->size - offset;
                total_len = left > size ? size : left;
                memcpy(buf, cache + offset, total_len);
@@ -716,20 +824,21 @@ static int proc_uptime_read(char *buf, size_t size, off_t offset,
         * get_reaper_busy() function.
         */
        idletime = reaperage;
+       busytime = get_reaper_busy(fc->pid);
        if (reaperage >= busytime)
                idletime = reaperage - busytime;
 
-       total_len = snprintf(d->buf, d->buflen, "%.2lf %.2lf\n", reaperage, idletime);
-       if (total_len < 0 || total_len >= d->buflen)
-               return log_error(0, "Failed to write to cache");
+       ret = snprintf(d->buf, d->buflen, "%.2lf %.2lf\n", reaperage, idletime);
+       if (ret < 0 || ret >= d->buflen)
+               return read_file_fuse("/proc/uptime", buf, size, d);
+       total_len = ret;
 
-       d->size = (int)total_len;
        d->cached = 1;
-
-       if (total_len > size)
+       d->size = total_len;
+       if ((size_t)total_len > size)
                total_len = size;
-
        memcpy(buf, d->buf, total_len);
+
        return total_len;
 }
 
@@ -759,7 +868,7 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
        int cg_cpu_usage_size = 0;
 
        if (offset) {
-               int left;
+               size_t left;
 
                if (offset > d->size)
                        return -EINVAL;
@@ -799,6 +908,10 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
        if (!f)
                return 0;
 
+       /* Skip first system cpu line. */
+       if (getline(&line, &linelen, f) < 0)
+               return log_error(0, "proc_stat_read read first line failed");
+
        /*
         * Read cpuacct.usage_all for all CPUs.
         * If the cpuacct cgroup is present, it is used to calculate the container's
@@ -815,10 +928,6 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
                lxcfs_v("proc_stat_read failed to read from cpuacct, falling back to the host's /proc/stat");
        }
 
-       //skip first line
-       if (getline(&line, &linelen, f) < 0)
-               return log_error(0, "proc_stat_read read first line failed");
-
        while (getline(&line, &linelen, f) != -1) {
                ssize_t l;
                char cpu_char[10]; /* That's a lot of cores */
@@ -833,7 +942,7 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
                        l = snprintf(cache, cache_size, "%s", line);
                        if (l < 0)
                                return log_error(0, "Failed to write cache");
-                       if (l >= cache_size)
+                       if ((size_t)l >= cache_size)
                                return log_error(0, "Write to cache was truncated");
 
                        cache += l;
@@ -870,7 +979,7 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
                        l = snprintf(cache, cache_size, "cpu%d%s", curcpu, c);
                        if (l < 0)
                                return log_error(0, "Failed to write cache");
-                       if (l >= cache_size)
+                       if ((size_t)l >= cache_size)
                                return log_error(0, "Write to cache was truncated");
 
                        cache += l;
@@ -890,9 +999,8 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
 
                        if (all_used >= cg_used) {
                                new_idle = idle + (all_used - cg_used);
-
                        } else {
-                               lxcfs_error("cpu%d from %s has unexpected cpu time: %" PRIu64 " in /proc/stat, %" PRIu64 " in cpuacct.usage_all; unable to determine idle time",
+                               lxcfs_debug("cpu%d from %s has unexpected cpu time: %" PRIu64 " in /proc/stat, %" PRIu64 " in cpuacct.usage_all; unable to determine idle time",
                                            curcpu, cg, all_used, cg_used);
                                new_idle = idle;
                        }
@@ -903,7 +1011,7 @@ static int proc_stat_read(char *buf, size_t size, off_t offset,
                                     cg_cpu_usage[physcpu].system, new_idle);
                        if (l < 0)
                                return log_error(0, "Failed to write cache");
-                       if (l >= cache_size)
+                       if ((size_t)l >= cache_size)
                                return log_error(0, "Write to cache was truncated");
 
                        cache += l;
@@ -1031,14 +1139,15 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                             struct fuse_file_info *fi)
 {
        __do_free char *cgroup = NULL, *line = NULL, *memusage_str = NULL,
-                      *memswlimit_str = NULL, *memswusage_str = NULL;
+                      *memswusage_str = NULL, *memswpriority_str = NULL;
        __do_free void *fopen_cache = NULL;
        __do_fclose FILE *f = NULL;
        struct fuse_context *fc = fuse_get_context();
-       struct lxcfs_opts *opts = (struct lxcfs_opts *)fuse_get_context()->private_data;
+       bool wants_swap = lxcfs_has_opt(fuse_get_context()->private_data, LXCFS_SWAP_ON);
        struct file_info *d = INTTYPE_TO_PTR(fi->fh);
        uint64_t memlimit = 0, memusage = 0, memswlimit = 0, memswusage = 0,
-                hosttotal = 0;
+                hosttotal = 0, swfree = 0, swusage = 0, swtotal = 0,
+                memswpriority = 1;
        struct memory_stat mstat = {};
        size_t linelen = 0, total_len = 0;
        char *cache = d->buf;
@@ -1046,7 +1155,7 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
        int ret;
 
        if (offset) {
-               int left;
+               size_t left;
 
                if (offset > d->size)
                        return -EINVAL;
@@ -1071,37 +1180,50 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
 
        prune_init_slice(cgroup);
 
-       memlimit = get_min_memlimit(cgroup, false);
-
+       /* memory limits */
        ret = cgroup_ops->get_memory_current(cgroup_ops, cgroup, &memusage_str);
        if (ret < 0)
-               return 0;
+               return read_file_fuse("/proc/meminfo", buf, size, d);
+
+       if (safe_uint64(memusage_str, &memusage, 10) < 0)
+               lxcfs_error("Failed to convert memusage %s", memusage_str);
 
        if (!cgroup_parse_memory_stat(cgroup, &mstat))
-               return 0;
+               return read_file_fuse("/proc/meminfo", buf, size, d);
+
+       memlimit = get_min_memlimit(cgroup, false);
 
        /*
         * Following values are allowed to fail, because swapaccount might be
         * turned off for current kernel.
         */
-       ret = cgroup_ops->get_memory_swap_max(cgroup_ops, cgroup, &memswlimit_str);
-       if (ret >= 0)
-               ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cgroup, &memswusage_str);
-       if (ret >= 0) {
+       if (wants_swap) {
                memswlimit = get_min_memlimit(cgroup, true);
-               memswusage = strtoull(memswusage_str, NULL, 10);
-               memswlimit = memswlimit / 1024;
-               memswusage = memswusage / 1024;
-       }
+               if (memswlimit > 0) {
+                       ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cgroup, &memswusage_str);
+                       if (ret >= 0 && safe_uint64(memswusage_str, &memswusage, 10) == 0) {
+                               if (memlimit > memswlimit)
+                                       swtotal = 0;
+                               else
+                                       swtotal = (memswlimit - memlimit) / 1024;
+                               if (memusage > memswusage || swtotal == 0)
+                                       swusage = 0;
+                               else
+                                       swusage = (memswusage - memusage) / 1024;
+                       }
+               }
 
-       memusage = strtoull(memusage_str, NULL, 10);
-       memlimit /= 1024;
-       memusage /= 1024;
+               ret = cgroup_ops->get_memory_swappiness(cgroup_ops, cgroup, &memswpriority_str);
+               if (ret >= 0)
+                       safe_uint64(memswpriority_str, &memswpriority, 10);
+       }
 
        f = fopen_cached("/proc/meminfo", "re", &fopen_cache);
        if (!f)
-               return 0;
+               return read_file_fuse("/proc/meminfo", buf, size, d);
 
+       memusage /= 1024;
+       memlimit /= 1024;
        while (getline(&line, &linelen, f) != -1) {
                ssize_t l;
                char *printme, lbuf[100];
@@ -1109,6 +1231,9 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                memset(lbuf, 0, 100);
                if (startswith(line, "MemTotal:")) {
                        sscanf(line+sizeof("MemTotal:")-1, "%" PRIu64, &hosttotal);
+                       if (memlimit == 0)
+                               memlimit = hosttotal;
+
                        if (hosttotal < memlimit)
                                memlimit = hosttotal;
                        snprintf(lbuf, 100, "MemTotal:       %8" PRIu64 " kB\n", memlimit);
@@ -1119,30 +1244,39 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                } else if (startswith(line, "MemAvailable:")) {
                        snprintf(lbuf, 100, "MemAvailable:   %8" PRIu64 " kB\n", memlimit - memusage + mstat.total_cache / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "SwapTotal:") && memswlimit > 0 &&
-                          opts && opts->swap_off == false) {
-                       memswlimit -= memlimit;
-                       snprintf(lbuf, 100, "SwapTotal:      %8" PRIu64 " kB\n", memswlimit);
-                       printme = lbuf;
-               } else if (startswith(line, "SwapTotal:") && opts && opts->swap_off == true) {
-                       snprintf(lbuf, 100, "SwapTotal:      %8" PRIu64 " kB\n", (uint64_t)0);
-                       printme = lbuf;
-               } else if (startswith(line, "SwapFree:") && memswlimit > 0 &&
-                          memswusage > 0 && opts && opts->swap_off == false) {
-                       uint64_t swaptotal = memswlimit,
-                                swapusage = memusage > memswusage
-                                                ? 0
-                                                : memswusage - memusage,
-                                swapfree = swapusage < swaptotal
-                                               ? swaptotal - swapusage
-                                               : 0;
-                       snprintf(lbuf, 100, "SwapFree:       %8" PRIu64 " kB\n", swapfree);
+               } else if (startswith(line, "SwapTotal:")) {
+                       if (wants_swap) {
+                               uint64_t hostswtotal = 0;
+
+                               sscanf(line + STRLITERALLEN("SwapTotal:"), "%" PRIu64, &hostswtotal);
+
+                               /* The total amount of swap is always reported to be the
+                                  lesser of the RAM+SWAP limit or the SWAP device size.
+                                  This is because the kernel can swap as much as it
+                                  wants and not only up to swtotal. */
+
+                               swtotal = memlimit + swtotal;
+                               if (hostswtotal < swtotal) {
+                                       swtotal = hostswtotal;
+                               }
+
+                               /* When swappiness is 0, pretend we can't swap. */
+                               if (memswpriority == 0) {
+                                       swtotal = swusage;
+                               }
+                       }
+
+                       snprintf(lbuf, 100, "SwapTotal:      %8" PRIu64 " kB\n", swtotal);
                        printme = lbuf;
-               } else if (startswith(line, "SwapFree:") && opts && opts->swap_off == true) {
-                       snprintf(lbuf, 100, "SwapFree:       %8" PRIu64 " kB\n", (uint64_t)0);
+               } else if (startswith(line, "SwapFree:")) {
+                       if (wants_swap) {
+                               swfree = swtotal - swusage;
+                       }
+
+                       snprintf(lbuf, 100, "SwapFree:       %8" PRIu64 " kB\n", swfree);
                        printme = lbuf;
                } else if (startswith(line, "Slab:")) {
-                       snprintf(lbuf, 100, "Slab:        %8" PRIu64 " kB\n", (uint64_t)0);
+                       snprintf(lbuf, 100, "Slab:           %8" PRIu64 " kB\n", (uint64_t)0);
                        printme = lbuf;
                } else if (startswith(line, "Buffers:")) {
                        snprintf(lbuf, 100, "Buffers:        %8" PRIu64 " kB\n", (uint64_t)0);
@@ -1166,61 +1300,61 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                                  mstat.total_inactive_file) /
                                     1024);
                        printme = lbuf;
-               } else if (startswith(line, "Active(anon)")) {
+               } else if (startswith(line, "Active(anon):")) {
                        snprintf(lbuf, 100, "Active(anon):   %8" PRIu64 " kB\n",
                                 mstat.total_active_anon / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "Inactive(anon)")) {
+               } else if (startswith(line, "Inactive(anon):")) {
                        snprintf(lbuf, 100, "Inactive(anon): %8" PRIu64 " kB\n",
                                 mstat.total_inactive_anon / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "Active(file)")) {
+               } else if (startswith(line, "Active(file):")) {
                        snprintf(lbuf, 100, "Active(file):   %8" PRIu64 " kB\n",
                                 mstat.total_active_file / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "Inactive(file)")) {
+               } else if (startswith(line, "Inactive(file):")) {
                        snprintf(lbuf, 100, "Inactive(file): %8" PRIu64 " kB\n",
                                 mstat.total_inactive_file / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "Unevictable")) {
+               } else if (startswith(line, "Unevictable:")) {
                        snprintf(lbuf, 100, "Unevictable:    %8" PRIu64 " kB\n",
                                 mstat.total_unevictable / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "Dirty")) {
+               } else if (startswith(line, "Dirty:")) {
                        snprintf(lbuf, 100, "Dirty:          %8" PRIu64 " kB\n",
                                 mstat.total_dirty / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "Writeback")) {
+               } else if (startswith(line, "Writeback:")) {
                        snprintf(lbuf, 100, "Writeback:      %8" PRIu64 " kB\n",
                                 mstat.total_writeback / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "AnonPages")) {
+               } else if (startswith(line, "AnonPages:")) {
                        snprintf(lbuf, 100, "AnonPages:      %8" PRIu64 " kB\n",
                                 (mstat.total_active_anon +
                                  mstat.total_inactive_anon - mstat.total_shmem) /
                                     1024);
                        printme = lbuf;
-               } else if (startswith(line, "Mapped")) {
+               } else if (startswith(line, "Mapped:")) {
                        snprintf(lbuf, 100, "Mapped:         %8" PRIu64 " kB\n",
                                 mstat.total_mapped_file / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "SReclaimable")) {
+               } else if (startswith(line, "SReclaimable:")) {
                        snprintf(lbuf, 100, "SReclaimable:   %8" PRIu64 " kB\n", (uint64_t)0);
                        printme = lbuf;
-               } else if (startswith(line, "SUnreclaim")) {
+               } else if (startswith(line, "SUnreclaim:")) {
                        snprintf(lbuf, 100, "SUnreclaim:     %8" PRIu64 " kB\n", (uint64_t)0);
                        printme = lbuf;
                } else if (startswith(line, "Shmem:")) {
                        snprintf(lbuf, 100, "Shmem:          %8" PRIu64 " kB\n",
                                 mstat.total_shmem / 1024);
                        printme = lbuf;
-               } else if (startswith(line, "ShmemHugePages")) {
+               } else if (startswith(line, "ShmemHugePages:")) {
                        snprintf(lbuf, 100, "ShmemHugePages: %8" PRIu64 " kB\n", (uint64_t)0);
                        printme = lbuf;
-               } else if (startswith(line, "ShmemPmdMapped")) {
+               } else if (startswith(line, "ShmemPmdMapped:")) {
                        snprintf(lbuf, 100, "ShmemPmdMapped: %8" PRIu64 " kB\n", (uint64_t)0);
                        printme = lbuf;
-               } else if (startswith(line, "AnonHugePages")) {
+               } else if (startswith(line, "AnonHugePages:")) {
                        snprintf(lbuf, 100, "AnonHugePages:  %8" PRIu64 " kB\n",
                                 mstat.total_rss_huge / 1024);
                        printme = lbuf;
@@ -1231,7 +1365,76 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                l = snprintf(cache, cache_size, "%s", printme);
                if (l < 0)
                        return log_error(0, "Failed to write cache");
-               if (l >= cache_size)
+               if ((size_t)l >= cache_size)
+                       return log_error(0, "Write to cache was truncated");
+
+               cache += l;
+               cache_size -= l;
+               total_len += l;
+       }
+
+       d->cached = 1;
+       d->size = total_len;
+       if (total_len > size)
+               total_len = size;
+       memcpy(buf, d->buf, total_len);
+
+       return total_len;
+}
+
+static int proc_slabinfo_read(char *buf, size_t size, off_t offset,
+                             struct fuse_file_info *fi)
+{
+       __do_free char *cgroup = NULL, *line = NULL;
+       __do_free void *fopen_cache = NULL;
+       __do_fclose FILE *f = NULL;
+       __do_close int fd = -EBADF;
+       struct fuse_context *fc = fuse_get_context();
+       struct file_info *d = INTTYPE_TO_PTR(fi->fh);
+       size_t linelen = 0, total_len = 0;
+       char *cache = d->buf;
+       size_t cache_size = d->buflen;
+       pid_t initpid;
+
+       if (offset) {
+               size_t left;
+
+               if (offset > d->size)
+                       return -EINVAL;
+
+               if (!d->cached)
+                       return 0;
+
+               left = d->size - offset;
+               total_len = left > size ? size : left;
+               memcpy(buf, cache + offset, total_len);
+
+               return total_len;
+       }
+
+       initpid = lookup_initpid_in_store(fc->pid);
+       if (initpid <= 1 || is_shared_pidns(initpid))
+               initpid = fc->pid;
+
+       cgroup = get_pid_cgroup(initpid, "memory");
+       if (!cgroup)
+               return read_file_fuse("/proc/slabinfo", buf, size, d);
+
+       prune_init_slice(cgroup);
+
+       fd = cgroup_ops->get_memory_slabinfo_fd(cgroup_ops, cgroup);
+       if (fd < 0)
+               return read_file_fuse("/proc/slabinfo", buf, size, d);
+
+       f = fdopen_cached(fd, "re", &fopen_cache);
+       if (!f)
+               return read_file_fuse("/proc/slabinfo", buf, size, d);
+
+       while (getline(&line, &linelen, f) != -1) {
+               ssize_t l = snprintf(cache, cache_size, "%s", line);
+               if (l < 0)
+                       return log_error(0, "Failed to write cache");
+               if ((size_t)l >= cache_size)
                        return log_error(0, "Write to cache was truncated");
 
                cache += l;
@@ -1296,6 +1499,12 @@ __lxcfs_fuse_ops int proc_read(const char *path, char *buf, size_t size,
 
                return read_file_fuse_with_offset(LXC_TYPE_PROC_LOADAVG_PATH,
                                                  buf, size, offset, f);
+       case LXC_TYPE_PROC_SLABINFO:
+               if (liblxcfs_functional())
+                       return proc_slabinfo_read(buf, size, offset, fi);
+
+               return read_file_fuse_with_offset(LXC_TYPE_PROC_SLABINFO_PATH,
+                                                 buf, size, offset, f);
        }
 
        return -EINVAL;