NetworkInador/lib/launch_process.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;
}