| #include <EventsSourceSpawner.h> |
| #include <memory> |
| #include <cstring> |
| |
| extern "C" |
| { |
| #include <unistd.h> |
| #include <signal.h> |
| #include <spawn.h> |
| #include <fcntl.h> |
| #include <sys/wait.h> |
| #include <errno.h> |
| } |
| |
| const char* EventsSourceSpawner::EVENTS_PROGRAM = "drbdsetup"; |
| const char* EventsSourceSpawner::EVENTS_PROGRAM_ARGS[] = |
| { |
| "drbdsetup", |
| "events2", |
| "all", |
| nullptr |
| }; |
| |
| const int EventsSourceSpawner::PIPE_READ_SIDE = 0; |
| const int EventsSourceSpawner::PIPE_WRITE_SIDE = 1; |
| |
| EventsSourceSpawner::EventsSourceSpawner(MessageLog& logRef): |
| log(logRef) |
| { |
| } |
| |
| EventsSourceSpawner::~EventsSourceSpawner() |
| { |
| try |
| { |
| cleanup_child_processes_impl(); |
| } |
| catch (EventsSourceException&) |
| { |
| // Triggered by cleanup_child_processes_impl() if the events source |
| // process tracked by this instance exited |
| } |
| |
| terminate_child_process(); |
| } |
| |
| pid_t EventsSourceSpawner::get_process_id() |
| { |
| return spawned_pid; |
| } |
| |
| int EventsSourceSpawner::get_events_source_fd() |
| { |
| return pipe_fd[PIPE_READ_SIDE]; |
| } |
| |
| // @throws std::bad_alloc, EventSourceException |
| int EventsSourceSpawner::spawn_source() |
| { |
| const std::unique_ptr<posix_spawn_file_actions_t> pipe_init_actions_alloc(new posix_spawn_file_actions_t); |
| const std::unique_ptr<posix_spawnattr_t> spawn_attr_alloc(new posix_spawnattr_t); |
| |
| posix_spawn_file_actions_t* pipe_init_actions {nullptr}; |
| posix_spawnattr_t* spawn_attr {nullptr}; |
| char** spawn_args {nullptr}; |
| |
| // Indicates that posix_spawn_file_actions_init() was executed on pipe_init_actions |
| // and requires cleanup by executing posix_spawn_file_actions_destroy() |
| bool cleanup_pipe_init_actions {false}; |
| // Indicates that posix_spawnattr_init() was executed on spawn_attr |
| // and requires cleanup by executing posix_spawnattr_destroy() |
| bool cleanup_spawn_attr {false}; |
| |
| try |
| { |
| // Initialize the pipe |
| checked_int_rc(pipe2(pipe_fd, 0)); |
| // Make the pipe's read end nonblocking |
| fcntl(pipe_fd[PIPE_READ_SIDE], F_SETFL, O_NONBLOCK); |
| // Make stdin nonblocking |
| fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); |
| |
| // Initialize the datastructures for posix_spawn()) |
| { |
| // Use direct pointers |
| pipe_init_actions = pipe_init_actions_alloc.get(); |
| spawn_attr = spawn_attr_alloc.get(); |
| |
| // Initialize a copy of the spawn arguments |
| spawn_args = init_spawn_args(); |
| |
| checked_int_rc(posix_spawn_file_actions_init(pipe_init_actions)); |
| cleanup_pipe_init_actions = true; |
| |
| // Redirect stdout to the pipe's write side |
| checked_int_rc( |
| posix_spawn_file_actions_adddup2( |
| pipe_init_actions, |
| pipe_fd[PIPE_WRITE_SIDE], |
| STDOUT_FILENO |
| ) |
| ); |
| // Close the read end of the pipe |
| checked_int_rc( |
| posix_spawn_file_actions_addclose( |
| pipe_init_actions, |
| pipe_fd[PIPE_READ_SIDE] |
| ) |
| ); |
| |
| checked_int_rc(posix_spawnattr_init(spawn_attr)); |
| cleanup_spawn_attr = true; |
| |
| // Reset ignored signals to the default action |
| sigset_t mask; |
| checked_int_rc(sigemptyset(&mask)); |
| checked_int_rc(sigaddset(&mask, SIGTERM)); |
| checked_int_rc(sigaddset(&mask, SIGHUP)); |
| checked_int_rc(sigaddset(&mask, SIGINT)); |
| checked_int_rc(sigaddset(&mask, SIGWINCH)); |
| checked_int_rc(sigaddset(&mask, SIGCHLD)); |
| checked_int_rc(posix_spawnattr_setsigdefault(spawn_attr, &mask)); |
| checked_int_rc(posix_spawnattr_setflags(spawn_attr, POSIX_SPAWN_SETSIGDEF)); |
| |
| // Attempt to spawn the events source program |
| int spawn_rc = posix_spawnp(&spawned_pid, EVENTS_PROGRAM, pipe_init_actions, spawn_attr, |
| spawn_args, environ); |
| if (spawn_rc != 0) |
| { |
| spawned_pid = -1; |
| log.add_entry( |
| MessageLog::log_level::ALERT, |
| "Spawning the events source process failed" |
| ); |
| throw EventsSourceException(); |
| } |
| |
| destroy_spawn_args(spawn_args); |
| |
| // Close the local write side of the pipe |
| { |
| int close_rc = 0; |
| do |
| { |
| errno = 0; |
| close_rc = close(pipe_fd[PIPE_WRITE_SIDE]); |
| } |
| while (close_rc != 0 && errno == EINTR); |
| } |
| |
| // Cleanup pipe_init_actions |
| static_cast<void> (posix_spawn_file_actions_destroy(pipe_init_actions)); |
| cleanup_pipe_init_actions = false; |
| // Cleanup spawn_attr |
| static_cast<void> (posix_spawnattr_destroy(spawn_attr)); |
| cleanup_spawn_attr = false; |
| } |
| } |
| catch (EventsSourceException&) |
| { |
| // Cleanup calls below may change errno, so the relevant |
| // information must be cached |
| bool spawn_out_of_memory = (errno == ENOMEM); |
| |
| if (cleanup_pipe_init_actions) |
| { |
| static_cast<void> (posix_spawn_file_actions_destroy(pipe_init_actions)); |
| } |
| |
| if (cleanup_spawn_attr) |
| { |
| static_cast<void> (posix_spawnattr_destroy(spawn_attr)); |
| } |
| |
| destroy_spawn_args(spawn_args); |
| |
| close_pipe(); |
| |
| if (spawn_out_of_memory) |
| { |
| throw std::bad_alloc(); |
| } |
| throw; |
| } |
| catch (std::bad_alloc&) |
| { |
| if (cleanup_pipe_init_actions) |
| { |
| static_cast<void> (posix_spawn_file_actions_destroy(pipe_init_actions)); |
| } |
| |
| if (cleanup_spawn_attr) |
| { |
| static_cast<void> (posix_spawnattr_destroy(spawn_attr)); |
| } |
| |
| destroy_spawn_args(spawn_args); |
| |
| close_pipe(); |
| |
| throw; |
| } |
| |
| return pipe_fd[PIPE_READ_SIDE]; |
| } |
| |
| // @throws EventsSourceException |
| void EventsSourceSpawner::cleanup_child_processes() |
| { |
| cleanup_child_processes_impl(); |
| } |
| |
| // @throws EventsSourceException |
| void EventsSourceSpawner::cleanup_child_processes_impl() |
| { |
| // Cleanup any child processes that have exited (zombie processes)) |
| pid_t child_pid {0}; |
| do |
| { |
| int exit_status; |
| child_pid = waitpid(-1, &exit_status, WNOHANG); |
| if (spawned_pid != -1 && child_pid == spawned_pid && |
| (WIFEXITED(exit_status) || WIFSIGNALED(exit_status))) |
| { |
| // Events source process exited, throw an EventsSourceException |
| // to trigger reinitialization of the application and |
| // thereby a respawn of the events source process |
| spawned_pid = -1; |
| throw EventsSourceException(); |
| } |
| } |
| while (child_pid > 0); |
| } |
| |
| void EventsSourceSpawner::terminate_child_process() noexcept |
| { |
| // Unless the child process exited already or was not ever spawned, |
| // tell the child process to terminate |
| if (spawned_pid != -1) |
| { |
| static_cast<void> (kill(spawned_pid, SIGTERM)); |
| } |
| } |
| |
| void EventsSourceSpawner::close_pipe() |
| { |
| // Close the pipe file descriptors |
| if (pipe_fd[PIPE_READ_SIDE] != -1) |
| { |
| int rc = 0; |
| do |
| { |
| errno = 0; |
| rc = close(pipe_fd[PIPE_READ_SIDE]); |
| } |
| while (rc != 0 && errno == EINTR); |
| pipe_fd[PIPE_READ_SIDE] = -1; |
| } |
| if (pipe_fd[PIPE_WRITE_SIDE] != -1) |
| { |
| int rc = 0; |
| do |
| { |
| errno = 0; |
| rc = close(pipe_fd[PIPE_WRITE_SIDE]); |
| } |
| while (rc != 0 && errno == EINTR); |
| pipe_fd[PIPE_WRITE_SIDE] = -1; |
| } |
| } |
| |
| // Throws EventsSourceException if rc is not equal to 0 |
| // @throws EventsSourceException |
| void EventsSourceSpawner::checked_int_rc(int rc) const |
| { |
| if (rc != 0) |
| { |
| throw EventsSourceException(); |
| } |
| } |
| |
| // @throws std::bad_alloc |
| char** EventsSourceSpawner::init_spawn_args() const |
| { |
| char** spawn_args = nullptr; |
| |
| try |
| { |
| // Number of arguments, excluding the trailing null pointer |
| size_t args_count = 0; |
| while (EVENTS_PROGRAM_ARGS[args_count] != nullptr) |
| { |
| ++args_count; |
| } |
| |
| // Allocate slots for the arguments and an additional one for |
| // the trailing null pointer |
| spawn_args = new char*[args_count + 1]; |
| for (size_t index = 0; index < args_count; ++index) |
| { |
| size_t arg_length = std::strlen(EVENTS_PROGRAM_ARGS[index]) + 1; |
| // If the allocation fails, make sure there is a null pointer in this slot |
| // Required for clean deallocation |
| spawn_args[index] = nullptr; |
| spawn_args[index] = new char[arg_length]; |
| std::strcpy(spawn_args[index], EVENTS_PROGRAM_ARGS[index]); |
| } |
| spawn_args[args_count] = nullptr; |
| } |
| catch (std::bad_alloc&) |
| { |
| destroy_spawn_args(spawn_args); |
| throw; |
| } |
| |
| return spawn_args; |
| } |
| |
| void EventsSourceSpawner::destroy_spawn_args(char** spawn_args) const noexcept |
| { |
| if (spawn_args != nullptr) |
| { |
| for (size_t index = 0; spawn_args[index] != nullptr; ++index) |
| { |
| delete[] spawn_args[index]; |
| } |
| delete[] spawn_args; |
| } |
| } |