diff --git a/src/lcpex/liblcpex.cpp b/src/lcpex/liblcpex.cpp index b0e033e..db79c19 100644 --- a/src/lcpex/liblcpex.cpp +++ b/src/lcpex/liblcpex.cpp @@ -1,5 +1,69 @@ #include "liblcpex.h" +#include  +#include  +#include  +#include  +#include "../logger/Logger.h" +#include "FileHandle.h" +#define BUFFER_SIZE 1024 + + +// Initialize logger +static Logger logger(E_INFO, "lcpex"); +std::string user = logger.get_user_name(); +std::string group = logger.get_group_name(); + +void signal_handler(int sig) { + std::string command = "signal_handler"; // Command context + + // Create an instance of Logger (you can change the log level and mask accordingly) + // Log the signal handling + if (sig == SIGCHLD) { + // Log that SIGCHLD was received, but leave the exit status to the parent + logger.log_to_json_file("E_INFO", "SIGCHLD received. A child process ended.", user, group, command); + } else if (sig == SIGINT) { + logger.log_to_json_file("E_FATAL", "SIGINT received. Gracefully terminating...", user, group, command); + } else if (sig == SIGTERM) { + logger.log_to_json_file("E_FATAL", "SIGTERM received. Terminating...", user, group, command); + } else if (sig == SIGSEGV) { + logger.log_to_json_file("E_FATAL", "SIGSEGV received. Possible segmentation fault.", user, group, command); + } else { + logger.log_to_json_file("E_FATAL", "Unhandled signal received", user, group, command); + } +} + +// Setup signal registrations +void setup_signal_handlers() { + struct sigaction sa; + sa.sa_handler = signal_handler; // <-- handler function + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + std::string command = "setup_signal_handlers"; // Command context + + // SIGCHLD + if (sigaction(SIGCHLD, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGCHLD handler: " + std::string(strerror(errno)); + logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file + } + + // SIGINT + if (sigaction(SIGINT, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGINT handler: " + std::string(strerror(errno)); + logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file + }  + // SIGTERM + if (sigaction(SIGTERM, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGTERM handler: " + std::string(strerror(errno)); + logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file + } + + // SIGSEGV + if (sigaction(SIGSEGV, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGSEGV handler: " + std::string(strerror(errno)); + logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file + } +}  std::string prefix_generator( std::string command, @@ -37,8 +101,12 @@ std::string prefix_generator( // it's not a shell command, so we can just execute it directly prefix = command; } - std::cout << "LAUNCHER: " << prefix << std::endl; + + // Log the message to JSON file + logger.log_to_json_file("E_INFO", "LAUNCHER: " + prefix, user, group, command); + //logger.log(E_INFO, "LAUNCHER: " + prefix); return prefix; + }   @@ -69,6 +137,7 @@ int lcpex( environment_file_path ); command = prefix; + setup_signal_handlers();  // if we are forcing a pty, then we will use the vpty library if( force_pty ) @@ -91,10 +160,14 @@ int lcpex( */ void set_cloexec_flag(int fd) { + std::string command = "set_cloexec_flag"; if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { - perror("fcntl"); - exit(1); + std::string error_message = "fcntl(F_SETFD) failed for fd " + std::to_string(fd); + + logger.log_to_json_file("E_FATAL", error_message, user, group, command); + + throw std::runtime_error("fcntl(F_SETFD) failed for fd " + std::to_string(fd)); } }  @@ -125,6 +198,13 @@ void set_cloexec_flag(int fd) void run_child_process(bool context_override, const char* context_user, const char* context_group, char* processed_command[], int fd_child_stdout_pipe[], int fd_child_stderr_pipe[]) { while ((dup2(fd_child_stdout_pipe[WRITE_END], STDOUT_FILENO) == -1) && (errno == EINTR)) {} while ((dup2(fd_child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {} + // Close unused pipe ends to avoid leaks + + close(fd_child_stdout_pipe[READ_END]); + close(fd_child_stdout_pipe[WRITE_END]); + close(fd_child_stderr_pipe[READ_END]); + close(fd_child_stderr_pipe[WRITE_END]); + std::string command = "run_child_process"; // Command context  if ( context_override ) { int context_result = set_identity_context(context_user, context_group); @@ -132,34 +212,38 @@ void run_child_process(bool context_override, const char* context_user, const ch case IDENTITY_CONTEXT_ERRORS::ERROR_NONE: break; case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER: - std::cerr << "REX: Aborting: context user not found: " << context_user << std::endl; - exit(1); + logger.log(E_FATAL, "Aborting: context user not found: " + std::string(context_user)); + logger.log_to_json_file("E_FATAL", "Aborting: context user not found: " + std::string(context_user), user, group, command); + _exit(1); break; case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP: - std::cerr << "REX: Aborting: context group not found: " << context_group << std::endl; - exit(1); + logger.log(E_FATAL, "Aborting: context group not found: " + std::string(context_group)); + logger.log_to_json_file("E_FATAL", "Aborting: context group not found: " + std::string(context_group), user, group, command); + _exit(1); break; case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED: - std::cerr << "REX: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl; - exit(1); + logger.log(E_FATAL, "Aborting: Setting GID failed: " + std::string(context_user) + "/" + std::string(context_group)); + logger.log_to_json_file("E_FATAL", "Aborting: Setting GID failed: " + std::string(context_user) + "/" + std::string(context_group), user, group, command); + _exit(1); break; case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED: - std::cerr << "REX: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl; - exit(1); + logger.log(E_FATAL, "Aborting: Setting UID failed: " + std::string(context_user) + "/" + std::string(context_group)); + logger.log_to_json_file("E_FATAL", "Aborting: Setting UID failed: " + std::string(context_user) + "/" + std::string(context_group), user, group, command); + _exit(1); break; default: - std::cerr << "REX: Aborting: Unknown error while setting identity context." << std::endl; - exit(1); + logger.log(E_FATAL, "Aborting: Unknown error while setting identity context."); + logger.log_to_json_file("E_FATAL", "Aborting: Unknown error while setting identity context.", user, group, command); + _exit(1); break; } } - int exit_code = execvp(processed_command[0], processed_command); - perror("failed on execvp in child"); - exit(exit_code); + logger.log_to_json_file("E_FATAL", "failed on execvp in child", user, group, command); + logger.log(E_FATAL, "failed on execvp in child"); + _Exit(exit_code); }  - int execute( std::string command, FILE * stdout_log_fh, @@ -169,6 +253,8 @@ int execute( std::string context_group, bool environment_supplied ){ + + try { // this does three things: // - execute a dang string as a subprocess command // - capture child stdout/stderr to respective log files @@ -190,13 +276,13 @@ int execute( int fd_child_stderr_pipe[2];  if ( pipe(fd_child_stdout_pipe ) == -1 ) { - perror( "child stdout pipe" ); - exit( 1 ); + throw std::runtime_error("Failed to create stdout pipe"); }  if ( pipe( fd_child_stderr_pipe ) == -1 ) { - perror( "child stderr pipe" ); - exit( 1 ); + close(fd_child_stdout_pipe[0]); + close(fd_child_stdout_pipe[1]); + throw std::runtime_error("Failed to create stderr pipe"); }  // using O_CLOEXEC to ensure that the child process closes the file descriptors @@ -206,15 +292,20 @@ int execute( set_cloexec_flag( fd_child_stderr_pipe[WRITE_END] );  // status result basket for the parent process to capture the child's exit status - int status = 616; + int status; pid_t pid = fork();  switch( pid ) { case -1: { // fork failed - perror("fork failure"); - exit(1); + logger.log(E_FATAL, "fork failure: " + std::string(strerror(errno))); + logger.log_to_json_file("E_FATAL", "fork failure: " + std::string(strerror(errno)), user, group, command); + close(fd_child_stdout_pipe[0]); // Pipe Leaks on Error Paths + close(fd_child_stdout_pipe[1]); // Pipe Leaks on Error Paths + close(fd_child_stderr_pipe[0]); // Pipe Leaks on Error Paths + close(fd_child_stderr_pipe[1]); // Pipe Leaks on Error Paths + throw std::runtime_error("fork() failed: " + std::string(strerror(errno))); }  case 0: @@ -234,14 +325,23 @@ int execute( { // parent process  + FdGuard stdout_read_guard(fd_child_stdout_pipe[READ_END]); + FdGuard stderr_read_guard(fd_child_stderr_pipe[READ_END]); + FdGuard stdout_write_guard(fd_child_stdout_pipe[WRITE_END]); + FdGuard stderr_write_guard(fd_child_stderr_pipe[WRITE_END]); + + // We don't need WRITE_ENDs in parent, close them now + stdout_write_guard.reset(); // Closes and sets fd to -1 + stderr_write_guard.reset(); + // The parent process has no need to access the entrance to the pipe, so fd_child_*_pipe[1|0] should be closed // within that process too: - close(fd_child_stdout_pipe[WRITE_END]); - close(fd_child_stderr_pipe[WRITE_END]);  - // attempt to write to stdout,stderr from child as well as to write each to file - char buf[BUFFER_SIZE];  + // attempt to write to stdout,stderr from child as well as to write each to file + char buf[BUFFER_SIZE] = {0}; + std::strncpy(buf, command.c_str(), BUFFER_SIZE - 1); + buf[BUFFER_SIZE - 1] = '\0'; // contains the byte count of the last read from the pipe ssize_t byte_count;  @@ -251,11 +351,11 @@ int execute( // populate the watched_fds array  // child STDOUT to parent - watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].fd = fd_child_stdout_pipe[READ_END]; + watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].fd = stdout_read_guard.get(); watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].events = POLLIN;  // child STDERR to parent - watched_fds[CHILD_PIPE_NAMES::STDERR_READ].fd = fd_child_stderr_pipe[READ_END]; + watched_fds[CHILD_PIPE_NAMES::STDERR_READ].fd = stderr_read_guard.get(); watched_fds[CHILD_PIPE_NAMES::STDERR_READ].events = POLLIN;  // number of files poll() reports as ready @@ -275,8 +375,9 @@ int execute(  if (num_files_readable == -1) { // error occurred in poll() - perror("poll"); - exit(1); + logger.log(E_FATAL, "poll() failed: " + std::string(strerror(errno))); + logger.log_to_json_file("E_FATAL", "poll() failed", user, group, command); + throw std::runtime_error("poll() failed"); } if (num_files_readable == 0) { // poll reports no files readable @@ -291,7 +392,10 @@ int execute( if (byte_count == -1) { if (errno == EAGAIN) { continue; } else { // error reading from pipe - perror("read"); + logger.log(E_FATAL, "Error while reading: " + std::string(strerror(errno))); + logger.log_to_json_file("E_FATAL", "Error while reading from pipe", user, group, command); + stdout_read_guard.reset(-1); + stderr_read_guard.reset(-1); exit(EXIT_FAILURE); }  @@ -312,13 +416,17 @@ int execute( write_all(STDERR_FILENO, buf, byte_count); } else { // this should never happen - perror("Logic error!"); + logger.log(E_FATAL, "Logic error: unexpected pipe index."); exit(EXIT_FAILURE); } } } if (watched_fds[this_fd].revents & POLLERR) { - close(watched_fds[this_fd].fd); + if (this_fd == CHILD_PIPE_NAMES::STDOUT_READ) { + stdout_read_guard.reset(-1); + } else if (this_fd == CHILD_PIPE_NAMES::STDERR_READ) { + stderr_read_guard.reset(-1); + } break_out = true; } if (watched_fds[this_fd].revents & POLLHUP) { @@ -333,21 +441,46 @@ int execute( waitpid(pid, &status, 0);  // Drain the pipes before exiting - while ((byte_count = read(fd_child_stdout_pipe[READ_END], buf, BUFFER_SIZE)) > 0) { + while ((byte_count = read(stdout_read_guard.get(), buf, BUFFER_SIZE)) > 0) { write_all(stdout_log_fh->_fileno, buf, byte_count); write_all(STDOUT_FILENO, buf, byte_count); } - while ((byte_count = read(fd_child_stderr_pipe[READ_END], buf, BUFFER_SIZE)) > 0) { + while ((byte_count = read(stderr_read_guard.get(), buf, BUFFER_SIZE)) > 0) { write_all(stderr_log_fh->_fileno, buf, byte_count); write_all(STDERR_FILENO, buf, byte_count); }  - if WIFEXITED(status) { - return WEXITSTATUS(status); - } else { - return -617; + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); // Non-blocking wait for child process + if (pid > 0) { // If a child process has terminated + // Check if the child process exited normally + if (WIFEXITED(status)) { + int exit_code = WEXITSTATUS(status); + logger.log(E_INFO, "Child exited with status " + std::to_string(exit_code)); + logger.log_to_json_file("E_INFO", "Child exited with status " + std::to_string(exit_code), user, group, command); + } + // Check if the child process was terminated by a signal + else if (WIFSIGNALED(status)) { + int signal_number = WTERMSIG(status); + logger.log(E_FATAL, "Process terminated by signal: " + std::to_string(signal_number)); + logger.log_to_json_file("E_FATAL", "Process terminated by signal: " + std::to_string(signal_number), user, group, command); + // Reset signal handler to default before exiting + signal(signal_number, SIG_DFL); + // Return 128 + signal number as the exit code for signal termination + return 128 + signal_number; // POSIX exit code format + } + // Handle unexpected exit conditions + else { + logger.log(E_WARN, "Unknown child exit condition."); + logger.log_to_json_file("E_WARN", "Unknown child exit condition", user, group, command); + } } } } + return 0; + } catch (const std::exception& ex) { + std::cerr << "[LCPEX ERROR] " << ex.what() << std::endl; + return -999; +} }