From dad0a556256a411b760104327c45a5ccd1a1b3eb Mon Sep 17 00:00:00 2001 From: Karol Lewandowski Date: Wed, 7 Nov 2018 12:04:34 +0100 Subject: [PATCH] Replace system_command_* and run_command_* APIs with simpler spawn() This commit introduces following utility functions to handle external process execution: - spawn() - forks parent and executes specified command via execve(), establishes pipe between child and parent to monitor child lifetime. Ability to customize child environment is provided by the means of callbacks - spawn_{setstdout{,err},chdir,umask} are provided to set fds, chdir, and umask respectively. - wait_for_pid() - waitpid() for child, returning its exit code - spawn_wait() - combines both of above and adds ability to optionally specify timeout after which child is killed. Returns childs exit code (as wait_for_pid()), or -1 when timed out. Change-Id: I55d1e4ae8f547be3883c43132a0e083b91f730e3 --- src/crash-manager/CMakeLists.txt | 1 + src/crash-manager/crash-manager.c | 153 ++++++--------- src/dump_systemstate/CMakeLists.txt | 1 + src/dump_systemstate/dump_systemstate.c | 74 +++---- src/shared/spawn.c | 189 ++++++++++++++++++ src/shared/spawn.h | 41 ++++ src/shared/util.c | 329 -------------------------------- src/shared/util.h | 12 -- 8 files changed, 329 insertions(+), 471 deletions(-) create mode 100644 src/shared/spawn.c create mode 100644 src/shared/spawn.h diff --git a/src/crash-manager/CMakeLists.txt b/src/crash-manager/CMakeLists.txt index 5c36d30..8e6a356 100644 --- a/src/crash-manager/CMakeLists.txt +++ b/src/crash-manager/CMakeLists.txt @@ -7,6 +7,7 @@ SET(CRASH_MANAGER_SRCS so-info.c dbus_notify.c ${CMAKE_SOURCE_DIR}/src/shared/util.c + ${CMAKE_SOURCE_DIR}/src/shared/spawn.c ) INCLUDE(FindPkgConfig) diff --git a/src/crash-manager/crash-manager.c b/src/crash-manager/crash-manager.c index cbe4b65..9f08d4b 100644 --- a/src/crash-manager/crash-manager.c +++ b/src/crash-manager/crash-manager.c @@ -15,36 +15,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include -#include -#include -#include -#include -#include + +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include -#include +#include + #include -#include "crash-manager.h" -#include "so-info.h" -#include "shared/log.h" -#include "shared/util.h" -#include "dbus_notify.h" +#include #undef LOG_TAG #define LOG_TAG "CRASH_MANAGER" +#include "crash-manager.h" +#include "dbus_notify.h" +#include "shared/log.h" +#include "shared/spawn.h" +#include "shared/util.h" +#include "so-info.h" + /* Parsing */ #define KEY_MAX 255 #define CRASH_SECTION "CrashManager" @@ -72,10 +75,9 @@ #define WAIT_FOR_OPT_TIMEOUT_SEC 60 -#define DEFAULT_COMMAND_TIMEOUT 12*60 -#define MINICOREDUMPER_TIMEOUT DEFAULT_COMMAND_TIMEOUT -#define CRASH_STACK_TIMEOUT DEFAULT_COMMAND_TIMEOUT -#define ZIP_TIMEOUT DEFAULT_COMMAND_TIMEOUT +#define MINICOREDUMPER_TIMEOUT_MS DEFAULT_COMMAND_TIMEOUT_MS +#define CRASH_STACK_TIMEOUT_MS DEFAULT_COMMAND_TIMEOUT_MS +#define ZIP_TIMEOUT_MS DEFAULT_COMMAND_TIMEOUT_MS enum { RET_EXCEED = 1, @@ -683,20 +685,10 @@ exit: g_object_unref(conn); } -static int dump_system_state(const struct crash_info *cinfo) +static bool dump_system_state(const struct crash_info *cinfo, pid_t *pid) { - int ret; - char command[PATH_MAX]; - - ret = snprintf(command, sizeof(command), - "/usr/bin/dump_systemstate -d -k -j -f '%s'", - cinfo->log_path); - if (ret < 0) { - _E("Failed to snprintf for dump_systemstate command"); - return -1; - } - - return system_command_parallel(command); + char *av[] = {"/usr/bin/dump_systemstate", "-d", "-k", "-j", "-f", cinfo->log_path, NULL}; + return spawn(av, NULL, NULL, NULL, pid, NULL); } static void save_so_info(const struct crash_info *cinfo) @@ -748,11 +740,11 @@ end: #define SNPRINTF_OR_EXIT_W(name, format, member) if (snprintf(name##_str, sizeof(name##_str), format, cinfo->member) < 0) goto out; #define SNPRINTF_OR_EXIT(name, format) SNPRINTF_OR_EXIT_W(name, format, name##_info) -static int execute_minicoredump(struct crash_info *cinfo) +static bool execute_minicoredump(struct crash_info *cinfo, int *exit_code) { char *coredump_name = NULL; char *prstatus_fd_str = NULL; - int ret = -1; + bool is_ok = false; if (asprintf(&coredump_name, "%s.coredump", cinfo->name) == -1 || asprintf(&prstatus_fd_str, "%d", cinfo->prstatus_fd) == -1) { @@ -788,20 +780,12 @@ static int execute_minicoredump(struct crash_info *cinfo) NULL }; - _D(" %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s", - args[0], args[1], args[2], args[3], - args[4], args[5], args[6], args[7], - args[8], args[9], args[10], args[11], - args[12], args[13], args[14]); - - run_command_timeout(args[0], args, NULL, MINICOREDUMPER_TIMEOUT); - ret = 0; + is_ok = spawn_wait(args, NULL, NULL, NULL, MINICOREDUMPER_TIMEOUT_MS, exit_code); /* Minicoredumper must be executed to dump at least PRSTATUS for other tools, coredump, however, might have been disabled. */ if (!dump_core) { - ret = remove_file_in_dir(cinfo->pfx, coredump_name); - if (ret != 0) + if (remove_file_in_dir(cinfo->pfx, coredump_name) != 0) _E("Saving core disabled - removing coredump %s/%s failed: %m", cinfo->pfx, coredump_name); else @@ -813,15 +797,13 @@ out: free(coredump_name); free(prstatus_fd_str); - return ret; + return is_ok; } -static int execute_crash_stack(const struct crash_info *cinfo) +static bool execute_crash_stack(const struct crash_info *cinfo, int *exit_code) { - int ret = -1; - int fd = -1; - char pid_str[11], tid_str[11], sig_str[11], prstatus_fd_str[11]; + bool is_ok = false; SNPRINTF_OR_EXIT(pid, "%d") SNPRINTF_OR_EXIT(tid, "%d") @@ -842,46 +824,39 @@ static int execute_crash_stack(const struct crash_info *cinfo) NULL }; - _D(" %s %s %s %s %s %s %s %s %s", - args[0], args[1], args[2], args[3], - args[4], args[5], args[6], args[7], args[8]); - - fd = open(cinfo->info_path, O_WRONLY | O_CREAT, 0600); + int fd = open(cinfo->info_path, O_WRONLY | O_CREAT, 0600); if (fd < 0) { _E("open %s error: %m", cinfo->info_path); - goto out; + return false; } - ret = run_command_write_fd_timeout(CRASH_STACK_PATH, args, NULL, fd, NULL, 0, CRASH_STACK_TIMEOUT); + is_ok = spawn_wait(args, NULL, spawn_setstdout, (void *)fd, CRASH_STACK_TIMEOUT_MS, exit_code); + close(fd); out: - if (fd >= 0) - close(fd); - - return ret; + return is_ok; } #undef SNPRINTF_OR_EXIT #undef SNPRINTF_OR_EXIT_W -static int execute_crash_modules(struct crash_info *cinfo) +static bool execute_crash_modules(struct crash_info *cinfo) { - _D("Execute crash module: "); - - if (execute_minicoredump(cinfo) < 0) { + int exit_code = 0; + if (!execute_minicoredump(cinfo, &exit_code) || exit_code != 0) { _E("Failed to run minicoredumper - can not continue"); - return -1; + return false; } #ifdef SYS_ASSERT /* Use process_vm_readv() version as fallback if sys-assert * failed to generate report */ if (cinfo->have_sysassert_report) - return -1; + return false; #endif - execute_crash_stack(cinfo); + execute_crash_stack(cinfo, NULL); - return 0; + return true; } static int lock_dumpdir(void) @@ -1036,7 +1011,7 @@ static int check_disk_available(const char *path, int check_size) return 0; } -static int remove_file(struct file_info file) +static int remove_file_info(struct file_info file) { if (file.isdir) return remove_dir(file.path, 1); @@ -1098,7 +1073,7 @@ static void clean_dump(void) if (!remove_flag) continue; - if (remove_file(dump_list[i]) < 0) { + if (remove_file_info(dump_list[i]) < 0) { _E("Failed to remove %s", dump_list[i].path); continue; } @@ -1123,7 +1098,6 @@ static void compress(struct crash_info *cinfo) { int ret, lock_fd; char zip_path[PATH_MAX]; - char cwd[PATH_MAX]; ret = snprintf(zip_path, sizeof(zip_path), "%s/report.zip", cinfo->temp_dir); @@ -1132,16 +1106,6 @@ static void compress(struct crash_info *cinfo) return; } - if (getcwd(cwd, sizeof(cwd)) == NULL) { - _E("getcwd() error: %m\n"); - return; - } - - if (chdir(cinfo->temp_dir) == -1) { - _E("chdir() to %s error: %m\n", cinfo->temp_dir); - return; - } - char *args[] = { "/bin/zip", "-y", @@ -1151,12 +1115,7 @@ static void compress(struct crash_info *cinfo) NULL }; - run_command_timeout(args[0], args, NULL, ZIP_TIMEOUT); - - if (chdir(cwd) == -1) { - _E("chdir() to %s error: %m\n", cwd); - return; - } + (void)spawn_wait(args, NULL, spawn_chdir, (void *)cinfo->temp_dir, ZIP_TIMEOUT_MS, NULL); if ((lock_fd = lock_dumpdir()) < 0) return; @@ -1226,7 +1185,7 @@ int main(int argc, char *argv[]) struct crash_info cinfo = {.prstatus_fd = -1}; /* Execute dump_systemstate in parallel */ - static int dump_state_pid; + static pid_t dump_state_pid; int debug_mode = access(DEBUGMODE_PATH, F_OK) == 0; int res = 0; @@ -1272,16 +1231,17 @@ int main(int argc, char *argv[]) if (report_type >= REP_TYPE_FULL) { /* Exec dump_systemstate */ - dump_state_pid = dump_system_state(&cinfo); - - if (dump_state_pid < 0) { + if (!dump_system_state(&cinfo, &dump_state_pid)) { res = EXIT_FAILURE; + _E("Failed to get system state report"); goto exit; } } + /* Exec crash modules */ - if (execute_crash_modules(&cinfo) < 0) { + if (!execute_crash_modules(&cinfo)) { res = EXIT_FAILURE; + _E("Failed to get basic crash information"); goto exit; } @@ -1290,7 +1250,7 @@ int main(int argc, char *argv[]) save_so_info(&cinfo); /* Wait dump_system_state */ - wait_system_command(dump_state_pid); + wait_for_pid(dump_state_pid, NULL); /* Tar compression */ if (allow_zip) @@ -1340,6 +1300,7 @@ int main(int argc, char *argv[]) launch_crash_popup(&cinfo); exit: + _I("Exiting with exit code %d", res); if (cinfo.prstatus_fd >= 0) close(cinfo.prstatus_fd); free(crash_temp_path); diff --git a/src/dump_systemstate/CMakeLists.txt b/src/dump_systemstate/CMakeLists.txt index d15805b..60737cb 100755 --- a/src/dump_systemstate/CMakeLists.txt +++ b/src/dump_systemstate/CMakeLists.txt @@ -5,6 +5,7 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src) SET(SRCS dump_systemstate.c ${CMAKE_SOURCE_DIR}/src/shared/util.c + ${CMAKE_SOURCE_DIR}/src/shared/spawn.c ) INCLUDE(FindPkgConfig) diff --git a/src/dump_systemstate/dump_systemstate.c b/src/dump_systemstate/dump_systemstate.c index f7f254d..451a7ad 100644 --- a/src/dump_systemstate/dump_systemstate.c +++ b/src/dump_systemstate/dump_systemstate.c @@ -35,10 +35,19 @@ #include "shared/util.h" #include "shared/log.h" +#include "shared/spawn.h" #define FILE_PERM (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) -#define TIMEOUT_DEFAULT 60 +#define TIMEOUT_DEFAULT_MS (60*1000) /* 60sec */ + + +enum { + EXIT_OK = 0, // all ok + EXIT_ERR = (1 << 0), // setup error + EXIT_FILEERR = (1 << 1), // at least one file failed to be copied + EXIT_CMDERR = (1 << 2), // at least one command failed +}; static struct dump_item { const char *title; @@ -84,12 +93,6 @@ static int get_disk_used_percent(const char *path) int main(int argc, char *argv[]) { -#define INFORM_IF_ERROR(ret) \ - if (ret < 0) { \ - exit_code = ret; \ - fprintf_fd(out_fd, "\nCommand failed with error code: %d", ret); \ - } - int c, ret, i, is_root, dpercent, exit_code = 0; const char *arg_file = NULL; int out_fd = -1; @@ -136,7 +139,7 @@ int main(int argc, char *argv[]) out_fd = open(arg_file, O_WRONLY | O_TRUNC | O_CREAT, FILE_PERM); if (out_fd < 0) { perror("couldn't open output file"); - exit_code = out_fd; + exit_code = EXIT_ERR; goto exit; } } @@ -150,79 +153,82 @@ int main(int argc, char *argv[]) fprintf_fd(out_fd, "\n%s(%s)\n", dump_item[i].title, dump_item[i].path); ret = dump_file_write_fd(out_fd, (char *)dump_item[i].path); - INFORM_IF_ERROR(ret) + if (ret < 0) { + fprintf_fd(out_fd, "Unable to copy file.\n"); + exit_code |= EXIT_FILEERR; + } } fprintf_fd(out_fd, "\n"); +#define spawn_wait_checked(av, env) \ + do { \ + int err; \ + if (!spawn_wait(av, env, spawn_setstdout, (void *)out_fd, DEFAULT_COMMAND_TIMEOUT_MS, &err) || err != 0) { \ + exit_code |= EXIT_CMDERR; \ + fprintf_fd(out_fd, "\nCommand failed with error code: %d", err); \ + } \ + } while (0) + fprintf_fd(out_fd, "\n==== System disk space usage (/bin/df -h)\n"); char *df_args[] = {"/bin/df", "-h", NULL}; - ret = run_command_write_fd_timeout(df_args[0], df_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(df_args, NULL); dpercent = get_disk_used_percent("/opt"); if (90 < dpercent) { fprintf_fd(out_fd, "\n==== System disk space usage detail - %d%% (/bin/du -ah /opt)\n", dpercent); char *du_args[] = {"/bin/du", "-ah", "/opt", "--exclude=/opt/usr", NULL}; - ret = run_command_write_fd_timeout(du_args[0], du_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(du_args, NULL); } fprintf_fd(out_fd, "\n==== System timezone (ls -al /opt/etc/localtime)\n"); char *ls_args[] = {"/bin/ls", "-al", "/opt/etc/localtime", NULL}; - ret = run_command_write_fd_timeout(ls_args[0], ls_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(ls_args, NULL); fprintf_fd(out_fd, "\n==== System summary (/usr/bin/top -bcH -n 1)\n"); char *top_args[] = {"/bin/top", "-bcH", "-n", "1", NULL}; char *top_env[] = {"COLUMNS=200", NULL}; - ret = run_command_write_fd_timeout(top_args[0], top_args, top_env, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(top_args, top_env); fprintf_fd(out_fd, "\n==== Current processes (/bin/ps auxfw)\n"); char *ps_args[] = {"/bin/ps", "auxfw", NULL}; - ret = run_command_write_fd_timeout(ps_args[0], ps_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(ps_args, NULL); fprintf_fd(out_fd, "\n==== System memory statistics (/usr/bin/memps -v)\n"); char *memps_args[] = {"/bin/memps", "-v", NULL}; - ret = run_command_write_fd_timeout(memps_args[0], memps_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(memps_args, NULL); if (is_root) { fprintf_fd(out_fd, "\n==== System configuration (/usr/bin/buxton2ctl dump memory, system)\n"); - char *get_mem_args[] = {"/usr/bin/buxton2ctl", "dump", "memory", NULL}; - ret = run_command_write_fd_timeout(get_mem_args[0], get_mem_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + char *get_mem_args[] = {"/bin/buxton2ctl", "dump", "memory", NULL}; + spawn_wait_checked(get_mem_args, NULL); - char *get_db_args[] = {"/usr/bin/buxton2ctl", "dump", "system", NULL}; - ret = run_command_write_fd_timeout(get_db_args[0], get_db_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + char *get_sys_args[] = {"/bin/buxton2ctl", "dump", "system", NULL}; + spawn_wait_checked(get_sys_args, NULL); } if (arg_dmesg && is_root) { fprintf_fd(out_fd, "\n==== Kernel messages (TZ=UTC /bin/dmesg -T)\n"); char *dmesg_args[] = {"/bin/dmesg", "-T", NULL}; char *dmesg_env[] = {"TZ=UTC", NULL}; - ret = run_command_write_fd_timeout(dmesg_args[0], dmesg_args, dmesg_env, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(dmesg_args, dmesg_env); } if (arg_dlog) { fprintf_fd(out_fd, "\n==== Log messages\n"); char *dlogutil_args[] = {"/bin/dlogutil", "-d", "-v", "threadtime", "-u", "16384", NULL}; - ret = run_command_write_fd_timeout(dlogutil_args[0], dlogutil_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(dlogutil_args, NULL); } if (arg_journal) { fprintf_fd(out_fd, "\n==== Journal messages\n"); char *journalctl_args[] = {"/bin/journalctl", "-b", "-n", "1024", NULL}; - ret = run_command_write_fd_timeout(journalctl_args[0], journalctl_args, NULL, out_fd, NULL, 0, TIMEOUT_DEFAULT); - INFORM_IF_ERROR(ret) + spawn_wait_checked(journalctl_args, NULL); } +#undef spawn_wait_checked + if (arg_file) close(out_fd); exit: return exit_code; -#undef INFORM_IF_ERROR + } diff --git a/src/shared/spawn.c b/src/shared/spawn.c new file mode 100644 index 0000000..130dccd --- /dev/null +++ b/src/shared/spawn.c @@ -0,0 +1,189 @@ +/* Utilities for spawning sub-processes + * + * Copyright (c) 2018-2019 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "spawn.h" + +/* spawn prepare function(s) - to be called in child process */ + +int spawn_setstdout(void *userdata) +{ + int fd = (int)userdata; + return dup2(fd, STDOUT_FILENO) < 0 ? -1 : 0; +} + +int spawn_setstdouterr(void *userdata) +{ + int fd = (int)userdata; + return dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0 ? -1 : 0; +} + +int spawn_chdir(void *userdata) +{ + return chdir((char *)userdata); +} + +int spawn_umask(void *userdata) +{ + (void)umask((mode_t)userdata); + return 0; +} + +/* spawn api */ + +bool wait_for_pid(pid_t pid, int *exit_code) +{ + int status = 0; + int r = 0; + bool is_ok; + + if (pid < 0) + return false; + + do { + r = waitpid(pid, &status, 0); + is_ok = r >= 0; + } while (!is_ok && errno == EINTR); + + if (!is_ok) { + _E("Error while waiting for child %d status: %m", (int)pid); + return false; + } + + /* child has terminated */ + if (WIFEXITED(status)) + r = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + r = WTERMSIG(status); + else if (WIFSTOPPED(status)) + r = WSTOPSIG(status); + + _D("Child with pid %d terminated with exit code %d", (int)pid, r); + if (exit_code) + *exit_code = r; + + return true; +} + +static int spawn_child(char *const av[], char *const ev[], spawn_prepare_fn prep, void *prepdata) +{ + static const int spawn_error = 127; + + int r = prep ? prep(prepdata) : 0; + if (r < 0) + return spawn_error; + + execve(av[0], av, ev); + return spawn_error; +} + +bool spawn(char *const av[], char *const ev[], spawn_prepare_fn prep, void *prepdata, pid_t *childpid, int *childfd) +{ + int pipefd[2]; + if (pipe(pipefd) < 0) { + _E("pipe: %m"); + return false; + } + + pid_t pid = fork(); + if (pid < 0) { + _E("fork: %m"); + close(pipefd[0]); + close(pipefd[1]); + return false; + } else if (pid == 0) { + close(pipefd[0]); + _exit(spawn_child(av, ev, prep, prepdata)); + } + close(pipefd[1]); + + char *cmd = concatenate(av); + char *env = concatenate(ev); + _D("Spawned child with pid %d to execute command <%s> with environment <%s>", (int)pid, cmd, env); + free(cmd); + free(env); + + // parent + if (childpid) + *childpid = pid; + + if (childfd) { + _D("Leaving childfd status descriptor %d open for child status notification", pipefd[0]); + *childfd = pipefd[0]; + } else + close(pipefd[0]); + + return true; +} + +/* returns true if child terminated */ +static bool wait_and_kill(pid_t childpid, int childfd, int timeout_ms) +{ + struct pollfd pfd = { .fd = childfd, .events = POLLIN | POLLERR | POLLHUP }; + int r; + + _D("Beginning to wait %dms for child pid %d", timeout_ms, (int)childpid); + do { + r = poll(&pfd, 1, timeout_ms); + } while (r < 0 && errno == EINTR); + + if (r < 0) { + _E("Internal error while trying to wait for child pid %d: %m", (int)childpid); + return false; + } else if (r == 0) { + _W("Timeout %dms for child pid %d expired. Killing.", timeout_ms, (int)childpid); + kill(childpid, SIGKILL); + } else + _D("Child pid %d terminated before timeout.", (int)childpid); + + return true; +} + +bool spawn_wait(char *const av[], char *const ev[], spawn_prepare_fn prep, void *prepdata, int timeout_ms, int *exit_code) +{ + pid_t childpid; + int childfd; + + if (!spawn(av, ev, prep, prepdata, &childpid, &childfd)) { + _E("spawn() returned an error - aborting waiting"); + return false; + } + + if (timeout_ms > 0) + (void)wait_and_kill(childpid, childfd, timeout_ms); + + bool wait_ok = wait_for_pid(childpid, exit_code); + + close(childfd); + + return wait_ok; +} diff --git a/src/shared/spawn.h b/src/shared/spawn.h new file mode 100644 index 0000000..d511185 --- /dev/null +++ b/src/shared/spawn.h @@ -0,0 +1,41 @@ +/* Utilities for spawning sub-processes + * + * Copyright (c) 2018-2019 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __SPAWN_H__ +#define __SPAWN_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEFAULT_COMMAND_TIMEOUT_MS (60*1000) /* 60sec */ + +typedef int (*spawn_prepare_fn)(void *); + +int spawn_setstdout(void *userdata); +int spawn_setstdouterr(void *userdata); +int spawn_chdir(void *userdata); +int spawn_umask(void *userdata); + +bool wait_for_pid(pid_t pid, int *exit_code); +bool spawn(char *const av[], char *const ev[], spawn_prepare_fn prep, void *prepdata, pid_t *childpid, int *childfd); +bool spawn_wait(char *const av[], char *const ev[], spawn_prepare_fn prep, void *prepdata, int timeout_ms, int *exit_code); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/shared/util.c b/src/shared/util.c index 203d684..1161bed 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -37,113 +37,6 @@ #include "util.h" #include "log.h" -#define READ_BUFF_SIZE 4096 -#define SELECT_TIMEOUT_US 100000 - -int system_command_parallel(char *command) -{ - int pid = 0; - const char *environ[] = { NULL }; - - if (command == NULL) - return -1; - pid = fork(); - if (pid == -1) - return -1; - if (pid == 0) { - char *argv[4]; - argv[0] = "sh"; - argv[1] = "-c"; - argv[2] = (char *)command; - argv[3] = 0; - execve("/bin/sh", argv, (char **)environ); - exit(127); - } - - return pid; -} - -int wait_system_command(int pid) -{ - int status = 0; - - if (pid < 0) - return -1; - - do { - if (waitpid(pid, &status, 0) == -1) { - if (errno != EINTR) - return -1; - } else { - if (WIFEXITED(status)) - return WEXITSTATUS(status); - else if (WIFSIGNALED(status)) - return WTERMSIG(status); - else if (WIFSTOPPED(status)) - return WSTOPSIG(status); - } - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); - - return 0; -} - -int system_command(char *command) -{ - int pid = 0; - - pid = system_command_parallel(command); - - return wait_system_command(pid); -} - -int system_command_with_timeout(int timeout_seconds, char *command) -{ - const char *environ[] = { NULL }; - - if (command == NULL) - return -1; - clock_t start = clock(); - pid_t pid = fork(); - /* handle error case */ - if (pid < 0) { - _E("fork: %d\n", errno); - return pid; - } - /* handle child case */ - if (pid == 0) { - char *argv[4]; - argv[0] = "sh"; - argv[1] = "-c"; - argv[2] = (char *)command; - argv[3] = 0; - - execve("/bin/sh", argv, (char **)environ); - _SI("exec(%s): %d\n", command, errno); - _exit(-1); - } - /* handle parent case */ - for (;;) { - int status; - pid_t p = waitpid(pid, &status, WNOHANG); - float elapsed = (float) (clock() - start) / CLOCKS_PER_SEC; - if (p == pid) { - if (WIFSIGNALED(status)) - _SI("%s: Killed by signal %d\n", command, WTERMSIG(status)); - else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) - _SI("%s: Exit code %d\n", command, WEXITSTATUS(status)); - return WEXITSTATUS(status); - } - if (timeout_seconds && elapsed > timeout_seconds) { - _SI("%s: Timed out after %.1fs (killing pid %d)\n", - command, elapsed, pid); - kill(pid, SIGTERM); - return -1; - } - /* poll every 0.1 sec */ - usleep(100000); - } -} - int write_fd(int fd, const void *buf, int len) { int count; @@ -280,228 +173,6 @@ int dump_file_write_fd(int dfd, char *src) return res; } -static int run_command(char *path, char *args[], char *env[], int fd[]) -{ - if (dup2(fd[1], STDOUT_FILENO) == -1) { - _E("dup2 error: %m"); - return -1; - } - - if (close(fd[1]) == -1) { - _E("close fd error: %m"); - return -1; - } - - if (close(fd[0]) == -1) { - _E("close fd error: %m"); - return -1; - } - - if (execvpe(path, args, env) == -1) { - _E("run command %s error: %m", path); - return -1; - } - return -1; -} - -static int wait_for_child(pid_t pid, int *exit_code, int timeout) -{ - for (int i = 0; i < 10*timeout; i++) { - int status; - pid_t p = waitpid(pid, &status, WNOHANG); - if (p == pid) { - if (WIFSIGNALED(status)) { - _I("Killed by signal %d\n", WTERMSIG(status)); - return -1; - } else if (WIFEXITED(status)) { - *exit_code = WEXITSTATUS(status); - return 0; - } - } else if (p == -1) { - _E("waitpid error: %m"); - return -1; - } - usleep(100000); - } - return -1; -} - -static int read_into_buff(int fd, char *buff, int size, int timeout_us, int *eof) -{ - struct timeval tout; - int sel_ret; - fd_set set; - - FD_ZERO(&set); - FD_SET(fd, &set); - - tout.tv_sec = timeout_us / 1000000; - tout.tv_usec = timeout_us % 1000000; - *eof = 0; - - int buff_pos = 0; - if ((sel_ret = select(fd+1, &set, NULL, NULL, &tout)) >= 0) { - if (sel_ret > 0) { - // we can do nonblocking read - int readed = read(fd, &buff[buff_pos], size); - - if (readed > 0) { - buff_pos += readed; - size -= readed; - } else if (readed == 0) { - // no more data to read - *eof = 1; - } else { - // error - _E("read data from the pipe error: %m"); - return -1; - } - } - } else - _E("select() error: %m"); - return buff_pos; -} - -// Usage: -// if buff is not NULL then 'size' bytes of the result is written the buffer, -// otherwise result is written to the dfd descriptor -int run_command_write_fd_timeout(char *path, char *args[], char *env[], int dfd, char *buff, int size, int timeout) -{ - char BUFF[READ_BUFF_SIZE]; - int fd[2]; - struct timeval start, end; - int write_to_fd = buff == NULL ? 1 : 0; - - if (!write_to_fd && size <= 0) { - _E("buffer size must be greather than zero"); - return -1; - } - - if (pipe(fd)) { - _E("pipe create error: %m"); - return -1; - } - - pid_t pid = fork(); - - if (pid == 0) { - return run_command(path, args, env, fd); - } else if (pid > 0) { - if (close(fd[1]) == -1) { - _E("close fd error: %m"); - return -1; - } - - if (gettimeofday(&start, NULL) == -1) { - _E("gettimeofday error: %m"); - return -1; - } - - int readed; - int eof = 0; - int outdated = 0; - int count = 0; - - int act_size; - char *act_buff; - - if (write_to_fd) { - act_size = READ_BUFF_SIZE; - act_buff = BUFF; - } else { - act_size = size; - act_buff = buff; - } - - while ((readed = read_into_buff(fd[0], act_buff, act_size, 1000000, &eof)) >= 0) { - if (readed > 0) { - // we have some data - if (count < (INT_MAX - readed)) - count += readed; - else - count = INT_MAX; - - if (write_to_fd) { - if (write_fd(dfd, act_buff, readed) == -1) { - _E("write data to pipe error: %m"); - break; - } - } else { - act_buff += readed; - act_size -= readed; - - if (act_size == 0) { - // buff is full, we can return - eof = 1; - } - } - } - - if (eof) - break; - - if (gettimeofday(&end, NULL) == -1) { - _E("gettimeofday error: %m"); - break; - } - - if ((end.tv_sec - start.tv_sec) > timeout) { - outdated = 1; - break; - } - - if (readed == 0) - usleep(100000); - } - - if (outdated) { - _E("command timeout: %s", path); - if (kill(pid, 0) == 0) { - // we can kill a child because we don't - // need it anymore - if (kill(pid, SIGTERM) == -1) - _E("kill child %d error: %m", pid); - } - } - - if (close(fd[0]) == -1) { - _E("close fd error: %m"); - return -1; - } - - // let's wait a second for a child - int exit_code = -1; - int wait_res = wait_for_child(pid, &exit_code, 1); - - if (wait_res != 0) - _I("wait_for_child for \%s\" returns non-zero value\n", path); - else if (exit_code != 0) - _I("\"%s\" exit code: %d\n", path, exit_code); - - return (eof == 1 && exit_code == 0) ? count : -abs(exit_code); - } else { - _E("fork() error: %m"); - return -1; - } - - return -1; -} - -int run_command_timeout(char *path, char *args[], char *env[], int timeout) -{ - int fd = open("/dev/null", O_WRONLY); - if (fd < 0) { - _E("open /dev/null error: %m"); - return -1; - } - - int res = run_command_write_fd_timeout(path, args, env, fd, NULL, 0, timeout); - - close(fd); - - return res; -} - int fsync_path(char *const path) { int fd, ret; diff --git a/src/shared/util.h b/src/shared/util.h index 651a544..4f6c6ab 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -29,14 +29,6 @@ extern "C" { #endif -int system_command(char *command); - -int system_command_with_timeout(int timeout_seconds, char *command); - -int system_command_parallel(char *command); - -int wait_system_command(int pid); - int write_fd(int fd, const void *buf, int len); int copy_bytes(int destfd, int srcfd, off_t *ncopied); @@ -47,10 +39,6 @@ int move_file(char *dst, char *src); int dump_file_write_fd(int dfd, char *src); -int run_command_write_fd_timeout(char *path, char *args[], char *env[], int dfd, char *buff, int size, int timeout); - -int run_command_timeout(char *path, char *args[], char *env[], int timeout); - int fsync_path(char *const path); int make_dir(const char *path, const char *name, int mode); -- 2.7.4