--- /dev/null
+/**
+ * Libcgroup extended cgget. Works with both cgroup v1 and v2
+ *
+ * Copyright (c) 2021-2022 Oracle and/or its affiliates.
+ * Author: Tom Hromatka <tom.hromatka@oracle.com>
+ */
+
+/*
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of version 2.1 of the GNU Lesser General Public License as
+ * published by the Free Software Foundation.
+ *
+ * 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>.
+ */
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+
+#include <dirent.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tools-common.h"
+#include "abstraction-common.h"
+
+#define MODE_SHOW_HEADERS 1
+#define MODE_SHOW_NAMES 2
+
+#define LL_MAX 100
+
+static struct option const long_options[] =
+{
+ {"v1", no_argument, NULL, '1'},
+ {"v2", no_argument, NULL, '2'},
+ {"variable", required_argument, NULL, 'r'},
+ {"help", no_argument, NULL, 'h'},
+ {"all", no_argument, NULL, 'a'},
+ {"values-only", no_argument, NULL, 'v'},
+ {NULL, 0, NULL, 0}
+};
+
+static void usage(int status, const char *program_name)
+{
+ if (status != 0) {
+ fprintf(stderr, "Wrong input parameters,"
+ " try %s -h' for more information.\n",
+ program_name);
+ return;
+ }
+ printf("Usage: %s [-nv] [-r <name>] [-g <controllers>] "\
+ "[-a] <path> ...\n"\
+ " or: %s [-nv] [-r <name>] -g <controllers>:<path> ...\n",
+ program_name, program_name);
+ printf("Print parameter(s) of given group(s).\n");
+ printf(" -1, --v1 Provided parameters are in "
+ "v1 format\n");
+ printf(" -2, --v2 Provided parameters are in "
+ "v2 format\n");
+ printf(" -a, --all Print info about all relevant "
+ "controllers\n");
+ printf(" -g <controllers> Controller which info should "
+ "be displayed\n");
+ printf(" -g <controllers>:<path> Control group which info "
+ "should be displayed\n");
+ printf(" -h, --help Display this help\n");
+ printf(" -n Do not print headers\n");
+ printf(" -r, --variable <name> Define parameter to display\n");
+ printf(" -v, --values-only Print only values, not "
+ "parameter names\n");
+}
+
+static int get_controller_from_name(const char * const name,
+ char **controller)
+{
+ char *dot;
+
+ *controller = strdup(name);
+ if (*controller == NULL)
+ return ECGOTHER;
+
+ dot = strchr(*controller, '.');
+ if (dot == NULL) {
+ fprintf(stderr, "cgget: error parsing parameter name\n" \
+ " '%s'", name);
+ return ECGINVAL;
+ }
+
+ *dot = '\0';
+ return 0;
+}
+
+static int create_cg(struct cgroup **cg_list[], int * const cg_list_len)
+{
+ *cg_list = realloc(*cg_list,
+ ((*cg_list_len) + 1) * sizeof(struct cgroup *));
+ if ((*cg_list) == NULL)
+ return ECGCONTROLLERCREATEFAILED;
+ memset(&(*cg_list)[*cg_list_len], 0, sizeof(struct cgroup *));
+
+ (*cg_list)[*cg_list_len] = cgroup_new_cgroup("");
+ if ((*cg_list)[*cg_list_len] == NULL)
+ return ECGCONTROLLERCREATEFAILED;
+
+ (*cg_list_len)++;
+ return 0;
+}
+
+static int parse_a_flag(struct cgroup **cg_list[], int * const cg_list_len)
+{
+ struct cgroup_mount_point controller;
+ struct cgroup_controller *cgc;
+ struct cgroup *cg = NULL;
+ void *handle;
+ int ret = 0;
+
+ if ((*cg_list_len) == 0) {
+ ret = create_cg(cg_list, cg_list_len);
+ if (ret)
+ goto out;
+ }
+
+ /* if "-r" was provided, then we know that the cgroup(s) will be an
+ * optarg at the end with no flag. Let's temporarily populate the
+ * first cgroup with the requested control values.
+ */
+ cg = (*cg_list)[0];
+
+ ret = cgroup_get_controller_begin(&handle, &controller);
+ while (ret == 0) {
+ cgc = cgroup_get_controller(cg, controller.name);
+ if (!cgc) {
+ cgc = cgroup_add_controller(cg, controller.name);
+ if (!cgc) {
+ ret = ECGCONTROLLERCREATEFAILED;
+ goto out;
+ }
+ }
+
+ ret = cgroup_get_controller_next(&handle, &controller);
+ }
+ if (ret == ECGEOF)
+ /* we successfully reached the end of the controller list;
+ * this is not an error
+ */
+ ret = 0;
+
+ cgroup_get_controller_end(&handle);
+
+ return ret;
+
+out:
+ cgroup_get_controller_end(&handle);
+ return ret;
+}
+
+static int parse_r_flag(struct cgroup **cg_list[], int * const cg_list_len,
+ const char * const cntl_value)
+{
+ char *cntl_value_controller = NULL;
+ struct cgroup_controller *cgc;
+ struct cgroup *cg = NULL;
+ int ret = 0;
+
+ if ((*cg_list_len) == 0) {
+ ret = create_cg(cg_list, cg_list_len);
+ if (ret)
+ goto out;
+ }
+
+ /* if "-r" was provided, then we know that the cgroup(s) will be an
+ * optarg at the end with no flag. Let's temporarily populate the
+ * first cgroup with the requested control values.
+ */
+ cg = (*cg_list)[0];
+
+ ret = get_controller_from_name(cntl_value, &cntl_value_controller);
+ if (ret)
+ goto out;
+
+ cgc = cgroup_get_controller(cg, cntl_value_controller);
+ if (!cgc) {
+ cgc = cgroup_add_controller(cg, cntl_value_controller);
+ if (!cgc) {
+ fprintf(stderr, "cgget: cannot find controller '%s'\n",
+ cntl_value_controller);
+ ret = ECGOTHER;
+ goto out;
+ }
+ }
+
+ ret = cgroup_add_value_string(cgc, cntl_value, NULL);
+
+out:
+ if (cntl_value_controller)
+ free(cntl_value_controller);
+ return ret;
+}
+
+static int parse_g_flag_no_colon(struct cgroup **cg_list[],
+ int * const cg_list_len, const char * const ctrl_str)
+{
+ struct cgroup_controller *cgc;
+ struct cgroup *cg = NULL;
+ int ret = 0;
+
+ if ((*cg_list_len) > 1) {
+ ret = ECGMAXVALUESEXCEEDED;
+ goto out;
+ }
+
+ if ((*cg_list_len) == 0) {
+ ret = create_cg(cg_list, cg_list_len);
+ if (ret)
+ goto out;
+ }
+
+ /* if "-g <controller>" was provided, then we know that the cgroup(s)
+ * will be an optarg at the end with no flag. Let's temporarily
+ * populate the first cgroup with the requested control values.
+ */
+ cg = *cg_list[0];
+
+ cgc = cgroup_get_controller(cg, ctrl_str);
+ if (!cgc) {
+ cgc = cgroup_add_controller(cg, ctrl_str);
+ if (!cgc) {
+ ret = ECGCONTROLLERCREATEFAILED;
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+
+static int split_cgroup_name(const char * const ctrl_str, char *cg_name)
+{
+ char *colon;
+
+ colon = strchr(ctrl_str, ':');
+ if (colon == NULL) {
+ /* ctrl_str doesn't contain a ":" */
+ cg_name[0] = '\0';
+ return ECGINVAL;
+ }
+
+ strncpy(cg_name, &colon[1], FILENAME_MAX - 1);
+
+ return 0;
+}
+
+static int split_controllers(const char * const in,
+ char **ctrl[], int * const ctrl_len)
+{
+ char *copy, *tok, *colon, *saveptr = NULL;
+ char **tmp;
+ int ret = 0;
+
+ copy = strdup(in);
+ if (!copy)
+ goto out;
+ saveptr = copy;
+
+ colon = strchr(copy, ':');
+ if (colon)
+ colon[0] = '\0';
+
+ while ((tok = strtok_r(copy, ",", ©))) {
+ tmp = realloc(*ctrl, sizeof(char *) * ((*ctrl_len) + 1));
+ if (!tmp) {
+ ret = ECGOTHER;
+ goto out;
+ }
+
+ *ctrl = tmp;
+ (*ctrl)[*ctrl_len] = strdup(tok);
+ if ((*ctrl)[*ctrl_len] == NULL) {
+ ret = ECGOTHER;
+ goto out;
+ }
+
+ (*ctrl_len)++;
+ }
+
+out:
+ if (saveptr)
+ free(saveptr);
+ return ret;
+}
+
+static int parse_g_flag_with_colon(struct cgroup **cg_list[],
+ int * const cg_list_len, const char * const ctrl_str)
+{
+ struct cgroup_controller *cgc;
+ struct cgroup *cg = NULL;
+ char **controllers = NULL;
+ int controllers_len = 0;
+ int i, ret = 0;
+
+ ret = create_cg(cg_list, cg_list_len);
+ if (ret)
+ goto out;
+
+ cg = (*cg_list)[(*cg_list_len) - 1];
+
+ ret = split_cgroup_name(ctrl_str, cg->name);
+ if (ret)
+ goto out;
+
+ ret = split_controllers(ctrl_str, &controllers, &controllers_len);
+ if (ret)
+ goto out;
+
+ for (i = 0; i < controllers_len; i++) {
+ cgc = cgroup_get_controller(cg, controllers[i]);
+ if (!cgc) {
+ cgc = cgroup_add_controller(cg, controllers[i]);
+ if (!cgc) {
+ ret = ECGCONTROLLERCREATEFAILED;
+ goto out;
+ }
+ }
+ }
+
+out:
+ for (i = 0; i < controllers_len; i++)
+ free(controllers[i]);
+ return ret;
+}
+
+static int parse_opt_args(int argc, char *argv[], struct cgroup **cg_list[],
+ int * const cg_list_len, bool first_cg_is_dummy)
+{
+ struct cgroup *cg = NULL;
+ int ret = 0;
+
+ /* The first cgroup was temporarily populated and requires
+ * the user to provide a cgroup name as an opt */
+ if (argv[optind] == NULL && first_cg_is_dummy) {
+ usage(1, argv[0]);
+ exit(-1);
+ }
+
+ /* The user has provided a cgroup and controller via the
+ * -g <controller>:<cgroup> flag and has also provided a cgroup via
+ * the optind. This was not supported by the previous cgget
+ * implementation. Continue that approach.
+ *
+ * Example of a command that will hit this code:
+ * $ cgget -g cpu:mycgroup mycgroup
+ */
+ if (argv[optind] != NULL && (*cg_list_len) > 0 &&
+ strcmp((*cg_list)[0]->name, "") != 0) {
+ usage(1, argv[0]);
+ exit(-1);
+ }
+
+ while (argv[optind] != NULL) {
+ if ((*cg_list_len) > 0)
+ cg = (*cg_list)[(*cg_list_len) - 1];
+ else
+ cg = NULL;
+
+ if ((*cg_list_len) == 0) {
+ /* The user didn't provide a '-r' or '-g' flag.
+ * The parse_a_flag() function can be reused here
+ * because we both have the same use case - gather
+ * all the data about this particular cgroup.
+ */
+ ret = parse_a_flag(cg_list, cg_list_len);
+ if (ret)
+ goto out;
+
+ strncpy((*cg_list)[(*cg_list_len) - 1]->name,
+ argv[optind],
+ sizeof((*cg_list)[(*cg_list_len) - 1]->name) - 1);
+ } else if (cg != NULL && strlen(cg->name) == 0) {
+ /* this cgroup was created based upon control/value
+ * pairs or with a -g <controller> option. we'll
+ * populate it with the parameter provided by the
+ * user
+ */
+ strncpy(cg->name, argv[optind], sizeof(cg->name) - 1);
+ } else {
+ ret = create_cg(cg_list, cg_list_len);
+ if (ret)
+ goto out;
+
+ ret = cgroup_copy_cgroup((*cg_list)[(*cg_list_len) - 1],
+ (*cg_list)[(*cg_list_len) - 2]);
+ if (ret)
+ goto out;
+
+ strncpy((*cg_list)[(*cg_list_len) - 1]->name,
+ argv[optind],
+ sizeof((*cg_list)[(*cg_list_len) - 1]->name) - 1);
+ }
+
+ optind++;
+ }
+
+out:
+ return ret;
+}
+static int parse_opts(int argc, char *argv[], struct cgroup **cg_list[],
+ int * const cg_list_len, int * const mode,
+ enum cg_version_t * const version)
+{
+ bool do_not_fill_controller = false;
+ bool fill_controller = false;
+ bool first_cgroup_is_dummy = false;
+ int ret = 0;
+ int c;
+
+ /* Parse arguments. */
+ while ((c = getopt_long(argc, argv, "r:hnvg:a12", long_options, NULL))
+ > 0) {
+ switch (c) {
+ case 'h':
+ usage(0, argv[0]);
+ exit(0);
+ case 'n':
+ /* Do not show headers. */
+ *mode = (*mode) & (INT_MAX ^ MODE_SHOW_HEADERS);
+ break;
+ case 'v':
+ /* Do not show parameter names. */
+ *mode = (*mode) & (INT_MAX ^ MODE_SHOW_NAMES);
+ break;
+ case 'r':
+ do_not_fill_controller = true;
+ first_cgroup_is_dummy = true;
+ ret = parse_r_flag(cg_list, cg_list_len, optarg);
+ if (ret)
+ goto err;
+ break;
+ case 'g':
+ fill_controller = true;
+ if (strchr(optarg, ':') == NULL) {
+ first_cgroup_is_dummy = true;
+ ret = parse_g_flag_no_colon(cg_list,
+ cg_list_len, optarg);
+ if (ret)
+ goto err;
+ } else {
+ ret = parse_g_flag_with_colon(cg_list,
+ cg_list_len, optarg);
+ if (ret)
+ goto err;
+ }
+ break;
+ case 'a':
+ fill_controller = true;
+ ret = parse_a_flag(cg_list, cg_list_len);
+ if (ret)
+ goto err;
+ break;
+ case '1':
+ *version = CGROUP_V1;
+ break;
+ case '2':
+ *version = CGROUP_V2;
+ break;
+ default:
+ usage(1, argv[0]);
+ exit(1);
+ }
+ }
+
+ /* Don't allow '-r' and ('-g' or '-a') */
+ if (fill_controller && do_not_fill_controller) {
+ usage(1, argv[0]);
+ exit(1);
+ }
+
+ ret = parse_opt_args(argc, argv, cg_list, cg_list_len,
+ first_cgroup_is_dummy);
+ if (ret)
+ goto err;
+
+err:
+ return ret;
+}
+
+static int get_cv_value(struct control_value * const cv,
+ const char * const cg_name,
+ const char * const controller_name)
+{
+ bool is_multiline = false;
+ char tmp_line[LL_MAX];
+ void *handle, *tmp;
+ int ret;
+
+ ret = cgroup_read_value_begin(controller_name, cg_name, cv->name,
+ &handle, tmp_line, LL_MAX);
+ if (ret == ECGEOF)
+ goto read_end;
+ if (ret != 0) {
+ if (ret == ECGOTHER) {
+ int tmp_ret;
+
+ /* to maintain compatibility with an earlier version
+ * of cgget, try to determine if the failure was due
+ * to an invalid controller
+ */
+ tmp_ret = cgroup_test_subsys_mounted(controller_name);
+ if (tmp_ret == 0)
+ fprintf(stderr, "cgget: cannot find controller " \
+ "'%s' in group '%s'\n", controller_name,
+ cg_name);
+ else
+ fprintf(stderr, "variable file read failed %s\n",
+ cgroup_strerror(ret));
+ }
+
+ goto end;
+ }
+
+ /* remove the newline character */
+ tmp_line[strcspn(tmp_line, "\n")] = '\0';
+
+ strncpy(cv->value, tmp_line, CG_CONTROL_VALUE_MAX - 1);
+ cv->multiline_value = strdup(cv->value);
+ if (cv->multiline_value == NULL)
+ goto end;
+
+ while((ret = cgroup_read_value_next(&handle, tmp_line, LL_MAX)) == 0) {
+ if (ret == 0) {
+ is_multiline = true;
+ cv->value[0] = '\0';
+
+ /* remove the newline character */
+ tmp_line[strcspn(tmp_line, "\n")] = '\0';
+
+ tmp = realloc(cv->multiline_value, sizeof(char) *
+ (strlen(cv->multiline_value) + strlen(tmp_line) + 3));
+ if (tmp == NULL)
+ goto end;
+
+ cv->multiline_value = tmp;
+ strcat(cv->multiline_value, "\n\t");
+ strcat(cv->multiline_value, tmp_line);
+ }
+ }
+
+read_end:
+ cgroup_read_value_end(&handle);
+ if (ret == ECGEOF)
+ ret = 0;
+
+end:
+ if (is_multiline == false && cv->multiline_value) {
+ free(cv->multiline_value);
+ cv->multiline_value = NULL;
+ }
+
+ return ret;
+}
+
+static int indent_multiline_value(struct control_value * const cv)
+{
+ char tmp_val[CG_CONTROL_VALUE_MAX] = {0};
+ char *tok, *saveptr = NULL;
+
+ tok = strtok_r(cv->value, "\n", &saveptr);
+ strcat(tmp_val, tok);
+ /* don't indent the first value */
+ while ((tok = strtok_r(NULL, "\n", &saveptr))) {
+ strcat(tmp_val, "\n\t");
+ strcat(tmp_val, tok);
+ }
+
+ cv->multiline_value = strdup(tmp_val);
+ if (!cv->multiline_value)
+ return ECGOTHER;
+
+ return 0;
+}
+
+static int fill_empty_controller(struct cgroup * const cg,
+ struct cgroup_controller * const cgc)
+{
+ struct dirent *ctrl_dir = NULL;
+ char path[FILENAME_MAX];
+ bool found_mount = false;
+ int i, path_len, ret = 0;
+ DIR *dir = NULL;
+
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+
+ for (i = 0; i < CG_CONTROLLER_MAX &&
+ cg_mount_table[i].name[0] != '\0'; i++) {
+ if (strlen(cgc->name) == strlen(cg_mount_table[i].name) &&
+ strncmp(cgc->name, cg_mount_table[i].name,
+ strlen(cgc->name)) == 0) {
+ found_mount = true;
+ break;
+ }
+ }
+
+ if (found_mount == false)
+ goto out;
+
+ if (!cg_build_path_locked(NULL, path,
+ cg_mount_table[i].name)) {
+ goto out;
+ }
+
+ path_len = strlen(path);
+ strncat(path, cg->name, FILENAME_MAX - path_len - 1);
+ path[sizeof(path) - 1] = '\0';
+
+ if (access(path, F_OK))
+ goto out;
+
+ if (!cg_build_path_locked(cg->name, path,
+ cg_mount_table[i].name))
+ goto out;
+
+ dir = opendir(path);
+ if (!dir) {
+ ret = ECGOTHER;
+ goto out;
+ }
+
+ while ((ctrl_dir = readdir(dir)) != NULL) {
+ /*
+ * Skip over non regular files
+ */
+ if (ctrl_dir->d_type != DT_REG)
+ continue;
+
+ ret = cgroup_fill_cgc(ctrl_dir, cg, cgc, i);
+ if (ret == ECGFAIL) {
+ closedir(dir);
+ goto out;
+ }
+
+ if (cgc->index > 0) {
+ cgc->values[cgc->index - 1]->dirty = false;
+
+ /* previous versions of cgget indented the second and
+ * all subsequent lines. continue that behavior
+ */
+ if (strchr(cgc->values[cgc->index - 1]->value, '\n')) {
+ ret = indent_multiline_value(
+ cgc->values[cgc->index - 1]);
+ if (ret)
+ goto out;
+ }
+ }
+ }
+
+ closedir(dir);
+
+out:
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return ret;
+}
+
+static int get_controller_values(struct cgroup * const cg,
+ struct cgroup_controller * const cgc)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; i < cgc->index; i++) {
+ ret = get_cv_value(cgc->values[i], cg->name, cgc->name);
+ if (ret)
+ goto out;
+ }
+
+ if (cgc->index == 0) {
+ /* fill the entire controller since no values were provided */
+ ret = fill_empty_controller(cg, cgc);
+ if (ret)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static int get_cgroup_values(struct cgroup * const cg)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; i < cg->index; i++) {
+ ret = get_controller_values(cg, cg->controller[i]);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static int get_values(struct cgroup *cg_list[], int cg_list_len)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; i < cg_list_len; i++) {
+ ret = get_cgroup_values(cg_list[i]);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+void print_control_values(const struct control_value * const cv, int mode)
+{
+ if (mode & MODE_SHOW_NAMES)
+ printf("%s: ", cv->name);
+
+ if (cv->multiline_value)
+ printf("%s\n", cv->multiline_value);
+ else
+ printf("%s\n", cv->value);
+}
+
+void print_controller(const struct cgroup_controller * const cgc, int mode)
+{
+ int i;
+
+ for (i = 0; i < cgc->index; i++) {
+ print_control_values(cgc->values[i], mode);
+ }
+}
+
+static void print_cgroup(const struct cgroup * const cg, int mode)
+{
+ int i;
+
+ if (mode & MODE_SHOW_HEADERS)
+ printf("%s:\n", cg->name);
+
+ for (i = 0; i < cg->index; i++)
+ print_controller(cg->controller[i], mode);
+
+ if (mode & MODE_SHOW_HEADERS)
+ printf("\n");
+}
+
+static void print_cgroups(struct cgroup *cg_list[], int cg_list_len, int mode)
+{
+ int i;
+
+ for (i = 0; i < cg_list_len; i++) {
+ print_cgroup(cg_list[i], mode);
+ }
+}
+
+int convert_cgroups(struct cgroup **cg_list[], int cg_list_len,
+ enum cg_version_t in_version,
+ enum cg_version_t out_version)
+{
+ struct cgroup **cg_converted_list;
+ int i = 0, j, ret = 0;
+
+ cg_converted_list = malloc(cg_list_len * sizeof(struct cgroup *));
+ if (cg_converted_list == NULL)
+ goto out;
+
+ for (i = 0; i < cg_list_len; i++) {
+ cg_converted_list[i] = cgroup_new_cgroup((*cg_list)[i]->name);
+ if (cg_converted_list[i] == NULL) {
+ ret = ECGCONTROLLERCREATEFAILED;
+ goto out;
+ }
+
+ ret = cgroup_convert_cgroup(cg_converted_list[i],
+ out_version, (*cg_list)[i], in_version);
+ if (ret)
+ goto out;
+ }
+
+out:
+ if (ret) {
+ /* the conversion failed */
+ for (j = 0; j < i; j++)
+ cgroup_free(&(cg_converted_list[i]));
+ } else {
+ /* the conversion succeeded. free the old list */
+ for (i = 0; i < cg_list_len; i++)
+ cgroup_free(cg_list[i]);
+
+ *cg_list = cg_converted_list;
+ }
+
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ struct cgroup **cg_list = NULL;
+ int cg_list_len = 0;
+ int ret = 0, i;
+ int mode = MODE_SHOW_NAMES | MODE_SHOW_HEADERS;
+ enum cg_version_t version = CGROUP_UNK;
+
+ /* No parameter on input? */
+ if (argc < 2) {
+ usage(1, argv[0]);
+ return 1;
+ }
+
+ ret = cgroup_init();
+ if (ret) {
+ fprintf(stderr, "%s: libcgroup initialization failed: %s\n",
+ argv[0], cgroup_strerror(ret));
+ goto err;
+ }
+
+ ret = parse_opts(argc, argv, &cg_list, &cg_list_len, &mode, &version);
+ if (ret)
+ goto err;
+
+ ret = convert_cgroups(&cg_list, cg_list_len, version, CGROUP_DISK);
+ if (ret)
+ goto err;
+
+ ret = get_values(cg_list, cg_list_len);
+ if (ret)
+ goto err;
+
+ ret = convert_cgroups(&cg_list, cg_list_len, CGROUP_DISK, version);
+ if (ret)
+ goto err;
+
+ print_cgroups(cg_list, cg_list_len, mode);
+
+err:
+ for (i = 0; i < cg_list_len; i++)
+ cgroup_free(&(cg_list[i]));
+
+ return ret;
+}