diff options
| author | Dennis Camera <skonfig@dtnr.ch> | 2022-08-22 00:27:47 +0200 |
|---|---|---|
| committer | Dennis Camera <skonfig@dtnr.ch> | 2022-08-22 00:27:47 +0200 |
| commit | 2f425cb9e9d28c8e7f017a4fbe1203c7431e745b (patch) | |
| tree | 9ac97abaebdbe3363ee71c6a9f3210799d4494ab /src/util | |
| parent | 580b957769a5ee4ab7522e474f3996c03426a286 (diff) | |
| download | skonfig-c-2f425cb9e9d28c8e7f017a4fbe1203c7431e745b.tar.gz skonfig-c-2f425cb9e9d28c8e7f017a4fbe1203c7431e745b.zip | |
Implement command execution
Diffstat (limited to 'src/util')
| -rw-r--r-- | src/util/envp.c | 182 | ||||
| -rw-r--r-- | src/util/envp.h | 111 | ||||
| -rw-r--r-- | src/util/exec.c | 97 | ||||
| -rw-r--r-- | src/util/exec.h | 23 | ||||
| -rw-r--r-- | src/util/fs.c | 66 | ||||
| -rw-r--r-- | src/util/fs.h | 18 | ||||
| -rw-r--r-- | src/util/string.h | 24 |
7 files changed, 521 insertions, 0 deletions
diff --git a/src/util/envp.c b/src/util/envp.c new file mode 100644 index 0000000..808d9b8 --- /dev/null +++ b/src/util/envp.c @@ -0,0 +1,182 @@ +#include "envp.h" + +#include "string.h" + +#include <stdlib.h> + +extern char **environ; + +static inline bool _envp_string_match_key( + const char *restrict string, const char *restrict key) { + for (size_t i = 0; ; ++i) { + if ('\0' == key[i]) { + return ('=' == string[i]); + } else if ('\0' == string[i]) { + return false; + } else if (string[i] != key[i]) { + return false; + } + } +} + + +static int _envp_append(envp_t *envp, char *string) { + /* find the end */ + envp_t p = *envp; + size_t i = 0; + while (NULL != p[i]) i++; + + /* make space for one more element */ + p = realloc(p, ((i+2) * sizeof(char *))); + if (NULL == p) { + return -1; + } + *envp = p; + + /* append element */ + p[i+1] = 0; + p[i] = string; + + return 0; +} + +static int _envp_replace(char **old, char *new) { + char *oldv = *old; + *old = new; + free(oldv); + + return 0; +} + + +envp_t *envp_alloc(void) { + envp_t envp = calloc(1, sizeof(*envp)); + envp_t *envpp = malloc(sizeof(envpp)); + *envpp = envp; + return envpp; +} + +envp_t *envp_dup(void) { + envp_t *envp = envp_alloc(); + + for (size_t i = 0; NULL != environ[i]; ++i) { + (void)_envp_append(envp, strdup(environ[i])); + } + + return envp; +} + +static char **_envp_find_elem(envp_t envp, const char *restrict name) { + for (size_t i = 0; NULL != envp[i]; ++i) { + if (_envp_string_match_key(envp[i], name)) { + return &envp[i]; + } + } + + return NULL; +} + +static char *_envp_get_elem(envp_t envp, const char *restrict name) { + char **elem = _envp_find_elem(envp, name); + return (elem ? *elem : NULL); +} + +char *envp_get(envp_t *envp, const char *name) { + if (NULL == envp) return NULL; + + char *elem = _envp_get_elem(*envp, name); + + if (NULL != elem) { + elem = strchr(elem, '='); + if (NULL != elem) elem++; /* skip over = */ + } + + return elem; +} + + +int envp_put(envp_t *envp, char *string) { + char envname[strchr(string, '=')-string+1]; + strncpy(envname, string, sizeof(envname)); + + char **elem = _envp_find_elem(*envp, envname); + + if (NULL != elem) { + return _envp_replace(elem, string); + } else { + return _envp_append(envp, string); + } +} + +int envp_set( + envp_t *envp, const char *restrict envname, const char *restrict envval, + bool overwrite) { + char **elem = _envp_find_elem(*envp, envname); + + char *string = NULL; + if (NULL == elem || overwrite) { + size_t slen = (strlen(envname) + strlen (envval) + 2); + string = malloc(slen * sizeof(char)); + snprintf(string, slen, "%s=%s", envname, envval); + } + + if (NULL != elem) { + if (overwrite) { + return _envp_replace(elem, string); + } else { + /* no change */ + return 0; + } + } else { + return _envp_append(envp, string); + } +} + +int envp_unset(envp_t *envp, const char *restrict name) { + size_t i; + + /* find element */ + for (i = 0; NULL != (*envp)[i]; ++i) { + if (_envp_string_match_key((*envp)[i], name)) { + break; + } + } + + char *elem = (*envp)[i]; + if (NULL == elem) { + /* not found -> ok */ + return 0; + } + + /* shift rest of elements */ + for (; NULL != (*envp)[i]; ++i) { + (*envp)[i] = (*envp)[i+1]; + } + + /* delete match */ + free(elem); + + return 0; +} + +void envp_merge(envp_t *dest, envp_t *const src) { + if (NULL == src) return; + + for (size_t i = 0; NULL != (*src)[i]; ++i) { + envp_put(dest, strdup((*src)[i])); + } +} + +void envp_print(envp_t *envp, FILE *restrict stream) { + for (size_t i = 0; NULL != (*envp)[i]; ++i) { + fprintf(stream, "%s\n", (*envp)[i]); + } +} + +void envp_free(envp_t *envp) { + for (size_t i = 0; NULL != (*envp)[i]; ++i) { + free((*envp)[i]); + } + free(*envp); + free(envp); +} diff --git a/src/util/envp.h b/src/util/envp.h new file mode 100644 index 0000000..37871f9 --- /dev/null +++ b/src/util/envp.h @@ -0,0 +1,111 @@ +#ifndef _SK_UTIL_ENVP_H +#define _SK_UTIL_ENVP_H + +#if HAVE_STDBOOL_H +#include <stdbool.h> +#endif + +#include <stdio.h> + +typedef char ** envp_t; + +/** + * envp_alloc: + * allocates a new, empty envp_t. + * + * the returned pointer should be passed to envp_free() when done. + * + * @return new, empty envp_t. + */ +envp_t *envp_alloc(void); + +/** + * envp_dup: + * allocates a new envp_t with the contents of the process’ environ. + * + * the returned pointer should be passed to envp_free() when done. + * + * @return new envp_t. + */ +envp_t *envp_dup(void); + +/** + * envp_get: + * returns the value of an environment variable in an envp. + * + * @param envp: the envp to query. + * @param name: the name of the environment variable to return. + * @return the value of the environment variable if found, NULL otherwise. + */ +char *envp_get(envp_t *envp, const char *name); + +/** + * envp_put: + * puts an environment variable into an envp. + * + * NOTE that the @string pointer will be included in the envp environment as is, + * i.e. it must be a string pointer which can be passed to free(). + * Moreover, the @string will be freed when the @envp is passed to envp_free(). + * + * If an environment variable with the same name already exists, its value will + * be replaced with @string. + * + * @param envp: the envp to modify. + * @param string: a string of the form "name=value". + * @return 0 on success, -1 otherwise and sets errno. + */ +int envp_put(envp_t *envp, char *string); + +/** + * envp_set: + * sets a new environment variable in an envp. + * + * @param envp: the envp to modify. + * @param envname: the name of the environment variable. + * @param envval: the value of @envname. + * @param overwrite: if an environment variable with @envname already exists in + * @envp, replace its value. If @overwrite is false, the function returns + * 0 and will not update @envp. + * @return 0 on success, -1 otherwise and sets errno. + */ +int envp_set( + envp_t *envp, const char *restrict envname, const char *restrict envval, + bool overwrite); + +/** + * envp_unset: + * removes an environment variable from an envp. + * + * @param envp: the envp to modify. + * @param name: the name of the environment variable to remove. + * @return 0 + */ +int envp_unset(envp_t *envp, const char *restrict name); + +/** + * envp_merge: + * add all items from an envp to another one. + * + * @param dest: the envp to modify. + * @param src: the source envp. + */ +void envp_merge(envp_t *dest, envp_t *const src); + +/** + * envp_print: + * prints the contents of an envp to an output stream. + * + * @param envp: the envp to print. + * @param stream: the stream to output to. + */ +void envp_print(envp_t *envp, FILE *restrict stream); + +/** + * envp_free: + * frees a given envp_t and all of its contents. + * + * @param envp: the structure to free. + */ +void envp_free(envp_t *envp); + +#endif diff --git a/src/util/exec.c b/src/util/exec.c new file mode 100644 index 0000000..687cfbd --- /dev/null +++ b/src/util/exec.c @@ -0,0 +1,97 @@ +#include "exec.h" + +#if HAVE_STDBOOL_H +#include <stdbool.h> +#endif + +#include "fs.h" + +#include "../log.h" + +#include <errno.h> + +#include <limits.h> +#if HAVE_LINUX_LIMITS_H +#include <linux/limits.h> +#endif + +#include <unistd.h> + +static int max_fd() { + int fd_max = 0; + +#ifdef NR_OPEN /* from <linux/limits.h> */ + fd_max = NR_OPEN; + if (0 < fd_max) return fd_max; +#endif +#ifdef _SC_OPEN_MAX /* from <unistd.h> */ + fd_max = sysconf(_SC_OPEN_MAX); + if (0 < fd_max) return fd_max; +#endif +#ifdef _POSIX_OPEN_MAX /* from <limits.h> */ + fd_max = _POSIX_OPEN_MAX; + if (0 < fd_max) return fd_max; +#endif + + return 256; /* random guess falllback value */ +} + +pid_t exec_subcmd( + char *const argv[], int stdin, int stdout, int stderr, + bool close_fds, const char *cwd, envp_t *const envp) { + pid_t pid = fork(); + if (0 != pid) { + /* parent */ + return pid; + } + + /* child */ + + /* replace "standard" FDs */ + if (-1 == dup2(stdin, 0)) { + sk_error("failed to replace stdin for subcmd: %s", strerror(errno)); + return -1; + } + if (-1 == dup2(stdout, 1)) { + sk_error("failed to replace stdout for subcmd: %s", strerror(errno)); + return -1; + } + if (-1 == dup2(stderr, 2)) { + sk_error("failed to replace stderr for subcmd: %s", strerror(errno)); + return -1; + } + + if (close_fds) { + /* close all "non-standard" FDs */ + for (int i = 3, m = max_fd(); i < m; ++i) { + (void)close(i); + } + } + + /* chdir */ + if (NULL != cwd) { + if (-1 == chdir(cwd)) { + sk_error( + "error in subcommand execution: chdir(%s) failed: %s", + cwd, strerror(errno)); + exit(-1); + } + } + + char executable[PATH_MAX+1]; + which(argv[0], executable, envp_get(envp, "PATH")); + + if (is_empty_string(executable)) { + /* no such command found */ + /* TODO: handle error */ + exit(-1); + } + + if (NULL != envp) { + execve(executable, argv, *envp); + } else { + execv(executable, argv); + } + + exit(-1); +} diff --git a/src/util/exec.h b/src/util/exec.h new file mode 100644 index 0000000..4572a43 --- /dev/null +++ b/src/util/exec.h @@ -0,0 +1,23 @@ +#ifndef _SK_UTIL_EXEC_H +#define _SK_UTIL_EXEC_H + +#if HAVE_STDBOOL_H +#include <stdbool.h> +#endif + +#include "envp.h" + +#include <sys/types.h> + +/** + * exec_subcmd: + * ... + * + * @param + * @return ... + */ +pid_t exec_subcmd( + char *const argv[], int stdin, int stdout, int stderr, + bool close_fds, const char *cwd, envp_t *const envp); + +#endif diff --git a/src/util/fs.c b/src/util/fs.c index 72c87f3..762c9a5 100644 --- a/src/util/fs.c +++ b/src/util/fs.c @@ -7,6 +7,8 @@ #include <sys/stat.h> #include <fcntl.h> #include <ftw.h> +#include <string.h> +#include <unistd.h> bool is_dir(const char *restrict pathname) { #ifdef STAT_MACROS_BROKEN @@ -122,3 +124,67 @@ static int __rmtree_fn( int rmtree(const char *dir) { return nftw(dir, __rmtree_fn, 16, FTW_DEPTH | FTW_PHYS); } + +void which( + const char *restrict name, char *restrict resolved_path, + const char *restrict search_path) { + char *path = NULL; + + /* check if name is a path */ + if (realpath(name, resolved_path)) { + if (access(resolved_path, X_OK)) { + return; + } + } + + resolved_path[0] = '\0'; + + if (NULL == search_path) { + /* use PATH from environment */ + search_path = getenv("PATH"); + } + if (NULL != search_path) { + path = strdup(search_path); + } +#ifdef _CS_PATH + if (NULL == search_path) { + /* fall back to POSIX path */ + size_t pathlen = confstr(_CS_PATH, NULL, 0); + path = malloc(pathlen); + if (NULL != path) { + (void)confstr(_CS_PATH, path, pathlen); + } + } +#endif + + if (NULL == path) { + /* cannot determine PATH */ + return; + } + + char *saveptr = NULL; + char *path_tok = strtok_r(path, stringize(PATHSEP), &saveptr); + do { + if (PATH_MAX < pathjoin_r(resolved_path, PATH_MAX+1, path_tok, name)) { + /* TODO: handle error */ + continue; + } + + if (access(resolved_path, X_OK)) { + /* no match */ + resolved_path[0] = '\0'; + continue; + } + + /* match */ + break; + } while ((path_tok = strtok_r(NULL, stringize(PATHSEP), &saveptr))); + + if (!is_empty_string(resolved_path)) { + sk_debug("resolved command %s: %s", name, resolved_path); + } else { + sk_warn("could not resolve command: %s", name); + } + + free(path); +} diff --git a/src/util/fs.h b/src/util/fs.h index ad0a449..69849b9 100644 --- a/src/util/fs.h +++ b/src/util/fs.h @@ -72,4 +72,22 @@ char *mktempdir(char *dest); */ int rmtree(const char *dir); +/** + * which: + * find an executable in a PATH. + * + * this function searches @search_path (or the "PATH" environment variable if + * @search_path is NULL) for an executable with @name. + * The full path of the resolved executable will be stored in @resolved_path. + * If no match could be found, the content of @resolved_path will be the empty + * string. + * + * @param name: name of the executable to search for. + * @param resolved_path: a pointer to a buffer of at least PATH_MAX+1 length. + * @param search_path: PATH to search in (defaults to the "PATH" env variable). + */ +void which( + const char *restrict name, char *restrict resolved_path, + const char *restrict search_path); + #endif diff --git a/src/util/string.h b/src/util/string.h index 1ef07d1..b75e783 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,9 +1,14 @@ #ifndef _SK_UTIL_STRING_H #define _SK_UTIL_STRING_H +#if HAVE_STDBOOL_H +#include <stdbool.h> +#endif + #include "array.h" #include <stddef.h> +#include <string.h> /** * stringize: @@ -36,6 +41,25 @@ #define boolstr(b) (b ? "true" : "false") /** + * is_empty_string: + * Checks if the given string is empty (""). + * + * @param s: the string to check + * @return: true if @s is the empty string (""), false otherwise. + */ +#define is_empty_string(s) ('\0' == *s) + +/** + * string_starts_with: + * Checks if the given string starts with a given prefix. + * + * @param s: the string to check. + * @param p: the prefix. + * @return true if @s starts with @p, false otherwise. + */ +#define string_starts_with(s, p) (strncmp(s, p, strlen(p))) + +/** * strdup: * like strdup(3p). */ |
