Compare commits
29 Commits
3.0
...
lcpex_enha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd8191c2d2 | ||
|
|
08c9d5bf07 | ||
|
|
879f07ec4b | ||
|
|
54d95aa582 | ||
|
|
698ba9373c | ||
|
|
d13793954d | ||
|
|
e9fd8c80bc | ||
|
|
d15847dbfe | ||
|
|
60a9f2bad2 | ||
|
|
e03a7e67b8 | ||
|
|
0561460b54 | ||
|
|
344cbfc56e | ||
| bb324087a8 | |||
| 0f35e752d3 | |||
| cddc271ac7 | |||
| 2904e9dcd8 | |||
| 7a816d5920 | |||
| f510f2b8cc | |||
| 67ab74a8c8 | |||
| d4d5a2b30d | |||
| bb466185d0 | |||
| e16c663b5f | |||
| 96e7cae5df | |||
| 74bce85f1e | |||
| e9556c6075 | |||
|
|
130d539f81 | ||
|
|
35248176cb | ||
| 5f8ec22166 | |||
| 9a33086cdd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ cmake-build-release
|
|||||||
CMakeFiles
|
CMakeFiles
|
||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
Makefile
|
Makefile
|
||||||
|
sample/logs/*
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ project(rex)
|
|||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
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/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)
|
||||||
|
|||||||
5
Rex.cpp
5
Rex.cpp
@@ -15,12 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include "src/logger/Logger.h"
|
#include "src/logger/Logger.h"
|
||||||
#include "src/config/Config.h"
|
#include "src/config/Config.h"
|
||||||
#include "src/suite/Suite.h"
|
#include "src/suite/Suite.h"
|
||||||
#include "src/plan/Plan.h"
|
#include "src/plan/Plan.h"
|
||||||
|
#include "src/misc/helpers.h"
|
||||||
|
|
||||||
void version_info()
|
void version_info()
|
||||||
{
|
{
|
||||||
@@ -162,6 +165,8 @@ int main( int argc, char * argv[] )
|
|||||||
interpolate( config_path );
|
interpolate( config_path );
|
||||||
interpolate( plan_path );
|
interpolate( plan_path );
|
||||||
|
|
||||||
|
plan_path = get_absolute_path( plan_path );
|
||||||
|
|
||||||
// default logging level
|
// default logging level
|
||||||
int L_LEVEL = E_INFO;
|
int L_LEVEL = E_INFO;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
#
|
#
|
||||||
echo test from script
|
echo test from script
|
||||||
/usr/bin/dialog --title "This should be one argument" --inputbox "Enter your name:" 0 0
|
#/usr/bin/dialog --title "This should be one argument" --inputbox "Enter your name:" 0 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set -a
|
set -a
|
||||||
|
|
||||||
echo "variables file says hello and set a variable named TEST_VAR"
|
echo "variables file says hello and set a variable named TEST_VAR"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"plan": [
|
"plan": [
|
||||||
{ "name": "independent test 1", "dependencies": [ null ] }
|
{ "name": "independent test 1", "dependencies": [ null ] },
|
||||||
|
{ "name": "independent test 2", "dependencies": [ null ] }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"project_root": "/tmp/sample",
|
"project_root": "$HOME/development/internal/rex/sample",
|
||||||
"units_path": "units/",
|
"units_path": "units/",
|
||||||
"logs_path": "logs/",
|
"logs_path": "logs/",
|
||||||
"shells_path": "shells/shells.definitions",
|
"shells_path": "shells/shells.definitions",
|
||||||
|
|||||||
@@ -13,8 +13,26 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"required": true,
|
"required": true,
|
||||||
"set_user_context": true,
|
"set_user_context": true,
|
||||||
"user": "bagira",
|
"user": "$USER",
|
||||||
"group": "bagira",
|
"group": "$USER",
|
||||||
|
"supply_environment": true,
|
||||||
|
"environment": "environments/rex.variables"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "independent test 2",
|
||||||
|
"target": "components/independent_test_1.bash --ls -s --arg",
|
||||||
|
"is_shell_command": true,
|
||||||
|
"shell_definition": "bash",
|
||||||
|
"force_pty": true,
|
||||||
|
"set_working_directory": false,
|
||||||
|
"working_directory": "",
|
||||||
|
"rectify": false,
|
||||||
|
"rectifier": "echo rectifier executed",
|
||||||
|
"active": true,
|
||||||
|
"required": true,
|
||||||
|
"set_user_context": true,
|
||||||
|
"user": "$USER",
|
||||||
|
"group": "$USER",
|
||||||
"supply_environment": true,
|
"supply_environment": true,
|
||||||
"environment": "environments/rex.variables"
|
"environment": "environments/rex.variables"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ void Conf::set_object_s(std::string keyname, std::string & object_member, std::s
|
|||||||
if ( this->get_string(jval_s, keyname) != 0) {
|
if ( this->get_string(jval_s, keyname) != 0) {
|
||||||
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
|
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
|
||||||
} else {
|
} else {
|
||||||
interpolate(jval_s);
|
//interpolate(jval_s);
|
||||||
object_member = jval_s;
|
object_member = jval_s;
|
||||||
}
|
}
|
||||||
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
|
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
|
||||||
@@ -101,7 +101,7 @@ void Conf::set_object_s_derivedpath(std::string keyname, std::string & object_me
|
|||||||
if ( this->get_string(jval_s, keyname) != 0) {
|
if ( this->get_string(jval_s, keyname) != 0) {
|
||||||
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
|
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
|
||||||
} else {
|
} else {
|
||||||
interpolate(jval_s);
|
//interpolate(jval_s);
|
||||||
object_member = prepend_project_root( jval_s );
|
object_member = prepend_project_root( jval_s );
|
||||||
}
|
}
|
||||||
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
|
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
|
||||||
@@ -157,29 +157,7 @@ std::string Conf::prepend_project_root( std::string relative_path)
|
|||||||
return this->project_root + "/" + relative_path;
|
return this->project_root + "/" + relative_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the absolute path from a relative path
|
|
||||||
*
|
|
||||||
* This function takes a relative path and returns the corresponding absolute path.
|
|
||||||
* The absolute path is obtained by calling the `realpath` function.
|
|
||||||
* If the `realpath` function returns a null pointer, an error message is printed to the standard error stream and an empty string is returned.
|
|
||||||
*
|
|
||||||
* @param relative_path The relative path to be converted to an absolute path
|
|
||||||
*
|
|
||||||
* @return The absolute path corresponding to the relative path
|
|
||||||
*/
|
|
||||||
std::string get_absolute_path(const std::string &relative_path)
|
|
||||||
{
|
|
||||||
char resolved_path[1024];
|
|
||||||
memset(resolved_path, 0, sizeof(resolved_path));
|
|
||||||
|
|
||||||
if( realpath( relative_path.c_str(), resolved_path) == nullptr ) {
|
|
||||||
std::cerr << "Error resolving path: " << relative_path << std::endl;
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::string(resolved_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if a path exists
|
* @brief Check if a path exists
|
||||||
@@ -265,8 +243,6 @@ Shell Conf::get_shell_by_name( std::string name ) {
|
|||||||
throw ConfigLoadException("The shell specified ('" + name + "') is not defined in the shell definitions file.");
|
throw ConfigLoadException("The shell specified ('" + name + "') is not defined in the shell definitions file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Conf
|
* @class Conf
|
||||||
* @brief Loads the configuration for the application
|
* @brief Loads the configuration for the application
|
||||||
@@ -284,9 +260,9 @@ Shell Conf::get_shell_by_name( std::string name ) {
|
|||||||
Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_conf_" )
|
Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_conf_" )
|
||||||
{
|
{
|
||||||
this->LOG_LEVEL = LOG_LEVEL;
|
this->LOG_LEVEL = LOG_LEVEL;
|
||||||
this->slog.log_task( E_DEBUG, "LOAD", "Loading configuration file: " + filename );
|
|
||||||
|
|
||||||
interpolate( filename );
|
interpolate( filename );
|
||||||
|
this->slog.log_task( E_DEBUG, "LOAD", "Loading configuration file: " + filename );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// load the test file.
|
// load the test file.
|
||||||
@@ -295,6 +271,7 @@ Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(
|
|||||||
this->slog.log( E_FATAL, "Unable to load configuration file: '" + filename + "'. Error: " + e.what());
|
this->slog.log( E_FATAL, "Unable to load configuration file: '" + filename + "'. Error: " + e.what());
|
||||||
throw ConfigLoadException("Parsing error in configuration file.");
|
throw ConfigLoadException("Parsing error in configuration file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value jbuff;
|
Json::Value jbuff;
|
||||||
|
|
||||||
if ( this->get_serialized( jbuff, "config" ) != 0) {
|
if ( this->get_serialized( jbuff, "config" ) != 0) {
|
||||||
@@ -305,22 +282,27 @@ Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(
|
|||||||
this->json_root = jbuff;
|
this->json_root = jbuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_object_s( "project_root", this->project_root, filename );
|
set_object_s( "project_root", this->project_root, filename );
|
||||||
|
interpolate( this->project_root );
|
||||||
|
|
||||||
// convert to an absolute path after all the interpolation is done.
|
// convert to an absolute path after all the interpolation is done.
|
||||||
this->project_root = get_absolute_path( this->project_root );
|
this->project_root = get_absolute_path( this->project_root );
|
||||||
set_object_s( "logs_path", this->logs_path, filename );
|
|
||||||
this->logs_path = get_absolute_path( this->logs_path );
|
|
||||||
|
|
||||||
|
set_object_s( "logs_path", this->logs_path, filename );
|
||||||
|
interpolate( this->logs_path );
|
||||||
|
|
||||||
// all other paths are relative to project_root
|
// all other paths are relative to project_root
|
||||||
set_object_s_derivedpath( "units_path", this->units_path, filename );
|
set_object_s_derivedpath( "units_path", this->units_path, filename );
|
||||||
set_object_s_derivedpath( "shells_path", this->shell_definitions_path, filename );
|
interpolate( this->units_path );
|
||||||
|
|
||||||
|
set_object_s_derivedpath( "shells_path", this->shell_definitions_path, filename );
|
||||||
|
interpolate( this->shell_definitions_path );
|
||||||
|
|
||||||
// ensure these paths exists, with exception to the logs_path, which will be created at runtime
|
// ensure these paths exists, with exception to the logs_path, which will be created at runtime
|
||||||
this->slog.log_task( E_DEBUG, "SANITY_CHECKS", "Checking for sanity..." );
|
this->slog.log_task( E_DEBUG, "SANITY_CHECKS", "Checking for sanity..." );
|
||||||
checkPathExists( "project_root", this->project_root );
|
checkPathExists( "project_root", this->project_root );
|
||||||
checkPathExists( "units_path", this->units_path );
|
checkPathExists( "units_path", this->units_path );
|
||||||
checkPathExists( "shells_path", this->shell_definitions_path );
|
checkPathExists( "shells_path", this->shell_definitions_path );
|
||||||
|
|
||||||
// shells are scoped beyond plan so they need to be considered part of config
|
// shells are scoped beyond plan so they need to be considered part of config
|
||||||
load_shells();
|
load_shells();
|
||||||
|
|||||||
54
src/lcpex/FileHandle.h
Normal file
54
src/lcpex/FileHandle.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <unistd.h> // 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
15
src/lcpex/helpers.cpp
Normal file
15
src/lcpex/helpers.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include "helpers.h"
|
||||||
|
|
||||||
|
ssize_t write_all(int fd, const void *buf, size_t count) {
|
||||||
|
const char *p = (const char *)buf;
|
||||||
|
while (count > 0) {
|
||||||
|
ssize_t written = write(fd, p, count);
|
||||||
|
if (written == -1) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) continue; // Retry
|
||||||
|
return -1; // Other errors
|
||||||
|
}
|
||||||
|
count -= written;
|
||||||
|
p += written;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#ifndef LCPEX_HELPERS_H
|
#ifndef LCPEX_HELPERS_H
|
||||||
#define LCPEX_HELPERS_H
|
#define LCPEX_HELPERS_H
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "errno.h"
|
||||||
|
|
||||||
// helper for sanity
|
// helper for sanity
|
||||||
enum PIPE_ENDS {
|
enum PIPE_ENDS {
|
||||||
READ_END = 0,
|
READ_END = 0,
|
||||||
@@ -16,6 +19,6 @@ enum CHILD_PIPE_NAMES {
|
|||||||
|
|
||||||
#define BUFFER_SIZE 1024
|
#define BUFFER_SIZE 1024
|
||||||
|
|
||||||
|
ssize_t write_all(int fd, const void *buf, size_t count);
|
||||||
|
|
||||||
#endif //LCPEX_HELPERS_H
|
#endif //LCPEX_HELPERS_H
|
||||||
|
|||||||
@@ -1,4 +1,77 @@
|
|||||||
#include "liblcpex.h"
|
#include "liblcpex.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string>
|
||||||
|
#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 prefix_generator(
|
||||||
std::string command,
|
std::string command,
|
||||||
@@ -7,7 +80,9 @@ std::string prefix_generator(
|
|||||||
std::string shell_execution_arg,
|
std::string shell_execution_arg,
|
||||||
bool supply_environment,
|
bool supply_environment,
|
||||||
std::string shell_source_subcommand,
|
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 = "";
|
std::string prefix = "";
|
||||||
if ( is_shell_command ) {
|
if ( is_shell_command ) {
|
||||||
@@ -36,8 +111,12 @@ std::string prefix_generator(
|
|||||||
// it's not a shell command, so we can just execute it directly
|
// it's not a shell command, so we can just execute it directly
|
||||||
prefix = command;
|
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;
|
return prefix;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +133,9 @@ int lcpex(
|
|||||||
std::string shell_execution_arg,
|
std::string shell_execution_arg,
|
||||||
bool supply_environment,
|
bool supply_environment,
|
||||||
std::string shell_source_subcommand,
|
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
|
// generate the prefix
|
||||||
@@ -65,18 +146,21 @@ int lcpex(
|
|||||||
shell_execution_arg,
|
shell_execution_arg,
|
||||||
supply_environment,
|
supply_environment,
|
||||||
shell_source_subcommand,
|
shell_source_subcommand,
|
||||||
environment_file_path
|
environment_file_path,
|
||||||
|
task_context,
|
||||||
|
log_directory
|
||||||
);
|
);
|
||||||
command = prefix;
|
command = prefix;
|
||||||
|
setup_signal_handlers(task_context, log_directory);
|
||||||
|
|
||||||
// if we are forcing a pty, then we will use the vpty library
|
// if we are forcing a pty, then we will use the vpty library
|
||||||
if( force_pty )
|
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
|
// 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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,12 +172,16 @@ int lcpex(
|
|||||||
*
|
*
|
||||||
* @param fd The file descriptor for which to set the close-on-exec flag
|
* @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)
|
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
|
||||||
{
|
{
|
||||||
perror("fcntl");
|
std::string error_message = "fcntl(F_SETFD) failed for fd " + std::to_string(fd);
|
||||||
exit(1);
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +209,16 @@ void set_cloexec_flag(int fd)
|
|||||||
* Finally, the child process calls execvp() with the processed_command to run the shell command.
|
* 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.
|
* 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_stdout_pipe[WRITE_END], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
|
||||||
while ((dup2(fd_child_stderr_pipe[WRITE_END], STDERR_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 ) {
|
if ( context_override ) {
|
||||||
int context_result = set_identity_context(context_user, context_group);
|
int context_result = set_identity_context(context_user, context_group);
|
||||||
@@ -131,34 +226,38 @@ void run_child_process(bool context_override, const char* context_user, const ch
|
|||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
|
||||||
break;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
||||||
std::cerr << "REX: Aborting: context user not found: " << context_user << std::endl;
|
logger.log(E_FATAL, "Aborting: context user not found: " + std::string(context_user));
|
||||||
exit(1);
|
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;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
||||||
std::cerr << "REX: Aborting: context group not found: " << context_group << std::endl;
|
logger.log(E_FATAL, "Aborting: context group not found: " + std::string(context_group));
|
||||||
exit(1);
|
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;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
||||||
std::cerr << "REX: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
|
logger.log(E_FATAL, "Aborting: Setting GID failed: " + std::string(context_user) + "/" + std::string(context_group));
|
||||||
exit(1);
|
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;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
||||||
std::cerr << "REX: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
|
logger.log(E_FATAL, "Aborting: Setting UID failed: " + std::string(context_user) + "/" + std::string(context_group));
|
||||||
exit(1);
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
std::cerr << "REX: Aborting: Unknown error while setting identity context." << std::endl;
|
logger.log(E_FATAL, "Aborting: Unknown error while setting identity context.");
|
||||||
exit(1);
|
logger.log_to_json_file("E_FATAL", "Aborting: Unknown error while setting identity context.", user, group, command, task_context, log_directory);
|
||||||
|
_exit(1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int exit_code = execvp(processed_command[0], processed_command);
|
int exit_code = execvp(processed_command[0], processed_command);
|
||||||
perror("failed on execvp in child");
|
logger.log_to_json_file("E_FATAL", "failed on execvp in child", user, group, command, task_context, log_directory);
|
||||||
exit(exit_code);
|
logger.log(E_FATAL, "failed on execvp in child");
|
||||||
|
_Exit(exit_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int execute(
|
int execute(
|
||||||
std::string command,
|
std::string command,
|
||||||
FILE * stdout_log_fh,
|
FILE * stdout_log_fh,
|
||||||
@@ -166,8 +265,12 @@ int execute(
|
|||||||
bool context_override,
|
bool context_override,
|
||||||
std::string context_user,
|
std::string context_user,
|
||||||
std::string context_group,
|
std::string context_group,
|
||||||
bool environment_supplied
|
bool environment_supplied,
|
||||||
|
std::string task_context,
|
||||||
|
std::string log_directory
|
||||||
){
|
){
|
||||||
|
|
||||||
|
try {
|
||||||
// this does three things:
|
// this does three things:
|
||||||
// - execute a dang string as a subprocess command
|
// - execute a dang string as a subprocess command
|
||||||
// - capture child stdout/stderr to respective log files
|
// - capture child stdout/stderr to respective log files
|
||||||
@@ -180,7 +283,8 @@ int execute(
|
|||||||
// before we fork the process, so that the child process will inherit the environment
|
// before we fork the process, so that the child process will inherit the environment
|
||||||
// from the parent process
|
// from the parent process
|
||||||
if ( environment_supplied ) {
|
if ( environment_supplied ) {
|
||||||
clearenv();
|
// this breaks reuse of env variables in between executions
|
||||||
|
//clearenv();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the pipes for the child process to write and read from using its stdin/stdout/stderr
|
// create the pipes for the child process to write and read from using its stdin/stdout/stderr
|
||||||
@@ -188,31 +292,36 @@ int execute(
|
|||||||
int fd_child_stderr_pipe[2];
|
int fd_child_stderr_pipe[2];
|
||||||
|
|
||||||
if ( pipe(fd_child_stdout_pipe ) == -1 ) {
|
if ( pipe(fd_child_stdout_pipe ) == -1 ) {
|
||||||
perror( "child stdout pipe" );
|
throw std::runtime_error("Failed to create stdout pipe");
|
||||||
exit( 1 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
||||||
perror( "child stderr pipe" );
|
close(fd_child_stdout_pipe[0]);
|
||||||
exit( 1 );
|
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
|
// 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_stdout_pipe[READ_END], task_context, log_directory );
|
||||||
set_cloexec_flag( fd_child_stderr_pipe[READ_END] );
|
set_cloexec_flag( fd_child_stderr_pipe[READ_END], task_context, log_directory );
|
||||||
set_cloexec_flag( fd_child_stdout_pipe[WRITE_END] );
|
set_cloexec_flag( fd_child_stdout_pipe[WRITE_END], task_context, log_directory );
|
||||||
set_cloexec_flag( fd_child_stderr_pipe[WRITE_END] );
|
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
|
// status result basket for the parent process to capture the child's exit status
|
||||||
int status = 616;
|
int status;
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
|
|
||||||
switch( pid ) {
|
switch( pid ) {
|
||||||
case -1:
|
case -1:
|
||||||
{
|
{
|
||||||
// fork failed
|
// fork failed
|
||||||
perror("fork failure");
|
logger.log(E_FATAL, "fork failure: " + std::string(strerror(errno)));
|
||||||
exit(1);
|
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:
|
case 0:
|
||||||
@@ -224,7 +333,9 @@ int execute(
|
|||||||
context_group.c_str(),
|
context_group.c_str(),
|
||||||
processed_command,
|
processed_command,
|
||||||
fd_child_stdout_pipe,
|
fd_child_stdout_pipe,
|
||||||
fd_child_stderr_pipe
|
fd_child_stderr_pipe,
|
||||||
|
task_context,
|
||||||
|
log_directory
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,14 +343,23 @@ int execute(
|
|||||||
{
|
{
|
||||||
// parent process
|
// 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
|
// 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:
|
// 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
|
// 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
|
// contains the byte count of the last read from the pipe
|
||||||
ssize_t byte_count;
|
ssize_t byte_count;
|
||||||
|
|
||||||
@@ -249,11 +369,11 @@ int execute(
|
|||||||
// populate the watched_fds array
|
// populate the watched_fds array
|
||||||
|
|
||||||
// child STDOUT to parent
|
// 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;
|
watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].events = POLLIN;
|
||||||
|
|
||||||
// child STDERR to parent
|
// 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;
|
watched_fds[CHILD_PIPE_NAMES::STDERR_READ].events = POLLIN;
|
||||||
|
|
||||||
// number of files poll() reports as ready
|
// number of files poll() reports as ready
|
||||||
@@ -265,10 +385,17 @@ int execute(
|
|||||||
// loop until we've read all the data from the child process
|
// loop until we've read all the data from the child process
|
||||||
while ( ! break_out ) {
|
while ( ! break_out ) {
|
||||||
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
||||||
|
// after the poll() call, add a check to see if both pipes are closed
|
||||||
|
if (!(watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].events & POLLIN) &&
|
||||||
|
!(watched_fds[CHILD_PIPE_NAMES::STDERR_READ].events & POLLIN)) {
|
||||||
|
break_out = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (num_files_readable == -1) {
|
if (num_files_readable == -1) {
|
||||||
// error occurred in poll()
|
// error occurred in poll()
|
||||||
perror("poll");
|
logger.log(E_FATAL, "poll() failed: " + std::string(strerror(errno)));
|
||||||
exit(1);
|
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) {
|
if (num_files_readable == 0) {
|
||||||
// poll reports no files readable
|
// poll reports no files readable
|
||||||
@@ -281,9 +408,15 @@ int execute(
|
|||||||
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
||||||
|
|
||||||
if (byte_count == -1) {
|
if (byte_count == -1) {
|
||||||
// error reading from pipe
|
if (errno == EAGAIN) { continue; } else {
|
||||||
perror("read");
|
// error reading from pipe
|
||||||
exit(EXIT_FAILURE);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (byte_count == 0) {
|
} else if (byte_count == 0) {
|
||||||
// reached EOF on one of the streams but not a HUP
|
// reached EOF on one of the streams but not a HUP
|
||||||
// we've read all we can this cycle, so go to the next fd in the for loop
|
// we've read all we can this cycle, so go to the next fd in the for loop
|
||||||
@@ -293,41 +426,79 @@ int execute(
|
|||||||
// write to stdout,stderr
|
// write to stdout,stderr
|
||||||
if (this_fd == CHILD_PIPE_NAMES::STDOUT_READ) {
|
if (this_fd == CHILD_PIPE_NAMES::STDOUT_READ) {
|
||||||
// the child's stdout pipe is readable
|
// the child's stdout pipe is readable
|
||||||
write(stdout_log_fh->_fileno, buf, byte_count);
|
write_all(stdout_log_fh->_fileno, buf, byte_count);
|
||||||
write(STDOUT_FILENO, buf, byte_count);
|
write_all(STDOUT_FILENO, buf, byte_count);
|
||||||
} else if (this_fd == CHILD_PIPE_NAMES::STDERR_READ) {
|
} else if (this_fd == CHILD_PIPE_NAMES::STDERR_READ) {
|
||||||
// the child's stderr pipe is readable
|
// the child's stderr pipe is readable
|
||||||
write(stderr_log_fh->_fileno, buf, byte_count);
|
write_all(stderr_log_fh->_fileno, buf, byte_count);
|
||||||
write(STDERR_FILENO, buf, byte_count);
|
write_all(STDERR_FILENO, buf, byte_count);
|
||||||
} else {
|
} else {
|
||||||
// this should never happen
|
// this should never happen
|
||||||
perror("Logic error!");
|
logger.log(E_FATAL, "Logic error: unexpected pipe index.");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (watched_fds[this_fd].revents & POLLERR) {
|
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;
|
break_out = true;
|
||||||
}
|
}
|
||||||
if (watched_fds[this_fd].revents & POLLHUP) {
|
if (watched_fds[this_fd].revents & POLLHUP) {
|
||||||
// this pipe has hung up
|
// this pipe has hung up
|
||||||
close(watched_fds[this_fd].fd);
|
// don't close the file descriptor yet, there might still be data to read
|
||||||
break_out = true;
|
// instead, remove the POLLIN event to avoid getting a POLLHUP event in the next poll() call
|
||||||
|
watched_fds[this_fd].events &= ~POLLIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// wait for child to exit, capture status
|
// wait for child to exit, capture status
|
||||||
waitpid(pid, &status, 0);
|
waitpid(pid, &status, 0);
|
||||||
|
|
||||||
|
// Drain the pipes before exiting
|
||||||
|
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(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) {
|
int status;
|
||||||
return WEXITSTATUS(status);
|
pid_t pid = waitpid(-1, &status, WNOHANG); // Non-blocking wait for child process
|
||||||
} else {
|
if (pid > 0) { // If a child process has terminated
|
||||||
return -617;
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ int execute(
|
|||||||
bool context_override,
|
bool context_override,
|
||||||
std::string context_user,
|
std::string context_user,
|
||||||
std::string context_group,
|
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,
|
std::string shell_execution_arg,
|
||||||
bool supply_environment,
|
bool supply_environment,
|
||||||
std::string shell_source_subcommand,
|
std::string shell_source_subcommand,
|
||||||
std::string environment_file_path
|
std::string environment_file_path,
|
||||||
|
std::string task_context = "",
|
||||||
|
std::string log_directory = ""
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,74 @@
|
|||||||
#include "libclpex_tty.h"
|
#include "libclpex_tty.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string>
|
||||||
|
#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
|
* @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.
|
* Then, it calls `ttyResetExit()` with `ttyOrig` as its argument to reset the terminal settings.
|
||||||
* Finally, the function exits the program with exit code 1.
|
* 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;
|
std::string command = "set_tty_cloexec_flag";
|
||||||
ttyResetExit( ttyOrig );
|
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
|
||||||
exit(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
|
* @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()`.
|
* 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.
|
* 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
|
// 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
|
// 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)) {}
|
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[WRITE_END] );
|
||||||
|
close( fd_child_stderr_pipe[READ_END] );
|
||||||
|
|
||||||
// if the user has specified a context override, set the context
|
// if the user has specified a context override, set the context
|
||||||
if ( context_override )
|
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:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
|
||||||
break;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
||||||
std::cerr << "lcpex: Aborting: context user not found: " << context_user << std::endl;
|
tty_logger.log(E_FATAL, "Aborting: context user not found: " + context_user);
|
||||||
exit(1);
|
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;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
||||||
std::cerr << "lcpex: Aborting: context group not found: " << context_group << std::endl;
|
tty_logger.log(E_FATAL, "Aborting: context group not found: " + context_group);
|
||||||
exit(1);
|
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;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
||||||
std::cerr << "lcpex: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
|
tty_logger.log(E_FATAL, "Aborting: Setting GID failed: " + context_user + "/" + context_group);
|
||||||
exit(1);
|
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;
|
break;
|
||||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
||||||
std::cerr << "lcpex: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
|
tty_logger.log(E_FATAL, "Aborting: Setting UID failed: " + context_user + "/" + context_group);
|
||||||
exit(1);
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
std::cerr << "lcpex: Aborting: Unknown error while setting identity context." << std::endl;
|
tty_logger.log(E_FATAL, "Aborting: Unknown error while setting identity context.");
|
||||||
exit(1);
|
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;
|
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!!!!
|
// execute the dang command, print to stdout, stderr (of parent), and dump to file for each!!!!
|
||||||
// (and capture exit code in parent)
|
// (and capture exit code in parent)
|
||||||
int exit_code = execvp( processed_command[0], processed_command );
|
int exit_code = execvp( processed_command[0], processed_command );
|
||||||
safe_perror("failed on execvp in child", ttyOrig );
|
tty_logger.log_to_json_file("E_FATAL", "failed on execvp in child", tty_user, tty_group, command, task_context, log_directory);
|
||||||
exit(exit_code);
|
tty_logger.log(E_FATAL, "failed on execvp in child");
|
||||||
|
_Exit(exit_code);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this does three things:
|
// this does three things:
|
||||||
@@ -99,8 +191,15 @@ int exec_pty(
|
|||||||
bool context_override,
|
bool context_override,
|
||||||
std::string context_user,
|
std::string context_user,
|
||||||
std::string context_group,
|
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
|
// initialize the terminal settings obj
|
||||||
struct termios ttyOrig;
|
struct termios ttyOrig;
|
||||||
|
|
||||||
@@ -108,20 +207,20 @@ int exec_pty(
|
|||||||
// before we fork the process, so that the child process will inherit the environment
|
// before we fork the process, so that the child process will inherit the environment
|
||||||
// from the parent process
|
// from the parent process
|
||||||
if ( environment_supplied ) {
|
if ( environment_supplied ) {
|
||||||
clearenv();
|
//clearenv();
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn our command string into something execvp can consume
|
// turn our command string into something execvp can consume
|
||||||
char ** processed_command = expand_env( command );
|
char ** processed_command = expand_env( command );
|
||||||
|
|
||||||
if ( stdout_log_fh == NULL ) {
|
if ( stdout_log_fh == NULL ) {
|
||||||
safe_perror( "Error opening STDOUT log file. Aborting.", &ttyOrig );
|
tty_logger.log_to_json_file("E_FATAL", "Error opening STDOUT log file. Aborting.", tty_user, tty_group, exec_command, task_context, log_directory);
|
||||||
exit( 1 );
|
throw std::runtime_error("Error opening STDOUT log file");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( stderr_log_fh == NULL ) {
|
if ( stderr_log_fh == NULL ) {
|
||||||
safe_perror( "Error opening STDERR log file. Aborting.", &ttyOrig );
|
tty_logger.log_to_json_file("E_FATAL", "Error opening STDERR log file. Aborting.", tty_user, tty_group, exec_command, task_context, log_directory);
|
||||||
exit( 1 );
|
throw std::runtime_error("Error opening STDERR log file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -129,16 +228,13 @@ int exec_pty(
|
|||||||
int fd_child_stderr_pipe[2];
|
int fd_child_stderr_pipe[2];
|
||||||
|
|
||||||
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
||||||
safe_perror( "child stderr pipe", &ttyOrig );
|
std::string error_message = "Failed to create stderr pipe: " + std::string(strerror(errno));
|
||||||
exit( 1 );
|
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
|
||||||
// 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 );
|
||||||
if ( fcntl( fd_child_stderr_pipe[READ_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
|
set_tty_cloexec_flag( fd_child_stderr_pipe[WRITE_END], task_context, log_directory );
|
||||||
|
|
||||||
// // same for stderr write
|
|
||||||
if ( fcntl( fd_child_stderr_pipe[WRITE_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
|
|
||||||
|
|
||||||
// status result basket for the parent process to capture the child's exit status
|
// status result basket for the parent process to capture the child's exit status
|
||||||
int status = 616;
|
int status = 616;
|
||||||
|
|
||||||
@@ -148,39 +244,52 @@ int exec_pty(
|
|||||||
struct winsize ws;
|
struct winsize ws;
|
||||||
|
|
||||||
/* Retrieve the attributes of terminal on which we are started */
|
/* Retrieve the attributes of terminal on which we are started */
|
||||||
if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
|
if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1){
|
||||||
safe_perror("tcgetattr", &ttyOrig);
|
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)
|
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 );
|
pid_t pid = ptyFork( &masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws );
|
||||||
|
|
||||||
switch( pid ) {
|
switch( pid ) {
|
||||||
case -1:
|
case -1:
|
||||||
{
|
{
|
||||||
// fork failed
|
// fork failed
|
||||||
safe_perror("ptyfork failure", &ttyOrig );
|
tty_logger.log(E_FATAL, "ptyfork failure: " + std::string(strerror(errno)));
|
||||||
exit(1);
|
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:
|
case 0:
|
||||||
{
|
{
|
||||||
// child process
|
// 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:
|
default:
|
||||||
{
|
{
|
||||||
// parent process
|
// parent process
|
||||||
// start ptyfork integration
|
// start ptyfork integration
|
||||||
ttySetRaw(STDIN_FILENO, &ttyOrig);
|
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
|
// We don't need WRITE_END in parent, close it now
|
||||||
close(fd_child_stderr_pipe[WRITE_END]);
|
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
|
// 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
|
// contains the byte count of the last read from the pipe
|
||||||
ssize_t byte_count;
|
ssize_t byte_count;
|
||||||
@@ -195,11 +304,11 @@ int exec_pty(
|
|||||||
watched_fds[0].events = POLLIN;
|
watched_fds[0].events = POLLIN;
|
||||||
|
|
||||||
// child pty to parent (stdout)
|
// child pty to parent (stdout)
|
||||||
watched_fds[1].fd = masterFd;
|
watched_fds[1].fd = master_fd_guard.get();
|
||||||
watched_fds[1].events = POLLIN;
|
watched_fds[1].events = POLLIN;
|
||||||
|
|
||||||
// child stderr to parent
|
// 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;
|
watched_fds[2].events = POLLIN;
|
||||||
|
|
||||||
// number of files poll() reports as ready
|
// number of files poll() reports as ready
|
||||||
@@ -211,11 +320,18 @@ int exec_pty(
|
|||||||
// loop until we've read all the data from the child process
|
// loop until we've read all the data from the child process
|
||||||
while ( ! break_out ) {
|
while ( ! break_out ) {
|
||||||
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
||||||
|
// after the poll() call, add a check to see if both pipes are closed
|
||||||
|
if (!(watched_fds[1].events & POLLIN) &&
|
||||||
|
!(watched_fds[2].events & POLLIN)) {
|
||||||
|
break_out = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (num_files_readable == -1) {
|
if (num_files_readable == -1) {
|
||||||
// error occurred in poll()
|
// error occurred in poll()
|
||||||
safe_perror("poll", &ttyOrig );
|
tty_logger.log(E_FATAL, "poll() failed: " + std::string(strerror(errno)));
|
||||||
exit(1);
|
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) {
|
if (num_files_readable == 0) {
|
||||||
@@ -230,9 +346,15 @@ int exec_pty(
|
|||||||
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
||||||
|
|
||||||
if (byte_count == -1) {
|
if (byte_count == -1) {
|
||||||
// error reading from pipe
|
if (errno == EAGAIN) { continue; } else {
|
||||||
safe_perror("read", &ttyOrig );
|
// error reading from pipe
|
||||||
exit(EXIT_FAILURE);
|
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) {
|
} else if (byte_count == 0) {
|
||||||
// reached EOF on one of the streams but not a HUP
|
// reached EOF on one of the streams but not a HUP
|
||||||
// we've read all we can this cycle, so go to the next fd in the for loop
|
// we've read all we can this cycle, so go to the next fd in the for loop
|
||||||
@@ -240,40 +362,89 @@ int exec_pty(
|
|||||||
} else {
|
} else {
|
||||||
// byte count was sane
|
// byte count was sane
|
||||||
// write to stdout,stderr
|
// write to stdout,stderr
|
||||||
|
|
||||||
if (this_fd == 0) {
|
if (this_fd == 0) {
|
||||||
// parent stdin received, write to child pty (stdin)
|
// parent stdin received, write to child pty (stdin)
|
||||||
write(masterFd, buf, byte_count);
|
write_all(masterFd, buf, byte_count);
|
||||||
} else if (this_fd == 1 ) {
|
} else if (this_fd == 1 ) {
|
||||||
// child pty sent some stuff, write to parent stdout and log
|
// child pty sent some stuff, write to parent stdout and log
|
||||||
write(stdout_log_fh->_fileno, buf, byte_count);
|
write_all(stdout_log_fh->_fileno, buf, byte_count);
|
||||||
write(STDOUT_FILENO, buf, byte_count);
|
write_all(STDOUT_FILENO, buf, byte_count);
|
||||||
} else if ( this_fd == 2 ){
|
} else if ( this_fd == 2 ){
|
||||||
//the child's stderr pipe sent some stuff, write to parent stderr and log
|
//the child's stderr pipe sent some stuff, write to parent stderr and log
|
||||||
write(stderr_log_fh->_fileno, buf, byte_count);
|
write_all(stderr_log_fh->_fileno, buf, byte_count);
|
||||||
write(STDERR_FILENO, buf, byte_count);
|
write_all(STDERR_FILENO, buf, byte_count);
|
||||||
} else {
|
} else {
|
||||||
// this should never happen
|
// this should never happen
|
||||||
perror("Logic error!");
|
tty_logger.log(E_FATAL, "Logic error: unexpected pipe index.");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (watched_fds[this_fd].revents & POLLERR) {
|
if (watched_fds[this_fd].revents & POLLERR) {
|
||||||
close(watched_fds[this_fd].fd);
|
if (this_fd == 1) {
|
||||||
//break_out = true;
|
master_fd_guard.reset(-1);
|
||||||
continue;
|
} else if (this_fd == 2) {
|
||||||
|
stderr_read_guard.reset(-1);
|
||||||
|
}
|
||||||
|
//close(watched_fds[this_fd].fd);
|
||||||
|
break_out = true;
|
||||||
|
//continue;
|
||||||
}
|
}
|
||||||
|
// if (watched_fds[this_fd].revents & POLLHUP) {
|
||||||
|
// // this pipe has hung up
|
||||||
|
// close(watched_fds[this_fd].fd);
|
||||||
|
// break_out = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
if (watched_fds[this_fd].revents & POLLHUP) {
|
if (watched_fds[this_fd].revents & POLLHUP) {
|
||||||
// this pipe has hung up
|
// this pipe has hung up
|
||||||
close(watched_fds[this_fd].fd);
|
// don't close the file descriptor yet, there might still be data to read
|
||||||
break_out = true;
|
// instead, remove the POLLIN event to avoid getting a POLLHUP event in the next poll() call
|
||||||
break;
|
watched_fds[this_fd].events &= ~POLLIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// wait for child to exit, capture status
|
// wait for child to exit, capture status
|
||||||
waitpid(pid, &status, 0);
|
waitpid(pid, &status, 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(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);
|
ttyResetExit( &ttyOrig);
|
||||||
if WIFEXITED(status) {
|
if WIFEXITED(status) {
|
||||||
return WEXITSTATUS(status);
|
return WEXITSTATUS(status);
|
||||||
@@ -282,5 +453,9 @@ int exec_pty(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
std::cerr << "[LCPEX TTY ERROR] " << ex.what() << std::endl;
|
||||||
|
return -999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ int exec_pty(
|
|||||||
bool context_override,
|
bool context_override,
|
||||||
std::string context_user,
|
std::string context_user,
|
||||||
std::string context_group,
|
std::string context_group,
|
||||||
bool environment_supplied
|
bool environment_supplied,
|
||||||
|
std::string task_context = "",
|
||||||
|
std::string log_directory = ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,13 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
#include <string>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <unistd.h> // For FdGuard
|
||||||
|
#include <fcntl.h> // For O_RDONLY, O_WRONLY, open()
|
||||||
|
#include "../lcpex/FileHandle.h" // Include the FdGuard header
|
||||||
|
|
||||||
Logger::Logger( int LOG_LEVEL, std::string mask )
|
Logger::Logger( int LOG_LEVEL, std::string mask )
|
||||||
{
|
{
|
||||||
@@ -26,6 +33,11 @@ Logger::Logger( int LOG_LEVEL, std::string mask )
|
|||||||
this->mask = mask;
|
this->mask = mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destructor added in the update
|
||||||
|
Logger::~Logger() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void Logger::log( int LOG_LEVEL, std::string msg )
|
void Logger::log( int LOG_LEVEL, std::string msg )
|
||||||
{
|
{
|
||||||
std::string ERR = "XXXX";
|
std::string ERR = "XXXX";
|
||||||
@@ -42,7 +54,7 @@ void Logger::log( int LOG_LEVEL, std::string msg )
|
|||||||
|
|
||||||
std::string s_msg = "[" + ERR + "] " + 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;
|
std::cerr << "[" << get_8601() << "] [" << ERR << "] " << "[" << this->mask << "] " << msg.c_str() << std::endl;
|
||||||
} else {
|
} 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 )
|
void Logger::log_task( int LOG_LEVEL, std::string task_name, std::string msg )
|
||||||
{
|
{
|
||||||
std::string final_msg = "[" + task_name + "] " + msg;
|
// Added memory management from the updated file
|
||||||
this->log( LOG_LEVEL, final_msg );
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,14 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <fcntl.h> // For O_RDONLY, O_WRONLY, open()
|
||||||
|
#include <ctime>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstdarg> // for va_list in FileGuard::printf
|
||||||
#include "../misc/helpers.h"
|
#include "../misc/helpers.h"
|
||||||
|
|
||||||
enum L_LVL {
|
enum L_LVL {
|
||||||
@@ -37,15 +43,29 @@ enum L_LVL {
|
|||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
public:
|
public:
|
||||||
Logger( int LOG_LEVEL, std::string mask );
|
// Constructor and Destructor
|
||||||
void log( int LOG_LEVEL, std::string msg );
|
Logger(int LOG_LEVEL, std::string mask);
|
||||||
void log_task( int LOG_LEVEL, std::string task_name, std::string msg );
|
~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:
|
private:
|
||||||
int LOG_LEVEL;
|
int LOG_LEVEL;
|
||||||
std::string mask;
|
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
|
#endif //REX_LOGGER_H
|
||||||
|
|||||||
@@ -96,19 +96,57 @@ std::string get_8601()
|
|||||||
* @brief Interpolates the environment variables in the input text
|
* @brief Interpolates the environment variables in the input text
|
||||||
*
|
*
|
||||||
* This function takes a string reference as input and replaces all occurrences of
|
* This function takes a string reference as input and replaces all occurrences of
|
||||||
* environment variables in the format `${VAR_NAME}` with their corresponding values.
|
* environment variables in the format `${VAR_NAME}` or `$VAR_NAME` with their corresponding values.
|
||||||
* If an environment variable is not set, it is replaced with an empty string.
|
* If an environment variable is not set, it is replaced with an empty string.
|
||||||
*
|
*
|
||||||
* @param text The input text to be processed
|
* @param text The input text to be processed
|
||||||
*/
|
*/
|
||||||
|
//void interpolate( std::string & text )
|
||||||
|
//{
|
||||||
|
// static std::regex env1( "\\$\\{([^}]+)\\}" );
|
||||||
|
// static std::regex env2( "\\$([^/]+)" ); // matches $VAR_NAME until a / is found
|
||||||
|
// std::smatch match;
|
||||||
|
// while ( std::regex_search( text, match, env1 ) || std::regex_search( text, match, env2 ) )
|
||||||
|
// {
|
||||||
|
// const char * s = getenv( match[1].str().c_str() );
|
||||||
|
// const std::string var( s == NULL ? "" : s );
|
||||||
|
// text.replace( match[0].first, match[0].second, var );
|
||||||
|
// }
|
||||||
|
//}
|
||||||
void interpolate( std::string & text )
|
void interpolate( std::string & text )
|
||||||
{
|
{
|
||||||
static std::regex env( "\\$\\{([^}]+)\\}" );
|
std::regex env1( "\\$\\{([^}]+)\\}" );
|
||||||
|
std::regex env2( "\\$([^/]+)" ); // matches $VAR_NAME until a / is found
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
while ( std::regex_search( text, match, env ) )
|
while ( std::regex_search( text, match, env1 ) || std::regex_search( text, match, env2 ) )
|
||||||
{
|
{
|
||||||
const char * s = getenv( match[1].str().c_str() );
|
const char * s = getenv( match[1].str().c_str() );
|
||||||
const std::string var( s == NULL ? "" : s );
|
const std::string var( s == NULL ? "" : s );
|
||||||
text.replace( match[0].first, match[0].second, var );
|
text.replace( match[0].first, match[0].second, var );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the absolute path from a relative path
|
||||||
|
*
|
||||||
|
* This function takes a relative path and returns the corresponding absolute path.
|
||||||
|
* The absolute path is obtained by calling the `realpath` function.
|
||||||
|
* If the `realpath` function returns a null pointer, an error message is printed to the standard error stream and an empty string is returned.
|
||||||
|
*
|
||||||
|
* @param relative_path The relative path to be converted to an absolute path
|
||||||
|
*
|
||||||
|
* @return The absolute path corresponding to the relative path
|
||||||
|
*/
|
||||||
|
std::string get_absolute_path(const std::string &relative_path)
|
||||||
|
{
|
||||||
|
char resolved_path[1024];
|
||||||
|
memset(resolved_path, 0, sizeof(resolved_path));
|
||||||
|
|
||||||
|
if( realpath( relative_path.c_str(), resolved_path) == nullptr ) {
|
||||||
|
std::cerr << "Error resolving path: " << relative_path << std::endl;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(resolved_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
#define REX_HELPERS_H
|
#define REX_HELPERS_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -51,4 +54,17 @@ std::string get_8601();
|
|||||||
|
|
||||||
const char * command2args( std::string input_string );
|
const char * command2args( std::string input_string );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the absolute path from a relative path
|
||||||
|
*
|
||||||
|
* This function takes a relative path and returns the corresponding absolute path.
|
||||||
|
* The absolute path is obtained by calling the `realpath` function.
|
||||||
|
* If the `realpath` function returns a null pointer, an error message is printed to the standard error stream and an empty string is returned.
|
||||||
|
*
|
||||||
|
* @param relative_path The relative path to be converted to an absolute path
|
||||||
|
*
|
||||||
|
* @return The absolute path corresponding to the relative path
|
||||||
|
*/
|
||||||
|
std::string get_absolute_path(const std::string &relative_path);
|
||||||
|
|
||||||
#endif //REX_HELPERS_H
|
#endif //REX_HELPERS_H
|
||||||
|
|||||||
@@ -354,8 +354,19 @@ void Task::execute( Conf * configuration )
|
|||||||
std::string environment_file = this->definition.get_environment_file();
|
std::string environment_file = this->definition.get_environment_file();
|
||||||
std::string logs_root = configuration->get_logs_path();
|
std::string logs_root = configuration->get_logs_path();
|
||||||
|
|
||||||
|
interpolate(task_name);
|
||||||
this->slog.log_task( E_DEBUG, task_name, "Using unit definition: \"" + task_name + "\"." );
|
this->slog.log_task( E_DEBUG, task_name, "Using unit definition: \"" + task_name + "\"." );
|
||||||
|
|
||||||
|
interpolate(command);
|
||||||
|
interpolate(shell_name);
|
||||||
|
interpolate(user);
|
||||||
|
interpolate(group);
|
||||||
|
interpolate(environment_file);
|
||||||
|
interpolate(new_working_dir);
|
||||||
|
interpolate(rectifier);
|
||||||
|
interpolate(logs_root);
|
||||||
|
|
||||||
|
|
||||||
// sanitize all path inputs from unit definition to be either absolute paths or relative to
|
// sanitize all path inputs from unit definition to be either absolute paths or relative to
|
||||||
// project_root
|
// project_root
|
||||||
|
|
||||||
@@ -459,7 +470,9 @@ void Task::execute( Conf * configuration )
|
|||||||
shell_definition.execution_arg,
|
shell_definition.execution_arg,
|
||||||
supply_environment,
|
supply_environment,
|
||||||
shell_definition.source_cmd,
|
shell_definition.source_cmd,
|
||||||
environment_file
|
environment_file,
|
||||||
|
task_name,
|
||||||
|
logs_root
|
||||||
);
|
);
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
@@ -528,7 +541,9 @@ void Task::execute( Conf * configuration )
|
|||||||
shell_definition.execution_arg,
|
shell_definition.execution_arg,
|
||||||
supply_environment,
|
supply_environment,
|
||||||
shell_definition.source_cmd,
|
shell_definition.source_cmd,
|
||||||
environment_file
|
environment_file,
|
||||||
|
task_name,
|
||||||
|
logs_root
|
||||||
);
|
);
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
@@ -580,7 +595,9 @@ void Task::execute( Conf * configuration )
|
|||||||
shell_definition.execution_arg,
|
shell_definition.execution_arg,
|
||||||
supply_environment,
|
supply_environment,
|
||||||
shell_definition.source_cmd,
|
shell_definition.source_cmd,
|
||||||
environment_file
|
environment_file,
|
||||||
|
task_name,
|
||||||
|
logs_root
|
||||||
);
|
);
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
|
|||||||
Reference in New Issue
Block a user