i/o for parent child good, but some fd is staying open
parent
d4a228f351
commit
31ed2feb7f
|
@ -1,10 +1,16 @@
|
||||||
#include "Sproc.h"
|
#include "Sproc.h"
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
|
#include <fstream>
|
||||||
#include "../Logger/Logger.h"
|
#include "../Logger/Logger.h"
|
||||||
|
#include "errno.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include "fcntl.h"
|
||||||
|
|
||||||
|
|
||||||
|
// converts username to UID
|
||||||
|
// returns false on failure
|
||||||
int username_to_uid( std::string username, int & uid )
|
int username_to_uid( std::string username, int & uid )
|
||||||
{
|
{
|
||||||
// assume failure unless proven otherwise
|
// assume failure unless proven otherwise
|
||||||
|
@ -22,6 +28,8 @@ int username_to_uid( std::string username, int & uid )
|
||||||
return r_code;
|
return r_code;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// converts group name to GID
|
||||||
|
// returns false on failure
|
||||||
int groupname_to_gid( std::string groupname, int & gid )
|
int groupname_to_gid( std::string groupname, int & gid )
|
||||||
{
|
{
|
||||||
int r_code = false;
|
int r_code = false;
|
||||||
|
@ -37,77 +45,266 @@ int groupname_to_gid( std::string groupname, int & gid )
|
||||||
return r_code;
|
return r_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// teebuf constructor
|
||||||
|
teebuf::teebuf(std::streambuf *sb1, std::streambuf *sb2): sb1(sb1), sb2(sb2)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// teebuf overflow method
|
||||||
|
int teebuf::overflow( int c )
|
||||||
|
{
|
||||||
|
if (c == EOF)
|
||||||
|
{
|
||||||
|
return !EOF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int const r1 = sb1->sputc(c);
|
||||||
|
int const r2 = sb2->sputc(c);
|
||||||
|
return r1 == EOF || r2 == EOF ? EOF : c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// teebuf sync method
|
||||||
|
int teebuf::sync()
|
||||||
|
{
|
||||||
|
int const r1 = sb1->pubsync();
|
||||||
|
int const r2 = sb2->pubsync();
|
||||||
|
return r1 == 0 && r2 == 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// teestream impl constructor
|
||||||
|
teestream::teestream(std::ostream &o1, std::ostream &o2) : std::ostream( &tbuf), tbuf( o1.rdbuf(), o2.rdbuf() )
|
||||||
|
{}
|
||||||
|
|
||||||
|
enum PIPE_FILE_DESCRIPTORS {
|
||||||
|
READ_FD = 0,
|
||||||
|
WRITE_FD = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum SPROC_RETURN_CODES {
|
||||||
|
SUCCESS = true,
|
||||||
|
UID_NOT_FOUND = -404,
|
||||||
|
GID_NOT_FOUND = -405,
|
||||||
|
SET_GID_FAILED = -401,
|
||||||
|
SET_UID_FAILED = -404,
|
||||||
|
EXEC_FAILURE_GENERAL = -666,
|
||||||
|
DUP2_FAILED = -999,
|
||||||
|
PIPE_FAILED = -998
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FORK_STATES {
|
||||||
|
FORK_FAILURE = -1,
|
||||||
|
CHILD = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
int set_identity_context( std::string task_name, std::string user_name, std::string group_name, Logger slog ) {
|
||||||
|
// the UID and GID for the username and groupname provided for context setting
|
||||||
|
int context_user_id;
|
||||||
|
int context_group_id;
|
||||||
|
|
||||||
|
// show the user something usable in debug mode
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as user '" + user_name + "'.");
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as group_name '" + group_name + "'.");
|
||||||
|
|
||||||
|
// convert username to UID
|
||||||
|
if ( username_to_uid(user_name, context_user_id ) )
|
||||||
|
{
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] UID of '" + user_name + "' is '" + std::to_string(context_user_id ) + "'." );
|
||||||
|
} else {
|
||||||
|
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up UID for '" + user_name + "'.");
|
||||||
|
return SPROC_RETURN_CODES::UID_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert group name to GID
|
||||||
|
if ( groupname_to_gid(group_name, context_group_id ) )
|
||||||
|
{
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] GID of '" + group_name + "' is '" + std::to_string(context_group_id ) + "'." );
|
||||||
|
} else {
|
||||||
|
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up GID for '" + group_name + "'.");
|
||||||
|
return SPROC_RETURN_CODES::GID_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setgid(context_group_id) == 0) {
|
||||||
|
slog.log(E_DEBUG,
|
||||||
|
"[ '" + task_name + "' ] Successfully set GID to '" + std::to_string(context_group_id) + "' (" +
|
||||||
|
group_name + ").");
|
||||||
|
} else {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Failed to set GID. Panicking.");
|
||||||
|
return SPROC_RETURN_CODES::SET_GID_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setuid(context_user_id) == 0) {
|
||||||
|
slog.log(E_DEBUG,
|
||||||
|
"[ '" + task_name + "' ] Successfully set UID to '" + std::to_string(context_user_id) + "' (" +
|
||||||
|
user_name + ").");
|
||||||
|
} else {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Failed to set UID. Panicking.");
|
||||||
|
return SPROC_RETURN_CODES::SET_UID_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SPROC_RETURN_CODES::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
/// Sproc::execute
|
/// Sproc::execute
|
||||||
///
|
///
|
||||||
/// \param input - The commandline input to execute.
|
/// \param input - The commandline input to execute.
|
||||||
/// \return - The return code of the execution of input in the calling shell.
|
/// \return - The return code of the execution of input in the calling shell.
|
||||||
int Sproc::execute( std::string shell, std::string environment_file, std::string run_as, std::string group, std::string command, int LOG_LEVEL, std::string task_name )
|
int Sproc::execute(std::string shell, std::string environment_file, std::string user_name, std::string group_name, std::string command, int LOG_LEVEL, std::string task_name )
|
||||||
{
|
{
|
||||||
|
// the logger
|
||||||
Logger slog = Logger( LOG_LEVEL, "_sproc" );
|
Logger slog = Logger( LOG_LEVEL, "_sproc" );
|
||||||
|
|
||||||
// the run_as_uid to capture the run_as_uid to run as
|
|
||||||
int run_as_uid;
|
|
||||||
int run_as_gid;
|
|
||||||
|
|
||||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as user '" + run_as + "'.");
|
|
||||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as group '" + group + "'.");
|
|
||||||
|
|
||||||
if ( username_to_uid( run_as, run_as_uid ) )
|
|
||||||
{
|
|
||||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] UID of '" + run_as + "' is '" + std::to_string( run_as_uid ) + "'." );
|
|
||||||
} else {
|
|
||||||
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up UID for '" + run_as + "'.");
|
|
||||||
return -404;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( groupname_to_gid( group, run_as_gid ) )
|
|
||||||
{
|
|
||||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] GID of '" + group + "' is '" + std::to_string( run_as_gid ) + "'." );
|
|
||||||
} else {
|
|
||||||
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up GID for '" + group + "'.");
|
|
||||||
return -404;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if you get this return value, it's an issue with this method and not your
|
// if you get this return value, it's an issue with this method and not your
|
||||||
// called executable.
|
// called executable.
|
||||||
int exit_code_raw = -666;
|
int exit_code_raw = SPROC_RETURN_CODES::EXEC_FAILURE_GENERAL;
|
||||||
|
|
||||||
// fork a process
|
// An explanation is due here:
|
||||||
int pid = fork();
|
// We want to log the STDOUT and STDERR of the child process, while still displaying them in the parent, in a way
|
||||||
|
// that does not interfere with, for example libcurses compatibility.
|
||||||
|
|
||||||
if ( pid == 0 )
|
// To simplify the handling of I/O, we will "tee" STDOUT and STDERR of the parent to respective log files.
|
||||||
{
|
|
||||||
// child process
|
|
||||||
if ( setgid( run_as_gid ) == 0 )
|
|
||||||
{
|
|
||||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] Successfully set GID to '" + std::to_string(run_as_gid) + "' (" + group + ")." );
|
|
||||||
} else {
|
|
||||||
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to set GID. Panicking." );
|
|
||||||
return -401;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( setuid( run_as_uid ) == 0 )
|
// Then fork(), and exec() the command to execute in the child, and link its STDOUT/STDERR to the parents' in
|
||||||
{
|
// realtime.
|
||||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] Successfully set UID to '" + std::to_string(run_as_uid) + "' (" + run_as + ")." );
|
|
||||||
} else {
|
|
||||||
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to set UID. Panicking." );
|
|
||||||
return -401;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string sourcer = shell + " -c \". " + environment_file + " && " + command + "\"";
|
// Since the parent has a Tee between STDOUT/STDOUT_LOG and another between STDERR/STDERR_LOG, simply piping the
|
||||||
|
// child STDOUT/STDERR to the parent STDOUT/STDERR should simplify I/O redirection happening here without
|
||||||
|
// potentially corrupting user interaction with TUIs in the processes. This should give us our log and our output
|
||||||
|
// in as hands off a way as possible with as few assumptions as possible, while still doing this in a somewhat C++-y
|
||||||
|
// way.
|
||||||
|
|
||||||
// I have no idea why this never shows up in the output!
|
|
||||||
|
// set up the "Tee" with the parent
|
||||||
|
std::string child_stdout_log_path = "./stdout.log";
|
||||||
|
std::string child_stderr_log_path = "./stderr.log";
|
||||||
|
|
||||||
|
std::ofstream stdout_log;
|
||||||
|
std::ofstream stderr_log;
|
||||||
|
|
||||||
|
stdout_log.open( child_stdout_log_path.c_str(), std::ofstream::out | std::ofstream::app );
|
||||||
|
stderr_log.open( child_stderr_log_path.c_str(), std::ofstream::out | std::ofstream::app );
|
||||||
|
|
||||||
|
// avoid cyclic dependencies between stdout and tee_out
|
||||||
|
std::ostream tmp_stdout( std::cout.rdbuf() );
|
||||||
|
std::ostream tmp_stderr( std::cerr.rdbuf() );
|
||||||
|
|
||||||
|
// writing to this ostream derivative will write to stdout log file and std::cout
|
||||||
|
teestream tee_out(tmp_stdout, stdout_log);
|
||||||
|
teestream tee_err(tmp_stderr, stderr_log);
|
||||||
|
|
||||||
|
// pop the cout/cerr buffers to the appropriate Tees' buffers
|
||||||
|
std::cout.rdbuf( tee_out.rdbuf() );
|
||||||
|
std::cerr.rdbuf( tee_err.rdbuf() );
|
||||||
|
// end - "set up the 'Tee' with the parent"
|
||||||
|
slog.log( E_INFO, "Tee Logging enabled for \"" + task_name + "\"");
|
||||||
|
|
||||||
|
|
||||||
|
// build the command to execute in the shell
|
||||||
|
std::string sourcer = ". " + environment_file + " && " + command;
|
||||||
|
// Show the user a debug print of what is going to be executed in the shell.
|
||||||
slog.log(E_DEBUG, "[ '" + task_name + "' ] Shell call for loading: ``" + sourcer + "``.");
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] Shell call for loading: ``" + sourcer + "``.");
|
||||||
|
|
||||||
exit_code_raw = system( sourcer.c_str() );
|
// file descriptors for parent/child i/o
|
||||||
exit( WEXITSTATUS( exit_code_raw ) );
|
int stdout_filedes[2];
|
||||||
} else if ( pid > 0 )
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] STDIN/STDOUT/STDERR file descriptors created." );
|
||||||
|
|
||||||
|
// man 3 pipe
|
||||||
|
if (pipe(stdout_filedes) == -1 ) {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] PIPE FAILED");
|
||||||
|
return SPROC_RETURN_CODES::PIPE_FAILED;
|
||||||
|
} else {
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] file descriptors piped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // avoids the need to take any explicit action within the child process to close file descriptors
|
||||||
|
// if (fcntl(stdout_filedes[READ_FD], F_SETFD, FD_CLOEXEC) == -1) {
|
||||||
|
// perror("fcntl");
|
||||||
|
// exit(1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fork a process
|
||||||
|
pid_t pid = fork();
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] Process forked. Reporting. (PID: " + std::to_string(pid) + ")" );
|
||||||
|
|
||||||
|
|
||||||
|
switch ( pid ) {
|
||||||
|
case FORK_STATES::FORK_FAILURE:
|
||||||
|
{
|
||||||
|
// fork failed
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Fork Failed.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FORK_STATES::CHILD:
|
||||||
|
{
|
||||||
|
// enter child process
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] Entering child process.");
|
||||||
|
|
||||||
|
while ((dup2(stdout_filedes[WRITE_FD], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
|
||||||
|
close( stdout_filedes[WRITE_FD] );
|
||||||
|
close( stdout_filedes[READ_FD] );
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] DUP2 on stdout_filedes[1]->STDOUT_FILENO in child.");
|
||||||
|
|
||||||
|
|
||||||
|
// set identity context
|
||||||
|
// set gid and uid
|
||||||
|
int context_status = set_identity_context(task_name, user_name, group_name, slog);
|
||||||
|
if (!(context_status)) {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Identity context switch failed.");
|
||||||
|
return context_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// exit_code_raw = system( sourcer.c_str() );
|
||||||
|
int ret = execl("/bin/sh", "/bin/sh", "-c", sourcer.c_str(), (char *) NULL);
|
||||||
|
|
||||||
|
// print something useful to debug with if execl fails
|
||||||
|
slog.log(E_FATAL, "ret code: " + std::to_string(ret) + "; errno: " + strerror(errno));
|
||||||
|
// exit child -- if this is executing, you've had a failure
|
||||||
|
|
||||||
|
exit(exit_code_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
{
|
{
|
||||||
// parent process
|
// parent process
|
||||||
while ( ( pid = waitpid( pid, &exit_code_raw, 0 ) ) == -1 ) {}
|
close(stdout_filedes[WRITE_FD]);
|
||||||
|
// ---
|
||||||
|
// clean up Tee
|
||||||
|
stdout_log.close();
|
||||||
|
stderr_log.close();
|
||||||
|
|
||||||
|
|
||||||
|
char buffer[1000] = {0};
|
||||||
|
std::cout.flush();
|
||||||
|
|
||||||
|
// read from fd until child completes
|
||||||
|
while ( 1 ) {
|
||||||
|
ssize_t count = read(stdout_filedes[READ_FD], buffer, sizeof(buffer));
|
||||||
|
if (count == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
} else {
|
} else {
|
||||||
// fork failed
|
perror("read");
|
||||||
slog.log( E_FATAL, "[ '" + task_name + "' ] Fork Failed");
|
exit(1);
|
||||||
|
}
|
||||||
|
} else if (count == 0) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
std::cout << buffer;
|
||||||
|
std::cout.flush();
|
||||||
|
memset(&buffer[0], 0, sizeof(buffer));
|
||||||
|
// handle_child_process_output(buffer, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while ((pid = waitpid(pid, &exit_code_raw, 0)) == -1) {}
|
||||||
|
//waitpid( pid, &exit_code_raw, 0 );
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return WEXITSTATUS( exit_code_raw );
|
return WEXITSTATUS( exit_code_raw );
|
||||||
}
|
}
|
|
@ -25,13 +25,42 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "../Logger/Logger.h"
|
#include "../Logger/Logger.h"
|
||||||
|
#include <streambuf>
|
||||||
|
#include "unistd.h"
|
||||||
|
|
||||||
// executes a subprocess and captures STDOUT, STDERR, and return code.
|
// executes a subprocess and captures STDOUT, STDERR, and return code.
|
||||||
// should be able to recieve path of binary to be executed as well as any parameters
|
// should be able to recieve path of binary to be executed as well as any parameters
|
||||||
class Sproc {
|
class Sproc {
|
||||||
public:
|
public:
|
||||||
// call the object. returnvalue is enum representing external execution attempt not binary exit code
|
// call the object. returnvalue is enum representing external execution attempt not binary exit code
|
||||||
static int execute( std::string shell, std::string enviornment_file, std::string run_as, std::string group, std::string command, int LOG_LEVEL, std::string task_name );
|
static int execute(std::string shell, std::string enviornment_file, std::string user_name, std::string group_name, std::string command, int LOG_LEVEL, std::string task_name );
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// mostly lifted from:
|
||||||
|
// http://wordaligned.org/articles/cpp-streambufs
|
||||||
|
// a teebuff implementation
|
||||||
|
class teebuf: public std::streambuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Construct a streambuf which tees output to both input
|
||||||
|
// streambufs.
|
||||||
|
teebuf(std::streambuf * sb1, std::streambuf * sb2);
|
||||||
|
private:
|
||||||
|
virtual int overflow(int c);
|
||||||
|
virtual int sync();
|
||||||
|
std::streambuf * sb1;
|
||||||
|
std::streambuf * sb2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// a simple helper class to create a tee stream from two input streams
|
||||||
|
class teestream : public std::ostream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Construct an ostream which tees output to the supplied ostreams.
|
||||||
|
teestream( std::ostream & o1, std::ostream & o2);
|
||||||
|
teebuf tbuf;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FTESTS_SPROC_H
|
#endif //FTESTS_SPROC_H
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo "Test var is: $TEST_VAR"
|
echo "TEST OUTPUT: Test var is: $TEST_VAR"
|
||||||
|
|
||||||
#dialog --stdout --title "Interact with me!" \
|
#dialog --stdout --title "Interact with me!" \
|
||||||
# --backtitle "This is user interaction." \
|
# --backtitle "This is user interaction." \
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set -a
|
set -a
|
||||||
|
|
||||||
echo "This is output from loading the variables file."
|
echo "variables file says hello and set a variable named TEST_VAR"
|
||||||
TEST_VAR="999"
|
TEST_VAR="999"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue