summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile26
-rw-r--r--Makefile.in14
-rw-r--r--README_daemon178
-rw-r--r--api.c808
-rw-r--r--cgrulesengd.c625
-rw-r--r--cgrulesengd.h124
-rw-r--r--libcgroup-internal.h18
-rw-r--r--libcgroup.h79
-rw-r--r--samples/cgred.conf19
-rw-r--r--scripts/init.d/cgred129
-rw-r--r--tests/Makefile7
-rw-r--r--tests/setuid.c75
12 files changed, 1906 insertions, 196 deletions
diff --git a/Makefile b/Makefile
index 2819e8b..380dde7 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ bindir=${exec_prefix}/bin
sbindir=${exec_prefix}/sbin
libdir=${exec_prefix}/lib
includedir=${prefix}/include
-prefix=/usr/local
+prefix=/usr
exec_prefix=${prefix}
INSTALL=install
INSTALL_DATA=install -m 644
@@ -29,18 +29,22 @@ PACKAGE_VERSION=0.31
CFLAGS=-g -O2 $(INC) -DPACKAGE_VERSION=$(PACKAGE_VERSION)
VERSION=1
-all: libcgroup.so cgconfigparser cgexec cgclassify
+all: libcgroup.so cgconfigparser cgexec cgclassify cgrulesengd
cgconfigparser: libcgroup.so config.c y.tab.c lex.yy.c libcgroup.h file-ops.c
$(CC) $(CFLAGS) -o $@ y.tab.c lex.yy.c config.c file-ops.c \
- $(LDFLAGS) $(LIBS)
+ $(LDFLAGS) $(LIBS)
cgexec: libcgroup.so cgexec.c libcgroup.h
$(CC) $(CFLAGS) -Wall -o $@ cgexec.c $(LDFLAGS) $(LIBS)
-cgclassify: cgclassify.c
+cgclassify: libcgroup.so cgclassify.c
$(CC) $(CFLAGS) -Wall -o $@ cgclassify.c $(LDFLAGS) $(LIBS)
+cgrulesengd: libcgroup.so libcgroup.h cgrulesengd.c cgrulesengd.h
+ $(CC) -std=gnu99 $(DEBUG) $(CFLAGS) -Wall -o $@ cgrulesengd.c \
+ $(LDFLAGS) $(LIBS)
+
y.tab.c: parse.y lex.yy.c
$(YACC) -v -d parse.y
@@ -49,7 +53,7 @@ lex.yy.c: lex.l
libcgroup.so: api.c libcgroup.h wrapper.c
$(CC) $(CFLAGS) -shared -fPIC -Wl,--soname,$@.$(VERSION) -o $@ api.c \
- wrapper.c
+ wrapper.c
ln -sf $@ $@.$(VERSION)
test:
@@ -59,14 +63,15 @@ pam_cgroup.so: pam_cgroup.c
$(CC) $(CFLAGS) -shared -fPIC -Wall -o $@ pam_cgroup.c $(LDFLAGS) \
$(LIBS) -lpam
-install: libcgroup.so cgexec cgclassify
+install: libcgroup.so cgexec cgclassify cgconfigparser
$(INSTALL_DATA) -D libcgroup.h $(DESTDIR)$(includedir)/libcgroup.h
$(INSTALL) -D libcgroup.so $(DESTDIR)$(libdir)/libcgroup-$(PACKAGE_VERSION).so
ln -sf libcgroup-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libcgroup.so.$(VERSION)
ln -sf libcgroup.so.$(VERSION) $(DESTDIR)$(libdir)/libcgroup.so
$(INSTALL) -D cgconfigparser $(DESTDIR)$(sbindir)
- $(INSTALL) cgexec $(DESTDIR)$(bindir)/cgexec
- $(INSTALL) cgclassify $(DESTDIR)$(bindir)/cgclassify
+ $(INSTALL) -D cgexec $(DESTDIR)$(bindir)/cgexec
+ $(INSTALL) -D cgclassify $(DESTDIR)$(bindir)/cgclassify
+ $(INSTALL) -D cgrulesengd $(DESTDIR)$(bindir)/cgrulesengd
uninstall: libcgroup.so
rm -f $(DESTDIR)$(includedir)/libcgroup.h
@@ -76,8 +81,9 @@ uninstall: libcgroup.so
rm -f $(DESTDIR)$(sbindir)/cgconfigparser
rm -f $(DESTDIR)$(bindir)/cgexec
rm -f $(DESTDIR)$(bindir)/cgclassify
+ rm -f $(DESTDIR)$(bindir)/cgrulesengd
clean:
- \rm -f y.tab.c y.tab.h lex.yy.c y.output libcgroup.so cgclassify\
+ \rm -f y.tab.c y.tab.h lex.yy.c y.output libcgroup.so cgclassify \
libcgroup.so.$(VERSION) cgconfigparser config.log config.status cgexec \
- pam_cgroup.so
+ pam_cgroup.so cgrulesengd
diff --git a/Makefile.in b/Makefile.in
index f875578..aa68f4e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -29,11 +29,11 @@ PACKAGE_VERSION=@PACKAGE_VERSION@
CFLAGS=@CFLAGS@ $(INC) -DPACKAGE_VERSION=$(PACKAGE_VERSION)
VERSION=1
-all: libcgroup.so cgconfigparser cgexec cgclassify
+all: libcgroup.so cgconfigparser cgexec cgclassify cgrulesengd
cgconfigparser: libcgroup.so config.c y.tab.c lex.yy.c libcgroup.h file-ops.c
$(CC) $(CFLAGS) -o $@ y.tab.c lex.yy.c config.c file-ops.c \
- $(LDFLAGS) $(LIBS)
+ $(LDFLAGS) $(LIBS)
cgexec: libcgroup.so cgexec.c libcgroup.h
$(CC) $(CFLAGS) -Wall -o $@ cgexec.c $(LDFLAGS) $(LIBS)
@@ -41,6 +41,10 @@ cgexec: libcgroup.so cgexec.c libcgroup.h
cgclassify: libcgroup.so cgclassify.c
$(CC) $(CFLAGS) -Wall -o $@ cgclassify.c $(LDFLAGS) $(LIBS)
+cgrulesengd: libcgroup.so libcgroup.h cgrulesengd.c cgrulesengd.h
+ $(CC) -std=gnu99 $(DEBUG) $(CFLAGS) -Wall -o $@ cgrulesengd.c \
+ $(LDFLAGS) $(LIBS)
+
y.tab.c: parse.y lex.yy.c
$(YACC) -v -d parse.y
@@ -49,7 +53,7 @@ lex.yy.c: lex.l
libcgroup.so: api.c libcgroup.h wrapper.c
$(CC) $(CFLAGS) -shared -fPIC -Wl,--soname,$@.$(VERSION) -o $@ api.c \
- wrapper.c
+ wrapper.c
ln -sf $@ $@.$(VERSION)
test:
@@ -67,6 +71,7 @@ install: libcgroup.so cgexec cgclassify cgconfigparser
$(INSTALL) -D cgconfigparser $(DESTDIR)$(sbindir)
$(INSTALL) -D cgexec $(DESTDIR)$(bindir)/cgexec
$(INSTALL) -D cgclassify $(DESTDIR)$(bindir)/cgclassify
+ $(INSTALL) -D cgrulesengd $(DESTDIR)$(bindir)/cgrulesengd
uninstall: libcgroup.so
rm -f $(DESTDIR)$(includedir)/libcgroup.h
@@ -76,8 +81,9 @@ uninstall: libcgroup.so
rm -f $(DESTDIR)$(sbindir)/cgconfigparser
rm -f $(DESTDIR)$(bindir)/cgexec
rm -f $(DESTDIR)$(bindir)/cgclassify
+ rm -f $(DESTDIR)$(bindir)/cgrulesengd
clean:
\rm -f y.tab.c y.tab.h lex.yy.c y.output libcgroup.so cgclassify \
libcgroup.so.$(VERSION) cgconfigparser config.log config.status cgexec \
- pam_cgroup.so
+ pam_cgroup.so cgrulesengd
diff --git a/README_daemon b/README_daemon
new file mode 100644
index 0000000..1ff7a33
--- /dev/null
+++ b/README_daemon
@@ -0,0 +1,178 @@
+DESCRIPTION
+===========
+The CGroup Rules Engine Daemon is a tool that will automatically place tasks
+into the correct cgroup based on UID/GID events from the kernel. It will not
+automatically classify tasks that are already running, but it will classify
+any new tasks, and any tasks which change their UID/GID. Note that we use
+the euid and egid, not the ruid and rgid.
+
+Unlike other tools, cgrulesengd caches the rules configuration in a data
+structure (it's actually just a FIFO linked list) so that it doesn't need to
+parse the configuration file more than once. This should be much faster than
+parsing the rules for each UID/GID event. Eventually, this caching logic
+should be part of libcgroup, so that any other program can take advantage of it
+(and so that all programs are using the same table). The configuration can
+be reloaded without stopping the daemon (more information below).
+
+WHY A DAEMON?
+=============
+A daemon is easy to use, and allows an administrator to ensure that all tasks
+are classified into the correct cgroups without constantly monitoring the
+system. The daemon is transparent to the users, and does not require any
+modifications to existing userspace programs. Finally, the daemon can be
+started and stopped at any time, including at boot time with other services.
+Thus, sytem administrators can decide not to use the daemon if they choose.
+
+Most importantly, some programs create new users and/or run scripts,
+threads, etc. as those users using suexec(). This call does not go through
+PAM, so these scripts would continue running in the same cgroup as the parent
+program. This behavior is likely not ideal, and the daemon would solve this
+problem.
+
+Apache does this. Apache creates a user called 'apache' and uses setuid() to
+launch tasks as that user. This does not go through PAM, so without a daemon,
+these tasks would continue to run in the 'root' cgroup rather than in the
+'apache' or 'webserver' cgroup. The daemon fixes this problem by catching the
+setuid() call and moving the tasks into the correct cgroup.
+
+We would ask Apache to modify their software to interface with libcgroup, but
+this solution is less than optimal because a lot of userspace software would
+have to be changed, and some authors might intentionally not interact with
+libcgroup, which could create an exploit. The daemon is a simple, transparent
+solution.
+
+USING THE DAEMON
+================
+The daemon can be used as a service with the cgred script, which is shipped
+as scripts/init.d/cgred. This script should be installed as /etc/init.d/cgred
+and used like any other service. To start the daemon,
+ /etc/init.d/cgred start
+To stop it,
+ /etc/init.d/cgred stop
+The restart (stop,start), condrestart (same as restart, but only if the daemon
+was already started), and status (print whether the daemon is started or
+stopped) commands are also supported. An additional command, "flash", allows
+you to reload the configuration file without stopping the daemon.
+ /etc/init.d/cgred flash
+The cgred script automatically loads configuration from /etc/cgred.d/cgred.conf,
+which is shipped as samples/cgred.conf. See that file for more information.
+
+If you choose not to run the daemon as a service, the following options are
+currently supported:
+ --nodaemon Do not run as a daemon
+ --nolog Write log output to stdout instead of a log file
+ --config [FILE] Read rules configuration from FILE instead of
+ /etc/cgrules.conf
+
+You can ask the daemon to reload the configuration by sending it SIGUSR2. The
+easiest way to do this is with the 'kill' command:
+ kill -s SIGUSR2 [PID]
+
+TESTING
+=======
+The program setuid (found in tests/setuid.c) can help you test the daemon. By
+default, this program attempts to change its UID to root and then idles until
+you kill it. You can change the default behavior to use a different UID, or
+you can uncomment the second block of code to instead attempt to change the
+GID.
+
+In order to make sure that everything works, I used the following rules:
+ sjo cpu default
+ cgtest cpu cgtest
+ % memory default
+ @cgroup cpu,memory cgtest
+ peter cpu test1
+ % memory test2
+ @root * default
+ * * default
+
+The users 'sjo' and 'cgtest' were normal users. 'peter' is not a user on the
+system. The group 'cgroup' is a group containing sjo,root,cgtest as members,
+and the group 'root' contains only root. The cgroups 'default' and 'cgtest'
+exist, while 'test1' and 'test2' do not. Currently, the daemon does not check
+for the existance of 'test1', though this would be easier to do once the
+parsing and caching logic is moved into libcgroup.
+
+I ran the following tests, all of which were successful:
+ - set UID to sjo (should move cpu controller into default)
+ - set UID to root (should move cpu,memory controllers into cgtest)
+ - set UID to cgtest (should move cpu controller into cgtest, memory
+ controller into default)
+ - set GID to root (should move all controllers into default)
+ - set GID to cgroup (should move cpu, memory into cgtest)
+ - set GID to users (should move all controllers into default)
+
+The parsing logic will skip the 'peter' rule as well as its multi-line
+components (in this case "% memory test2"), because the user does not exist.
+This should work for group rules, too. Attempting to setuid() or setgid() to a
+user/group that doesn't exist will just return an error and not generate a
+kernel event of the PROC_EVENT_UID or PROC_EVENT_GID type, so the daemon won't
+do anything for it.
+
+CONCERNS/ISSUES
+===============
+ - Netlink can be unreliable, and the daemon might miss an event if the buffer
+ is full. One possible solution is to have one or two files that the kernel
+ can queue UID/GID changes in, and have the daemon read those files whenever
+ they are updated. From testing, this does not actually appear to be a real
+ problem, but it could become one with faster machines.
+ - The daemon does not care for namespaces at all, which can cause conflicts
+ with containers. If a user places his tasks into exec-based cgroups (such
+ as 'network' and 'development'), the daemon will not realize this and will
+ simply place them into the user's cgroup (so, sjo/ instead of sjo/network/).
+
+CHANGELOG
+=========
+V9:
+ - Updated documentation, because it was very old and incorrect.
+ - Reverted the changes to cgexec and cgclassify.
+ - New API function: cgroup_change_cgroup_uid_gid_flags().
+ - Deprecated cgroup_change_cgroup_uid_gid().
+ - Rewrote some of the rule matching and execution logic in api.c to be
+ faster, and easier to read.
+ - Changes all negative return values to positive values. As a side effect,
+ cgroup_parse_rules() now returns -1 when we get a match and we are using
+ non-cached rules.
+ - Changes CGROUP_FUSECACHE to CGFLAG_USECACHE.
+ - Flags are now enumerated (cgflags), instead of #defines.
+
+V8:
+ - Moved the event-handling logic back into the daemon, where it should be.
+ - Changed cgroup_parse_rules() to work with cached rules or non-cached rules.
+ The other parsing function is no longer needed, and should be deprecated.
+ - Non-cached rules now work with the same structs as cached rules.
+ - Modified cgroup_change_cgroup_uid_gid() with a new 'flags' parameter.
+ Currently, the only flag is "CGROUP_FUSECACHE" to use the cached rules logic
+ (or not).
+ - Added cgroup_rules_loaded() boolean, to check whether the cached rules have
+ been loaded yet, and cgroup_init_rules_cache() to load them.
+ - Modified cgexec and cgclassify to work with the new
+ cgroup_change_cgroup_uid_gid().
+
+V7:
+ - Moved parsing and caching logic into libcgroup.
+ - Added locking mechanisms around the list of rules.
+ - Cleaned up #includes in cgrulesegnd.[h,c].
+ - Added notification if netlink receive queue overflows.
+ - Added logic to catch SIGINT in addition to SIGTERM.
+ - New API functions:
+ - cgroup_free_rule(struct cgroup_rule*)
+ - cgroup_free_rule_list(struct cgroup_rule_list*)
+ - cgroup_parse_rules(void)
+ - cgroup_print_rules_config(FILE*)
+ - cgroup_reload_cached_rules(void)
+ - cgroup_change_cgroup_event(struct proc_event*, int, FILE*)
+
+V6:
+ - Wrote new parsing logic, which is cleaner and simpler.
+ - Added cgred script to enable using the daemon as a service.
+ - Wrote caching logic to cache rules table.
+ - Added the ability to force a reload of the rules table with SIGUSR2 signal.
+ - Added two structures to libcgroup: cgre_rule and cgre_rules_list
+ - New API function: cgroup_reload_cached_rules, which reloads the rules table.
+ - Added logging capabilities (default log is /root/cgrulesengd.conf)
+
+TODO
+====
+ - Find a way to replace Netlink, or at least clean up that code.
+ - Find a solution to the namespace problem.
diff --git a/api.c b/api.c
index fc7e18a..60a458c 100644
--- a/api.c
+++ b/api.c
@@ -58,6 +58,18 @@ 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;
+
static int cg_chown_file(FTS *fts, FTSENT *ent, uid_t owner, gid_t group)
{
int ret = 0;
@@ -121,6 +133,359 @@ static int cgroup_test_subsys_mounted(const char *name)
}
/**
+ * 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) {
+ 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)) {
+ 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) {
+ dbg("Failed to open configuration file %s with"
+ " error: %s\n", CGRULES_CONF_FILE,
+ strerror(errno));
+ ret = errno;
+ goto finish;
+ }
+
+ buff = calloc(CGROUP_RULE_MAXLINE, sizeof(char));
+ if (!buff) {
+ dbg("Out of memory? Error: %s\n", strerror(errno));
+ ret = errno;
+ 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. */
+ 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 == '%') {
+ 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) {
+ 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). */
+ 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 {
+ 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 {
+ 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) {
+ dbg("Out of memory? Error: %s\n", strerror(errno));
+ ret = errno;
+ 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) {
+ dbg("Failed to parse controllers on line"
+ " %d\n", linenum);
+ goto destroyrule;
+ }
+
+ i = 0;
+ do {
+ if (i >= MAX_MNT_ELEMENTS) {
+ 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])) {
+ 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;
+ }
+
+ 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++) {
+ dbg(" %s", lst->tail->controllers[i]);
+ }
+ 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. */
+ 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
@@ -293,7 +658,7 @@ static char *cg_build_path_locked(char *name, char *path, char *type)
return NULL;
}
-char *cg_build_path(char *name, char *path, char *type)
+static 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);
@@ -301,6 +666,7 @@ char *cg_build_path(char *name, char *path, char *type)
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.
@@ -771,7 +1137,7 @@ free_parent:
/**
* @cgroup: cgroup data structure to be filled with parent values and then
- * passed down for creation
+ * passed down for creation
* @ignore_ownership: Ignore doing a chown on the newly created cgroup
*/
int cgroup_create_cgroup_from_parent(struct cgroup *cgroup,
@@ -1225,216 +1591,197 @@ static void cg_free_controller_array(char *controllers[])
}
}
-/** cg_parse_rules_config_file
- * parses the config file and determines the rule application based on
- * uid and gid.
+/**
+ * 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.
*
- * returns 0 on success.
+ * 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 int cg_parse_rules_config_file(struct cgroup_rules_data *cgrldp,
- struct cgroup *cgroups[])
+static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(const uid_t uid,
+ const gid_t gid)
{
- FILE *fp;
- char buf[FILENAME_MAX];
- char user[FILENAME_MAX];
- char dest[PATH_MAX];
- char buf_ctrl[FILENAME_MAX];
- char *controllers[CG_CONTROLLER_MAX];
- struct cgroup *cgroup;
- int cgindex = 0, match_uid = 0, match_gid = 0, i, ret = 0;
-
- memset(controllers, 0, CG_CONTROLLER_MAX);
- memset(buf_ctrl, 0, FILENAME_MAX);
+ /* Return value */
+ struct cgroup_rule *ret = rl.head;
- fp = fopen(CGRULES_CONF_FILE, "r");
- if (fp == NULL) {
- dbg("Open of file %s failed: %s", CGRULES_CONF_FILE,
- strerror(errno));
- return ECGOTHER;
- }
+ /* Temporary user data */
+ struct passwd *usr = NULL;
- /* In case of multi line rule, we need to prepare multiple
- * cgroups structure. That's why caller has passed an array
- * of cgroup pointers. Keep a index of current empty cgroup
- * structure which can be passed to cg_prepare_cgroup.
- */
- cgindex = 0;
-
- /* Parse file */
- while (fgets(buf, FILENAME_MAX, fp) != NULL) {
- char *tptr, *line;
- struct group *group;
-
- line = buf;
- /* skip the leading white space */
- while (*line && isspace(*line))
- line++;
-
- /* Rip off the comments */
- tptr = strchr(line, '#');
- if (tptr)
- *tptr = '\0';
-
- /* Rip off the newline char */
- tptr = strchr(line, '\n');
- if (tptr)
- *tptr = '\0';
-
- /* Anything left ? */
- if (!strlen(line))
- continue;
+ /* Temporary group data */
+ struct group *grp = NULL;
- user[0] = dest[0] = buf_ctrl[0] = '\0';
+ /* Temporary string pointer */
+ char *sp = NULL;
- i = sscanf(line, "%s%s%s", user, buf_ctrl, dest);
- dbg("scanned line[%d]: user[%s], controllers[%s],"
- " dest[%s]\n", i, user, buf_ctrl, dest);
+ /* Loop variable */
+ int i = 0;
- /* If we encounter a rule which does not begin with %,
- * and either match_uid or match_gid is set, that means
- * we have processed one rule and if that rule was muti
- * line then it has ended. Return back. Remember, we execute
- * only first matching rule (either single line or multiline)
- */
- if ((match_uid || match_gid) && strcmp(user, "%")) {
- match_uid = 0;
- match_gid = 0;
- fclose(fp);
- return 0;
+ pthread_rwlock_wrlock(&rl_lock);
+ while (ret) {
+ /* The wildcard rule always matches. */
+ if ((ret->uid == CGRULE_WILD) && (ret->gid == CGRULE_WILD)) {
+ goto finished;
}
- if (i == CGRULES_MAX_FIELDS_PER_LINE) {
- /* a complete line */
- if (((strcmp(cgrldp->pw->pw_name, user) == 0) ||
- (strcmp(user, "*") == 0)) ||
- (match_uid && !strcmp(user, "%"))) {
- match_uid = 1;
-
- cgroup = calloc(1, sizeof(struct cgroup));
- if (!cgroup) {
- ret = ECGOTHER;
- goto out;
- }
- cgroups[cgindex] = cgroup;
- cgindex++;
-
- ret = cg_prepare_controller_array(buf_ctrl,
- controllers);
- if (ret)
- goto out;
- ret = cg_prepare_cgroup(cgroup, cgrldp->pid,
- dest, controllers);
- if (ret)
- goto out;
-
- cg_free_controller_array(controllers);
- } else if (user[0] == '@' ||
- (match_gid && !strcmp(user, "%"))) {
- errno = 0;
- group = getgrgid(cgrldp->gid);
- if (!group) {
- dbg("getgrgid() failed for gid %d\n",
- cgrldp->pw->pw_gid);
- ret = ECGOTHER;
- goto out;
- }
- if ((strcmp(group->gr_name, user+1) == 0)
- || (strcmp(user, "*") == 0) ||
- (match_gid && !strcmp(user, "%"))) {
- match_gid = 1;
-
- cgroup = (struct cgroup *)
- calloc(1, sizeof(struct cgroup));
- if (!cgroup) {
- ret = ECGOTHER;
- goto out;
- }
- cgroups[cgindex] = cgroup;
- cgindex++;
- ret = cg_prepare_controller_array(buf_ctrl, controllers);
- if (ret)
- goto out;
- ret = cg_prepare_cgroup(cgroup,
- cgrldp->pid,
- dest, controllers);
- if (ret)
- goto out;
- cg_free_controller_array(controllers);
- }
+ /* 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;
}
- } else {
- dbg("invalid line '%s' - skipped", line);
}
+
+ /* If we haven't matched, try the next rule. */
+ ret = ret->next;
}
- /* If we are here, then none of the rule matched for the task */
- dbg("No rules matched for task with pid %d\n", cgrldp->pid);
- fclose(fp);
- return 0;
-out:
- fclose(fp);
- cg_free_controller_array(controllers);
- /* Free the cgroups allocated so far */
- for (i = 0; (i < CG_CONTROLLER_MAX) && cgroups[i]; i++)
- cgroup_free(&cgroups[i]);
+
+ /* If we get here, no rules matched. */
+ ret = NULL;
+
+finished:
+ pthread_rwlock_unlock(&rl_lock);
return ret;
}
-/** cgroup_change_cgroup_uid_gid changes the cgroup of a program based on
- * rules in the config file. Rules are search based on uid and gid
- * and the pid is placed into destination group (if permissions are
- * there).
+/**
+ * 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
*
- * returns 0 on success.
+ * 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(uid_t uid, gid_t gid, pid_t pid)
+int cgroup_change_cgroup_uid_gid_flags(const uid_t uid, const gid_t gid,
+ const pid_t pid, const int flags)
{
- int ret = 0, i;
- struct passwd *pw;
- struct cgroup_rules_data cgrld, *cgrldp = &cgrld;
- struct cgroup *cgroups[CG_HIER_MAX];
+ /* 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) {
dbg("libcgroup is not initialized\n");
- return ECGROUPNOTINITIALIZED;
+ ret = ECGROUPNOTINITIALIZED;
+ goto finished;
}
- memset(cgrldp, 0, sizeof(struct cgroup_rules_data));
- memset(cgroups, 0, CG_HIER_MAX);
- pw = getpwuid(uid);
- if (!pw) {
- dbg("Could not retrieve the credentials of user"
- "with uid %d\n", uid);
- return ECGOTHER;
- }
- cgrldp->pw = pw;
- cgrldp->pid = pid;
- cgrldp->gid = gid;
+ /*
+ * 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)) {
+ 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) {
+ dbg("Failed to parse the configuration rules.\n");
+ goto finished;
+ }
- /* Parse config file */
- ret = cg_parse_rules_config_file(cgrldp, cgroups);
- if (ret) {
- dbg("Parsing of %s failed\n", CGRULES_CONF_FILE);
- return ret;
+ /* We did not find a matching rule, so we're done. */
+ if (ret == 0) {
+ 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) {
+ dbg("No rule found to match PID: %d, UID: %d, "
+ "GID: %d\n", pid, uid, gid);
+ ret = 0;
+ goto finished;
+ }
}
+ dbg("Found matching rule %s for PID: %d, UID: %d, GID: %d\n",
+ tmp->name, pid, uid, gid);
- /* Add task to cgroups */
- for (i = 0; (i < CG_HIER_MAX) && cgroups[i]; i++) {
- ret = cgroup_attach_task_pid(cgroups[i], cgrldp->pid);
+ /* If we are here, then we found a matching rule, so execute it. */
+ do {
+ dbg("Executing rule %s for PID %d... ", tmp->name, pid);
+ ret = cgroup_change_cgroup_path(tmp->destination,
+ pid, tmp->controllers);
if (ret) {
- dbg("cgroup_attach_task_pid failed:%d\n", ret);
- goto out;
+ dbg("FAILED! (Error Code: %d)\n", ret);
+ goto finished;
}
- }
-out:
- /* Free the cgroups */
- for (i = 0; (i < CG_HIER_MAX) && cgroups[i]; i++)
- cgroup_free(&cgroups[i]);
+ 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;
}
-/** cgroup_change_cgroup_path changes the cgroup of a program based on
- * the path provided by user. In this case user already knows in which
- * cgroup the task should go and no rules file have to be parsed.
+/**
+ * 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.
*/
@@ -1460,3 +1807,104 @@ int cgroup_change_cgroup_path(char *dest, pid_t pid, char *controllers[])
}
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;
+
+ /* 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;
+
+ dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE);
+ if ((ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID))) {
+ dbg("Error parsing configuration file \"%s\": %d.\n",
+ CGRULES_CONF_FILE, ret);
+ ret = ECGROUPPARSEFAIL;
+ goto finished;
+ }
+
+ #ifdef 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) {
+ dbg("Could not initialize rule cache, error was: %d\n", ret);
+ cgroup_rules_loaded = false;
+ } else {
+ cgroup_rules_loaded = true;
+ }
+
+ return ret;
+}
diff --git a/cgrulesengd.c b/cgrulesengd.c
new file mode 100644
index 0000000..02d0cd5
--- /dev/null
+++ b/cgrulesengd.c
@@ -0,0 +1,625 @@
+/*
+ * 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 <sys/stat.h>
+#include <unistd.h>
+#include <linux/connector.h>
+#include <linux/cn_proc.h>
+
+/* Log file */
+FILE* logfile;
+
+/**
+ * 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");
+ fprintf(fd, " usage : cgrulesengd [--nodaemon] [--nolog] [--log FILE]"
+ "\n");
+ va_end(ap);
+}
+
+/**
+ * Prints a formatted message (like printf()) to a file stream, and flushes
+ * the file stream's buffer so that the message is immediately readable.
+ * @param fd The file stream to write to
+ * @param format The format for the message (printf style)
+ * @param ... Any args to format (printf style)
+ */
+void flog(FILE* fd, const char* format, ...)
+{
+ /* List of args to format */
+ va_list ap;
+
+ /* Print the message to the given stream. */
+ va_start(ap, format);
+ vfprintf(fd, format, ap);
+ va_end(ap);
+
+ /* Flush the stream's buffer, so the data is readable immediately. */
+ fflush(fd);
+}
+
+/**
+ * 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;
+
+ /* GID data */
+ gid_t rgid, egid, sgid, fsgid;
+
+ /* 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(logfile, "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(logfile, "Failed to process event, out of"
+ "memory? Error: %s\n",
+ 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(logfile, "For some reason, we're processing a non-UID/GID"
+ " event. Something is wrong!\n");
+ 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:
+ flog(logfile, "Attempting to change cgroup for PID: %d, "
+ "UID: %d, GID: %d... ",
+ ev->event_data.id.process_pid,
+ ev->event_data.id.e.euid, egid);
+ 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:
+ flog(logfile, "Attempting to change cgroup for PID: %d, "
+ "UID: %d, GID: %d... ",
+ ev->event_data.id.process_pid, euid,
+ ev->event_data.id.e.egid);
+ 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) {
+ flog(logfile, "FAILED!\n (Error Code: %d)\n", ret);
+ } else {
+ flog(logfile, "OK!\n");
+ }
+
+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(logfile, "UID Event:\n");
+ flog(logfile, " PID = %d, tGID = %d, rUID = %d, eUID = %d\n",
+ 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(logfile, "GID Event:\n");
+ flog(logfile, " PID = %d, tGID = %d, rGID = %d, eGID = %d\n",
+ 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) {
+ printf("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) {
+ printf("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];
+ printf("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);
+ printf("sending netlink message len=%d, cn_msg len=%d\n",
+ nl_hdr->nlmsg_len, sizeof(struct cn_msg));
+ if (send(sk_nl, nl_hdr, nl_hdr->nlmsg_len, 0) != nl_hdr->nlmsg_len) {
+ printf("failed to send proc connector mcast ctl op!\n");
+ goto close_and_exit;
+ }
+ printf("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(logfile, "************************************"
+ "***********\n"
+ "!***ERROR: NETLINK BUFFER FULL, MSG "
+ "DROPPED***!\n"
+ "************************************"
+ "***********\n");
+ 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;
+}
+
+/**
+ * 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
+ * @param daemon False to turn off daemon mode (no fork, leave FDs open)
+ * @param logs False to disable logging (no log FD, leave stdout open)
+ * @return 0 on success, > 0 on error
+ */
+int cgre_start_daemon(const char* logp, const unsigned char daemon,
+ const unsigned char logs)
+{
+ /* PID returned from the fork() */
+ pid_t pid;
+
+ /* Current system time */
+ time_t tm;
+
+ /* 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();
+ flog(stderr, "Failed to fork(), %s\n", strerror(errno));
+ return 1;
+ } else if (pid > 0) {
+ flog(stdout, "Starting in daemon mode.\n");
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Change the file mode mask. */
+ umask(0);
+ } else {
+ dbg("Not using daemon mode.\n");
+ pid = getpid();
+ }
+
+ if (logs) {
+ logfile = fopen(logp, "a");
+ if (!logfile) {
+ flog(stderr, "Failed to open log file %s, error: %s."
+ " Continuing anyway.\n", logp,
+ strerror(errno));
+ logfile = stdout;
+ } else {
+ flog(logfile, "CGroup Rules Engine Daemon\n");
+ tm = time(0);
+ flog(logfile, "Current time: %s", ctime(&tm));
+ flog(stdout, "Opened log file: %s\n", logp);
+ }
+ } else {
+ logfile = stdout;
+ flog(stdout, "Proceeding with stdout as log output.\n");
+ }
+
+ if (!daemon) {
+ /* We can skip the rest, since we're not becoming a daemon. */
+ flog(logfile, "Proceeding with PID %d\n\n", getpid());
+ if (logfile != stdout)
+ flog(stdout, "Proceeding with PID %d\n", getpid());
+ return 0;
+ } else {
+ /* Get a new SID for the child. */
+ if (setsid() < 0) {
+ flog(logfile, "Failed to get a new SID, error: %s\n",
+ strerror(errno));
+ return 2;
+ }
+
+ /* Change to the root directory. */
+ if (chdir("/") < 0) {
+ flog(logfile, "Failed to chdir to /, error: %s\n",
+ 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(logfile, "Proceeding with PID %d\n\n", 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(logfile, "\nReloading rules configuration.\n");
+ flog(logfile, "Current time: %s\n", ctime(&tm));
+
+ /* Ask libcgroup to reload the rules table. */
+ cgroup_reload_cached_rules();
+
+ /* Print the results of the new table to our log file. */
+ cgroup_print_rules_config(logfile);
+ flog(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(logfile, "\nStopped CGroup Rules Engine Daemon at %s",
+ ctime(&tm));
+ flog(logfile, "========================================");
+ flog(logfile, "========================================\n\n");
+
+ /* Close the log file, if we opened one. */
+ if (logfile && logfile != stdout)
+ fclose(logfile);
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ /* Patch to the log file */
+ char logp[FILENAME_MAX];
+
+ /* For catching signals */
+ struct sigaction sa;
+
+ /* Should we daemonize? */
+ unsigned char daemon = 1;
+
+ /* Should we log? */
+ unsigned char logs = 1;
+
+ /* Return codes */
+ int ret = 0;
+
+ /* Loop variable */
+ int i = 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;
+ }
+
+ /* Set the default log file. */
+ memset(logp, '\0', FILENAME_MAX);
+ strncpy(logp, "/root/cgrulesengd.log",
+ strlen("/root/cgrulesengd.log"));
+ logfile = NULL;
+
+ /* Parse user args. */
+ for (i = 1; i < argc; i++) {
+ if (strncmp(argv[i], "--log", strlen("--log")) == 0) {
+ i++;
+ memset(logp, '\0', FILENAME_MAX);
+ strncpy(logp, argv[i], strlen(argv[i]));
+ continue;
+ }
+ if (strncmp(argv[i], "--nodaemon", strlen("--nodaemon")) == 0) {
+ daemon = 0;
+ continue;
+ }
+ if (strncmp(argv[i], "--nolog", strlen("--nolog")) == 0) {
+ logs = 0;
+ continue;
+ }
+
+ /* If we get here, the user specified an invalid arg. */
+ usage(stderr, "Invalid argument: %s", argv[i]);
+ ret = 2;
+ goto finished;
+ }
+
+ flog(stdout, "Log file is: %s\n", logp);
+
+ /* 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. */
+ if ((ret = cgre_start_daemon(logp, daemon, logs)) < 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(logfile, "Failed to set up signal handler for SIGUSR2."
+ " Error: %s\n", 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(logfile, "Failed to set up the signal handler. Error:"
+ " %s\n", strerror(errno));
+ goto finished;
+ }
+
+ /* Print the configuration to the log file, or stdout. */
+ cgroup_print_rules_config(logfile);
+ flog(logfile, "Started the CGroup Rules Engine Daemon.\n");
+
+ /* 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/cgrulesengd.h b/cgrulesengd.h
new file mode 100644
index 0000000..bdffd31
--- /dev/null
+++ b/cgrulesengd.h
@@ -0,0 +1,124 @@
+/*
+ * 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 "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 DEBUG is defined. */
+#ifdef DEBUG
+ #define fdbg(a, b...) fprintf(a, b)
+#else
+ #define fdbg(a, b...) do {} while(0)
+#endif /* 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 a file stream, and flushes
+ * the file stream's buffer so that the message is immediately readable.
+ * @param fd The file stream to write to
+ * @param format The format for the message (printf style)
+ * @param ... Any args to format (printf style)
+ */
+void flog(FILE* fd, 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
+ * @param daemon False to turn off daemon mode (no fork, leave FDs open)
+ * @param logs False to disable logging (no log FD, leave stdout open)
+ * @return 0 on success, > 0 on error
+ */
+int cgre_start_daemon(const char *logp, const unsigned char daemon,
+ const unsigned char logs);
+
+/**
+ * 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/libcgroup-internal.h b/libcgroup-internal.h
index f422cc5..c2f2ce3 100644
--- a/libcgroup-internal.h
+++ b/libcgroup-internal.h
@@ -60,6 +60,24 @@ struct cgroup_rules_data {
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;
+};
+
+
__END_DECLS
#endif
diff --git a/libcgroup.h b/libcgroup.h
index dc7e23f..c9e9689 100644
--- a/libcgroup.h
+++ b/libcgroup.h
@@ -27,6 +27,8 @@ __BEGIN_DECLS
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <limits.h>
+#include <linux/cn_proc.h>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
@@ -94,6 +96,19 @@ struct list_of_names {
struct list_of_names *next;
};
+/* Maximum length of a line in the daemon config file */
+#define CGROUP_RULE_MAXLINE (FILENAME_MAX + LOGIN_NAME_MAX + \
+ CG_CONTROLLER_MAX + 3)
+
+/* Definitions for the uid and gid members of a cgroup_rules */
+#define CGRULE_INVALID (-1)
+#define CGRULE_WILD (-2)
+
+/* Flags for cgroup_change_cgroup_uid_gid() */
+enum cgflags {
+ CGFLAG_USECACHE = 0x01,
+};
+
enum cg_msg_type {
CG_MSG_LOAD_FILE,
CG_MSG_UNLOAD_FILE,
@@ -124,6 +139,8 @@ enum cgroup_errors {
ECGOTHER,
ECGROUPNOTEQUAL,
ECGCONTROLLERNOTEQUAL,
+ ECGROUPPARSEFAIL, /* Failed to parse rules configuration file. */
+ ECGROUPNORULES, /* Rules list does not exist. */
};
#define CG_MAX_MSG_SIZE 256
@@ -175,10 +192,70 @@ struct cgroup *cgroup_get_cgroup(struct cgroup *cgroup);
int cgroup_create_cgroup_from_parent(struct cgroup *cgroup, int ignore_ownership);
int cgroup_copy_cgroup(struct cgroup *dst, struct cgroup *src);
-/* Changes the cgroup of calling application based on rule file */
+/**
+ * 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 if not safe.
+ */
+int cgroup_change_cgroup_uid_gid_flags(const uid_t uid, const gid_t gid,
+ const pid_t pid, const int flags);
+
+/**
+ * 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);
+
+/**
+ * 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 *path, pid_t pid, char *controllers[]);
+/**
+ * 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);
+
+/**
+ * Reloads the rules list, using the given configuration file. This function
+ * is probably NOT thread safe (calls cgroup_parse_rules_config()).
+ * @return 0 on success, > 0 on failure
+ */
+int cgroup_reload_cached_rules(void);
+
+/**
+ * Initializes the rules cache.
+ * @return 0 on success, > 0 on failure
+ */
+int cgroup_init_rules_cache(void);
+
+
/* The wrappers for filling libcg structures */
struct cgroup *cgroup_new_cgroup(const char *name);
diff --git a/samples/cgred.conf b/samples/cgred.conf
new file mode 100644
index 0000000..5363be5
--- /dev/null
+++ b/samples/cgred.conf
@@ -0,0 +1,19 @@
+# /etc/cgred.d/cgred.conf - CGroup Rules Engine Daemon configuration file
+#
+# The four options listed below (CONFIG_FILE, LOG_FILE, NODAEMON, NOLOG) are
+# the only valid ones. Defining anything else in this file will cause the
+# CGroup Rules Engine program to fail. So, don't do it.
+
+# The pathname to the configuration file for CGroup Rules Engine
+CONFIG_FILE="/etc/cgrules.conf"
+
+# The pathname to the log file for CGroup Rules Engine
+LOG_FILE="/root/cgrulesengd.log"
+
+# Uncomment the second line to run CGroup Rules Engine in non-daemon mode
+NODAEMON=""
+#NODAEMON="--nodaemon"
+
+# Uncomment the second line to disable logging for CGroup Rules Engine
+NOLOG=""
+#NOLOG="--nolog"
diff --git a/scripts/init.d/cgred b/scripts/init.d/cgred
new file mode 100644
index 0000000..b99e769
--- /dev/null
+++ b/scripts/init.d/cgred
@@ -0,0 +1,129 @@
+#!/bin/bash
+#
+# Start/Stop the CGroups Rules Engine Daemon
+#
+# Copyright Red Hat Inc. 2008
+#
+# Authors: 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.
+#
+# cgred GGroups Rules Engine Daemon
+# chkconfig: 2345 80 20
+# description: This is a daemon for automatically classifying processes \
+# into cgroups based on UID/GID.
+#
+# processname: cgrulesengd
+# pidfile: /var/run/cgred.pid
+#
+### BEGIN INIT INFO
+# Provides: cgrulesengd
+# Required-Start: $local_fs $syslog $wlm
+# Required-Stop: $local_fs $syslog
+# Should-Start:
+# Should-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start and stop the cgroups rules engine daemon
+# Description: CGroup Rules Engine is a tool for automatically using \
+# cgroups to classify processes
+### END INIT INFO
+
+prefix=/usr
+exec_prefix=/usr
+bindir=/bin
+
+CGRED_BIN=${bindir}/cgrulesengd
+
+# Sanity checks
+[ -x $CGRED_BIN ] || exit 1
+[ -x /lib/libcgroup.so ] || exit 1
+
+# Source function library & LSB routines
+. /etc/rc.d/init.d/functions
+. /lib/lsb/init-functions
+
+# Read in configuration options.
+if [ -f "/etc/cgred.d/cgred.conf" ] ; then
+ . /etc/cgred.d/cgred.conf
+ OPTIONS="--config $CONFIG_FILE --log $LOG_FILE $NODAEMON $NOLOG"
+else
+ OPTIONS=""
+fi
+
+# For convenience
+processname=cgrulesengd
+servicename=cgred
+pidfile=/var/run/cgred.pid
+
+RETVAL=0
+
+start()
+{
+ echo $"Starting CGroup Rules Engine Daemon..."
+ if [ -f "/var/lock/subsys/$servicename" ] ; then
+ echo "$servicename is already running with PID `cat ${pidfile}`"
+ return 1
+ fi
+ daemon --check $servicename --pidfile $pidfile $processname $OPTIONS
+ RETVAL=$?
+ echo
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$servicename
+ echo "`pidof $processname`" > $pidfile
+}
+
+stop()
+{
+ echo -n $"Stopping CGroup Rules Engine Daemon..."
+ killproc -p $pidfile $processname -12
+ RETVAL=$?
+ echo
+ if [ $RETVAL -eq 0 ] ; then
+ rm -f /var/lock/subsys/$servicename
+ rm -f $pidfile
+ fi
+}
+
+# See how we are called
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ status)
+ status -p $pidfile $processname
+ RETVAL=$?
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ condrestart)
+ if [ -f /var/lock/subsys/$servicename ] ; then
+ stop
+ start
+ fi
+ ;;
+ flash)
+ if [ -f /var/lock/subsys/$servicename ] ; then
+ echo $"Reloading rules configuration..."
+ kill -s 12 `cat ${pidfile}`
+ RETVAL=$?
+ echo
+ else
+ echo $"$servicename is not running."
+ fi
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart|condrestart|flash}"
+ ;;
+esac
+
+exit $RETVAL
diff --git a/tests/Makefile b/tests/Makefile
index e668032..7a38b7a 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -2,9 +2,11 @@ LDFLAGS = -L ..
LIBS = -lcgroup -lpthread
INC = -I ..
CXXFLAGS = -g -O2 -Wall -DDEBUG $(INC)
+CFLAGS = -g -O2 -Wall -DDEBUG
TARGET= libcgrouptest01 \
- libcg_ba
+ libcg_ba \
+ setuid
all: $(TARGET)
@@ -14,5 +16,8 @@ libcgrouptest01: libcgrouptest01.c
libcg_ba: libcg_ba.cpp
$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS) $(LIBS)
+setuid: setuid.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LIBS)
+
clean:
\rm -f $(TARGET)
diff --git a/tests/setuid.c b/tests/setuid.c
new file mode 100644
index 0000000..9d77850
--- /dev/null
+++ b/tests/setuid.c
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <string.h>
+
+/*
+ * This is just a simple program for changing a UID or a GID. Comment out
+ * whichever block you don't want to use.
+ */
+int main(int argc, char *argv[])
+{
+ /* User data */
+ struct passwd *pwd;
+
+ /* UID of user */
+ uid_t uid;
+
+ /* Group data */
+ struct group *grp;
+
+ /* GID of group */
+ gid_t gid;
+
+ /* Return codes */
+ int ret;
+
+ pwd = getpwnam(argv[1]);
+ uid = pwd->pw_uid;
+ fprintf(stdout, "Setting UID to %s (%d).\n", pwd->pw_name, uid);
+ if ((ret = setuid(uid))) {
+ fprintf(stderr, "Call to setuid() failed with error: %s\n",
+ strerror(errno));
+ ret = -errno;
+ goto finished;
+ }
+
+// while(1) {
+// grp = getgrnam("root");
+// gid = grp->gr_gid;
+// fprintf(stdout, "Setting GID to %s (%d).\n",
+// grp->gr_name, gid);
+// if ((ret = setgid(gid))) {
+// fprintf(stderr, "Call to setgid() failed with error:"
+// " %s\n", strerror(errno));
+// ret = -errno;
+// goto finished;
+// }
+// }
+
+ while (1) {
+ usleep(3000000);
+ }
+
+finished:
+ return ret;
+}