summaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
authorDennis Camera <skonfig@dtnr.ch>2022-08-22 00:27:47 +0200
committerDennis Camera <skonfig@dtnr.ch>2022-08-22 00:27:47 +0200
commit2f425cb9e9d28c8e7f017a4fbe1203c7431e745b (patch)
tree9ac97abaebdbe3363ee71c6a9f3210799d4494ab /src/util
parent580b957769a5ee4ab7522e474f3996c03426a286 (diff)
downloadskonfig-c-2f425cb9e9d28c8e7f017a4fbe1203c7431e745b.tar.gz
skonfig-c-2f425cb9e9d28c8e7f017a4fbe1203c7431e745b.zip
Implement command execution
Diffstat (limited to 'src/util')
-rw-r--r--src/util/envp.c182
-rw-r--r--src/util/envp.h111
-rw-r--r--src/util/exec.c97
-rw-r--r--src/util/exec.h23
-rw-r--r--src/util/fs.c66
-rw-r--r--src/util/fs.h18
-rw-r--r--src/util/string.h24
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).
*/