/*
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * Author: Daniel P. Berrange <berrange@redhat.com>
 */

#include <config.h>

#ifdef __linux__
# include "virmock.h"
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <fcntl.h>
# include <sys/stat.h>

# ifdef MAJOR_IN_MKDEV
#  include <sys/mkdev.h>
# elif MAJOR_IN_SYSMACROS
#  include <sys/sysmacros.h>
# endif

# include <stdarg.h>
# include "testutilslxc.h"
# include "virstring.h"
# include "virfile.h"

static int (*real_open)(const char *path, int flags, ...);
static FILE *(*real_fopen)(const char *path, const char *mode);
static int (*real_access)(const char *path, int mode);
static int (*real_stat)(const char *path, struct stat *sb);
static int (*real___xstat)(int ver, const char *path, struct stat *sb);
static int (*real_lstat)(const char *path, struct stat *sb);
static int (*real___lxstat)(int ver, const char *path, struct stat *sb);
static int (*real_mkdir)(const char *path, mode_t mode);

/* Don't make static, since it causes problems with clang
 * when passed as an arg to asprintf()
 * vircgroupmock.c:462:22: error: static variable 'fakesysfsdir' is used in an inline function with external linkage [-Werror,-Wstatic-in-inline]
 */
char *fakerootdir;
char *fakesysfscgroupdir;
const char *fakedevicedir0 = FAKEDEVDIR0;
const char *fakedevicedir1 = FAKEDEVDIR1;


# define SYSFS_CGROUP_PREFIX "/not/really/sys/fs/cgroup/"
# define SYSFS_CPU_PRESENT "/sys/devices/system/cpu/present"
# define SYSFS_CPU_PRESENT_MOCKED "devices_system_cpu_present"

/*
 * The plan:
 *
 * We fake out /proc/mounts, so make it look as is cgroups
 * are mounted on /not/really/sys/fs/cgroup. We don't
 * use /sys/fs/cgroup, because we want to make it easy to
 * detect places where we've not mocked enough syscalls.
 *
 * In any open/acces/mkdir calls we look at path and if
 * it starts with /not/really/sys/fs/cgroup, we rewrite
 * the path to point at a subdirectory of the temporary
 * directory referred to by LIBVIRT_FAKE_ROOT_DIR env
 * variable that is set by the main test suite
 *
 * In mkdir() calls, we simulate the cgroups behaviour
 * whereby creating the directory auto-creates a bunch
 * of files beneath it
 */

/*
 * Intentionally missing the 'devices' mount.
 * Co-mounting cpu & cpuacct controllers
 * An anonymous controller for systemd
 */
const char *procmounts =
    "rootfs / rootfs rw 0 0\n"
    "tmpfs /run tmpfs rw,seclabel,nosuid,nodev,mode=755 0 0\n"
    "tmpfs /not/really/sys/fs/cgroup tmpfs rw,seclabel,nosuid,nodev,noexec,mode=755 0 0\n"
    "cgroup /not/really/sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0\n"
    "cgroup /not/really/sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0\n"
    "cgroup /not/really/sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpuacct,cpu 0 0\n"
    "cgroup /not/really/sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0\n"
    "cgroup /not/really/sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0\n"
    "cgroup /not/really/sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0\n"
    "/dev/sda1 /boot ext4 rw,seclabel,relatime,data=ordered 0 0\n"
    "tmpfs /tmp tmpfs rw,seclabel,relatime,size=1024000k 0 0\n";

const char *procselfcgroups =
    "115:memory:/\n"
    "8:blkio:/\n"
    "6:freezer:/\n"
    "3:cpuacct,cpu:/system\n"
    "2:cpuset:/\n"
    "1:name=systemd:/user/berrange/123\n";

const char *proccgroups =
    "#subsys_name    hierarchy       num_cgroups     enabled\n"
    "cpuset  2       4       1\n"
    "cpu     3       48      1\n"
    "cpuacct 3       48      1\n"
    "memory  4       4       1\n"
    "devices 5       4       1\n"
    "freezer 6       4       1\n"
    "blkio   8       4       1\n";


const char *procmountsallinone =
    "rootfs / rootfs rw 0 0\n"
    "sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0\n"
    "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0\n"
    "udev /dev devtmpfs rw,relatime,size=16458560k,nr_inodes=4114640,mode=755 0 0\n"
    "devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0\n"
    "nfsd /proc/fs/nfsd nfsd rw,relatime 0 0\n"
    "cgroup /not/really/sys/fs/cgroup cgroup rw,relatime,blkio,devices,memory,cpuacct,cpu,cpuset 0 0\n";

const char *procselfcgroupsallinone =
    "6:blkio,devices,memory,cpuacct,cpu,cpuset:/";

const char *proccgroupsallinone =
    "#subsys_name    hierarchy       num_cgroups     enabled\n"
    "cpuset   6   1  1\n"
    "cpu      6   1  1\n"
    "cpuacct  6   1  1\n"
    "memory   6   1  1\n"
    "devices  6   1  1\n"
    "blkio    6   1  1\n";

const char *procmountslogind =
    "none /not/really/sys/fs/cgroup tmpfs rw,rootcontext=system_u:object_r:sysfs_t:s0,seclabel,relatime,size=4k,mode=755 0 0\n"
    "systemd /not/really/sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,name=systemd 0 0\n";

const char *procselfcgroupslogind =
    "1:name=systemd:/\n";

const char *proccgroupslogind =
    "#subsys_name    hierarchy       num_cgroups     enabled\n"
    "cpuset    0  1  1\n"
    "cpu       0  1  1\n"
    "cpuacct   0  1  1\n"
    "memory    0  1  0\n"
    "devices   0  1  1\n"
    "freezer   0  1  1\n"
    "net_cls   0  1  1\n"
    "blkio     0  1  1\n"
    "perf_event  0  1  1\n";



static int make_file(const char *path,
                     const char *name,
                     const char *value)
{
    int fd = -1;
    int ret = -1;
    char *filepath = NULL;

    if (asprintf(&filepath, "%s/%s", path, name) < 0)
        return -1;

    if ((fd = real_open(filepath, O_CREAT|O_WRONLY, 0600)) < 0)
        goto cleanup;

    if (write(fd, value, strlen(value)) != strlen(value))
        goto cleanup;

    ret = 0;
 cleanup:
    if (fd != -1 && close(fd) < 0)
        ret = -1;
    free(filepath);

    return ret;
}

static int make_controller(const char *path, mode_t mode)
{
    int ret = -1;
    const char *controller;

    if (!STRPREFIX(path, fakesysfscgroupdir)) {
        errno = EINVAL;
        return -1;
    }
    controller = path + strlen(fakesysfscgroupdir) + 1;

    if (STREQ(controller, "cpu"))
        return symlink("cpu,cpuacct", path);
    if (STREQ(controller, "cpuacct"))
        return symlink("cpu,cpuacct", path);

    if (real_mkdir(path, mode) < 0)
        goto cleanup;

# define MAKE_FILE(name, value)                 \
    do {                                        \
        if (make_file(path, name, value) < 0)   \
            goto cleanup;                       \
    } while (0)

    if (STRPREFIX(controller, "cpu,cpuacct")) {
        MAKE_FILE("cpu.cfs_period_us", "100000\n");
        MAKE_FILE("cpu.cfs_quota_us", "-1\n");
        MAKE_FILE("cpu.rt_period_us", "1000000\n");
        MAKE_FILE("cpu.rt_runtime_us", "950000\n");
        MAKE_FILE("cpu.shares", "1024\n");
        MAKE_FILE("cpu.stat",
                  "nr_periods 0\n"
                  "nr_throttled 0\n"
                  "throttled_time 0\n");
        MAKE_FILE("cpuacct.stat",
                  "user 216687025\n"
                  "system 43421396\n");
        MAKE_FILE("cpuacct.usage", "2787788855799582\n");
        MAKE_FILE("cpuacct.usage_percpu",
                  "7059492996 0 0 0 0 0 0 0 4180532496 0 0 0 0 0 0 0 "
                  "1957541268 0 0 0 0 0 0 0 2065932204 0 0 0 0 0 0 0 "
                  "18228689414 0 0 0 0 0 0 0 4245525148 0 0 0 0 0 0 0 "
                  "2911161568 0 0 0 0 0 0 0 1407758136 0 0 0 0 0 0 0 "
                  "1836807700 0 0 0 0 0 0 0 1065296618 0 0 0 0 0 0 0 "
                  "2046213266 0 0 0 0 0 0 0 747889778 0 0 0 0 0 0 0 "
                  "709566900 0 0 0 0 0 0 0 444777342 0 0 0 0 0 0 0 "
                  "5683512916 0 0 0 0 0 0 0 635751356 0 0 0 0 0 0 0\n");
    } else if (STRPREFIX(controller, "cpuset")) {
        MAKE_FILE("cpuset.cpu_exclusive", "1\n");
        if (STREQ(controller, "cpuset"))
            MAKE_FILE("cpuset.cpus", "0-1");
        else
            MAKE_FILE("cpuset.cpus", ""); /* Values don't inherit */
        MAKE_FILE("cpuset.mem_exclusive", "1\n");
        MAKE_FILE("cpuset.mem_hardwall", "0\n");
        MAKE_FILE("cpuset.memory_migrate", "0\n");
        MAKE_FILE("cpuset.memory_pressure", "0\n");
        MAKE_FILE("cpuset.memory_pressure_enabled", "0\n");
        MAKE_FILE("cpuset.memory_spread_page", "0\n");
        MAKE_FILE("cpuset.memory_spread_slab", "0\n");
        if (STREQ(controller, "cpuset"))
            MAKE_FILE("cpuset.mems", "0");
        else
            MAKE_FILE("cpuset.mems", ""); /* Values don't inherit */
        MAKE_FILE("cpuset.sched_load_balance", "1\n");
        MAKE_FILE("cpuset.sched_relax_domain_level", "-1\n");
    } else if (STRPREFIX(controller, "memory")) {
        MAKE_FILE("memory.failcnt", "0\n");
        MAKE_FILE("memory.force_empty", ""); /* Write only */
        MAKE_FILE("memory.kmem.tcp.failcnt", "0\n");
        MAKE_FILE("memory.kmem.tcp.limit_in_bytes", "9223372036854775807\n");
        MAKE_FILE("memory.kmem.tcp.max_usage_in_bytes", "0\n");
        MAKE_FILE("memory.kmem.tcp.usage_in_bytes", "16384\n");
        MAKE_FILE("memory.limit_in_bytes", "9223372036854775807\n");
        MAKE_FILE("memory.max_usage_in_bytes", "0\n");
        MAKE_FILE("memory.memsw.failcnt", ""); /* Not supported */
        MAKE_FILE("memory.memsw.limit_in_bytes", ""); /* Not supported */
        MAKE_FILE("memory.memsw.max_usage_in_bytes", ""); /* Not supported */
        MAKE_FILE("memory.memsw.usage_in_bytes", ""); /* Not supported */
        MAKE_FILE("memory.move_charge_at_immigrate", "0\n");
        MAKE_FILE("memory.numa_stat",
                  "total=367664 N0=367664\n"
                  "file=314764 N0=314764\n"
                  "anon=51999 N0=51999\n"
                  "unevictable=901 N0=901\n");
        MAKE_FILE("memory.oom_control",
                  "oom_kill_disable 0\n"
                  "under_oom 0\n");
        MAKE_FILE("memory.soft_limit_in_bytes", "9223372036854775807\n");
        MAKE_FILE("memory.stat",
                  "cache 1336619008\n"
                  "rss 97792000\n"
                  "mapped_file 42090496\n"
                  "pgpgin 13022605027\n"
                  "pgpgout 13023820533\n"
                  "pgfault 54429417056\n"
                  "pgmajfault 315715\n"
                  "inactive_anon 145887232\n"
                  "active_anon 67100672\n"
                  "inactive_file 627400704\n"
                  "active_file 661872640\n"
                  "unevictable 3690496\n"
                  "hierarchical_memory_limit 9223372036854775807\n"
                  "total_cache 1336635392\n"
                  "total_rss 118689792\n"
                  "total_mapped_file 42106880\n"
                  "total_pgpgin 13022606816\n"
                  "total_pgpgout 13023820793\n"
                  "total_pgfault 54429422313\n"
                  "total_pgmajfault 315715\n"
                  "total_inactive_anon 145891328\n"
                  "total_active_anon 88010752\n"
                  "total_inactive_file 627400704\n"
                  "total_active_file 661872640\n"
                  "total_unevictable 3690496\n"
                  "recent_rotated_anon 112807028\n"
                  "recent_rotated_file 2547948\n"
                  "recent_scanned_anon 113796164\n"
                  "recent_scanned_file 8199863\n");
        MAKE_FILE("memory.swappiness", "60\n");
        MAKE_FILE("memory.usage_in_bytes", "1455321088\n");
        MAKE_FILE("memory.use_hierarchy", "0\n");
    } else if (STRPREFIX(controller, "freezer")) {
        MAKE_FILE("freezer.state", "THAWED");
    } else if (STRPREFIX(controller, "blkio")) {
        MAKE_FILE("blkio.io_merged",
                  "8:0 Read 1100949\n"
                  "8:0 Write 2248076\n"
                  "8:0 Sync 63063\n"
                  "8:0 Async 3285962\n"
                  "8:0 Total 3349025\n");
        MAKE_FILE("blkio.io_queued",
                  "8:0 Read 0\n"
                  "8:0 Write 0\n"
                  "8:0 Sync 0\n"
                  "8:0 Async 0\n"
                  "8:0 Total 0\n");
        MAKE_FILE("blkio.io_service_bytes",
                  "8:0 Read 59542078464\n"
                  "8:0 Write 397369182208\n"
                  "8:0 Sync 234080922624\n"
                  "8:0 Async 222830338048\n"
                  "8:0 Total 456911260672\n");
        MAKE_FILE("blkio.io_serviced",
                  "8:0 Read 3402504\n"
                  "8:0 Write 14966516\n"
                  "8:0 Sync 12064031\n"
                  "8:0 Async 6304989\n"
                  "8:0 Total 18369020\n");
        MAKE_FILE("blkio.io_service_time",
                  "8:0 Read 10747537542349\n"
                  "8:0 Write 9200028590575\n"
                  "8:0 Sync 6449319855381\n"
                  "8:0 Async 13498246277543\n"
                  "8:0 Total 19947566132924\n");
        MAKE_FILE("blkio.io_wait_time",
                  "8:0 Read 14687514824889\n"
                  "8:0 Write 357748452187691\n"
                  "8:0 Sync 55296974349413\n"
                  "8:0 Async 317138992663167\n"
                  "8:0 Total 372435967012580\n");
        MAKE_FILE("blkio.reset_stats", ""); /* Write only */
        MAKE_FILE("blkio.sectors", "8:0 892404806\n");
        MAKE_FILE("blkio.throttle.io_service_bytes",
                  "8:0 Read 59542107136\n"
                  "8:0 Write 411440480256\n"
                  "8:0 Sync 248486822912\n"
                  "8:0 Async 222495764480\n"
                  "8:0 Total 470982587392\n"
                  "9:0 Read 59542107137\n"
                  "9:0 Write 411440480257\n"
                  "9:0 Sync 248486822912\n"
                  "9:0 Async 222495764480\n"
                  "9:0 Total 470982587392\n");
        MAKE_FILE("blkio.throttle.io_serviced",
                  "8:0 Read 4832583\n"
                  "8:0 Write 36641903\n"
                  "8:0 Sync 30723171\n"
                  "8:0 Async 10751315\n"
                  "8:0 Total 41474486\n"
                  "9:0 Read 4832584\n"
                  "9:0 Write 36641904\n"
                  "9:0 Sync 30723171\n"
                  "9:0 Async 10751315\n"
                  "9:0 Total 41474486\n");
        MAKE_FILE("blkio.throttle.read_bps_device", "");
        MAKE_FILE("blkio.throttle.read_iops_device", "");
        MAKE_FILE("blkio.throttle.write_bps_device", "");
        MAKE_FILE("blkio.throttle.write_iops_device", "");
        MAKE_FILE("blkio.time", "8:0 61019089\n");
        MAKE_FILE("blkio.weight", "1000\n");
        MAKE_FILE("blkio.weight_device", "");

    } else {
        errno = EINVAL;
        goto cleanup;
    }

    ret = 0;
 cleanup:
    return ret;
}

static void init_syms(void)
{
    if (real_fopen)
        return;

    VIR_MOCK_REAL_INIT(fopen);
    VIR_MOCK_REAL_INIT(access);
    VIR_MOCK_REAL_INIT_ALT(lstat, __lxstat);
    VIR_MOCK_REAL_INIT_ALT(stat, __xstat);
    VIR_MOCK_REAL_INIT(mkdir);
    VIR_MOCK_REAL_INIT(open);
}

static void init_sysfs(void)
{
    if (fakerootdir && fakesysfscgroupdir)
        return;

    if (!(fakerootdir = getenv("LIBVIRT_FAKE_ROOT_DIR"))) {
        fprintf(stderr, "Missing LIBVIRT_FAKE_ROOT_DIR env variable\n");
        abort();
    }

    if (virAsprintfQuiet(&fakesysfscgroupdir, "%s%s",
                         fakerootdir, SYSFS_CGROUP_PREFIX) < 0)
        abort();

    if (virFileMakePath(fakesysfscgroupdir) < 0) {
        fprintf(stderr, "Cannot create %s\n", fakesysfscgroupdir);
        abort();
    }

# define MAKE_CONTROLLER(subpath)                                      \
    do {                                                               \
        char *path;                                                    \
        if (asprintf(&path, "%s/%s", fakesysfscgroupdir, subpath) < 0) \
            abort();                                                   \
        if (make_controller(path, 0755) < 0) {                         \
            fprintf(stderr, "Cannot initialize %s\n", path);           \
            free(path);                                                \
            abort();                                                   \
        }                                                              \
        free(path);                                                    \
    } while (0)

    MAKE_CONTROLLER("cpu");
    MAKE_CONTROLLER("cpuacct");
    MAKE_CONTROLLER("cpu,cpuacct");
    MAKE_CONTROLLER("cpu,cpuacct/system");
    MAKE_CONTROLLER("cpuset");
    MAKE_CONTROLLER("blkio");
    MAKE_CONTROLLER("memory");
    MAKE_CONTROLLER("freezer");

    if (make_file(fakesysfscgroupdir,
                  SYSFS_CPU_PRESENT_MOCKED, "8-23,48-159\n") < 0)
        abort();
}


FILE *fopen(const char *path, const char *mode)
{
    const char *mock;
    bool allinone = false, logind = false;
    init_syms();

    mock = getenv("VIR_CGROUP_MOCK_MODE");
    if (mock) {
        if (STREQ(mock, "allinone"))
            allinone = true;
        else if (STREQ(mock, "logind"))
            logind = true;
    }

    if (STREQ(path, "/proc/mounts")) {
        if (STREQ(mode, "r")) {
            if (allinone)
                return fmemopen((void *)procmountsallinone,
                                strlen(procmountsallinone), mode);
            else if (logind)
                return fmemopen((void *)procmountslogind,
                                strlen(procmountslogind), mode);
            else
                return fmemopen((void *)procmounts, strlen(procmounts), mode);
        } else {
            errno = EACCES;
            return NULL;
        }
    }
    if (STREQ(path, "/proc/cgroups")) {
        if (STREQ(mode, "r")) {
            if (allinone)
                return fmemopen((void *)proccgroupsallinone,
                                strlen(proccgroupsallinone), mode);
            else if (logind)
                return fmemopen((void *)proccgroupslogind,
                                strlen(proccgroupslogind), mode);
            else
                return fmemopen((void *)proccgroups, strlen(proccgroups), mode);
        } else {
            errno = EACCES;
            return NULL;
        }
    }
    if (STREQ(path, "/proc/self/cgroup")) {
        if (STREQ(mode, "r")) {
            if (allinone)
                return fmemopen((void *)procselfcgroupsallinone,
                                strlen(procselfcgroupsallinone), mode);
            else if (logind)
                return fmemopen((void *)procselfcgroupslogind,
                                strlen(procselfcgroupslogind), mode);
            else
                return fmemopen((void *)procselfcgroups, strlen(procselfcgroups), mode);
        } else {
            errno = EACCES;
            return NULL;
        }
    }

    return real_fopen(path, mode);
}

int access(const char *path, int mode)
{
    int ret;

    init_syms();

    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
        init_sysfs();
        char *newpath;
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
            errno = ENOMEM;
            return -1;
        }
        ret = real_access(newpath, mode);
        free(newpath);
    } else if (STREQ(path, "/proc/cgroups") ||
               STREQ(path, "/proc/self/cgroup") ||
               STREQ(path, SYSFS_CPU_PRESENT)) {
        /* These files are readable for all. */
        ret = (mode == F_OK || mode == R_OK) ? 0 : -1;
    } else if (STREQ(path, "/proc/mounts")) {
        /* This one is accessible anytime for anybody. In fact, it's just
         * a symlink to /proc/self/mounts. */
        ret = 0;
    } else {
        ret = real_access(path, mode);
    }
    return ret;
}

int __lxstat(int ver, const char *path, struct stat *sb)
{
    int ret;

    init_syms();

    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
        init_sysfs();
        char *newpath;
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
            errno = ENOMEM;
            return -1;
        }
        ret = real___lxstat(ver, newpath, sb);
        free(newpath);
    } else if (STRPREFIX(path, fakedevicedir0)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(8, 0);
        return 0;
    } else if (STRPREFIX(path, fakedevicedir1)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(9, 0);
        return 0;
    } else {
        ret = real___lxstat(ver, path, sb);
    }
    return ret;
}

int lstat(const char *path, struct stat *sb)
{
    int ret;

    init_syms();

    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
        init_sysfs();
        char *newpath;
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
            errno = ENOMEM;
            return -1;
        }
        ret = real_lstat(newpath, sb);
        free(newpath);
    } else if (STRPREFIX(path, fakedevicedir0)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(8, 0);
        return 0;
    } else if (STRPREFIX(path, fakedevicedir1)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(9, 0);
        return 0;
    } else {
        ret = real_lstat(path, sb);
    }
    return ret;
}

int __xstat(int ver, const char *path, struct stat *sb)
{
    int ret;

    init_syms();

    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
        init_sysfs();
        char *newpath;
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
            errno = ENOMEM;
            return -1;
        }
        ret = real___xstat(ver, newpath, sb);
        free(newpath);
    } else if (STRPREFIX(path, fakedevicedir0)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(8, 0);
        return 0;
    } else if (STRPREFIX(path, fakedevicedir1)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(9, 0);
        return 0;
    } else {
        ret = real___xstat(ver, path, sb);
    }
    return ret;
}

int stat(const char *path, struct stat *sb)
{
    char *newpath = NULL;
    int ret;

    init_syms();

    if (STREQ(path, SYSFS_CPU_PRESENT)) {
        init_sysfs();
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     SYSFS_CPU_PRESENT_MOCKED) < 0) {
            errno = ENOMEM;
            return -1;
        }
    } else if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
        init_sysfs();
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
            errno = ENOMEM;
            return -1;
        }
    } else if (STRPREFIX(path, fakedevicedir0)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(8, 0);
        return 0;
    } else if (STRPREFIX(path, fakedevicedir1)) {
        sb->st_mode = S_IFBLK;
        sb->st_rdev = makedev(9, 0);
        return 0;
    } else {
        if (!(newpath = strdup(path)))
            return -1;
    }
    ret = real_stat(newpath, sb);
    free(newpath);
    return ret;
}

int mkdir(const char *path, mode_t mode)
{
    int ret;

    init_syms();

    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
        init_sysfs();
        char *newpath;
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
            errno = ENOMEM;
            return -1;
        }
        ret = make_controller(newpath, mode);
        free(newpath);
    } else {
        ret = real_mkdir(path, mode);
    }
    return ret;
}

int open(const char *path, int flags, ...)
{
    int ret;
    char *newpath = NULL;

    init_syms();

    if (STREQ(path, SYSFS_CPU_PRESENT)) {
        init_sysfs();
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     SYSFS_CPU_PRESENT_MOCKED) < 0) {
            errno = ENOMEM;
            return -1;
        }
    }

    if (STRPREFIX(path, SYSFS_CGROUP_PREFIX)) {
        init_sysfs();
        if (asprintf(&newpath, "%s/%s",
                     fakesysfscgroupdir,
                     path + strlen(SYSFS_CGROUP_PREFIX)) < 0) {
            errno = ENOMEM;
            return -1;
        }
    }
    if (flags & O_CREAT) {
        va_list ap;
        mode_t mode;
        va_start(ap, flags);
        mode = va_arg(ap, mode_t);
        va_end(ap);
        ret = real_open(newpath ? newpath : path, flags, mode);
    } else {
        ret = real_open(newpath ? newpath : path, flags);
    }
    free(newpath);
    return ret;
}
#else
/* Nothing to override on non-__linux__ platforms */
#endif
