/* SPDX-License-Identifier: LGPL-2.1+ */

#include "config.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libgen.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wait.h>
#include <linux/magic.h>
#include <linux/sched.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/personality.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/sysinfo.h>
#include <sys/vfs.h>

#include "proc_fuse.h"

#include "bindings.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"
#include "utils.h"

struct memory_stat {
	uint64_t hierarchical_memory_limit;
	uint64_t hierarchical_memsw_limit;
	uint64_t total_cache;
	uint64_t total_rss;
	uint64_t total_rss_huge;
	uint64_t total_shmem;
	uint64_t total_mapped_file;
	uint64_t total_dirty;
	uint64_t total_writeback;
	uint64_t total_swap;
	uint64_t total_pgpgin;
	uint64_t total_pgpgout;
	uint64_t total_pgfault;
	uint64_t total_pgmajfault;
	uint64_t total_inactive_anon;
	uint64_t total_active_anon;
	uint64_t total_inactive_file;
	uint64_t total_active_file;
	uint64_t total_unevictable;
	uint64_t slab;
	uint64_t slab_reclaimable;
	uint64_t slab_unreclaimable;
	uint64_t zswapped;
	uint64_t zswap;
};

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;
}

static off_t get_procfile_size_with_personality(const char *path)
{
	struct fuse_context *fc = fuse_get_context();
	__u32 host_personality = liblxcfs_personality(), caller_personality;
	bool change_personality;
	int ret;
	off_t procfile_size_ret;

	if (get_task_personality(fc->pid, &caller_personality) < 0)
		return log_error(0, "Failed to get caller process (pid: %d) personality", fc->pid);

	/* do we need to change thread personality? */
	change_personality = host_personality != caller_personality;

	if (change_personality) {
		ret = personality(caller_personality);
		if (ret == -1)
			return log_error(0, "Call to personality(%d) failed: %s\n",
					caller_personality, strerror(errno));

		lxcfs_debug("task (tid: %d) personality was changed %d -> %d\n",
				(int)syscall(SYS_gettid), ret, caller_personality);
	}

	procfile_size_ret = get_procfile_size(path);

	if (change_personality) {
		ret = personality(host_personality);
		if (ret == -1)
			return log_error(0, "Call to personality(%d) failed: %s\n",
					host_personality, strerror(errno));

		lxcfs_debug("task (tid: %d) personality was restored %d -> %d\n",
				(int)syscall(SYS_gettid), ret, host_personality);
	}

	return procfile_size_ret;
}

__lxcfs_fuse_ops int proc_getattr(const char *path, struct stat *sb)
{
	struct timespec now;

	memset(sb, 0, sizeof(struct stat));
	if (clock_gettime(CLOCK_REALTIME, &now) < 0)
		return -EINVAL;

	sb->st_uid = sb->st_gid = 0;
	sb->st_atim = sb->st_mtim = sb->st_ctim = now;
	if (strcmp(path, "/proc") == 0) {
		sb->st_mode = S_IFDIR | 00555;
		sb->st_nlink = 2;
		return 0;
	}
	if (strcmp(path, "/proc/pressure") == 0) {
		sb->st_mode = S_IFDIR | 00555;
		sb->st_nlink = 2;
		return 0;
	}

	if (strcmp(path, "/proc/meminfo")	== 0 ||
	    strcmp(path, "/proc/cpuinfo")	== 0 ||
	    strcmp(path, "/proc/uptime")	== 0 ||
	    strcmp(path, "/proc/stat")		== 0 ||
	    strcmp(path, "/proc/diskstats")	== 0 ||
	    strcmp(path, "/proc/swaps")		== 0 ||
	    strcmp(path, "/proc/loadavg")	== 0 ||
	    strcmp(path, "/proc/slabinfo")	== 0) {
		if (liblxcfs_functional()) {
			if (!can_access_personality())
				return log_error(-EACCES, RESTRICTED_PERSONALITY_ACCESS_POLICY);
			sb->st_size = get_procfile_size_with_personality(path);
		}
		else
			sb->st_size = get_procfile_size(path);
		sb->st_mode = S_IFREG | 00444;
		sb->st_nlink = 1;
		return 0;
	}
	if (strcmp(path, "/proc/pressure/io")		== 0 ||
	    strcmp(path, "/proc/pressure/cpu")		== 0 ||
	    strcmp(path, "/proc/pressure/memory")	== 0) {
		if (liblxcfs_functional()) {
			if (!can_access_personality())
				return log_error(-EACCES, RESTRICTED_PERSONALITY_ACCESS_POLICY);
			sb->st_size = get_procfile_size_with_personality(path);
		}
		else
			sb->st_size = get_procfile_size(path);
		/* TODO: read-only now, will be writable after monitoring support */
		sb->st_mode = S_IFREG | 00444;
		sb->st_nlink = 1;
		return 0;
	}

	return -ENOENT;
}

__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 (strcmp(path, "/proc") ==  0) {
		if (dir_filler(filler, buf, ".",		0) != 0 ||
		    dir_filler(filler, buf, "..",		0) != 0 ||
		    dir_filler(filler, buf, "cpuinfo",		0) != 0 ||
		    dir_filler(filler, buf, "meminfo",		0) != 0 ||
		    dir_filler(filler, buf, "stat",		0) != 0 ||
		    dir_filler(filler, buf, "uptime",		0) != 0 ||
		    dir_filler(filler, buf, "diskstats",	0) != 0 ||
		    dir_filler(filler, buf, "swaps",		0) != 0 ||
		    dir_filler(filler, buf, "loadavg",		0) != 0 ||
		    dir_filler(filler, buf, "slabinfo",		0) != 0 ||
		    dirent_filler(filler, path, "pressure", buf, 0) != 0)
			return -EINVAL;
		return 0;
	}
	if (strcmp(path, "/proc/pressure") ==  0) {
		if (dir_filler(filler, buf, ".",	0) != 0 ||
		    dir_filler(filler, buf, "..",	0) != 0 ||
		    dir_filler(filler, buf, "io",	0) != 0 ||
		    dir_filler(filler, buf, "cpu",	0) != 0 ||
		    dir_filler(filler, buf, "memory",	0) != 0)
			return -EINVAL;
		return 0;
	}

	return 0;
}

__lxcfs_fuse_ops int proc_open(const char *path, struct fuse_file_info *fi)
{
	__do_free struct file_info *info = NULL;
	int type = -1;

	if (strcmp(path, "/proc/meminfo") == 0)
		type = LXC_TYPE_PROC_MEMINFO;
	else if (strcmp(path, "/proc/cpuinfo") == 0)
		type = LXC_TYPE_PROC_CPUINFO;
	else if (strcmp(path, "/proc/uptime") == 0)
		type = LXC_TYPE_PROC_UPTIME;
	else if (strcmp(path, "/proc/stat") == 0)
		type = LXC_TYPE_PROC_STAT;
	else if (strcmp(path, "/proc/diskstats") == 0)
		type = LXC_TYPE_PROC_DISKSTATS;
	else if (strcmp(path, "/proc/swaps") == 0)
		type = LXC_TYPE_PROC_SWAPS;
	else if (strcmp(path, "/proc/loadavg") == 0)
		type = LXC_TYPE_PROC_LOADAVG;
	else if (strcmp(path, "/proc/slabinfo") == 0)
		type = LXC_TYPE_PROC_SLABINFO;
	else if (strcmp(path, "/proc/pressure/io") == 0)
		type = LXC_TYPE_PROC_PRESSURE_IO;
	else if (strcmp(path, "/proc/pressure/cpu") == 0)
		type = LXC_TYPE_PROC_PRESSURE_CPU;
	else if (strcmp(path, "/proc/pressure/memory") == 0)
		type = LXC_TYPE_PROC_PRESSURE_MEMORY;
	if (type == -1)
		return -ENOENT;

	info = zalloc(sizeof(*info));
	if (!info)
		return -ENOMEM;

	info->type = type;

	if (liblxcfs_functional()) {
		if (!can_access_personality())
			return log_error(-EACCES, RESTRICTED_PERSONALITY_ACCESS_POLICY);
		info->buflen = get_procfile_size_with_personality(path) + BUF_RESERVE_SIZE;
	}
	else
		info->buflen = get_procfile_size(path) + BUF_RESERVE_SIZE;

	info->buf = zalloc(info->buflen);
	if (!info->buf)
		return -ENOMEM;
	/* set actual size to buffer size */
	info->size = info->buflen;

	fi->fh = PTR_TO_UINT64(move_ptr(info));
	return 0;
}

__lxcfs_fuse_ops int proc_opendir(const char *path, struct fuse_file_info *fi)
{
	__do_free struct file_info *dir_info = NULL;
	int type = -1;

	if (!liblxcfs_functional())
		return -EIO;

	if (strcmp(path, "/proc") == 0)
		type = LXC_TYPE_PROC;
	else if (strcmp(path, "/proc/pressure") == 0)
		type = LXC_TYPE_PROC_PRESSURE;
	if (type == -1)
		return -ENOENT;

	dir_info = zalloc(sizeof(*dir_info));
	if (!dir_info)
		return -ENOMEM;

	dir_info->type = type;
	dir_info->buf = NULL;
	dir_info->file = NULL;
	dir_info->buflen = 0;

	fi->fh = PTR_TO_UINT64(move_ptr(dir_info));
	return 0;
}

__lxcfs_fuse_ops int proc_access(const char *path, int mask)
{
	if (strcmp(path, "/proc") == 0 && access(path, R_OK) == 0)
		return 0;
	if (strcmp(path, "/proc/pressure") == 0 && access(path, R_OK) == 0)
		return 0;

	/* these are all read-only */
	if ((mask & ~R_OK) != 0)
		return -EACCES;

	return 0;
}

/**
 * Gets a non-hierarchical memory controller limit, or UINT64_MAX if no limit is
 * in place. If `swap` is true, reads 'swap' (v2) or 'memsw' (v1); otherwise
 * reads the memory (RAM) limits.
 *
 * @returns 0 on success (and sets `*limit`), < 0 on error
 */
static int get_memlimit(const char *cgroup, bool swap, uint64_t *limit)
{
	__do_free char *memlimit_str = NULL;
	uint64_t memlimit = UINT64_MAX;
	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)
		return ret;

	if (memlimit_str[0]) {
		ret = safe_uint64(memlimit_str, &memlimit, 10);
		if (ret < 0) {
			lxcfs_error("Failed to convert memory%s.max=%s for cgroup %s",
				    swap ? ".swap" : "", memlimit_str, cgroup);
			return ret;
		}
	}
	*limit = memlimit;
	return 0;
}

/*
 * 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;
}

/**
 * Gets a hierarchical memory controller limit, or UINT64_MAX if no limit is
 * in place. If `swap` is true, reads 'swap' (v2) or 'memsw' (v1); otherwise
 * reads the memory (RAM) limits.
 *
 * @returns 0 on success (and sets `*limit`), < 0 on error
 */
static int get_min_memlimit(const char *cgroup, bool swap, uint64_t *limit)
{
	__do_free char *copy = NULL;
	uint64_t memlimit = UINT64_MAX, retlimit = UINT64_MAX;
	int ret;

	copy = strdup(cgroup);
	if (!copy)
		return log_error_errno(0, ENOMEM, "Failed to allocate memory");

	ret = get_memlimit(copy, swap, &retlimit);
	if (ret < 0)
		return ret;

	/*
	 * If the cgroup doesn't start with / (probably won't happen), dirname()
	 * will terminate with "" instead of "/"
	 */
	while (retlimit != 0 && *copy && strcmp(copy, "/") != 0) {
		char *it = copy;

		it = gnu_dirname(it);
		ret = get_memlimit(it, swap, &memlimit);
		if (ret < 0)
			return ret;
		if (memlimit < retlimit)
			retlimit = memlimit;
	}

	*limit = retlimit;
	return 0;
}

static inline bool startswith(const char *line, const char *pref)
{
	return strncmp(line, pref, strlen(pref)) == 0;
}

static void get_swap_info(const char *cgroup, uint64_t memlimit,
			  uint64_t memusage, uint64_t *swtotal,
			  uint64_t *swusage, uint64_t *memswpriority)
{
	__do_free char *memswusage_str = NULL, *memswpriority_str = NULL;
	uint64_t memswlimit = 0, memswusage = 0;
	int ret;

	*swtotal = *swusage = 0;
	*memswpriority = 1;

	ret = get_min_memlimit(cgroup, true, &memswlimit);
	if (ret < 0)
		return;
	ret = cgroup_ops->get_memory_swap_current(cgroup_ops, cgroup, &memswusage_str);
	if (ret < 0 || safe_uint64(memswusage_str, &memswusage, 10) < 0)
		return;

	if (liblxcfs_memory_is_cgroupv2()) {
		*swtotal = memswlimit / 1024;
		*swusage = memswusage / 1024;
	} else {
		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_swappiness(cgroup_ops, cgroup, &memswpriority_str);
	if (ret >= 0)
		safe_uint64(memswpriority_str, memswpriority, 10);
}

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, *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 memlimit = 0, memusage = 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) {
		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;
	}

	pid_t 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/swaps", buf, size, d);
	prune_init_slice(cgroup);

	ret = get_min_memlimit(cgroup, false, &memlimit);
	if (ret < 0)
		return 0;
	ret = cgroup_ops->get_memory_current(cgroup_ops, cgroup, &memusage_str);
	if (ret < 0)
		return 0;
	if (safe_uint64(memusage_str, &memusage, 10) < 0)
		lxcfs_error("Failed to convert memusage %s", memusage_str);

	if (wants_swap)
		get_swap_info(cgroup, memlimit, memusage, &swtotal, &swusage, &memswpriority);

	total_len = snprintf(d->buf, d->size, "Filename\t\t\t\tType\t\tSize\tUsed\tPriority\n");

	/* Read host total and free values */
	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) {
		/* For cgroups v1, 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. */
		if (!liblxcfs_memory_is_cgroupv2())
			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, swusage);
		total_len += l;
	}

	if (total_len < 0 || l < 0)
		return log_error(0, "Failed writing to cache");

	d->cached = 1;
	d->size = (int)total_len;

	if ((size_t)total_len > size)
		total_len = size;
	memcpy(buf, d->buf, total_len);

	return total_len;
}

static void get_blkio_io_value(char *str, unsigned major, unsigned minor,
			       char *iotype, uint64_t *v)
{
	char *eol;
	char key[32];
	size_t len;

	memset(key, 0, 32);
	snprintf(key, 32, "%u:%u %s", major, minor, iotype);

	*v = 0;
	len = strlen(key);
	while (*str) {
		if (startswith(str, key)) {
			sscanf(str + len, "%" PRIu64, v);
			return;
		}
		eol = strchr(str, '\n');
		if (!eol)
			return;
		str = eol + 1;
	}
}

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)
{
	__do_free char *cg = NULL, *io_serviced_str = NULL,
		       *io_merged_str = NULL, *io_service_bytes_str = NULL,
		       *io_wait_time_str = NULL, *io_service_time_str = NULL,
		       *line = NULL;
	__do_free void *fopen_cache = NULL;
	__do_fclose FILE *f = NULL;
	struct fuse_context *fc = fuse_get_context();
	struct file_info *d = INTTYPE_TO_PTR(fi->fh);
	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;
	size_t cache_size;
	size_t linelen = 0, total_len = 0;
	int i = 0;
	int ret;

	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, d->buf + offset, total_len);

		return total_len;
	}

	/* Realloc if /proc/diskstats has grown since open(). */
	if (!try_realloc_proc_buf(d, "/proc/diskstats"))
		return -ENOMEM;
	cache = d->buf;
	cache_size = d->buflen;

	pid_t initpid = lookup_initpid_in_store(fc->pid);
	if (initpid <= 1 || is_shared_pidns(initpid))
		initpid = fc->pid;

	cg = get_pid_cgroup(initpid, "blkio");
	if (!cg)
		return read_file_fuse("/proc/diskstats", buf, size, d);
	prune_init_slice(cg);

	ret = cgroup_ops->get_io_serviced(cgroup_ops, cg, &io_serviced_str);
	if (ret < 0) {
		if (ret == -EOPNOTSUPP)
			return read_file_fuse("/proc/diskstats", buf, size, d);
	}

	ret = cgroup_ops->get_io_merged(cgroup_ops, cg, &io_merged_str);
	if (ret < 0) {
		if (ret == -EOPNOTSUPP)
			return read_file_fuse("/proc/diskstats", buf, size, d);
	}

	ret = cgroup_ops->get_io_service_bytes(cgroup_ops, cg, &io_service_bytes_str);
	if (ret < 0) {
		if (ret == -EOPNOTSUPP)
			return read_file_fuse("/proc/diskstats", buf, size, d);
	}

	ret = cgroup_ops->get_io_wait_time(cgroup_ops, cg, &io_wait_time_str);
	if (ret < 0) {
		if (ret == -EOPNOTSUPP)
			return read_file_fuse("/proc/diskstats", buf, size, d);
	}

	ret = cgroup_ops->get_io_service_time(cgroup_ops, cg, &io_service_time_str);
	if (ret < 0) {
		if (ret == -EOPNOTSUPP)
			return read_file_fuse("/proc/diskstats", buf, size, d);
	}

	f = fopen_cached("/proc/diskstats", "re", &fopen_cache);
	if (!f)
		return 0;

	while (getline(&line, &linelen, f) != -1) {
		ssize_t l;
		char lbuf[256];

		i = sscanf(line, "%u %u %71s", &stats.major, &stats.minor, stats.dev_name);
		if (i != 3)
			continue;

		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, sizeof(lbuf));
		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 ||
		    stats.discard_merged || stats.discard_sectors || stats.discard_ticks) {
			ret = strnprintf(
				lbuf,
				sizeof(lbuf),
				"%u       %u" /* major, minor */
				" %s"         /* dev_name */
				" %" PRIu64   /* read */
				" %" PRIu64   /* read_merged */
				" %" PRIu64   /* read_sectors */
				" %" PRIu64   /* read_ticks */
				" %" PRIu64   /* write */
				" %" PRIu64   /* write_merged */
				" %" PRIu64   /* write_sectors */
				" %" PRIu64   /* write_ticks */
				" %" PRIu64   /* ios_pgr */
				" %" PRIu64   /* total_ticks */
				" %" PRIu64   /* rq_ticks */
				" %" PRIu64   /* discard */
				" %" PRIu64   /* discard_merged */
				" %" PRIu64   /* discard_sectors */
				" %" PRIu64   /* discard_ticks */
				"\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,
				stats.discard_merged,
				stats.discard_sectors,
				stats.discard_ticks);
			if (ret < 0) {
				lxcfs_error("Insufficient buffer for %u:%u %s diskstats",
					    stats.major, stats.minor, stats.dev_name);
				continue;
			}
		} else {
			continue;
		}

		l = snprintf(cache, cache_size, "%s", lbuf);
		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;
		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;
}

#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
 * 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
 * account as well. If someone has a clever solution for this please send a
 * patch!
 */
static double get_reaper_busy(pid_t task)
{
	__do_free char *cgroup = NULL, *usage_str = NULL;
	uint64_t usage = 0;
	pid_t initpid;

	initpid = lookup_initpid_in_store(task);
	if (initpid <= 0)
		return 0;

	cgroup = get_pid_cgroup(initpid, "cpuacct");
	if (!cgroup)
		return 0;
	prune_init_slice(cgroup);

	if (!cgroup_ops->get(cgroup_ops, "cpuacct", cgroup, "cpuacct.usage", &usage_str))
		return 0;

	if (safe_uint64(usage_str, &usage, 10) < 0)
		lxcfs_error("Failed to convert usage %s", usage_str);

	return ((double)usage / 1000000000);
}

static uint64_t get_reaper_start_time(pid_t pid)
{
	__do_free void *fopen_cache = NULL;
	__do_fclose FILE *f = NULL;
	int ret;
	uint64_t starttime;
	char path[STRLITERALLEN("/proc/") + LXCFS_NUMSTRLEN64 +
		  STRLITERALLEN("/stat") + 1];
	pid_t qpid;

	qpid = lookup_initpid_in_store(pid);
	if (qpid <= 0)
		return ret_errno(EINVAL);

	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)
		return ret_errno(EINVAL);

	/* Note that the *scanf() argument supression requires that length
	 * modifiers such as "l" are omitted. Otherwise some compilers will yell
	 * at us. It's like telling someone you're not married and then asking
	 * if you can bring your wife to the party.
	 */
	ret = fscanf(f, "%*d "      /* (1)  pid         %d   */
			"%*s "      /* (2)  comm        %s   */
			"%*c "      /* (3)  state       %c   */
			"%*d "      /* (4)  ppid        %d   */
			"%*d "      /* (5)  pgrp        %d   */
			"%*d "      /* (6)  session     %d   */
			"%*d "      /* (7)  tty_nr      %d   */
			"%*d "      /* (8)  tpgid       %d   */
			"%*u "      /* (9)  flags       %u   */
			"%*u "      /* (10) minflt      %lu  */
			"%*u "      /* (11) cminflt     %lu  */
			"%*u "      /* (12) majflt      %lu  */
			"%*u "      /* (13) cmajflt     %lu  */
			"%*u "      /* (14) utime       %lu  */
			"%*u "      /* (15) stime       %lu  */
			"%*d "      /* (16) cutime      %ld  */
			"%*d "      /* (17) cstime      %ld  */
			"%*d "      /* (18) priority    %ld  */
			"%*d "      /* (19) nice        %ld  */
			"%*d "      /* (20) num_threads %ld  */
			"%*d "      /* (21) itrealvalue %ld  */
			"%" PRIu64, /* (22) starttime   %llu */
		     &starttime);
	if (ret != 1)
		return ret_errno(EINVAL);

	return ret_set_errno(starttime, 0);
}

static double get_reaper_start_time_in_sec(pid_t pid)
{
	uint64_t clockticks, ticks_per_sec;
	int64_t ret;
	double res = 0;

	clockticks = get_reaper_start_time(pid);
	if (clockticks <= 0)
		return log_debug(0, "Failed to retrieve start time of pid %d", pid);

	ret = sysconf(_SC_CLK_TCK);
	if (ret < 0)
		return log_debug(0, "Failed to determine number of clock ticks in a second");

	ticks_per_sec = (uint64_t)ret;
	res = (double)clockticks / ticks_per_sec;
	return res;
}

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
	 * boot minus the time when the system has started to get the actual
	 * reaper age.
	 */
	procstart = get_reaper_start_time_in_sec(pid);
	procage = procstart;
	if (procstart > 0) {
		int ret;
		struct timespec spec;

		ret = clock_gettime(CLOCK_BOOTTIME, &spec);
		if (ret < 0)
			return 0;

		uptime_ms = (spec.tv_sec * 1000) + (spec.tv_nsec * 1e-6);
		procage = (uptime_ms - (procstart * 1000)) / 1000;
	}

	return procage;
}

/*
 * We read /proc/uptime and reuse its second field.
 * For the first field, we use the mtime for the reaper for
 * the calling pid as returned by getreaperage
 */
static int proc_uptime_read(char *buf, size_t size, off_t offset,
			    struct fuse_file_info *fi)
{
	struct fuse_context *fc = fuse_get_context();
	struct file_info *d = INTTYPE_TO_PTR(fi->fh);
	char *cache = d->buf;
	ssize_t total_len = 0, ret = 0;
	double busytime, idletime, reaperage;

#ifdef RELOADTEST
	iwashere();
#endif

	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;
	}

	reaperage = get_reaper_age(fc->pid);
	/*
	 * To understand why this is done, please read the comment to the
	 * get_reaper_busy() function.
	 */
	idletime = reaperage;
	busytime = get_reaper_busy(fc->pid);
	if (reaperage >= busytime)
		idletime = reaperage - busytime;

	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->cached = 1;
	d->size = total_len;
	if ((size_t)total_len > size)
		total_len = size;
	memcpy(buf, d->buf, total_len);

	return total_len;
}

#define CPUALL_MAX_SIZE (BUF_RESERVE_SIZE / 2)
static int proc_stat_read(char *buf, size_t size, off_t offset,
			  struct fuse_file_info *fi)
{
	__do_free char *cg = NULL, *cpu_cg = NULL, *cpuset = NULL, *line = NULL;
	__do_free void *fopen_cache = NULL;
	__do_free struct cpuacct_usage *cg_cpu_usage = NULL;
	__do_fclose FILE *f = NULL;
	struct fuse_context *fc = fuse_get_context();
	struct lxcfs_opts *opts = (struct lxcfs_opts *)fc->private_data;
	struct file_info *d = INTTYPE_TO_PTR(fi->fh);
	size_t linelen = 0, total_len = 0;
	int curcpu = -1; /* cpu numbering starts at 0 */
	int physcpu = 0;
	uint64_t user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0,
		 softirq = 0, steal = 0, guest = 0, guest_nice = 0;
	uint64_t user_sum = 0, nice_sum = 0, system_sum = 0, idle_sum = 0,
		 iowait_sum = 0, irq_sum = 0, softirq_sum = 0, steal_sum = 0,
		 guest_sum = 0, guest_nice_sum = 0;
	char cpuall[CPUALL_MAX_SIZE];
	/* reserve for cpu all */
	char *cache;
	size_t cache_size;
	int cg_cpu_usage_size = 0;
	bool use_view;
	int max_cpus = 0;

	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, d->buf + offset, total_len);

		return total_len;
	}

	/* Realloc if /proc/stat has grown since open(). */
	if (!try_realloc_proc_buf(d, "/proc/stat"))
		return -ENOMEM;
	cache = d->buf + CPUALL_MAX_SIZE;
	cache_size = d->buflen - CPUALL_MAX_SIZE;

	pid_t initpid = lookup_initpid_in_store(fc->pid);
	if (initpid <= 1 || is_shared_pidns(initpid))
		initpid = fc->pid;

	/*
	 * when container run with host pid namespace initpid == 1, cgroup will "/"
	 * we should return host os's /proc contents.
	 * in some case cpuacct_usage.all in "/" will larger then /proc/stat
	 */
	if (initpid == 1)
		return read_file_fuse("/proc/stat", buf, size, d);

	cg = get_pid_cgroup(initpid, "cpuset");
	if (!cg)
		return read_file_fuse("/proc/stat", buf, size, d);
	prune_init_slice(cg);
	cpu_cg = get_pid_cgroup(initpid, "cpu");
	if (!cpu_cg)
		return read_file_fuse("/proc/stat", buf, size, d);
	prune_init_slice(cpu_cg);
	cpuset = get_cpuset(cg);
	if (!cpuset)
		return 0;

	f = fopen_cached("/proc/stat", "re", &fopen_cache);
	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
	 * CPU usage. If not, values from the host's /proc/stat are used.
	 */
	if (read_cpuacct_usage_all(cg, cpuset, &cg_cpu_usage, &cg_cpu_usage_size) == 0) {
		if (cgroup_ops->can_use_cpuview(cgroup_ops) && opts && opts->use_cfs) {
			total_len = cpuview_proc_stat(cg, cpu_cg, cpuset, cg_cpu_usage,
						      cg_cpu_usage_size, f,
						      d->buf, d->buflen);
			goto out;
		}
	} else {
		lxcfs_v("proc_stat_read failed to read from cpuacct, falling back to the host's /proc/stat");
	}

	if (cgroup_ops->can_use_cpuview(cgroup_ops) && opts && opts->use_cfs)
		use_view = true;
	else
		use_view = false;
	if (use_view)
		max_cpus = max_cpu_count(cg, cpu_cg);

	while (getline(&line, &linelen, f) != -1) {
		ssize_t l;
		char cpu_char[10]; /* That's a lot of cores */
		char *c;
		uint64_t all_used, cg_used, new_idle;
		int ret, cpu_to_render;

		if (strlen(line) == 0)
			continue;
		if (sscanf(line, "cpu%9[^ ]", cpu_char) != 1) {
			/* not a ^cpuN line containing a number N, just print it */
			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;
			cache_size -= l;
			total_len += l;

			continue;
		}

		if (sscanf(cpu_char, "%d", &physcpu) != 1)
			continue;

		if (use_view && max_cpus > 0 && (curcpu + 1) == max_cpus)
			continue;	// cannot break here because we need to consume all non-cpu lines

		if (!cpu_in_cpuset(physcpu, cpuset))
			continue;

		curcpu++;

		if (use_view)
			cpu_to_render = curcpu;
		else
			cpu_to_render = physcpu;

		ret = sscanf(
			   line,
			   "%*s"        /* <skip> */
			   " %" PRIu64  /* user */
			   " %" PRIu64  /* nice */
			   " %" PRIu64  /* system */
			   " %" PRIu64  /* idle */
			   " %" PRIu64  /* iowait */
			   " %" PRIu64  /* irq */
			   " %" PRIu64  /* softirq */
			   " %" PRIu64  /* steal */
			   " %" PRIu64  /* guest */
			   " %" PRIu64, /* guest_nice */
			   &user,
			   &nice,
			   &system,
			   &idle,
			   &iowait,
			   &irq,
			   &softirq,
			   &steal,
			   &guest,
			   &guest_nice);
		if (ret != 10 || !cg_cpu_usage) {
			c = strchr(line, ' ');
			if (!c)
				continue;

			l = snprintf(cache, cache_size, "cpu%d%s", cpu_to_render, c);
			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;
			cache_size -= l;
			total_len += l;

			if (ret != 10)
				continue;
		}

		if (cg_cpu_usage) {
			if (physcpu >= cg_cpu_usage_size)
				break;

			all_used = user + nice + system + iowait + irq + softirq + steal + guest + guest_nice;
			cg_used = cg_cpu_usage[physcpu].user + cg_cpu_usage[physcpu].system;

			if (all_used >= cg_used) {
				new_idle = idle + (all_used - cg_used);
			} else {
				lxcfs_debug("cpu%d from %s has unexpected cpu time: %" PRIu64 " in /proc/stat, %" PRIu64 " in cpuacct.usage_all; unable to determine idle time",
					    cpu_to_render, cg, all_used, cg_used);
				new_idle = idle;
			}

			l = snprintf(cache, cache_size,
				     "cpu%d %" PRIu64 " 0 %" PRIu64 " %" PRIu64 " 0 0 0 0 0 0\n",
				     cpu_to_render, cg_cpu_usage[physcpu].user,
				     cg_cpu_usage[physcpu].system, new_idle);
			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;
			cache_size -= l;
			total_len += l;

			user_sum += cg_cpu_usage[physcpu].user;
			system_sum += cg_cpu_usage[physcpu].system;
			idle_sum += new_idle;
		} else {
			user_sum += user;
			nice_sum += nice;
			system_sum += system;
			idle_sum += idle;
			iowait_sum += iowait;
			irq_sum += irq;
			softirq_sum += softirq;
			steal_sum += steal;
			guest_sum += guest;
			guest_nice_sum += guest_nice;
		}
	}

	cache = d->buf;

	int cpuall_len = snprintf(
			cpuall,
			CPUALL_MAX_SIZE,
			"cpu "
			" %" PRIu64 /* user_sum */
			" %" PRIu64 /* nice_sum */
			" %" PRIu64 /* system_sum */
			" %" PRIu64 /* idle_sum */
			" %" PRIu64 /* iowait_sum */
			" %" PRIu64 /* irq_sum */
			" %" PRIu64 /* softirq_sum */
			" %" PRIu64 /* steal_sum */
			" %" PRIu64 /* guest_sum */
			" %" PRIu64 /* guest_nice_sum */
			"\n",
			user_sum,
			nice_sum,
			system_sum,
			idle_sum,
			iowait_sum,
			irq_sum,
			softirq_sum,
			steal_sum,
			guest_sum,
			guest_nice_sum);
	if (cpuall_len > 0 && cpuall_len < CPUALL_MAX_SIZE) {
		memcpy(cache, cpuall, cpuall_len);
		cache += cpuall_len;
	} else {
		/* shouldn't happen */
		lxcfs_error("proc_stat_read copy cpuall failed, cpuall_len=%d", cpuall_len);
		cpuall_len = 0;
	}

	memmove(cache, d->buf + CPUALL_MAX_SIZE, total_len);
	total_len += cpuall_len;

out:
	d->cached = 1;
	d->size = total_len;
	if (total_len > size)
		total_len = size;

	memcpy(buf, d->buf, total_len);
	return total_len;
}

/* Note that "memory.stat" in cgroup2 is hierarchical by default. */
static bool cgroup_parse_memory_stat(const char *cgroup, struct memory_stat *mstat)
{
	__do_close int fd = -EBADF;
	__do_fclose FILE *f = NULL;
	__do_free char *line = NULL;
	__do_free void *fdopen_cache = NULL;
	bool unified;
	size_t len = 0;
	ssize_t linelen;

	fd = cgroup_ops->get_memory_stats_fd(cgroup_ops, cgroup);
	if (fd < 0)
		return false;

	f = fdopen_cached(fd, "re", &fdopen_cache);
	if (!f)
		return false;

	unified = pure_unified_layout(cgroup_ops);
	while ((linelen = getline(&line, &len, f)) != -1) {
		if (!unified && startswith(line, "hierarchical_memory_limit")) {
			sscanf(line, "hierarchical_memory_limit %" PRIu64, &(mstat->hierarchical_memory_limit));
		} else if (!unified && startswith(line, "hierarchical_memsw_limit")) {
			sscanf(line, "hierarchical_memsw_limit %" PRIu64, &(mstat->hierarchical_memsw_limit));
		} else if (startswith(line, unified ? "file" :"total_cache")) {
			sscanf(line, unified ? "file %" PRIu64 : "total_cache %" PRIu64, &(mstat->total_cache));
		} else if (!unified && startswith(line, "total_rss")) {
			sscanf(line, "total_rss %" PRIu64, &(mstat->total_rss));
		} else if (!unified && startswith(line, "total_rss_huge")) {
			sscanf(line, "total_rss_huge %" PRIu64, &(mstat->total_rss_huge));
		} else if (startswith(line, unified ? "shmem" : "total_shmem")) {
			sscanf(line, unified ? "shmem %" PRIu64 : "total_shmem %" PRIu64, &(mstat->total_shmem));
		} else if (startswith(line, unified ? "file_mapped" : "total_mapped_file")) {
			sscanf(line, unified ? "file_mapped %" PRIu64 : "total_mapped_file %" PRIu64, &(mstat->total_mapped_file));
		} else if (!unified && startswith(line, "total_dirty")) {
			sscanf(line, "total_dirty %" PRIu64, &(mstat->total_dirty));
		} else if (!unified && startswith(line, "total_writeback")) {
			sscanf(line, "total_writeback %" PRIu64, &(mstat->total_writeback));
		} else if (!unified && startswith(line, "total_swap")) {
			sscanf(line, "total_swap %" PRIu64, &(mstat->total_swap));
		} else if (!unified && startswith(line, "total_pgpgin")) {
			sscanf(line, "total_pgpgin %" PRIu64, &(mstat->total_pgpgin));
		} else if (!unified && startswith(line, "total_pgpgout")) {
			sscanf(line, "total_pgpgout %" PRIu64, &(mstat->total_pgpgout));
		} else if (startswith(line, unified ? "pgfault" : "total_pgfault")) {
			sscanf(line, unified ? "pgfault %" PRIu64 : "total_pgfault %" PRIu64, &(mstat->total_pgfault));
		} else if (startswith(line, unified ? "pgmajfault" : "total_pgmajfault")) {
			sscanf(line, unified ? "pgmajfault %" PRIu64 : "total_pgmajfault %" PRIu64, &(mstat->total_pgmajfault));
		} else if (startswith(line, unified ? "inactive_anon" : "total_inactive_anon")) {
			sscanf(line, unified ? "inactive_anon %" PRIu64 : "total_inactive_anon %" PRIu64, &(mstat->total_inactive_anon));
		} else if (startswith(line, unified ? "active_anon" : "total_active_anon")) {
			sscanf(line, unified ? "active_anon %" PRIu64 : "total_active_anon %" PRIu64, &(mstat->total_active_anon));
		} else if (startswith(line, unified ? "inactive_file" : "total_inactive_file")) {
			sscanf(line, unified ? "inactive_file %" PRIu64 : "total_inactive_file %" PRIu64, &(mstat->total_inactive_file));
		} else if (startswith(line, unified ? "active_file" : "total_active_file")) {
			sscanf(line, unified ? "active_file %" PRIu64 : "total_active_file %" PRIu64, &(mstat->total_active_file));
		} else if (startswith(line, unified ? "unevictable" : "total_unevictable")) {
			sscanf(line, unified ? "unevictable %" PRIu64 : "total_unevictable %" PRIu64, &(mstat->total_unevictable));
		} else if (unified && startswith(line, "slab ")) {
			sscanf(line, "slab %" PRIu64, &(mstat->slab));
		} else if (unified && startswith(line, "slab_reclaimable")) {
			sscanf(line, "slab_reclaimable %" PRIu64, &(mstat->slab_reclaimable));
		} else if (unified && startswith(line, "slab_unreclaimable")) {
			sscanf(line, "slab_unreclaimable %" PRIu64, &(mstat->slab_unreclaimable));
		} else if (unified && startswith(line, "zswapped")) {
			sscanf(line, "zswapped %" PRIu64, &(mstat->zswapped));
		} else if (unified && startswith(line, "zswap")) {
			sscanf(line, "zswap %" PRIu64, &(mstat->zswap));
		}
	}

	return true;
}

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, *memswpriority_str = NULL;
	__do_free void *fopen_cache = NULL;
	__do_fclose FILE *f = NULL;
	struct fuse_context *fc = fuse_get_context();
	bool wants_swap = lxcfs_has_opt(fuse_get_context()->private_data, LXCFS_SWAP_ON);
	bool wants_zswap = lxcfs_has_opt(fuse_get_context()->private_data, LXCFS_ZSWAP_ON);
	struct file_info *d = INTTYPE_TO_PTR(fi->fh);
	uint64_t memlimit = 0, memusage = 0,
		 hosttotal = 0, swfree = 0, swusage = 0, swtotal = 0,
		 memswpriority = 1;
	struct memory_stat mstat = {};
	size_t linelen = 0, total_len = 0;
	char *cache;
	size_t cache_size;
	int ret;

	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, d->buf + offset, total_len);

		return total_len;
	}

	/* Realloc if /proc/meminfo has grown since open(). */
	if (!try_realloc_proc_buf(d, "/proc/meminfo"))
		return -ENOMEM;
	cache = d->buf;
	cache_size = d->buflen;

	pid_t 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/meminfo", buf, size, d);

	prune_init_slice(cgroup);

	/* memory limits */
	ret = cgroup_ops->get_memory_current(cgroup_ops, cgroup, &memusage_str);
	if (ret < 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 read_file_fuse("/proc/meminfo", buf, size, d);

	ret = get_min_memlimit(cgroup, false, &memlimit);
	if (ret < 0)
		return read_file_fuse("/proc/meminfo", buf, size, d);
	/*
	 * Following values are allowed to fail, because swapaccount might be
	 * turned off for current kernel.
	 */
	if (wants_swap)
		get_swap_info(cgroup, memlimit, memusage, &swtotal, &swusage, &memswpriority);

	f = fopen_cached("/proc/meminfo", "re", &fopen_cache);
	if (!f)
		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];

		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);
			printme = lbuf;
		} else if (startswith(line, "MemFree:")) {
			snprintf(lbuf, 100, "MemFree:        %8" PRIu64 " kB\n", memlimit - memusage);
			printme = lbuf;
		} else if (startswith(line, "MemAvailable:")) {
			snprintf(lbuf, 100, "MemAvailable:   %8" PRIu64 " kB\n", memlimit - memusage +
				(mstat.total_active_file + mstat.total_inactive_file + mstat.slab_reclaimable) / 1024);
			printme = lbuf;
		} else if (startswith(line, "SwapTotal:")) {
			if (wants_swap) {
				uint64_t hostswtotal = 0;

				sscanf(line + STRLITERALLEN("SwapTotal:"), "%" PRIu64, &hostswtotal);

				/* In cgroups v1, 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. */
				if (!liblxcfs_memory_is_cgroupv2())
					swtotal += memlimit;

				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:")) {
			if (wants_swap) {
				swfree = swtotal - swusage;
			}

			snprintf(lbuf, 100, "SwapFree:       %8" PRIu64 " kB\n", swfree);
			printme = lbuf;
		} else if (startswith(line, "Zswap:")) {
			snprintf(lbuf, 100, "Zswap:         %8" PRIu64 " kB\n", wants_zswap ? mstat.zswap / 1024 : 0);
			printme = lbuf;
		} else if (startswith(line, "Zswapped:")) {
			snprintf(lbuf, 100, "Zswapped:       %8" PRIu64 " kB\n", wants_zswap ? mstat.zswapped / 1024 : 0);
			printme = lbuf;
		} else if (startswith(line, "Slab:")) {
			snprintf(lbuf, 100, "Slab:           %8" PRIu64 " kB\n", mstat.slab / 1024);
			printme = lbuf;
		} else if (startswith(line, "Buffers:")) {
			snprintf(lbuf, 100, "Buffers:        %8" PRIu64 " kB\n", (uint64_t)0);
			printme = lbuf;
		} else if (startswith(line, "Cached:")) {
			snprintf(lbuf, 100, "Cached:         %8" PRIu64 " kB\n",
				 mstat.total_cache / 1024);
			printme = lbuf;
		} else if (startswith(line, "SwapCached:")) {
			snprintf(lbuf, 100, "SwapCached:     %8" PRIu64 " kB\n", (uint64_t)0);
			printme = lbuf;
		} else if (startswith(line, "Active:")) {
			snprintf(lbuf, 100, "Active:         %8" PRIu64 " kB\n",
				 (mstat.total_active_anon +
				  mstat.total_active_file) /
				     1024);
			printme = lbuf;
		} else if (startswith(line, "Inactive:")) {
			snprintf(lbuf, 100, "Inactive:       %8" PRIu64 " kB\n",
				 (mstat.total_inactive_anon +
				  mstat.total_inactive_file) /
				     1024);
			printme = lbuf;
		} 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):")) {
			snprintf(lbuf, 100, "Inactive(anon): %8" PRIu64 " kB\n",
				 mstat.total_inactive_anon / 1024);
			printme = lbuf;
		} 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):")) {
			snprintf(lbuf, 100, "Inactive(file): %8" PRIu64 " kB\n",
				 mstat.total_inactive_file / 1024);
			printme = lbuf;
		} else if (startswith(line, "Unevictable:")) {
			snprintf(lbuf, 100, "Unevictable:    %8" PRIu64 " kB\n",
				 mstat.total_unevictable / 1024);
			printme = lbuf;
 		} else if (startswith(line, "Dirty:")) {
			snprintf(lbuf, 100, "Dirty:          %8" PRIu64 " kB\n",
				 mstat.total_dirty / 1024);
			printme = lbuf;
 		} else if (startswith(line, "Writeback:")) {
			snprintf(lbuf, 100, "Writeback:      %8" PRIu64 " kB\n",
				 mstat.total_writeback / 1024);
			printme = lbuf;
 		} 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:")) {
			snprintf(lbuf, 100, "Mapped:         %8" PRIu64 " kB\n",
				 mstat.total_mapped_file / 1024);
			printme = lbuf;
		} else if (startswith(line, "SReclaimable:")) {
			snprintf(lbuf, 100, "SReclaimable:   %8" PRIu64 " kB\n", mstat.slab_reclaimable / 1024);
			printme = lbuf;
		} else if (startswith(line, "SUnreclaim:")) {
			snprintf(lbuf, 100, "SUnreclaim:     %8" PRIu64 " kB\n", mstat.slab_unreclaimable / 1024);
			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:")) {
			snprintf(lbuf, 100, "ShmemHugePages: %8" PRIu64 " kB\n", (uint64_t)0);
			printme = lbuf;
		} else if (startswith(line, "ShmemPmdMapped:")) {
			snprintf(lbuf, 100, "ShmemPmdMapped: %8" PRIu64 " kB\n", (uint64_t)0);
 			printme = lbuf;
 		} else if (startswith(line, "AnonHugePages:")) {
			snprintf(lbuf, 100, "AnonHugePages:  %8" PRIu64 " kB\n",
				 mstat.total_rss_huge / 1024);
			printme = lbuf;
 		} else {
 			printme = line;
		}

		l = snprintf(cache, cache_size, "%s", printme);
		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;
		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;
	size_t cache_size;
	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, d->buf + offset, total_len);

		return total_len;
	}

	/* Realloc if /proc/slabinfo has grown since open(). */
	if (!try_realloc_proc_buf(d, "/proc/slabinfo"))
		return -ENOMEM;
	cache = d->buf;
	cache_size = d->buflen;

	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;
		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_pressure_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;
	size_t cache_size;
	char *fallback_path;
	char *controller;
	int (*get_pressure_fd)(struct cgroup_ops *ops, const char *cgroup);
	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, d->buf + offset, total_len);

		return total_len;
	}

	switch (d->type) {
	case LXC_TYPE_PROC_PRESSURE_IO:
		fallback_path = LXC_TYPE_PROC_PRESSURE_IO_PATH;
		controller = "blkio";
		get_pressure_fd = cgroup_ops->get_pressure_io_fd;
		break;
	case LXC_TYPE_PROC_PRESSURE_CPU:
		fallback_path = LXC_TYPE_PROC_PRESSURE_CPU_PATH;
		controller = "cpu";
		get_pressure_fd = cgroup_ops->get_pressure_cpu_fd;
		break;
	case LXC_TYPE_PROC_PRESSURE_MEMORY:
		fallback_path = LXC_TYPE_PROC_PRESSURE_MEMORY_PATH;
		controller = "memory";
		get_pressure_fd = cgroup_ops->get_pressure_memory_fd;
		break;
	default:
		return -EINVAL;
	}

	/* Realloc if the pressure file has grown since open(). */
	if (!try_realloc_proc_buf(d, fallback_path))
		return -ENOMEM;
	cache = d->buf;
	cache_size = d->buflen;

	initpid = lookup_initpid_in_store(fc->pid);
	if (initpid <= 1 || is_shared_pidns(initpid))
		initpid = fc->pid;

	cgroup = get_pid_cgroup(initpid, controller);
	if (!cgroup)
		return read_file_fuse(fallback_path, buf, size, d);

	prune_init_slice(cgroup);

	fd = get_pressure_fd(cgroup_ops, cgroup);
	if (fd < 0)
		return read_file_fuse(fallback_path, buf, size, d);

	f = fdopen_cached(fd, "re", &fopen_cache);
	if (!f)
		return read_file_fuse(fallback_path, 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;
		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_read_with_personality(int (*do_proc_read)(char *, size_t, off_t,
			     struct fuse_file_info *), char *buf, size_t size, off_t offset,
			     struct fuse_file_info *fi)
{
	struct fuse_context *fc = fuse_get_context();
	__u32 host_personality = liblxcfs_personality(), caller_personality;
	bool change_personality;
	int ret, read_ret;

	if (get_task_personality(fc->pid, &caller_personality) < 0)
		return log_error(0, "Failed to get caller process (pid: %d) personality", fc->pid);

	/* do we need to change thread personality? */
	change_personality = host_personality != caller_personality;

	if (change_personality) {
		ret = personality(caller_personality);
		if (ret == -1)
			return log_error(0, "Call to personality(%d) failed: %s\n",
				caller_personality, strerror(errno));

		lxcfs_debug("task (tid: %d) personality was changed %d -> %d\n",
				(int)syscall(SYS_gettid), ret, caller_personality);
	}

	read_ret = do_proc_read(buf, size, offset, fi);

	if (change_personality) {
		ret = personality(host_personality);
		if (ret == -1)
			return log_error(0, "Call to personality(%d) failed: %s\n",
				host_personality, strerror(errno));

		lxcfs_debug("task (tid: %d) personality was restored %d -> %d\n",
				(int)syscall(SYS_gettid), ret, host_personality);
	}

	return read_ret;
}

__lxcfs_fuse_ops int proc_read(const char *path, char *buf, size_t size,
			       off_t offset, struct fuse_file_info *fi)
{
	struct file_info *f = INTTYPE_TO_PTR(fi->fh);

	switch (f->type) {
	case LXC_TYPE_PROC_MEMINFO:
		if (liblxcfs_functional())
			return proc_meminfo_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_MEMINFO_PATH,
						  buf, size, offset, f);
	case LXC_TYPE_PROC_CPUINFO:
		if (liblxcfs_functional()) {
			if (!can_access_personality())
				return log_error(-EACCES, RESTRICTED_PERSONALITY_ACCESS_POLICY);
			return proc_read_with_personality(&proc_cpuinfo_read, buf, size, offset, fi);
		}

		return read_file_fuse_with_offset(LXC_TYPE_PROC_CPUINFO_PATH,
						  buf, size, offset, f);
	case LXC_TYPE_PROC_UPTIME:
		if (liblxcfs_functional())
			return proc_uptime_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_UPTIME_PATH,
						  buf, size, offset, f);
	case LXC_TYPE_PROC_STAT:
		if (liblxcfs_functional())
			return proc_stat_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_STAT_PATH, buf,
						  size, offset, f);
	case LXC_TYPE_PROC_DISKSTATS:
		if (liblxcfs_functional())
			return proc_diskstats_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_DISKSTATS_PATH,
						  buf, size, offset, f);
	case LXC_TYPE_PROC_SWAPS:
		if (liblxcfs_functional())
			return proc_swaps_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_SWAPS_PATH, buf,
						  size, offset, f);
	case LXC_TYPE_PROC_LOADAVG:
		if (liblxcfs_functional())
			return proc_loadavg_read(buf, size, offset, fi);

		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);
	case LXC_TYPE_PROC_PRESSURE_IO:
		if (liblxcfs_functional())
			return proc_pressure_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_PRESSURE_IO_PATH,
						  buf, size, offset, f);
	case LXC_TYPE_PROC_PRESSURE_CPU:
		if (liblxcfs_functional())
			return proc_pressure_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_PRESSURE_CPU_PATH,
						  buf, size, offset, f);
	case LXC_TYPE_PROC_PRESSURE_MEMORY:
		if (liblxcfs_functional())
			return proc_pressure_read(buf, size, offset, fi);

		return read_file_fuse_with_offset(LXC_TYPE_PROC_PRESSURE_MEMORY_PATH,
						  buf, size, offset, f);
	}

	return -EINVAL;
}

typedef enum {
	POLL_NOTIFY_THREAD_EXITED = 0,
	POLL_NOTIFY_THREAD_SPAWNED  = 1,
	POLL_NOTIFY_THREAD_RUNNING  = 2,
} notify_poll_thread_state_t;

typedef struct psi_trigger {
	/* increase version if the structure was changed */
	__u16 version;

	pthread_mutex_t lock;

	pthread_t tid;
	notify_poll_thread_state_t thread_state;

	/* libfuse's FUSE_NOTIFY_POLL handle */
	struct fuse_pollhandle *ph;

	struct pollfd pfd;
} psi_trigger_t;

/* PSI trigger definitions from the kernel */
#define WINDOW_MAX_US 10000000	/* Max window size is 10s */

static int proc_psi_trigger_write(const char *path, const char *buf, size_t size,
				off_t offset, struct fuse_file_info *fi)
{
	struct fuse_context *fc = fuse_get_context();
	bool psi_virtualization_enabled = lxcfs_has_opt(fc->private_data, LXCFS_PSI_POLL_ON);
	struct file_info *f = INTTYPE_TO_PTR(fi->fh);
	__do_free psi_trigger_t *t = NULL;
	__do_free char *cgroup = NULL;
	__do_close int fd = -EBADF;
	char *controller;
	int (*get_pressure_fd)(struct cgroup_ops *ops, const char *cgroup);
	pid_t initpid;
	char tmpbuf[32];
	size_t tmpbuf_size;
	bool psi_full;
	__u32 threshold_us;
	__u32 window_us;
	int ret;

	if (!liblxcfs_functional())
		return -EIO;

	if (!psi_virtualization_enabled)
		return -EINVAL;

	/* mimic logic from kernel/sched/psi.c psi_write() */
	if (f->private_data)
		return -EBUSY;

	if (offset || !size)
		return -EINVAL;

	/*
	 * We could just use buf as an input argument for write()
	 * syscall on a real fd, but it can be dangerous. It is safer
	 * to do all the parsing, parameters validation on LXCFS side
	 * and only then call write() on the underlying fd.
	 */

	tmpbuf_size = MIN(size, sizeof(tmpbuf));
	memcpy(tmpbuf, buf, tmpbuf_size);
	tmpbuf[tmpbuf_size - 1] = '\0';

	if (sscanf(tmpbuf, "some %u %u", &threshold_us, &window_us) == 2)
		psi_full = false;
	else if (sscanf(tmpbuf, "full %u %u", &threshold_us, &window_us) == 2)
		psi_full = true;
	else
		return -EINVAL;

	if (window_us == 0 || window_us > WINDOW_MAX_US)
		return -EINVAL;

	/*
	 * Use limitation for unprivileged user (see kernels psi_trigger_create()).
	 * We can relax this later if needed.
	 */
	if (window_us % 2000000)
		window_us = 2000000;

	/* Check threshold */
	if (threshold_us == 0 || threshold_us > window_us)
		return -EINVAL;

	/* rebuild everything back */
	ret = strnprintf(tmpbuf, sizeof(tmpbuf), "%s %u %u",
			 psi_full ? "full" : "some", threshold_us, window_us);
	if (ret < 0)
		return -EIO;

	switch (f->type) {
	case LXC_TYPE_PROC_PRESSURE_IO:
		controller = "blkio";
		get_pressure_fd = cgroup_ops->get_pressure_io_fd;
		break;
	case LXC_TYPE_PROC_PRESSURE_CPU:
		controller = "cpu";
		get_pressure_fd = cgroup_ops->get_pressure_cpu_fd;
		break;
	case LXC_TYPE_PROC_PRESSURE_MEMORY:
		controller = "memory";
		get_pressure_fd = cgroup_ops->get_pressure_memory_fd;
		break;
	default:
		return -EINVAL;
	}

	initpid = lookup_initpid_in_store(fc->pid);
	if (initpid <= 1 || is_shared_pidns(initpid))
		initpid = fc->pid;

	cgroup = get_pid_cgroup(initpid, controller);
	if (!cgroup)
		return -EIO;

	prune_init_slice(cgroup);

	fd = get_pressure_fd(cgroup_ops, cgroup);
	if (fd < 0)
		return -EIO;

	if (write(fd, tmpbuf, strlen(tmpbuf) + 1) < 0) {
		lxcfs_error("/proc/pressure/{cpu, io, memory} write error: %s", strerror(errno));
		return -EIO;
	}

	t = zalloc(sizeof(*t));
	if (!t)
		return -EIO;

	t->version = 1;

	if (pthread_mutex_init(&t->lock, NULL))
		return -EIO;

	t->pfd.fd = move_fd(fd);
	t->pfd.events = POLLPRI;

	/* will be released in proc_psi_trigger_release() */
	f->private_data = move_ptr(t);

	return size;
}

static void notify_poll(psi_trigger_t *t)
{
	if (!t->ph) {
		lxcfs_error("notify_poll called without pollhandle");
		return;
	}

	lxcfs_debug("thread %d sends FUSE_NOTIFY_POLL", (int)syscall(SYS_gettid));

	/* send FUSE_NOTIFY_POLL to the kernel */
	fuse_notify_poll(t->ph);

	fuse_pollhandle_destroy(t->ph);
	t->ph = NULL;
}

static void *poll_thread(void *arg)
{
	psi_trigger_t *t = arg;
	int n;
#ifdef PSITRIGGERTEST
	struct timespec req = { 1, 0 };
#endif

	pthread_mutex_lock(&t->lock);
	t->thread_state = POLL_NOTIFY_THREAD_RUNNING;

	/*
	 * If t->pfd.revents is not zero, then we already got
	 * a poll event before, so our job is only to notify
	 * kernel about it with FUSE_NOTIFY_POLL and clean t->pfd.revents.
	 */
	if (t->pfd.revents) {
		notify_poll(t);
		t->pfd.revents = 0;
		goto exit;
	}

	lxcfs_debug("thread %d is going to poll", (int)syscall(SYS_gettid));

again:
#ifndef PSITRIGGERTEST
	n = poll(&t->pfd, 1, -1);
#else
	/*
	 * It would be not practical and not stable to simulate pressure events
	 * on CI. So instead of real poll-ing on PSI files, lets just simulate "events"
	 * with nanosleep. This allows to test all the main logic.
	 *
	 * See also tests/test-psi-triggers-poll.c
	 */
	n = nanosleep(&req, NULL);
	if (n == 0) {
		/* simulate successful poll() return with event */
		n = 1;
		t->pfd.revents = DEFAULT_POLLMASK | POLLPRI;
	}
#endif

	if (n < 0) {
		/* SIG_NOTIFY_POLL_WAKEUP signal? */
		if (errno == EINTR) {
			lxcfs_debug("poll() was interrupted by a signal");

			/* see comment in proc_psi_trigger_release() */
			if (t->pfd.fd > 0)
				goto again;
		} else {
			lxcfs_error("poll() failed: %s", strerror(errno));
			t->pfd.revents = DEFAULT_POLLMASK | EPOLLERR | EPOLLPRI;
		}

		goto exit;
	}

	lxcfs_debug("thread %d has done with poll-ing %d %x", (int)syscall(SYS_gettid), n, t->pfd.revents);

	if (n > 0)
		notify_poll(t);

exit:
	t->thread_state = POLL_NOTIFY_THREAD_EXITED;
	pthread_mutex_unlock(&t->lock);
	return NULL;
}

static int schedule_notify_poll(psi_trigger_t *t)
{
	int ret = 0;

	ret = pthread_create(&t->tid, NULL, poll_thread, t);
	if (ret) {
		lxcfs_error("Failed to spawn a thread for FUSE_NOTIFY_POLL: %s", strerror(ret));
		goto exit;
	}

	ret = pthread_detach(t->tid);
	if (ret) {
		lxcfs_error("Failed to detach the FUSE_NOTIFY_POLL thread: %s", strerror(ret));
		goto exit;
	}

	lxcfs_debug("FUSE_NOTIFY_POLL thread spawned");
	t->thread_state = POLL_NOTIFY_THREAD_SPAWNED;

exit:
	return ret;
}

static int proc_psi_trigger_poll(const char *path, struct fuse_file_info *fi,
			       struct fuse_pollhandle *ph, unsigned *reventsp)
{
	struct file_info *f = INTTYPE_TO_PTR(fi->fh);
	psi_trigger_t *t = f->private_data;
	int ret = 0;

	if (!liblxcfs_functional()) {
		fuse_pollhandle_destroy(ph);
		return -EIO;
	}

	if (f->type != LXC_TYPE_PROC_PRESSURE_IO &&
	    f->type != LXC_TYPE_PROC_PRESSURE_CPU &&
	    f->type != LXC_TYPE_PROC_PRESSURE_MEMORY) {
		fuse_pollhandle_destroy(ph);
		*reventsp = DEFAULT_POLLMASK;
		return 0;
	}

	if (!t) {
		fuse_pollhandle_destroy(ph);
		*reventsp = DEFAULT_POLLMASK | EPOLLERR | EPOLLPRI;
		return 0;
	}

	/*
	 * We shouldn't block the kernel and bail out quickly,
	 * otherwise poll-s with timeout won't work.
	 */
	if (pthread_mutex_trylock(&t->lock)) {
		lxcfs_debug("proc_psi_trigger_poll() can't take a mutex now");
		fuse_pollhandle_destroy(ph);
		*reventsp = DEFAULT_POLLMASK;
		return 0;
	}

	lxcfs_debug("proc_psi_trigger_poll() has taken t->lock");

	/*
	 * We want to ensure that notify thread was able to take t->lock mutex
	 * and make its job (or wasn't spawned yet) before we go again.
	 */
	if (t->thread_state != POLL_NOTIFY_THREAD_EXITED) {
		lxcfs_debug("FUSE_NOTIFY_POLL thread hasn't finished before next FUSE_POLL arrive");
		goto exit_retry;
	}

	if (!t->pfd.revents) {
		int n = poll(&t->pfd, 1, 0);
		if (n < 0) {
			lxcfs_error("poll() failed: %s", strerror(errno));
			t->pfd.revents = DEFAULT_POLLMASK | EPOLLERR | EPOLLPRI;
		}
	}

	/* let know the kernel which events we've got */
	*reventsp = t->pfd.revents;

	/* cleanup old notify handle */
	if (t->ph) {
		fuse_pollhandle_destroy(t->ph);
		t->ph = NULL;
	}

	/*
	 * When ph is not NULL it means that kernel asks us
	 * to do FUSE_NOTIFY_POLL once we have something.
	 *
	 * We must do this from a separate thread to prevent deadlocks.
	 */
	if (ph) {
		t->ph = ph;

		ret = schedule_notify_poll(t);
		if (ret)
			goto exit;
	} else {
		/*
		 * All my tests show that ph is *always* not NULL, basically,
		 * we always being asked by the kernel to send FUSE_NOTIFY_POLL
		 * even when *reventsp is non-zero. After sending a notification
		 * we clean t->pfd.revents to prepare it for the next poll() if needed.
		 *
		 * But if ph is NULL, we need to clean it up in here.
		 */
		t->pfd.revents = 0;
	}

exit:
	pthread_mutex_unlock(&t->lock);
	lxcfs_debug("returning ret = %d *reventsp = %x", ret, *reventsp);
	return ret;

exit_retry:
	if (ph) {
		if (t->ph) {
			fuse_pollhandle_destroy(t->ph);
			t->ph = NULL;
		}

		t->ph = ph;
	}

	/* ask the kernel to get back later */
	*reventsp = 0;
	pthread_mutex_unlock(&t->lock);
	lxcfs_debug("returning ret = %d *reventsp = %x", ret, *reventsp);
	return 0;
}

static void proc_psi_trigger_release(struct file_info *f)
{
	psi_trigger_t *t = f->private_data;

	if (!t)
		return;

	/*
	 * It can happen, that proc_psi_trigger_release() is called
	 * while poll notify thread is running. We can use t->pfd.fd
	 * as a signal for this thread that it's time to finish.
	 * Once we close fd and set t->pfd.fd to -EBADF, thread will
	 * gracefully exit on our signal.
	 */
	close_prot_errno_disarm(t->pfd.fd);
	pthread_kill(t->tid, SIG_NOTIFY_POLL_WAKEUP);

	lxcfs_debug("proc_psi_trigger_release is waiting on t->lock");
	pthread_mutex_lock(&t->lock);
	if (t->thread_state != POLL_NOTIFY_THREAD_EXITED) {
		lxcfs_error("FUSE_NOTIFY_POLL thread hasn't finished (gracefully)");
		pthread_kill(t->tid, SIGKILL);
	}
	pthread_mutex_unlock(&t->lock);

	/*
	 * pthread_mutex_lock() works as a barrier in this case, we know
	 * that nobody can access (t) anymore, because poll notify thread
	 * is dead by now and we are in f_op->release() callback, there is
	 * no active read/write/poll syscall in there, because (struct file)
	 * is being destroyed.
	 */

	pthread_mutex_destroy(&t->lock);

	if (t->ph) {
		fuse_pollhandle_destroy(t->ph);
		t->ph = NULL;
	}

	free_disarm(f->private_data);
	lxcfs_debug("proc_psi_trigger_release has done");
}

__lxcfs_fuse_ops int proc_write(const char *path, const char *buf, size_t size,
				off_t offset, struct fuse_file_info *fi)
{
	struct file_info *f = INTTYPE_TO_PTR(fi->fh);

	switch (f->type) {
	case LXC_TYPE_PROC_PRESSURE_IO:
	case LXC_TYPE_PROC_PRESSURE_CPU:
	case LXC_TYPE_PROC_PRESSURE_MEMORY:
		return proc_psi_trigger_write(path, buf, size, offset, fi);
	}

	return -EINVAL;
}

__lxcfs_fuse_ops int proc_poll(const char *path, struct fuse_file_info *fi,
			       struct fuse_pollhandle *ph, unsigned *reventsp)
{
	struct file_info *f = INTTYPE_TO_PTR(fi->fh);

	switch (f->type) {
	case LXC_TYPE_PROC_PRESSURE_IO:
	case LXC_TYPE_PROC_PRESSURE_CPU:
	case LXC_TYPE_PROC_PRESSURE_MEMORY:
		return proc_psi_trigger_poll(path, fi, ph, reventsp);
	}

	fuse_pollhandle_destroy(ph);
	*reventsp = DEFAULT_POLLMASK;
	return 0;
}

__lxcfs_fuse_ops int proc_release(const char *path, struct fuse_file_info *fi)
{
	struct file_info *f;

	f = INTTYPE_TO_PTR(fi->fh);
	if (!f)
		return 0;

	switch (f->type) {
	case LXC_TYPE_PROC_PRESSURE_IO:
	case LXC_TYPE_PROC_PRESSURE_CPU:
	case LXC_TYPE_PROC_PRESSURE_MEMORY:
		proc_psi_trigger_release(f);
		break;
	}

	do_release_file_info(fi);
	return 0;
}

__lxcfs_fuse_ops int proc_releasedir(const char *path, struct fuse_file_info *fi)
{
	do_release_file_info(fi);
	return 0;
}
