/******************************************************************************
 * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved.
 * iSulad licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 *     http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 * Author: maoweiyong
 * Create: 2017-11-22
 * Description: provide system information functions
 ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <dirent.h>

#include "error.h"
#include "libisulad.h"
#include "sysinfo.h"
#include "log.h"
#include "read_file.h"

// Cgroup Item Definition
#define CGROUP_BLKIO_WEIGHT "blkio.weight"
#define CGROUP_BLKIO_WEIGHT_DEVICE "blkio.weight_device"
#define CGROUP_BLKIO_READ_BPS_DEVICE "blkio.throttle.read_bps_device"
#define CGROUP_BLKIO_WRITE_BPS_DEVICE "blkio.throttle.write_bps_device"
#define CGROUP_BLKIO_READ_IOPS_DEVICE "blkio.throttle.read_iops_device"
#define CGROUP_BLKIO_WRITE_IOPS_DEVICE "blkio.throttle.write_iops_device"
#define CGROUP_CPU_SHARES "cpu.shares"
#define CGROUP_CPU_PERIOD "cpu.cfs_period_us"
#define CGROUP_CPU_QUOTA "cpu.cfs_quota_us"
#define CGROUP_CPU_RT_PERIOD "cpu.rt_period_us"
#define CGROUP_CPU_RT_RUNTIME "cpu.rt_runtime_us"
#define CGROUP_CPUSET_CPUS "cpuset.cpus"
#define CGROUP_CPUSET_MEMS "cpuset.mems"
#define CGROUP_MEMORY_LIMIT "memory.limit_in_bytes"
#define CGROUP_MEMORY_SWAP "memory.memsw.limit_in_bytes"
#define CGROUP_MEMORY_SWAPPINESS "memory.swappiness"
#define CGROUP_MEMORY_RESERVATION "memory.soft_limit_in_bytes"
#define CGROUP_KENEL_MEMORY_LIMIT "memory.kmem.limit_in_bytes"
#define CGROUP_MEMORY_OOM_CONTROL "memory.oom_control"

struct layer {
    char **controllers;
    char *mountpoint;
};

static void free_list(char **str_list)
{
    int i;

    if (str_list == NULL) {
        return;
    }

    for (i = 0; str_list[i]; i++) {
        free(str_list[i]);
        str_list[i] = NULL;
    }

    free(str_list);
}

static void free_layer(struct layer **layers)
{
    struct layer **it = NULL;

    if (layers == NULL) {
        return;
    }

    for (it = layers; it && *it; it++) {
        free((*it)->mountpoint);
        (*it)->mountpoint = NULL;
        free_list((*it)->controllers);
        (*it)->controllers = NULL;
        free(*it);
        *it = NULL;
    }
    free(layers);
}

static int add_null_to_list(void ***list)
{
    int ret = 0;
    size_t index = 0;
    size_t newsize, oldsize;
    void **newlist = NULL;

    if (*list != NULL) {
        for (; (*list)[index] != NULL; index++) {
        }
    }

    if (index > SIZE_MAX / sizeof(void **) - 2) {
        ERROR("Out of range");
        return -1;
    }
    newsize = (index + 2) * sizeof(void **);
    oldsize = index * sizeof(void **);
    ret = mem_realloc((void **)&newlist, newsize, (*list), oldsize);
    if (ret < 0) {
        ERROR("Out of memory");
        return -1;
    }
    *list = newlist;
    (*list)[index + 1] = NULL;
    return (int)index;
}

static int append_string(char ***list, const char *entry)
{
    int index;
    char *dup_entry = NULL;

    index = add_null_to_list((void ***)list);
    if (index < 0) {
        return -1;
    }
    dup_entry = util_strdup_s(entry);
    if (dup_entry == NULL) {
        return -1;
    }
    (*list)[index] = dup_entry;
    return 0;
}

static int append_subsystem_to_list(char ***klist, char ***nlist, const char *ptoken)
{
    int ret = 0;

    if (strncmp(ptoken, "name=", 5) == 0) {
        ret = append_string(nlist, ptoken);
        if (ret != 0) {
            ERROR("Failed to append string");
            return -1;
        }
    } else {
        ret = append_string(klist, ptoken);
        if (ret != 0) {
            ERROR("Failed to append string");
            return -1;
        }
    }

    return 0;
}

static int get_cgroup_subsystems(char ***klist, char ***nlist)
{
    int ret = 0;
    size_t length = 0;
    FILE *fp = NULL;
    char *pline = NULL;

    fp = util_fopen("/proc/self/cgroup", "r");
    if (fp == NULL) {
        return -1;
    }

    while (getline(&pline, &length, fp) != -1) {
        char *pos = NULL;
        char *pos2 = NULL;
        char *ptoken = NULL;
        char *psave = NULL;
        pos = strchr(pline, ':');
        if (pos == NULL) {
            ERROR("Invalid cgroup entry: must contain at least two colons: %s", pline);
            ret = -1;
            goto out;
        }
        pos++;
        pos2 = strchr(pos, ':');
        if (pos2 == NULL) {
            ERROR("Invalid cgroup entry: must contain at least two colons: %s", pline);
            ret = -1;
            goto out;
        }
        *pos2 = '\0';

        if ((pos2 - pos) == 0) {
            INFO("Not supported cgroup entry: %s", pline);
            continue;
        }

        for (ptoken = strtok_r(pos, ",", &psave); ptoken; ptoken = strtok_r(NULL, ",", &psave)) {
            if (append_subsystem_to_list(klist, nlist, ptoken)) {
                goto out;
            }
        }
    }

out:
    free(pline);
    fclose(fp);
    if (ret != 0) {
        free_list(*klist);
        *klist = NULL;
        free_list(*nlist);
        *nlist = NULL;
    }
    return ret;
}

static bool list_contain_string(const char **a_list, const char *str)
{
    int i;

    if (a_list == NULL) {
        return false;
    }

    for (i = 0; a_list[i]; i++) {
        if (strcmp(a_list[i], str) == 0) {
            return true;
        }
    }

    return false;
}

static char *cgroup_legacy_must_prefix_named(const char *entry)
{
    size_t len;
    char *prefixed = NULL;
    const char *prefix = "name=";

    len = strlen(entry);

    if (((SIZE_MAX - len) - 1) < strlen(prefix)) {
        ERROR("Out of memory");
        return NULL;
    }

    prefixed = util_common_calloc_s(len + strlen(prefix) + 1);
    if (prefixed == NULL) {
        ERROR("Out of memory");
        return NULL;
    }
    (void)memcpy(prefixed, prefix, strlen(prefix));
    (void)memcpy(prefixed + strlen(prefix), entry, len);

    prefixed[len + strlen(prefix)] = '\0';
    return prefixed;
}

static int append_controller(const char **klist, const char **nlist, char ***clist, const char *entry)
{
    int index;
    char *dup_entry = NULL;

    if (list_contain_string(klist, entry) && list_contain_string(nlist, entry)) {
        ERROR("Refusing to use ambiguous controller \"%s\"", entry);
        ERROR("It is both a named and kernel subsystem");
        return -1;
    }

    index = add_null_to_list((void ***)clist);
    if (index < 0) {
        return -1;
    }

    if (strncmp(entry, "name=", 5) == 0) {
        dup_entry = util_strdup_s(entry);
    } else if (list_contain_string(klist, entry)) {
        dup_entry = util_strdup_s(entry);
    } else {
        dup_entry = cgroup_legacy_must_prefix_named(entry);
    }
    if (dup_entry == NULL) {
        return -1;
    }
    (*clist)[index] = dup_entry;
    return 0;
}

static inline bool is_cgroup_mountpoint(const char *mp)
{
    return strncmp(mp, "/sys/fs/cgroup/", strlen("/sys/fs/cgroup/")) == 0;
}

static void set_char_to_terminator(char *p)
{
    *p = '\0';
}

static char **cgroup_get_controllers(const char **klist, const char **nlist, const char *line)
{
    int index;
    char *dup = NULL;
    char *pos2 = NULL;
    char *tok = NULL;
    const char *pos = line;
    char *psave = NULL;
    char *sep = ",";
    char **pret = NULL;

    for (index = 0; index < 4; index++) {
        pos = strchr(pos, ' ');
        if (pos == NULL) {
            ERROR("Invalid mountinfo format \"%s\"", line);
            return NULL;
        }
        pos++;
    }

    if (!is_cgroup_mountpoint(pos)) {
        return NULL;
    }

    pos += strlen("/sys/fs/cgroup/");
    pos2 = strchr(pos, ' ');
    if (pos2 == NULL) {
        ERROR("Invalid mountinfo format \"%s\"", line);
        return NULL;
    }
    set_char_to_terminator(pos2);

    dup = util_strdup_s(pos);
    *pos2 = ' ';

    for (tok = strtok_r(dup, sep, &psave); tok; tok = strtok_r(NULL, sep, &psave)) {
        if (append_controller(klist, nlist, &pret, tok)) {
            ERROR("Failed to append controller");
            free_list(pret);
            pret = NULL;
            break;
        }
    }

    free(dup);

    return pret;
}

/* add hierarchy */
static int cgroup_add_layer(struct layer ***layers, char **clist, char *mountpoint)
{
    int index;
    struct layer *newh = NULL;

    newh = util_common_calloc_s(sizeof(struct layer));
    if (newh == NULL) {
        return -1;
    }

    newh->controllers = clist;
    newh->mountpoint = mountpoint;
    index = add_null_to_list((void ***)layers);
    if (index < 0) {
        free(newh);
        return -1;
    }
    (*layers)[index] = newh;
    return 0;
}

int cgroup_get_mountpoint_and_root(char *pline, char **mountpoint, char **root)
{
    int index;
    char *posmp = NULL;
    char *posrt = NULL;
    char *pos = pline;

    // find root
    for (index = 0; index < 3; index++) {
        pos = strchr(pos, ' ');
        if (pos == NULL) {
            return -1;
        }
        pos++;
    }
    posrt = pos;

    // find mountpoint
    pos = strchr(pos, ' ');
    if (pos == NULL) {
        return -1;
    }

    *pos = '\0';
    if (root != NULL) {
        *root = util_strdup_s(posrt);
    }

    pos++;
    posmp = pos;

    if (!is_cgroup_mountpoint(posmp)) {
        return -1;
    }

    pos = strchr(pos + strlen("/sys/fs/cgroup/"), ' ');
    if (pos == NULL) {
        return -1;
    }
    *pos = '\0';

    if (mountpoint != NULL) {
        *mountpoint = util_strdup_s(posmp);
    }

    return 0;
}

static bool lists_intersect(const char **controllers, const char **list)
{
    int index;

    if (controllers == NULL || list == NULL) {
        return false;
    }

    for (index = 0; controllers[index]; index++) {
        if (list_contain_string(list, controllers[index])) {
            return true;
        }
    }

    return false;
}

static bool controller_list_is_dup(struct layer **llist, const char **clist)
{
    int index;

    if (llist == NULL) {
        return false;
    }

    for (index = 0; llist[index]; index++) {
        if (lists_intersect((const char **)llist[index]->controllers, (const char **)clist)) {
            return true;
        }
    }

    return false;
}

static struct layer **cgroup_layers_find(void)
{
    int nret;
    FILE *fp = NULL;
    size_t length = 0;
    char *pline = NULL;
    char **klist = NULL;
    char **nlist = NULL;
    struct layer **layers = NULL;

    nret = get_cgroup_subsystems(&klist, &nlist);
    if (nret < 0) {
        ERROR("Failed to retrieve available legacy cgroup controllers\n");
        return NULL;
    }

    fp = util_fopen("/proc/self/mountinfo", "r");
    if (fp == NULL) {
        ERROR("Failed to open \"/proc/self/mountinfo\"\n");
        goto out;
    }

    while (getline(&pline, &length, fp) != -1) {
        char *mountpoint = NULL;
        char **clist = NULL;
        int mret;

        clist = cgroup_get_controllers((const char **)klist, (const char **)nlist, pline);
        if (clist == NULL) {
            goto list_out;
        }

        if (controller_list_is_dup(layers, (const char **)clist)) {
            goto list_out;
        }

        mret = cgroup_get_mountpoint_and_root(pline, &mountpoint, NULL);
        if (mret != 0 || mountpoint == NULL) {
            ERROR("Failed parsing mountpoint from \"%s\"\n", pline);
            goto list_out;
        }

        nret = cgroup_add_layer(&layers, clist, mountpoint);
        if (nret != 0) {
            ERROR("Failed to add hierarchies");
            goto list_out;
        }

        continue;
list_out:
        free_list(clist);
        free(mountpoint);
    }
out:
    free_list(klist);
    free_list(nlist);
    if (fp != NULL) {
        fclose(fp);
    }
    free(pline);
    return layers;
}

/* cgroup enabled */
static bool cgroup_enabled(const char *mountpoint, const char *name)
{
    char path[PATH_MAX] = { 0 };
    int nret;

    nret = snprintf(path, sizeof(path), "%s/%s", mountpoint, name);
    if (nret < 0 || (size_t)nret >= sizeof(path)) {
        ERROR("Path is too long");
        return false;
    }
    return util_file_exists(path);
}

static char *cgroup_get_pagesize(const char *pline)
{
    size_t headlen;
    char *pos2 = NULL;
    const char *pos = pline;

    headlen = strlen("Hugepagesize");
    if (strncmp(pos, "Hugepagesize", headlen) != 0) {
        return NULL;
    }

    pos2 = strchr(pos + headlen, ':');
    if (pos2 == NULL) {
        ERROR("Invalid Hugepagesize format \"%s\"", pline);
        return NULL;
    }
    *pos2 = '\0';
    pos2++;
    return util_string_delchar(pos2, ' ');
}

/* get default huge page size */
char *get_default_huge_page_size(void)
{
    int ret = 0;
    int64_t sizenum = 0;
    size_t length = 0;
    FILE *fp = NULL;
    char *pagesize = NULL;
    char *humansize = NULL;
    char *pline = NULL;

    fp = util_fopen("/proc/meminfo", "r");
    if (fp == NULL) {
        ERROR("Failed to open \"/proc/meminfo\"\n");
        return NULL;
    }

    while (getline(&pline, &length, fp) != -1) {
        pagesize = cgroup_get_pagesize(pline);
        if (pagesize != NULL) {
            break;
        }
    }
    if (pagesize == NULL) {
        ERROR("Failed to get hugepage size");
        goto out;
    }

    util_trim_newline(pagesize);

    ret = util_parse_byte_size_string(pagesize, &sizenum);
    if (ret != 0) {
        ERROR("Invalid page size: %s", pagesize);
        goto out;
    }

    humansize = util_human_size((uint64_t)sizenum);
out:
    fclose(fp);
    free(pagesize);
    free(pline);
    return humansize;
}

/* get default total mem size */
uint64_t get_default_total_mem_size(void)
{
    FILE *fp = NULL;
    size_t len = 0;
    char *line = NULL;
    char *p = NULL;
    uint64_t sysmem_limit = 0;

    fp = util_fopen("/proc/meminfo", "r");
    if (fp == NULL) {
        ERROR("Failed to open /proc/meminfo: %s", strerror(errno));
        return sysmem_limit;
    }

    while (getline(&line, &len, fp) != -1) {
        p = strchr(line, ' ');
        if (p == NULL) {
            goto out;
        }
        *p = '\0';
        p++;
        if (strcmp(line, "MemTotal:") == 0) {
            while (*p == ' ' || *p == '\t') {
                p++;
            }
            if (*p == '\0') {
                goto out;
            }
            sysmem_limit = strtoull(p, NULL, 0);
            break;
        }
    }

out:
    fclose(fp);
    free(line);
    return sysmem_limit * SIZE_KB;
}

/* get default operating system */
char *get_operating_system(void)
{
    size_t len = 0;
    FILE *fp;
    char *prettyname = NULL;
    char *pretty_name = "PRETTY_NAME=";
    char *line = NULL;

    fp = fopen(etcOsRelease, "r");
    if (fp == NULL) {
        INFO("Failed to open %s :%s", etcOsRelease, strerror(errno));
        fp = fopen(altOsRelease, "r");
        if (fp == NULL) {
            ERROR("Failed to open %s :%s", altOsRelease, strerror(errno));
            goto out;
        }
    }

    while (getline(&line, &len, fp) != -1) {
        if ((strncmp(line, pretty_name, strlen(pretty_name))) == 0) {
            prettyname = util_strdup_s(line + strlen(pretty_name));
            break;
        }
    }

    prettyname = util_trim_quotation(prettyname);

out:
    if (fp != NULL) {
        fclose(fp);
    }
    free(line);
    if (prettyname != NULL) {
        return prettyname;
    }
    return util_strdup_s("Linux");
}

static void cgroup_do_log(bool quiet, bool do_log, const char *msg)
{
    if (!quiet && do_log) {
        WARN("%s", msg);
    }
}

static char *find_cgroup_subsystem_mountpoint(struct layer **layers, const char *subsystem)
{
    struct layer **it = NULL;

    for (it = layers; it && *it; it++) {
        char **cit = NULL;

        for (cit = (*it)->controllers; cit && *cit; cit++) {
            if (strcmp(*cit, subsystem) == 0) {
                return (*it)->mountpoint;
            }
        }
    }
    return NULL;
}

/* check cgroup mem */
static void check_cgroup_mem(struct layer **layers, bool quiet, cgroup_mem_info_t *meminfo)
{
    char *mountpoint = NULL;

    mountpoint = find_cgroup_subsystem_mountpoint(layers, "memory");
    if (mountpoint == NULL) {
        cgroup_do_log(quiet, true, "Your kernel does not support cgroup memory limit");
        return;
    }

    meminfo->limit = true;

    meminfo->swap = cgroup_enabled(mountpoint, CGROUP_MEMORY_SWAP);
    cgroup_do_log(quiet, !(meminfo->swap), "Your kernel does not support swap memory limit");

    meminfo->reservation = cgroup_enabled(mountpoint, CGROUP_MEMORY_RESERVATION);
    cgroup_do_log(quiet, !(meminfo->reservation), "Your kernel does not support memory reservation");

    meminfo->oomkilldisable = cgroup_enabled(mountpoint, CGROUP_MEMORY_OOM_CONTROL);
    cgroup_do_log(quiet, !(meminfo->oomkilldisable), "Your kernel does not support oom control");

    meminfo->swappiness = cgroup_enabled(mountpoint, CGROUP_MEMORY_SWAPPINESS);
    cgroup_do_log(quiet, !(meminfo->swappiness), "Your kernel does not support memory swappiness");

    meminfo->kernel = cgroup_enabled(mountpoint, CGROUP_KENEL_MEMORY_LIMIT);
    cgroup_do_log(quiet, !(meminfo->kernel), "Your kernel does not support kernel memory limit");
}

/* check cgroup cpu */
static void check_cgroup_cpu(struct layer **layers, bool quiet, cgroup_cpu_info_t *cpuinfo)
{
    char *mountpoint = NULL;

    mountpoint = find_cgroup_subsystem_mountpoint(layers, "cpu");
    if (mountpoint == NULL) {
        cgroup_do_log(quiet, true, "Unable to find cpu cgroup in mounts");
        return;
    }

    cpuinfo->cpu_rt_period = cgroup_enabled(mountpoint, CGROUP_CPU_RT_PERIOD);
    cgroup_do_log(quiet, !(cpuinfo->cpu_rt_period), "Your kernel does not support cgroup rt period");

    cpuinfo->cpu_rt_runtime = cgroup_enabled(mountpoint, CGROUP_CPU_RT_RUNTIME);
    cgroup_do_log(quiet, !(cpuinfo->cpu_rt_runtime), "Your kernel does not support cgroup rt runtime");

    cpuinfo->cpu_shares = cgroup_enabled(mountpoint, CGROUP_CPU_SHARES);
    cgroup_do_log(quiet, !(cpuinfo->cpu_shares), "Your kernel does not support cgroup cpu shares");

    cpuinfo->cpu_cfs_period = cgroup_enabled(mountpoint, CGROUP_CPU_PERIOD);
    cgroup_do_log(quiet, !(cpuinfo->cpu_cfs_period), "Your kernel does not support cgroup cfs period");

    cpuinfo->cpu_cfs_quota = cgroup_enabled(mountpoint, CGROUP_CPU_QUOTA);
    cgroup_do_log(quiet, !(cpuinfo->cpu_cfs_quota), "Your kernel does not support cgroup cfs quota");
}

/* check cgroup blkio info */
static void check_cgroup_blkio_info(struct layer **layers, bool quiet, cgroup_blkio_info_t *blkioinfo)
{
    char *mountpoint = NULL;

    mountpoint = find_cgroup_subsystem_mountpoint(layers, "blkio");
    if (mountpoint == NULL) {
        cgroup_do_log(quiet, true, "Unable to find blkio cgroup in mounts");
        return;
    }

    blkioinfo->blkio_weight = cgroup_enabled(mountpoint, CGROUP_BLKIO_WEIGHT);
    cgroup_do_log(quiet, !(blkioinfo->blkio_weight), "Your kernel does not support cgroup blkio weight");

    blkioinfo->blkio_weight_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_WEIGHT_DEVICE);
    cgroup_do_log(quiet, !(blkioinfo->blkio_weight_device), "Your kernel does not support cgroup blkio weight_device");

    blkioinfo->blkio_read_bps_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_READ_BPS_DEVICE);
    cgroup_do_log(quiet, !(blkioinfo->blkio_read_bps_device),
                  "Your kernel does not support cgroup blkio throttle.read_bps_device");

    blkioinfo->blkio_write_bps_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_WRITE_BPS_DEVICE);
    cgroup_do_log(quiet, !(blkioinfo->blkio_write_bps_device),
                  "Your kernel does not support cgroup blkio throttle.write_bps_device");

    blkioinfo->blkio_read_iops_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_READ_IOPS_DEVICE);
    cgroup_do_log(quiet, !(blkioinfo->blkio_read_iops_device),
                  "Your kernel does not support cgroup blkio throttle.read_iops_device");

    blkioinfo->blkio_write_iops_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_WRITE_IOPS_DEVICE);
    cgroup_do_log(quiet, !(blkioinfo->blkio_write_iops_device),
                  "Your kernel does not support cgroup blkio throttle.write_iops_device");
}

/* check cgroup cpuset info */
static void check_cgroup_cpuset_info(struct layer **layers, bool quiet, cgroup_cpuset_info_t *cpusetinfo)
{
    size_t file_size = 0;
    char *mountpoint = NULL;
    char cpuset_cpus_path[PATH_MAX] = { 0 };
    char cpuset_mems_path[PATH_MAX] = { 0 };

    mountpoint = find_cgroup_subsystem_mountpoint(layers, "cpuset");
    if (mountpoint == NULL) {
        cgroup_do_log(quiet, true, ("Unable to find cpuset cgroup in mounts"));
        return;
    }

    int nret = snprintf(cpuset_cpus_path, sizeof(cpuset_cpus_path), "%s/%s", mountpoint, CGROUP_CPUSET_CPUS);
    if (nret < 0 || (size_t)nret >= sizeof(cpuset_cpus_path)) {
        ERROR("Path is too long");
        goto error;
    }

    cpusetinfo->cpus = read_file(cpuset_cpus_path, &file_size);
    if (cpusetinfo->cpus == NULL) {
        ERROR("Failed to read the file: %s", cpuset_cpus_path);
        goto error;
    }

    nret = snprintf(cpuset_mems_path, sizeof(cpuset_mems_path), "%s/%s", mountpoint, CGROUP_CPUSET_MEMS);
    if (nret < 0 || (size_t)nret >= sizeof(cpuset_mems_path)) {
        ERROR("Path is too long");
        goto error;
    }

    cpusetinfo->mems = read_file(cpuset_mems_path, &file_size);
    if (cpusetinfo->mems == NULL) {
        ERROR("Failed to read the file: %s", cpuset_mems_path);
        goto error;
    }
    cpusetinfo->cpus = util_trim_space(cpusetinfo->cpus);
    cpusetinfo->mems = util_trim_space(cpusetinfo->mems);
    cpusetinfo->cpuset = true;
    return;
error:
    free(cpusetinfo->cpus);
    cpusetinfo->cpus = NULL;
    free(cpusetinfo->mems);
    cpusetinfo->mems = NULL;
}

/* check cgroup pids */
static void check_cgroup_pids(bool quiet, cgroup_pids_info_t *pidsinfo)
{
    int ret = 0;
    char *pidsmp = NULL;

    ret = find_cgroup_mountpoint_and_root("pids", &pidsmp, NULL);
    if (ret != 0 || pidsmp == NULL) {
        if (!quiet) {
            WARN("Unable to find pids cgroup in mounts");
        }
        goto out;
    }

    pidsinfo->pidslimit = true;
out:
    free(pidsmp);
}

/* check cgroup files */
static void check_cgroup_files(bool quiet, cgroup_files_info_t *filesinfo)
{
    int ret = 0;
    char *filesmp = NULL;

    ret = find_cgroup_mountpoint_and_root("files", &filesmp, NULL);
    if (ret != 0 || filesmp == NULL) {
        if (!quiet) {
            WARN("Unable to find pids cgroup in mounts");
        }
        goto out;
    }

    filesinfo->fileslimit = true;
out:
    free(filesmp);
}

/* find cgroup mountpoint and root */
int find_cgroup_mountpoint_and_root(const char *subsystem, char **mountpoint, char **root)
{
    int ret = 0;
    FILE *fp = NULL;
    size_t length = 0;
    char *pline = NULL;

    fp = util_fopen("/proc/self/mountinfo", "r");
    if (fp == NULL) {
        ERROR("Failed to open \"/proc/self/mountinfo\"\n");
        ret = -1;
        goto free_out;
    }

    while (getline(&pline, &length, fp) != -1) {
        char *dup = NULL;
        char *p = NULL;
        char *tok = NULL;
        char *mp = NULL;
        char *rt = NULL;
        char *saveptr = NULL;
        char *sep = ",";
        int mret;

        mret = cgroup_get_mountpoint_and_root(pline, &mp, &rt);
        if (mret != 0 || mp == NULL || rt == NULL) {
            goto mp_out;
        }

        p = mp;
        p += strlen("/sys/fs/cgroup/");
        dup = util_strdup_s(p);
        if (dup == NULL) {
            ERROR("Out of memory");
            free(mp);
            ret = -1;
            goto free_out;
        }

        for (tok = strtok_r(dup, sep, &saveptr); tok; tok = strtok_r(NULL, sep, &saveptr)) {
            if (strcmp(tok, subsystem) != 0) {
                continue;
            }
            if (mountpoint != NULL) {
                *mountpoint = mp;
            } else {
                free(mp);
            }
            if (root != NULL) {
                *root = rt;
            } else {
                free(rt);
            }
            free(dup);
            goto free_out;
        }
        free(dup);
mp_out:
        free(mp);
        free(rt);
        continue;
    }
free_out:
    if (fp != NULL) {
        fclose(fp);
    }
    free(pline);
    return ret;
}

/* check cgroup hugetlb */
static void check_cgroup_hugetlb(struct layer **layers, bool quiet, cgroup_hugetlb_info_t *hugetlbinfo)
{
    int nret;
    char *mountpoint = NULL;
    char *defaultpagesize = NULL;
    char hugetlbpath[64] = { 0x00 };

    mountpoint = find_cgroup_subsystem_mountpoint(layers, "hugetlb");
    if (mountpoint == NULL) {
        cgroup_do_log(quiet, true, "Your kernel does not support cgroup hugetlb limit");
        return;
    }
    defaultpagesize = get_default_huge_page_size();
    if (defaultpagesize == NULL) {
        WARN("Your kernel does not support cgroup hugetlb limit");
        return;
    }
    nret = snprintf(hugetlbpath, sizeof(hugetlbpath), "hugetlb.%s.limit_in_bytes", defaultpagesize);
    if (nret < 0 || (size_t)nret >= sizeof(hugetlbpath)) {
        WARN("Failed to print hugetlb path");
        goto free_out;
    }
    hugetlbinfo->hugetlblimit = cgroup_enabled(mountpoint, hugetlbpath);
    cgroup_do_log(quiet, !hugetlbinfo->hugetlblimit, ("Your kernel does not support hugetlb limit"));

free_out:
    free(defaultpagesize);
}

/* get huge page sizes */
static char **get_huge_page_sizes()
{
    int index;
    int ret = 0;
    char *hugetlbmp = NULL;
    char **hps = NULL;
    DIR *dir = NULL;
    struct dirent *info_archivo = NULL;

    ret = find_cgroup_mountpoint_and_root("hugetlb", &hugetlbmp, NULL);
    if (ret != 0 || hugetlbmp == NULL) {
        ERROR("Hugetlb cgroup not supported");
        return NULL;
    }

    dir = opendir(hugetlbmp);
    if (dir == NULL) {
        ERROR("Failed to open hugetlb cgroup directory: %s", hugetlbmp);
        goto free_out;
    }
    info_archivo = readdir(dir);
    for (; info_archivo != NULL; info_archivo = readdir(dir)) {
        char *contain = NULL;
        char *dup = NULL;
        char *pos = NULL;
        char *dot2 = NULL;

        contain = strstr(info_archivo->d_name, "limit_in_bytes");
        if (contain == NULL) {
            continue;
        }

        dup = util_strdup_s(info_archivo->d_name);
        if (dup == NULL) {
            goto free_out;
        }

        pos = dup;
        pos = strchr(pos, '.');
        if (pos == NULL) {
            goto dup_free;
        }
        *pos = '\0';
        pos++;
        dot2 = strchr(pos, '.');
        if (dot2 == NULL) {
            goto dup_free;
        }
        *dot2 = '\0';

        index = add_null_to_list((void ***)&hps);
        if (index < 0) {
            free(dup);
            free_list(hps);
            hps = NULL;
            goto free_out;
        }
        hps[index] = util_strdup_s(pos);
dup_free:
        free(dup);
        continue;
    }
free_out:

    free(hugetlbmp);
    if (dir != NULL) {
        closedir(dir);
    }
    return hps;
}

/* is huge pagesize valid */
static bool is_huge_pagesize_valid(const char *pagesize)
{
    int nret;
    bool bret = false;
    size_t hps_len;
    char **hps = NULL;
    char **it = NULL;
    char hpsbuf[BUFSIZ] = { 0 };

    hps = get_huge_page_sizes();
    if (hps == NULL) {
        ERROR("Hugetlb cgroup not supported");
        goto free_out;
    }
    hps_len = util_array_len((const char **)hps);
    if (hps_len == 0) {
        ERROR("Hugetlb cgroup not supported");
        goto free_out;
    }

    for (it = hps; *it; it++) {
        nret = snprintf(hpsbuf, sizeof(hpsbuf), "%s ", *it);
        if (nret < 0 || (size_t)nret >= sizeof(hpsbuf)) {
            ERROR("hps buf is too short");
            goto free_out;
        }
        if (strcmp(*it, pagesize) == 0) {
            bret = true;
        }
    }
    hpsbuf[strlen(hpsbuf) - 1] = '\0';
free_out:
    if (!bret) {
        ERROR("Invalid hugepage size: %s, should be one of [%s]", pagesize, hpsbuf);
        isulad_set_error_message("Invalid hugepage size: %s, should be one of [%s]", pagesize, hpsbuf);
        if (g_isulad_errmsg == NULL) {
            ERROR("Out of memory");
        }
    }
    free_list(hps);
    return bret;
}

// isHugeLimitValid check whether input hugetlb limit legal
// it will check whether the limit size is times of size
static void is_hugelimit_valid(const char *pagesize, uint64_t limit)
{
    int ret;
    int64_t sizeint = 0;

    ret = util_parse_byte_size_string(pagesize, &sizeint);
    if (ret < 0 || !sizeint) {
        WARN("Invalid pagesize: %s", pagesize);
        return;
    }
    if (limit % (uint64_t)sizeint != 0) {
        WARN("HugeTlb limit should be times of hugepage size. "
             "cgroup will down round to the nearest multiple");
    }
}

// check whether hugetlb pagesize and limit legal
char *validate_hugetlb(const char *pagesize, uint64_t limit)
{
    char *newpagesize = NULL;
    int64_t sizeint = 0;

    if (pagesize != NULL && strlen(pagesize)) {
        int nret = util_parse_byte_size_string(pagesize, &sizeint);
        if (nret < 0) {
            ERROR("Invalid pagesize: %s", pagesize);
            return NULL;
        }
        newpagesize = util_human_size((uint64_t)sizeint);
        if (newpagesize == NULL) {
            ERROR("Invalid pagesize: %s", pagesize);
            return NULL;
        }
        bool valid = is_huge_pagesize_valid(newpagesize);
        if (!valid) {
            free(newpagesize);
            return NULL;
        }
    } else {
        newpagesize = get_default_huge_page_size();
        if (newpagesize == NULL) {
            ERROR("Failed to get system hugepage size");
            return NULL;
        }
    }

    is_hugelimit_valid(newpagesize, limit);

    return newpagesize;
}

/* free sysinfo */
void free_sysinfo(sysinfo_t *sysinfo)
{
    if (sysinfo == NULL) {
        return;
    }

    free(sysinfo->cpusetinfo.cpus);
    sysinfo->cpusetinfo.cpus = NULL;

    free(sysinfo->cpusetinfo.mems);
    sysinfo->cpusetinfo.mems = NULL;

    free(sysinfo);
}

/* get sys info */
sysinfo_t *get_sys_info(bool quiet)
{
    struct layer **layers = NULL;
    sysinfo_t *sysinfo = NULL;
    bool ret = true;

    sysinfo = util_common_calloc_s(sizeof(sysinfo_t));
    if (sysinfo == NULL) {
        ERROR("Out of memory");
        return NULL;
    }

    layers = cgroup_layers_find();
    if (layers == NULL) {
        ERROR("Failed to parse cgroup information");
        ret = false;
        goto out;
    }

    check_cgroup_mem(layers, quiet, &sysinfo->cgmeminfo);
    check_cgroup_cpu(layers, quiet, &sysinfo->cgcpuinfo);
    check_cgroup_hugetlb(layers, quiet, &sysinfo->hugetlbinfo);
    check_cgroup_blkio_info(layers, quiet, &sysinfo->blkioinfo);
    check_cgroup_cpuset_info(layers, quiet, &sysinfo->cpusetinfo);
    check_cgroup_pids(quiet, &sysinfo->pidsinfo);
    check_cgroup_files(quiet, &sysinfo->filesinfo);
out:
    free_layer(layers);
    if (!ret) {
        free_sysinfo(sysinfo);
        sysinfo = NULL;
    }
    return sysinfo;
}

/* free mount info */
void free_mount_info(mountinfo_t *info)
{
    if (info == NULL) {
        return;
    }

    free(info->root);
    info->root = NULL;

    free(info->mountpoint);
    info->mountpoint = NULL;

    free(info->opts);
    info->opts = NULL;

    free(info->optional);
    info->optional = NULL;

    free(info->fstype);
    info->fstype = NULL;

    free(info->source);
    info->source = NULL;

    free(info->vfsopts);
    info->vfsopts = NULL;

    free(info);
}

mountinfo_t *get_mount_info(const char *pline)
{
    size_t length;
    int ret = 0;
    mountinfo_t *info = NULL;
    char **list = NULL;

    info = util_common_calloc_s(sizeof(mountinfo_t));
    if (info == NULL) {
        ERROR("Out of memory");
        return NULL;
    }

    list = util_string_split(pline, ' ');
    if (list == NULL) {
        ERROR("Out of memory");
        ret = -1;
        goto free_out;
    }
    length = util_array_len((const char **)list);
    if (length < 8) {
        ERROR("Invalid mountinfo '%s'", pline);
        ret = -1;
        goto free_out;
    }

    info->mountpoint = util_strdup_s(list[4]);

    if (strcmp(list[6], "-")) {
        info->optional = util_strdup_s(list[6]);
    }

free_out:
    util_free_array(list);
    if (ret != 0) {
        free_mount_info(info);
        info = NULL;
    }
    return info;
}

/* free mounts info */
void free_mounts_info(mountinfo_t **minfos)
{
    mountinfo_t **it = NULL;

    if (minfos == NULL) {
        return;
    }

    for (it = minfos; it && *it; it++) {
        free_mount_info(*it);
        *it = NULL;
    }
    free(minfos);
}

/* find mount info */
mountinfo_t *find_mount_info(mountinfo_t **minfos, const char *dir)
{
    mountinfo_t **it = NULL;

    for (it = minfos; it && *it; it++) {
        if ((*it)->mountpoint && !strcmp((*it)->mountpoint, dir)) {
            return *it;
        }
    }
    return NULL;
}

/* getmountsinfo */
mountinfo_t **getmountsinfo(void)
{
    mountinfo_t **minfos = NULL;
    int ret = 0;
    FILE *fp = NULL;
    size_t length;
    char *pline = NULL;

    fp = util_fopen("/proc/self/mountinfo", "r");
    if (fp == NULL) {
        ERROR("Failed to open \"/proc/self/mountinfo\"\n");
        return NULL;
    }

    while (getline(&pline, &length, fp) != -1) {
        int index;
        mountinfo_t *info;

        info = get_mount_info(pline);
        if (info == NULL) {
            ret = -1;
            goto free_out;
        }

        index = add_null_to_list((void ***)&minfos);
        if (index < 0) {
            free_mount_info(info);
            ret = -1;
            goto free_out;
        }
        minfos[index] = info;
    }
free_out:

    fclose(fp);
    free(pline);
    if (ret != 0) {
        free_mounts_info(minfos);
        minfos = NULL;
    }
    return minfos;
}

