Replace system_command_* and run_command_* APIs with simpler spawn() 09/189809/26
authorKarol Lewandowski <k.lewandowsk@samsung.com>
Wed, 7 Nov 2018 11:04:34 +0000 (12:04 +0100)
committerKarol Lewandowski <k.lewandowsk@samsung.com>
Thu, 28 Feb 2019 11:12:30 +0000 (12:12 +0100)
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
src/crash-manager/crash-manager.c
src/dump_systemstate/CMakeLists.txt
src/dump_systemstate/dump_systemstate.c
src/shared/spawn.c [new file with mode: 0644]
src/shared/spawn.h [new file with mode: 0644]
src/shared/util.c
src/shared/util.h

index 5c36d30..8e6a356 100644 (file)
@@ -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)
index cbe4b65..9f08d4b 100644 (file)
  * 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"
 
 #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);
index d15805b..60737cb 100755 (executable)
@@ -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)
index f7f254d..451a7ad 100644 (file)
 
 #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 (file)
index 0000000..130dccd
--- /dev/null
@@ -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 (file)
index 0000000..d511185
--- /dev/null
@@ -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
index 203d684..1161bed 100644 (file)
 #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;
index 651a544..4f6c6ab 100644 (file)
 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);