]> git.ipfire.org Git - thirdparty/libcgroup.git/commitdiff
cgxget: Introduce cgxget
authorTom Hromatka <tom.hromatka@oracle.com>
Fri, 28 Jan 2022 17:22:11 +0000 (10:22 -0700)
committerTom Hromatka <tom.hromatka@oracle.com>
Thu, 3 Feb 2022 21:42:20 +0000 (14:42 -0700)
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 <tom.hromatka@oracle.com>
Reviewed-by: Kamalesh Babulal <kamalesh.babulal@oracle.com>
src/tools/Makefile.am
src/tools/cgget.c
src/tools/cgxget.c [new file with mode: 0644]

index 69806a3f96f6c8bb8161b42a59c02c31ddf5aa8e..268630b9ef9e92fd5c53476fd26585b0e92d59ac 100644 (file)
@@ -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)
index 01fae3c8714b2987fd534039533ef5a080cc0c37..22e171118a0778ee45aa5d16e64c6fda52dd0d51 100644 (file)
@@ -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 (file)
index 0000000..b82474f
--- /dev/null
@@ -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 <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, ",", &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;
+}