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 | |
| parent | 580b957769a5ee4ab7522e474f3996c03426a286 (diff) | |
| download | skonfig-c-2f425cb9e9d28c8e7f017a4fbe1203c7431e745b.tar.gz skonfig-c-2f425cb9e9d28c8e7f017a4fbe1203c7431e745b.zip | |
Implement command execution
| -rw-r--r-- | Makefile.am | 15 | ||||
| -rw-r--r-- | configure.ac | 5 | ||||
| -rw-r--r-- | src/rcopy.c | 43 | ||||
| -rw-r--r-- | src/rcopy.h | 17 | ||||
| -rw-r--r-- | src/rexec.c | 48 | ||||
| -rw-r--r-- | src/rexec.h | 18 | ||||
| -rw-r--r-- | src/run.c | 58 | ||||
| -rw-r--r-- | src/skonfig.c | 1 | ||||
| -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 |
15 files changed, 715 insertions, 11 deletions
diff --git a/Makefile.am b/Makefile.am index 7f8a228..faf7197 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,6 +4,15 @@ AM_CFLAGS += -Wall -Werror AM_CFLAGS += -include config.h bin_PROGRAMS = skonfig -skonfig_SOURCES = src/skonfig.c \ - src/config.c src/log.c src/run.c \ - src/util/array.c src/util/fs.c src/util/string.c +skonfig_SOURCES = \ + src/config.c \ + src/log.c \ + src/rcopy.c \ + src/rexec.c \ + src/run.c \ + src/skonfig.c \ + src/util/array.c \ + src/util/envp.c \ + src/util/exec.c \ + src/util/fs.c \ + src/util/string.c diff --git a/configure.ac b/configure.ac index 37560ea..8780d5b 100644 --- a/configure.ac +++ b/configure.ac @@ -84,9 +84,10 @@ check_set_ccopt -fstack-clash-protection \ # Checks for header files. AC_HEADER_STDC AC_HEADER_STDBOOL -AC_CHECK_HEADERS([sys/stat.h]) AC_HEADER_STAT -AC_CHECK_HEADERS([stdarg.h]) + +AC_CHECK_HEADERS_ONCE([stdarg.h]) +AC_CHECK_HEADERS_ONCE([linux/limits.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST diff --git a/src/rcopy.c b/src/rcopy.c new file mode 100644 index 0000000..8858f07 --- /dev/null +++ b/src/rcopy.c @@ -0,0 +1,43 @@ +#if HAVE_STDBOOL_H +#include <stdbool.h> +#endif + +#include "rexec.h" + + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +static char *alloc_cmd(const char *restrict format, ...) { + va_list ap; + va_start(ap, format); + int len = vsnprintf(NULL, 0, format, ap); + va_end(ap); + + char *buf = malloc(len+1); + va_start(ap, format); + (void)vsnprintf(buf, len+1, format, ap); + va_end(ap); + + return buf; +} + +int sk_rcopy_file( + const char *restrict src, const char *restrict target_host, + const char *restrict dst) { + char *remote_cmd = alloc_cmd("cat >%s && chmod 0700 %s", dst, dst); + FILE *sfil = fopen(src, "r"); + + /* TODO: set correct stdout and stderr */ + int rv = sk_rexec_command( + target_host, remote_cmd, + fileno(sfil), fileno(stdout), fileno(stderr), + NULL); + + /* clean up */ + fclose(sfil); + free(remote_cmd); + + return rv; +} diff --git a/src/rcopy.h b/src/rcopy.h new file mode 100644 index 0000000..d816f4a --- /dev/null +++ b/src/rcopy.h @@ -0,0 +1,17 @@ +#ifndef _SK_RCOPY_H +#define _SK_RCOPY_H + +/** + * sk_rcopy_file: + * copies a local file to a remote machine. + * + * @param src: the pathname to the local file. + * @param target_host: the name of the remote machine. + * @param dst: the pathname where to store the file on @target_host. + * @return 0 on success, other value otherwise. + */ +int sk_rcopy_file( + const char *restrict src, const char *restrict target_host, + const char *restrict dst); + +#endif diff --git a/src/rexec.c b/src/rexec.c new file mode 100644 index 0000000..56b0b4e --- /dev/null +++ b/src/rexec.c @@ -0,0 +1,48 @@ +#include "rexec.h" + +#include "log.h" + +#include "util/exec.h" + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/wait.h> + +int sk_rexec_command( + const char *restrict target_host, char *restrict cmd, + int stdin, int stdout, int stderr, envp_t *const extra_envp) { + envp_t *envp = envp_dup(); + + /* FIXME: set actual values */ + envp_set(envp, "__target_host", target_host, true); + envp_set(envp, "__target_fqdn", target_host, true); + envp_set(envp, "__target_hostname", target_host, true); + + if (NULL != envp) { + envp_merge(envp, extra_envp); + } + + /* TODO: use remote_exec instead of ssh */ + /* FIXME: Remove echo */ + pid_t pid = exec_subcmd( + (char *const[]){"echo", "ssh", (char *)target_host, cmd}, + stdin, stdout, stderr, true, NULL, envp); + + int rv = -1; + if (0 > pid) { + /* command could not be launched */ + sk_error("failed to execute command (%s) on target: %s", + cmd, strerror(errno)); + goto ret; + } + + int cmd_status = 0; + while (0 >= waitpid(pid, &cmd_status, 0)); + if (WIFEXITED(cmd_status)) { rv = WEXITSTATUS(cmd_status); } + + ret: + envp_free(envp); + + return rv; +} diff --git a/src/rexec.h b/src/rexec.h new file mode 100644 index 0000000..128241c --- /dev/null +++ b/src/rexec.h @@ -0,0 +1,18 @@ +#ifndef _SK_REXEC_H +#define _SK_REXEC_H + +#include "util/envp.h" + +/** + * sk_rexec_command: + * executes a command on a remote machine. + * + * @param target_host: the name of the remote machine. + * @param cmd: the command to execute. + * @return the exit status of the command. + */ +int sk_rexec_command( + const char *restrict target_host, char *restrict cmd, + int stdin, int stdout, int stderr, envp_t *const extra_envp); + +#endif @@ -2,6 +2,8 @@ #include "log.h" +#include "rcopy.h" + #include "util/array.h" #include "util/fs.h" @@ -249,6 +251,8 @@ sk_run_error sk_run_install_type_cmds(const char *local_hostdir) { } } + (void)closedir(dirp); + return SK_RUN_OK; } @@ -284,18 +288,60 @@ sk_run_error sk_run_setup_object_marker_file( sk_run_error sk_run_exec_global_explorers( const char *target_host, bool dry_run, const char *local_hostdir, struct sk_config *config) { - char explorers_dir[PATH_MAX+1]; + char local_explorers_dir[PATH_MAX+1], remote_explorers_dir[PATH_MAX+1]; + + if (PATH_MAX < pathjoin_r( + local_explorers_dir, PATH_MAX+1, + local_hostdir, "conf", "explorer")) { + sk_error("local global explorers path is longer than PATH_MAX"); + return SK_RUN_GENERIC_ERROR; + } + if (PATH_MAX < pathjoin_r( - explorers_dir, PATH_MAX+1, - local_hostdir, "explorer")) { - sk_error("global explorers path is longer than PATH_MAX"); + remote_explorers_dir, PATH_MAX+1, + config->global.remote_out_path, "conf", "explorer")) { + sk_error("remote global explorers path is longer than PATH_MAX"); return SK_RUN_GENERIC_ERROR; } sk_debug("Running global explorers"); + sk_trace("local explorers dir = %s", local_explorers_dir); - /* transfer global exploers to target */ - /* TODO */ + /* transfer global explorers to target */ + DIR *dirp = opendir(local_explorers_dir); + if (NULL == dirp) { + sk_error("cannot open directory (%s): %s", + local_explorers_dir, strerror(errno)); + return SK_RUN_GENERIC_ERROR; + } + + struct dirent *dp; + while (NULL != (dp = readdir(dirp))) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) { + continue; + } + + char explorer_src[PATH_MAX+1], explorer_dst[PATH_MAX+1]; + if (PATH_MAX < pathjoin_r( + explorer_src, PATH_MAX+1, + local_explorers_dir, dp->d_name)) { + sk_error("explorer path too long: %s/%s", + local_explorers_dir, dp->d_name); + continue; + } + if (PATH_MAX < pathjoin_r( + explorer_dst, PATH_MAX+1, + remote_explorers_dir, dp->d_name)) { + sk_error("explorer destination too long: %s/%s", + remote_explorers_dir, dp->d_name); + continue; + } + + sk_trace("copying explorer %s to %s:%s", + explorer_src, target_host, explorer_dst); + sk_rcopy_file(explorer_src, target_host, explorer_dst); + } + (void)closedir(dirp); /* execute global explorers */ /* TODO: implement multiple jobs */ diff --git a/src/skonfig.c b/src/skonfig.c index 4b1002e..0d2341b 100644 --- a/src/skonfig.c +++ b/src/skonfig.c @@ -237,6 +237,7 @@ int main(int argc, char *argv[]) { sk_init_config(config, NULL); /* update config from command line arguments */ + free(config->global.init_manifest); config->global.init_manifest = strdup(options.init_manifest); config->global.jobs = options.jobs; 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). */ |
