/* * rc-misc.c * rc misc functions */ /* * Copyright (c) 2007-2015 The OpenRC Authors. * See the Authors file at the top-level directory of this distribution and * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS * * This file is part of OpenRC. It is subject to the license terms in * the LICENSE file found in the top-level directory of this * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE * This file may not be copied, modified, propagated, or distributed * except according to the terms contained in the LICENSE file. */ #include #include #include "queue.h" #include "librc.h" #include "helpers.h" bool rc_yesno(const char *value) { if (!value) { errno = ENOENT; return false; } if (strcasecmp(value, "yes") == 0 || strcasecmp(value, "y") == 0 || strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) return true; if (strcasecmp(value, "no") != 0 && strcasecmp(value, "n") != 0 && strcasecmp(value, "false") != 0 && strcasecmp(value, "0") != 0) errno = EINVAL; return false; } /** * Read the entire @file into the buffer and set @len to the * size of the buffer when finished. For C strings, this will * be strlen(buffer) + 1. * Don't forget to free the buffer afterwards! */ bool rc_getfile(const char *file, char **buffer, size_t *len) { bool ret = false; FILE *fp; int fd; struct stat st; size_t done, left; fp = fopen(file, "re"); if (!fp) return false; /* assume fileno() never fails */ fd = fileno(fp); if (fstat(fd, &st)) goto finished; left = st.st_size; *len = left + 1; /* NUL terminator */ *buffer = xrealloc(*buffer, *len); while (left) { done = fread(*buffer, sizeof(*buffer[0]), left, fp); if (done == 0 && ferror(fp)) goto finished; left -= done; } ret = true; finished: if (!ret) { free(*buffer); *len = 0; } else (*buffer)[*len - 1] = '\0'; fclose(fp); return ret; } ssize_t rc_getline(char **line, size_t *len, FILE *fp) { char *p; size_t last = 0; while (!feof(fp)) { if (*line == NULL || last != 0) { *len += BUFSIZ; *line = xrealloc(*line, *len); } p = *line + last; memset(p, 0, BUFSIZ); if (fgets(p, BUFSIZ, fp) == NULL) break; last += strlen(p); if (last && (*line)[last - 1] == '\n') { (*line)[last - 1] = '\0'; break; } } return last; } char * rc_proc_getent(const char *ent _unused) { #ifdef __linux__ FILE *fp; char *proc, *p, *value = NULL; size_t i, len; if (!exists("/proc/cmdline")) return NULL; if (!(fp = fopen("/proc/cmdline", "r"))) return NULL; proc = NULL; i = 0; if (rc_getline(&proc, &i, fp) == -1 || proc == NULL) return NULL; if (proc != NULL) { len = strlen(ent); while ((p = strsep(&proc, " "))) { if (strncmp(ent, p, len) == 0 && (p[len] == '\0' || p[len] == ' ' || p[len] == '=')) { p += len; if (*p == '=') p++; value = xstrdup(p); } } } if (!value) errno = ENOENT; fclose(fp); free(proc); return value; #else return NULL; #endif } RC_STRINGLIST * rc_config_list(const char *file) { FILE *fp; char *buffer = NULL; size_t len = 0; char *p; char *token; RC_STRINGLIST *list = rc_stringlist_new(); if (!(fp = fopen(file, "r"))) return list; while ((rc_getline(&buffer, &len, fp))) { p = buffer; /* Strip leading spaces/tabs */ while ((*p == ' ') || (*p == '\t')) p++; /* Get entry - we do not want comments */ token = strsep(&p, "#"); if (token && (strlen(token) > 1)) { /* If not variable assignment then skip */ if (strchr(token, '=')) { /* Strip the newline if present */ if (token[strlen(token) - 1] == '\n') token[strlen(token) - 1] = 0; rc_stringlist_add(list, token); } } } fclose(fp); free(buffer); return list; } static void rc_config_set_value(RC_STRINGLIST *config, char *value) { RC_STRING *cline; char *entry; size_t i = 0; char *newline; char *p = value; bool replaced; char *token; if (!p) return; if (strncmp(p, "export ", 7) == 0) p += 7; if (!(token = strsep(&p, "="))) return; entry = xstrdup(token); /* Preserve shell coloring */ if (*p == '$') token = value; else do { /* Bash variables are usually quoted */ token = strsep(&p, "\"\'"); } while (token && *token == '\0'); /* Drop a newline if that's all we have */ if (token) { i = strlen(token) - 1; if (token[i] == '\n') token[i] = 0; xasprintf(&newline, "%s=%s", entry, token); } else { xasprintf(&newline, "%s=", entry); } replaced = false; /* In shells the last item takes precedence, so we need to remove any prior values we may already have */ TAILQ_FOREACH(cline, config, entries) { i = strlen(entry); if (strncmp(entry, cline->value, i) == 0 && cline->value[i] == '=') { /* We have a match now - to save time we directly replace it */ free(cline->value); cline->value = newline; replaced = true; break; } } if (!replaced) { rc_stringlist_add(config, newline); free(newline); } free(entry); } /* * Override some specific rc.conf options on the kernel command line. * I only know how to do this in Linux, so if someone wants to supply * a patch for this on *BSD or tell me how to write the code to do this, * any suggestions are welcome. */ static RC_STRINGLIST *rc_config_kcl(RC_STRINGLIST *config) { #ifdef __linux__ RC_STRINGLIST *overrides; RC_STRING *cline, *override, *config_np; char *tmp = NULL; char *value = NULL; size_t varlen = 0; overrides = rc_stringlist_new(); /* A list of variables which may be overridden on the kernel command line */ rc_stringlist_add(overrides, "rc_parallel"); TAILQ_FOREACH(override, overrides, entries) { varlen = strlen(override->value); value = rc_proc_getent(override->value); /* No need to continue if there's nothing to override */ if (!value) { free(value); continue; } if (value != NULL) { xasprintf(&tmp, "%s=%s", override->value, value); } /* * Whenever necessary remove the old config entry first to prevent * duplicates */ TAILQ_FOREACH_SAFE(cline, config, entries, config_np) { if (strncmp(override->value, cline->value, varlen) == 0 && cline->value[varlen] == '=') { rc_stringlist_delete(config, cline->value); break; } } /* Add the option (var/value) to the current config */ rc_stringlist_add(config, tmp); free(tmp); free(value); } rc_stringlist_free(overrides); #endif return config; } static RC_STRINGLIST * rc_config_directory(RC_STRINGLIST *config) { DIR *dp; struct dirent *d; RC_STRINGLIST *rc_conf_d_files = rc_stringlist_new(); RC_STRING *fname; RC_STRINGLIST *rc_conf_d_list; char path[PATH_MAX]; RC_STRING *line; if ((dp = opendir(RC_CONF_D)) != NULL) { while ((d = readdir(dp)) != NULL) { if (fnmatch("*.conf", d->d_name, FNM_PATHNAME) == 0) { rc_stringlist_addu(rc_conf_d_files, d->d_name); } } closedir(dp); if (rc_conf_d_files) { rc_stringlist_sort(&rc_conf_d_files); TAILQ_FOREACH(fname, rc_conf_d_files, entries) { if (!fname->value) continue; sprintf(path, "%s/%s", RC_CONF_D, fname->value); rc_conf_d_list = rc_config_list(path); TAILQ_FOREACH(line, rc_conf_d_list, entries) if (line->value) rc_config_set_value(config, line->value); rc_stringlist_free(rc_conf_d_list); } rc_stringlist_free(rc_conf_d_files); } } return config; } RC_STRINGLIST * rc_config_load(const char *file) { RC_STRINGLIST *list; RC_STRINGLIST *config; RC_STRING *line; list = rc_config_list(file); config = rc_stringlist_new(); TAILQ_FOREACH(line, list, entries) { rc_config_set_value(config, line->value); } rc_stringlist_free(list); return config; } char * rc_config_value(RC_STRINGLIST *list, const char *entry) { RC_STRING *line; char *p; size_t len; len = strlen(entry); TAILQ_FOREACH(line, list, entries) { p = strchr(line->value, '='); if (p != NULL) { if (strncmp(entry, line->value, len) == 0 && line->value[len] == '=') return ++p; } } return NULL; } /* Global for caching the strings loaded from rc.conf to avoid reparsing for * each rc_conf_value call */ static RC_STRINGLIST *rc_conf = NULL; static void _free_rc_conf(void) { rc_stringlist_free(rc_conf); } char * rc_conf_value(const char *setting) { RC_STRINGLIST *old; RC_STRING *s; char *p; if (!rc_conf) { rc_conf = rc_config_load(RC_CONF); atexit(_free_rc_conf); /* Support old configs. */ if (exists(RC_CONF_OLD)) { old = rc_config_load(RC_CONF_OLD); TAILQ_CONCAT(rc_conf, old, entries); free(old); } rc_conf = rc_config_directory(rc_conf); rc_conf = rc_config_kcl(rc_conf); /* Convert old uppercase to lowercase */ TAILQ_FOREACH(s, rc_conf, entries) { p = s->value; while (p && *p && *p != '=') { if (isupper((unsigned char)*p)) *p = tolower((unsigned char)*p); p++; } } } return rc_config_value(rc_conf, setting); }