332 lines
7.9 KiB
C
332 lines
7.9 KiB
C
/*
|
|
* lauch_process.c
|
|
* This file is part of Network-Inador
|
|
*
|
|
* Copyright (C) 2024 - Félix Arreola Rodríguez
|
|
*
|
|
* Network-Inador 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.
|
|
*
|
|
* Network-Inador 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 Network-Inador; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA
|
|
*/
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
static ssize_t write_all (int fd, const void * vbuf, size_t to_write) {
|
|
char *buf = (char *) vbuf;
|
|
|
|
while (to_write > 0) {
|
|
ssize_t count = write (fd, buf, to_write);
|
|
if (count < 0) {
|
|
if (errno != EINTR)
|
|
return 0;
|
|
} else {
|
|
to_write -= count;
|
|
buf += count;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* This function is called between fork() and exec() and hence must be
|
|
* async-signal-safe (see signal-safety(7)). */
|
|
static void write_err_and_exit (int fd, int msg) {
|
|
int en = errno;
|
|
|
|
write_all (fd, &msg, sizeof (msg));
|
|
write_all (fd, &en, sizeof (en));
|
|
|
|
_exit (1);
|
|
}
|
|
|
|
static int safe_close (int fd) {
|
|
int ret;
|
|
|
|
do {
|
|
ret = close (fd);
|
|
} while (ret < 0 && errno == EINTR);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void close_and_invalidate (int *fd) {
|
|
if (*fd < 0) {
|
|
return;
|
|
} else {
|
|
safe_close (*fd);
|
|
*fd = -1;
|
|
}
|
|
}
|
|
|
|
static int safe_dup2 (int fd1, int fd2) {
|
|
int ret;
|
|
|
|
do {
|
|
ret = dup2 (fd1, fd2);
|
|
} while (ret < 0 && (errno == EINTR || errno == EBUSY));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int read_ints (int fd, int *buf, int n_ints_in_buf, int *n_ints_read, int *error) {
|
|
size_t bytes = 0;
|
|
|
|
while (1) {
|
|
ssize_t chunk;
|
|
|
|
if (bytes >= sizeof (int) * 2)
|
|
break; /* give up, who knows what happened, should not be possible. */
|
|
|
|
again:
|
|
chunk = read (fd,((char *) buf) + bytes, sizeof (int) * n_ints_in_buf - bytes);
|
|
if (chunk < 0 && errno == EINTR)
|
|
goto again;
|
|
|
|
if (chunk < 0) {
|
|
if (error != NULL) {
|
|
*error = errno;
|
|
}
|
|
|
|
return 0;
|
|
} else if (chunk == 0) {
|
|
break; /* EOF */
|
|
} else {/* chunk > 0 */
|
|
bytes += chunk;
|
|
}
|
|
}
|
|
|
|
*n_ints_read = (int) (bytes / sizeof (int));
|
|
|
|
return 1;
|
|
}
|
|
|
|
enum {
|
|
CHILD_CHDIR_FAILED,
|
|
CHILD_EXEC_FAILED,
|
|
CHILD_DUP2_FAILED,
|
|
CHILD_FORK_FAILED
|
|
};
|
|
|
|
static void do_exec (int child_err_report_fd, int stdin_fd, int stdout_fd, int stderr_fd, const char *working_directory, char **argv, char **envp) {
|
|
if (working_directory && chdir (working_directory) < 0) {
|
|
write_err_and_exit (child_err_report_fd, CHILD_CHDIR_FAILED);
|
|
}
|
|
|
|
if (stdin_fd >= 0) {
|
|
if (safe_dup2 (stdin_fd, 0) < 0) {
|
|
write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
|
|
}
|
|
|
|
close (stdin_fd);
|
|
} else {
|
|
int read_null = open ("/dev/null", O_RDONLY);
|
|
if (read_null < 0) {
|
|
write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
|
|
}
|
|
safe_dup2 (read_null, 0);
|
|
close_and_invalidate (&read_null);
|
|
}
|
|
|
|
if (stdout_fd >= 0) {
|
|
if (safe_dup2 (stdout_fd, 1) < 0) {
|
|
write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
|
|
}
|
|
|
|
close (stdout_fd);
|
|
} else {
|
|
int read_null = open ("/dev/null", O_WRONLY);
|
|
if (read_null < 0) {
|
|
write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
|
|
}
|
|
safe_dup2 (read_null, 1);
|
|
close_and_invalidate (&read_null);
|
|
}
|
|
|
|
/* Ahora para stderr */
|
|
if (stderr_fd >= 0) {
|
|
if (safe_dup2 (stderr_fd, 2) < 0) {
|
|
write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
|
|
}
|
|
|
|
close (stderr_fd);
|
|
} else {
|
|
int read_null = open ("/dev/null", O_WRONLY);
|
|
if (read_null < 0) {
|
|
write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED);
|
|
}
|
|
safe_dup2 (read_null, 2);
|
|
close_and_invalidate (&read_null);
|
|
}
|
|
|
|
/* TODO: Si queremos cerrar todos los descriptores, este es el momento */
|
|
fcntl (child_err_report_fd, F_SETFD, FD_CLOEXEC);
|
|
|
|
/* Hacer el exec */
|
|
execvpe (argv[0], argv, envp);
|
|
|
|
write_err_and_exit (child_err_report_fd, CHILD_EXEC_FAILED);
|
|
}
|
|
|
|
static int fork_exec_with_fds (const char *working_directory, char **argv, char **envp, pid_t *child_pid, int *child_close_fds, int stdin_fd, int stdout_fd, int stderr_fd, int *error) {
|
|
pid_t pid;
|
|
int child_err_report_pipe[2] = { -1, -1 };
|
|
|
|
if (pipe (child_err_report_pipe) != 0) {
|
|
if (error != NULL) *error = errno;
|
|
return 0;
|
|
}
|
|
pid = fork ();
|
|
|
|
if (pid < 0) {
|
|
if (error != NULL) *error = errno;
|
|
|
|
goto cleanup_and_fail;
|
|
} else if (pid == 0) {
|
|
/* Proceso intermedio */
|
|
|
|
/* Reset some signal handlers that we may use */
|
|
signal (SIGCHLD, SIG_DFL);
|
|
signal (SIGINT, SIG_DFL);
|
|
signal (SIGTERM, SIG_DFL);
|
|
signal (SIGHUP, SIG_DFL);
|
|
|
|
/* Be sure we crash if the parent exits
|
|
* and we write to the err_report_pipe
|
|
*/
|
|
signal (SIGPIPE, SIG_DFL);
|
|
|
|
/* Close the parent's end of the pipes;
|
|
* not needed in the close_descriptors case,
|
|
* though
|
|
*/
|
|
close_and_invalidate (&child_err_report_pipe[0]);
|
|
if (child_close_fds != NULL) {
|
|
int i = -1;
|
|
while (child_close_fds[++i] != -1)
|
|
close_and_invalidate (&child_close_fds[i]);
|
|
}
|
|
|
|
do_exec (child_err_report_pipe[1], stdin_fd, stdout_fd, stderr_fd, working_directory, argv, envp);
|
|
} else {
|
|
/* Proceso padre */
|
|
int buf[2];
|
|
int n_ints = 0;
|
|
|
|
close_and_invalidate (&child_err_report_pipe[1]);
|
|
|
|
if (!read_ints (child_err_report_pipe[0], buf, 2, &n_ints, error)) {
|
|
goto cleanup_and_fail;
|
|
}
|
|
|
|
if (n_ints >= 2) {
|
|
/* El argumento buf[0] tiene el error, el buf[1] tiene el errno del proceso hijo */
|
|
if (error != NULL) *error = buf[1];
|
|
goto cleanup_and_fail;
|
|
}
|
|
|
|
close_and_invalidate (&child_err_report_pipe[0]);
|
|
|
|
if (child_pid) {
|
|
*child_pid = pid;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
cleanup_and_fail:
|
|
if (pid > 0) {
|
|
wait_failed:
|
|
if (waitpid (pid, NULL, 0) < 0) {
|
|
if (errno == EINTR)
|
|
goto wait_failed;
|
|
else if (errno == ECHILD)
|
|
; /* do nothing, child already reaped */
|
|
/*else
|
|
g_warning ("waitpid() should not fail in 'fork_exec_with_pipes'");*/
|
|
}
|
|
}
|
|
|
|
close_and_invalidate (&child_err_report_pipe[0]);
|
|
close_and_invalidate (&child_err_report_pipe[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int launch_process (const char *working_directory, char **argv, char **envp, pid_t *child_pid, int *standard_input, int *standard_output, int *standard_error, int *error) {
|
|
int stdin_pipe[2] = { -1, -1 };
|
|
int stdout_pipe[2] = { -1, -1 };
|
|
int stderr_pipe[2] = { -1, -1 };
|
|
int child_close_fds[4];
|
|
int c;
|
|
int ret;
|
|
|
|
if (standard_input && pipe (stdin_pipe) != 0)
|
|
goto failed_launch_cleanup;
|
|
|
|
if (standard_output && pipe (stdout_pipe) != 0)
|
|
goto failed_launch_cleanup;
|
|
|
|
if (standard_error && pipe (stderr_pipe) != 0)
|
|
goto failed_launch_cleanup;
|
|
|
|
c = 0;
|
|
if (stdin_pipe[1] != -1) child_close_fds[c++] = stdin_pipe[1];
|
|
if (stdout_pipe[0] != -1) child_close_fds[c++] = stdout_pipe[0];
|
|
if (stderr_pipe[0] != -1) child_close_fds[c++] = stderr_pipe[0];
|
|
child_close_fds[c++] = -1;
|
|
|
|
ret = fork_exec_with_fds (working_directory, argv, envp, child_pid, child_close_fds, stdin_pipe[0], stdout_pipe[1], stderr_pipe[1], error);
|
|
|
|
if (!ret) goto failed_launch_cleanup;
|
|
|
|
close_and_invalidate (&stdin_pipe[0]);
|
|
close_and_invalidate (&stdout_pipe[1]);
|
|
close_and_invalidate (&stderr_pipe[1]);
|
|
|
|
if (standard_input) {
|
|
*standard_input = stdin_pipe[1];
|
|
}
|
|
|
|
if (standard_output) {
|
|
*standard_output = stdout_pipe[0];
|
|
}
|
|
|
|
if (standard_error) {
|
|
*standard_error = stderr_pipe[0];
|
|
}
|
|
|
|
return 1;
|
|
failed_launch_cleanup:
|
|
close_and_invalidate (&stdin_pipe[0]);
|
|
close_and_invalidate (&stdin_pipe[1]);
|
|
close_and_invalidate (&stdout_pipe[0]);
|
|
close_and_invalidate (&stdout_pipe[1]);
|
|
close_and_invalidate (&stderr_pipe[0]);
|
|
close_and_invalidate (&stderr_pipe[1]);
|
|
|
|
return 0;
|
|
}
|
|
|