]> git.ipfire.org Git - thirdparty/libcgroup.git/commitdiff
libcgroup: New API interfaces for task placement
authorDhaval Giani <dhaval@linux.vnet.ibm.com>
Wed, 13 Aug 2008 19:58:05 +0000 (19:58 +0000)
committerDhaval Giani <dhaval@linux.vnet.ibm.com>
Wed, 13 Aug 2008 19:58:05 +0000 (19:58 +0000)
From: Vivek Goyal <vgoyal@redhat.com>

Enhancement to libcg to do following.
- Parse the /etc/cgrules.conf file and place the task in the right cgroup.
- Exported two APIs which can place the task based on /etc/cgrules.conf
  or based on cgroup explicitly specified by the caller.

- There can be multiple users.
        - PAM module (pam_cgroup)
        - cgexec tool
        - cgclassify tool
        - APIs can be directly used by other cgroup aware user progarams.
        - Classification daemon

Signed-off-by: Vivek Goyal <vgoyal@redhat.com>
Signed-off-by: Dhaval Giani <dhaval@linux.vnet.ibm.com>
git-svn-id: https://libcg.svn.sourceforge.net/svnroot/libcg/trunk@136 4f4bb910-9a46-0410-90c8-c897d4f1cd53

api.c
libcgroup-internal.h
libcgroup.h
samples/cgrules.conf [new file with mode: 0644]

diff --git a/api.c b/api.c
index 41e0c6dcf5ade6704b8c03b8e8171406d92d22ed..68eea8a532eb9d300487be51c924f4292f9bca7b 100644 (file)
--- a/api.c
+++ b/api.c
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <fcntl.h>
 #include <sys/syscall.h>
 #include <unistd.h>
 #include <fts.h>
+#include <ctype.h>
+#include <pwd.h>
 
 #ifndef PACKAGE_VERSION
 #define PACKAGE_VERSION 0.01
@@ -896,3 +899,343 @@ unlock_error:
        cgroup = NULL;
        return NULL;
 }
+
+/** cg_prepare_cgroup
+ * Process the selected rule. Prepare the cgroup structure which can be
+ * used to add the task to destination cgroup.
+ *
+ *
+ *  returns 0 on success.
+ */
+static int cg_prepare_cgroup(struct cgroup *cgroup, pid_t pid,
+                                       const char *dest,
+                                       char *controllers[])
+{
+       int ret = 0, i;
+       char *controller;
+       struct cgroup_controller *cptr;
+
+       /* Fill in cgroup details.  */
+       dbg("Will move pid %d to cgroup '%s'\n", pid, dest);
+
+       strcpy(cgroup->name, dest);
+
+       /* Scan all the controllers */
+       for (i = 0; i < CG_CONTROLLER_MAX; i++) {
+               if (!controllers[i])
+                       return 0;
+               controller = controllers[i];
+
+               /* If first string is "*" that means all the mounted
+                * controllers. */
+               if (strcmp(controller, "*") == 0) {
+                       pthread_rwlock_rdlock(&cg_mount_table_lock);
+                       for (i = 0; i < CG_CONTROLLER_MAX &&
+                               cg_mount_table[i].name[0] != '\0'; i++) {
+                               dbg("Adding controller %s\n",
+                                       cg_mount_table[i].name);
+                               cptr = cgroup_add_controller(cgroup,
+                                               cg_mount_table[i].name);
+                               if (!cptr) {
+                                       dbg("Adding controller '%s' failed\n",
+                                               cg_mount_table[i].name);
+                                       pthread_rwlock_unlock(&cg_mount_table_lock);
+                                       return ECGROUPNOTALLOWED;
+                               }
+                       }
+                       pthread_rwlock_unlock(&cg_mount_table_lock);
+                       return ret;
+               }
+
+               /* it is individual controller names and not "*" */
+               dbg("Adding controller %s\n", controller);
+               cptr = cgroup_add_controller(cgroup, controller);
+               if (!cptr) {
+                       dbg("Adding controller '%s' failed\n", controller);
+                       return ECGROUPNOTALLOWED;
+               }
+       }
+
+       return ret;
+}
+
+/**
+ * This function takes a string which has got list of controllers separated
+ * by commas and it converts it to an array of string pointer where each
+ * string contains name of one controller.
+ *
+ * returns 0 on success.
+ */
+static int cg_prepare_controller_array(char *cstr, char *controllers[])
+{
+       int j = 0;
+       char *temp, *saveptr = NULL;
+
+       do {
+               if (j == 0)
+                       temp = strtok_r(cstr, ",", &saveptr);
+               else
+                       temp = strtok_r(NULL, ",", &saveptr);
+
+               if (temp) {
+                       controllers[j] = (char *) malloc(strlen(temp) + 1);
+                       if (!controllers[j])
+                               return ECGOTHER;
+                       else
+                               strcpy(controllers[j], temp);
+               }
+               j++;
+       } while (temp);
+       return 0;
+}
+
+
+static void cg_free_controller_array(char *controllers[])
+{
+       int j = 0;
+
+       /* Free up temporary controllers array */
+       for (j = 0; j < CG_CONTROLLER_MAX; j++) {
+               if (!controllers[j])
+                       break;
+               free(controllers[j]);
+               controllers[j] = 0;
+       }
+}
+
+/** cg_parse_rules_config_file
+ * parses the config file and determines the rule application based on
+ * uid and gid.
+ *
+ *  returns 0 on success.
+ */
+static int cg_parse_rules_config_file(struct cgroup_rules_data *cgrldp,
+                                               struct cgroup *cgroups[])
+{
+       FILE *fp;
+       char buf[FILENAME_MAX];
+       char user[FILENAME_MAX];
+       char dest[PATH_MAX];
+       char buf_ctrl[FILENAME_MAX];
+       char *controllers[CG_CONTROLLER_MAX];
+       struct cgroup *cgroup;
+       int cgindex = 0, match_uid = 0, match_gid = 0, i, ret = 0;
+
+       memset(controllers, 0, CG_CONTROLLER_MAX);
+       memset(buf_ctrl, 0, FILENAME_MAX);
+
+       fp = fopen(CGRULES_CONF_FILE, "r");
+       if (fp == NULL) {
+               dbg("Open of file %s failed: %s", CGRULES_CONF_FILE,
+                       strerror(errno));
+               return ECGOTHER;
+       }
+
+       /* In case of multi line rule, we need to prepare multiple
+        * cgroups structure. That's why caller has passed an array
+        * of cgroup pointers. Keep a index of current empty cgroup
+        * structure which can be passed to cg_prepare_cgroup.
+        */
+       cgindex = 0;
+
+       /* Parse file */
+       while (fgets(buf, FILENAME_MAX, fp) != NULL) {
+               char *tptr, *line;
+               struct group *group;
+
+               line = buf;
+               /* skip the leading white space */
+               while (*line && isspace(*line))
+                       line++;
+
+               /* Rip off the comments */
+               tptr = strchr(line, '#');
+               if (tptr)
+                       *tptr = '\0';
+
+               /* Rip off the newline char */
+               tptr = strchr(line, '\n');
+               if (tptr)
+                       *tptr = '\0';
+
+               /* Anything left ? */
+               if (!strlen(line))
+                       continue;
+
+               user[0] = dest[0] = buf_ctrl[0] = '\0';
+
+               i = sscanf(line, "%s%s%s", user, buf_ctrl, dest);
+               dbg("scanned line[%d]: user[%s], controllers[%s],"
+                       " dest[%s]\n", i, user, buf_ctrl, dest);
+
+               /* If we encounter a rule which does not begin with %,
+                * and either match_uid or match_gid is set, that means
+                * we have processed one rule and if that rule was muti
+                * line then it has ended. Return back. Remember, we execute
+                * only first matching rule (either single line or multiline)
+                */
+               if ((match_uid || match_gid) && strcmp(user, "%")) {
+                       match_uid = 0;
+                       match_gid = 0;
+                       fclose(fp);
+                       return 0;
+               }
+
+               if (i == 3) {
+                       /* a complete line */
+                       if (((strcmp(cgrldp->pw->pw_name, user) == 0) ||
+                               (strcmp(user, "*") == 0)) ||
+                               (match_uid && !strcmp(user, "%"))) {
+                               match_uid = 1;
+
+                               cgroup = (struct cgroup *)
+                                       calloc(1, sizeof(struct cgroup));
+                               if (!cgroup) {
+                                       ret = ECGOTHER;
+                                       goto out;
+                               }
+                               cgroups[cgindex] = cgroup;
+                               cgindex++;
+
+                               ret = cg_prepare_controller_array(buf_ctrl,
+                                               controllers);
+                               if (ret)
+                                       goto out;
+                               ret = cg_prepare_cgroup(cgroup, cgrldp->pid,
+                                                       dest, controllers);
+                               if (ret)
+                                       goto out;
+
+                               cg_free_controller_array(controllers);
+                       } else if (user[0] == '@' ||
+                                       (match_gid && !strcmp(user, "%"))) {
+                               errno = 0;
+                               group = getgrgid(cgrldp->gid);
+                               if (!group) {
+                                       dbg("getgrgid() failed for gid %d\n",
+                                               cgrldp->pw->pw_gid);
+                                       ret = ECGOTHER;
+                                       goto out;
+                               }
+                               if ((strcmp(group->gr_name, user+1) == 0)
+                                       || (strcmp(user, "*") == 0) ||
+                                       (match_gid && !strcmp(user, "%"))) {
+                                       match_gid = 1;
+
+                                       cgroup = (struct cgroup *)
+                                               calloc(1, sizeof(struct cgroup));
+                                       if (!cgroup) {
+                                               ret = ECGOTHER;
+                                               goto out;
+                                       }
+                                       cgroups[cgindex] = cgroup;
+                                       cgindex++;
+                                       ret = cg_prepare_controller_array(buf_ctrl, controllers);
+                                       if (ret)
+                                               goto out;
+                                       ret = cg_prepare_cgroup(cgroup,
+                                                       cgrldp->pid,
+                                                       dest, controllers);
+                                       if (ret)
+                                               goto out;
+                                       cg_free_controller_array(controllers);
+                               }
+                       }
+               } else {
+                       dbg("invalid line '%s' - skipped", line);
+               }
+       }
+       /* If we are here, then none of the rule matched for the task */
+       dbg("No rules matched for task with pid %d\n", cgrldp->pid);
+       fclose(fp);
+       return 0;
+out:
+       fclose(fp);
+       cg_free_controller_array(controllers);
+       /* Free the cgroups allocated so far */
+       for (i = 0; (i < CG_CONTROLLER_MAX) && cgroups[i]; i++)
+               cgroup_free(&cgroups[i]);
+       return ret;
+}
+
+/** cgroup_change_cgroup_uid_gid changes the cgroup of a program based on
+ * rules in the config file. Rules are search based on uid and gid
+ * and the pid is placed into destination group (if permissions are
+ * there).
+ *
+ *  returns 0 on success.
+ */
+int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid)
+{
+       int ret = 0, i;
+       struct passwd *pw;
+       struct cgroup_rules_data cgrld, *cgrldp = &cgrld;
+       struct cgroup *cgroups[CG_CONTROLLER_MAX];
+
+       if (!cgroup_initialized) {
+               dbg("libcgroup is not initialized\n");
+               return ECGROUPNOTINITIALIZED;
+       }
+       memset(cgrldp, 0, sizeof(struct cgroup_rules_data));
+       memset(cgroups, 0, CG_CONTROLLER_MAX);
+
+       pw = getpwuid(uid);
+       if (!pw) {
+               dbg("Could not retrieve the credentials of user"
+                       "with uid %d\n", uid);
+               return ECGOTHER;
+       }
+       cgrldp->pw = pw;
+       cgrldp->pid = pid;
+       cgrldp->gid = gid;
+
+       /* Parse config file */
+       ret = cg_parse_rules_config_file(cgrldp, cgroups);
+       if (ret) {
+               dbg("Parsing of %s failed\n", CGRULES_CONF_FILE);
+               return ret;
+       }
+
+       /* Add task to cgroups */
+       for (i = 0; (i < CG_CONTROLLER_MAX) && cgroups[i]; i++) {
+               ret = cgroup_attach_task_pid(cgroups[i], cgrldp->pid);
+               if (ret) {
+                       dbg("cgroup_attach_task_pid failed:%d\n", ret);
+                       goto out;
+               }
+       }
+out:
+       /* Free the cgroups */
+       for (i = 0; (i < CG_CONTROLLER_MAX) && cgroups[i]; i++)
+               cgroup_free(&cgroups[i]);
+       return ret;
+}
+
+/** cgroup_change_cgroup_path changes the cgroup of a program based on
+ * the path provided by user. In this case user already knows in which
+ * cgroup the task should go and no rules file have to be parsed.
+ *
+ *  returns 0 on success.
+ */
+int cgroup_change_cgroup_path(char *dest, pid_t pid, char *controllers[])
+{
+       int ret;
+       struct cgroup cgroup;
+
+       if (!cgroup_initialized) {
+               dbg("libcgroup is not initialized\n");
+               return ECGROUPNOTINITIALIZED;
+       }
+       memset(&cgroup, 0, sizeof(struct cgroup));
+
+       ret = cg_prepare_cgroup(&cgroup, pid, dest, controllers);
+       if (ret)
+               return ret;
+       /* Add task to cgroup */
+       ret = cgroup_attach_task_pid(&cgroup, pid);
+       if (ret) {
+               dbg("cgroup_attach_task_pid failed:%d\n", ret);
+               return ret;
+       }
+       return 0;
+}
index ce2c7c9159e8034c45fa06e9c1d6d1c0d72b60f0..c01dfa90c9893ec0ffc3f78bf6834ebf03d09a16 100644 (file)
@@ -19,6 +19,9 @@
 __BEGIN_DECLS
 
 #include <libcgroup.h>
+#include <limits.h>
+
+#define CGRULES_CONF_FILE       "/etc/cgrules.conf"
 
 struct control_value {
        char name[FILENAME_MAX];
@@ -47,6 +50,15 @@ struct cg_mount_table_s {
        char path[FILENAME_MAX];
 };
 
+struct cgroup_rules_data {
+       pid_t   pid; /* pid of the process which needs to change group */
+
+       /* Details of user under consideration for destination cgroup */
+       struct passwd   *pw;
+       /* Gid of the process */
+       gid_t   gid;
+};
+
 __END_DECLS
 
 #endif
index fe1375871ac7ed1817eb0c003b2b4636304f0419..ae73a2215291e8f167cf8d251e1b866443020716 100644 (file)
@@ -168,6 +168,10 @@ int cgroup_delete_cgroup(struct cgroup *cgroup, int ignore_migration);
 int cgroup_attach_task_pid(struct cgroup *cgroup, pid_t tid);
 struct cgroup *cgroup_get_cgroup(struct cgroup *cgroup);
 
+/* Changes the cgroup of calling application based on rule file */
+int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid);
+int cgroup_change_cgroup_path(char *path, pid_t pid, char *controllers[]);
+
 /* The wrappers for filling libcg structures */
 
 struct cgroup *cgroup_new_cgroup(const char *name);
diff --git a/samples/cgrules.conf b/samples/cgrules.conf
new file mode 100644 (file)
index 0000000..1844633
--- /dev/null
@@ -0,0 +1,45 @@
+# /etc/cgrules.conf
+#
+#Each line describes a rule for a user in the form:
+#
+#<user>   <controllers>                <destination>
+#
+#Where:
+#<user> can be:
+#        - an user name
+#        - a group name, with @group syntax
+#        - the wildcard *, for any user or group.
+#        - The %, which is equivalent to "ditto". This is useful for
+#          multiline rules where different cgroups need to be specified
+#          for various hierarchies for a single user.
+#
+# <controller> can be:
+#       - comma separated controller names (no spaces)
+#       - * (for all mounted controllers)
+#
+# <destination> can be:
+#       - path with-in the controller hierarchy (ex. pgrp1/gid1/uid1)
+#
+# Note:
+# - It currently has rules only based on uids and gids.
+#
+# - Don't put overlapping rules. First rule which matches the criteria
+#   will be executed.
+#
+# - Multiline rules can be specified for specifying different cgroups
+#   for multiple hierarchies. In the example below, user "peter" has
+#   specified 2 line rule. First line says put peter's task in test1/
+#   dir for "cpu" controller and second line says put peter's tasks in
+#   test2/ dir for memory controller. Make a note of "%" sign in second line.
+#   This is an indication that it is continuation of previous rule.
+#
+#
+#<user>        <controllers>   <destination>
+#
+#john          cpu             usergroup/faculty/john/
+#@student      cpu,memory      usergroup/student/
+#peter        cpu              test1/
+#%            memory           test2/
+#@root         *               admingroup/
+#*             *               default/
+# End of file