From: Tom Hromatka Date: Fri, 28 Jan 2022 17:22:11 +0000 (-0700) Subject: cgxget: Introduce cgxget X-Git-Tag: v3.0~234 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8ac6f336b4ff694463242d6b55ff803f4ff8ce0d;p=thirdparty%2Flibcgroup.git cgxget: Introduce cgxget Add a new libcgroup tool - cgxget. cgxget is based upon cgget, but it supports converting from cgroup version to another. For example, a request like the following will work on a system running the cgroup v1 cpu controller or the cgroup v2 cpu controller. $ cgxget -2 -r cpu.weight MyCgroup The return value to the user will match the cgroup version requested. If the user requests a v1 setting, the return value will be a cgroup v1 setting. If the user requests a v2 setting, the return value will be a cgroup v2 setting. Signed-off-by: Tom Hromatka Reviewed-by: Kamalesh Babulal --- diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index 69806a3f..268630b9 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -5,8 +5,8 @@ LDADD = $(top_builddir)/src/libcgroup.la -lpthread if WITH_TOOLS -bin_PROGRAMS = cgexec cgclassify cgcreate cgset cgget cgdelete lssubsys \ - lscgroup cgsnapshot +bin_PROGRAMS = cgexec cgclassify cgcreate cgset cgget cgxget cgdelete \ + lssubsys lscgroup cgsnapshot sbin_PROGRAMS = cgconfigparser @@ -37,6 +37,10 @@ cgget_SOURCES = cgget.c tools-common.c tools-common.h cgget_LIBS = $(CODE_COVERAGE_LIBS) cgget_CFLAGS = $(CODE_COVERAGE_CFLAGS) +cgxget_SOURCES = cgxget.c tools-common.c tools-common.h +cgxget_LIBS = $(CODE_COVERAGE_LIBS) +cgxget_CFLAGS = $(CODE_COVERAGE_CFLAGS) + cgconfigparser_SOURCES = cgconfig.c tools-common.c tools-common.h cgconfigparser_LIBS = $(CODE_COVERAGE_LIBS) cgconfigparser_CFLAGS = $(CODE_COVERAGE_CFLAGS) diff --git a/src/tools/cgget.c b/src/tools/cgget.c index 01fae3c8..22e17111 100644 --- a/src/tools/cgget.c +++ b/src/tools/cgget.c @@ -52,7 +52,7 @@ static void usage(int status, const char *program_name) } static int get_controller_from_name(const char * const name, - char **controller) + char **controller) { char *dot; @@ -643,7 +643,7 @@ out: static int get_controller_values(struct cgroup * const cg, struct cgroup_controller * const cgc) { - int ret; + int ret = 0; int i; for (i = 0; i < cgc->index; i++) { @@ -665,7 +665,7 @@ out: static int get_cgroup_values(struct cgroup * const cg) { - int ret; + int ret = 0; int i; for (i = 0; i < cg->index; i++) { @@ -679,7 +679,7 @@ static int get_cgroup_values(struct cgroup * const cg) static int get_values(struct cgroup *cg_list[], int cg_list_len) { - int ret; + int ret = 0; int i; for (i = 0; i < cg_list_len; i++) { diff --git a/src/tools/cgxget.c b/src/tools/cgxget.c new file mode 100644 index 00000000..b82474ff --- /dev/null +++ b/src/tools/cgxget.c @@ -0,0 +1,848 @@ +/** + * Libcgroup extended cgget. Works with both cgroup v1 and v2 + * + * Copyright (c) 2021-2022 Oracle and/or its affiliates. + * Author: Tom Hromatka + */ + +/* + * 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 . + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 ] [-g ] "\ + "[-a] ...\n"\ + " or: %s [-nv] [-r ] -g : ...\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 Controller which info should " + "be displayed\n"); + printf(" -g : 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 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 " 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 : 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 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; +}