/* SPDX-License-Identifier: LGPL-2.1+ */
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-
#include "config.h"
-#ifdef HAVE_FUSE3
-#ifndef FUSE_USE_VERSION
-#define FUSE_USE_VERSION 30
-#endif
-#else
-#ifndef FUSE_USE_VERSION
-#define FUSE_USE_VERSION 26
-#endif
-#endif
-
-#define _FILE_OFFSET_BITS 64
-
-#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 "cgroup_fuse.h"
#include "cgroups/cgroup.h"
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;
strcmp(path, "/proc/stat") == 0 ||
strcmp(path, "/proc/diskstats") == 0 ||
strcmp(path, "/proc/swaps") == 0 ||
- strcmp(path, "/proc/loadavg") == 0) {
- sb->st_size = 4096;
+ 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;
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, "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;
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;
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;
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);
+ }
+
+ 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;
+ }
- 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);
+ /* When swappiness is 0, pretend we can't swap. */
+ if (memswpriority == 0) {
+ swtotal = swusage;
}
}
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);
int ret;
if (offset) {
- int left;
+ size_t left;
if (offset > d->size)
return -EINVAL;
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;
return total_len;
}
-#if RELOADTEST
+#ifdef RELOADTEST
static inline void iwashere(void)
{
mknod("/tmp/lxcfs-iwashere", S_IFREG, 0644);
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;
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);
int cg_cpu_usage_size = 0;
if (offset) {
- int left;
+ size_t left;
if (offset > d->size)
return -EINVAL;
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;
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;
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;
}
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;
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;
int ret;
if (offset) {
- int left;
+ size_t left;
if (offset > d->size)
return -EINVAL;
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);
sscanf(line + STRLITERALLEN("SwapTotal:"), "%" PRIu64, &hostswtotal);
- /*
- * If swtotal is 0 it should mean that
- * memory.memsw.limit_in_bytes and
- * memory.limit_in_bytes are both unlimited or
- * both set to the same value. In both cases we
- * have no idea what the technical swap limit
- * is supposed to be (It's a shared limit
- * anyway.) so fallback to the host's values in
- * that case too.
- */
- if ((hostswtotal < swtotal) || swtotal == 0) {
+ /* 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;
}
}
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);
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;
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;