summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarol Lewandowski <k.lewandowsk@samsung.com>2018-11-07 12:04:34 +0100
committerKarol Lewandowski <k.lewandowsk@samsung.com>2019-02-28 12:12:30 +0100
commitdad0a556256a411b760104327c45a5ccd1a1b3eb (patch)
treee6ff2a6e9342ee782f8849766b97a2ea25995f99
parent7468269cc9e230683bc7cbaf9cf1a6ed4807ccf1 (diff)
downloadcrash-worker-dad0a556256a411b760104327c45a5ccd1a1b3eb.tar.gz
crash-worker-dad0a556256a411b760104327c45a5ccd1a1b3eb.tar.bz2
crash-worker-dad0a556256a411b760104327c45a5ccd1a1b3eb.zip
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
-rw-r--r--src/crash-manager/CMakeLists.txt1
-rw-r--r--src/crash-manager/crash-manager.c153
-rwxr-xr-xsrc/dump_systemstate/CMakeLists.txt1
-rw-r--r--src/dump_systemstate/dump_systemstate.c74
-rw-r--r--src/shared/spawn.c189
-rw-r--r--src/shared/spawn.h41
-rw-r--r--src/shared/util.c329
-rw-r--r--src/shared/util.h12
8 files changed, 329 insertions, 471 deletions
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 <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <limits.h>
-#include <unistd.h>
-#include <libgen.h>
+
+#include <dirent.h>
#include <fcntl.h>
+#include <gio/gio.h>
+#include <iniparser.h>
#include <inttypes.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/file.h>
#include <sys/mman.h>
+#include <sys/prctl.h>
#include <sys/procfs.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <sys/prctl.h>
-#include <sys/file.h>
#include <sys/vfs.h>
-#include <fcntl.h>
-#include <gio/gio.h>
-#include <dirent.h>
-#include <iniparser.h>
-#include <tzplatform_config.h>
+#include <unistd.h>
+
#include <pkgmgr-info.h>
-#include "crash-manager.h"
-#include "so-info.h"
-#include "shared/log.h"
-#include "shared/util.h"
-#include "dbus_notify.h"
+#include <tzplatform_config.h>
#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 <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <wait.h>
+
+#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);