]> 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 a99162c31afee5d8bb667575cae295223e517d74..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,6 +152,8 @@ __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;
 
@@ -213,6 +209,68 @@ static uint64_t get_memlimit(const char *cgroup, bool swap)
        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;
@@ -224,10 +282,14 @@ static uint64_t get_min_memlimit(const char *cgroup, bool swap)
 
        retlimit = get_memlimit(copy, swap);
 
-       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 > 0 && memlimit < retlimit)
                        retlimit = memlimit;
@@ -244,20 +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 *cgroup = 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();
-       struct lxcfs_opts *opts = (struct lxcfs_opts *)fuse_get_context()->private_data;
-       bool wants_swap = opts && !opts->swap_off && liblxcfs_can_use_swap();
+       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,
-                swtotal = 0, swfree = 0, swusage = 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;
@@ -303,37 +370,48 @@ static int proc_swaps_read(char *buf, size_t size, off_t offset,
                                        swusage = 0;
                                else
                                        swusage = (memswusage - memusage) / 1024;
-                               if (swtotal >= swusage)
-                                       swfree = swtotal - swusage;
                        }
+
+                       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", &swtotal);
-                       else if (startswith(line, "SwapFree:"))
-                               sscanf(line, "SwapFree:      %8" PRIu64 " kB", &swfree);
+       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 (swtotal > 0) {
                l = snprintf(d->buf + total_len, d->size - total_len,
                             "none%*svirtual\t\t%" PRIu64 "\t%" PRIu64 "\t0\n",
-                            36, " ", swtotal, swfree);
+                            36, " ", swtotal, swusage);
                total_len += l;
        }
 
@@ -343,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);
 
@@ -374,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)
 {
@@ -385,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;
@@ -462,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;
@@ -518,7 +643,7 @@ 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);
@@ -673,12 +798,12 @@ static int proc_uptime_read(char *buf, size_t size, off_t offset,
        ssize_t total_len = 0, ret = 0;
        double busytime, idletime, reaperage;
 
-#if RELOADTEST
+#ifdef RELOADTEST
        iwashere();
 #endif
 
        if (offset) {
-               int left;
+               size_t left;
 
                if (offset > d->size)
                        return -EINVAL;
@@ -710,7 +835,7 @@ static int proc_uptime_read(char *buf, size_t size, off_t offset,
 
        d->cached = 1;
        d->size = total_len;
-       if (total_len > size)
+       if ((size_t)total_len > size)
                total_len = size;
        memcpy(buf, d->buf, total_len);
 
@@ -743,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;
@@ -817,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;
@@ -854,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;
@@ -874,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;
                        }
@@ -887,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;
@@ -1015,15 +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,
-                      *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 = opts && !opts->swap_off && liblxcfs_can_use_swap(), host_swap = false;
+       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, swfree = 0, swusage = 0, swtotal = 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;
@@ -1031,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;
@@ -1088,6 +1212,10 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                                        swusage = (memswusage - 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);
@@ -1122,9 +1250,19 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
 
                                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;
-                                       host_swap = true;
+                               }
+
+                               /* When swappiness is 0, pretend we can't swap. */
+                               if (memswpriority == 0) {
+                                       swtotal = swusage;
                                }
                        }
 
@@ -1132,20 +1270,13 @@ static int proc_meminfo_read(char *buf, size_t size, off_t offset,
                        printme = lbuf;
                } else if (startswith(line, "SwapFree:")) {
                        if (wants_swap) {
-                               uint64_t hostswfree = 0;
-
-                               if (host_swap) {
-                                       sscanf(line + STRLITERALLEN("SwapFree:"), "%" PRIu64, &hostswfree);
-                                       swfree = hostswfree;
-                               } else if (swtotal >= swusage) {
-                                       swfree = swtotal - swusage;
-                               }
+                               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);
@@ -1234,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;
@@ -1299,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;