diff options
Diffstat (limited to 'src/librc-misc.c')
-rw-r--r-- | src/librc-misc.c | 750 |
1 files changed, 750 insertions, 0 deletions
diff --git a/src/librc-misc.c b/src/librc-misc.c new file mode 100644 index 00000000..604c5518 --- /dev/null +++ b/src/librc-misc.c @@ -0,0 +1,750 @@ +/* + rc-misc.c + rc misc functions + Copyright 2007 Gentoo Foundation + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <regex.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc-misc.h" +#include "rc.h" +#include "strlist.h" + +#define ERRX eerrorx("out of memory"); + +#define PROFILE_ENV "/etc/profile.env" +#define SYS_WHITELIST RC_LIBDIR "conf.d/env_whitelist" +#define USR_WHITELIST "/etc/conf.d/env_whitelist" +#define RC_CONFIG "/etc/conf.d/rc" + +#define PATH_PREFIX RC_LIBDIR "bin:/bin:/sbin:/usr/bin:/usr/sbin" + +#ifndef S_IXUGO +# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) +#endif + +void *rc_xcalloc (size_t n, size_t size) +{ + void *value = calloc (n, size); + + if (value) + return value; + + ERRX +} + +void *rc_xmalloc (size_t size) +{ + void *value = malloc (size); + + if (value) + return (value); + + ERRX +} + +void *rc_xrealloc (void *ptr, size_t size) +{ + void *value = realloc (ptr, size); + + if (value) + return (value); + + ERRX +} + + +char *rc_xstrdup (const char *str) +{ + char *value; + + if (! str) + return (NULL); + + value = strdup (str); + + if (value) + return (value); + + ERRX +} + +bool rc_is_env (const char *var, const char *val) +{ + char *v; + + if (! var) + return (false); + + v = getenv (var); + if (! v) + return (val == NULL ? true : false); + + return (strcasecmp (v, val) == 0 ? true : false); +} + +char *rc_strcatpaths (const char *path1, const char *paths, ...) +{ + va_list ap; + int length; + int i; + char *p; + char *path; + char *pathp; + + if (! path1 || ! paths) + return (NULL); + + length = strlen (path1) + strlen (paths) + 3; + i = 0; + va_start (ap, paths); + while ((p = va_arg (ap, char *)) != NULL) + length += strlen (p) + 1; + va_end (ap); + + path = rc_xmalloc (length); + memset (path, 0, length); + memcpy (path, path1, strlen (path1)); + pathp = path + strlen (path1) - 1; + if (*pathp != '/') + { + pathp++; + *pathp++ = '/'; + } + else + pathp++; + memcpy (pathp, paths, strlen (paths)); + pathp += strlen (paths); + + va_start (ap, paths); + while ((p = va_arg (ap, char *)) != NULL) + { + if (*pathp != '/') + *pathp++ = '/'; + i = strlen (p); + memcpy (pathp, p, i); + pathp += i; + } + va_end (ap); + + *pathp++ = 0; + + return (path); +} + +bool rc_exists (const char *pathname) +{ + struct stat buf; + + if (! pathname) + return (false); + + if (stat (pathname, &buf) == 0) + return (true); + + errno = 0; + return (false); +} + +bool rc_is_file (const char *pathname) +{ + struct stat buf; + + if (! pathname) + return (false); + + if (stat (pathname, &buf) == 0) + return (S_ISREG (buf.st_mode)); + + errno = 0; + return (false); +} + +bool rc_is_dir (const char *pathname) +{ + struct stat buf; + + if (! pathname) + return (false); + + if (stat (pathname, &buf) == 0) + return (S_ISDIR (buf.st_mode)); + + errno = 0; + return (false); +} + +bool rc_is_link (const char *pathname) +{ + struct stat buf; + + if (! pathname) + return (false); + + if (lstat (pathname, &buf) == 0) + return (S_ISLNK (buf.st_mode)); + + errno = 0; + return (false); +} + +bool rc_is_exec (const char *pathname) +{ + struct stat buf; + + if (! pathname) + return (false); + + if (lstat (pathname, &buf) == 0) + return (buf.st_mode & S_IXUGO); + + errno = 0; + return (false); +} + +char **rc_ls_dir (char **list, const char *dir, int options) +{ + DIR *dp; + struct dirent *d; + + if (! dir) + return (list); + + if ((dp = opendir (dir)) == NULL) + { + eerror ("failed to opendir `%s': %s", dir, strerror (errno)); + return (list); + } + + errno = 0; + while (((d = readdir (dp)) != NULL) && errno == 0) + { + if (d->d_name[0] != '.') + { + if (options & RC_LS_INITD) + { + int l = strlen (d->d_name); + char *init = rc_strcatpaths (RC_INITDIR, d->d_name, NULL); + bool ok = rc_exists (init); + free (init); + if (! ok) + continue; + + /* .sh files are not init scripts */ + if (l > 2 && d->d_name[l - 3] == '.' && + d->d_name[l - 2] == 's' && + d->d_name[l - 1] == 'h') + continue; + } + list = rc_strlist_addsort (list, d->d_name); + } + } + closedir (dp); + + if (errno != 0) + { + eerror ("failed to readdir `%s': %s", dir, strerror (errno)); + rc_strlist_free (list); + return (NULL); + } + + return (list); +} + +bool rc_rm_dir (const char *pathname, bool top) +{ + DIR *dp; + struct dirent *d; + + if (! pathname) + return (false); + + if ((dp = opendir (pathname)) == NULL) + { + eerror ("failed to opendir `%s': %s", pathname, strerror (errno)); + return (false); + } + + errno = 0; + while (((d = readdir (dp)) != NULL) && errno == 0) + { + if (strcmp (d->d_name, ".") != 0 && strcmp (d->d_name, "..") != 0) + { + char *tmp = rc_strcatpaths (pathname, d->d_name, NULL); + if (d->d_type == DT_DIR) + { + if (! rc_rm_dir (tmp, true)) + { + free (tmp); + closedir (dp); + return (false); + } + } + else + { + if (unlink (tmp)) + { + eerror ("failed to unlink `%s': %s", tmp, strerror (errno)); + free (tmp); + closedir (dp); + return (false); + } + } + free (tmp); + } + } + if (errno != 0) + eerror ("failed to readdir `%s': %s", pathname, strerror (errno)); + closedir (dp); + + if (top && rmdir (pathname) != 0) + { + eerror ("failed to rmdir `%s': %s", pathname, strerror (errno)); + return false; + } + + return (true); +} + +char **rc_get_config (char **list, const char *file) +{ + FILE *fp; + char buffer[RC_LINEBUFFER]; + char *p; + char *token; + char *line; + char *linep; + char *linetok; + int i = 0; + bool replaced; + char *entry; + char *newline; + + if (! (fp = fopen (file, "r"))) + { + ewarn ("load_config_file `%s': %s", file, strerror (errno)); + return (list); + } + + while (fgets (buffer, RC_LINEBUFFER, fp)) + { + p = buffer; + + /* Strip leading spaces/tabs */ + while ((*p == ' ') || (*p == '\t')) + p++; + + if (! p || strlen (p) < 3 || p[0] == '#') + continue; + + /* Get entry */ + token = strsep (&p, "="); + if (! token) + continue; + + entry = rc_xstrdup (token); + + do + { + /* Bash variables are usually quoted */ + token = strsep (&p, "\"\'"); + } + while ((token) && (strlen (token) == 0)); + + /* Drop a newline if that's all we have */ + i = strlen (token) - 1; + if (token[i] == 10) + token[i] = 0; + + i = strlen (entry) + strlen (token) + 2; + newline = rc_xmalloc (i); + snprintf (newline, i, "%s=%s", entry, token); + + replaced = false; + /* In shells the last item takes precedence, so we need to remove + any prior values we may already have */ + STRLIST_FOREACH (list, line, i) + { + char *tmp = rc_xstrdup (line); + linep = tmp; + linetok = strsep (&linep, "="); + if (strcmp (linetok, entry) == 0) + { + /* We have a match now - to save time we directly replace it */ + free (list[i - 1]); + list[i - 1] = newline; + replaced = true; + free (tmp); + break; + } + free (tmp); + } + + if (! replaced) + { + list = rc_strlist_addsort (list, newline); + free (newline); + } + free (entry); + } + fclose (fp); + + return (list); +} + +char *rc_get_config_entry (char **list, const char *entry) +{ + char *line; + int i; + char *p; + + STRLIST_FOREACH (list, line, i) + { + p = strchr (line, '='); + if (p && strncmp (entry, line, p - line) == 0) + return (p += 1); + } + + return (NULL); +} + +char **rc_get_list (char **list, const char *file) +{ + FILE *fp; + char buffer[RC_LINEBUFFER]; + char *p; + char *token; + + if (! (fp = fopen (file, "r"))) + { + ewarn ("rc_get_list `%s': %s", file, strerror (errno)); + return (list); + } + + while (fgets (buffer, RC_LINEBUFFER, 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)) + { + token[strlen (token) - 1] = 0; + list = rc_strlist_add (list, token); + } + } + fclose (fp); + + return (list); +} + +char **rc_filter_env (void) +{ + char **env = NULL; + char **whitelist = NULL; + char *env_name = NULL; + char **profile = NULL; + int count = 0; + bool got_path = false; + char *env_var; + int env_len; + char *p; + char *token; + char *sep; + char *e; + int pplen = strlen (PATH_PREFIX); + + whitelist = rc_get_list (whitelist, SYS_WHITELIST); + if (! whitelist) + ewarn ("system environment whitelist (" SYS_WHITELIST ") missing"); + + whitelist = rc_get_list (whitelist, USR_WHITELIST); + + if (! whitelist) + return (NULL); + + if (rc_is_file (PROFILE_ENV)) + profile = rc_get_config (profile, PROFILE_ENV); + + STRLIST_FOREACH (whitelist, env_name, count) + { + char *space = strchr (env_name, ' '); + if (space) + *space = 0; + + env_var = getenv (env_name); + + if (! env_var && profile) + { + env_len = strlen (env_name) + strlen ("export ") + 1; + p = rc_xmalloc (sizeof (char *) * env_len); + snprintf (p, env_len, "export %s", env_name); + env_var = rc_get_config_entry (profile, p); + free (p); + } + + if (! env_var) + continue; + + /* Ensure our PATH is prefixed with the system locations first + for a little extra security */ + if (strcmp (env_name, "PATH") == 0 && + strncmp (PATH_PREFIX, env_var, pplen) != 0) + { + got_path = true; + env_len = strlen (env_name) + strlen (env_var) + pplen + 2; + e = p = rc_xmalloc (sizeof (char *) * env_len); + p += sprintf (e, "%s=%s", env_name, PATH_PREFIX); + + /* Now go through the env var and only add bits not in our PREFIX */ + sep = env_var; + while ((token = strsep (&sep, ":"))) + { + char *np = strdup (PATH_PREFIX); + char *npp = np; + char *tok = NULL; + while ((tok = strsep (&npp, ":"))) + if (strcmp (tok, token) == 0) + break; + if (! tok) + p += sprintf (p, ":%s", token); + free (np); + } + *p++ = 0; + } + else + { + env_len = strlen (env_name) + strlen (env_var) + 2; + e = rc_xmalloc (sizeof (char *) * env_len); + snprintf (e, env_len, "%s=%s", env_name, env_var); + } + + env = rc_strlist_add (env, e); + free (e); + } + + /* We filtered the env but didn't get a PATH? Very odd. + However, we do need a path, so use a default. */ + if (! got_path) + { + env_len = strlen ("PATH=") + strlen (PATH_PREFIX) + 2; + p = rc_xmalloc (sizeof (char *) * env_len); + snprintf (p, env_len, "PATH=%s", PATH_PREFIX); + env = rc_strlist_add (env, p); + free (p); + } + + rc_strlist_free (whitelist); + rc_strlist_free (profile); + + return (env); +} + +/* Other systems may need this at some point, but for now it's Linux only */ +#ifdef __linux__ +static bool file_regex (const char *file, const char *regex) +{ + FILE *fp; + char buffer[RC_LINEBUFFER]; + regex_t re; + bool retval = false; + int result; + + if (! rc_exists (file)) + return (false); + + if (! (fp = fopen (file, "r"))) + { + ewarn ("file_regex `%s': %s", file, strerror (errno)); + return (false); + } + + if ((result = regcomp (&re, regex, REG_EXTENDED | REG_NOSUB)) != 0) + { + fclose (fp); + regerror (result, &re, buffer, sizeof (buffer)); + eerror ("file_regex: %s", buffer); + return (false); + } + + while (fgets (buffer, RC_LINEBUFFER, fp)) + { + if (regexec (&re, buffer, 0, NULL, 0) == 0) + { + retval = true; + break; + } + } + fclose (fp); + regfree (&re); + + return (retval); +} +#endif + +char **rc_config_env (char **env) +{ + char *line; + int i; + char *p; + char **config = rc_get_config (NULL, RC_CONFIG); + char *e; + char sys[6]; + struct utsname uts; + bool has_net_fs_list = false; + FILE *fp; + char buffer[PATH_MAX]; + + STRLIST_FOREACH (config, line, i) + { + p = strchr (line, '='); + if (! p) + continue; + + *p = 0; + e = getenv (line); + if (! e) + { + *p = '='; + env = rc_strlist_add (env, line); + } + else + { + int len = strlen (line) + strlen (e) + 2; + char *new = rc_xmalloc (sizeof (char *) * len); + snprintf (new, len, "%s=%s", line, e); + env = rc_strlist_add (env, new); + free (new); + } + } + rc_strlist_free (config); + + i = strlen ("RC_LIBDIR=//rcscripts") + strlen (LIBDIR) + 2; + line = rc_xmalloc (sizeof (char *) * i); + snprintf (line, i, "RC_LIBDIR=/" LIBDIR "/rcscripts"); + env = rc_strlist_add (env, line); + free (line); + + i += strlen ("/init.d"); + line = rc_xmalloc (sizeof (char *) * i); + snprintf (line, i, "RC_SVCDIR=/" LIBDIR "/rcscripts/init.d"); + env = rc_strlist_add (env, line); + free (line); + + env = rc_strlist_add (env, "RC_BOOTLEVEL=" RC_LEVEL_BOOT); + + p = rc_get_runlevel (); + i = strlen ("RC_SOFTLEVEL=") + strlen (p) + 1; + line = rc_xmalloc (sizeof (char *) * i); + snprintf (line, i, "RC_SOFTLEVEL=%s", p); + env = rc_strlist_add (env, line); + free (line); + + if (rc_exists (RC_SVCDIR "ksoftlevel")) + { + if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "r"))) + eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel", + strerror (errno)); + else + { + memset (buffer, 0, sizeof (buffer)); + if (fgets (buffer, sizeof (buffer), fp)) + { + i = strlen (buffer) - 1; + if (buffer[i] == '\n') + buffer[i] = 0; + i += strlen ("RC_DEFAULTLEVEL=") + 2; + line = rc_xmalloc (sizeof (char *) * i); + snprintf (line, i, "RC_DEFAULTLEVEL=%s", buffer); + env = rc_strlist_add (env, line); + free (line); + } + fclose (fp); + } + } + else + env = rc_strlist_add (env, "RC_DEFAULTLEVEL=" RC_LEVEL_DEFAULT); + + memset (sys, 0, sizeof (sys)); + +/* Linux can run some funky stuff like Xen, VServer, UML, etc + We store this special system in RC_SYS so our scripts run fast */ +#ifdef __linux__ + if (rc_is_dir ("/proc/xen")) + { + fp = fopen ("/proc/xen/capabilities", "r"); + if (fp) + { + fclose (fp); + if (file_regex ("/proc/xen/capabilities", "control_d")) + sprintf (sys, "XENU"); + } + if (! sys) + sprintf (sys, "XEN0"); + } + else if (file_regex ("/proc/cpuinfo", "UML")) + sprintf (sys, "UML"); + else if (file_regex ("/proc/self/status", + "(s_context|VxID|envID):[[:space:]]*[1-9]")) + sprintf(sys, "VPS"); +#endif + + /* Only add a NET_FS list if not defined */ + STRLIST_FOREACH (env, line, i) + if (strncmp (line, "RC_NET_FS_LIST=", strlen ("RC_NET_FS_LIST=")) == 0) + { + has_net_fs_list = true; + break; + } + if (! has_net_fs_list) + { + i = strlen ("RC_NET_FS_LIST=") + strlen (RC_NET_FS_LIST_DEFAULT) + 1; + line = rc_xmalloc (sizeof (char *) * i); + snprintf (line, i, "RC_NET_FS_LIST=%s", RC_NET_FS_LIST_DEFAULT); + env = rc_strlist_add (env, line); + free (line); + } + + if (sys[0]) + { + i = strlen ("RC_SYS=") + strlen (sys) + 2; + line = rc_xmalloc (sizeof (char *) * i); + snprintf (line, i, "RC_SYS=%s", sys); + env = rc_strlist_add (env, line); + free (line); + } + + /* Some scripts may need to take a different code path if Linux/FreeBSD, etc + To save on calling uname, we store it in an environment variable */ + if (uname (&uts) == 0) + { + i = strlen ("RC_UNAME=") + strlen (uts.sysname) + 2; + line = rc_xmalloc (sizeof (char *) * i); + snprintf (line, i, "RC_UNAME=%s", uts.sysname); + env = rc_strlist_add (env, line); + free (line); + } + + /* Set this var to ensure that things are POSIX, which makes scripts work + on non GNU systems with less effort. */ + env = rc_strlist_add (env, "POSIXLY_CORRECT=1"); + + return (env); +} |