34 Commits

Author SHA1 Message Date
ODNADev
2118fb3b55 Remove accidental file 'tatus' 2025-08-04 13:46:43 +05:30
ODNADev
5713e6f88b lcpex enhancement - initial commit 2025-08-04 13:44:56 +05:30
phanes
879f07ec4b interpolate issue resolved 2024-02-10 16:44:21 -05:00
phanes
54d95aa582 fixing interpolation/environment var lifecycle issue 2024-02-10 08:43:37 -05:00
phanes
698ba9373c cleaning up interpolation 2024-02-09 01:20:17 -05:00
phanes
d13793954d possible fix for buffering issue 2024-02-08 15:12:59 -05:00
phanes
e9fd8c80bc possible fix for buffering issue 2024-02-08 15:04:52 -05:00
phanes
d15847dbfe working on issue with pty redirection, not easily reproducible 2024-02-08 07:23:01 -05:00
phanes
60a9f2bad2 working on issue with pty redirection, not easily reproducible 2024-02-08 07:04:28 -05:00
phanes
e03a7e67b8 ack 2024-02-08 06:56:35 -05:00
phanes
0561460b54 fixed nonsense with truncation 2024-02-08 06:15:06 -05:00
phanes
344cbfc56e fixed nonsense with truncation 2024-02-08 05:57:09 -05:00
bb324087a8 Update src/lcpex/helpers.h 2024-02-08 10:45:26 +00:00
0f35e752d3 Update src/lcpex/helpers.h 2024-02-08 10:44:33 +00:00
cddc271ac7 Update src/lcpex/liblcpex.cpp 2024-02-08 10:43:46 +00:00
2904e9dcd8 Update src/misc/helpers.h 2024-02-08 10:43:19 +00:00
7a816d5920 Update src/misc/helpers.cpp 2024-02-08 10:43:01 +00:00
f510f2b8cc Update src/lcpex/vpty/libclpex_tty.cpp 2024-02-08 10:41:58 +00:00
67ab74a8c8 Update src/lcpex/vpty/libclpex_tty.cpp 2024-02-08 10:40:57 +00:00
d4d5a2b30d Update src/lcpex/vpty/libclpex_tty.cpp 2024-02-08 10:34:17 +00:00
bb466185d0 Update src/lcpex/liblcpex.cpp 2024-02-08 10:24:51 +00:00
e16c663b5f Update src/lcpex/liblcpex.cpp 2024-02-08 10:15:38 +00:00
96e7cae5df Update src/lcpex/liblcpex.cpp 2024-02-08 10:10:28 +00:00
74bce85f1e Update src/lcpex/liblcpex.cpp 2024-02-08 10:02:53 +00:00
e9556c6075 Update src/lcpex/liblcpex.cpp 2024-02-08 09:53:32 +00:00
phanes
130d539f81 more interpolation 2024-02-07 01:42:30 -05:00
phanes
35248176cb interpolation for task values 2024-02-07 00:55:41 -05:00
5f8ec22166 Update src/config/Config.h 2024-02-07 05:25:59 +00:00
9a33086cdd Update src/config/Config.h 2024-02-07 05:25:10 +00:00
b439959413 Update 'src/config/Config.cpp' 2023-02-19 01:05:55 +00:00
phanes
d0fbd30f31 prep for point release 2023-02-17 03:21:24 -05:00
phanes
004addd2b4 error handling for log file handle 2023-02-16 03:49:56 -05:00
phanes
79bc82e365 shell attribute fix 2023-02-16 03:27:16 -05:00
phanes
c129f72b91 log path fix 2023-02-16 03:06:32 -05:00
34 changed files with 866 additions and 223 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ cmake-build-release
CMakeFiles CMakeFiles
CMakeLists.txt CMakeLists.txt
Makefile Makefile
sample/logs/*

View File

@@ -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)

View File

@@ -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;

View File

@@ -1,5 +1,8 @@
#!/bin/bash #!/usr/bin/bash
echo "This is an independent test. It does not depend on other tests." #
echo "Environment file check: TEST_VAR from environment file is set to: $TEST_VAR" echo test from script
#/usr/bin/dialog --title "This should be one argument" --inputbox "Enter your name:" 0 0
exit $?
env

View File

@@ -1,11 +1,8 @@
#!/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"
TEST_VAR="999" TEST_VAR="999"
TERM=xterm

View File

@@ -1,2 +0,0 @@
lcpex: setgid failed: Operation not permitted
lcpex: Aborting: Setting GID failed: bagira/bagira

View File

@@ -1 +0,0 @@
/usr/bin/bash: line 1: /home/phanes/development/internal/rex-rewrite/sample/environments/rex.variables: Permission denied

View File

@@ -1 +0,0 @@
/usr/bin/bash: line 1: /home/phanes/development/internal/rex-rewrite/sample/environments/rex.variables: Permission denied

0
sample/plans/atomic.plan Normal file → Executable file
View File

3
sample/plans/test.plan Normal file → Executable file
View File

@@ -1,5 +1,6 @@
{ {
"plan": [ "plan": [
{ "name": "independent test 1", "dependencies": [ null ] } { "name": "independent test 1", "dependencies": [ null ] },
{ "name": "independent test 2", "dependencies": [ null ] }
] ]
} }

2
sample/rex.config Normal file → Executable file
View File

@@ -1,6 +1,6 @@
{ {
"config": { "config": {
"project_root": "/home/phanes/development/internal/rex-rewrite/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",

0
sample/shells/shells.definitions Normal file → Executable file
View File

Binary file not shown.

22
sample/units/new.units Normal file → Executable file
View File

@@ -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"
} }

View File

@@ -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 );
@@ -134,6 +134,12 @@ void Conf::set_object_b(std::string keyname, bool & object_member, std::string f
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "' " + std::to_string(object_member)); this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "' " + std::to_string(object_member));
} }
void removeTrailingSlash(std::string &str) {
if (!str.empty() && str.back() == '/') {
str.pop_back();
}
}
/** /**
* @brief Prepend the project root to a relative path * @brief Prepend the project root to a relative path
* *
@@ -147,32 +153,11 @@ void Conf::set_object_b(std::string keyname, bool & object_member, std::string f
*/ */
std::string Conf::prepend_project_root( std::string relative_path) std::string Conf::prepend_project_root( std::string relative_path)
{ {
removeTrailingSlash(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
@@ -231,7 +216,7 @@ void Conf::load_shells() {
{ {
tmp_S.load_root( jbuff[index] ); tmp_S.load_root( jbuff[index] );
this->shells.push_back( tmp_S ); this->shells.push_back( tmp_S );
this->slog.log_task( E_DEBUG, "SHELLS", "Loaded shell: '" + tmp_S.name + "'" ); this->slog.log_task( E_DEBUG, "SHELLS", "Loaded shell: '" + tmp_S.name + "' (" + tmp_S.path + ")" );
} }
} }
@@ -258,7 +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
@@ -276,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.
@@ -287,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) {
@@ -298,13 +283,20 @@ Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(
} }
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 );
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( "logs_path", this->logs_path, filename ); interpolate( this->units_path );
set_object_s_derivedpath( "shells_path", this->shell_definitions_path, filename ); 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..." );

54
src/lcpex/FileHandle.h Normal file
View 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
View 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;
}

View File

@@ -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

View File

@@ -1,4 +1,69 @@
#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();
void signal_handler(int sig) {
std::string command = "signal_handler"; // Command context
// Create an instance of Logger (you can change the log level and mask accordingly)
// Log the signal handling
if (sig == SIGCHLD) {
// Log that SIGCHLD was received, but leave the exit status to the parent
logger.log_to_json_file("E_INFO", "SIGCHLD received. A child process ended.", user, group, command);
} else if (sig == SIGINT) {
logger.log_to_json_file("E_FATAL", "SIGINT received. Gracefully terminating...", user, group, command);
} else if (sig == SIGTERM) {
logger.log_to_json_file("E_FATAL", "SIGTERM received. Terminating...", user, group, command);
} else if (sig == SIGSEGV) {
logger.log_to_json_file("E_FATAL", "SIGSEGV received. Possible segmentation fault.", user, group, command);
} else {
logger.log_to_json_file("E_FATAL", "Unhandled signal received", user, group, command);
}
}
// Setup signal registrations
void setup_signal_handlers() {
struct sigaction sa;
sa.sa_handler = signal_handler; // <-- handler function
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
std::string command = "setup_signal_handlers"; // Command context
// SIGCHLD
if (sigaction(SIGCHLD, &sa, nullptr) == -1) {
std::string error_message = "Failed to set SIGCHLD handler: " + std::string(strerror(errno));
logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file
}
// SIGINT
if (sigaction(SIGINT, &sa, nullptr) == -1) {
std::string error_message = "Failed to set SIGINT handler: " + std::string(strerror(errno));
logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file
}
// SIGTERM
if (sigaction(SIGTERM, &sa, nullptr) == -1) {
std::string error_message = "Failed to set SIGTERM handler: " + std::string(strerror(errno));
logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file
}
// SIGSEGV
if (sigaction(SIGSEGV, &sa, nullptr) == -1) {
std::string error_message = "Failed to set SIGSEGV handler: " + std::string(strerror(errno));
logger.log_to_json_file("E_FATAL", error_message, user, group, command); // Log to JSON file
}
}
std::string prefix_generator( std::string prefix_generator(
std::string command, std::string command,
@@ -16,7 +81,8 @@ std::string prefix_generator(
prefix = shell_path; prefix = shell_path;
// if the shell takes an argument to execute a command, add it enclosed in quotes // if the shell takes an argument to execute a command, add it enclosed in quotes
if (shell_execution_arg != "") { if ( shell_execution_arg != "" )
{
// add the execution arg // add the execution arg
prefix += " " + shell_execution_arg + " "; prefix += " " + shell_execution_arg + " ";
} else { } else {
@@ -35,15 +101,19 @@ 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 << "prefix: " << prefix << std::endl;
// Log the message to JSON file
logger.log_to_json_file("E_INFO", "LAUNCHER: " + prefix, user, group, command);
//logger.log(E_INFO, "LAUNCHER: " + prefix);
return prefix; return prefix;
} }
int lcpex( int lcpex(
std::string command, std::string command,
std::string stdout_log_file, FILE * stdout_log_fh,
std::string stderr_log_file, FILE * stderr_log_fh,
bool context_override, bool context_override,
std::string context_user, std::string context_user,
std::string context_group, std::string context_group,
@@ -67,15 +137,16 @@ int lcpex(
environment_file_path environment_file_path
); );
command = prefix; command = prefix;
setup_signal_handlers();
// 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_file, stderr_log_file, 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 );
} }
// otherwise, we will use the execute function // otherwise, we will use the execute function
return execute( command, stdout_log_file, stderr_log_file, 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 );
} }
/** /**
@@ -89,10 +160,14 @@ int lcpex(
*/ */
void set_cloexec_flag(int fd) void set_cloexec_flag(int fd)
{ {
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);
throw std::runtime_error("fcntl(F_SETFD) failed for fd " + std::to_string(fd));
} }
} }
@@ -123,6 +198,13 @@ void set_cloexec_flag(int fd)
void run_child_process(bool context_override, const char* context_user, const char* context_group, char* processed_command[], int fd_child_stdout_pipe[], int fd_child_stderr_pipe[]) { void run_child_process(bool context_override, const char* context_user, const char* context_group, char* processed_command[], int fd_child_stdout_pipe[], int fd_child_stderr_pipe[]) {
while ((dup2(fd_child_stdout_pipe[WRITE_END], STDOUT_FILENO) == -1) && (errno == EINTR)) {} while ((dup2(fd_child_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);
@@ -130,43 +212,49 @@ 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 << "lcpex: 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);
_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; 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);
_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; 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);
_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; 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);
_exit(1);
break; break;
default: default:
std::cerr << "lcpex: 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);
_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);
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,
std::string stdout_log_file, FILE * stdout_log_fh,
std::string stderr_log_file, FILE * stderr_log_fh,
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
){ ){
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
@@ -179,25 +267,22 @@ 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();
} }
// open file handles to the two log files we need to create for each execution
FILE * stdout_log_fh = fopen( stdout_log_file.c_str(), "a+" );
FILE * stderr_log_fh = fopen( stderr_log_file.c_str(), "a+" );
// 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
int fd_child_stdout_pipe[2]; int fd_child_stdout_pipe[2];
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
@@ -207,15 +292,20 @@ int execute(
set_cloexec_flag( fd_child_stderr_pipe[WRITE_END] ); set_cloexec_flag( fd_child_stderr_pipe[WRITE_END] );
// 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);
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:
@@ -235,14 +325,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;
@@ -252,11 +351,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
@@ -268,10 +367,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);
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
@@ -284,9 +390,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) {
if (errno == EAGAIN) { continue; } else {
// error reading from pipe // error reading from pipe
perror("read"); logger.log(E_FATAL, "Error while reading: " + std::string(strerror(errno)));
logger.log_to_json_file("E_FATAL", "Error while reading from pipe", user, group, command);
stdout_read_guard.reset(-1);
stderr_read_guard.reset(-1);
exit(EXIT_FAILURE); 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
@@ -296,43 +408,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);
// close the log file handles // Drain the pipes before exiting
fclose(stdout_log_fh); while ((byte_count = read(stdout_read_guard.get(), buf, BUFFER_SIZE)) > 0) {
fclose(stderr_log_fh); write_all(stdout_log_fh->_fileno, buf, byte_count);
if WIFEXITED(status) { write_all(STDOUT_FILENO, buf, byte_count);
return WEXITSTATUS(status); }
} else { while ((byte_count = read(stderr_read_guard.get(), buf, BUFFER_SIZE)) > 0) {
return -617; write_all(stderr_log_fh->_fileno, buf, byte_count);
write_all(STDERR_FILENO, buf, byte_count);
}
int status;
pid_t pid = waitpid(-1, &status, WNOHANG); // Non-blocking wait for child process
if (pid > 0) { // If a child process has terminated
// Check if the child process exited normally
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
logger.log(E_INFO, "Child exited with status " + std::to_string(exit_code));
logger.log_to_json_file("E_INFO", "Child exited with status " + std::to_string(exit_code), user, group, command);
}
// Check if the child process was terminated by a signal
else if (WIFSIGNALED(status)) {
int signal_number = WTERMSIG(status);
logger.log(E_FATAL, "Process terminated by signal: " + std::to_string(signal_number));
logger.log_to_json_file("E_FATAL", "Process terminated by signal: " + std::to_string(signal_number), user, group, command);
// Reset signal handler to default before exiting
signal(signal_number, SIG_DFL);
// Return 128 + signal number as the exit code for signal termination
return 128 + signal_number; // POSIX exit code format
}
// Handle unexpected exit conditions
else {
logger.log(E_WARN, "Unknown child exit condition.");
logger.log_to_json_file("E_WARN", "Unknown child exit condition", user, group, command);
} }
} }
} }
}
return 0;
} catch (const std::exception& ex) {
std::cerr << "[LCPEX ERROR] " << ex.what() << std::endl;
return -999;
}
} }

View File

@@ -39,8 +39,8 @@
*/ */
int execute( int execute(
std::string command, std::string command,
std::string stdout_log_file, FILE * stdout_log_fh,
std::string stderr_log_file, FILE * stderr_log_fh,
bool context_override, bool context_override,
std::string context_user, std::string context_user,
std::string context_group, std::string context_group,
@@ -76,8 +76,8 @@ int execute(
*/ */
int lcpex( int lcpex(
std::string command, std::string command,
std::string stdout_log_file, FILE * stdout_log_fh,
std::string stderr_log_file, FILE * stderr_log_fh,
bool context_override, bool context_override,
std::string context_user, std::string context_user,
std::string context_group, std::string context_group,

View File

@@ -1,5 +1,7 @@
#include "string_expansion.h" #include "string_expansion.h"
// convert a string to a char** representing our artificial argv to be consumed by execvp // convert a string to a char** representing our artificial argv to be consumed by execvp
char ** expand_env(const std::string& var, int flags ) char ** expand_env(const std::string& var, int flags )
{ {

View File

@@ -1,4 +1,67 @@
#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();
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);
} else if (sig == SIGINT) {
tty_logger.log_to_json_file("E_FATAL", "SIGINT received. Gracefully terminating...", tty_user, tty_group, command);
} else if (sig == SIGTERM) {
tty_logger.log_to_json_file("E_FATAL", "SIGTERM received. Terminating...", tty_user, tty_group, command);
} else if (sig == SIGSEGV) {
tty_logger.log_to_json_file("E_FATAL", "SIGSEGV received. Possible segmentation fault.", tty_user, tty_group, command);
} else {
tty_logger.log_to_json_file("E_FATAL", "Unhandled signal received", tty_user, tty_group, command);
}
}
// Setup signal registrations
void setup_tty_signal_handlers() {
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); // 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); // 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); // 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); // Log to JSON file
}
}
/** /**
* @brief Safely prints a message and exits the program * @brief Safely prints a message and exits the program
@@ -11,13 +74,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::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);
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 command = "safe_perror"; // Command context
tty_logger.log_to_json_file("E_FATAL", msg, tty_user, tty_group, command); // 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
@@ -44,12 +120,14 @@ void safe_perror( const char * msg, struct termios * ttyOrig )
*/ */
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 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,33 +137,40 @@ 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);
_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);
_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);
_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);
_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);
_exit(1);
break; break;
} }
} }
// 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);
exit(exit_code); tty_logger.log(E_FATAL, "failed on execvp in child");
_Exit(exit_code);
} }
// this does three things: // this does three things:
@@ -94,13 +179,18 @@ void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[],
// - TEE child stdout/stderr to parent stdout/stderr // - TEE child stdout/stderr to parent stdout/stderr
int exec_pty( int exec_pty(
std::string command, std::string command,
std::string stdout_log_file, FILE * stdout_log_fh,
std::string stderr_log_file, FILE * stderr_log_fh,
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
) { ) {
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);
// Setup signal handlers
setup_tty_signal_handlers();
// initialize the terminal settings obj // initialize the terminal settings obj
struct termios ttyOrig; struct termios ttyOrig;
@@ -108,30 +198,34 @@ 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 ) {
tty_logger.log_to_json_file("E_FATAL", "Error opening STDOUT log file. Aborting.", tty_user, tty_group, exec_command);
throw std::runtime_error("Error opening STDOUT log file");
}
if ( stderr_log_fh == NULL ) {
tty_logger.log_to_json_file("E_FATAL", "Error opening STDERR log file. Aborting.", tty_user, tty_group, exec_command);
throw std::runtime_error("Error opening STDERR log file");
}
// open file handles to the two log files we need to create for each execution
FILE * stdout_log_fh = fopen( stdout_log_file.c_str(), "a+" );
FILE * stderr_log_fh = fopen( stderr_log_file.c_str(), "a+" );
// create the pipes for the child process to write and read from using its stderr // create the pipes for the child process to write and read from using its stderr
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);
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
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[READ_END] );
set_tty_cloexec_flag( fd_child_stderr_pipe[WRITE_END] );
// // 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;
@@ -141,39 +235,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);
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);
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);
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 );
} }
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;
@@ -188,11 +295,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
@@ -204,11 +311,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);
ttyResetExit( &ttyOrig);
throw std::runtime_error("poll() failed");
} }
if (num_files_readable == 0) { if (num_files_readable == 0) {
@@ -223,9 +337,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) {
if (errno == EAGAIN) { continue; } else {
// error reading from pipe // 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);
ttyResetExit( &ttyOrig);
stderr_read_guard.reset(-1);
master_fd_guard.reset(-1);
exit(EXIT_FAILURE); 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
@@ -233,43 +353,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);
// close the log file handles while ((byte_count = read(master_fd_guard.get(), buf, BUFFER_SIZE)) > 0) {
fclose(stdout_log_fh); write_all(stdout_log_fh->_fileno, buf, byte_count);
fclose(stderr_log_fh); 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);
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);
// 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);
}
}
ttyResetExit( &ttyOrig); ttyResetExit( &ttyOrig);
if WIFEXITED(status) { if WIFEXITED(status) {
return WEXITSTATUS(status); return WEXITSTATUS(status);
@@ -278,5 +444,9 @@ int exec_pty(
} }
} }
} }
return 0;
} catch (const std::exception& ex) {
std::cerr << "[LCPEX TTY ERROR] " << ex.what() << std::endl;
return -999;
}
} }

View File

@@ -38,8 +38,8 @@
*/ */
int exec_pty( int exec_pty(
std::string command, std::string command,
std::string stdout_log_file, FILE * stdout_log_fh,
std::string stderr_log_file, FILE * stderr_log_fh,
bool context_override, bool context_override,
std::string context_user, std::string context_user,
std::string context_group, std::string context_group,

View File

@@ -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,140 @@ 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, 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
std::string timestamp = get_current_timestamp();
std::string 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);
}
}
} // namespace lcpex

View File

@@ -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,28 @@ 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, 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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -307,7 +307,7 @@ bool createDirectory(const std::string& path) {
return true; return true;
} }
bool Task::prepare_logs( std::string task_name, std::string logs_root, std::string timestamp ) bool Task::prepare_logs( std::string task_name, std::string logs_root )
{ {
std::string full_path = logs_root + "/" + task_name; std::string full_path = logs_root + "/" + task_name;
bool ret = false; bool ret = false;
@@ -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
@@ -407,19 +418,24 @@ void Task::execute( Conf * configuration )
logs_root = configuration->get_project_root() + "/" + logs_root; logs_root = configuration->get_project_root() + "/" + logs_root;
} }
std::string timestamp = get_8601();
// set these first so the pre-execution logs get there. // set these first so the pre-execution logs get there.
/* /*
* create the logs dir here * create the logs dir here
*/ */
if (! this->prepare_logs( task_name, logs_root, timestamp ) ) if (! this->prepare_logs( task_name, logs_root ) )
{ {
throw TaskException("Could not prepare logs for task execution at '" + logs_root + "'."); throw TaskException("Could not prepare logs for task execution at '" + logs_root + "'.");
} }
std::string stdout_log_file = logs_root + "/" + timestamp + ".stdout.log"; std::string timestamp = get_8601();
std::string stderr_log_file = logs_root + "/" + timestamp + ".stderr.log"; std::string stdout_log_file = logs_root + "/" + task_name + "/" + timestamp + ".stdout.log";
std::string stderr_log_file = logs_root + "/" + task_name + "/" + timestamp + ".stderr.log";
// open file handles to the two log files we need to create for each execution
FILE * stdout_log_fh = fopen( stdout_log_file.c_str(), "a+" );
FILE * stderr_log_fh = fopen( stderr_log_file.c_str(), "a+" );
// check if working directory is to be set // check if working directory is to be set
if ( override_working_dir ) if ( override_working_dir )
@@ -443,8 +459,8 @@ void Task::execute( Conf * configuration )
int return_code = lcpex( int return_code = lcpex(
command, command,
stdout_log_file, stdout_log_fh,
stderr_log_file, stderr_log_fh,
set_user_context, set_user_context,
user, user,
group, group,
@@ -457,7 +473,6 @@ void Task::execute( Conf * configuration )
environment_file environment_file
); );
// ********************************************** // **********************************************
// d[0] Error Code Check // d[0] Error Code Check
// ********************************************** // **********************************************
@@ -513,8 +528,8 @@ void Task::execute( Conf * configuration )
this->slog.log_task( E_INFO, task_name, "Executing rectification: " + rectifier + "." ); this->slog.log_task( E_INFO, task_name, "Executing rectification: " + rectifier + "." );
int rectifier_error = lcpex( int rectifier_error = lcpex(
rectifier, rectifier,
stdout_log_file, stdout_log_fh,
stderr_log_file, stderr_log_fh,
set_user_context, set_user_context,
user, user,
group, group,
@@ -565,8 +580,8 @@ void Task::execute( Conf * configuration )
int retry_code = lcpex( int retry_code = lcpex(
command, command,
stdout_log_file, stdout_log_fh,
stderr_log_file, stderr_log_fh,
set_user_context, set_user_context,
user, user,
group, group,
@@ -618,4 +633,7 @@ void Task::execute( Conf * configuration )
// end d[1] Rectify Check // end d[1] Rectify Check
// ********************************************** // **********************************************
} }
// close the log file handles
fclose(stdout_log_fh);
fclose(stderr_log_fh);
} }

View File

@@ -47,7 +47,7 @@ class Task
// the readiness of this task to execute // the readiness of this task to execute
bool defined; bool defined;
bool prepare_logs( std::string task_name, std::string logs_root, std::string timestamp ); bool prepare_logs( std::string task_name, std::string logs_root );
public: public:

View File

@@ -54,11 +54,11 @@ int Shell::load_root( Json::Value loader_root )
throw ShellException("No execution_arg attribute specified when loading a shell definition."); throw ShellException("No execution_arg attribute specified when loading a shell definition.");
} }
if ( loader_root.isMember("execution_arg") ) if ( loader_root.isMember("source_cmd") )
{ {
this->execution_arg = loader_root.get( "execution_arg", errmsg ).asString(); this->source_cmd = loader_root.get( "source_cmd", errmsg ).asString();
} else { } else {
throw ShellException("No execution_arg attribute specified when loading a shell definition."); throw ShellException("No source_cmd attribute specified when loading a shell definition.");
} }
return 0; return 0;
} }

View File

@@ -129,7 +129,7 @@ void Suite::get_units_from_dir( std::vector<std::string> * files, std::string pa
// you want here. Something like: // you want here. Something like:
if ( strstr( hFile->d_name, ".units" )) if ( strstr( hFile->d_name, ".units" ))
{ {
std::string full_path = path + hFile->d_name; std::string full_path = path + "/" + hFile->d_name;
files->push_back( full_path ); files->push_back( full_path );
} }
} }