diff --git a/CMakeLists.txt b/CMakeLists.txt index b5531e1..ad3cea8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,4 +3,5 @@ project(rex) set(CMAKE_CXX_STANDARD 14) -add_executable(rex Rex.cpp src/json_support/jsoncpp/json.h src/json_support/jsoncpp/json-forwards.h src/json_support/jsoncpp/jsoncpp.cpp src/logger/Logger.cpp src/logger/Logger.h src/json_support/JSON.cpp src/json_support/JSON.h src/misc/helpers.cpp src/misc/helpers.h src/config/Config.cpp src/config/Config.h src/suite/Suite.cpp src/suite/Suite.h src/suite/Unit.cpp src/suite/Unit.h src/shells/shells.cpp src/shells/shells.h src/plan/Plan.cpp src/plan/Plan.h src/plan/Task.cpp src/plan/Task.h src/lcpex/helpers.h src/lcpex/helpers.cpp src/lcpex/liblcpex.h src/lcpex/liblcpex.cpp src/lcpex/vpty/libclpex_tty.h src/lcpex/vpty/libclpex_tty.cpp src/lcpex/Contexts.h src/lcpex/Contexts.cpp src/lcpex/helpers.h src/lcpex/string_expansion/string_expansion.h src/lcpex/string_expansion/string_expansion.cpp src/lcpex/vpty/pty_fork_mod/pty_fork.h src/lcpex/vpty/pty_fork_mod/pty_fork.cpp src/lcpex/vpty/pty_fork_mod/pty_master_open.h src/lcpex/vpty/pty_fork_mod/pty_master_open.cpp src/lcpex/vpty/pty_fork_mod/tty_functions.h src/lcpex/vpty/pty_fork_mod/tty_functions.cpp ) +add_executable(rex Rex.cpp src/json_support/jsoncpp/json.h src/json_support/jsoncpp/json-forwards.h src/json_support/jsoncpp/jsoncpp.cpp src/logger/Logger.cpp src/logger/Logger.h src/json_support/JSON.cpp src/json_support/JSON.h src/misc/helpers.cpp src/misc/helpers.h src/config/Config.cpp src/config/Config.h src/suite/Suite.cpp src/suite/Suite.h src/suite/Unit.cpp src/suite/Unit.h src/shells/shells.cpp src/shells/shells.h src/plan/Plan.cpp src/plan/Plan.h src/plan/Task.cpp src/plan/Task.h src/lcpex/helpers.h src/lcpex/helpers.cpp src/lcpex/liblcpex.h src/lcpex/liblcpex.cpp src/lcpex/vpty/libclpex_tty.h src/lcpex/vpty/libclpex_tty.cpp src/lcpex/Contexts.h src/lcpex/Contexts.cpp src/lcpex/helpers.h src/lcpex/string_expansion/string_expansion.h src/lcpex/string_expansion/string_expansion.cpp src/lcpex/vpty/pty_fork_mod/pty_fork.h src/lcpex/vpty/pty_fork_mod/pty_fork.cpp src/lcpex/vpty/pty_fork_mod/pty_master_open.h src/lcpex/vpty/pty_fork_mod/pty_master_open.cpp src/lcpex/vpty/pty_fork_mod/tty_functions.h src/lcpex/vpty/pty_fork_mod/tty_functions.cpp + src/lcpex/FileHandle.h) diff --git a/src/lcpex/FileHandle.h b/src/lcpex/FileHandle.h new file mode 100644 index 0000000..96ba22c --- /dev/null +++ b/src/lcpex/FileHandle.h @@ -0,0 +1,54 @@ +#pragma once +#include // for close() + +// Simplified RAII wrapper for file descriptors (C-style compatible) +class FdGuard { + int fd_; +public: + // Constructor + explicit FdGuard(int fd = -1) : fd_(fd) {} + + // Disable copy semantics + FdGuard(const FdGuard&) = delete; + FdGuard& operator=(const FdGuard&) = delete; + + // Move constructor + FdGuard(FdGuard&& other) /* no noexcept */ { + fd_ = other.fd_; + other.fd_ = -1; + } + + // Move assignment + FdGuard& operator=(FdGuard&& other) /* no noexcept */ { + if (this != &other) { + reset(); // Close current if valid + fd_ = other.fd_; + other.fd_ = -1; + } + return *this; + } + + // Destructor + ~FdGuard() { + close_if_valid(); + } + + // Reset with new fd (or close if -1) + void reset(int new_fd = -1) { + close_if_valid(); + fd_ = new_fd; + } + + // Get the raw fd + int get() const { + return fd_; + } + +private: + void close_if_valid() { + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } + } +}; diff --git a/src/lcpex/liblcpex.cpp b/src/lcpex/liblcpex.cpp index b0e033e..dc88d49 100644 --- a/src/lcpex/liblcpex.cpp +++ b/src/lcpex/liblcpex.cpp @@ -1,6 +1,78 @@ #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(); + +// Global variables for signal handler context +static std::string g_task_context = ""; +static std::string g_log_directory = ""; + +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, g_task_context, g_log_directory); + } else if (sig == SIGINT) { + logger.log_to_json_file("E_FATAL", "SIGINT received. Gracefully terminating...", user, group, command, g_task_context, g_log_directory); + } else if (sig == SIGTERM) { + logger.log_to_json_file("E_FATAL", "SIGTERM received. Terminating...", user, group, command, g_task_context, g_log_directory); + } else if (sig == SIGSEGV) { + logger.log_to_json_file("E_FATAL", "SIGSEGV received. Possible segmentation fault.", user, group, command, g_task_context, g_log_directory); + } else { + logger.log_to_json_file("E_FATAL", "Unhandled signal received", user, group, command, g_task_context, g_log_directory); + } +} + +// Setup signal registrations +void setup_signal_handlers(std::string task_context = "", std::string log_directory = "") { + // Set global variables for signal handler context + g_task_context = task_context; + g_log_directory = log_directory; + + 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, task_context, log_directory); // 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, task_context, log_directory); // 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, task_context, log_directory); // 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, task_context, log_directory); // Log to JSON file + } +} + std::string prefix_generator( std::string command, bool is_shell_command, @@ -8,7 +80,9 @@ std::string prefix_generator( std::string shell_execution_arg, bool supply_environment, std::string shell_source_subcommand, - std::string environment_file_path + std::string environment_file_path, + std::string task_context, + std::string log_directory ) { std::string prefix = ""; if ( is_shell_command ) { @@ -37,8 +111,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, task_context, log_directory); + //logger.log(E_INFO, "LAUNCHER: " + prefix); return prefix; + } @@ -55,7 +133,9 @@ int lcpex( std::string shell_execution_arg, bool supply_environment, std::string shell_source_subcommand, - std::string environment_file_path + std::string environment_file_path, + std::string task_context, + std::string log_directory ) { // generate the prefix @@ -66,18 +146,21 @@ int lcpex( shell_execution_arg, supply_environment, shell_source_subcommand, - environment_file_path + environment_file_path, + task_context, + log_directory ); command = prefix; + setup_signal_handlers(task_context, log_directory); // if we are forcing a pty, then we will use the vpty library if( force_pty ) { - return exec_pty( command, stdout_log_fh, stderr_log_fh, context_override, context_user, context_group, supply_environment ); + return exec_pty( command, stdout_log_fh, stderr_log_fh, context_override, context_user, context_group, supply_environment, task_context, log_directory ); } // otherwise, we will use the execute function - return execute( command, stdout_log_fh, stderr_log_fh, context_override, context_user, context_group, supply_environment ); + return execute( command, stdout_log_fh, stderr_log_fh, context_override, context_user, context_group, supply_environment, task_context, log_directory ); } /** @@ -89,12 +172,16 @@ int lcpex( * * @param fd The file descriptor for which to set the close-on-exec flag */ -void set_cloexec_flag(int fd) +void set_cloexec_flag(int fd, std::string task_context = "", std::string log_directory = "") { + 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, task_context, log_directory); + + throw std::runtime_error("fcntl(F_SETFD) failed for fd " + std::to_string(fd)); } } @@ -122,9 +209,16 @@ void set_cloexec_flag(int fd) * Finally, the child process calls execvp() with the processed_command to run the shell command. * If the execvp() function fails, an error message is displayed. */ -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[]) { +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[], std::string task_context = "", std::string log_directory = "") { 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 +226,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, task_context, log_directory); + _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, task_context, log_directory); + _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, task_context, log_directory); + _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, task_context, log_directory); + _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, task_context, log_directory); + _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, task_context, log_directory); + logger.log(E_FATAL, "failed on execvp in child"); + _Exit(exit_code); } - int execute( std::string command, FILE * stdout_log_fh, @@ -167,8 +265,12 @@ int execute( bool context_override, std::string context_user, std::string context_group, - bool environment_supplied + bool environment_supplied, + std::string task_context, + std::string log_directory ){ + + try { // this does three things: // - execute a dang string as a subprocess command // - capture child stdout/stderr to respective log files @@ -190,31 +292,36 @@ 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 - set_cloexec_flag( fd_child_stdout_pipe[READ_END] ); - set_cloexec_flag( fd_child_stderr_pipe[READ_END] ); - set_cloexec_flag( fd_child_stdout_pipe[WRITE_END] ); - set_cloexec_flag( fd_child_stderr_pipe[WRITE_END] ); + set_cloexec_flag( fd_child_stdout_pipe[READ_END], task_context, log_directory ); + set_cloexec_flag( fd_child_stderr_pipe[READ_END], task_context, log_directory ); + set_cloexec_flag( fd_child_stdout_pipe[WRITE_END], task_context, log_directory ); + set_cloexec_flag( fd_child_stderr_pipe[WRITE_END], task_context, log_directory ); // 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, task_context, log_directory); + 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: @@ -226,7 +333,9 @@ int execute( context_group.c_str(), processed_command, fd_child_stdout_pipe, - fd_child_stderr_pipe + fd_child_stderr_pipe, + task_context, + log_directory ); } @@ -234,14 +343,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]; - + 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 +369,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 +393,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, task_context, log_directory); + throw std::runtime_error("poll() failed"); } if (num_files_readable == 0) { // poll reports no files readable @@ -291,7 +410,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, task_context, log_directory); + stdout_read_guard.reset(-1); + stderr_read_guard.reset(-1); exit(EXIT_FAILURE); } @@ -312,13 +434,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 +459,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, task_context, log_directory); + } + // 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, task_context, log_directory); + // 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, task_context, log_directory); + } } } } + return 0; + } catch (const std::exception& ex) { + std::cerr << "[LCPEX ERROR] " << ex.what() << std::endl; + return -999; +} } diff --git a/src/lcpex/liblcpex.h b/src/lcpex/liblcpex.h index f958661..7e7d147 100644 --- a/src/lcpex/liblcpex.h +++ b/src/lcpex/liblcpex.h @@ -44,7 +44,9 @@ int execute( bool context_override, std::string context_user, std::string context_group, - bool environment_supplied + bool environment_supplied, + std::string task_context = "", + std::string log_directory = "" ); @@ -87,7 +89,9 @@ int lcpex( std::string shell_execution_arg, bool supply_environment, std::string shell_source_subcommand, - std::string environment_file_path + std::string environment_file_path, + std::string task_context = "", + std::string log_directory = "" ); /** diff --git a/src/lcpex/vpty/libclpex_tty.cpp b/src/lcpex/vpty/libclpex_tty.cpp index a423535..f1b3fd0 100644 --- a/src/lcpex/vpty/libclpex_tty.cpp +++ b/src/lcpex/vpty/libclpex_tty.cpp @@ -1,4 +1,74 @@ #include "libclpex_tty.h" +#include +#include +#include +#include +#include "../../logger/Logger.h" +#include "../FileHandle.h" +#define BUFFER_SIZE 1024 + +// Initialize logger +static Logger tty_logger(E_INFO, "lcpex_tty"); +std::string tty_user = tty_logger.get_user_name(); +std::string tty_group = tty_logger.get_group_name(); + +// Global variables for signal handler context +static std::string g_tty_task_context = ""; +static std::string g_tty_log_directory = ""; + +void tty_signal_handler(int sig) { + std::string command = "tty_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 + tty_logger.log_to_json_file("E_INFO", "SIGCHLD received. A child process ended.", tty_user, tty_group, command, g_tty_task_context, g_tty_log_directory); + } else if (sig == SIGINT) { + tty_logger.log_to_json_file("E_FATAL", "SIGINT received. Gracefully terminating...", tty_user, tty_group, command, g_tty_task_context, g_tty_log_directory); + } else if (sig == SIGTERM) { + tty_logger.log_to_json_file("E_FATAL", "SIGTERM received. Terminating...", tty_user, tty_group, command, g_tty_task_context, g_tty_log_directory); + } else if (sig == SIGSEGV) { + tty_logger.log_to_json_file("E_FATAL", "SIGSEGV received. Possible segmentation fault.", tty_user, tty_group, command, g_tty_task_context, g_tty_log_directory); + } else { + tty_logger.log_to_json_file("E_FATAL", "Unhandled signal received", tty_user, tty_group, command, g_tty_task_context, g_tty_log_directory); + } +} +// Setup signal registrations +void setup_tty_signal_handlers(std::string task_context = "", std::string log_directory = "") { + // Set global variables for signal handler context + g_tty_task_context = task_context; + g_tty_log_directory = log_directory; + + struct sigaction sa; + sa.sa_handler = tty_signal_handler; // <-- handler function + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + std::string command = "setup_tty_signal_handlers"; // Command context + + // SIGCHLD + if (sigaction(SIGCHLD, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGCHLD handler: " + std::string(strerror(errno)); + tty_logger.log_to_json_file("E_FATAL", error_message, tty_user, tty_group, command, task_context, log_directory); // Log to JSON file + } + + // SIGINT + if (sigaction(SIGINT, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGINT handler: " + std::string(strerror(errno)); + tty_logger.log_to_json_file("E_FATAL", error_message, tty_user, tty_group, command, task_context, log_directory); // Log to JSON file + } + + // SIGTERM + if (sigaction(SIGTERM, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGTERM handler: " + std::string(strerror(errno)); + tty_logger.log_to_json_file("E_FATAL", error_message, tty_user, tty_group, command, task_context, log_directory); // Log to JSON file + } + + // SIGSEGV + if (sigaction(SIGSEGV, &sa, nullptr) == -1) { + std::string error_message = "Failed to set SIGSEGV handler: " + std::string(strerror(errno)); + tty_logger.log_to_json_file("E_FATAL", error_message, tty_user, tty_group, command, task_context, log_directory); // Log to JSON file + } +} /** * @brief Safely prints a message and exits the program @@ -11,13 +81,26 @@ * Then, it calls `ttyResetExit()` with `ttyOrig` as its argument to reset the terminal settings. * Finally, the function exits the program with exit code 1. */ -void safe_perror( const char * msg, struct termios * ttyOrig ) + +void set_tty_cloexec_flag(int fd, std::string task_context = "", std::string log_directory = "") { - std::cerr << msg << std::endl; - ttyResetExit( ttyOrig ); - exit(1); + std::string command = "set_tty_cloexec_flag"; + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + { + std::string error_message = "fcntl(F_SETFD) failed for fd " + std::to_string(fd); + tty_logger.log_to_json_file("E_FATAL", error_message, tty_user, tty_group, command, task_context, log_directory); + throw std::runtime_error("fcntl(F_SETFD) failed for fd " + std::to_string(fd)); + } } +void safe_perror( const char * msg, struct termios * ttyOrig, std::string task_context = "", std::string log_directory = "" ) +{ + std::string command = "safe_perror"; // Command context + tty_logger.log_to_json_file("E_FATAL", msg, tty_user, tty_group, command, task_context, log_directory); // Log the message before exiting + std::cerr << msg << std::endl; + ttyResetExit( ttyOrig ); + throw std::runtime_error(std::string("TTY Error: ") + msg); +} /** * @brief Executes the child process @@ -42,14 +125,16 @@ void safe_perror( const char * msg, struct termios * ttyOrig ) * Finally, the function executes the command specified in `processed_command` using `execvp()`. * If the execution of `execvp()` fails, the function calls `safe_perror()` to print a message and exit the program. */ -void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[], struct termios * ttyOrig, bool context_override, std::string context_user, std::string context_group ) +void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[], struct termios * ttyOrig, bool context_override, std::string context_user, std::string context_group, std::string task_context = "", std::string log_directory = "" ) { + std::string command = "run_child_process_tty"; // Command context // redirect stderr to the write end of the stderr pipe // close the file descriptor STDERR_FILENO if it was previously open, then (re)open it as a copy of while ((dup2(fd_child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {} - // close the write end of the stderr pipe + // close the write end of the stderr pipe and read end to avoid leaks close( fd_child_stderr_pipe[WRITE_END] ); + close( fd_child_stderr_pipe[READ_END] ); // if the user has specified a context override, set the context if ( context_override ) @@ -59,24 +144,29 @@ void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[], case IDENTITY_CONTEXT_ERRORS::ERROR_NONE: break; case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER: - std::cerr << "lcpex: Aborting: context user not found: " << context_user << std::endl; - exit(1); + tty_logger.log(E_FATAL, "Aborting: context user not found: " + context_user); + tty_logger.log_to_json_file("E_FATAL", "Context user not found: " + context_user, tty_user, tty_group, command, task_context, log_directory); + _exit(1); break; case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP: - std::cerr << "lcpex: Aborting: context group not found: " << context_group << std::endl; - exit(1); + tty_logger.log(E_FATAL, "Aborting: context group not found: " + context_group); + tty_logger.log_to_json_file("E_FATAL", "Context group not found: " + context_group, tty_user, tty_group, command, task_context, log_directory); + _exit(1); break; case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED: - std::cerr << "lcpex: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl; - exit(1); + tty_logger.log(E_FATAL, "Aborting: Setting GID failed: " + context_user + "/" + context_group); + tty_logger.log_to_json_file("E_FATAL", "Setting GID failed for: " + context_user + "/" + context_group, tty_user, tty_group, command, task_context, log_directory); + _exit(1); break; case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED: - std::cerr << "lcpex: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl; - exit(1); + tty_logger.log(E_FATAL, "Aborting: Setting UID failed: " + context_user + "/" + context_group); + tty_logger.log_to_json_file("E_FATAL", "Setting UID failed for: " + context_user + "/" + context_group, tty_user, tty_group, command, task_context, log_directory); + _exit(1); break; default: - std::cerr << "lcpex: Aborting: Unknown error while setting identity context." << std::endl; - exit(1); + tty_logger.log(E_FATAL, "Aborting: Unknown error while setting identity context."); + tty_logger.log_to_json_file("E_FATAL", "Unknown error while setting identity context.", tty_user, tty_group, command, task_context, log_directory); + _exit(1); break; } } @@ -84,8 +174,10 @@ void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[], // execute the dang command, print to stdout, stderr (of parent), and dump to file for each!!!! // (and capture exit code in parent) int exit_code = execvp( processed_command[0], processed_command ); - safe_perror("failed on execvp in child", ttyOrig ); - exit(exit_code); + tty_logger.log_to_json_file("E_FATAL", "failed on execvp in child", tty_user, tty_group, command, task_context, log_directory); + tty_logger.log(E_FATAL, "failed on execvp in child"); + _Exit(exit_code); + } // this does three things: @@ -99,8 +191,15 @@ int exec_pty( bool context_override, std::string context_user, std::string context_group, - bool environment_supplied + bool environment_supplied, + std::string task_context, + std::string log_directory ) { + try { + std::string exec_command = "exec_pty"; // Command context + tty_logger.log_to_json_file("E_INFO", "TTY LAUNCHER: " + command, tty_user, tty_group, exec_command, task_context, log_directory); + // Setup signal handlers + setup_tty_signal_handlers(task_context, log_directory); // initialize the terminal settings obj struct termios ttyOrig; @@ -115,13 +214,13 @@ int exec_pty( char ** processed_command = expand_env( command ); if ( stdout_log_fh == NULL ) { - safe_perror( "Error opening STDOUT log file. Aborting.", &ttyOrig ); - exit( 1 ); + tty_logger.log_to_json_file("E_FATAL", "Error opening STDOUT log file. Aborting.", tty_user, tty_group, exec_command, task_context, log_directory); + throw std::runtime_error("Error opening STDOUT log file"); } if ( stderr_log_fh == NULL ) { - safe_perror( "Error opening STDERR log file. Aborting.", &ttyOrig ); - exit( 1 ); + tty_logger.log_to_json_file("E_FATAL", "Error opening STDERR log file. Aborting.", tty_user, tty_group, exec_command, task_context, log_directory); + throw std::runtime_error("Error opening STDERR log file"); } @@ -129,16 +228,13 @@ int exec_pty( int fd_child_stderr_pipe[2]; if ( pipe( fd_child_stderr_pipe ) == -1 ) { - safe_perror( "child stderr pipe", &ttyOrig ); - exit( 1 ); + std::string error_message = "Failed to create stderr pipe: " + std::string(strerror(errno)); + tty_logger.log_to_json_file("E_FATAL", error_message, tty_user, tty_group, exec_command, task_context, log_directory); + throw std::runtime_error("Failed to create stderr pipe"); } - - // using O_CLOEXEC to ensure that the child process closes the file descriptors - if ( fcntl( fd_child_stderr_pipe[READ_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); } - - // // same for stderr write - if ( fcntl( fd_child_stderr_pipe[WRITE_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); } - + // using O_CLOEXEC to ensure that the child process closes the file descriptors + set_tty_cloexec_flag( fd_child_stderr_pipe[READ_END], task_context, log_directory ); + set_tty_cloexec_flag( fd_child_stderr_pipe[WRITE_END], task_context, log_directory ); // status result basket for the parent process to capture the child's exit status int status = 616; @@ -148,39 +244,52 @@ int exec_pty( struct winsize ws; /* Retrieve the attributes of terminal on which we are started */ - if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1) - safe_perror("tcgetattr", &ttyOrig); + if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1){ + tty_logger.log_to_json_file("E_FATAL", "tcgetattr failed", tty_user, tty_group, exec_command, task_context, log_directory); + close(fd_child_stderr_pipe[READ_END]); // Pipe Leaks on Error Paths + close(fd_child_stderr_pipe[WRITE_END]); // Pipe Leaks on Error Paths + throw std::runtime_error("tcgetattr failed"); +} if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) - safe_perror("ioctl-TIOCGWINSZ", &ttyOrig ); - +{ + tty_logger.log_to_json_file("E_FATAL", "ioctl-TIOCGWINSZ failed", tty_user, tty_group, exec_command, task_context, log_directory); + close(fd_child_stderr_pipe[READ_END]); // Pipe Leaks on Error Paths + close(fd_child_stderr_pipe[WRITE_END]); // Pipe Leaks on Error Paths + throw std::runtime_error("ioctl-TIOCGWINSZ failed"); +} pid_t pid = ptyFork( &masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws ); - switch( pid ) { case -1: { // fork failed - safe_perror("ptyfork failure", &ttyOrig ); - exit(1); + tty_logger.log(E_FATAL, "ptyfork failure: " + std::string(strerror(errno))); + tty_logger.log_to_json_file("E_FATAL", "ptyfork failure: " + std::string(strerror(errno)), tty_user, tty_group, exec_command, task_context, log_directory); + close(fd_child_stderr_pipe[READ_END]); // Pipe Leaks on Error Paths + close(fd_child_stderr_pipe[WRITE_END]); // Pipe Leaks on Error Paths + throw std::runtime_error("ptyfork() failed: " + std::string(strerror(errno))); } - case 0: { // child process - run_child_process( fd_child_stderr_pipe, processed_command, &ttyOrig, context_override, context_user, context_group ); + run_child_process( fd_child_stderr_pipe, processed_command, &ttyOrig, context_override, context_user, context_group, task_context, log_directory ); } - default: { // parent process // start ptyfork integration ttySetRaw(STDIN_FILENO, &ttyOrig); + // RAII guards for file descriptors + FdGuard stderr_read_guard(fd_child_stderr_pipe[READ_END]); + FdGuard stderr_write_guard(fd_child_stderr_pipe[WRITE_END]); + FdGuard master_fd_guard(masterFd); - // The parent process has no need to access the entrance to the pipe - close(fd_child_stderr_pipe[WRITE_END]); - + // We don't need WRITE_END in parent, close it now + stderr_write_guard.reset(); // Closes and sets fd to -1 // attempt to write to stdout,stderr from child as well as to write each to file - char buf[BUFFER_SIZE]; + 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; @@ -195,11 +304,11 @@ int exec_pty( watched_fds[0].events = POLLIN; // child pty to parent (stdout) - watched_fds[1].fd = masterFd; + watched_fds[1].fd = master_fd_guard.get(); watched_fds[1].events = POLLIN; // child stderr to parent - watched_fds[2].fd = fd_child_stderr_pipe[READ_END]; + watched_fds[2].fd = stderr_read_guard.get(); watched_fds[2].events = POLLIN; // number of files poll() reports as ready @@ -219,8 +328,10 @@ int exec_pty( if (num_files_readable == -1) { // error occurred in poll() - safe_perror("poll", &ttyOrig ); - exit(1); + tty_logger.log(E_FATAL, "poll() failed: " + std::string(strerror(errno))); + tty_logger.log_to_json_file("E_FATAL", "poll() failed", tty_user, tty_group, exec_command, task_context, log_directory); + ttyResetExit( &ttyOrig); + throw std::runtime_error("poll() failed"); } if (num_files_readable == 0) { @@ -237,7 +348,11 @@ int exec_pty( if (byte_count == -1) { if (errno == EAGAIN) { continue; } else { // error reading from pipe - safe_perror("read", &ttyOrig ); + tty_logger.log(E_FATAL, "Error while reading: " + std::string(strerror(errno))); + tty_logger.log_to_json_file("E_FATAL", "Error while reading from pipe", tty_user, tty_group, exec_command, task_context, log_directory); + ttyResetExit( &ttyOrig); + stderr_read_guard.reset(-1); + master_fd_guard.reset(-1); exit(EXIT_FAILURE); } } else if (byte_count == 0) { @@ -261,12 +376,17 @@ int exec_pty( write_all(STDERR_FILENO, buf, byte_count); } else { // this should never happen - perror("Logic error!"); + tty_logger.log(E_FATAL, "Logic error: unexpected pipe index."); exit(EXIT_FAILURE); } } } if (watched_fds[this_fd].revents & POLLERR) { + if (this_fd == 1) { + master_fd_guard.reset(-1); + } else if (this_fd == 2) { + stderr_read_guard.reset(-1); + } //close(watched_fds[this_fd].fd); break_out = true; //continue; @@ -288,15 +408,42 @@ int exec_pty( // wait for child to exit, capture status waitpid(pid, &status, 0); - while ((byte_count = read(watched_fds[1].fd, buf, BUFFER_SIZE)) > 0) { + while ((byte_count = read(master_fd_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); } + // Handle child process status with proper signal handling + pid_t child_pid = waitpid(-1, &status, WNOHANG); // Non-blocking wait for child process + if (child_pid > 0) { // If a child process has terminated + // Check if the child process exited normally + if (WIFEXITED(status)) { + int exit_code = WEXITSTATUS(status); + tty_logger.log(E_INFO, "Child exited with status " + std::to_string(exit_code)); + tty_logger.log_to_json_file("E_INFO", "Child exited with status " + std::to_string(exit_code), tty_user, tty_group, exec_command, task_context, log_directory); + ttyResetExit( &ttyOrig); + return exit_code; + } + // Check if the child process was terminated by a signal + else if (WIFSIGNALED(status)) { + int signal_number = WTERMSIG(status); + tty_logger.log(E_FATAL, "Process terminated by signal: " + std::to_string(signal_number)); + tty_logger.log_to_json_file("E_FATAL", "Process terminated by signal: " + std::to_string(signal_number), tty_user, tty_group, exec_command, task_context, log_directory); + // Reset signal handler to default before exiting + signal(signal_number, SIG_DFL); + ttyResetExit( &ttyOrig); + return 128 + signal_number; // POSIX exit code format + } + // Handle unexpected exit conditions + else { + tty_logger.log(E_WARN, "Unknown child exit condition."); + tty_logger.log_to_json_file("E_WARN", "Unknown child exit condition", tty_user, tty_group, exec_command, task_context, log_directory); + } + } ttyResetExit( &ttyOrig); if WIFEXITED(status) { @@ -306,4 +453,9 @@ int exec_pty( } } } + return 0; + } catch (const std::exception& ex) { + std::cerr << "[LCPEX TTY ERROR] " << ex.what() << std::endl; + return -999; + } } \ No newline at end of file diff --git a/src/lcpex/vpty/libclpex_tty.h b/src/lcpex/vpty/libclpex_tty.h index 328cf8f..407d165 100644 --- a/src/lcpex/vpty/libclpex_tty.h +++ b/src/lcpex/vpty/libclpex_tty.h @@ -43,7 +43,9 @@ int exec_pty( bool context_override, std::string context_user, std::string context_group, - bool environment_supplied + bool environment_supplied, + std::string task_context = "", + std::string log_directory = "" ); diff --git a/src/logger/Logger.cpp b/src/logger/Logger.cpp index 8b022d4..37518ff 100644 --- a/src/logger/Logger.cpp +++ b/src/logger/Logger.cpp @@ -19,6 +19,13 @@ */ #include "Logger.h" +#include +#include +#include +#include +#include // For FdGuard +#include // For O_RDONLY, O_WRONLY, open() +#include "../lcpex/FileHandle.h" // Include the FdGuard header Logger::Logger( int LOG_LEVEL, std::string mask ) { @@ -26,6 +33,11 @@ Logger::Logger( int LOG_LEVEL, std::string mask ) this->mask = mask; } +// Destructor added in the update +Logger::~Logger() { + +} + void Logger::log( int LOG_LEVEL, std::string msg ) { std::string ERR = "XXXX"; @@ -42,7 +54,7 @@ void Logger::log( int LOG_LEVEL, std::string msg ) std::string s_msg = "[" + ERR + "] " + msg; - if ( LOG_LEVEL == E_FATAL | LOG_LEVEL == E_WARN ) + if ( LOG_LEVEL == E_FATAL || LOG_LEVEL == E_WARN ) { std::cerr << "[" << get_8601() << "] [" << ERR << "] " << "[" << this->mask << "] " << msg.c_str() << std::endl; } else { @@ -53,6 +65,152 @@ void Logger::log( int LOG_LEVEL, std::string msg ) void Logger::log_task( int LOG_LEVEL, std::string task_name, std::string msg ) { - std::string final_msg = "[" + task_name + "] " + msg; - this->log( LOG_LEVEL, final_msg ); + // Added memory management from the updated file + + size_t task_msg_len = task_name.length() + msg.length() + 4; + char* task_msg = (char*)malloc(task_msg_len); + + if (task_msg) { + snprintf(task_msg, task_msg_len, "[%s] %s", task_name.c_str(), msg.c_str()); + log(LOG_LEVEL, task_msg); + free(task_msg); + } +} + +// JSON logging method +void Logger::create_json_log_entry(char* buffer, size_t buffer_size, + const char* log_level, const char* message, + const char* user, const char* group, + const char* command) +{ + std::string timestamp = get_current_timestamp(); + snprintf(buffer, buffer_size, + "{\n" + " \"timestamp\": \"%s\",\n" + " \"log_level\": \"%s\",\n" + " \"message\": \"%s\",\n" + " \"context\": {\n" + " \"user\": \"%s\",\n" + " \"group\": \"%s\",\n" + " \"command\": \"%s\"\n" + " }\n" + "}", + timestamp.c_str(), log_level, message, user, group, command); +} + +// Add methods for user/group name and timestamp +std::string Logger::get_user_name() +{ + struct passwd* pw = getpwuid(getuid()); + if (pw) { + return std::string(pw->pw_name); + } + return "unknown"; +} + +std::string Logger::get_group_name() +{ + struct group* grp = getgrgid(getgid()); + if (grp) { + return std::string(grp->gr_name); + } + return "unknown"; +} + +std::string Logger::get_current_timestamp() +{ + time_t now = time(NULL); + struct tm* timeinfo = localtime(&now); + char buffer[64]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d_%H:%M:%S", timeinfo); + return std::string(buffer); +} + +//Log to JSON file +void Logger::log_to_json_file(const std::string& log_level, const std::string& message, + const std::string& user, const std::string& group, + const std::string& command, const std::string& task_context, + const std::string& log_directory, bool log_to_console) +{ + // Log to console if requested + const char* log_level_str = "UNKNOWN"; + if (log_level == "E_INFO") log_level_str = "INFO"; + else if (log_level == "E_FATAL") log_level_str = "FATAL"; + else if (log_level == "E_WARN") log_level_str = "WARN"; + else if (log_level == "E_DEBUG") log_level_str = "DEBUG"; + + if (log_to_console && (log_level_str == "INFO" || log_level_str == "FATAL")) { + std::string timestamp = get_current_timestamp(); + printf("[%s] [%s] [%s] %s\n", timestamp.c_str(), log_level_str, this->mask.c_str(), message.c_str()); + fflush(stdout); + } + + // 2. Create JSON log entry + char json_log[2048]; + create_json_log_entry(json_log, sizeof(json_log), log_level_str, message.c_str(), user.c_str(), group.c_str(), command.c_str()); + + // 3. Generate filename with format: YYYY-MM-DD_context.log.json + std::string filename; + if (!task_context.empty() && !log_directory.empty()) { + // Use task-specific directory and format + time_t now = time(NULL); + struct tm* timeinfo = localtime(&now); + char date_buffer[16]; + strftime(date_buffer, sizeof(date_buffer), "%Y-%m-%d", timeinfo); + std::string date_str = std::string(date_buffer); + + filename = log_directory + "/" + task_context + "/" + date_str + "_" + task_context + ".log.json"; + } else { + // Fallback to original format for backward compatibility + std::string timestamp = get_current_timestamp(); + filename = "log_" + timestamp + ".json"; + } + + // 4. Use FdGuard for file handling (to manage file opening and closing) + FdGuard check_file(open(filename.c_str(), O_RDONLY)); + bool file_empty = true; + + if (check_file.get() != -1) { + off_t file_size = lseek(check_file.get(), 0, SEEK_END); + file_empty = (file_size == 0); + } + + if (file_empty) { + // First entry - create new JSON array + FdGuard json_file(open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)); + if (json_file.get() != -1) { + dprintf(json_file.get(), "[\n %s\n]\n", json_log); + } else { + fprintf(stderr, "Failed to open log file for writing: %s\n", filename.c_str()); + } + } else { + // Subsequent entries - handle JSON array properly + FdGuard read_file(open(filename.c_str(), O_RDONLY)); + if (read_file.get() == -1) { + fprintf(stderr, "Failed to open log file for reading: %s\n", filename.c_str()); + return; + } + + off_t file_size = lseek(read_file.get(), 0, SEEK_END); + lseek(read_file.get(), 0, SEEK_SET); + char* content = (char*)malloc(file_size + 1); + if (!content) { + fprintf(stderr, "Failed to allocate memory for file content\n"); + return; + } + read(read_file.get(), content, file_size); + content[file_size] = '\0'; + // Find and remove the last ']' + char* last_bracket = strrchr(content, ']'); + if (last_bracket) { + *last_bracket = '\0'; + } + FdGuard write_file(open(filename.c_str(), O_WRONLY | O_TRUNC, 0644)); + if (write_file.get() != -1) { + dprintf(write_file.get(), "%s,\n %s\n]\n", content, json_log); + } else { + fprintf(stderr, "Failed to open log file for writing: %s\n", filename.c_str()); + } + free(content); + } } diff --git a/src/logger/Logger.h b/src/logger/Logger.h index f4c5330..5e3af7d 100644 --- a/src/logger/Logger.h +++ b/src/logger/Logger.h @@ -24,8 +24,14 @@ #include #include -#include -#include +#include +#include // For O_RDONLY, O_WRONLY, open() +#include +#include +#include +#include +#include +#include // for va_list in FileGuard::printf #include "../misc/helpers.h" enum L_LVL { @@ -37,15 +43,29 @@ enum L_LVL { class Logger { public: - Logger( int LOG_LEVEL, std::string mask ); - void log( int LOG_LEVEL, std::string msg ); - void log_task( int LOG_LEVEL, std::string task_name, std::string msg ); + // Constructor and Destructor + Logger(int LOG_LEVEL, std::string mask); + ~Logger(); // Added Destructor + // Logging methods + void log(int LOG_LEVEL, std::string msg); + void log_task(int LOG_LEVEL, std::string task_name, std::string msg); + void log_to_json_file(const std::string& log_level, const std::string& message, + const std::string& user, const std::string& group, + const std::string& command, const std::string& task_context = "", + const std::string& log_directory = "", bool log_to_console = true); + // Helper methods + std::string get_current_timestamp(); + std::string get_user_name(); + std::string get_group_name(); private: int LOG_LEVEL; std::string mask; + // Internal helper methods + void create_json_log_entry(char* buffer, size_t buffer_size, + const char* log_level, const char* message, + const char* user, const char* group, + const char* command); }; - - #endif //REX_LOGGER_H diff --git a/src/plan/Task.cpp b/src/plan/Task.cpp index 6263388..e96d8ad 100644 --- a/src/plan/Task.cpp +++ b/src/plan/Task.cpp @@ -470,7 +470,9 @@ void Task::execute( Conf * configuration ) shell_definition.execution_arg, supply_environment, shell_definition.source_cmd, - environment_file + environment_file, + task_name, + logs_root ); // ********************************************** @@ -539,7 +541,9 @@ void Task::execute( Conf * configuration ) shell_definition.execution_arg, supply_environment, shell_definition.source_cmd, - environment_file + environment_file, + task_name, + logs_root ); // ********************************************** @@ -591,7 +595,9 @@ void Task::execute( Conf * configuration ) shell_definition.execution_arg, supply_environment, shell_definition.source_cmd, - environment_file + environment_file, + task_name, + logs_root ); // **********************************************