summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJan Safranek <jsafrane@redhat.com>2009-03-13 15:16:19 +0100
committerJan Safranek <jsafrane@redhat.com>2009-03-26 09:34:18 +0100
commitf8e05fc8c129a13fed256b03a23537ef94c77152 (patch)
treec64ea7d9f7daeefd307feec1bcb90ea5e3e6d600 /src
parent04bb98f8bd9751dd8a514b0e3a6c4862ceabeae9 (diff)
downloadlibcg-f8e05fc8c129a13fed256b03a23537ef94c77152.tar.gz
libcg-f8e05fc8c129a13fed256b03a23537ef94c77152.tar.xz
libcg-f8e05fc8c129a13fed256b03a23537ef94c77152.zip
Distribute files to various subdirectories
Signed-off-by: Jan Safranek <jsafrane@redhat.com>
Diffstat (limited to 'src')
-rw-r--r--src/api.c2320
-rw-r--r--src/config.c496
-rw-r--r--src/daemon/cgrulesengd.c793
-rw-r--r--src/daemon/cgrulesengd.h127
-rw-r--r--src/lex.l34
-rw-r--r--src/libcgroup-internal.h105
-rw-r--r--src/libcgroup.map56
-rw-r--r--src/pam/pam_cgroup.c162
-rw-r--r--src/parse.y269
-rw-r--r--src/tools/cgclassify.c219
-rw-r--r--src/tools/cgconfig.c74
-rw-r--r--src/tools/cgexec.c122
-rw-r--r--src/tools/tools-common.c88
-rw-r--r--src/tools/tools-common.h54
-rw-r--r--src/wrapper.c557
15 files changed, 5476 insertions, 0 deletions
diff --git a/src/api.c b/src/api.c
new file mode 100644
index 0000000..28365b4
--- /dev/null
+++ b/src/api.c
@@ -0,0 +1,2320 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Author: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ * Author: Balbir Singh <balbir@linux.vnet.ibm.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * TODOs:
+ * 1. Convert comments to Docbook style.
+ * 2. Add more APIs for the control groups.
+ * 3. Handle the configuration related APIs.
+ * 4. Error handling.
+ *
+ * Code initiated and designed by Dhaval Giani. All faults are most likely
+ * his mistake.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <mntent.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#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>
+#include <libgen.h>
+#include <assert.h>
+
+#ifndef PACKAGE_VERSION
+#define PACKAGE_VERSION 0.01
+#endif
+
+#define VERSION(ver) #ver
+
+/*
+ * The errno which happend the last time (have to be thread specific)
+ */
+__thread int last_errno;
+
+#define MAXLEN 256
+
+/* the value have to be thread specific */
+__thread char errtext[MAXLEN];
+
+/*
+ * Remember to bump this up for major API changes.
+ */
+const static char cg_version[] = VERSION(PACKAGE_VERSION);
+
+struct cg_mount_table_s cg_mount_table[CG_CONTROLLER_MAX];
+static pthread_rwlock_t cg_mount_table_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+/* Check if cgroup_init has been called or not. */
+static int cgroup_initialized;
+
+/* Check if the rules cache has been loaded or not. */
+static bool cgroup_rules_loaded;
+
+/* List of configuration rules */
+static struct cgroup_rule_list rl;
+
+/* Temporary list of configuration rules (for non-cache apps) */
+static struct cgroup_rule_list trl;
+
+/* Lock for the list of rules (rl) */
+static pthread_rwlock_t rl_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+char *cgroup_strerror_codes[] = {
+ "Cgroup is not compiled in",
+ "Cgroup is not mounted",
+ "Cgroup does not exist",
+ "Cgroup has not been created",
+ "Cgroup one of the needed subsystems is not mounted",
+ "Cgroup, request came in from non owner",
+ "Cgroup controllers controllers are bound to different mount points",
+ "Cgroup, operation not allowed",
+ "Cgroup value set exceeds maximum",
+ "Cgroup controller already exists",
+ "Cgroup value already exists",
+ "Cgroup invalid operation",
+ "Cgroup, creation of controller failed",
+ "Cgroup operation failed",
+ "Cgroup not initialized",
+ "Cgroup trying to set value for control that does not exist",
+ "Cgroup generic error, see errno",
+ "Cgroup values are not equal",
+ "Cgroup controllers are different",
+ "Cgroup parsing failed",
+ "Cgroup, rules file does not exist",
+ "Cgroup mounting failed",
+ "The config file can not be opend",
+ "End of File or iterator",
+};
+
+static int cg_chown_file(FTS *fts, FTSENT *ent, uid_t owner, gid_t group)
+{
+ int ret = 0;
+ const char *filename = fts->fts_path;
+ cgroup_dbg("seeing file %s\n", filename);
+ switch (ent->fts_info) {
+ case FTS_ERR:
+ errno = ent->fts_errno;
+ break;
+ case FTS_D:
+ case FTS_DC:
+ case FTS_NSOK:
+ case FTS_NS:
+ case FTS_DNR:
+ case FTS_DP:
+ ret = chown(filename, owner, group);
+ if (ret)
+ goto fail_chown;
+ ret = chmod(filename, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP |
+ S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH);
+ break;
+ case FTS_F:
+ case FTS_DEFAULT:
+ ret = chown(filename, owner, group);
+ if (ret)
+ goto fail_chown;
+ ret = chmod(filename, S_IRUSR | S_IWUSR | S_IRGRP |
+ S_IWGRP | S_IROTH);
+ break;
+ }
+fail_chown:
+ if (ret < 0) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ }
+ return ret;
+}
+
+/*
+ * TODO: Need to decide a better place to put this function.
+ */
+static int cg_chown_recursive(char **path, uid_t owner, gid_t group)
+{
+ int ret = 0;
+ cgroup_dbg("path is %s\n", *path);
+ FTS *fts = fts_open(path, FTS_PHYSICAL | FTS_NOCHDIR |
+ FTS_NOSTAT, NULL);
+ while (1) {
+ FTSENT *ent;
+ ent = fts_read(fts);
+ if (!ent) {
+ cgroup_dbg("fts_read failed\n");
+ break;
+ }
+ ret = cg_chown_file(fts, ent, owner, group);
+ }
+ fts_close(fts);
+ return ret;
+}
+
+static int cgroup_test_subsys_mounted(const char *name)
+{
+ int i;
+
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+
+ for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
+ if (strncmp(cg_mount_table[i].name, name,
+ sizeof(cg_mount_table[i].name)) == 0) {
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return 1;
+ }
+ }
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return 0;
+}
+
+/**
+ * Free a single cgroup_rule struct.
+ * @param r The rule to free from memory
+ */
+static void cgroup_free_rule(struct cgroup_rule *r)
+{
+ /* Loop variable */
+ int i = 0;
+
+ /* Make sure our rule is not NULL, first. */
+ if (!r) {
+ cgroup_dbg("Warning: Attempted to free NULL rule.\n");
+ return;
+ }
+
+ /* We must free any used controller strings, too. */
+ for(i = 0; i < MAX_MNT_ELEMENTS; i++) {
+ if (r->controllers[i])
+ free(r->controllers[i]);
+ }
+
+ free(r);
+}
+
+/**
+ * Free a list of cgroup_rule structs. If rl is the main list of rules,
+ * the lock must be taken for writing before calling this function!
+ * @param rl Pointer to the list of rules to free from memory
+ */
+static void cgroup_free_rule_list(struct cgroup_rule_list *rl)
+{
+ /* Temporary pointer */
+ struct cgroup_rule *tmp = NULL;
+
+ /* Make sure we're not freeing NULL memory! */
+ if (!(rl->head)) {
+ cgroup_dbg("Warning: Attempted to free NULL list.\n");
+ return;
+ }
+
+ while (rl->head) {
+ tmp = rl->head;
+ rl->head = tmp->next;
+ cgroup_free_rule(tmp);
+ }
+
+ /* Don't leave wild pointers around! */
+ rl->head = NULL;
+ rl->tail = NULL;
+}
+
+/**
+ * Parse the configuration file that maps UID/GIDs to cgroups. If ever the
+ * configuration file is modified, applications should call this function to
+ * load the new configuration rules. The function caller is responsible for
+ * calling free() on each rule in the list.
+ *
+ * The cache parameter alters the behavior of this function. If true, this
+ * function will read the entire configuration file and store the results in
+ * rl (global rules list). If false, this function will only parse until it
+ * finds a rule matching the given UID or GID. It will store this rule in rl,
+ * as well as any children rules (rules that begin with a %) that it has.
+ *
+ * This function is NOT thread safe!
+ * @param cache True to cache rules, else false
+ * @param muid If cache is false, the UID to match against
+ * @param mgid If cache is false, the GID to match against
+ * @return 0 on success, -1 if no cache and match found, > 0 on error.
+ * TODO: Make this function thread safe!
+ */
+static int cgroup_parse_rules(bool cache, uid_t muid, gid_t mgid)
+{
+ /* File descriptor for the configuration file */
+ FILE *fp = NULL;
+
+ /* Buffer to store the line we're working on */
+ char *buff = NULL;
+
+ /* Iterator for the line we're working on */
+ char *itr = NULL;
+
+ /* Pointer to the list that we're using */
+ struct cgroup_rule_list *lst = NULL;
+
+ /* Rule to add to the list */
+ struct cgroup_rule *newrule = NULL;
+
+ /* Structure to get GID from group name */
+ struct group *grp = NULL;
+
+ /* Structure to get UID from user name */
+ struct passwd *pwd = NULL;
+
+ /* Temporary storage for a configuration rule */
+ char user[LOGIN_NAME_MAX] = { '\0' };
+ char controllers[CG_CONTROLLER_MAX] = { '\0' };
+ char destination[FILENAME_MAX] = { '\0' };
+ uid_t uid = CGRULE_INVALID;
+ gid_t gid = CGRULE_INVALID;
+
+ /* The current line number */
+ unsigned int linenum = 0;
+
+ /* Did we skip the previous line? */
+ bool skipped = false;
+
+ /* Have we found a matching rule (non-cache mode)? */
+ bool matched = false;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* Temporary buffer for strtok() */
+ char *stok_buff = NULL;
+
+ /* Loop variable. */
+ int i = 0;
+
+ /* Open the configuration file. */
+ pthread_rwlock_wrlock(&rl_lock);
+ fp = fopen(CGRULES_CONF_FILE, "r");
+ if (!fp) {
+ cgroup_dbg("Failed to open configuration file %s with"
+ " error: %s\n", CGRULES_CONF_FILE,
+ strerror(errno));
+ last_errno = errno;
+ goto finish;
+ }
+
+ buff = calloc(CGROUP_RULE_MAXLINE, sizeof(char));
+ if (!buff) {
+ cgroup_dbg("Out of memory? Error: %s\n", strerror(errno));
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto close_unlock;
+ }
+
+ /* Determine which list we're using. */
+ if (cache)
+ lst = &rl;
+ else
+ lst = &trl;
+
+ /* If our list already exists, clean it. */
+ if (lst->head)
+ cgroup_free_rule_list(lst);
+
+ /* Now, parse the configuration file one line at a time. */
+ cgroup_dbg("Parsing configuration file.\n");
+ while ((itr = fgets(buff, CGROUP_RULE_MAXLINE, fp)) != NULL) {
+ linenum++;
+
+ /* We ignore anything after a # sign as comments. */
+ if ((itr = strchr(buff, '#')))
+ *itr = '\0';
+
+ /* We also need to remove the newline character. */
+ if ((itr = strchr(buff, '\n')))
+ *itr = '\0';
+
+ /* Now, skip any leading tabs and spaces. */
+ itr = buff;
+ while (itr && isblank(*itr))
+ itr++;
+
+ /* If there's nothing left, we can ignore this line. */
+ if (!strlen(itr))
+ continue;
+
+ /*
+ * If we skipped the last rule and this rule is a continuation
+ * of it (begins with %), then we should skip this rule too.
+ */
+ if (skipped && *itr == '%') {
+ cgroup_dbg("Warning: Skipped child of invalid rule,"
+ " line %d.\n", linenum);
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ continue;
+ }
+
+ /*
+ * If there is something left, it should be a rule. Otherwise,
+ * there's an error in the configuration file.
+ */
+ skipped = false;
+ memset(user, '\0', LOGIN_NAME_MAX);
+ memset(controllers, '\0', CG_CONTROLLER_MAX);
+ memset(destination, '\0', FILENAME_MAX);
+ i = sscanf(itr, "%s%s%s", user, controllers, destination);
+ if (i != 3) {
+ cgroup_dbg("Failed to parse configuration file on"
+ " line %d.\n", linenum);
+ goto parsefail;
+ }
+
+ /*
+ * Next, check the user/group. If it's a % sign, then we
+ * are continuing another rule and UID/GID should not be
+ * reset. If it's a @, we're dealing with a GID rule. If
+ * it's a *, then we do not need to do a lookup because the
+ * rule always applies (it's a wildcard). If we're using
+ * non-cache mode and we've found a matching rule, we only
+ * continue to parse if we're looking at a child rule.
+ */
+ if ((!cache) && matched && (strncmp(user, "%", 1) != 0)) {
+ /* If we make it here, we finished (non-cache). */
+ cgroup_dbg("Parsing of configuration file"
+ " complete.\n\n");
+ ret = -1;
+ goto cleanup;
+ }
+ if (strncmp(user, "@", 1) == 0) {
+ /* New GID rule. */
+ itr = &(user[1]);
+ if ((grp = getgrnam(itr))) {
+ uid = CGRULE_INVALID;
+ gid = grp->gr_gid;
+ } else {
+ cgroup_dbg("Warning: Entry for %s not"
+ "found. Skipping rule on line"
+ " %d.\n", itr, linenum);
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ skipped = true;
+ continue;
+ }
+ } else if (strncmp(user, "*", 1) == 0) {
+ /* Special wildcard rule. */
+ uid = CGRULE_WILD;
+ gid = CGRULE_WILD;
+ } else if (*itr != '%') {
+ /* New UID rule. */
+ if ((pwd = getpwnam(user))) {
+ uid = pwd->pw_uid;
+ gid = CGRULE_INVALID;
+ } else {
+ cgroup_dbg("Warning: Entry for %s not"
+ "found. Skipping rule on line"
+ " %d.\n", user, linenum);
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ skipped = true;
+ continue;
+ }
+ } /* Else, we're continuing another rule (UID/GID are okay). */
+
+ /*
+ * If we are not caching rules, then we need to check for a
+ * match before doing anything else. We consider four cases:
+ * The UID matches, the GID matches, the UID is a member of the
+ * GID, or we're looking at the wildcard rule, which always
+ * matches. If none of these are true, we simply continue to
+ * the next line in the file.
+ */
+ if (grp && muid != CGRULE_INVALID) {
+ pwd = getpwuid(muid);
+ for (i = 0; grp->gr_mem[i]; i++) {
+ if (!(strcmp(pwd->pw_name, grp->gr_mem[i])))
+ matched = true;
+ }
+ }
+
+ if (uid == muid || gid == mgid || uid == CGRULE_WILD) {
+ matched = true;
+ }
+
+ if (!cache && !matched)
+ continue;
+
+ /*
+ * Now, we're either caching rules or we found a match. Either
+ * way, copy everything into a new rule and push it into the
+ * list.
+ */
+ newrule = calloc(1, sizeof(struct cgroup_rule));
+ if (!newrule) {
+ cgroup_dbg("Out of memory? Error: %s\n",
+ strerror(errno));
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto cleanup;
+ }
+
+ newrule->uid = uid;
+ newrule->gid = gid;
+ strncpy(newrule->name, user, strlen(user));
+ strncpy(newrule->destination, destination, strlen(destination));
+ newrule->next = NULL;
+
+ /* Parse the controller list, and add that to newrule too. */
+ stok_buff = strtok(controllers, ",");
+ if (!stok_buff) {
+ cgroup_dbg("Failed to parse controllers on line"
+ " %d\n", linenum);
+ goto destroyrule;
+ }
+
+ i = 0;
+ do {
+ if (i >= MAX_MNT_ELEMENTS) {
+ cgroup_dbg("Too many controllers listed"
+ " on line %d\n", linenum);
+ goto destroyrule;
+ }
+
+ newrule->controllers[i] = strndup(stok_buff,
+ strlen(stok_buff) + 1);
+ if (!(newrule->controllers[i])) {
+ cgroup_dbg("Out of memory? Error was: %s\n",
+ strerror(errno));
+ goto destroyrule;
+ }
+ i++;
+ } while ((stok_buff = strtok(NULL, ",")));
+
+ /* Now, push the rule. */
+ if (lst->head == NULL) {
+ lst->head = newrule;
+ lst->tail = newrule;
+ } else {
+ lst->tail->next = newrule;
+ lst->tail = newrule;
+ }
+
+ cgroup_dbg("Added rule %s (UID: %d, GID: %d) -> %s for"
+ " controllers:", lst->tail->name, lst->tail->uid,
+ lst->tail->gid, lst->tail->destination);
+ for (i = 0; lst->tail->controllers[i]; i++) {
+ cgroup_dbg(" %s", lst->tail->controllers[i]);
+ }
+ cgroup_dbg("\n");
+
+ /* Finally, clear the buffer. */
+ memset(buff, '\0', CGROUP_RULE_MAXLINE);
+ grp = NULL;
+ pwd = NULL;
+ }
+
+ /* If we make it here, there were no errors. */
+ cgroup_dbg("Parsing of configuration file complete.\n\n");
+ ret = (matched && !cache) ? -1 : 0;
+ goto cleanup;
+
+destroyrule:
+ cgroup_free_rule(newrule);
+
+parsefail:
+ ret = ECGROUPPARSEFAIL;
+
+cleanup:
+ free(buff);
+
+close_unlock:
+ fclose(fp);
+ pthread_rwlock_unlock(&rl_lock);
+finish:
+ return ret;
+}
+
+/**
+ * cgroup_init(), initializes the MOUNT_POINT.
+ *
+ * This code is theoretically thread safe now. Its not really tested
+ * so it can blow up. If does for you, please let us know with your
+ * test case and we can really make it thread safe.
+ *
+ */
+int cgroup_init()
+{
+ FILE *proc_mount = NULL;
+ struct mntent *ent = NULL;
+ struct mntent *temp_ent = NULL;
+ int found_mnt = 0;
+ int ret = 0;
+ static char *controllers[CG_CONTROLLER_MAX];
+ FILE *proc_cgroup = NULL;
+ char subsys_name[FILENAME_MAX];
+ int hierarchy, num_cgroups, enabled;
+ int i=0;
+ char *mntopt = NULL;
+ int err;
+ char *buf = NULL;
+ char mntent_buffer[4 * FILENAME_MAX];
+ char *strtok_buffer = NULL;
+
+ pthread_rwlock_wrlock(&cg_mount_table_lock);
+
+ proc_cgroup = fopen("/proc/cgroups", "r");
+
+ if (!proc_cgroup) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+
+ /*
+ * The first line of the file has stuff we are not interested in.
+ * So just read it and discard the information.
+ *
+ * XX: fix the size for fgets
+ */
+ buf = malloc(FILENAME_MAX);
+ if (!buf) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+ buf = fgets(buf, FILENAME_MAX, proc_cgroup);
+ if (!buf) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+ free(buf);
+
+ while (!feof(proc_cgroup)) {
+ err = fscanf(proc_cgroup, "%s %d %d %d", subsys_name,
+ &hierarchy, &num_cgroups, &enabled);
+ if (err < 0)
+ break;
+ controllers[i] = strdup(subsys_name);
+ i++;
+ }
+ controllers[i] = NULL;
+
+ proc_mount = fopen("/proc/mounts", "r");
+ if (proc_mount == NULL) {
+ ret = ECGFAIL;
+ goto unlock_exit;
+ }
+
+ temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
+
+ if (!temp_ent) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto unlock_exit;
+ }
+
+ while ((ent = getmntent_r(proc_mount, temp_ent,
+ mntent_buffer,
+ sizeof(mntent_buffer))) != NULL) {
+ if (!strcmp(ent->mnt_type, "cgroup")) {
+ for (i = 0; controllers[i] != NULL; i++) {
+ mntopt = hasmntopt(ent, controllers[i]);
+
+ if (!mntopt)
+ continue;
+
+ mntopt = strtok_r(mntopt, ",", &strtok_buffer);
+
+ if (strcmp(mntopt, controllers[i]) == 0) {
+ cgroup_dbg("matched %s:%s\n", mntopt,
+ controllers[i]);
+ strcpy(cg_mount_table[found_mnt].name,
+ controllers[i]);
+ strcpy(cg_mount_table[found_mnt].path,
+ ent->mnt_dir);
+ cgroup_dbg("Found cgroup option %s, "
+ " count %d\n",
+ ent->mnt_opts, found_mnt);
+ found_mnt++;
+ }
+ }
+ }
+ }
+
+ free(temp_ent);
+
+ if (!found_mnt) {
+ cg_mount_table[0].name[0] = '\0';
+ ret = ECGROUPNOTMOUNTED;
+ goto unlock_exit;
+ }
+
+ found_mnt++;
+ cg_mount_table[found_mnt].name[0] = '\0';
+
+ cgroup_initialized = 1;
+
+unlock_exit:
+ if (proc_cgroup)
+ fclose(proc_cgroup);
+
+ if (proc_mount)
+ fclose(proc_mount);
+
+ for (i = 0; controllers[i]; i++) {
+ free(controllers[i]);
+ controllers[i] = NULL;
+ }
+
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+
+ return ret;
+}
+
+static int cg_test_mounted_fs()
+{
+ FILE *proc_mount = NULL;
+ struct mntent *ent = NULL;
+ struct mntent *temp_ent = NULL;
+ char mntent_buff[4 * FILENAME_MAX];
+ int ret = 1;
+
+ proc_mount = fopen("/proc/mounts", "r");
+ if (proc_mount == NULL) {
+ return 0;
+ }
+
+ temp_ent = (struct mntent *) malloc(sizeof(struct mntent));
+ if (!temp_ent) {
+ /* We just fail at the moment. */
+ return 0;
+ }
+
+ ent = getmntent_r(proc_mount, temp_ent, mntent_buff,
+ sizeof(mntent_buff));
+
+ if (!ent)
+ return 0;
+
+ while (strcmp(ent->mnt_type, "cgroup") !=0) {
+ ent = getmntent_r(proc_mount, temp_ent, mntent_buff,
+ sizeof(mntent_buff));
+ if (ent == NULL) {
+ ret = 0;
+ goto done;
+ }
+ }
+done:
+ fclose(proc_mount);
+ free(temp_ent);
+ return ret;
+}
+
+static inline pid_t cg_gettid()
+{
+ return syscall(__NR_gettid);
+}
+
+
+/* Call with cg_mount_table_lock taken */
+static char *cg_build_path_locked(char *name, char *path, char *type)
+{
+ int i;
+ for (i = 0; cg_mount_table[i].name[0] != '\0'; i++) {
+ /*
+ * XX: Change to snprintf once you figure what n should be
+ */
+ if (strcmp(cg_mount_table[i].name, type) == 0) {
+ sprintf(path, "%s/", cg_mount_table[i].path);
+ if (name) {
+ char *tmp;
+ tmp = strdup(path);
+ sprintf(path, "%s%s/", tmp, name);
+ free(tmp);
+ }
+ return path;
+ }
+ }
+ return NULL;
+}
+
+char *cg_build_path(char *name, char *path, char *type)
+{
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ path = cg_build_path_locked(name, path, type);
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+
+ return path;
+}
+
+/** cgroup_attach_task_pid is used to assign tasks to a cgroup.
+ * struct cgroup *cgroup: The cgroup to assign the thread to.
+ * pid_t tid: The thread to be assigned to the cgroup.
+ *
+ * returns 0 on success.
+ * returns ECGROUPNOTOWNER if the caller does not have access to the cgroup.
+ * returns ECGROUPNOTALLOWED for other causes of failure.
+ */
+int cgroup_attach_task_pid(struct cgroup *cgroup, pid_t tid)
+{
+ char path[FILENAME_MAX];
+ FILE *tasks = NULL;
+ int i, ret = 0;
+
+ if (!cgroup_initialized) {
+ cgroup_dbg("libcgroup is not initialized\n");
+ return ECGROUPNOTINITIALIZED;
+ }
+ if(!cgroup)
+ {
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ for(i = 0; i < CG_CONTROLLER_MAX &&
+ cg_mount_table[i].name[0]!='\0'; i++) {
+ if (!cg_build_path_locked(NULL, path,
+ cg_mount_table[i].name))
+ continue;
+ strncat(path, "/tasks", sizeof(path) - strlen(path));
+
+ tasks = fopen(path, "w");
+ if (!tasks) {
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ switch (errno) {
+ case EPERM:
+ return ECGROUPNOTOWNER;
+ case ENOENT:
+ return ECGROUPNOTEXIST;
+ default:
+ return ECGROUPNOTALLOWED;
+ }
+ }
+ ret = fprintf(tasks, "%d", tid);
+ if (ret < 0) {
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ ret = fflush(tasks);
+ if (ret) {
+ last_errno = errno;
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ return ECGOTHER;
+ }
+ fclose(tasks);
+ }
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ } else {
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name)) {
+ cgroup_dbg("subsystem %s is not mounted\n",
+ cgroup->controller[i]->name);
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+ }
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+
+ strncat(path, "/tasks", sizeof(path) - strlen(path));
+
+ tasks = fopen(path, "w");
+ if (!tasks) {
+ cgroup_dbg("fopen failed for %s:%s", path,
+ strerror(errno));
+
+ switch (errno) {
+ case EPERM:
+ return ECGROUPNOTOWNER;
+ case ENOENT:
+ return ECGROUPNOTEXIST;
+ default:
+ return ECGROUPNOTALLOWED;
+ }
+ }
+ ret = fprintf(tasks, "%d", tid);
+ if (ret < 0) {
+ last_errno = errno;
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ return ECGOTHER;
+ }
+ ret = fflush(tasks);
+ if (ret) {
+ last_errno = errno;
+ cgroup_dbg("Error writing tid %d to %s:%s\n",
+ tid, path, strerror(errno));
+ fclose(tasks);
+ return ECGOTHER;
+ }
+ fclose(tasks);
+ }
+ }
+ return 0;
+}
+
+/** cgroup_attach_task is used to attach the current thread to a cgroup.
+ * struct cgroup *cgroup: The cgroup to assign the current thread to.
+ *
+ * See cg_attach_task_pid for return values.
+ */
+int cgroup_attach_task(struct cgroup *cgroup)
+{
+ pid_t tid = cg_gettid();
+ int error;
+
+ error = cgroup_attach_task_pid(cgroup, tid);
+
+ return error;
+}
+
+/**
+ * cg_mkdir_p, emulate the mkdir -p command (recursively creating paths)
+ * @path: path to create
+ */
+static int cg_mkdir_p(const char *path)
+{
+ char *real_path = NULL;
+ char *wd = NULL;
+ int i = 0, j = 0;
+ char pos;
+ char *str = NULL;
+ int ret = 0;
+ char cwd[FILENAME_MAX];
+ char *buf = NULL;
+
+ buf = getcwd(cwd, FILENAME_MAX);
+
+ if (!buf) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ real_path = strdup(path);
+ if (!real_path) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ do {
+ while (real_path[j] != '\0' && real_path[j] != '/')
+ j++;
+ while (real_path[j] != '\0' && real_path[j] == '/')
+ j++;
+ if (i == j)
+ continue;
+ pos = real_path[j];
+ real_path[j] = '\0'; /* Temporarily overwrite "/" */
+ str = &real_path[i];
+ ret = mkdir(str, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ wd = strdup(str);
+ if (!wd) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ break;
+ }
+ real_path[j] = pos;
+ if (ret) {
+ switch (errno) {
+ case EEXIST:
+ ret = 0; /* Not fatal really */
+ break;
+ case EPERM:
+ ret = ECGROUPNOTOWNER;
+ free(wd);
+ goto done;
+ default:
+ ret = ECGROUPNOTALLOWED;
+ free(wd);
+ goto done;
+ }
+ }
+ i = j;
+ ret = chdir(wd);
+ if (ret) {
+ cgroup_dbg("could not chdir to child directory (%s)\n",
+ wd);
+ break;
+ }
+ free(wd);
+ } while (real_path[i]);
+
+ ret = chdir(buf);
+ if (ret) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ cgroup_dbg("could not go back to old directory (%s)\n", cwd);
+ }
+
+done:
+ free(real_path);
+ return ret;
+}
+
+/*
+ * create_control_group()
+ * This is the basic function used to create the control group. This function
+ * just makes the group. It does not set any permissions, or any control values.
+ * The argument path is the fully qualified path name to make it generic.
+ */
+static int cg_create_control_group(char *path)
+{
+ int error;
+ if (!cg_test_mounted_fs())
+ return ECGROUPNOTMOUNTED;
+ error = cg_mkdir_p(path);
+ return error;
+}
+
+/*
+ * set_control_value()
+ * This is the low level function for putting in a value in a control file.
+ * This function takes in the complete path and sets the value in val in that
+ * file.
+ */
+static int cg_set_control_value(char *path, char *val)
+{
+ FILE *control_file = NULL;
+ if (!cg_test_mounted_fs())
+ return ECGROUPNOTMOUNTED;
+
+ control_file = fopen(path, "r+");
+
+ if (!control_file) {
+ if (errno == EPERM) {
+ /*
+ * We need to set the correct error value, does the
+ * group exist but we don't have the subsystem
+ * mounted at that point, or is it that the group
+ * does not exist. So we check if the tasks file
+ * exist. Before that, we need to extract the path.
+ */
+ int len = strlen(path);
+
+ while (*(path+len) != '/')
+ len--;
+ *(path+len+1) = '\0';
+ strncat(path, "tasks", sizeof(path) - strlen(path));
+ control_file = fopen(path, "r");
+ if (!control_file) {
+ if (errno == ENOENT)
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+ fclose(control_file);
+ return ECGROUPNOTALLOWED;
+ }
+ return ECGROUPVALUENOTEXIST;
+ }
+
+ fprintf(control_file, "%s", val);
+ fclose(control_file);
+ return 0;
+}
+
+/** cgroup_modify_cgroup modifies the cgroup control files.
+ * struct cgroup *cgroup: The name will be the cgroup to be modified.
+ * The values will be the values to be modified, those not mentioned
+ * in the structure will not be modified.
+ *
+ * The uids cannot be modified yet.
+ *
+ * returns 0 on success.
+ *
+ */
+
+int cgroup_modify_cgroup(struct cgroup *cgroup)
+{
+ char *path, base[FILENAME_MAX];
+ int i;
+ int error;
+ int ret;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cgroup)
+ return ECGROUPNOTALLOWED;
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name)) {
+ cgroup_dbg("subsystem %s is not mounted\n",
+ cgroup->controller[i]->name);
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+ }
+
+ for (i = 0; i < cgroup->index; i++) {
+ int j;
+ if (!cg_build_path(cgroup->name, base,
+ cgroup->controller[i]->name))
+ continue;
+ for (j = 0; j < cgroup->controller[i]->index; j++) {
+ ret = asprintf(&path, "%s%s", base,
+ cgroup->controller[i]->values[j]->name);
+ if (ret < 0) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ error = cg_set_control_value(path,
+ cgroup->controller[i]->values[j]->value);
+ free(path);
+ path = NULL;
+ if (error)
+ goto err;
+ }
+ }
+ if (path)
+ free(path);
+ return 0;
+err:
+ if (path)
+ free(path);
+ return error;
+
+}
+
+/**
+ * @dst: Destination controller
+ * @src: Source controller from which values will be copied to dst
+ *
+ * Create a duplicate copy of values under the specified controller
+ */
+int cgroup_copy_controller_values(struct cgroup_controller *dst,
+ struct cgroup_controller *src)
+{
+ int i, ret = 0;
+
+ if (!dst || !src)
+ return ECGFAIL;
+
+ strncpy(dst->name, src->name, FILENAME_MAX);
+ for (i = 0; i < src->index; i++, dst->index++) {
+ struct control_value *src_val = src->values[i];
+ struct control_value *dst_val;
+
+ dst->values[i] = calloc(1, sizeof(struct control_value));
+ if (!dst->values[i]) {
+ ret = ECGFAIL;
+ goto err;
+ }
+
+ dst_val = dst->values[i];
+ strncpy(dst_val->value, src_val->value, CG_VALUE_MAX);
+ strncpy(dst_val->name, src_val->name, FILENAME_MAX);
+ }
+err:
+ return ret;
+}
+
+/**
+ * @dst: Destination control group
+ * @src: Source from which values will be copied to dst
+ *
+ * Create a duplicate copy of src in dst. This will be useful for those who
+ * that intend to create new instances based on an existing control group
+ */
+int cgroup_copy_cgroup(struct cgroup *dst, struct cgroup *src)
+{
+ int ret = 0, i;
+
+ if (!dst || !src)
+ return ECGROUPNOTEXIST;
+
+ /*
+ * Should we just use the restrict keyword instead?
+ */
+ if (dst == src)
+ return ECGFAIL;
+
+ cgroup_free_controllers(dst);
+
+ for (i = 0; i < src->index; i++, dst->index++) {
+ struct cgroup_controller *src_ctlr = src->controller[i];
+ struct cgroup_controller *dst_ctlr;
+
+ dst->controller[i] = calloc(1, sizeof(struct cgroup_controller));
+ if (!dst->controller[i]) {
+ ret = ECGFAIL;
+ goto err;
+ }
+
+ dst_ctlr = dst->controller[i];
+ ret = cgroup_copy_controller_values(dst_ctlr, src_ctlr);
+ if (ret)
+ goto err;
+ }
+err:
+ return ret;
+}
+
+/** cgroup_create_cgroup creates a new control group.
+ * struct cgroup *cgroup: The control group to be created
+ *
+ * returns 0 on success. We recommend calling cg_delete_cgroup
+ * if this routine fails. That should do the cleanup operation.
+ */
+int cgroup_create_cgroup(struct cgroup *cgroup, int ignore_ownership)
+{
+ char *fts_path[2];
+ char *base = NULL;
+ char *path = NULL;
+ int i, j, k;
+ int error = 0;
+ int retval = 0;
+ int ret;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cgroup)
+ return ECGROUPNOTALLOWED;
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name))
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+
+ fts_path[0] = (char *)malloc(FILENAME_MAX);
+ if (!fts_path[0]) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+ fts_path[1] = NULL;
+ path = fts_path[0];
+
+ /*
+ * XX: One important test to be done is to check, if you have multiple
+ * subsystems mounted at one point, all of them *have* be on the cgroup
+ * data structure. If not, we fail.
+ */
+ for (k = 0; k < cgroup->index; k++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[k]->name))
+ continue;
+
+ error = cg_create_control_group(path);
+ if (error)
+ goto err;
+
+ base = strdup(path);
+
+ if (!base) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+
+ if (!ignore_ownership) {
+ cgroup_dbg("Changing ownership of %s\n", fts_path[0]);
+ error = cg_chown_recursive(fts_path,
+ cgroup->control_uid, cgroup->control_gid);
+ }
+
+ if (error)
+ goto err;
+
+ for (j = 0; j < cgroup->controller[k]->index; j++) {
+ ret = snprintf(path, FILENAME_MAX, "%s%s", base,
+ cgroup->controller[k]->values[j]->name);
+ cgroup_dbg("setting %s to %s, error %d\n", path,
+ cgroup->controller[k]->values[j]->name, ret);
+ if (ret < 0 || ret >= FILENAME_MAX) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ error = cg_set_control_value(path,
+ cgroup->controller[k]->values[j]->value);
+ /*
+ * Should we undo, what we've done in the loops above?
+ * An error should not be treated as fatal, since we
+ * have several read-only files and several files that
+ * are only conditionally created in the child.
+ *
+ * A middle ground would be to track that there
+ * was an error and return that value.
+ */
+ if (error) {
+ retval = error;
+ continue;
+ }
+ }
+
+ if (!ignore_ownership) {
+ ret = snprintf(path, FILENAME_MAX, "%s/tasks", base);
+ if (ret < 0 || ret >= FILENAME_MAX) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ error = chown(path, cgroup->tasks_uid,
+ cgroup->tasks_gid);
+ if (error) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto err;
+ }
+ }
+ free(base);
+ base = NULL;
+ }
+
+err:
+ if (path)
+ free(path);
+ if (base)
+ free(base);
+ if (retval && !error)
+ error = retval;
+ return error;
+}
+
+/**
+ * Find the parent of the specified directory. It returns the parent (the
+ * parent is usually name/.. unless name is a mount point.
+ */
+char *cgroup_find_parent(char *name)
+{
+ char child[FILENAME_MAX];
+ char *parent = NULL;
+ struct stat stat_child, stat_parent;
+ char *type = NULL;
+ char *dir = NULL;
+
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ type = cg_mount_table[0].name;
+ if (!cg_build_path_locked(name, child, type)) {
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return NULL;
+ }
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+
+ cgroup_dbg("path is %s\n", child);
+ dir = dirname(child);
+ cgroup_dbg("directory name is %s\n", dir);
+
+ if (asprintf(&parent, "%s/..", dir) < 0)
+ return NULL;
+
+ cgroup_dbg("parent's name is %s\n", parent);
+
+ if (stat(dir, &stat_child) < 0)
+ goto free_parent;
+
+ if (stat(parent, &stat_parent) < 0)
+ goto free_parent;
+
+ /*
+ * Is the specified "name" a mount point?
+ */
+ if (stat_parent.st_dev != stat_child.st_dev) {
+ cgroup_dbg("parent is a mount point\n");
+ strcpy(parent, ".");
+ } else {
+ dir = strdup(name);
+ if (!dir)
+ goto free_parent;
+ dir = dirname(dir);
+ if (strcmp(dir, ".") == 0)
+ strcpy(parent, "..");
+ else
+ strcpy(parent, dir);
+ }
+
+ return parent;
+
+free_parent:
+ free(parent);
+ return NULL;
+}
+
+/**
+ * @cgroup: cgroup data structure to be filled with parent values and then
+ * passed down for creation
+ * @ignore_ownership: Ignore doing a chown on the newly created cgroup
+ */
+int cgroup_create_cgroup_from_parent(struct cgroup *cgroup,
+ int ignore_ownership)
+{
+ char *parent = NULL;
+ struct cgroup *parent_cgroup = NULL;
+ int ret = ECGFAIL;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ parent = cgroup_find_parent(cgroup->name);
+ if (!parent)
+ return ret;
+
+ cgroup_dbg("parent is %s\n", parent);
+ parent_cgroup = cgroup_new_cgroup(parent);
+ if (!parent_cgroup)
+ goto err_nomem;
+
+ if (cgroup_get_cgroup(parent_cgroup))
+ goto err_parent;
+
+ cgroup_dbg("got parent group for %s\n", parent_cgroup->name);
+ ret = cgroup_copy_cgroup(cgroup, parent_cgroup);
+ if (ret)
+ goto err_parent;
+
+ cgroup_dbg("copied parent group %s to %s\n", parent_cgroup->name,
+ cgroup->name);
+ ret = cgroup_create_cgroup(cgroup, ignore_ownership);
+
+err_parent:
+ cgroup_free(&parent_cgroup);
+err_nomem:
+ free(parent);
+ return ret;
+}
+
+/** cgroup_delete cgroup deletes a control group.
+ * struct cgroup *cgroup takes the group which is to be deleted.
+ *
+ * returns 0 on success.
+ */
+int cgroup_delete_cgroup(struct cgroup *cgroup, int ignore_migration)
+{
+ FILE *delete_tasks = NULL, *base_tasks = NULL;
+ int tids;
+ char path[FILENAME_MAX];
+ int error = ECGROUPNOTALLOWED;
+ int i, ret;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cgroup)
+ return ECGROUPNOTALLOWED;
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cgroup_test_subsys_mounted(cgroup->controller[i]->name))
+ return ECGROUPSUBSYSNOTMOUNTED;
+ }
+
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+ strncat(path, "../tasks", sizeof(path) - strlen(path));
+
+ base_tasks = fopen(path, "w");
+ if (!base_tasks)
+ goto open_err;
+
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name)) {
+ fclose(base_tasks);
+ continue;
+ }
+
+ strncat(path, "tasks", sizeof(path) - strlen(path));
+
+ delete_tasks = fopen(path, "r");
+ if (!delete_tasks) {
+ fclose(base_tasks);
+ goto open_err;
+ }
+
+ while (!feof(delete_tasks)) {
+ ret = fscanf(delete_tasks, "%d", &tids);
+ if (ret == EOF || ret < 1)
+ break;
+ fprintf(base_tasks, "%d", tids);
+ }
+
+ fclose(delete_tasks);
+ fclose(base_tasks);
+
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+ error = rmdir(path);
+ last_errno = errno;
+ }
+open_err:
+ if (ignore_migration) {
+ for (i = 0; i < cgroup->index; i++) {
+ if (!cg_build_path(cgroup->name, path,
+ cgroup->controller[i]->name))
+ continue;
+ error = rmdir(path);
+ if (error < 0 && errno == ENOENT) {
+ last_errno = errno;
+ error = 0;
+ }
+ }
+ }
+ if (error)
+ return ECGOTHER;
+
+ return error;
+}
+
+/*
+ * This function should really have more checks, but this version
+ * will assume that the callers have taken care of everything.
+ * Including the locking.
+ */
+static int cg_rd_ctrl_file(char *subsys, char *cgroup, char *file, char **value)
+{
+ char path[FILENAME_MAX];
+ FILE *ctrl_file = NULL;
+ int ret;
+
+ if (!cg_build_path_locked(cgroup, path, subsys))
+ return ECGFAIL;
+
+ strncat(path, file, sizeof(path) - strlen(path));
+ ctrl_file = fopen(path, "r");
+ if (!ctrl_file)
+ return ECGROUPVALUENOTEXIST;
+
+ *value = malloc(CG_VALUE_MAX);
+ if (!*value) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ /*
+ * using %as crashes when we try to read from files like
+ * memory.stat
+ */
+ ret = fscanf(ctrl_file, "%s", *value);
+ if (ret == 0 || ret == EOF) {
+ free(*value);
+ *value = NULL;
+ }
+
+ fclose(ctrl_file);
+
+ return 0;
+}
+
+/*
+ * Call this function with required locks taken.
+ */
+static int cgroup_fill_cgc(struct dirent *ctrl_dir, struct cgroup *cgroup,
+ struct cgroup_controller *cgc, int index)
+{
+ char *ctrl_name = NULL;
+ char *ctrl_file = NULL;
+ char *ctrl_value = NULL;
+ char *d_name = NULL;
+ char path[FILENAME_MAX+1];
+ char *buffer = NULL;
+ int error = 0;
+ struct stat stat_buffer;
+
+ d_name = strdup(ctrl_dir->d_name);
+
+ if (!strcmp(d_name, ".") || !strcmp(d_name, "..")) {
+ error = ECGINVAL;
+ goto fill_error;
+ }
+
+
+ /*
+ * This part really needs to be optimized out. Probably use
+ * some sort of a flag, but this is fine for now.
+ */
+
+ cg_build_path_locked(cgroup->name, path, cg_mount_table[index].name);
+ strncat(path, d_name, sizeof(path) - strlen(path));
+
+ error = stat(path, &stat_buffer);
+
+ if (error) {
+ error = ECGFAIL;
+ goto fill_error;
+ }
+
+ cgroup->control_uid = stat_buffer.st_uid;
+ cgroup->control_gid = stat_buffer.st_gid;
+
+ ctrl_name = strtok_r(d_name, ".", &buffer);
+
+ if (!ctrl_name) {
+ error = ECGFAIL;
+ goto fill_error;
+ }
+
+ ctrl_file = strtok_r(NULL, ".", &buffer);
+
+ if (!ctrl_file) {
+ error = ECGINVAL;
+ goto fill_error;
+ }
+
+ if (strcmp(ctrl_name, cg_mount_table[index].name) == 0) {
+ error = cg_rd_ctrl_file(cg_mount_table[index].name,
+ cgroup->name, ctrl_dir->d_name, &ctrl_value);
+ if (error || !ctrl_value)
+ goto fill_error;
+
+ if (cgroup_add_value_string(cgc, ctrl_dir->d_name,
+ ctrl_value)) {
+ error = ECGFAIL;
+ goto fill_error;
+ }
+ }
+fill_error:
+ if (ctrl_value)
+ free(ctrl_value);
+ free(d_name);
+ return error;
+}
+
+/*
+ * cgroup_get_cgroup reads the cgroup data from the filesystem.
+ * struct cgroup has the name of the group to be populated
+ *
+ * return 0 on success.
+ */
+int cgroup_get_cgroup(struct cgroup *cgroup)
+{
+ int i;
+ char path[FILENAME_MAX];
+ DIR *dir = NULL;
+ struct dirent *ctrl_dir = NULL;
+ char *control_path = NULL;
+ int error;
+ int ret;
+
+ if (!cgroup_initialized) {
+ /* ECGROUPNOTINITIALIZED */
+ return ECGROUPNOTINITIALIZED;
+ }
+
+ if (!cgroup) {
+ /* ECGROUPNOTALLOWED */
+ return ECGROUPNOTALLOWED;
+ }
+
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ for (i = 0; i < CG_CONTROLLER_MAX &&
+ cg_mount_table[i].name[0] != '\0'; i++) {
+ /*
+ * cgc will not leak, since it has to be freed using
+ * cgroup_free_cgroup
+ */
+ struct cgroup_controller *cgc;
+ struct stat stat_buffer;
+ int path_len;
+
+ if (!cg_build_path_locked(NULL, path,
+ cg_mount_table[i].name))
+ continue;
+
+ path_len = strlen(path);
+ strncat(path, cgroup->name, FILENAME_MAX - path_len - 1);
+
+ if (access(path, F_OK))
+ continue;
+
+ if (!cg_build_path_locked(cgroup->name, path,
+ cg_mount_table[i].name)) {
+ /*
+ * This fails when the cgroup does not exist
+ * for that controller.
+ */
+ continue;
+ }
+
+ /*
+ * Get the uid and gid information
+ */
+
+ ret = asprintf(&control_path, "%s/tasks", path);
+
+ if (ret < 0) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto unlock_error;
+ }
+
+ if (stat(control_path, &stat_buffer)) {
+ last_errno = errno;
+ free(control_path);
+ error = ECGOTHER;
+ goto unlock_error;
+ }
+
+ cgroup->tasks_uid = stat_buffer.st_uid;
+ cgroup->tasks_gid = stat_buffer.st_gid;
+
+ free(control_path);
+
+ cgc = cgroup_add_controller(cgroup,
+ cg_mount_table[i].name);
+ if (!cgc) {
+ error = ECGINVAL;
+ goto unlock_error;
+ }
+
+ dir = opendir(path);
+ if (!dir) {
+ last_errno = errno;
+ error = ECGOTHER;
+ goto unlock_error;
+ }
+
+ while ((ctrl_dir = readdir(dir)) != NULL) {
+ /*
+ * Skip over non regular files
+ */
+ if (ctrl_dir->d_type != DT_REG)
+ continue;
+
+ error = cgroup_fill_cgc(ctrl_dir, cgroup, cgc, i);
+ if (error == ECGFAIL) {
+ closedir(dir);
+ goto unlock_error;
+ }
+ }
+ closedir(dir);
+ }
+
+ /* Check if the group really exists or not */
+ if (!cgroup->index) {
+ error = ECGROUPNOTEXIST;
+ goto unlock_error;
+ }
+
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ return 0;
+
+unlock_error:
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ /*
+ * XX: Need to figure out how to cleanup? Cleanup just the stuff
+ * we added, or the whole structure.
+ */
+ cgroup = NULL;
+ return error;
+}
+
+/** 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 = NULL;
+ struct cgroup_controller *cptr = NULL;
+
+ /* Fill in cgroup details. */
+ cgroup_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++) {
+ cgroup_dbg("Adding controller %s\n",
+ cg_mount_table[i].name);
+ cptr = cgroup_add_controller(cgroup,
+ cg_mount_table[i].name);
+ if (!cptr) {
+ cgroup_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 "*" */
+ cgroup_dbg("Adding controller %s\n", controller);
+ cptr = cgroup_add_controller(cgroup, controller);
+ if (!cptr) {
+ cgroup_dbg("Adding controller '%s' failed\n",
+ controller);
+ return ECGROUPNOTALLOWED;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Finds the first rule in the cached list that matches the given UID or GID,
+ * and returns a pointer to that rule. This function uses rl_lock.
+ *
+ * This function may NOT be thread safe.
+ * @param uid The UID to match
+ * @param gid The GID to match
+ * @return Pointer to the first matching rule, or NULL if no match
+ * TODO: Determine thread-safeness and fix if not safe.
+ */
+static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(const uid_t uid,
+ const gid_t gid)
+{
+ /* Return value */
+ struct cgroup_rule *ret = rl.head;
+
+ /* Temporary user data */
+ struct passwd *usr = NULL;
+
+ /* Temporary group data */
+ struct group *grp = NULL;
+
+ /* Temporary string pointer */
+ char *sp = NULL;
+
+ /* Loop variable */
+ int i = 0;
+
+ pthread_rwlock_wrlock(&rl_lock);
+ while (ret) {
+ /* The wildcard rule always matches. */
+ if ((ret->uid == CGRULE_WILD) && (ret->gid == CGRULE_WILD)) {
+ goto finished;
+ }
+
+ /* This is the simple case of the UID matching. */
+ if (ret->uid == uid) {
+ goto finished;
+ }
+
+ /* This is the simple case of the GID matching. */
+ if (ret->gid == gid) {
+ goto finished;
+ }
+
+ /* If this is a group rule, the UID might be a member. */
+ if (ret->name[0] == '@') {
+ /* Get the group data. */
+ sp = &(ret->name[1]);
+ grp = getgrnam(sp);
+ if (!grp) {
+ continue;
+ }
+
+ /* Get the data for UID. */
+ usr = getpwuid(uid);
+ if (!usr) {
+ continue;
+ }
+
+ /* If UID is a member of group, we matched. */
+ for (i = 0; grp->gr_mem[i]; i++) {
+ if (!(strcmp(usr->pw_name, grp->gr_mem[i])))
+ goto finished;
+ }
+ }
+
+ /* If we haven't matched, try the next rule. */
+ ret = ret->next;
+ }
+
+ /* If we get here, no rules matched. */
+ ret = NULL;
+
+finished:
+ pthread_rwlock_unlock(&rl_lock);
+ return ret;
+}
+
+/**
+ * Changes the cgroup of a program based on the rules in the config file. If a
+ * rule exists for the given UID or GID, then the given PID is placed into the
+ * correct group. By default, this function parses the configuration file each
+ * time it is called.
+ *
+ * The flags can alter the behavior of this function:
+ * CGFLAG_USECACHE: Use cached rules instead of parsing the config file
+ *
+ * This function may NOT be thread safe.
+ * @param uid The UID to match
+ * @param gid The GID to match
+ * @param pid The PID of the process to move
+ * @param flags Bit flags to change the behavior, as defined above
+ * @return 0 on success, > 0 on error
+ * TODO: Determine thread-safeness and fix of not safe.
+ */
+int cgroup_change_cgroup_uid_gid_flags(const uid_t uid, const gid_t gid,
+ const pid_t pid, const int flags)
+{
+ /* Temporary pointer to a rule */
+ struct cgroup_rule *tmp = NULL;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* We need to check this before doing anything else! */
+ if (!cgroup_initialized) {
+ cgroup_dbg("libcgroup is not initialized\n");
+ ret = ECGROUPNOTINITIALIZED;
+ goto finished;
+ }
+
+ /*
+ * If the user did not ask for cached rules, we must parse the
+ * configuration to find a matching rule (if one exists). Else, we'll
+ * find the first match in the cached list (rl).
+ */
+ if (!(flags & CGFLAG_USECACHE)) {
+ cgroup_dbg("Not using cached rules for PID %d.\n", pid);
+ ret = cgroup_parse_rules(false, uid, gid);
+
+ /* The configuration file has an error! We must exit now. */
+ if (ret != -1 && ret != 0) {
+ cgroup_dbg("Failed to parse the configuration"
+ " rules.\n");
+ goto finished;
+ }
+
+ /* We did not find a matching rule, so we're done. */
+ if (ret == 0) {
+ cgroup_dbg("No rule found to match PID: %d, UID: %d, "
+ "GID: %d\n", pid, uid, gid);
+ goto finished;
+ }
+
+ /* Otherwise, we did match a rule and it's in trl. */
+ tmp = trl.head;
+ } else {
+ /* Find the first matching rule in the cached list. */
+ tmp = cgroup_find_matching_rule_uid_gid(uid, gid);
+ if (!tmp) {
+ cgroup_dbg("No rule found to match PID: %d, UID: %d, "
+ "GID: %d\n", pid, uid, gid);
+ ret = 0;
+ goto finished;
+ }
+ }
+ cgroup_dbg("Found matching rule %s for PID: %d, UID: %d, GID: %d\n",
+ tmp->name, pid, uid, gid);
+
+ /* If we are here, then we found a matching rule, so execute it. */
+ do {
+ cgroup_dbg("Executing rule %s for PID %d... ", tmp->name, pid);
+ ret = cgroup_change_cgroup_path(tmp->destination,
+ pid, tmp->controllers);
+ if (ret) {
+ cgroup_dbg("FAILED! (Error Code: %d)\n", ret);
+ goto finished;
+ }
+ cgroup_dbg("OK!\n");
+
+ /* Now, check for multi-line rules. As long as the "next"
+ * rule starts with '%', it's actually part of the rule that
+ * we just executed.
+ */
+ tmp = tmp->next;
+ } while (tmp && (tmp->name[0] == '%'));
+
+finished:
+ return ret;
+}
+
+/**
+ * Provides backwards-compatibility with older versions of the API. This
+ * function is deprecated, and cgroup_change_cgroup_uid_gid_flags() should be
+ * used instead. In fact, this function simply calls the newer one with flags
+ * set to 0 (none).
+ * @param uid The UID to match
+ * @param gid The GID to match
+ * @param pid The PID of the process to move
+ * @return 0 on success, > 0 on error
+ *
+ */
+int cgroup_change_cgroup_uid_gid(uid_t uid, gid_t gid, pid_t pid)
+{
+ return cgroup_change_cgroup_uid_gid_flags(uid, gid, pid, 0);
+}
+
+/**
+ * Changes the cgroup of a program based on the path provided. In this case,
+ * the user must already know into which cgroup the task should be placed and
+ * no rules will 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) {
+ cgroup_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) {
+ cgroup_dbg("cgroup_attach_task_pid failed:%d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * Print the cached rules table. This function should be called only after
+ * first calling cgroup_parse_config(), but it will work with an empty rule
+ * list.
+ * @param fp The file stream to print to
+ */
+void cgroup_print_rules_config(FILE *fp)
+{
+ /* Iterator */
+ struct cgroup_rule *itr = NULL;
+
+ /* Loop variable */
+ int i = 0;
+
+ pthread_rwlock_rdlock(&rl_lock);
+
+ if (!(rl.head)) {
+ fprintf(fp, "The rules table is empty.\n\n");
+ pthread_rwlock_unlock(&rl_lock);
+ return;
+ }
+
+ itr = rl.head;
+ while (itr) {
+ fprintf(fp, "Rule: %s\n", itr->name);
+
+ if (itr->uid == CGRULE_WILD)
+ fprintf(fp, " UID: any\n");
+ else if (itr->uid == CGRULE_INVALID)
+ fprintf(fp, " UID: N/A\n");
+ else
+ fprintf(fp, " UID: %d\n", itr->uid);
+
+ if (itr->gid == CGRULE_WILD)
+ fprintf(fp, " GID: any\n");
+ else if (itr->gid == CGRULE_INVALID)
+ fprintf(fp, " GID: N/A\n");
+ else
+ fprintf(fp, " GID: %d\n", itr->gid);
+
+ fprintf(fp, " DEST: %s\n", itr->destination);
+
+ fprintf(fp, " CONTROLLERS:\n");
+ for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
+ if (itr->controllers[i]) {
+ fprintf(fp, " %s\n", itr->controllers[i]);
+ }
+ }
+ fprintf(fp, "\n");
+ itr = itr->next;
+ }
+ pthread_rwlock_unlock(&rl_lock);
+}
+
+/**
+ * Reloads the rules list, using the given configuration file. This function
+ * is probably NOT thread safe (calls cgroup_parse_rules()).
+ * @return 0 on success, > 0 on failure
+ */
+int cgroup_reload_cached_rules()
+{
+ /* Return codes */
+ int ret = 0;
+
+ cgroup_dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE);
+ if ((ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID))) {
+ cgroup_dbg("Error parsing configuration file \"%s\": %d.\n",
+ CGRULES_CONF_FILE, ret);
+ ret = ECGROUPPARSEFAIL;
+ goto finished;
+ }
+
+ #ifdef CGROUP_DEBUG
+ cgroup_print_rules_config(stdout);
+ #endif
+
+finished:
+ return ret;
+}
+
+/**
+ * Initializes the rules cache.
+ * @return 0 on success, > 0 on error
+ */
+int cgroup_init_rules_cache()
+{
+ /* Return codes */
+ int ret = 0;
+
+ /* Attempt to read the configuration file and cache the rules. */
+ ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID);
+ if (ret) {
+ cgroup_dbg("Could not initialize rule cache, error was: %d\n",
+ ret);
+ cgroup_rules_loaded = false;
+ } else {
+ cgroup_rules_loaded = true;
+ }
+
+ return ret;
+}
+
+/**
+ * cgroup_get_current_controller_path
+ * @pid: pid of the current process for which the path is to be determined
+ * @controller: name of the controller for which to determine current path
+ * @current_path: a pointer that is filled with the value of the current
+ * path as seen in /proc/<pid>/cgroup
+ */
+int cgroup_get_current_controller_path(pid_t pid, const char *controller,
+ char **current_path)
+{
+ char *path = NULL;
+ int ret;
+ FILE *pid_cgroup_fd = NULL;
+
+ if (!controller)
+ return ECGOTHER;
+
+ if (!cgroup_initialized) {
+ cgroup_dbg("libcgroup is not initialized\n");
+ return ECGROUPNOTINITIALIZED;
+ }
+
+ ret = asprintf(&path, "/proc/%d/cgroup", pid);
+ if (ret <= 0) {
+ cgroup_dbg("cannot allocate memory (/proc/pid/cgroup) ret %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = ECGROUPNOTEXIST;
+ pid_cgroup_fd = fopen(path, "r");
+ if (!pid_cgroup_fd)
+ goto cleanup_path;
+
+ /*
+ * Why do we grab the cg_mount_table_lock?, the reason is that
+ * the cgroup of a pid can change via the cgroup_attach_task_pid()
+ * call. To make sure, we return consitent and safe results,
+ * we acquire the lock upfront. We can optimize by acquiring
+ * and releasing the lock in the while loop, but that
+ * will be more expensive.
+ */
+ pthread_rwlock_rdlock(&cg_mount_table_lock);
+ while (!feof(pid_cgroup_fd)) {
+ char controllers[FILENAME_MAX];
+ char cgroup_path[FILENAME_MAX];
+ int num;
+ char *savedptr;
+ char *token;
+
+ ret = fscanf(pid_cgroup_fd, "%d:%[^:]:%s\n", &num, controllers,
+ cgroup_path);
+ /*
+ * Magic numbers like "3" seem to be integrating into
+ * my daily life, I need some magic to help make them
+ * disappear :)
+ */
+ if (ret != 3 || ret == EOF) {
+ cgroup_dbg("read failed for pid_cgroup_fd ret %d\n",
+ ret);
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto done;
+ }
+
+ token = strtok_r(controllers, ",", &savedptr);
+ do {
+ if (strncmp(controller, token, strlen(controller) + 1)
+ == 0) {
+ *current_path = strdup(cgroup_path);
+ if (!*current_path) {
+ last_errno = errno;
+ ret = ECGOTHER;
+ goto done;
+ }
+ ret = 0;
+ goto done;
+ }
+ token = strtok_r(NULL, ",", &savedptr);
+ } while (token);
+ }
+
+done:
+ pthread_rwlock_unlock(&cg_mount_table_lock);
+ fclose(pid_cgroup_fd);
+cleanup_path:
+ free(path);
+ return ret;
+}
+
+char *cgroup_strerror(int code)
+{
+ assert((code >= ECGROUPNOTCOMPILED) && (code < ECGSENTINEL));
+ if (code == ECGOTHER) {
+ snprintf(errtext, MAXLEN, "%s: error message: %s",
+ cgroup_strerror_codes[code % ECGROUPNOTCOMPILED],
+ strerror(cgroup_get_last_errno()));
+ return errtext;
+ }
+ return cgroup_strerror_codes[code % ECGROUPNOTCOMPILED];
+}
+
+/**
+ * Return last errno, which caused ECGOTHER error.
+ */
+int cgroup_get_last_errno()
+{
+ return last_errno;
+}
+
+
+static int cg_walk_node(FTS *fts, FTSENT *ent, const int depth,
+ struct cgroup_file_info *info)
+{
+ int ret = 0;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ cgroup_dbg("seeing file %s\n", ent->fts_path);
+
+ info->path = ent->fts_name;
+ info->parent = ent->fts_parent->fts_name;
+ info->full_path = ent->fts_path;
+ info->depth = ent->fts_level;
+ info->type = CGROUP_FILE_TYPE_OTHER;
+
+ if (depth && (info->depth > depth))
+ return 0;
+
+ switch (ent->fts_info) {
+ case FTS_DNR:
+ case FTS_ERR:
+ errno = ent->fts_errno;
+ break;
+ case FTS_D:
+ info->type = CGROUP_FILE_TYPE_DIR;
+ break;
+ case FTS_DC:
+ case FTS_NSOK:
+ case FTS_NS:
+ case FTS_DP:
+ break;
+ case FTS_F:
+ info->type = CGROUP_FILE_TYPE_FILE;
+ break;
+ case FTS_DEFAULT:
+ break;
+ }
+ return ret;
+}
+
+int cgroup_walk_tree_next(const int depth, void **handle,
+ struct cgroup_file_info *info, int base_level)
+{
+ int ret = 0;
+ FTS *fts = *(FTS **)handle;
+ FTSENT *ent;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!handle)
+ return ECGINVAL;
+ ent = fts_read(fts);
+ if (!ent)
+ return ECGEOF;
+ if (!base_level && depth)
+ base_level = ent->fts_level + depth;
+ ret = cg_walk_node(fts, ent, base_level, info);
+ *handle = fts;
+ return ret;
+}
+
+int cgroup_walk_tree_end(void **handle)
+{
+ FTS *fts = *(FTS **)handle;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!handle)
+ return ECGINVAL;
+ fts_close(fts);
+ return 0;
+}
+
+/*
+ * TODO: Need to decide a better place to put this function.
+ */
+int cgroup_walk_tree_begin(char *controller, char *base_path, const int depth,
+ void **handle, struct cgroup_file_info *info,
+ int *base_level)
+{
+ int ret = 0;
+ cgroup_dbg("path is %s\n", base_path);
+ char *cg_path[2];
+ char full_path[FILENAME_MAX];
+ FTSENT *ent;
+ FTS *fts;
+
+ if (!cgroup_initialized)
+ return ECGROUPNOTINITIALIZED;
+
+ if (!cg_build_path(base_path, full_path, controller))
+ return ECGOTHER;
+
+ *base_level = 0;
+ cg_path[0] = full_path;
+ cg_path[1] = NULL;
+
+ fts = fts_open(cg_path, FTS_LOGICAL | FTS_NOCHDIR |
+ FTS_NOSTAT, NULL);
+ ent = fts_read(fts);
+ if (!ent) {
+ cgroup_dbg("fts_read failed\n");
+ return ECGINVAL;
+ }
+ if (!*base_level && depth)
+ *base_level = ent->fts_level + depth;
+ ret = cg_walk_node(fts, ent, *base_level, info);
+ *handle = fts;
+ return ret;
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..b188985
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,496 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Balbir Singh <balbir@linux.vnet.ibm.com>
+ * Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * TODOs:
+ * 1. Implement our own hashing scheme
+ * 2. Add namespace support
+ * 3. Add support for parsing cgroup filesystem and creating a
+ * config out of it.
+ *
+ * Code initiated and designed by Balbir Singh. All faults are most likely
+ * his mistake.
+ *
+ * Cleanup and changes to use the "official" structures and functions made
+ * by Dhaval Giani. All faults will still be Balbir's mistake :)
+ */
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <grp.h>
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <limits.h>
+#include <pwd.h>
+#include <pthread.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define MAX_CGROUPS 1024
+
+extern FILE *yyin;
+extern int yyparse(void);
+
+/*
+ * The basic global data structures.
+ *
+ * config_mount_table -> Where what controller is mounted
+ * table_index -> Where in the table are we.
+ * config_table_lock -> Serializing access to config_mount_table.
+ * cgroup_table -> Which cgroups have to be created.
+ * cgroup_table_index -> Where in the cgroup_table we are.
+ */
+static struct cg_mount_table_s config_mount_table[CG_CONTROLLER_MAX];
+static int config_table_index;
+static pthread_rwlock_t config_table_lock = PTHREAD_RWLOCK_INITIALIZER;
+static struct cgroup config_cgroup_table[MAX_CGROUPS];
+int cgroup_table_index;
+
+/*
+ * Needed for the type while mounting cgroupfs.
+ */
+static const char cgroup_filesystem[] = "cgroup";
+
+/*
+ * NOTE: All these functions return 1 on success
+ * and not 0 as is the library convention
+ */
+
+/*
+ * This call just sets the name of the cgroup. It will
+ * always be called in the end, because the parser will
+ * work bottom up.
+ */
+int cgroup_config_insert_cgroup(char *cg_name)
+{
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+
+ strncpy(config_cgroup->name, cg_name, FILENAME_MAX);
+ /*
+ * Since this will be the last part to be parsed.
+ */
+ cgroup_table_index++;
+ free(cg_name);
+ return 1;
+}
+
+/*
+ * This function sets the various controller's control
+ * files. It will always append values for cgroup_table_index
+ * entry in the cgroup_table. The index is incremented in
+ * cgroup_config_insert_cgroup
+ */
+int cgroup_config_parse_controller_options(char *controller, char *name_value)
+{
+ char *buffer = NULL;
+ char *name, *value;
+ struct cgroup_controller *cgc;
+ int error;
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+ char *nm_pairs, *nv_buf;
+
+ cgroup_dbg("Adding controller %s, value %s\n", controller, name_value);
+ cgc = cgroup_add_controller(config_cgroup, controller);
+
+ if (!cgc)
+ goto parse_error;
+
+ /*
+ * Did we just specify the controller to create the correct
+ * set of directories, without setting any values?
+ */
+ if (!name_value)
+ goto done;
+
+ nm_pairs = strtok_r(name_value, ":", &nv_buf);
+ cgroup_dbg("[1] name value pair being processed is %s\n", nm_pairs);
+
+ name = strtok_r(nm_pairs, " ", &buffer);
+
+ if (!name)
+ goto parse_error;
+
+ value = strtok_r(NULL, " ", &buffer);
+
+ if (!value)
+ goto parse_error;
+
+ cgroup_dbg("name is %s, value is %s\n", name, value);
+ error = cgroup_add_value_string(cgc, name, value);
+
+ if (error)
+ goto parse_error;
+
+ while ((nm_pairs = strtok_r(NULL, ":", &nv_buf))) {
+ cgroup_dbg("[2] name value pair being processed is %s\n",
+ nm_pairs);
+ name = strtok_r(nm_pairs, " ", &buffer);
+
+ if (!name)
+ goto parse_error;
+
+ value = strtok_r(NULL, " ", &buffer);
+
+ if (!value)
+ goto parse_error;
+
+ cgroup_dbg("name is %s, value is %s\n", name, value);
+ error = cgroup_add_value_string(cgc, name, value);
+
+ if (error)
+ goto parse_error;
+ }
+
+done:
+ free(controller);
+ free(name_value);
+ return 1;
+
+parse_error:
+ free(controller);
+ free(name_value);
+ cgroup_delete_cgroup(config_cgroup, 1);
+ cgroup_table_index--;
+ return 0;
+}
+
+/*
+ * Sets the tasks file's uid and gid
+ */
+int cgroup_config_group_task_perm(char *perm_type, char *value)
+{
+ struct passwd *pw, *pw_buffer;
+ struct group *group, *group_buffer;
+ int error;
+ long val = atoi(value);
+ char buffer[CGROUP_BUFFER_LEN];
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+
+ if (!strcmp(perm_type, "uid")) {
+ if (!val) {
+ pw = (struct passwd *) malloc(sizeof(struct passwd));
+
+ if (!pw)
+ goto group_task_error;
+
+ error = getpwnam_r(value, pw, buffer, CGROUP_BUFFER_LEN,
+ &pw_buffer);
+ if (pw_buffer == NULL) {
+ free(pw);
+ goto group_task_error;
+ }
+
+ val = pw->pw_uid;
+ free(pw);
+ }
+ config_cgroup->tasks_uid = val;
+ }
+
+ if (!strcmp(perm_type, "gid")) {
+ if (!val) {
+ group = (struct group *) malloc(sizeof(struct group));
+
+ if (!group)
+ goto group_task_error;
+
+ error = getgrnam_r(value, group, buffer,
+ CGROUP_BUFFER_LEN, &group_buffer);
+
+ if (group_buffer == NULL) {
+ free(group);
+ goto group_task_error;
+ }
+
+ val = group->gr_gid;
+ free(group);
+ }
+ config_cgroup->tasks_gid = val;
+ }
+
+ free(perm_type);
+ free(value);
+ return 1;
+
+group_task_error:
+ free(perm_type);
+ free(value);
+ cgroup_delete_cgroup(config_cgroup, 1);
+ cgroup_table_index--;
+ return 0;
+}
+
+/*
+ * Set the control file's uid/gid
+ */
+int cgroup_config_group_admin_perm(char *perm_type, char *value)
+{
+ struct passwd *pw, *pw_buffer;
+ struct group *group, *group_buffer;
+ int error;
+ struct cgroup *config_cgroup =
+ &config_cgroup_table[cgroup_table_index];
+ long val = atoi(value);
+ char buffer[CGROUP_BUFFER_LEN];
+
+ if (!strcmp(perm_type, "uid")) {
+ if (!val) {
+ pw = (struct passwd *) malloc(sizeof(struct passwd));
+
+ if (!pw)
+ goto admin_error;
+
+ error = getpwnam_r(value, pw, buffer, CGROUP_BUFFER_LEN,
+ &pw_buffer);
+ if (pw_buffer == NULL) {
+ free(pw);
+ goto admin_error;
+ }
+
+ val = pw->pw_uid;
+ free(pw);
+ }
+ config_cgroup->control_uid = val;
+ }
+
+ if (!strcmp(perm_type, "gid")) {
+ if (!val) {
+ group = (struct group *) malloc(sizeof(struct group));
+
+ if (!group)
+ goto admin_error;
+
+ error = getgrnam_r(value, group, buffer,
+ CGROUP_BUFFER_LEN, &group_buffer);
+
+ if (group_buffer == NULL) {
+ free(group);
+ goto admin_error;
+ }
+
+ val = group->gr_gid;
+ free(group);
+ }
+ config_cgroup->control_gid = val;
+ }
+
+ free(perm_type);
+ free(value);
+ return 1;
+
+admin_error:
+ free(perm_type);
+ free(value);
+ cgroup_delete_cgroup(config_cgroup, 1);
+ cgroup_table_index--;
+ return 0;
+}
+
+/*
+ * The moment we have found the controller's information
+ * insert it into the config_mount_table.
+ */
+int cgroup_config_insert_into_mount_table(char *name, char *mount_point)
+{
+ int i;
+
+ if (config_table_index >= CG_CONTROLLER_MAX)
+ return 0;
+
+ pthread_rwlock_wrlock(&config_table_lock);
+
+ /*
+ * Merge controller names with the same mount point
+ */
+ for (i = 0; i < config_table_index; i++) {
+ if (strcmp(config_mount_table[i].path, mount_point) == 0) {
+ char *cname = config_mount_table[i].name;
+ strncat(cname, ",", FILENAME_MAX - strlen(cname) - 1);
+ strncat(cname, name,
+ FILENAME_MAX - strlen(cname) - 1);
+ goto done;
+ }
+ }
+
+ strcpy(config_mount_table[config_table_index].name, name);
+ strcpy(config_mount_table[config_table_index].path, mount_point);
+ config_table_index++;
+done:
+ pthread_rwlock_unlock(&config_table_lock);
+ free(name);
+ free(mount_point);
+ return 1;
+}
+
+/*
+ * Cleanup all the data from the config_mount_table
+ */
+void cgroup_config_cleanup_mount_table(void)
+{
+ memset(&config_mount_table, 0,
+ sizeof(struct cg_mount_table_s) * CG_CONTROLLER_MAX);
+}
+
+/*
+ * Start mounting the mount table.
+ */
+int cgroup_config_mount_fs()
+{
+ int ret;
+ struct stat buff;
+ int i;
+
+ for (i = 0; i < config_table_index; i++) {
+ struct cg_mount_table_s *curr = &(config_mount_table[i]);
+
+ ret = stat(curr->path, &buff);
+
+ if (ret < 0 && errno != ENOENT) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ if (errno == ENOENT) {
+ ret = mkdir(curr->path,
+ S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ if (ret < 0) {
+ last_errno = errno;
+ return ECGOTHER;
+ }
+ } else if (!S_ISDIR(buff.st_mode)) {
+ errno = ENOTDIR;
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ ret = mount(cgroup_filesystem, curr->path, cgroup_filesystem,
+ 0, curr->name);
+
+ if (ret < 0)
+ return ECGMOUNTFAIL;
+ }
+ return 0;
+}
+
+/*
+ * Actually create the groups once the parsing has been finished.
+ */
+int cgroup_config_create_groups()
+{
+ int error = 0;
+ int i;
+
+ for (i = 0; i < cgroup_table_index; i++) {
+ struct cgroup *cgroup = &config_cgroup_table[i];
+ error = cgroup_create_cgroup(cgroup, 0);
+ cgroup_dbg("creating group %s, error %d\n", cgroup->name,
+ error);
+ if (error)
+ return error;
+ }
+ return error;
+}
+
+/*
+ * Destroy the cgroups
+ */
+int cgroup_config_destroy_groups(void)
+{
+ int error = 0;
+ int i;
+
+ for (i = 0; i < cgroup_table_index; i++) {
+ struct cgroup *cgroup = &config_cgroup_table[i];
+ error = cgroup_delete_cgroup(cgroup, 0);
+ if (error)
+ return error;
+ }
+ return error;
+}
+
+/*
+ * Unmount the controllers
+ */
+int cgroup_config_unmount_controllers(void)
+{
+ int i;
+ int error;
+
+ for (i = 0; i < config_table_index; i++) {
+ /*
+ * We ignore failures and ensure that all mounted
+ * containers are unmounted
+ */
+ error = umount(config_mount_table[i].path);
+ if (error < 0)
+ cgroup_dbg("Unmount failed\n");
+ error = rmdir(config_mount_table[i].path);
+ if (error < 0)
+ cgroup_dbg("rmdir failed\n");
+ }
+
+ return 0;
+}
+
+/*
+ * The main function which does all the setup of the data structures
+ * and finally creates the cgroups
+ */
+int cgroup_config_load_config(const char *pathname)
+{
+ int error;
+ yyin = fopen(pathname, "r");
+
+ if (!yyin) {
+ cgroup_dbg("Failed to open file %s\n", pathname);
+ last_errno = errno;
+ return ECGOTHER;
+ }
+
+ if (yyparse() != 0) {
+ cgroup_dbg("Failed to parse file %s\n", pathname);
+ return ECGROUPPARSEFAIL;
+ }
+
+ error = cgroup_config_mount_fs();
+ if (error)
+ goto err_mnt;
+
+ error = cgroup_init();
+ if (error)
+ goto err_mnt;
+
+ error = cgroup_config_create_groups();
+ cgroup_dbg("creating all cgroups now, error=%d\n", error);
+ if (error)
+ goto err_grp;
+
+ fclose(yyin);
+ return 0;
+err_grp:
+ cgroup_config_destroy_groups();
+err_mnt:
+ cgroup_config_unmount_controllers();
+ fclose(yyin);
+ return error;
+}
diff --git a/src/daemon/cgrulesengd.c b/src/daemon/cgrulesengd.c
new file mode 100644
index 0000000..8efdce1
--- /dev/null
+++ b/src/daemon/cgrulesengd.c
@@ -0,0 +1,793 @@
+/*
+ * Copyright Red Hat Inc. 2008
+ *
+ * Author: Steve Olivieri <sjo@redhat.com>
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * Some part of the programs have been derived from Dhaval Giani's posting
+ * for daemon to place the task in right container. Original copyright notice
+ * follows.
+ *
+ * Copyright IBM Corporation, 2007
+ * Author: Dhaval Giani <dhaval <at> linux.vnet.ibm.com>
+ * Derived from test_cn_proc.c by Matt Helsley
+ * Original copyright notice follows
+ *
+ * Copyright (C) Matt Helsley, IBM Corp. 2005
+ * Derived from fcctl.c by Guillaume Thouvenin
+ * Original copyright notice follows:
+ *
+ * Copyright (C) 2005 BULL SA.
+ * Written by Guillaume Thouvenin <guillaume.thouvenin <at> bull.net>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * TODO Stop using netlink for communication (or at least rewrite that part).
+ */
+
+#include "libcgroup.h"
+#include "cgrulesengd.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+#include <string.h>
+#include <linux/netlink.h>
+#include <signal.h>
+#include <time.h>
+#include <syslog.h>
+#include <getopt.h>
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <linux/connector.h>
+#include <linux/cn_proc.h>
+
+/* Log file, NULL if logging to file is disabled */
+FILE* logfile;
+
+/* Log facility, 0 if logging to syslog is disabled */
+int logfacility;
+
+/* Current log level */
+int loglevel;
+
+/**
+ * Prints the usage information for this program and, optionally, an error
+ * message. This function uses vfprintf.
+ * @param fd The file stream to print to
+ * @param msg The error message to print (printf style)
+ * @param ... Any args to msg (printf style)
+ */
+void usage(FILE* fd, const char* msg, ...)
+{
+ /* List of args to msg */
+ va_list ap;
+
+ /* Put all args after msg into the list. */
+ va_start(ap, msg);
+
+ if (msg)
+ vfprintf(fd, msg, ap);
+ fprintf(fd, "\n");
+ fprintf(fd, "cgrulesengd -- a daemon for the cgroups rules engine\n\n");
+ fprintf(fd, "Usage : cgrulesengd [options]\n\n");
+ fprintf(fd, " options :\n");
+ fprintf(fd, " -q | --quiet quiet mode\n"
+ " -v | --verbose verbose mode\n"
+ " -f <path> | --logfile=<path> write log to file\n"
+ " -s[facility] | --syslog=[facility] write log to syslog\n"
+ " -n | --nodaemom don't fork daemon\n"
+ " -d | --debug same as -v -v -n -f -\n"
+ " -Q | --nolog disable logging\n"
+ " -h | --help show this help\n\n"
+ );
+ va_end(ap);
+}
+
+/**
+ * Prints a formatted message (like printf()) to all log destinations.
+ * Flushes the file stream's buffer so that the message is immediately
+ * readable.
+ * @param level The log level (LOG_EMERG ... LOG_DEBUG)
+ * @param format The format for the message (printf style)
+ * @param ... Any args to format (printf style)
+ */
+void flog(int level, const char *format, ...)
+{
+ /* List of args to format */
+ va_list ap;
+
+ /* Check the log level */
+ if (level > loglevel)
+ return;
+
+ if (logfile) {
+ /* Print the message to the given stream. */
+ va_start(ap, format);
+ vfprintf(logfile, format, ap);
+ va_end(ap);
+ fprintf(logfile, "\n");
+
+ /*
+ * Flush the stream's buffer, so the data is readable
+ * immediately.
+ */
+ fflush(logfile);
+ }
+
+ if (logfacility) {
+ va_start(ap, format);
+ vsyslog(LOG_MAKEPRI(logfacility, level), format, ap);
+ va_end(ap);
+ }
+}
+
+/**
+ * Process an event from the kernel, and determine the correct UID/GID/PID to
+ * pass to libcgroup. Then, libcgroup will decide the cgroup to move the PID
+ * to, if any.
+ * @param ev The event to process
+ * @param type The type of event to process (part of ev)
+ * @return 0 on success, > 0 on failure
+ */
+int cgre_process_event(const struct proc_event *ev, const int type)
+{
+ /* Handle for the /proc/PID/status file */
+ FILE *f;
+
+ /* Path for /proc/PID/status file */
+ char path[FILENAME_MAX];
+
+ /* Temporary buffer */
+ char *buf = NULL;
+
+ /* UID data */
+ uid_t ruid, euid, suid, fsuid, log_uid = 0;
+
+ /* GID data */
+ gid_t rgid, egid, sgid, fsgid, log_gid = 0;
+
+ /* PID, just for logging */
+ pid_t log_pid = 0;
+
+ /* Return codes */
+ int ret = 0;
+
+ /*
+ * First, we need to open the /proc/PID/status file so that we can
+ * get the effective UID and GID for the process that we're working
+ * on. This process is probably not us, so we can't just call
+ * geteuid() or getegid().
+ */
+ sprintf(path, "/proc/%d/status", ev->event_data.id.process_pid);
+ f = fopen(path, "r");
+ if (!f) {
+ flog(LOG_WARNING, "Failed to open %s", path);
+ goto finished;
+ }
+
+ /* Now, we need to find either the eUID or the eGID of the process. */
+ buf = calloc(4096, sizeof(char));
+ if (!buf) {
+ flog(LOG_WARNING, "Failed to process event, out of"
+ "memory? Error: %s",
+ strerror(errno));
+ ret = errno;
+ fclose(f);
+ goto finished;
+ }
+ switch (type) {
+ case PROC_EVENT_UID:
+ /* Have the eUID, need to find the eGID. */
+ while (fgets(buf, 4096, f)) {
+ if (!strncmp(buf, "Gid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", &rgid, &egid,
+ &sgid, &fsgid);
+ break;
+ }
+ memset(buf, '\0', 4096);
+ }
+ break;
+ case PROC_EVENT_GID:
+ /* Have the eGID, need to find the eUID. */
+ while (fgets(buf, 4096, f)) {
+ if (!strncmp(buf, "Uid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", &ruid, &euid,
+ &suid, &fsuid);
+ break;
+ }
+ memset(buf, '\0', 4096);
+ }
+ break;
+ default:
+ flog(LOG_WARNING, "For some reason, we're processing a"
+ " non-UID/GID event. Something is wrong!");
+ break;
+ }
+ free(buf);
+ fclose(f);
+
+ /*
+ * Now that we have the UID, the GID, and the PID, we can make a call
+ * to libcgroup to change the cgroup for this PID.
+ */
+ switch (type) {
+ case PROC_EVENT_UID:
+ log_uid = ev->event_data.id.e.euid;
+ log_gid = egid;
+ log_pid = ev->event_data.id.process_pid;
+ ret = cgroup_change_cgroup_uid_gid_flags(
+ ev->event_data.id.e.euid,
+ egid, ev->event_data.id.process_pid,
+ CGFLAG_USECACHE);
+ break;
+ case PROC_EVENT_GID:
+ log_uid = euid;
+ log_gid = ev->event_data.id.e.egid;
+ log_pid = ev->event_data.id.process_pid;
+ ret = cgroup_change_cgroup_uid_gid_flags(euid,
+ ev->event_data.id.e.egid,
+ ev->event_data.id.process_pid,
+ CGFLAG_USECACHE);
+ break;
+ default:
+ break;
+ }
+
+ if (ret) {
+ /*
+ * TODO: add some supression, do not spam log when every group
+ * change fails
+ */
+ flog(LOG_WARNING, "Cgroup change for PID: %d, UID: %d, GID: %d"
+ " FAILED! (Error Code: %d)", log_pid, log_uid, log_gid,
+ ret);
+ } else {
+ flog(LOG_INFO, "Cgroup change for PID: %d, UID: %d, GID: %d OK",
+ log_pid, log_uid, log_gid);
+ }
+
+finished:
+ return ret;
+}
+
+/**
+ * Handle a netlink message. In the event of PROC_EVENT_UID or PROC_EVENT_GID,
+ * we pass the event along to cgre_process_event for further processing. All
+ * other events are ignored.
+ * @param cn_hdr The netlink message
+ * @return 0 on success, > 0 on error
+ */
+int cgre_handle_msg(struct cn_msg *cn_hdr)
+{
+ /* The event to consider */
+ struct proc_event *ev;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* Get the event data. We only care about two event types. */
+ ev = (struct proc_event*)cn_hdr->data;
+ switch (ev->what) {
+ case PROC_EVENT_UID:
+ flog(LOG_DEBUG, "UID Event: PID = %d, tGID = %d, rUID = %d,"
+ " eUID = %d", ev->event_data.id.process_pid,
+ ev->event_data.id.process_tgid,
+ ev->event_data.id.r.ruid,
+ ev->event_data.id.e.euid);
+ ret = cgre_process_event(ev, PROC_EVENT_UID);
+ break;
+ case PROC_EVENT_GID:
+ flog(LOG_DEBUG, "GID Event: PID = %d, tGID = %d, rGID = %d,"
+ " eGID = %d", ev->event_data.id.process_pid,
+ ev->event_data.id.process_tgid,
+ ev->event_data.id.r.rgid,
+ ev->event_data.id.e.egid);
+ ret = cgre_process_event(ev, PROC_EVENT_GID);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+int cgre_create_netlink_socket_process_msg()
+{
+ int sk_nl;
+ int err;
+ struct sockaddr_nl my_nla, kern_nla, from_nla;
+ socklen_t from_nla_len;
+ char buff[BUFF_SIZE];
+ int rc = -1;
+ struct nlmsghdr *nl_hdr;
+ struct cn_msg *cn_hdr;
+ enum proc_cn_mcast_op *mcop_msg;
+ size_t recv_len = 0;
+
+ /*
+ * Create an endpoint for communication. Use the kernel user
+ * interface device (PF_NETLINK) which is a datagram oriented
+ * service (SOCK_DGRAM). The protocol used is the connector
+ * protocol (NETLINK_CONNECTOR)
+ */
+ sk_nl = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+ if (sk_nl == -1) {
+ cgroup_dbg("socket sk_nl error");
+ return rc;
+ }
+
+ my_nla.nl_family = AF_NETLINK;
+ my_nla.nl_groups = CN_IDX_PROC;
+ my_nla.nl_pid = getpid();
+ my_nla.nl_pad = 0;
+
+ kern_nla.nl_family = AF_NETLINK;
+ kern_nla.nl_groups = CN_IDX_PROC;
+ kern_nla.nl_pid = 1;
+ kern_nla.nl_pad = 0;
+
+ err = bind(sk_nl, (struct sockaddr *)&my_nla, sizeof(my_nla));
+ if (err == -1) {
+ cgroup_dbg("binding sk_nl error");
+ goto close_and_exit;
+ }
+
+ nl_hdr = (struct nlmsghdr *)buff;
+ cn_hdr = (struct cn_msg *)NLMSG_DATA(nl_hdr);
+ mcop_msg = (enum proc_cn_mcast_op*)&cn_hdr->data[0];
+ cgroup_dbg("sending proc connector: PROC_CN_MCAST_LISTEN... ");
+ memset(buff, 0, sizeof(buff));
+ *mcop_msg = PROC_CN_MCAST_LISTEN;
+
+ /* fill the netlink header */
+ nl_hdr->nlmsg_len = SEND_MESSAGE_LEN;
+ nl_hdr->nlmsg_type = NLMSG_DONE;
+ nl_hdr->nlmsg_flags = 0;
+ nl_hdr->nlmsg_seq = 0;
+ nl_hdr->nlmsg_pid = getpid();
+
+ /* fill the connector header */
+ cn_hdr->id.idx = CN_IDX_PROC;
+ cn_hdr->id.val = CN_VAL_PROC;
+ cn_hdr->seq = 0;
+ cn_hdr->ack = 0;
+ cn_hdr->len = sizeof(enum proc_cn_mcast_op);
+ cgroup_dbg("sending netlink message len=%d, cn_msg len=%d\n",
+ nl_hdr->nlmsg_len, (int) sizeof(struct cn_msg));
+ if (send(sk_nl, nl_hdr, nl_hdr->nlmsg_len, 0) != nl_hdr->nlmsg_len) {
+ cgroup_dbg("failed to send proc connector mcast ctl op!\n");
+ goto close_and_exit;
+ }
+ cgroup_dbg("sent\n");
+
+ for(memset(buff, 0, sizeof(buff)), from_nla_len = sizeof(from_nla);
+ ; memset(buff, 0, sizeof(buff)), from_nla_len = sizeof(from_nla)) {
+ struct nlmsghdr *nlh = (struct nlmsghdr*)buff;
+ memcpy(&from_nla, &kern_nla, sizeof(from_nla));
+ recv_len = recvfrom(sk_nl, buff, BUFF_SIZE, 0,
+ (struct sockaddr*)&from_nla, &from_nla_len);
+ if (recv_len == ENOBUFS) {
+ flog(LOG_ERR, "ERROR: NETLINK BUFFER FULL, MESSAGE "
+ "DROPPED!");
+ continue;
+ }
+ if (recv_len < 1)
+ continue;
+ while (NLMSG_OK(nlh, recv_len)) {
+ cn_hdr = NLMSG_DATA(nlh);
+ if (nlh->nlmsg_type == NLMSG_NOOP)
+ continue;
+ if ((nlh->nlmsg_type == NLMSG_ERROR) ||
+ (nlh->nlmsg_type == NLMSG_OVERRUN))
+ break;
+ if(cgre_handle_msg(cn_hdr) < 0) {
+ goto close_and_exit;
+ }
+ if (nlh->nlmsg_type == NLMSG_DONE)
+ break;
+ nlh = NLMSG_NEXT(nlh, recv_len);
+ }
+ }
+
+close_and_exit:
+ close(sk_nl);
+ return rc;
+}
+
+/**
+ * Start logging. Opens syslog and/or log file and sets log level.
+ * @param logp Path of the log file, NULL if no log file was specified
+ * @param logf Syslog facility, NULL if no facility was specified
+ * @param logv Log verbosity, 2 is the default, 0 = no logging, 4 = everything
+ */
+static void cgre_start_log(const char *logp, int logf, int logv)
+{
+ /* Current system time */
+ time_t tm;
+
+ /* Log levels */
+ int loglevels[] = {
+ LOG_EMERG, /* -qq */
+ LOG_ERR, /* -q */
+ LOG_NOTICE, /* default */
+ LOG_INFO, /* -v */
+ LOG_DEBUG /* -vv */
+ };
+
+ /* Set default logging destination if nothing was specified */
+ if (!logp && !logf)
+ logf = LOG_DAEMON;
+
+ /* Open log file */
+ if (logp) {
+ if (strcmp("-", logp) == 0) {
+ logfile = stdout;
+ } else {
+ logfile = fopen(logp, "a");
+ if (!logfile) {
+ fprintf(stderr, "Failed to open log file %s,"
+ " error: %s. Continuing anyway.\n",
+ logp, strerror(errno));
+ logfile = stdout;
+ }
+ }
+ } else
+ logfile = NULL;
+
+ /* Open syslog */
+ if (logf) {
+ openlog("CGRE", LOG_CONS | LOG_PID, logf);
+ logfacility = logf;
+ } else
+ logfacility = 0;
+
+ /* Set the log level */
+ if (logv < 0)
+ logv = 0;
+ if (logv >= sizeof(loglevels)/sizeof(int))
+ logv = sizeof(loglevels)/sizeof(int)-1;
+
+ loglevel = loglevels[logv];
+
+ flog(LOG_DEBUG, "CGroup Rules Engine Daemon log started");
+ tm = time(0);
+ flog(LOG_DEBUG, "Current time: %s", ctime(&tm));
+ flog(LOG_DEBUG, "Opened log file: %s, log facility: %d, log level: %d",
+ logp, logfacility, loglevel);
+}
+
+
+/**
+ * Turns this program into a daemon. In doing so, we fork() and kill the
+ * parent process. Note too that stdout, stdin, and stderr are closed in
+ * daemon mode, and a file descriptor for a log file is opened.
+ * @param logp Path of the log file, NULL if no log file was specified
+ * @param logf Syslog facility, 0 if no facility was specified
+ * @param daemon False to turn off daemon mode (no fork, leave FDs open)
+ * @param logv Log verbosity, 2 is the default, 0 = no logging, 5 = everything
+ * @return 0 on success, > 0 on error
+ */
+int cgre_start_daemon(const char *logp, const int logf,
+ const unsigned char daemon, const int logv)
+{
+ /* PID returned from the fork() */
+ pid_t pid;
+
+ /* Fork and die. */
+ if (daemon) {
+ pid = fork();
+ if (pid < 0) {
+ openlog("CGRE", LOG_CONS, LOG_DAEMON|LOG_WARNING);
+ syslog(LOG_DAEMON|LOG_WARNING, "Failed to fork,"
+ " error: %s", strerror(errno));
+ closelog();
+ fprintf(stderr, "Failed to fork(), %s\n",
+ strerror(errno));
+ return 1;
+ } else if (pid > 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Change the file mode mask. */
+ umask(0);
+ } else {
+ cgroup_dbg("Not using daemon mode.\n");
+ pid = getpid();
+ }
+
+ cgre_start_log(logp, logf, logv);
+
+ if (!daemon) {
+ /* We can skip the rest, since we're not becoming a daemon. */
+ flog(LOG_INFO, "Proceeding with PID %d", getpid());
+ return 0;
+ } else {
+ /* Get a new SID for the child. */
+ if (setsid() < 0) {
+ flog(LOG_ERR, "Failed to get a new SID, error: %s",
+ strerror(errno));
+ return 2;
+ }
+
+ /* Change to the root directory. */
+ if (chdir("/") < 0) {
+ flog(LOG_ERR, "Failed to chdir to /, error: %s",
+ strerror(errno));
+ return 3;
+ }
+
+ /* Close standard file descriptors. */
+ close(STDIN_FILENO);
+ if (logfile != stdout)
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ }
+
+ /* If we make it this far, we're a real daemon! Or we chose not to. */
+ flog(LOG_INFO, "Proceeding with PID %d", getpid());
+ return 0;
+}
+
+/**
+ * Catch the SIGUSR2 signal and reload the rules configuration. This function
+ * makes use of the logfile and flog() to print the new rules.
+ * @param signum The signal that we caught (always SIGUSR2)
+ */
+void cgre_flash_rules(int signum)
+{
+ /* Current time */
+ time_t tm = time(0);
+
+ flog(LOG_NOTICE, "Reloading rules configuration.");
+ flog(LOG_DEBUG, "Current time: %s", ctime(&tm));
+
+ /* Ask libcgroup to reload the rules table. */
+ cgroup_reload_cached_rules();
+
+ /* Print the results of the new table to our log file. */
+ if (logfile && loglevel >= LOG_INFO) {
+ cgroup_print_rules_config(logfile);
+ fprintf(logfile, "\n");
+ }
+}
+
+/**
+ * Catch the SIGTERM and SIGINT signals so that we can exit gracefully. Before
+ * exiting, this function makes use of the logfile and flog().
+ * @param signum The signal that we caught (SIGTERM, SIGINT)
+ */
+void cgre_catch_term(int signum)
+{
+ /* Current time */
+ time_t tm = time(0);
+
+ flog(LOG_NOTICE, "Stopped CGroup Rules Engine Daemon at %s",
+ ctime(&tm));
+
+ /* Close the log file, if we opened one */
+ if (logfile && logfile != stdout)
+ fclose(logfile);
+
+ /* Close syslog */
+ if (logfacility)
+ closelog();
+
+ exit(EXIT_SUCCESS);
+}
+
+/**
+ * Parse the syslog facility as received on command line.
+ * @param arg Command line argument with the syslog facility
+ * @return the syslog facility (e.g. LOG_DAEMON) or 0 on error
+ */
+static int cgre_parse_syslog_facility(const char *arg)
+{
+ if (arg == NULL)
+ return 0;
+
+ if (strlen(arg) > 1)
+ return 0;
+
+ switch (arg[0]) {
+ case '0':
+ return LOG_LOCAL0;
+ case '1':
+ return LOG_LOCAL1;
+ case '2':
+ return LOG_LOCAL2;
+ case '3':
+ return LOG_LOCAL3;
+ case '4':
+ return LOG_LOCAL4;
+ case '5':
+ return LOG_LOCAL5;
+ case '6':
+ return LOG_LOCAL6;
+ case '7':
+ return LOG_LOCAL7;
+ case 'D':
+ return LOG_DAEMON;
+ default:
+ return 0;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ /* Patch to the log file */
+ const char *logp = NULL;
+
+ /* Syslog facility */
+ int facility = 0;
+
+ /* Verbose level */
+ int verbosity = 2;
+
+ /* For catching signals */
+ struct sigaction sa;
+
+ /* Should we daemonize? */
+ unsigned char daemon = 1;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* Command line arguments */
+ const char *short_options = "hvqf:s::ndQ";
+ struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"logfile", required_argument, NULL, 'f'},
+ {"syslog", optional_argument, NULL, 's'},
+ {"nodaemon", no_argument, NULL, 'n'},
+ {"debug", no_argument, NULL, 'd'},
+ {"nolog", no_argument, NULL, 'Q'},
+ {NULL, 0, NULL, 0}
+ };
+
+ /* Make sure the user is root. */
+ if (getuid() != 0) {
+ fprintf(stderr, "Error: Only root can start/stop the control"
+ " group rules engine daemon\n");
+ ret = 1;
+ goto finished;
+ }
+
+ while (1) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h': /* --help */
+ usage(stdout, "Help:\n");
+ ret = 0;
+ goto finished;
+
+ case 'v': /* --verbose */
+ verbosity++;
+ break;
+
+ case 'q': /* --quiet */
+ verbosity--;
+ break;
+
+ case 'Q': /* --nolog */
+ verbosity = 0;
+ break;
+
+ case 'f': /* --logfile=<filename> */
+ logp = optarg;
+ break;
+
+ case 's': /* --syslog=[facility] */
+ if (optarg) {
+ facility = cgre_parse_syslog_facility(optarg);
+ if (facility == 0) {
+ fprintf(stderr,
+ "Unknown syslog facility: %s\n",
+ optarg);
+ ret = 2;
+ goto finished;
+ }
+ } else {
+ facility = LOG_DAEMON;
+ }
+ break;
+
+ case 'n': /* --no-fork */
+ daemon = 0;
+ break;
+
+ case 'd': /* --debug */
+ /* same as -vvn */
+ daemon = 0;
+ verbosity = 4;
+ logp = "-";
+ break;
+
+ default:
+ usage(stderr, "");
+ ret = 2;
+ goto finished;
+ }
+ }
+
+ /* Initialize libcgroup. */
+ if ((ret = cgroup_init()) != 0) {
+ fprintf(stderr, "Error: libcgroup initialization failed, %d\n",
+ ret);
+ goto finished;
+ }
+
+ /* Ask libcgroup to load the configuration rules. */
+ if ((ret = cgroup_init_rules_cache()) != 0) {
+ fprintf(stderr, "Error: libcgroup failed to initialize rules"
+ "cache, %d\n", ret);
+ goto finished;
+ }
+
+ /* Now, start the daemon. */
+ ret = cgre_start_daemon(logp, facility, daemon, verbosity);
+ if (ret < 0) {
+ fprintf(stderr, "Error: Failed to launch the daemon, %d\n",
+ ret);
+ goto finished;
+ }
+
+ /*
+ * Set up the signal handler to reload the cached rules upon reception
+ * of a SIGUSR2 signal.
+ */
+ sa.sa_handler = &cgre_flash_rules;
+ sa.sa_flags = 0;
+ sa.sa_restorer = NULL;
+ sigemptyset(&sa.sa_mask);
+ if ((ret = sigaction(SIGUSR2, &sa, NULL))) {
+ flog(LOG_ERR, "Failed to set up signal handler for SIGUSR2."
+ " Error: %s", strerror(errno));
+ goto finished;
+ }
+
+ /*
+ * Set up the signal handler to catch SIGINT and SIGTERM so that we
+ * can exit gracefully.
+ */
+ sa.sa_handler = &cgre_catch_term;
+ ret = sigaction(SIGINT, &sa, NULL);
+ ret |= sigaction(SIGTERM, &sa, NULL);
+ if (ret) {
+ flog(LOG_ERR, "Failed to set up the signal handler. Error:"
+ " %s", strerror(errno));
+ goto finished;
+ }
+
+ /* Print the configuration to the log file, or stdout. */
+ if (logfile && loglevel >= LOG_INFO)
+ cgroup_print_rules_config(logfile);
+
+ flog(LOG_NOTICE, "Started the CGroup Rules Engine Daemon.");
+
+ /* We loop endlesly in this function, unless we encounter an error. */
+ ret = cgre_create_netlink_socket_process_msg();
+
+finished:
+ if (logfile && logfile != stdout)
+ fclose(logfile);
+
+ return ret;
+}
diff --git a/src/daemon/cgrulesengd.h b/src/daemon/cgrulesengd.h
new file mode 100644
index 0000000..1840143
--- /dev/null
+++ b/src/daemon/cgrulesengd.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright Red Hat Inc. 2008
+ *
+ * Author: Steve Olivieri <sjo@redhat.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef _CGRULESENGD_H
+#define _CGRULESENGD_H
+
+#include <features.h>
+
+__BEGIN_DECLS
+
+#include "config.h"
+#include "libcgroup.h"
+#include <linux/connector.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+
+/* A simple macro for printing messages only when CGROUP_DEBUG is defined. */
+#ifdef CGROUP_DEBUG
+ #define cgroup_dbg(a...) printf(a)
+#else
+ #define cgroup_dbg(a...) do {} while (0)
+#endif /* CGROUP_DEBUG */
+
+/* The following ten macros are all for the Netlink code. */
+#define SEND_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \
+ sizeof(enum proc_cn_mcast_op)))
+#define RECV_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \
+ sizeof(struct proc_event)))
+
+#define SEND_MESSAGE_SIZE (NLMSG_SPACE(SEND_MESSAGE_LEN))
+#define RECV_MESSAGE_SIZE (NLMSG_SPACE(RECV_MESSAGE_LEN))
+
+#define max(x,y) ((y)<(x)?(x):(y))
+#define min(x,y) ((y)>(x)?(x):(y))
+
+#define BUFF_SIZE (max(max(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE), 1024))
+#define MIN_RECV_SIZE (min(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE))
+
+#define PROC_CN_MCAST_LISTEN (1)
+#define PROC_CN_MCAST_IGNORE (2)
+
+/**
+ * Prints the usage information for this program and, optionally, an error
+ * message. This function uses vfprintf.
+ * @param fd The file stream to print to
+ * @param msg The error message to print (printf style)
+ * @param ... Any args to msg (printf style)
+ */
+void cgre_usage(FILE *fd, const char *msg, ...);
+
+/**
+ * Prints a formatted message (like printf()) to all log destinations.
+ * Flushes the file stream's buffer so that the message is immediately
+ * readable.
+ * @param level The log level (LOG_EMERG ... LOG_DEBUG)
+ * @param format The format for the message (printf style)
+ * @param ... Any args to format (printf style)
+ */
+void flog(int level, const char *msg, ...);
+
+/**
+ * Process an event from the kernel, and determine the correct UID/GID/PID to
+ * pass to libcgroup. Then, libcgroup will decide the cgroup to move the PID
+ * to, if any.
+ * @param ev The event to process
+ * @param type The type of event to process (part of ev)
+ * @return 0 on success, > 0 on failure
+ */
+int cgre_process_event(const struct proc_event *ev, const int type);
+
+/**
+ * Handle a netlink message. In the event of PROC_EVENT_UID or PROC_EVENT_GID,
+ * we pass the event along to cgre_process_event for further processing. All
+ * other events are ignored.
+ * @param cn_hdr The netlink message
+ * @return 0 on success, > 0 on error
+ */
+int cgre_handle_message(struct cn_msg *cn_hdr);
+
+/**
+ * Turns this program into a daemon. In doing so, we fork() and kill the
+ * parent process. Note too that stdout, stdin, and stderr are closed in
+ * daemon mode, and a file descriptor for a log file is opened.
+ * @param logp Path of the log file, NULL if no log file was specified
+ * @param logf Syslog facility, NULL if no facility was specified
+ * @param daemon False to turn off daemon mode (no fork, leave FDs open)
+ * @param logv Log verbosity, 2 is the default, 0 = no logging, 5 = everything
+ * @return 0 on success, > 0 on error
+ */
+int cgre_start_daemon(const char *logp, const int logf,
+ const unsigned char daemon, const int logv);
+
+/**
+ * Catch the SIGUSR2 signal and reload the rules configuration. This function
+ * makes use of the logfile and flog() to print the new rules.
+ * @param signum The signal that we caught (always SIGUSR2)
+ */
+void cgre_flash_rules(int signum);
+
+/**
+ * Catch the SIGTERM and SIGINT signal so that we can exit gracefully. Before
+ * exiting, this function makes use of the logfile and flog().
+ * @param signum The signal that we caught (SIGTERM, SIGINT)
+ */
+void cgre_catch_term(int signum);
+
+__END_DECLS
+
+#endif /* _CGRULESENGD_H */
+
diff --git a/src/lex.l b/src/lex.l
new file mode 100644
index 0000000..48a53b4
--- /dev/null
+++ b/src/lex.l
@@ -0,0 +1,34 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Balbir Singh <balbir@linux.vnet.ibm.com>
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+%{
+#include <string.h>
+#include "y.tab.h"
+int line_no = 1;
+
+%}
+
+%%
+\n {line_no++;}
+[ \t] {/* DO NOTHING */}
+^#.*[ \t]* {/* Comments */}
+^\*.*[ \t]* {/* Comments */}
+"mount" {return MOUNT;}
+"task" {return TASK;}
+"admin" {return ADMIN;}
+"perm" {return PERM;}
+"group" {return GROUP;}
+[a-zA-Z0-9_\-\/\.]+ {yylval.name = strdup(yytext); return ID;}
+. {return yytext[0];}
+%%
+
diff --git a/src/libcgroup-internal.h b/src/libcgroup-internal.h
new file mode 100644
index 0000000..001da1a
--- /dev/null
+++ b/src/libcgroup-internal.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright IBM Corporation. 2008
+ *
+ * Author: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+#ifndef __LIBCG_INTERNAL
+
+#define __LIBCG_INTERNAL
+
+__BEGIN_DECLS
+
+#include "config.h"
+#include <libcgroup.h>
+#include <limits.h>
+
+#define CGRULES_CONF_FILE "/etc/cgrules.conf"
+#define CGRULES_MAX_FIELDS_PER_LINE 3
+
+#define CGROUP_BUFFER_LEN (5 * FILENAME_MAX)
+
+#ifdef CGROUP_DEBUG
+#define cgroup_dbg(x...) printf(x)
+#else
+#define cgroup_dbg(x...) do {} while (0)
+#endif
+
+struct control_value {
+ char name[FILENAME_MAX];
+ char value[CG_VALUE_MAX];
+};
+
+struct cgroup_controller {
+ char name[FILENAME_MAX];
+ struct control_value *values[CG_NV_MAX];
+ int index;
+};
+
+struct cgroup {
+ char name[FILENAME_MAX];
+ struct cgroup_controller *controller[CG_CONTROLLER_MAX];
+ int index;
+ uid_t tasks_uid;
+ gid_t tasks_gid;
+ uid_t control_uid;
+ gid_t control_gid;
+};
+
+
+struct cg_mount_table_s {
+ char name[FILENAME_MAX];
+ char path[FILENAME_MAX];
+ int index;
+};
+
+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;
+};
+
+/* A rule that maps UID/GID to a cgroup */
+struct cgroup_rule {
+ uid_t uid;
+ gid_t gid;
+ char name[LOGIN_NAME_MAX];
+ char destination[FILENAME_MAX];
+ char *controllers[MAX_MNT_ELEMENTS];
+ struct cgroup_rule *next;
+};
+
+/* Container for a list of rules */
+struct cgroup_rule_list {
+ struct cgroup_rule *head;
+ struct cgroup_rule *tail;
+ int len;
+};
+
+
+/* Internal API */
+char *cg_build_path(char *name, char *path, char *type);
+
+/*
+ * config related API
+ */
+int cgroup_config_insert_cgroup(char *cg_name);
+int cgroup_config_parse_controller_options(char *controller, char *name_value);
+int cgroup_config_group_task_perm(char *perm_type, char *value);
+int cgroup_config_group_admin_perm(char *perm_type, char *value);
+int cgroup_config_insert_into_mount_table(char *name, char *mount_point);
+void cgroup_config_cleanup_mount_table(void);
+__END_DECLS
+
+#endif
diff --git a/src/libcgroup.map b/src/libcgroup.map
new file mode 100644
index 0000000..1989f90
--- /dev/null
+++ b/src/libcgroup.map
@@ -0,0 +1,56 @@
+CGROUP_0.32 {
+global:
+ cgroup_init;
+ cgroup_attach_task;
+ cgroup_modify_cgroup;
+ cgroup_create_cgroup;
+ cgroup_delete_cgroup;
+ cgroup_attach_task_pid;
+ cgroup_get_cgroup;
+ cgroup_create_cgroup_from_parent;
+ cgroup_copy_cgroup;
+ cgroup_change_cgroup_uid_gid;
+ cgroup_change_cgroup_path;
+ cgroup_new_cgroup;
+ cgroup_add_controller;
+ cgroup_free;
+ cgroup_free_controllers;
+ cgroup_add_value_string;
+ cgroup_add_value_int64;
+ cgroup_add_value_uint64;
+ cgroup_add_value_bool;
+ cgroup_compare_cgroup;
+ cgroup_compare_controllers;
+ cgroup_set_uid_gid;
+ cgroup_get_uid_gid;
+ cgroup_get_value_string;
+ cgroup_set_value_string;
+ cgroup_get_value_int64;
+ cgroup_set_value_int64;
+ cgroup_get_value_uint64;
+ cgroup_set_value_uint64;
+ cgroup_get_value_bool;
+ cgroup_set_value_bool;
+ cgroup_change_cgroup_uid_gid_flags;
+ cgroup_print_rules_config;
+ cgroup_reload_cached_rules;
+ cgroup_init_rules_cache;
+ cgroup_get_current_controller_path;
+ cgroup_config_load_config;
+local:
+ *;
+};
+
+CGROUP_0.32.1 {
+global:
+ cgroup_strerror;
+} CGROUP_0.32;
+
+CGROUP_0.33 {
+global:
+ cgroup_get_last_errno;
+ cgroup_walk_tree_begin;
+ cgroup_walk_tree_next;
+ cgroup_walk_tree_end;
+} CGROUP_0.32.1;
+
diff --git a/src/pam/pam_cgroup.c b/src/pam/pam_cgroup.c
new file mode 100644
index 0000000..c4ce633
--- /dev/null
+++ b/src/pam/pam_cgroup.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright RedHat Inc. 2008
+ *
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * Derived from pam_limits.c. Original Copyright notice follows.
+ *
+ * Copyright (c) Cristian Gafton, 1996-1997, <gafton@redhat.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * End of original copyright notice.
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <syslog.h>
+#include <libcgroup.h>
+#include <pwd.h>
+
+/*
+ * Module defines
+ */
+
+#define PAM_SM_SESSION
+
+#include <security/pam_modules.h>
+#include <security/_pam_macros.h>
+#include <security/pam_modutil.h>
+#include <security/pam_ext.h>
+
+/* argument parsing */
+
+#define PAM_DEBUG_ARG 0x0001
+
+static int _pam_parse(const pam_handle_t *pamh, int argc, const char **argv)
+{
+ int ctrl = 0;
+
+ /* step through arguments */
+ for (ctrl = 0; argc-- > 0; ++argv) {
+ if (!strcmp(*argv, "debug"))
+ ctrl |= PAM_DEBUG_ARG;
+ else
+ pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
+ }
+
+ return ctrl;
+}
+
+/* now the session stuff */
+PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ pid_t pid;
+ int ctrl, ret;
+ char *user_name;
+ struct passwd *pwd;
+
+ D(("called."));
+
+ ctrl = _pam_parse(pamh, argc, argv);
+
+ ret = pam_get_item(pamh, PAM_USER, (void *) &user_name);
+ if (user_name == NULL || ret != PAM_SUCCESS) {
+ pam_syslog(pamh, LOG_ERR, "open_session - error recovering"
+ "username");
+ return PAM_SESSION_ERR;
+ }
+
+ pwd = pam_modutil_getpwnam(pamh, user_name);
+ if (!pwd) {
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_ERR, "open_session username"
+ " '%s' does not exist", user_name);
+ return PAM_SESSION_ERR;
+ }
+
+ D(("user name is %s", user_name));
+
+ /* Initialize libcg */
+ ret = cgroup_init();
+ if (ret) {
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_ERR, "libcgroup initialization"
+ " failed");
+ return PAM_SESSION_ERR;
+ }
+
+ D(("Initialized libcgroup successfuly."));
+
+ /* Determine the pid of the task */
+ pid = getpid();
+
+ /* Note: We are using default gid here. Is there a way to determine
+ * under what egid service will be provided?
+ */
+ ret = cgroup_change_cgroup_uid_gid(pwd->pw_uid, pwd->pw_gid, pid);
+ if (ret) {
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_ERR, "Change of cgroup for process"
+ " with username %s failed.\n", user_name);
+ return PAM_SESSION_ERR;
+ }
+
+ if (ctrl & PAM_DEBUG_ARG)
+ pam_syslog(pamh, LOG_DEBUG, "Changed cgroup for process %d"
+ " with username %s.\n", pid, user_name);
+
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
+ const char **argv)
+{
+ D(("called pam_cgroup close session"));
+
+ /* nothing to do yet */
+ return PAM_SUCCESS;
+}
diff --git a/src/parse.y b/src/parse.y
new file mode 100644
index 0000000..8c7ae24
--- /dev/null
+++ b/src/parse.y
@@ -0,0 +1,269 @@
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Balbir Singh <balbir@linux.vnet.ibm.com>
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * NOTE: The grammar has been modified, not to be the most efficient, but
+ * to allow easy updation of internal data structures.
+ */
+%{
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+
+int yylex(void);
+extern int line_no;
+extern char *yytext;
+
+void yyerror(char *s)
+{
+ fprintf(stderr, "error at line number %d at %c:%s", line_no, *yytext,
+ s);
+}
+
+int yywrap(void)
+{
+ return 1;
+}
+
+%}
+
+%token ID MOUNT GROUP PERM TASK ADMIN
+
+%union {
+ char *name;
+ char chr;
+ int val;
+}
+%type <name> ID namevalue_conf
+%type <val> mountvalue_conf mount task_namevalue_conf admin_namevalue_conf
+%type <val> admin_conf task_conf task_or_admin group_conf group start
+%start start
+%%
+
+start : start group
+ {
+ $$ = $1;
+ }
+ | start mount
+ {
+ $$ = $1;
+ }
+ |
+ {
+ $$ = 1;
+ }
+ ;
+
+group : GROUP ID '{' group_conf '}'
+ {
+ $$ = $4;
+ if ($$)
+ cgroup_config_insert_cgroup($2);
+ else {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+group_conf
+ : ID '{' namevalue_conf '}'
+ {
+ $$ = cgroup_config_parse_controller_options($1, $3);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | group_conf ID '{' namevalue_conf '}'
+ {
+ $$ = cgroup_config_parse_controller_options($2, $4);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | PERM '{' task_or_admin '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+namevalue_conf
+ : ID '=' ID ';'
+ {
+ $1 = realloc($1, strlen($1) + strlen($3) + 2);
+ $1 = strncat($1, " ", strlen(" "));
+ $$ = strncat($1, $3, strlen($3));
+ free($3);
+ }
+ | namevalue_conf ID '=' ID ';'
+ {
+ int len = 0;
+ if ($1)
+ len = strlen($1);
+ $2 = realloc($2, len + strlen($2) + strlen($4) + 3);
+ $2 = strncat($2, " ", strlen(" "));
+ $$ = strncat($2, $4, strlen($4));
+ if ($1) {
+ $2 = strncat($2, ":", strlen(":"));
+ $$ = strncat($2, $1, strlen($1));
+ }
+ free($4);
+ }
+ |
+ {
+ $$ = NULL;
+ }
+ ;
+
+task_namevalue_conf
+ : ID '=' ID ';'
+ {
+ $$ = cgroup_config_group_task_perm($1, $3);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | task_namevalue_conf ID '=' ID ';'
+ {
+ $$ = $1 && cgroup_config_group_task_perm($2, $4);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+admin_namevalue_conf
+ : ID '=' ID ';'
+ {
+ $$ = cgroup_config_group_admin_perm($1, $3);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | admin_namevalue_conf ID '=' ID ';'
+ {
+ $$ = $1 && cgroup_config_group_admin_perm($2, $4);
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+task_or_admin
+ : TASK '{' task_namevalue_conf '}' admin_conf
+ {
+ $$ = $3 && $5;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ | ADMIN '{' admin_namevalue_conf '}' task_conf
+ {
+ $$ = $3 && $5;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+admin_conf: ADMIN '{' admin_namevalue_conf '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+task_conf: TASK '{' task_namevalue_conf '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+mountvalue_conf
+ : ID '=' ID ';'
+ {
+ if (!cgroup_config_insert_into_mount_table($1, $3)) {
+ cgroup_config_cleanup_mount_table();
+ $$ = 0;
+ return $$;
+ }
+ $$ = 1;
+ }
+ | mountvalue_conf ID '=' ID ';'
+ {
+ if (!cgroup_config_insert_into_mount_table($2, $4)) {
+ cgroup_config_cleanup_mount_table();
+ $$ = 0;
+ return $$;
+ }
+ $$ = 1;
+ }
+ ;
+
+mount : MOUNT '{' mountvalue_conf '}'
+ {
+ $$ = $3;
+ if (!$$) {
+ fprintf(stderr, "parsing failed at line number %d\n",
+ line_no);
+ $$ = 0;
+ return $$;
+ }
+ }
+ ;
+
+
+%%
diff --git a/src/tools/cgclassify.c b/src/tools/cgclassify.c
new file mode 100644
index 0000000..c044608
--- /dev/null
+++ b/src/tools/cgclassify.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright RedHat Inc. 2008
+ *
+ * Authors: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <libcgroup.h>
+#include <limits.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "tools-common.h"
+
+#define TEMP_BUF 81
+
+/*
+ * Go through /proc/<pid>/status file to determine the euid of the
+ * process.
+ * It returns 0 on success and negative values on failure.
+ */
+
+int euid_of_pid(pid_t pid)
+{
+ FILE *fp;
+ char path[FILENAME_MAX];
+ char buf[TEMP_BUF];
+ uid_t ruid, euid, suid, fsuid;
+
+ sprintf(path, "/proc/%d/status", pid);
+ fp = fopen(path, "r");
+ if (!fp) {
+ cgroup_dbg("Error in opening file %s:%s\n", path,
+ strerror(errno));
+ return -1;
+ }
+
+ while (fgets(buf, TEMP_BUF, fp)) {
+ if (!strncmp(buf, "Uid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", (int *)&ruid,
+ (int *)&euid, (int *)&suid, (int *)&fsuid);
+ cgroup_dbg("Scanned proc values are %d %d %d %d\n",
+ ruid, euid, suid, fsuid);
+ return euid;
+ }
+ }
+
+ /* If we are here, we could not find euid. Return error. */
+ return -1;
+}
+
+/*
+ * Go through /proc/<pid>/status file to determine the egid of the
+ * process.
+ * It returns 0 on success and negative values on failure.
+ */
+
+int egid_of_pid(pid_t pid)
+{
+ FILE *fp;
+ char path[FILENAME_MAX];
+ char buf[TEMP_BUF];
+ gid_t rgid, egid, sgid, fsgid;
+
+ sprintf(path, "/proc/%d/status", pid);
+ fp = fopen(path, "r");
+ if (!fp) {
+ cgroup_dbg("Error in opening file %s:%s\n", path,
+ strerror(errno));
+ return -1;
+ }
+
+ while (fgets(buf, TEMP_BUF, fp)) {
+ if (!strncmp(buf, "Gid:", 4)) {
+ sscanf((buf + 5), "%d%d%d%d", (int *)&rgid,
+ (int *)&egid, (int *)&sgid, (int *)&fsgid);
+ cgroup_dbg("Scanned proc values are %d %d %d %d\n",
+ rgid, egid, sgid, fsgid);
+ return egid;
+ }
+ }
+
+ /* If we are here, we could not find egid. Return error. */
+ return -1;
+}
+
+/*
+ * Change process group as specified on command line.
+ */
+int change_group_path(pid_t pid, struct cgroup_group_spec *cgroup_list[])
+{
+ int i;
+ int ret = 0;
+
+ for (i = 0; i < CG_HIER_MAX; i++) {
+ if (!cgroup_list[i])
+ break;
+
+ ret = cgroup_change_cgroup_path(cgroup_list[i]->path, pid,
+ cgroup_list[i]->controllers);
+ if (ret)
+ fprintf(stderr, "Error changing group of pid %d: %s\n",
+ pid, cgroup_strerror(ret));
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Change process group as specified in cgrules.conf.
+ */
+int change_group_uid_gid(pid_t pid)
+{
+ uid_t euid;
+ gid_t egid;
+ int ret;
+
+ /* Put pid into right cgroup as per rules in /etc/cgrules.conf */
+ euid = euid_of_pid(pid);
+ if (euid == -1) {
+ fprintf(stderr, "Error in determining euid of"
+ " pid %d\n", pid);
+ return -1;
+ }
+
+ egid = egid_of_pid(pid);
+ if (egid == -1) {
+ fprintf(stderr, "Error in determining egid of"
+ " pid %d\n", pid);
+ return -1;
+ }
+
+ /* Change the cgroup by determining the rules based on uid */
+ ret = cgroup_change_cgroup_uid_gid(euid, egid, pid);
+ if (ret) {
+ fprintf(stderr, "Error: change of cgroup failed for"
+ " pid %d: %s\n",
+ pid, cgroup_strerror(ret));
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = 0, i, exit_code = 0;
+ pid_t pid;
+ int cg_specified = 0;
+ struct cgroup_group_spec *cgroup_list[CG_HIER_MAX];
+ int c;
+
+
+ if (argc < 2) {
+ fprintf(stderr, "usage is %s "
+ "[-g <list of controllers>:<relative path to cgroup>] "
+ "<list of pids> \n",
+ argv[0]);
+ exit(2);
+ }
+
+ memset(cgroup_list, 0, sizeof(cgroup_list));
+ while ((c = getopt(argc, argv, "+g:")) > 0) {
+ switch (c) {
+ case 'g':
+ if (parse_cgroup_spec(cgroup_list, optarg)) {
+ fprintf(stderr, "cgroup controller and path"
+ "parsing failed\n");
+ return -1;
+ }
+ cg_specified = 1;
+ break;
+ default:
+ fprintf(stderr, "Invalid command line option\n");
+ exit(2);
+ break;
+ }
+ }
+
+
+ /* Initialize libcg */
+ ret = cgroup_init();
+ if (ret) {
+ fprintf(stderr, "libcgroup initialization failed:%d\n", ret);
+ return ret;
+ }
+
+ for (i = optind; i < argc; i++) {
+ pid = (uid_t) atoi(argv[i]);
+
+ if (cg_specified)
+ ret = change_group_path(pid, cgroup_list);
+ else
+ ret = change_group_uid_gid(pid);
+
+ /* if any group change fails */
+ if (ret)
+ exit_code = 1;
+ }
+ return exit_code;
+
+}
diff --git a/src/tools/cgconfig.c b/src/tools/cgconfig.c
new file mode 100644
index 0000000..cc33ad9
--- /dev/null
+++ b/src/tools/cgconfig.c
@@ -0,0 +1,74 @@
+
+/*
+ * Copyright IBM Corporation. 2007
+ *
+ * Authors: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ * Balbir Singh <balbir@linux.vnet.ibm.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Code initiated and designed by Dhaval Giani. All faults are most likely
+ * his mistake.
+ */
+
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+
+static void usage(char *progname)
+{
+ printf("Usage: %s [OPTION] [FILE]\n", basename(progname));
+ printf("Parse and load the specified cgroups configuration file\n");
+ printf("\n");
+ printf(" -h, --help Display this help\n");
+ printf(" -l, --load=FILE Parse and load the cgroups configuration file\n");
+ exit(2);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ char filename[PATH_MAX];
+ int ret;
+ static struct option options[] = {
+ {"help", 0, 0, 'h'},
+ {"load", 1, 0, 'l'},
+ {0, 0, 0, 0}
+ };
+
+ if (argc < 2)
+ usage(argv[0]); /* usage() exits */
+
+ while ((c = getopt_long(argc, argv, "hl:", options, NULL)) > 0) {
+ switch (c) {
+ case 'h':
+ usage(argv[0]);
+ break;
+ case 'l':
+ strncpy(filename, optarg, PATH_MAX);
+ ret = cgroup_config_load_config(filename);
+ if (ret) {
+ printf("Loading configuration file %s "
+ "failed\n%s\n", filename,
+ cgroup_strerror(ret));
+ exit(3);
+ }
+ return 0;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/src/tools/cgexec.c b/src/tools/cgexec.c
new file mode 100644
index 0000000..167d873
--- /dev/null
+++ b/src/tools/cgexec.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright RedHat Inc. 2008
+ *
+ * Authors: Vivek Goyal <vgoyal@redhat.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <errno.h>
+#include <grp.h>
+#include <libcgroup.h>
+#include <limits.h>
+#include <pwd.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "tools-common.h"
+
+int main(int argc, char *argv[])
+{
+ int ret = 0, i;
+ int cg_specified = 0;
+ uid_t euid;
+ pid_t pid;
+ gid_t egid;
+ char c;
+ struct cgroup_group_spec *cgroup_list[CG_HIER_MAX];
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage is %s"
+ " [-g <list of controllers>:<relative path to cgroup>]"
+ " command [arguments] \n",
+ argv[0]);
+ exit(2);
+ }
+
+ memset(cgroup_list, 0, sizeof(cgroup_list));
+
+ while ((c = getopt(argc, argv, "+g:")) > 0) {
+ switch (c) {
+ case 'g':
+ if (parse_cgroup_spec(cgroup_list, optarg)) {
+ fprintf(stderr, "cgroup controller and path"
+ "parsing failed\n");
+ return -1;
+ }
+ cg_specified = 1;
+ break;
+ default:
+ fprintf(stderr, "Invalid command line option\n");
+ exit(1);
+ break;
+ }
+ }
+
+ /* Executable name */
+ if (!argv[optind]) {
+ fprintf(stderr, "No command specified\n");
+ exit(1);
+ }
+
+ /* Initialize libcg */
+ ret = cgroup_init();
+ if (ret) {
+ fprintf(stderr, "libcgroup initialization failed:%d", ret);
+ return ret;
+ }
+
+ euid = geteuid();
+ egid = getegid();
+ pid = getpid();
+
+ if (cg_specified) {
+ /*
+ * User has specified the list of control group and
+ * controllers
+ * */
+ for (i = 0; i < CG_HIER_MAX; i++) {
+ if (!cgroup_list[i])
+ break;
+
+ ret = cgroup_change_cgroup_path(cgroup_list[i]->path,
+ pid,
+ cgroup_list[i]->controllers);
+ if (ret) {
+ fprintf(stderr,
+ "cgroup change of group failed\n");
+ return ret;
+ }
+ }
+ } else {
+
+ /* Change the cgroup by determining the rules based on euid */
+ ret = cgroup_change_cgroup_uid_gid(euid, egid, pid);
+ if (ret) {
+ fprintf(stderr, "cgroup change of group failed\n");
+ return ret;
+ }
+ }
+
+ /* Now exec the new process */
+ ret = execvp(argv[optind], &argv[optind]);
+ if (ret == -1) {
+ fprintf(stderr, "%s", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/tools/tools-common.c b/src/tools/tools-common.c
new file mode 100644
index 0000000..0bb666f
--- /dev/null
+++ b/src/tools/tools-common.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright Red Hat, Inc. 2009
+ *
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ * Jan Safranek <jsafrane@redhat.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libcgroup.h>
+#include "tools-common.h"
+
+int parse_cgroup_spec(struct cgroup_group_spec *cdptr[], char *optarg)
+{
+ struct cgroup_group_spec *ptr;
+ int i, j;
+ char *cptr, *pathptr, *temp;
+
+ ptr = *cdptr;
+
+ /* Find first free entry inside the cgroup data array */
+ for (i = 0; i < CG_HIER_MAX; i++, ptr++) {
+ if (!cdptr[i])
+ break;
+ }
+
+ if (i == CG_HIER_MAX) {
+ /* No free slot found */
+ fprintf(stderr, "Max allowed hierarchies %d reached\n",
+ CG_HIER_MAX);
+ return -1;
+ }
+
+ /* Extract list of controllers */
+ cptr = strtok(optarg, ":");
+ cgroup_dbg("list of controllers is %s\n", cptr);
+ if (!cptr)
+ return -1;
+
+ /* Extract cgroup path */
+ pathptr = strtok(NULL, ":");
+ cgroup_dbg("cgroup path is %s\n", pathptr);
+ if (!pathptr)
+ return -1;
+
+ /* instanciate cgroup_data. */
+ cdptr[i] = malloc(sizeof(struct cgroup_group_spec));
+ if (!cdptr[i]) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return -1;
+ }
+ /* Convert list of controllers into an array of strings. */
+ j = 0;
+ do {
+ if (j == 0)
+ temp = strtok(cptr, ",");
+ else
+ temp = strtok(NULL, ",");
+
+ if (temp) {
+ cdptr[i]->controllers[j] = strdup(temp);
+ if (!cdptr[i]->controllers[j]) {
+ free(cdptr[i]);
+ fprintf(stderr, "%s\n", strerror(errno));
+ return -1;
+ }
+ }
+ j++;
+ } while (temp);
+
+ /* Store path to the cgroup */
+ strncpy(cdptr[i]->path, pathptr, FILENAME_MAX);
+ cdptr[i]->path[FILENAME_MAX-1] = '\0';
+
+ return 0;
+}
diff --git a/src/tools/tools-common.h b/src/tools/tools-common.h
new file mode 100644
index 0000000..3437973
--- /dev/null
+++ b/src/tools/tools-common.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright Red Hat, Inc. 2009
+ *
+ * Author: Vivek Goyal <vgoyal@redhat.com>
+ * Jan Safranek <jsafrane@redhat.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#ifndef __TOOLS_COMMON
+
+#define __TOOLS_COMMON
+
+#include "config.h"
+#include <libcgroup.h>
+
+#ifdef CGROUP_DEBUG
+#define cgroup_dbg(x...) printf(x)
+#else
+#define cgroup_dbg(x...) do {} while (0)
+#endif
+
+/**
+ * Auxiliary specifier of group, used to store parsed command line options.
+ */
+struct cgroup_group_spec {
+ char path[FILENAME_MAX];
+ char *controllers[CG_CONTROLLER_MAX];
+};
+
+
+/**
+ * Parse command line option with group specifier into provided data structure.
+ * The option must have form of 'controller1,controller2,..:group_name'.
+ *
+ * The parsed list of controllers and group name is added at the end of
+ * provided cdptr.
+ *
+ * @param cdptr Target data structure to fill. New item is allocated and added
+ * at the end.
+ * @param optarg Argument to parse.
+ * @return 0 on success, != 0 on error.
+ */
+int parse_cgroup_spec(struct cgroup_group_spec *cdptr[], char *optarg);
+
+
+#endif /* TOOLS_COMMON */
diff --git a/src/wrapper.c b/src/wrapper.c
new file mode 100644
index 0000000..2f8fcf2
--- /dev/null
+++ b/src/wrapper.c
@@ -0,0 +1,557 @@
+/*
+ * Copyright IBM Corporation. 2008
+ *
+ * Author: Dhaval Giani <dhaval@linux.vnet.ibm.com>
+ *
+ * This program 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 program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Code initiated and designed by Dhaval Giani. All faults are most likely
+ * his mistake.
+ */
+
+#include <libcgroup.h>
+#include <libcgroup-internal.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct cgroup *cgroup_new_cgroup(const char *name)
+{
+ struct cgroup *cgroup = calloc(1, sizeof(struct cgroup));
+
+ if (!cgroup)
+ return NULL;
+
+ strncpy(cgroup->name, name, sizeof(cgroup->name));
+
+ return cgroup;
+}
+
+struct cgroup_controller *cgroup_add_controller(struct cgroup *cgroup,
+ const char *name)
+{
+ int i;
+ struct cgroup_controller *controller;
+
+ if (!cgroup)
+ return NULL;
+
+ /*
+ * Still not sure how to handle the failure here.
+ */
+ if (cgroup->index >= CG_CONTROLLER_MAX)
+ return NULL;
+
+ /*
+ * Still not sure how to handle the failure here.
+ */
+ for (i = 0; i < cgroup->index; i++) {
+ if (strncmp(name, cgroup->controller[i]->name,
+ sizeof(cgroup->controller[i]->name)) == 0)
+ return NULL;
+ }
+
+ controller = calloc(1, sizeof(struct cgroup_controller));
+
+ if (!controller)
+ return NULL;
+
+ strncpy(controller->name, name, sizeof(controller->name));
+ controller->index = 0;
+
+ cgroup->controller[cgroup->index] = controller;
+ cgroup->index++;
+
+ return controller;
+}
+
+void cgroup_free_controllers(struct cgroup *cgroup)
+{
+ int i, j;
+
+ if (!cgroup)
+ return;
+
+ for (i = 0; i < cgroup->index; i++) {
+ for (j = 0; j < cgroup->controller[i]->index; j++)
+ free(cgroup->controller[i]->values[j]);
+ free(cgroup->controller[i]);
+ }
+}
+
+void cgroup_free(struct cgroup **cgroup)
+{
+ struct cgroup *cg = *cgroup;
+
+ /*
+ * Passing NULL pointers is OK. We just return.
+ */
+ if (!cg)
+ return;
+
+ cgroup_free_controllers(cg);
+ free(cg);
+ *cgroup = NULL;
+}
+
+int cgroup_add_value_string(struct cgroup_controller *controller,
+ const char *name, const char *value)
+{
+ int i;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name, sizeof(cntl_value->name));
+ strncpy(cntl_value->value, value, sizeof(cntl_value->value));
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+}
+
+int cgroup_add_value_int64(struct cgroup_controller *controller,
+ const char *name, int64_t value)
+{
+ int i;
+ unsigned ret;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name,
+ sizeof(cntl_value->name));
+ ret = snprintf(cntl_value->value,
+ sizeof(cntl_value->value), "%" PRId64, value);
+
+ if (ret >= sizeof(cntl_value->value))
+ return ECGINVAL;
+
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+
+}
+
+int cgroup_add_value_uint64(struct cgroup_controller *controller,
+ const char *name, u_int64_t value)
+{
+ int i;
+ unsigned ret;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name, sizeof(cntl_value->name));
+ ret = snprintf(cntl_value->value, sizeof(cntl_value->value),
+ "%" PRIu64, value);
+
+ if (ret >= sizeof(cntl_value->value))
+ return ECGINVAL;
+
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+
+}
+
+int cgroup_add_value_bool(struct cgroup_controller *controller,
+ const char *name, bool value)
+{
+ int i;
+ unsigned ret;
+ struct control_value *cntl_value;
+
+ if (!controller)
+ return ECGINVAL;
+
+ if (controller->index >= CG_VALUE_MAX)
+ return ECGMAXVALUESEXCEEDED;
+
+ for (i = 0; i < controller->index && i < CG_VALUE_MAX; i++) {
+ if (!strcmp(controller->values[i]->name, name))
+ return ECGVALUEEXISTS;
+ }
+
+ cntl_value = calloc(1, sizeof(struct control_value));
+
+ if (!cntl_value)
+ return ECGCONTROLLERCREATEFAILED;
+
+ strncpy(cntl_value->name, name, sizeof(cntl_value->name));
+
+ if (value)
+ ret = snprintf(cntl_value->value, sizeof(cntl_value->value), "1");
+ else
+ ret = snprintf(cntl_value->value, sizeof(cntl_value->value), "0");
+
+ if (ret >= sizeof(cntl_value->value))
+ return ECGINVAL;
+
+ controller->values[controller->index] = cntl_value;
+ controller->index++;
+
+ return 0;
+}
+
+int cgroup_compare_controllers(struct cgroup_controller *cgca,
+ struct cgroup_controller *cgcb)
+{
+ int i;
+
+ if (!cgca || !cgcb)
+ return ECGINVAL;
+
+ if (strcmp(cgca->name, cgcb->name))
+ return ECGCONTROLLERNOTEQUAL;
+
+ if (cgca->index != cgcb->index)
+ return ECGCONTROLLERNOTEQUAL;
+
+ for (i = 0; i < cgca->index; i++) {
+ struct control_value *cva = cgca->values[i];
+ struct control_value *cvb = cgcb->values[i];
+
+ if (strcmp(cva->name, cvb->name))
+ return ECGCONTROLLERNOTEQUAL;
+
+ if (strcmp(cva->value, cvb->value))
+ return ECGCONTROLLERNOTEQUAL;
+ }
+ return 0;
+}
+
+int cgroup_compare_cgroup(struct cgroup *cgroup_a, struct cgroup *cgroup_b)
+{
+ int i;
+
+ if (!cgroup_a || !cgroup_b)
+ return ECGINVAL;
+
+ if (strcmp(cgroup_a->name, cgroup_b->name))
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->tasks_uid != cgroup_b->tasks_uid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->tasks_gid != cgroup_b->tasks_gid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->control_uid != cgroup_b->control_uid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->control_gid != cgroup_b->control_gid)
+ return ECGROUPNOTEQUAL;
+
+ if (cgroup_a->index != cgroup_b->index)
+ return ECGROUPNOTEQUAL;
+
+ for (i = 0; i < cgroup_a->index; i++) {
+ struct cgroup_controller *cgca = cgroup_a->controller[i];
+ struct cgroup_controller *cgcb = cgroup_b->controller[i];
+
+ if (cgroup_compare_controllers(cgca, cgcb))
+ return ECGCONTROLLERNOTEQUAL;
+ }
+ return 0;
+}
+
+int cgroup_set_uid_gid(struct cgroup *cgroup, uid_t tasks_uid, gid_t tasks_gid,
+ uid_t control_uid, gid_t control_gid)
+{
+ if (!cgroup)
+ return ECGINVAL;
+
+ cgroup->tasks_uid = tasks_uid;
+ cgroup->tasks_gid = tasks_gid;
+ cgroup->control_uid = control_uid;
+ cgroup->control_gid = control_gid;
+
+ return 0;
+}
+
+int cgroup_get_uid_gid(struct cgroup *cgroup, uid_t *tasks_uid,
+ gid_t *tasks_gid, uid_t *control_uid, gid_t *control_gid)
+{
+ if (!cgroup)
+ return ECGINVAL;
+
+ *tasks_uid = cgroup->tasks_uid;
+ *tasks_gid = cgroup->tasks_gid;
+ *control_uid = cgroup->control_uid;
+ *control_gid = cgroup->control_uid;
+
+ return 0;
+}
+
+struct cgroup_controller *cgroup_get_controller(struct cgroup *cgroup,
+ const char *name)
+{
+ int i;
+ struct cgroup_controller *cgc;
+
+ if (!cgroup)
+ return NULL;
+
+ for (i = 0; i < cgroup->index; i++) {
+ cgc = cgroup->controller[i];
+
+ if (!strcmp(cgc->name, name))
+ return cgc;
+ }
+
+ return NULL;
+}
+
+int cgroup_get_value_string(struct cgroup_controller *controller,
+ const char *name, char **value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ *value = strdup(val->value);
+
+ if (!*value)
+ return ECGOTHER;
+
+ return 0;
+ }
+ }
+
+ return ECGROUPVALUENOTEXIST;
+
+}
+
+int cgroup_set_value_string(struct cgroup_controller *controller,
+ const char *name, const char *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+ if (!strcmp(val->name, name)) {
+ strncpy(val->value, value, CG_VALUE_MAX);
+ return 0;
+ }
+ }
+
+ return cgroup_add_value_string(controller, name, value);
+}
+
+int cgroup_get_value_int64(struct cgroup_controller *controller,
+ const char *name, int64_t *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+
+ if (sscanf(val->value, "%" SCNd64, value) != 1)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return ECGROUPVALUENOTEXIST;
+}
+
+int cgroup_set_value_int64(struct cgroup_controller *controller,
+ const char *name, int64_t value)
+{
+ int i;
+ unsigned ret;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ ret = snprintf(val->value,
+ sizeof(val->value), "%" PRId64, value);
+
+ if (ret >= sizeof(val->value) || ret < 0)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return cgroup_add_value_int64(controller, name, value);
+}
+
+int cgroup_get_value_uint64(struct cgroup_controller *controller,
+ const char *name, u_int64_t *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+ if (!strcmp(val->name, name)) {
+
+ if (sscanf(val->value, "%" SCNu64, value) != 1)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return ECGROUPVALUENOTEXIST;
+}
+
+int cgroup_set_value_uint64(struct cgroup_controller *controller,
+ const char *name, u_int64_t value)
+{
+ int i;
+ unsigned ret;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ ret = snprintf(val->value,
+ sizeof(val->value), "%" PRIu64, value);
+
+ if (ret >= sizeof(val->value) || ret < 0)
+ return ECGINVAL;
+
+ return 0;
+ }
+ }
+
+ return cgroup_add_value_uint64(controller, name, value);
+}
+
+int cgroup_get_value_bool(struct cgroup_controller *controller,
+ const char *name, bool *value)
+{
+ int i;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ int cgc_val;
+
+ if (sscanf(val->value, "%d", &cgc_val) != 1)
+ return ECGINVAL;
+
+ if (cgc_val)
+ *value = true;
+ else
+ *value = false;
+
+ return 0;
+ }
+ }
+ return ECGROUPVALUENOTEXIST;
+}
+
+int cgroup_set_value_bool(struct cgroup_controller *controller,
+ const char *name, bool value)
+{
+ int i;
+ unsigned ret;
+
+ if (!controller)
+ return ECGINVAL;
+
+ for (i = 0; i < controller->index; i++) {
+ struct control_value *val = controller->values[i];
+
+ if (!strcmp(val->name, name)) {
+ if (value) {
+ ret = snprintf(val->value,
+ sizeof(val->value), "1");
+ } else {
+ ret = snprintf(val->value,
+ sizeof(val->value), "0");
+ }
+
+ if (ret >= sizeof(val->value) || ret < 0)
+ return ECGINVAL;
+
+ return 0;
+
+ }
+ }
+
+ return cgroup_add_value_bool(controller, name, value);
+}