/* * exec.c * * Copyright (C) 2003 Bastian Blank * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAXLINE 1024 #define EX_NOEXEC 126 #define EX_NOTFOUND 127 static int internal_di_exec (const char *path, bool use_path, const char *const argv[], const char *const envp[], di_io_handler *stdout_handler, di_io_handler *stderr_handler, void *io_user_data, di_process_handler *parent_prepare_handler, void *parent_prepare_user_data, di_process_handler *child_prepare_handler, void *child_prepare_user_data) { char line[MAXLINE]; pid_t pid, pid2; int fds[4] = { -1, }, mode = 0, pipes = 0, i; if (stdout_handler) { mode += 1; pipes++; } if (stderr_handler) { mode += 2; pipes++; } for (i = 0; i < pipes; i++) { if (pipe (&fds[i * 2]) < 0) { int j; di_log (DI_LOG_LEVEL_WARNING, "pipe failed"); for (j = 0; j < i; j++) { close (fds[i * 2]); close (fds[i * 2 + 1]); } return -1; } } pid = fork (); if (pid <= 0) { for (i = 0; i < pipes; i++) close (fds[i * 2]); } if (pid == 0) { int temp, realfds[3] = { 0, fds[1], fds[3] }; temp = open ("/dev/null", O_RDWR); switch (mode) { case 0: realfds[1] = temp; realfds[2] = temp; break; case 2: realfds[1] = temp; realfds[2] = fds[1]; break; case 1: realfds[2] = fds[1]; break; } for (i = 1; i <= 2; i++) dup2 (realfds[i], i); close (temp); } for (i = 0; i < pipes; i++) close (fds[i * 2 + 1]); if (pid == 0) { if (child_prepare_handler) if (child_prepare_handler (pid, child_prepare_user_data)) exit (255); if (use_path) execvp (path, (char *const *) argv); else if (envp) execve (path, (char *const *) argv, (char *const *) envp); else execv (path, (char *const *) argv); exit (errno == ENOENT ? EX_NOTFOUND : EX_NOEXEC); } else if (pid < 0) { di_log (DI_LOG_LEVEL_WARNING, "fork failed"); return -1; } else { int status = -1; struct pollfd pollfds[pipes]; struct files { FILE *file; di_io_handler *handler; } files[pipes]; for (i = 0; i < pipes; i++) { fcntl (fds[i * 2], F_SETFL, O_NONBLOCK); files[i].file = fdopen (fds[i * 2], "r"); pollfds[i].fd = fds[i * 2]; pollfds[i].events = POLLIN; } switch (mode) { case 2: files[0].handler = stderr_handler; break; case 3: files[1].handler = stderr_handler; /* Fall through */ case 1: files[0].handler = stdout_handler; break; } if (parent_prepare_handler && parent_prepare_handler (pid, parent_prepare_user_data)) kill (pid, 9); else if (pipes) while (poll (pollfds, pipes, -1) >= 0) { bool exit = false; /* Implementations of poll() deliver various combinations of POLLIN and POLLHUP on EOF. fgets() detects it and we check with feof() below. References: http://www.greenend.org.uk/rjk/2001/06/poll.html */ for (i = 0; i < pipes; i++) { if (pollfds[i].revents & (POLLIN | POLLHUP)) { while (fgets (line, sizeof (line), files[i].file) != NULL) { size_t len = strlen (line); if (line[len - 1] == '\n') { line[len - 1] = '\0'; len--; } files[i].handler (line, len, io_user_data); exit = true; } } } if (exit) continue; for (i = 0; i < pipes; i++) if (feof(files[i].file)) exit = true; if (exit) break; } /* waitpid() can be interrupted by the SIGCHLD setup in log-output in case of a working poll() implementation. This depends on the kernel and the scheduler. */ do { pid2 = waitpid (pid, &status, 0); } while (pid2 == -1 && errno == EINTR); if (!pid2) return -1; for (i = 0; i < pipes; i++) fclose (files[i].file); /* closes fds[i * 2] */ return status; } return -1; } int di_exec_full (const char *path, const char *const argv[], di_io_handler *stdout_handler, di_io_handler *stderr_handler, void *io_user_data, di_process_handler *parent_prepare_handler, void *parent_prepare_user_data, di_process_handler *child_prepare_handler, void *child_prepare_user_data) { return internal_di_exec (path, false, argv, NULL, stdout_handler, stderr_handler, io_user_data, parent_prepare_handler, parent_prepare_user_data, child_prepare_handler, child_prepare_user_data); } int di_exec_env_full (const char *path, const char *const argv[], const char *const envp[], di_io_handler *stdout_handler, di_io_handler *stderr_handler, void *io_user_data, di_process_handler *parent_prepare_handler, void *parent_prepare_user_data, di_process_handler *child_prepare_handler, void *child_prepare_user_data) { return internal_di_exec (path, false, argv, envp, stdout_handler, stderr_handler, io_user_data, parent_prepare_handler, parent_prepare_user_data, child_prepare_handler, child_prepare_user_data); } int di_exec_path_full (const char *file, const char *const argv[], di_io_handler *stdout_handler, di_io_handler *stderr_handler, void *io_user_data, di_process_handler *parent_prepare_handler, void *parent_prepare_user_data, di_process_handler *child_prepare_handler, void *child_prepare_user_data) { return internal_di_exec (file, true, argv, NULL, stdout_handler, stderr_handler, io_user_data, parent_prepare_handler, parent_prepare_user_data, child_prepare_handler, child_prepare_user_data); } int di_exec_shell_full (const char *const cmd, di_io_handler *stdout_handler, di_io_handler *stderr_handler, void *io_user_data, di_process_handler *parent_prepare_handler, void *parent_prepare_user_data, di_process_handler *child_prepare_handler, void *child_prepare_user_data) { const char *const argv[] = { "sh", "-c", cmd, NULL }; return internal_di_exec ("/bin/sh", false, argv, NULL, stdout_handler, stderr_handler, io_user_data, parent_prepare_handler, parent_prepare_user_data, child_prepare_handler, child_prepare_user_data); } int di_exec_prepare_chdir (pid_t pid __attribute__ ((unused)), void *user_data) { char *path = user_data; if (chdir (path)) return -1; return 0; } int di_exec_prepare_chroot (pid_t pid __attribute__ ((unused)), void *user_data) { char *path = user_data; if (chroot (path)) return -1; if (chdir ("/")) return -1; return 0; } int di_exec_io_log (const char *buf, size_t len __attribute__ ((unused)), void *user_data __attribute__ ((unused))) { di_log (DI_LOG_LEVEL_OUTPUT, "%s", buf); return 0; } int di_exec_mangle_status (int status) { if (WIFEXITED (status)) return WEXITSTATUS (status); if (WIFSIGNALED (status)) return 128 + WTERMSIG (status); if (WIFSTOPPED (status)) return 128 + WSTOPSIG (status); return status; }