/* * 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 #include #include #include #include #include #include #include #include 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; }