#include "pmix_config.h"
#include "pmix.h"
#include "src/include/pmix_types.h"
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#include <signal.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#include <stdlib.h>
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <stdarg.h>
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#ifdef HAVE_DIRENT_H
# include <dirent.h>
#endif
#include <ctype.h>
#include "src/class/pmix_pointer_array.h"
#include "src/util/pmix_error.h"
#include "src/util/pmix_fd.h"
#include "src/util/pmix_environ.h"
#include "src/util/pmix_show_help.h"
#include "src/include/pmix_globals.h"
#include "src/threads/pmix_threads.h"
#include "src/util/pmix_name_fns.h"
#include "src/mca/pfexec/base/base.h"
#include "src/mca/pfexec/linux/pfexec_linux.h"
static pmix_status_t spawn_job(pmix_pfexec_fork_caddy_t *fcd);
static pmix_status_t kill_proc(pmix_proc_t *proc);
static pmix_status_t signal_proc(pmix_proc_t *proc, int32_t signal);
static void send_error_show_help(int fd, int exit_status, const char *file, const char *topic,
...) __pmix_attribute_noreturn__;
static void do_child(pmix_app_t *cd, char **env, pmix_pfexec_child_t *child,
int write_fd) __pmix_attribute_noreturn__;
pmix_pfexec_base_module_t pmix_pfexec_linux_module = {
.spawn_job = spawn_job,
.kill_proc = kill_proc,
.signal_proc = signal_proc,
};
static pmix_status_t sigproc(pid_t pd, int signum)
{
pid_t pgrp;
pid_t pid;
pid = pd;
#if HAVE_SETPGID
pgrp = getpgid(pd);
if (-1 != pgrp) {
pid = -pgrp;
}
#endif
if (0 != kill(pid, signum)) {
if (ESRCH != errno) {
pmix_output_verbose(2, pmix_pfexec_base_framework.framework_output,
"%s pfexec:linux:SENT SIGNAL %d TO PID %d GOT ERRNO %d",
PMIX_NAME_PRINT(&pmix_globals.myid), signum, (int) pid, errno);
return errno;
}
}
pmix_output_verbose(2, pmix_pfexec_base_framework.framework_output,
"%s pfexec:linux:SENT SIGNAL %d TO PID %d SUCCESS",
PMIX_NAME_PRINT(&pmix_globals.myid), signum, (int) pid);
return 0;
}
static pmix_status_t kill_proc(pmix_proc_t *proc)
{
pmix_status_t rc;
pmix_lock_t mylock;
pmix_pfexec_signal_caddy_t *kcd;
PMIX_CONSTRUCT_LOCK(&mylock);
PMIX_PFEXEC_KILL(kcd, proc, sigproc, &mylock);
PMIX_WAIT_THREAD(&mylock);
rc = mylock.status;
PMIX_DESTRUCT_LOCK(&mylock);
PMIX_RELEASE(kcd);
return rc;
}
static pmix_status_t signal_proc(pmix_proc_t *proc, int32_t signal)
{
pmix_status_t rc;
pmix_lock_t mylock;
pmix_pfexec_signal_caddy_t *scd;
PMIX_CONSTRUCT_LOCK(&mylock);
PMIX_PFEXEC_SIGNAL(scd, proc, signal, sigproc, &mylock);
PMIX_WAIT_THREAD(&mylock);
rc = mylock.status;
PMIX_DESTRUCT_LOCK(&mylock);
PMIX_RELEASE(scd);
return rc;
}
static void set_handler_linux(int sig)
{
struct sigaction act;
act.sa_handler = SIG_DFL;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(sig, &act, (struct sigaction *) 0);
}
static int write_help_msg(int fd, pmix_pfexec_pipe_err_msg_t *msg, const char *file,
const char *topic, va_list ap)
{
int ret;
char *str;
if (NULL == file || NULL == topic) {
return PMIX_ERR_BAD_PARAM;
}
str = pmix_show_help_vstring(file, topic, true, ap);
msg->file_str_len = (int) strlen(file);
if (msg->file_str_len > PMIX_PFEXEC_MAX_FILE_LEN) {
PMIX_ERROR_LOG(PMIX_ERR_BAD_PARAM);
return PMIX_ERR_BAD_PARAM;
}
msg->topic_str_len = (int) strlen(topic);
if (msg->topic_str_len > PMIX_PFEXEC_MAX_TOPIC_LEN) {
PMIX_ERROR_LOG(PMIX_ERR_BAD_PARAM);
return PMIX_ERR_BAD_PARAM;
}
msg->msg_str_len = (int) strlen(str);
if (PMIX_SUCCESS != (ret = pmix_fd_write(fd, sizeof(*msg), msg))) {
goto out;
}
if (msg->file_str_len > 0
&& PMIX_SUCCESS != (ret = pmix_fd_write(fd, msg->file_str_len, file))) {
goto out;
}
if (msg->topic_str_len > 0
&& PMIX_SUCCESS != (ret = pmix_fd_write(fd, msg->topic_str_len, topic))) {
goto out;
}
if (msg->msg_str_len > 0 && PMIX_SUCCESS != (ret = pmix_fd_write(fd, msg->msg_str_len, str))) {
goto out;
}
out:
free(str);
return ret;
}
static void send_error_show_help(int fd, int exit_status, const char *file, const char *topic, ...)
{
va_list ap;
pmix_pfexec_pipe_err_msg_t msg;
msg.fatal = true;
msg.exit_status = exit_status;
va_start(ap, topic);
write_help_msg(fd, &msg, file, topic, ap);
va_end(ap);
exit(exit_status);
}
static int close_open_file_descriptors(int write_fd, int keepalive)
{
#if defined(__OSX__)
DIR *dir = opendir("/dev/fd");
#else
DIR *dir = opendir("/proc/self/fd");
#endif
if (NULL == dir) {
return PMIX_ERR_FILE_OPEN_FAILURE;
}
struct dirent *files;
int dir_scan_fd = dirfd(dir);
if (dir_scan_fd < 0) {
return PMIX_ERR_FILE_OPEN_FAILURE;
}
while (NULL != (files = readdir(dir))) {
if (!isdigit(files->d_name[0])) {
continue;
}
int fd = strtol(files->d_name, NULL, 10);
if (errno == EINVAL || errno == ERANGE) {
closedir(dir);
return PMIX_ERR_TYPE_MISMATCH;
}
if (fd >= 3 && fd != write_fd && fd != dir_scan_fd && fd != keepalive) {
close(fd);
}
}
closedir(dir);
return PMIX_SUCCESS;
}
static void do_child(pmix_app_t *app, char **env, pmix_pfexec_child_t *child, int write_fd)
{
int i, errval;
sigset_t sigs;
long fd, fdmax = sysconf(_SC_OPEN_MAX);
char dir[MAXPATHLEN];
#if HAVE_SETPGID
setpgid(0, 0);
#endif
pmix_fd_set_cloexec(write_fd);
if (PMIX_SUCCESS != (i = pmix_pfexec_base_setup_child(child))) {
PMIX_ERROR_LOG(i);
send_error_show_help(write_fd, 1, "help-pfexec-linux.txt", "iof setup failed",
pmix_globals.hostname, app->cmd);
}
if (PMIX_SUCCESS != close_open_file_descriptors(write_fd, child->keepalive[1])) {
for (fd = 3; fd < fdmax; fd++) {
if (fd != write_fd && fd != child->keepalive[1]) {
close(fd);
}
}
}
set_handler_linux(SIGTERM);
set_handler_linux(SIGINT);
set_handler_linux(SIGHUP);
set_handler_linux(SIGPIPE);
set_handler_linux(SIGCHLD);
sigprocmask(0, 0, &sigs);
sigprocmask(SIG_UNBLOCK, &sigs, 0);
if (NULL != app->cwd) {
if (0 != chdir(app->cwd)) {
send_error_show_help(write_fd, 1, "help-pfexec-linux.txt", "wdir-not-found", "pmixd",
app->cwd, pmix_globals.hostname);
}
}
execve(app->cmd, app->argv, env);
errval = errno;
if (0 != getcwd(dir, sizeof(dir))) {
pmix_strncpy(dir, "GETCWD-FAILED", sizeof(dir));
}
send_error_show_help(write_fd, 1, "help-pfexec-linux.txt", "execve error",
pmix_globals.hostname, dir, app->cmd, strerror(errval));
}
static pmix_status_t do_parent(pmix_app_t *app, pmix_pfexec_child_t *child, int read_fd)
{
pmix_status_t rc;
pmix_pfexec_pipe_err_msg_t msg;
char file[PMIX_PFEXEC_MAX_FILE_LEN + 1], topic[PMIX_PFEXEC_MAX_TOPIC_LEN + 1], *str = NULL;
if (child->opts.connect_stdin && 0 <= child->opts.p_stdin[0]) {
close(child->opts.p_stdin[0]);
}
if (0 <= child->opts.p_stdout[1]) {
close(child->opts.p_stdout[1]);
}
if (0 <= child->opts.p_stderr[1])
close(child->opts.p_stderr[1]);
if (0 <= child->keepalive[1]) {
close(child->keepalive[1]);
}
while (1) {
rc = pmix_fd_read(read_fd, sizeof(msg), &msg);
if (PMIX_ERR_TIMEOUT == rc) {
break;
}
if (PMIX_SUCCESS != rc) {
PMIX_ERROR_LOG(rc);
close(read_fd);
return rc;
}
if (msg.file_str_len > 0) {
rc = pmix_fd_read(read_fd, msg.file_str_len, file);
if (PMIX_SUCCESS != rc) {
pmix_show_help("help-pfexec-linux.txt", "syscall fail", true, pmix_globals.hostname,
app->cmd, "pmix_fd_read", __FILE__, __LINE__);
return rc;
}
file[msg.file_str_len] = '\0';
}
if (msg.topic_str_len > 0) {
rc = pmix_fd_read(read_fd, msg.topic_str_len, topic);
if (PMIX_SUCCESS != rc) {
pmix_show_help("help-pfexec-linux.txt", "syscall fail", true, pmix_globals.hostname,
app->cmd, "pmix_fd_read", __FILE__, __LINE__);
return rc;
}
topic[msg.topic_str_len] = '\0';
}
if (msg.msg_str_len > 0) {
str = calloc(1, msg.msg_str_len + 1);
if (NULL == str) {
pmix_show_help("help-pfexec-linux.txt", "syscall fail", true, pmix_globals.hostname,
app->cmd, "calloc", __FILE__, __LINE__);
return PMIX_ERR_NOMEM;
}
rc = pmix_fd_read(read_fd, msg.msg_str_len, str);
if (PMIX_SUCCESS != rc) {
pmix_show_help("help-pfexec-linux.txt", "syscall fail", true, pmix_globals.hostname,
app->cmd, "pmix_fd_read", __FILE__, __LINE__);
free(str);
return rc;
}
str[msg.msg_str_len] = '\0'; }
if (msg.msg_str_len > 0) {
fprintf(stderr, "%s\n", str);
free(str);
str = NULL;
}
if (msg.fatal) {
close(read_fd);
if (NULL != str) {
free(str);
}
return PMIX_ERR_SYS_OTHER;
}
if (NULL != str) {
free(str);
str = NULL;
}
}
close(read_fd);
return PMIX_SUCCESS;
}
static int fork_proc(pmix_app_t *app, pmix_pfexec_child_t *child, char **env)
{
int p[2];
if (pipe(p) < 0) {
PMIX_ERROR_LOG(PMIX_ERR_SYS_OTHER);
return PMIX_ERR_SYS_OTHER;
}
child->pid = fork();
if (child->pid < 0) {
PMIX_ERROR_LOG(PMIX_ERR_SYS_OTHER);
return PMIX_ERR_SYS_OTHER;
}
if (child->pid == 0) {
if (0 <= p[0]) {
close(p[0]);
}
if (0 <= child->keepalive[0]) {
close(child->keepalive[0]);
child->keepalive[0] = -1;
}
do_child(app, env, child, p[1]);
}
close(p[1]);
return do_parent(app, child, p[0]);
}
static void wait_signal_callback(int fd, short event, void *arg)
{
(void) fd;
(void) event;
pmix_event_t *signal = (pmix_event_t *) arg;
int status;
pid_t pid;
pmix_pfexec_child_t *child;
PMIX_ACQUIRE_OBJECT(signal);
if (SIGCHLD != PMIX_EVENT_SIGNAL(signal)) {
return;
}
if (0 == pmix_list_get_size(&pmix_pfexec_globals.children)) {
return;
}
while (1) {
pid = waitpid(-1, &status, WNOHANG);
if (-1 == pid && EINTR == errno) {
continue;
}
if (pid <= 0) {
return;
}
PMIX_LIST_FOREACH (child, &pmix_pfexec_globals.children, pmix_pfexec_child_t) {
if (pid == child->pid) {
if (WIFEXITED(status)) {
child->exitcode = WEXITSTATUS(status);
} else {
if (WIFSIGNALED(status)) {
child->exitcode = WTERMSIG(status) + 128;
}
}
child->completed = true;
if ((NULL == child->stdoutev || !child->stdoutev->active)
&& (NULL == child->stderrev || !child->stderrev->active)) {
PMIX_PFEXEC_CHK_COMPLETE(child);
}
break;
}
}
}
}
static pmix_status_t spawn_job(pmix_pfexec_fork_caddy_t *fcd)
{
sigset_t unblock;
pmix_output_verbose(5, pmix_pfexec_base_framework.framework_output,
"%s pfexec:linux spawning child job", PMIX_NAME_PRINT(&pmix_globals.myid));
if (NULL == pmix_pfexec_globals.handler) {
if (0 != sigemptyset(&unblock)) {
return PMIX_ERROR;
}
if (0 != sigaddset(&unblock, SIGCHLD)) {
return PMIX_ERROR;
}
if (0 != sigprocmask(SIG_UNBLOCK, &unblock, NULL)) {
return PMIX_ERR_NOT_SUPPORTED;
}
pmix_pfexec_globals.handler = (pmix_event_t *) malloc(sizeof(pmix_event_t));
pmix_event_set(pmix_globals.evauxbase, pmix_pfexec_globals.handler, SIGCHLD,
PMIX_EV_SIGNAL | PMIX_EV_PERSIST, wait_signal_callback,
pmix_pfexec_globals.handler);
pmix_pfexec_globals.active = true;
pmix_event_add(pmix_pfexec_globals.handler, NULL);
}
fcd->frkfn = fork_proc;
PMIX_PFEXEC_SPAWN(fcd);
return PMIX_SUCCESS;
}