api.c was pretty big already and quite hard to maintain. Split it apart with a separate file in order to make it a bit easier to keep track of the rules as opposed to anything else.
Signed-off-by: Dhaval Giani <dhaval.gi...@gmail.com> Cc: Vivek Goyal <vgo...@redhat.com> --- src/Makefile.am | 2 src/api.c | 938 --------------------------------------------- src/cgrules.c | 974 +++++++++++++++++++++++++++++++++++++++++++++++ src/libcgroup-internal.h | 5 4 files changed, 981 insertions(+), 938 deletions(-) Index: libcg/src/api.c =================================================================== --- libcg.orig/src/api.c +++ libcg/src/api.c @@ -65,21 +65,6 @@ static __thread char errtext[MAXLEN]; /* Task command name length */ #define TASK_COMM_LEN 16 -/* 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; - /* Namespace */ __thread char *cg_namespace_table[CG_CONTROLLER_MAX]; @@ -264,7 +249,7 @@ err: } -static char *cgroup_basename(const char *path) +char *cgroup_basename(const char *path) { char *base; char *tmp_string; @@ -299,426 +284,6 @@ static int cgroup_test_subsys_mounted(co } /** - * 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; - } - if (r->procname) { - free(r->procname); - r->procname = NULL; - } - /* 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 *cg_rl) -{ - /* Temporary pointer */ - struct cgroup_rule *tmp = NULL; - - /* Make sure we're not freeing NULL memory! */ - if (!(cg_rl->head)) { - cgroup_dbg("Warning: Attempted to free NULL list.\n"); - return; - } - - while (cg_rl->head) { - tmp = cg_rl->head; - cg_rl->head = tmp->next; - cgroup_free_rule(tmp); - } - - /* Don't leave wild pointers around! */ - cg_rl->head = NULL; - cg_rl->tail = NULL; -} - -static char *cg_skip_unused_charactors_in_rule(char *rule) -{ - char *itr; - - /* We ignore anything after a # sign as comments. */ - itr = strchr(rule, '#'); - if (itr) - *itr = '\0'; - - /* We also need to remove the newline character. */ - itr = strchr(rule, '\n'); - if (itr) - *itr = '\0'; - - /* Now, skip any leading tabs and spaces. */ - itr = rule; - while (itr && isblank(*itr)) - itr++; - - /* If there's nothing left, we can ignore this line. */ - if (!strlen(itr)) - return NULL; - - return itr; -} - -/** - * 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, const char *mprocname) -{ - /* File descriptor for the configuration file */ - FILE *fp = NULL; - - /* Buffer to store the line we're working on */ - char buff[CGROUP_RULE_MAXLINE] = { '\0' }; - - /* Iterator for the line we're working on */ - char *itr = NULL; - - /* Pointer to process name in a line of the configuration file */ - char *procname = 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 key[CGROUP_RULE_MAXKEY] = { '\0' }; - 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; - size_t len_username; - int len_procname; - - /* 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, "re"); - if (!fp) { - cgroup_dbg("Failed to open configuration file %s with" - " error: %s\n", CGRULES_CONF_FILE, - strerror(errno)); - last_errno = errno; - ret = ECGOTHER; - goto 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 (fgets(buff, sizeof(buff), fp) != NULL) { - linenum++; - - itr = cg_skip_unused_charactors_in_rule(buff); - if (!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); - continue; - } - - /* - * If there is something left, it should be a rule. Otherwise, - * there's an error in the configuration file. - */ - skipped = false; - i = sscanf(itr, "%s%s%s", key, controllers, destination); - if (i != 3) { - cgroup_dbg("Failed to parse configuration file on" - " line %d.\n", linenum); - goto parsefail; - } - procname = strchr(key, ':'); - if (procname) { - /* <user>:<procname> <subsystem> <destination> */ - procname++; /* skip ':' */ - len_username = procname - key - 1; - len_procname = strlen(procname); - if (len_procname < 0) { - cgroup_dbg("Failed to parse configuration file" - " on line %d.\n", linenum); - goto parsefail; - } - } else { - len_username = strlen(key); - len_procname = 0; - } - len_username = min(len_username, sizeof(user) - 1); - memset(user, '\0', sizeof(user)); - strncpy(user, key, len_username); - - /* - * 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 close; - } - if (strncmp(user, "@", 1) == 0) { - /* New GID rule. */ - itr = &(user[1]); - grp = getgrnam(itr); - if (grp) { - uid = CGRULE_INVALID; - gid = grp->gr_gid; - } else { - cgroup_dbg("Warning: Entry for %s not" - "found. Skipping rule on line" - " %d.\n", itr, linenum); - skipped = true; - continue; - } - } else if (strncmp(user, "*", 1) == 0) { - /* Special wildcard rule. */ - uid = CGRULE_WILD; - gid = CGRULE_WILD; - } else if (*itr != '%') { - /* New UID rule. */ - pwd = getpwnam(user); - if (pwd) { - uid = pwd->pw_uid; - gid = CGRULE_INVALID; - } else { - cgroup_dbg("Warning: Entry for %s not" - "found. Skipping rule on line" - " %d.\n", user, linenum); - 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) { - if (!matched) - continue; - if (len_procname) { - char *mproc_base; - /* - * If there is a rule based on process name, - * it should be matched with mprocname. - */ - if (!mprocname) { - uid = CGRULE_INVALID; - gid = CGRULE_INVALID; - matched = false; - continue; - } - - mproc_base = cgroup_basename(mprocname); - if (strcmp(mprocname, procname) && - strcmp(mproc_base, procname)) { - uid = CGRULE_INVALID; - gid = CGRULE_INVALID; - matched = false; - free(mproc_base); - continue; - } - free(mproc_base); - } - } - - /* - * 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 close; - } - - newrule->uid = uid; - newrule->gid = gid; - len_username = min(len_username, - sizeof(newrule->username) - 1); - strncpy(newrule->username, user, len_username); - if (len_procname) { - newrule->procname = strdup(procname); - if (!newrule->procname) { - last_errno = errno; - ret = ECGOTHER; - goto close; - } - } else { - newrule->procname = NULL; - } - strncpy(newrule->destination, destination, - sizeof(newrule->destination) - 1); - 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->username, 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. */ - 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 close; - -destroyrule: - cgroup_free_rule(newrule); - -parsefail: - ret = ECGRULESPARSEFAIL; - -close: - fclose(fp); -unlock: - pthread_rwlock_unlock(&rl_lock); - return ret; -} - -/** * cgroup_init(), initializes the MOUNT_POINT. * * This code is theoretically thread safe now. Its not really tested @@ -2212,507 +1777,6 @@ unlock_error: 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, - const char * const controllers[]) -{ - int ret = 0, i; - const 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++) { - int j = 0; - 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 (j = 0; j < CG_CONTROLLER_MAX && - cg_mount_table[j].name[0] != '\0'; j++) { - cgroup_dbg("Adding controller %s\n", - cg_mount_table[j].name); - cptr = cgroup_add_controller(cgroup, - cg_mount_table[j].name); - if (!cptr) { - cgroup_dbg("Adding controller '%s'" - " failed\n", - cg_mount_table[j].name); - pthread_rwlock_unlock(&cg_mount_table_lock); - cgroup_free_controllers(cgroup); - 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); - cgroup_free_controllers(cgroup); - return ECGROUPNOTALLOWED; - } - } - - return ret; -} - -static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(uid_t uid, - gid_t gid, struct cgroup_rule *rule) -{ - /* 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; - - while (rule) { - /* Skip "%" which indicates continuation of previous rule. */ - if (rule->username[0] == '%') { - rule = rule->next; - continue; - } - /* The wildcard rule always matches. */ - if ((rule->uid == CGRULE_WILD) && (rule->gid == CGRULE_WILD)) - return rule; - - /* This is the simple case of the UID matching. */ - if (rule->uid == uid) - return rule; - - /* This is the simple case of the GID matching. */ - if (rule->gid == gid) - return rule; - - /* If this is a group rule, the UID might be a member. */ - if (rule->username[0] == '@') { - /* Get the group data. */ - sp = &(rule->username[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]))) - return rule; - } - } - - /* If we haven't matched, try the next rule. */ - rule = rule->next; - } - - /* If we get here, no rules matched. */ - return NULL; -} - -/** - * Finds the first rule in the cached list that matches the given UID, GID - * or PROCESS NAME, 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 - * @param procname The PROCESS NAME 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_t uid, - gid_t gid, const char *procname) -{ - /* Return value */ - struct cgroup_rule *ret = rl.head; - char *base = NULL; - - pthread_rwlock_wrlock(&rl_lock); - while (ret) { - ret = cgroup_find_matching_rule_uid_gid(uid, gid, ret); - if (!ret) - break; - if (!procname) - /* If procname is NULL, return a rule matching - * UID or GID */ - break; - if (!ret->procname) - /* If no process name in a rule, that means wildcard */ - break; - if (!strcmp(ret->procname, procname)) - break; - - base = cgroup_basename(procname); - if (!strcmp(ret->procname, base)) - /* Check a rule of basename. */ - break; - ret = ret->next; - free(base); - base = NULL; - } - pthread_rwlock_unlock(&rl_lock); - - if (base) - free(base); - - return ret; -} - -int cgroup_change_cgroup_flags(uid_t uid, gid_t gid, - const char *procname, pid_t pid, int flags) -{ - /* Temporary pointer to a rule */ - struct cgroup_rule *tmp = NULL; - - /* Temporary variables for destination substitution */ - char newdest[FILENAME_MAX]; - int i, j; - int written; - int available; - struct passwd *user_info; - struct group *group_info; - - /* 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, procname); - - /* 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, procname); - 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->username, 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->username, - pid); - /* Destination substitutions */ - for(j = i = 0; i < strlen(tmp->destination) && - (j < FILENAME_MAX - 2); ++i, ++j) { - if(tmp->destination[i] == '%') { - /* How many bytes did we write / error check */ - written = 0; - /* How many bytes can we write */ - available = FILENAME_MAX - j - 2; - /* Substitution */ - switch(tmp->destination[++i]) { - case 'u': - written = snprintf(newdest+j, available, - "%d", uid); - break; - case 'U': - user_info = getpwuid(uid); - if(user_info) { - written = snprintf(newdest + j, - available, "%s", - user_info -> pw_name); - } else { - written = snprintf(newdest + j, - available, "%d", uid); - } - break; - case 'g': - written = snprintf(newdest + j, - available, "%d", gid); - break; - case 'G': - group_info = getgrgid(gid); - if(group_info) { - written = snprintf(newdest + j, - available, "%s", - group_info -> gr_name); - } else { - written = snprintf(newdest + j, - available, "%d", gid); - } - break; - case 'p': - written = snprintf(newdest + j, - available, "%d", pid); - break; - case 'P': - if(procname) { - written = snprintf(newdest + j, - available, "%s", - procname); - } else { - written = snprintf(newdest + j, - available, "%d", pid); - } - break; - } - written = min(written, available); - /* - * written<1 only when either error occurred - * during snprintf or if no substitution was - * made at all. In both cases, we want to just - * copy input string. - */ - if(written<1) { - newdest[j] = '%'; - if(available>1) - newdest[++j] = - tmp->destination[i]; - } else { - /* - * In next iteration, we will write - * just after the substitution, but j - * will get incremented in the - * meantime. - */ - j += written - 1; - } - } else { - if(tmp->destination[i] == '\\') - ++i; - newdest[j] = tmp->destination[i]; - } - } - newdest[j] = 0; - - /* Apply the rule */ - ret = cgroup_change_cgroup_path(newdest, - pid, (const char * const *)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->username[0] == '%')); - -finished: - return ret; -} - -int cgroup_change_cgroup_uid_gid_flags(uid_t uid, gid_t gid, - pid_t pid, int flags) -{ - return cgroup_change_cgroup_flags(uid, gid, NULL, pid, 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) -{ - 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(const char *dest, pid_t pid, - const char *const 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); - cgroup_free_controllers(&cgroup); - return ret; -} - -/** - * 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", itr->username); - if (itr->procname) - fprintf(fp, ":%s", itr->procname); - fprintf(fp, "\n"); - - 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(void) -{ - /* Return codes */ - int ret = 0; - - cgroup_dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE); - ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL); - if (ret) { - cgroup_dbg("Error parsing configuration file \"%s\": %d.\n", - CGRULES_CONF_FILE, ret); - ret = ECGRULESPARSEFAIL; - 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(void) -{ - /* Return codes */ - int ret = 0; - - /* Attempt to read the configuration file and cache the rules. */ - ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL); - 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 Index: libcg/src/cgrules.c =================================================================== --- /dev/null +++ libcg/src/cgrules.c @@ -0,0 +1,974 @@ +/* + * XX: Correct copyrights and Steve's email address + * + * Copyright Red Hat Inc. 2008 + * + * Authors: + * 1. Vivek Goyal <vgo...@redhat.com> + * 2. Steve Olivieri <s...@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 <dirent.h> +#include <errno.h> +#include <libcgroup.h> +#include <libcgroup-internal.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.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> +#include <linux/un.h> +#include <grp.h> + +/* 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; + +/** + * 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; + } + if (r->procname) { + free(r->procname); + r->procname = NULL; + } + /* 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 *cg_rl) +{ + /* Temporary pointer */ + struct cgroup_rule *tmp = NULL; + + /* Make sure we're not freeing NULL memory! */ + if (!(cg_rl->head)) { + cgroup_dbg("Warning: Attempted to free NULL list.\n"); + return; + } + + while (cg_rl->head) { + tmp = cg_rl->head; + cg_rl->head = tmp->next; + cgroup_free_rule(tmp); + } + + /* Don't leave wild pointers around! */ + cg_rl->head = NULL; + cg_rl->tail = NULL; +} + +static char *cg_skip_unused_charactors_in_rule(char *rule) +{ + char *itr; + + /* We ignore anything after a # sign as comments. */ + itr = strchr(rule, '#'); + if (itr) + *itr = '\0'; + + /* We also need to remove the newline character. */ + itr = strchr(rule, '\n'); + if (itr) + *itr = '\0'; + + /* Now, skip any leading tabs and spaces. */ + itr = rule; + while (itr && isblank(*itr)) + itr++; + + /* If there's nothing left, we can ignore this line. */ + if (!strlen(itr)) + return NULL; + + return itr; +} + +/** + * 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, const char *mprocname) +{ + /* File descriptor for the configuration file */ + FILE *fp = NULL; + + /* Buffer to store the line we're working on */ + char buff[CGROUP_RULE_MAXLINE] = { '\0' }; + + /* Iterator for the line we're working on */ + char *itr = NULL; + + /* Pointer to process name in a line of the configuration file */ + char *procname = 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 key[CGROUP_RULE_MAXKEY] = { '\0' }; + 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; + size_t len_username; + int len_procname; + + /* 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, "re"); + if (!fp) { + cgroup_dbg("Failed to open configuration file %s with" + " error: %s\n", CGRULES_CONF_FILE, + strerror(errno)); + last_errno = errno; + ret = ECGOTHER; + goto 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 (fgets(buff, sizeof(buff), fp) != NULL) { + linenum++; + + itr = cg_skip_unused_charactors_in_rule(buff); + if (!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); + continue; + } + + /* + * If there is something left, it should be a rule. Otherwise, + * there's an error in the configuration file. + */ + skipped = false; + i = sscanf(itr, "%s%s%s", key, controllers, destination); + if (i != 3) { + cgroup_dbg("Failed to parse configuration file on" + " line %d.\n", linenum); + goto parsefail; + } + procname = strchr(key, ':'); + if (procname) { + /* <user>:<procname> <subsystem> <destination> */ + procname++; /* skip ':' */ + len_username = procname - key - 1; + len_procname = strlen(procname); + if (len_procname < 0) { + cgroup_dbg("Failed to parse configuration file" + " on line %d.\n", linenum); + goto parsefail; + } + } else { + len_username = strlen(key); + len_procname = 0; + } + len_username = min(len_username, sizeof(user) - 1); + memset(user, '\0', sizeof(user)); + strncpy(user, key, len_username); + + /* + * 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 close; + } + if (strncmp(user, "@", 1) == 0) { + /* New GID rule. */ + itr = &(user[1]); + grp = getgrnam(itr); + if (grp) { + uid = CGRULE_INVALID; + gid = grp->gr_gid; + } else { + cgroup_dbg("Warning: Entry for %s not" + "found. Skipping rule on line" + " %d.\n", itr, linenum); + skipped = true; + continue; + } + } else if (strncmp(user, "*", 1) == 0) { + /* Special wildcard rule. */ + uid = CGRULE_WILD; + gid = CGRULE_WILD; + } else if (*itr != '%') { + /* New UID rule. */ + pwd = getpwnam(user); + if (pwd) { + uid = pwd->pw_uid; + gid = CGRULE_INVALID; + } else { + cgroup_dbg("Warning: Entry for %s not" + "found. Skipping rule on line" + " %d.\n", user, linenum); + 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) { + if (!matched) + continue; + if (len_procname) { + char *mproc_base; + /* + * If there is a rule based on process name, + * it should be matched with mprocname. + */ + if (!mprocname) { + uid = CGRULE_INVALID; + gid = CGRULE_INVALID; + matched = false; + continue; + } + + mproc_base = cgroup_basename(mprocname); + if (strcmp(mprocname, procname) && + strcmp(mproc_base, procname)) { + uid = CGRULE_INVALID; + gid = CGRULE_INVALID; + matched = false; + free(mproc_base); + continue; + } + free(mproc_base); + } + } + + /* + * 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 close; + } + + newrule->uid = uid; + newrule->gid = gid; + len_username = min(len_username, + sizeof(newrule->username) - 1); + strncpy(newrule->username, user, len_username); + if (len_procname) { + newrule->procname = strdup(procname); + if (!newrule->procname) { + last_errno = errno; + ret = ECGOTHER; + goto close; + } + } else { + newrule->procname = NULL; + } + strncpy(newrule->destination, destination, + sizeof(newrule->destination) - 1); + 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->username, 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. */ + 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 close; + +destroyrule: + cgroup_free_rule(newrule); + +parsefail: + ret = ECGRULESPARSEFAIL; + +close: + fclose(fp); +unlock: + pthread_rwlock_unlock(&rl_lock); + return ret; +} + +/** 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, + const char * const controllers[]) +{ + int ret = 0, i; + const 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++) { + int j = 0; + 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 (j = 0; j < CG_CONTROLLER_MAX && + cg_mount_table[j].name[0] != '\0'; j++) { + cgroup_dbg("Adding controller %s\n", + cg_mount_table[j].name); + cptr = cgroup_add_controller(cgroup, + cg_mount_table[j].name); + if (!cptr) { + cgroup_dbg("Adding controller '%s'" + " failed\n", + cg_mount_table[j].name); + pthread_rwlock_unlock(&cg_mount_table_lock); + cgroup_free_controllers(cgroup); + 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); + cgroup_free_controllers(cgroup); + return ECGROUPNOTALLOWED; + } + } + + return ret; +} + +static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(uid_t uid, + gid_t gid, struct cgroup_rule *rule) +{ + /* 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; + + while (rule) { + /* Skip "%" which indicates continuation of previous rule. */ + if (rule->username[0] == '%') { + rule = rule->next; + continue; + } + /* The wildcard rule always matches. */ + if ((rule->uid == CGRULE_WILD) && (rule->gid == CGRULE_WILD)) + return rule; + + /* This is the simple case of the UID matching. */ + if (rule->uid == uid) + return rule; + + /* This is the simple case of the GID matching. */ + if (rule->gid == gid) + return rule; + + /* If this is a group rule, the UID might be a member. */ + if (rule->username[0] == '@') { + /* Get the group data. */ + sp = &(rule->username[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]))) + return rule; + } + } + + /* If we haven't matched, try the next rule. */ + rule = rule->next; + } + + /* If we get here, no rules matched. */ + return NULL; +} + +/** + * Finds the first rule in the cached list that matches the given UID, GID + * or PROCESS NAME, 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 + * @param procname The PROCESS NAME 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_t uid, + gid_t gid, const char *procname) +{ + /* Return value */ + struct cgroup_rule *ret = rl.head; + char *base = NULL; + + pthread_rwlock_wrlock(&rl_lock); + while (ret) { + ret = cgroup_find_matching_rule_uid_gid(uid, gid, ret); + if (!ret) + break; + if (!procname) + /* If procname is NULL, return a rule matching + * UID or GID */ + break; + if (!ret->procname) + /* If no process name in a rule, that means wildcard */ + break; + if (!strcmp(ret->procname, procname)) + break; + + base = cgroup_basename(procname); + if (!strcmp(ret->procname, base)) + /* Check a rule of basename. */ + break; + ret = ret->next; + free(base); + base = NULL; + } + pthread_rwlock_unlock(&rl_lock); + + if (base) + free(base); + + return ret; +} + +int cgroup_change_cgroup_flags(uid_t uid, gid_t gid, + const char *procname, pid_t pid, int flags) +{ + /* Temporary pointer to a rule */ + struct cgroup_rule *tmp = NULL; + + /* Temporary variables for destination substitution */ + char newdest[FILENAME_MAX]; + int i, j; + int written; + int available; + struct passwd *user_info; + struct group *group_info; + + /* 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, procname); + + /* 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, procname); + 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->username, 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->username, + pid); + /* Destination substitutions */ + for(j = i = 0; i < strlen(tmp->destination) && + (j < FILENAME_MAX - 2); ++i, ++j) { + if(tmp->destination[i] == '%') { + /* How many bytes did we write / error check */ + written = 0; + /* How many bytes can we write */ + available = FILENAME_MAX - j - 2; + /* Substitution */ + switch(tmp->destination[++i]) { + case 'u': + written = snprintf(newdest+j, available, + "%d", uid); + break; + case 'U': + user_info = getpwuid(uid); + if(user_info) { + written = snprintf(newdest + j, + available, "%s", + user_info -> pw_name); + } else { + written = snprintf(newdest + j, + available, "%d", uid); + } + break; + case 'g': + written = snprintf(newdest + j, + available, "%d", gid); + break; + case 'G': + group_info = getgrgid(gid); + if(group_info) { + written = snprintf(newdest + j, + available, "%s", + group_info -> gr_name); + } else { + written = snprintf(newdest + j, + available, "%d", gid); + } + break; + case 'p': + written = snprintf(newdest + j, + available, "%d", pid); + break; + case 'P': + if(procname) { + written = snprintf(newdest + j, + available, "%s", + procname); + } else { + written = snprintf(newdest + j, + available, "%d", pid); + } + break; + } + written = min(written, available); + /* + * written<1 only when either error occurred + * during snprintf or if no substitution was + * made at all. In both cases, we want to just + * copy input string. + */ + if(written<1) { + newdest[j] = '%'; + if(available>1) + newdest[++j] = + tmp->destination[i]; + } else { + /* + * In next iteration, we will write + * just after the substitution, but j + * will get incremented in the + * meantime. + */ + j += written - 1; + } + } else { + if(tmp->destination[i] == '\\') + ++i; + newdest[j] = tmp->destination[i]; + } + } + newdest[j] = 0; + + /* Apply the rule */ + ret = cgroup_change_cgroup_path(newdest, + pid, (const char * const *)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->username[0] == '%')); + +finished: + return ret; +} + +int cgroup_change_cgroup_uid_gid_flags(uid_t uid, gid_t gid, + pid_t pid, int flags) +{ + return cgroup_change_cgroup_flags(uid, gid, NULL, pid, 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) +{ + 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(const char *dest, pid_t pid, + const char *const 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); + cgroup_free_controllers(&cgroup); + return ret; +} + +/** + * 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", itr->username); + if (itr->procname) + fprintf(fp, ":%s", itr->procname); + fprintf(fp, "\n"); + + 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(void) +{ + /* Return codes */ + int ret = 0; + + cgroup_dbg("Reloading cached rules from %s.\n", CGRULES_CONF_FILE); + ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL); + if (ret) { + cgroup_dbg("Error parsing configuration file \"%s\": %d.\n", + CGRULES_CONF_FILE, ret); + ret = ECGRULESPARSEFAIL; + 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(void) +{ + /* Return codes */ + int ret = 0; + + /* Attempt to read the configuration file and cache the rules. */ + ret = cgroup_parse_rules(true, CGRULE_INVALID, CGRULE_INVALID, NULL); + 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; +} Index: libcg/src/Makefile.am =================================================================== --- libcg.orig/src/Makefile.am +++ libcg/src/Makefile.am @@ -7,7 +7,7 @@ CLEANFILES = lex.c parse.c parse.h INCLUDES = -I$(top_srcdir)/include lib_LTLIBRARIES = libcgroup.la -libcgroup_la_SOURCES = parse.h parse.y lex.l api.c config.c libcgroup-internal.h libcgroup.map wrapper.c +libcgroup_la_SOURCES = parse.h parse.y lex.l api.c config.c libcgroup-internal.h libcgroup.map wrapper.c cgrules.c libcgroup_la_LIBADD = -lpthread libcgroup_la_LDFLAGS = -Wl,--version-script,$(srcdir)/libcgroup.map \ -version-number $(LIBRARY_VERSION_MAJOR):$(LIBRARY_VERSION_MINOR):$(LIBRARY_VERSION_RELEASE) Index: libcg/src/libcgroup-internal.h =================================================================== --- libcg.orig/src/libcgroup-internal.h +++ libcg/src/libcgroup-internal.h @@ -67,6 +67,11 @@ __BEGIN_DECLS #define max(x,y) ((y)<(x)?(x):(y)) #define min(x,y) ((y)>(x)?(x):(y)) +/* Check if cgroup_init has been called or not. */ +int cgroup_initialized; + +char *cgroup_basename(const char *path); + struct control_value { char name[FILENAME_MAX]; char value[CG_VALUE_MAX]; ------------------------------------------------------------------------------ The modern datacenter depends on network connectivity to access resources and provide services. The best practices for maximizing a physical server's connectivity to a physical network are well understood - see how these rules translate into the virtual world? http://p.sf.net/sfu/oracle-sfdevnlfb _______________________________________________ Libcg-devel mailing list Libcg-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/libcg-devel