lcpex rewrite - alpha
This commit is contained in:
70
src/lcpex/Contexts.cpp
Normal file
70
src/lcpex/Contexts.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "Contexts.h"
|
||||
|
||||
|
||||
// converts username to UID
|
||||
int username_to_uid( std::string username, int & uid )
|
||||
{
|
||||
// assume failure unless proven otherwise
|
||||
int r_code = false;
|
||||
|
||||
struct passwd * pw;
|
||||
if ( ( pw = getpwnam( username.c_str() ) ) != NULL )
|
||||
{
|
||||
// successful user lookup
|
||||
r_code = true;
|
||||
uid = pw->pw_uid;
|
||||
} else {
|
||||
// failed lookup, do nothing, assumed failure
|
||||
}
|
||||
return r_code;
|
||||
};
|
||||
|
||||
// converts group name to GID
|
||||
int groupname_to_gid( std::string groupname, int & gid )
|
||||
{
|
||||
int r_code = false;
|
||||
|
||||
struct group * gp;
|
||||
if ( ( gp = getgrnam( groupname.c_str() ) ) != NULL )
|
||||
{
|
||||
r_code = true;
|
||||
gid = gp->gr_gid;
|
||||
} else {
|
||||
// failed lookup, do nothing, assumed failure
|
||||
}
|
||||
return r_code;
|
||||
}
|
||||
|
||||
// SET PROCESS TO A CERTAIN IDENTITY CONTEXT
|
||||
int set_identity_context( std::string user_name, std::string group_name ) {
|
||||
// the UID and GID for the username and groupname provided for context setting
|
||||
int context_user_id;
|
||||
int context_group_id;
|
||||
|
||||
int res = 0;
|
||||
|
||||
// convert username to UID
|
||||
if (! ( res = username_to_uid(user_name, context_user_id ) ) )
|
||||
{
|
||||
return ERROR_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
// convert group name to GID
|
||||
if (! ( res = groupname_to_gid(group_name, context_group_id ) ) )
|
||||
{
|
||||
return ERROR_NO_SUCH_GROUP;
|
||||
}
|
||||
|
||||
if ( ( res = setgid(context_group_id) ) ) {
|
||||
perror("lcpex: setgid failed");
|
||||
return ERROR_SETGID_FAILED;
|
||||
}
|
||||
|
||||
if ( ( res = setuid(context_user_id) ) ) {
|
||||
perror("lcpex: setuid failed");
|
||||
return ERROR_SETUID_FAILED;
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
69
src/lcpex/Contexts.h
Normal file
69
src/lcpex/Contexts.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef LCPEX_CONTEXTS_H
|
||||
#define LCPEX_CONTEXTS_H
|
||||
|
||||
#include <string>
|
||||
#include <csignal>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <iostream>
|
||||
|
||||
enum IDENTITY_CONTEXT_ERRORS {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_NO_SUCH_USER,
|
||||
ERROR_NO_SUCH_GROUP,
|
||||
ERROR_NO_SUCH_USER_IN_GROUP,
|
||||
ERROR_SETGID_FAILED,
|
||||
ERROR_SETUID_FAILED,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Converts a username to a user ID (UID)
|
||||
*
|
||||
* @param username The username to convert
|
||||
* @param uid A reference to the resulting UID
|
||||
*
|
||||
* @return A boolean indicating whether the conversion was successful
|
||||
*
|
||||
* This function takes a username as a string and returns the corresponding UID in the `uid` parameter.
|
||||
* The function returns a boolean indicating whether the conversion was successful.
|
||||
* If the username is not found, the function returns false and the value of `uid` is unchanged.
|
||||
*/
|
||||
int username_to_uid( std::string username, int & uid );
|
||||
|
||||
|
||||
/**
|
||||
* @brief Converts a group name to a group ID (GID)
|
||||
*
|
||||
* @param groupname The group name to convert
|
||||
* @param gid A reference to the resulting GID
|
||||
*
|
||||
* @return A boolean indicating whether the conversion was successful
|
||||
*
|
||||
* This function takes a group name as a string and returns the corresponding GID in the `gid` parameter.
|
||||
* The function returns a boolean indicating whether the conversion was successful.
|
||||
* If the group name is not found, the function returns false and the value of `gid` is unchanged.
|
||||
*/
|
||||
int groupname_to_gid( std::string groupname, int & gid );
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the execution context for a process
|
||||
*
|
||||
* @param user_name The username to use for the execution context
|
||||
* @param group_name The group name to use for the execution context
|
||||
*
|
||||
* @return An error code indicating the result of the context setting
|
||||
*
|
||||
* This function takes a username and group name as input and sets the execution context for the process.
|
||||
* The function converts the username and group name to UID and GID respectively, and sets the process's
|
||||
* user and group identity using the setuid() and setgid() functions.
|
||||
*
|
||||
* If either the username or group name is not found, the function returns `ERROR_NO_SUCH_USER` or `ERROR_NO_SUCH_GROUP` respectively.
|
||||
* If the call to setgid() fails, the function returns `ERROR_SETGID_FAILED`.
|
||||
* If the call to setuid() fails, the function returns `ERROR_SETUID_FAILED`.
|
||||
* If the context setting is successful, the function returns `ERROR_NONE`.
|
||||
*/
|
||||
int set_identity_context( std::string user_name, std::string group_name );
|
||||
|
||||
|
||||
#endif //LCPEX_CONTEXTS_H
|
||||
21
src/lcpex/helpers.h
Normal file
21
src/lcpex/helpers.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef LCPEX_HELPERS_H
|
||||
#define LCPEX_HELPERS_H
|
||||
|
||||
// helper for sanity
|
||||
enum PIPE_ENDS {
|
||||
READ_END = 0,
|
||||
WRITE_END = 1
|
||||
};
|
||||
|
||||
// helper for sanity
|
||||
// these do NOT represent fd values
|
||||
enum CHILD_PIPE_NAMES {
|
||||
STDOUT_READ = 0,
|
||||
STDERR_READ = 1
|
||||
};
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
|
||||
|
||||
#endif //LCPEX_HELPERS_H
|
||||
338
src/lcpex/liblcpex.cpp
Normal file
338
src/lcpex/liblcpex.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
#include "liblcpex.h"
|
||||
|
||||
std::string prefix_generator(
|
||||
std::string command,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
) {
|
||||
std::string prefix = "";
|
||||
if ( is_shell_command ) {
|
||||
|
||||
// it's a shell command, so we need to set up a shell execution of the command
|
||||
prefix = shell_path;
|
||||
|
||||
// if the shell takes an argument to execute a command, add it enclosed in quotes
|
||||
if (shell_execution_arg != "") {
|
||||
// add the execution arg
|
||||
prefix += " " + shell_execution_arg + " ";
|
||||
} else {
|
||||
// no execution arg, so add a space
|
||||
prefix += " ";
|
||||
}
|
||||
|
||||
if (supply_environment) {
|
||||
// add the source subcommand
|
||||
prefix += "'" + shell_source_subcommand + " " + environment_file_path + " && " + command + "'";
|
||||
} else {
|
||||
// no environment supplied, so just add the command
|
||||
prefix += "'" + command + "'";
|
||||
}
|
||||
} else {
|
||||
// it's not a shell command, so we can just execute it directly
|
||||
prefix = command;
|
||||
}
|
||||
std::cout << "prefix: " << prefix << std::endl;
|
||||
return prefix;
|
||||
}
|
||||
|
||||
|
||||
int lcpex(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool force_pty,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
) {
|
||||
|
||||
// generate the prefix
|
||||
std::string prefix = prefix_generator(
|
||||
command,
|
||||
is_shell_command,
|
||||
shell_path,
|
||||
shell_execution_arg,
|
||||
supply_environment,
|
||||
shell_source_subcommand,
|
||||
environment_file_path
|
||||
);
|
||||
command = prefix;
|
||||
|
||||
// if we are forcing a pty, then we will use the vpty library
|
||||
if( force_pty )
|
||||
{
|
||||
return exec_pty( command, stdout_log_file, stderr_log_file, context_override, context_user, context_group, supply_environment );
|
||||
}
|
||||
|
||||
// otherwise, we will use the execute function
|
||||
return execute( command, stdout_log_file, stderr_log_file, context_override, context_user, context_group, supply_environment );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the close-on-exec flag of a file descriptor
|
||||
*
|
||||
* This function uses the `fcntl` function to set the close-on-exec flag
|
||||
* of the specified file descriptor `fd`. If the `fcntl` call fails,
|
||||
* the function will print an error message to `stderr` and exit with status 1.
|
||||
*
|
||||
* @param fd The file descriptor for which to set the close-on-exec flag
|
||||
*/
|
||||
void set_cloexec_flag(int fd)
|
||||
{
|
||||
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
|
||||
{
|
||||
perror("fcntl");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function is executed by the child process to run a shell command
|
||||
*
|
||||
* @param context_override Indicates whether to override the current execution context
|
||||
* @param context_user The user to switch to for execution context
|
||||
* @param context_group The group to switch to for execution context
|
||||
* @param processed_command The command to be executed, after processing
|
||||
* @param fd_child_stdout_pipe The file descriptor for the child process's standard output pipe
|
||||
* @param fd_child_stderr_pipe The file descriptor for the child process's standard error pipe
|
||||
*
|
||||
* The run_child_process() function takes the parameters context_override, context_user, context_group, processed_command,
|
||||
* fd_child_stdout_pipe and fd_child_stderr_pipe.
|
||||
*
|
||||
* If context_override is set to true, the child process will run under the specified context_user and context_group.
|
||||
* If either the context_user or context_group does not exist, an error message will be displayed and the process will exit.
|
||||
*
|
||||
* The function first redirects the child process's standard output and standard error to pipes.
|
||||
*
|
||||
* If context_override is set to true, the child process sets its identity context using the set_identity_context() function.
|
||||
* If an error occurs while setting the identity context, a message will be displayed and the process will exit.
|
||||
*
|
||||
* Finally, the child process calls execvp() with the processed_command to run the shell command.
|
||||
* If the execvp() function fails, an error message is displayed.
|
||||
*/
|
||||
void run_child_process(bool context_override, const char* context_user, const char* context_group, char* processed_command[], int fd_child_stdout_pipe[], int fd_child_stderr_pipe[]) {
|
||||
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)) {}
|
||||
|
||||
if ( context_override ) {
|
||||
int context_result = set_identity_context(context_user, context_group);
|
||||
switch (context_result) {
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
||||
std::cerr << "lcpex: Aborting: context user not found: " << context_user << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
||||
std::cerr << "lcpex: Aborting: context group not found: " << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "lcpex: Aborting: Unknown error while setting identity context." << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int exit_code = execvp(processed_command[0], processed_command);
|
||||
perror("failed on execvp in child");
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
|
||||
int execute(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
){
|
||||
// this does three things:
|
||||
// - execute a dang string as a subprocess command
|
||||
// - capture child stdout/stderr to respective log files
|
||||
// - TEE child stdout/stderr to parent stdout/stderr
|
||||
|
||||
// turn our command string into something execvp can consume
|
||||
char ** processed_command = expand_env(command );
|
||||
|
||||
// if the user chose to supply the environment, then we need to clear the environment
|
||||
// before we fork the process, so that the child process will inherit the environment
|
||||
// from the parent process
|
||||
if ( environment_supplied ) {
|
||||
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
|
||||
int fd_child_stdout_pipe[2];
|
||||
int fd_child_stderr_pipe[2];
|
||||
|
||||
if ( pipe(fd_child_stdout_pipe ) == -1 ) {
|
||||
perror( "child stdout pipe" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
||||
perror( "child stderr pipe" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// using O_CLOEXEC to ensure that the child process closes the file descriptors
|
||||
set_cloexec_flag( fd_child_stdout_pipe[READ_END] );
|
||||
set_cloexec_flag( fd_child_stderr_pipe[READ_END] );
|
||||
set_cloexec_flag( fd_child_stdout_pipe[WRITE_END] );
|
||||
set_cloexec_flag( fd_child_stderr_pipe[WRITE_END] );
|
||||
|
||||
// status result basket for the parent process to capture the child's exit status
|
||||
int status = 616;
|
||||
pid_t pid = fork();
|
||||
|
||||
switch( pid ) {
|
||||
case -1:
|
||||
{
|
||||
// fork failed
|
||||
perror("fork failure");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
case 0:
|
||||
{
|
||||
// child process
|
||||
run_child_process(
|
||||
context_override,
|
||||
context_user.c_str(),
|
||||
context_group.c_str(),
|
||||
processed_command,
|
||||
fd_child_stdout_pipe,
|
||||
fd_child_stderr_pipe
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// parent process
|
||||
|
||||
// The parent process has no need to access the entrance to the pipe, so fd_child_*_pipe[1|0] should be closed
|
||||
// within that process too:
|
||||
close(fd_child_stdout_pipe[WRITE_END]);
|
||||
close(fd_child_stderr_pipe[WRITE_END]);
|
||||
|
||||
// attempt to write to stdout,stderr from child as well as to write each to file
|
||||
char buf[BUFFER_SIZE];
|
||||
|
||||
// contains the byte count of the last read from the pipe
|
||||
ssize_t byte_count;
|
||||
|
||||
// watched_fds for poll() to wait on
|
||||
struct pollfd watched_fds[2];
|
||||
|
||||
// populate the watched_fds array
|
||||
|
||||
// child STDOUT to parent
|
||||
watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].fd = fd_child_stdout_pipe[READ_END];
|
||||
watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].events = POLLIN;
|
||||
|
||||
// child STDERR to parent
|
||||
watched_fds[CHILD_PIPE_NAMES::STDERR_READ].fd = fd_child_stderr_pipe[READ_END];
|
||||
watched_fds[CHILD_PIPE_NAMES::STDERR_READ].events = POLLIN;
|
||||
|
||||
// number of files poll() reports as ready
|
||||
int num_files_readable;
|
||||
|
||||
// loop flag
|
||||
bool break_out = false;
|
||||
|
||||
// loop until we've read all the data from the child process
|
||||
while ( ! break_out ) {
|
||||
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
||||
if (num_files_readable == -1) {
|
||||
// error occurred in poll()
|
||||
perror("poll");
|
||||
exit(1);
|
||||
}
|
||||
if (num_files_readable == 0) {
|
||||
// poll reports no files readable
|
||||
break_out = true;
|
||||
break;
|
||||
}
|
||||
for (int this_fd = 0; this_fd < 2; this_fd++) {
|
||||
if (watched_fds[this_fd].revents & POLLIN) {
|
||||
// this pipe is readable
|
||||
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
||||
|
||||
if (byte_count == -1) {
|
||||
// error reading from pipe
|
||||
perror("read");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (byte_count == 0) {
|
||||
// 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
|
||||
continue;
|
||||
} else {
|
||||
// byte count was sane
|
||||
// write to stdout,stderr
|
||||
if (this_fd == CHILD_PIPE_NAMES::STDOUT_READ) {
|
||||
// the child's stdout pipe is readable
|
||||
write(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write(STDOUT_FILENO, buf, byte_count);
|
||||
} else if (this_fd == CHILD_PIPE_NAMES::STDERR_READ) {
|
||||
// the child's stderr pipe is readable
|
||||
write(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write(STDERR_FILENO, buf, byte_count);
|
||||
} else {
|
||||
// this should never happen
|
||||
perror("Logic error!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (watched_fds[this_fd].revents & POLLERR) {
|
||||
close(watched_fds[this_fd].fd);
|
||||
break_out = true;
|
||||
}
|
||||
if (watched_fds[this_fd].revents & POLLHUP) {
|
||||
// this pipe has hung up
|
||||
close(watched_fds[this_fd].fd);
|
||||
break_out = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// wait for child to exit, capture status
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
// close the log file handles
|
||||
fclose(stdout_log_fh);
|
||||
fclose(stderr_log_fh);
|
||||
if WIFEXITED(status) {
|
||||
return WEXITSTATUS(status);
|
||||
} else {
|
||||
return -617;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
122
src/lcpex/liblcpex.h
Normal file
122
src/lcpex/liblcpex.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef LCPEX_LIBLCPEX_H
|
||||
#define LCPEX_LIBLCPEX_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <iostream>
|
||||
#include "string_expansion/string_expansion.h"
|
||||
#include "helpers.h"
|
||||
#include "Contexts.h"
|
||||
#include "vpty/pty_fork_mod/tty_functions.h"
|
||||
#include "vpty/pty_fork_mod/pty_fork.h"
|
||||
#include "vpty/libclpex_tty.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief This function is executed by the child process to run a shell command
|
||||
*
|
||||
* @param context_override Indicates whether to override the current execution context
|
||||
* @param context_user The user to switch to for execution context
|
||||
* @param context_group The group to switch to for execution context
|
||||
* @param processed_command The command to be executed, after processing
|
||||
* @param fd_child_stdout_pipe The file descriptor for the child process's standard output pipe
|
||||
* @param fd_child_stderr_pipe The file descriptor for the child process's standard error pipe
|
||||
*
|
||||
* If context_override is set to true, the child process will run under the specified context_user and context_group.
|
||||
* If either the context_user or context_group does not exist, an error message will be displayed and the process will exit.
|
||||
*
|
||||
* The function first redirects the child process's standard output and standard error to pipes.
|
||||
*
|
||||
* If context_override is set to true, the child process sets its identity context using the set_identity_context() function.
|
||||
* If an error occurs while setting the identity context, a message will be displayed and the process will exit.
|
||||
*
|
||||
* Finally, the child process calls execvp() with the processed_command to run the shell command.
|
||||
* If the execvp() function fails, an error message is displayed.
|
||||
*/
|
||||
int execute(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Executes a command with logging and optional context switching
|
||||
*
|
||||
* @param command The command to be executed
|
||||
* @param stdout_log_file The file to log the standard output of the command
|
||||
* @param stderr_log_file The file to log the standard error of the command
|
||||
* @param context_override Indicates whether to override the current execution context
|
||||
* @param context_user The user to switch to for execution context
|
||||
* @param context_group The group to switch to for execution context
|
||||
* @param force_pty Indicates whether to force a pseudoterminal (pty) for the command execution
|
||||
* @param is_shell_command Indicates whether the command is a shell command
|
||||
* @param shell_path The path to the shell executable
|
||||
* @param shell_execution_arg The argument used to execute a command in the shell
|
||||
* @param supply_environment Indicates whether to supply an environment
|
||||
* @param shell_source_subcommand The shell subcommand used to source the environment file
|
||||
* @param environment_file_path The path to the environment file
|
||||
*
|
||||
* @return The exit status of the executed command
|
||||
*
|
||||
* This function executes a command with logging and optional context switching. The function generates
|
||||
* a prefix for the command using the `prefix_generator` function, which sets up a shell execution if
|
||||
* the command is a shell command. If a pseudoterminal (pty) is forced, the `exec_pty` function is used
|
||||
* to execute the command, otherwise the `execute` function is used. The standard output and standard
|
||||
* error of the command are logged to the provided log files. If context overriding is requested, the
|
||||
* execution context is switched to the provided user and group.
|
||||
*/
|
||||
int lcpex(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool force_pty,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Generates a command prefix for execution
|
||||
*
|
||||
* @param command The main command to be executed
|
||||
* @param is_shell_command Indicates whether the command is a shell command
|
||||
* @param shell_path The path to the shell executable
|
||||
* @param shell_execution_arg The argument used to execute a command in the shell
|
||||
* @param supply_environment Indicates whether to supply an environment
|
||||
* @param shell_source_subcommand The shell subcommand used to source the environment file
|
||||
* @param environment_file_path The path to the environment file
|
||||
*
|
||||
* @return The generated command prefix
|
||||
*
|
||||
* This function generates a prefix for a command to be executed, based on the provided parameters.
|
||||
* If the command is a shell command, the prefix will be set up for a shell execution. If the shell
|
||||
* takes an argument to execute a command, it will be added to the prefix. If an environment is to be
|
||||
* supplied, the shell subcommand for sourcing the environment file and the environment file path will
|
||||
* be added to the prefix. If the command is not a shell command, the command will be returned as is.
|
||||
*/
|
||||
std::string prefix_generator(
|
||||
std::string command,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
);
|
||||
|
||||
#endif //LCPEX_LIBLCPEX_H
|
||||
24
src/lcpex/string_expansion/string_expansion.cpp
Normal file
24
src/lcpex/string_expansion/string_expansion.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "string_expansion.h"
|
||||
|
||||
// convert a string to a char** representing our artificial argv to be consumed by execvp
|
||||
char ** expand_env(const std::string& var, int flags )
|
||||
{
|
||||
std::vector<std::string> vars;
|
||||
|
||||
wordexp_t p;
|
||||
if(!wordexp(var.c_str(), &p, flags))
|
||||
{
|
||||
if(p.we_wordc)
|
||||
for(char** exp = p.we_wordv; *exp; ++exp)
|
||||
vars.push_back(exp[0]);
|
||||
wordfree(&p);
|
||||
}
|
||||
|
||||
char ** arr = new char*[vars.size() + 1];
|
||||
for(size_t i = 0; i < vars.size(); i++){
|
||||
arr[i] = new char[vars[i].size() + 1];
|
||||
strcpy(arr[i], vars[i].c_str());
|
||||
}
|
||||
arr[vars.size()] = (char * ) nullptr;
|
||||
return arr;
|
||||
}
|
||||
29
src/lcpex/string_expansion/string_expansion.h
Normal file
29
src/lcpex/string_expansion/string_expansion.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef LCPEX_STRING_EXPANSION_H
|
||||
#define LCPEX_STRING_EXPANSION_H
|
||||
|
||||
#include <wordexp.h>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* @brief Convert a string to a 'char**' representing an argument list
|
||||
*
|
||||
* This function takes a string 'var' and converts it into a 'char**'
|
||||
* representing an argument list. The argument list can be consumed by
|
||||
* functions like 'execvp'.
|
||||
*
|
||||
* @param[in] var The string to be converted
|
||||
* @param[in] flags Flags controlling the behavior of the conversion
|
||||
*
|
||||
* @return A 'char**' representing the argument list
|
||||
*
|
||||
* The function uses the 'wordexp' function to perform the conversion. The
|
||||
* 'flags' argument controls the behavior of the 'wordexp' function. The
|
||||
* returned 'char**' is dynamically allocated, and must be freed by the
|
||||
* caller using 'delete[]'.
|
||||
*/
|
||||
char ** expand_env(const std::string& var, int flags = 0);
|
||||
|
||||
|
||||
#endif //LCPEX_STRING_EXPANSION_H
|
||||
282
src/lcpex/vpty/libclpex_tty.cpp
Normal file
282
src/lcpex/vpty/libclpex_tty.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
#include "libclpex_tty.h"
|
||||
|
||||
/**
|
||||
* @brief Safely prints a message and exits the program
|
||||
*
|
||||
* @param msg The message to print
|
||||
* @param ttyOrig A pointer to a struct termios representing the original terminal settings
|
||||
*
|
||||
* This function takes a message `msg` and a pointer to a struct termios `ttyOrig` as input.
|
||||
* The function first prints the `msg` to `stderr`.
|
||||
* Then, it calls `ttyResetExit()` with `ttyOrig` as its argument to reset the terminal settings.
|
||||
* Finally, the function exits the program with exit code 1.
|
||||
*/
|
||||
void safe_perror( const char * msg, struct termios * ttyOrig )
|
||||
{
|
||||
std::cerr << msg << std::endl;
|
||||
ttyResetExit( ttyOrig );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Executes the child process
|
||||
*
|
||||
* @param fd_child_stderr_pipe A file descriptor array for the child process's stderr pipe
|
||||
* @param processed_command An array of char pointers representing the command and its arguments to be executed
|
||||
* @param ttyOrig A pointer to a struct termios representing the original terminal settings
|
||||
* @param context_override A flag indicating whether to override the process's execution context
|
||||
* @param context_user The username to use for the execution context if `context_override` is `true`
|
||||
* @param context_group The group name to use for the execution context if `context_override` is `true`
|
||||
*
|
||||
* This function takes an array of file descriptors `fd_child_stderr_pipe` for the child process's stderr pipe,
|
||||
* an array of char pointers `processed_command` representing the command and its arguments to be executed,
|
||||
* a pointer to a struct termios `ttyOrig` representing the original terminal settings,
|
||||
* a flag `context_override` indicating whether to override the process's execution context,
|
||||
* a string `context_user` representing the username to use for the execution context if `context_override` is `true`,
|
||||
* and a string `context_group` representing the group name to use for the execution context if `context_override` is `true`.
|
||||
*
|
||||
* The function first redirects the child process's stderr to the write end of the stderr pipe.
|
||||
* If `context_override` is `true`, the function sets the process's execution context using `set_identity_context()`.
|
||||
* If `context_override` is `false`, the function does nothing.
|
||||
* Finally, the function executes the command specified in `processed_command` using `execvp()`.
|
||||
* If the execution of `execvp()` fails, the function calls `safe_perror()` to print a message and exit the program.
|
||||
*/
|
||||
void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[], struct termios * ttyOrig, bool context_override, std::string context_user, std::string context_group )
|
||||
{
|
||||
// redirect stderr to the write end of the stderr pipe
|
||||
// close the file descriptor STDERR_FILENO if it was previously open, then (re)open it as a copy of
|
||||
while ((dup2(fd_child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {}
|
||||
|
||||
// close the write end of the stderr pipe
|
||||
close( fd_child_stderr_pipe[WRITE_END] );
|
||||
|
||||
// if the user has specified a context override, set the context
|
||||
if ( context_override )
|
||||
{
|
||||
int context_result = set_identity_context(context_user, context_group);
|
||||
switch (context_result) {
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
||||
std::cerr << "lcpex: Aborting: context user not found: " << context_user << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
||||
std::cerr << "lcpex: Aborting: context group not found: " << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "lcpex: Aborting: Unknown error while setting identity context." << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// execute the dang command, print to stdout, stderr (of parent), and dump to file for each!!!!
|
||||
// (and capture exit code in parent)
|
||||
int exit_code = execvp(processed_command[0], processed_command);
|
||||
safe_perror("failed on execvp in child", ttyOrig );
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
// this does three things:
|
||||
// - execute a dang string as a subprocess command
|
||||
// - capture child stdout/stderr to respective log files
|
||||
// - TEE child stdout/stderr to parent stdout/stderr
|
||||
int exec_pty(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
) {
|
||||
// initialize the terminal settings obj
|
||||
struct termios ttyOrig;
|
||||
|
||||
// if the user chose to supply the environment, then we need to clear the environment
|
||||
// before we fork the process, so that the child process will inherit the environment
|
||||
// from the parent process
|
||||
if ( environment_supplied ) {
|
||||
clearenv();
|
||||
}
|
||||
|
||||
// turn our command string into something execvp can consume
|
||||
char ** processed_command = expand_env(command );
|
||||
|
||||
// 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
|
||||
int fd_child_stderr_pipe[2];
|
||||
|
||||
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
||||
safe_perror( "child stderr pipe", &ttyOrig );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// using O_CLOEXEC to ensure that the child process closes the file descriptors
|
||||
if ( fcntl( fd_child_stderr_pipe[READ_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
|
||||
|
||||
// // same for stderr write
|
||||
if ( fcntl( fd_child_stderr_pipe[WRITE_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
|
||||
|
||||
// status result basket for the parent process to capture the child's exit status
|
||||
int status = 616;
|
||||
|
||||
// start ptyfork integration
|
||||
char slaveName[MAX_SNAME];
|
||||
int masterFd;
|
||||
struct winsize ws;
|
||||
|
||||
/* Retrieve the attributes of terminal on which we are started */
|
||||
if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
|
||||
safe_perror("tcgetattr", &ttyOrig);
|
||||
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
|
||||
safe_perror("ioctl-TIOCGWINSZ", &ttyOrig );
|
||||
|
||||
pid_t pid = ptyFork( &masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws );
|
||||
|
||||
switch( pid ) {
|
||||
case -1:
|
||||
{
|
||||
// fork failed
|
||||
safe_perror("ptyfork failure", &ttyOrig );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
case 0:
|
||||
{
|
||||
// child process
|
||||
run_child_process( fd_child_stderr_pipe, processed_command, &ttyOrig, context_override, context_user, context_group );
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// parent process
|
||||
// start ptyfork integration
|
||||
ttySetRaw(STDIN_FILENO, &ttyOrig);
|
||||
|
||||
|
||||
// The parent process has no need to access the entrance to the pipe
|
||||
close(fd_child_stderr_pipe[WRITE_END]);
|
||||
|
||||
// attempt to write to stdout,stderr from child as well as to write each to file
|
||||
char buf[BUFFER_SIZE];
|
||||
|
||||
// contains the byte count of the last read from the pipe
|
||||
ssize_t byte_count;
|
||||
|
||||
// watched_fds for poll() to wait on
|
||||
struct pollfd watched_fds[3];
|
||||
|
||||
// populate the watched_fds array
|
||||
|
||||
// child in to parent
|
||||
watched_fds[0].fd = STDIN_FILENO;
|
||||
watched_fds[0].events = POLLIN;
|
||||
|
||||
// child pty to parent (stdout)
|
||||
watched_fds[1].fd = masterFd;
|
||||
watched_fds[1].events = POLLIN;
|
||||
|
||||
// child stderr to parent
|
||||
watched_fds[2].fd = fd_child_stderr_pipe[READ_END];
|
||||
watched_fds[2].events = POLLIN;
|
||||
|
||||
// number of files poll() reports as ready
|
||||
int num_files_readable;
|
||||
|
||||
// loop flag
|
||||
bool break_out = false;
|
||||
|
||||
// loop until we've read all the data from the child process
|
||||
while ( ! break_out ) {
|
||||
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
||||
|
||||
if (num_files_readable == -1) {
|
||||
// error occurred in poll()
|
||||
safe_perror("poll", &ttyOrig );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (num_files_readable == 0) {
|
||||
// poll reports no files readable
|
||||
break_out = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int this_fd = 0; this_fd < 3; this_fd++) {
|
||||
if (watched_fds[this_fd].revents & POLLIN) {
|
||||
// this pipe is readable
|
||||
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
||||
|
||||
if (byte_count == -1) {
|
||||
// error reading from pipe
|
||||
safe_perror("read", &ttyOrig );
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (byte_count == 0) {
|
||||
// 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
|
||||
continue;
|
||||
} else {
|
||||
// byte count was sane
|
||||
// write to stdout,stderr
|
||||
if (this_fd == 0) {
|
||||
// parent stdin received, write to child pty (stdin)
|
||||
write(masterFd, buf, byte_count);
|
||||
} else if (this_fd == 1 ) {
|
||||
// child pty sent some stuff, write to parent stdout and log
|
||||
write(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write(STDOUT_FILENO, buf, byte_count);
|
||||
} else if ( this_fd == 2 ){
|
||||
//the child's stderr pipe sent some stuff, write to parent stderr and log
|
||||
write(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write(STDERR_FILENO, buf, byte_count);
|
||||
} else {
|
||||
// this should never happen
|
||||
perror("Logic error!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (watched_fds[this_fd].revents & POLLERR) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// wait for child to exit, capture status
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
// close the log file handles
|
||||
fclose(stdout_log_fh);
|
||||
fclose(stderr_log_fh);
|
||||
ttyResetExit( &ttyOrig);
|
||||
if WIFEXITED(status) {
|
||||
return WEXITSTATUS(status);
|
||||
} else {
|
||||
return -617;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
src/lcpex/vpty/libclpex_tty.h
Normal file
50
src/lcpex/vpty/libclpex_tty.h
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Created by phanes on 2/6/23.
|
||||
//
|
||||
|
||||
#ifndef LCPEX_LIBCLPEX_TTY_H
|
||||
#define LCPEX_LIBCLPEX_TTY_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <iostream>
|
||||
#include "../string_expansion/string_expansion.h"
|
||||
#include "../helpers.h"
|
||||
#include "../Contexts.h"
|
||||
#include "../vpty/pty_fork_mod/tty_functions.h"
|
||||
#include "../vpty/pty_fork_mod/pty_fork.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief Execute a string as a subprocess command, capture its stdout/stderr to log files, and TEE its output to the parent process's stdout/stderr.
|
||||
* This function performs the following three tasks:
|
||||
* - Execute a string as a subprocess command.
|
||||
* - Capture the child process's stdout and stderr to respective log files.
|
||||
* - TEE the child process's stdout and stderr to the parent process's stdout and stderr.
|
||||
*
|
||||
* @param command The command to be executed as a subprocess.
|
||||
* @param stdout_log_file The file path where the child process's stdout will be logged.
|
||||
* @param stderr_log_file The file path where the child process's stderr will be logged.
|
||||
* @param context_override Specify whether to override the process's execution context.
|
||||
* @param context_user The user context to run the process as, if context_override is true.
|
||||
* @param context_group The group context to run the process as, if context_override is true.
|
||||
* @param environment_supplied Specify whether the environment is supplied.
|
||||
* @return The exit status of the child process. If the child process terminated due to a signal, returns -617.
|
||||
*/
|
||||
int exec_pty(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
);
|
||||
|
||||
|
||||
#endif //LCPEX_LIBCLPEX_TTY_H
|
||||
79
src/lcpex/vpty/pty_fork_mod/pty_fork.cpp
Normal file
79
src/lcpex/vpty/pty_fork_mod/pty_fork.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "pty_fork.h"
|
||||
|
||||
pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen,
|
||||
const struct termios *slaveTermios, const struct winsize *slaveWS)
|
||||
{
|
||||
int mfd, slaveFd, savedErrno;
|
||||
pid_t childPid;
|
||||
char slname[MAX_SNAME];
|
||||
|
||||
mfd = ptyMasterOpen(slname, MAX_SNAME);
|
||||
if (mfd == -1)
|
||||
return -1;
|
||||
|
||||
if (slaveName != NULL) { /* Return slave name to caller */
|
||||
if (strlen(slname) < snLen) {
|
||||
strncpy(slaveName, slname, snLen);
|
||||
|
||||
} else { /* 'slaveName' was too small */
|
||||
close(mfd);
|
||||
errno = EOVERFLOW;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
childPid = fork();
|
||||
|
||||
/* fork() failed */
|
||||
if (childPid == -1) {
|
||||
/* close() might change 'errno' */
|
||||
savedErrno = errno;
|
||||
/* Don't leak file descriptors */
|
||||
close(mfd);
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (childPid != 0) { /* Parent */
|
||||
*masterFd = mfd; /* Only parent gets master fd */
|
||||
return childPid; /* Like parent of fork() */
|
||||
}
|
||||
|
||||
/* Child falls through to here */
|
||||
|
||||
if (setsid() == -1) /* Start a new session */
|
||||
perror("ptyFork:setsid");
|
||||
|
||||
close(mfd); /* Not needed in child */
|
||||
|
||||
slaveFd = open(slname, O_RDWR); /* Becomes controlling tty */
|
||||
if (slaveFd == -1)
|
||||
perror("ptyFork:open-slave");
|
||||
|
||||
#ifdef TIOCSCTTY /* Acquire controlling tty on BSD */
|
||||
if (ioctl(slaveFd, TIOCSCTTY, 0) == -1)
|
||||
perror("ptyFork:ioctl-TIOCSCTTY");
|
||||
#endif
|
||||
|
||||
if (slaveTermios != NULL) /* Set slave tty attributes */
|
||||
if (tcsetattr(slaveFd, TCSANOW, slaveTermios) == -1)
|
||||
perror("ptyFork:tcsetattr");
|
||||
|
||||
if (slaveWS != NULL) /* Set slave tty window size */
|
||||
if (ioctl(slaveFd, TIOCSWINSZ, slaveWS) == -1)
|
||||
perror("ptyFork:ioctl-TIOCSWINSZ");
|
||||
|
||||
/* Duplicate pty slave to be child's stdin, stdout, and stderr */
|
||||
|
||||
if (dup2(slaveFd, STDIN_FILENO) != STDIN_FILENO)
|
||||
perror("ptyFork:dup2-STDIN_FILENO");
|
||||
if (dup2(slaveFd, STDOUT_FILENO) != STDOUT_FILENO)
|
||||
perror("ptyFork:dup2-STDOUT_FILENO");
|
||||
if (dup2(slaveFd, STDERR_FILENO) != STDERR_FILENO)
|
||||
perror("ptyFork:dup2-STDERR_FILENO");
|
||||
|
||||
if (slaveFd > STDERR_FILENO) /* Safety check */
|
||||
close(slaveFd); /* No longer need this fd */
|
||||
|
||||
return 0; /* Like child of fork() */
|
||||
}
|
||||
39
src/lcpex/vpty/pty_fork_mod/pty_fork.h
Normal file
39
src/lcpex/vpty/pty_fork_mod/pty_fork.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef LCPEX_PTY_FORK_H
|
||||
#define LCPEX_PTY_FORK_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include "./pty_master_open.h"
|
||||
#include <cstdio>
|
||||
#include "./tty_functions.h"
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
/* Maximum size for pty slave name */
|
||||
#define MAX_SNAME 1000
|
||||
|
||||
/**
|
||||
* @brief Fork a child process with a pseudo-terminal
|
||||
*
|
||||
* This function forks a child process and returns the child's process ID. The
|
||||
* child process is attached to a pseudo-terminal and its standard input,
|
||||
* output, and error streams are redirected to the slave side of the pseudo-terminal.
|
||||
*
|
||||
* @param[out] masterFd Pointer to store the file descriptor of the master pty
|
||||
* @param[out] slaveName Buffer to store the name of the slave pty
|
||||
* @param[in] snLen Length of the 'slaveName' buffer
|
||||
* @param[in] slaveTermios Terminal attributes for the slave pty (optional)
|
||||
* @param[in] slaveWS Window size for the slave pty (optional)
|
||||
*
|
||||
* @return Process ID of the child process on success, or -1 on error
|
||||
*
|
||||
* On success, the file descriptor of the master pty is stored in the location
|
||||
* pointed to by 'masterFd', and the name of the slave pty is stored in the buffer
|
||||
* pointed to by 'slaveName'. If the buffer is too small to hold the name of the
|
||||
* slave, an error of type 'EOVERFLOW' is returned. If 'slaveTermios' is non-NULL,
|
||||
* the terminal attributes specified by it will be set for the slave pty. If
|
||||
* 'slaveWS' is non-NULL, the window size specified by it will be set for the slave
|
||||
* pty.
|
||||
*/
|
||||
pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen, const struct termios *slaveTermios, const struct winsize *slaveWS);
|
||||
|
||||
|
||||
#endif //LCPEX_PTY_FORK_H
|
||||
76
src/lcpex/vpty/pty_fork_mod/pty_master_open.cpp
Normal file
76
src/lcpex/vpty/pty_fork_mod/pty_master_open.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/* pty_master_open.cPP
|
||||
|
||||
Implement our ptyMasterOpen() function, based on UNIX 98 pseudoterminals.
|
||||
See comments below.
|
||||
|
||||
See also pty_master_open_bsd.c.
|
||||
*/
|
||||
#if ! defined(__sun)
|
||||
/* Prevents ptsname() declaration being visible on Solaris 8 */
|
||||
#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "pty_master_open.h" /* Declares ptyMasterOpen() */
|
||||
|
||||
/* Some implementations don't have posix_openpt() */
|
||||
|
||||
#if defined(__sun) /* Not on Solaris 8 */
|
||||
#define NO_POSIX_OPENPT
|
||||
#endif
|
||||
|
||||
#ifdef NO_POSIX_OPENPT
|
||||
static int
|
||||
posix_openpt(int flags)
|
||||
{
|
||||
return open("/dev/ptmx", flags);
|
||||
}
|
||||
|
||||
#endif
|
||||
/* Open a pty master, returning file descriptor, or -1 on error.
|
||||
On successful completion, the name of the corresponding pty
|
||||
slave is returned in 'slaveName'. 'snLen' should be set to
|
||||
indicate the size of the buffer pointed to by 'slaveName'. */
|
||||
|
||||
int ptyMasterOpen(char *slaveName, size_t snLen)
|
||||
{
|
||||
int masterFd, savedErrno;
|
||||
char *p;
|
||||
|
||||
masterFd = posix_openpt(O_RDWR | O_NOCTTY); /* Open pty master */
|
||||
if (masterFd == -1)
|
||||
return -1;
|
||||
|
||||
if (grantpt(masterFd) == -1) { /* Grant access to slave pty */
|
||||
savedErrno = errno;
|
||||
close(masterFd); /* Might change 'errno' */
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (unlockpt(masterFd) == -1) { /* Unlock slave pty */
|
||||
savedErrno = errno;
|
||||
close(masterFd); /* Might change 'errno' */
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
p = ptsname(masterFd); /* Get slave pty name */
|
||||
if (p == nullptr) {
|
||||
savedErrno = errno;
|
||||
close(masterFd); /* Might change 'errno' */
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(p) < snLen) {
|
||||
strncpy(slaveName, p, snLen);
|
||||
} else { /* Return an error if buffer too small */
|
||||
close(masterFd);
|
||||
errno = EOVERFLOW;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return masterFd;
|
||||
}
|
||||
32
src/lcpex/vpty/pty_fork_mod/pty_master_open.h
Normal file
32
src/lcpex/vpty/pty_fork_mod/pty_master_open.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef LCPEX_PTY_MASTER_OPEN_H
|
||||
#define LCPEX_PTY_MASTER_OPEN_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
|
||||
/**
|
||||
* @brief Open a pseudo-terminal master
|
||||
*
|
||||
* This function opens a pseudo-terminal master and returns the file descriptor
|
||||
* for the master. The name of the corresponding pseudo-terminal slave is also
|
||||
* returned in the buffer pointed to by 'slaveName'.
|
||||
*
|
||||
* @param[out] slaveName Buffer to store the name of the slave pty
|
||||
* @param[in] snLen Length of the 'slaveName' buffer
|
||||
*
|
||||
* @return File descriptor of the master pty on success, or -1 on error
|
||||
*
|
||||
* On success, the name of the corresponding pseudo-terminal slave is stored in
|
||||
* the buffer pointed to by 'slaveName'. If the buffer is too small to hold the
|
||||
* name of the slave, an error of type 'EOVERFLOW' is returned.
|
||||
*/
|
||||
int ptyMasterOpen(char *slaveName, size_t snLen);
|
||||
|
||||
#endif //LCPEX_PTY_MASTER_OPEN_H
|
||||
77
src/lcpex/vpty/pty_fork_mod/tty_functions.cpp
Normal file
77
src/lcpex/vpty/pty_fork_mod/tty_functions.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "tty_functions.h"
|
||||
|
||||
/* Reset terminal mode on program exit */
|
||||
void ttyResetExit( struct termios * ttyOrig )
|
||||
{
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, ttyOrig) == -1)
|
||||
perror("tcsetattr");
|
||||
}
|
||||
|
||||
/* Place terminal referred to by 'fd' in cbreak mode (noncanonical mode
|
||||
with echoing turned off). This function assumes that the terminal is
|
||||
currently in cooked mode (i.e., we shouldn't call it if the terminal
|
||||
is currently in raw mode, since it does not undo all of the changes
|
||||
made by the ttySetRaw() function below). Return 0 on success, or -1
|
||||
on error. If 'prevTermios' is non-NULL, then use the buffer to which
|
||||
it points to return the previous terminal settings. */
|
||||
int ttySetCbreak(int fd, struct termios *prevTermios)
|
||||
{
|
||||
struct termios t;
|
||||
|
||||
if (tcgetattr(fd, &t) == -1)
|
||||
return -1;
|
||||
|
||||
if (prevTermios != nullptr)
|
||||
*prevTermios = t;
|
||||
|
||||
t.c_lflag &= ~(ICANON | ECHO);
|
||||
t.c_lflag |= ISIG;
|
||||
|
||||
t.c_iflag &= ~ICRNL;
|
||||
|
||||
t.c_cc[VMIN] = 1; /* Character-at-a-time input */
|
||||
t.c_cc[VTIME] = 0; /* with blocking */
|
||||
|
||||
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Place terminal referred to by 'fd' in raw mode (noncanonical mode
|
||||
with all input and output processing disabled). Return 0 on success,
|
||||
or -1 on error. If 'prevTermios' is non-NULL, then use the buffer to
|
||||
which it points to return the previous terminal settings. */
|
||||
|
||||
int ttySetRaw(int fd, struct termios *prevTermios)
|
||||
{
|
||||
struct termios t;
|
||||
|
||||
if (tcgetattr(fd, &t) == -1)
|
||||
return -1;
|
||||
|
||||
if (prevTermios != nullptr)
|
||||
*prevTermios = t;
|
||||
|
||||
t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
|
||||
/* Noncanonical mode, disable signals, extended
|
||||
input processing, and echoing */
|
||||
|
||||
t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR |
|
||||
INPCK | ISTRIP | IXON | PARMRK);
|
||||
/* Disable special handling of CR, NL, and BREAK.
|
||||
No 8th-bit stripping or parity error handling.
|
||||
Disable START/STOP output flow control. */
|
||||
|
||||
// this breaks cursor position change tracking in parent processes
|
||||
//t.c_oflag &= ~OPOST; /* Disable all output processing */
|
||||
|
||||
t.c_cc[VMIN] = 1; /* Character-at-a-time input */
|
||||
t.c_cc[VTIME] = 0; /* with blocking */
|
||||
|
||||
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
59
src/lcpex/vpty/pty_fork_mod/tty_functions.h
Normal file
59
src/lcpex/vpty/pty_fork_mod/tty_functions.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef LCPEX_TTY_FUNCTIONS_H
|
||||
#define LCPEX_TTY_FUNCTIONS_H
|
||||
|
||||
|
||||
#include <termios.h>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @brief Place terminal referred to by 'fd' in cbreak mode
|
||||
*
|
||||
* This function places the terminal referred to by the file descriptor 'fd'
|
||||
* in cbreak mode (noncanonical mode with echoing turned off). It assumes that
|
||||
* the terminal is currently in cooked mode (i.e., it should not be called
|
||||
* if the terminal is currently in raw mode, since it does not undo all of
|
||||
* the changes made by the ttySetRaw() function).
|
||||
*
|
||||
* @param fd File descriptor of the terminal
|
||||
* @param prevTermios Buffer to store the previous terminal settings
|
||||
*
|
||||
* @return 0 on success, or -1 on error
|
||||
*
|
||||
* If 'prevTermios' is non-NULL, then the buffer pointed to by it will be used
|
||||
* to return the previous terminal settings.
|
||||
*/
|
||||
int ttySetCbreak(int fd, struct termios * prevTermios);
|
||||
|
||||
/**
|
||||
* @brief Place terminal referred to by 'fd' in raw mode
|
||||
*
|
||||
* This function places the terminal referred to by the file descriptor 'fd'
|
||||
* in raw mode.
|
||||
*
|
||||
* @param fd File descriptor of the terminal
|
||||
* @param prevTermios Buffer to store the previous terminal settings
|
||||
*
|
||||
* @return 0 on success, or -1 on error
|
||||
*
|
||||
* If 'prevTermios' is non-NULL, then the buffer pointed to by it will be used
|
||||
* to return the previous terminal settings.
|
||||
*/
|
||||
int ttySetRaw(int fd, struct termios * prevTermios);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Reset terminal mode on program exit
|
||||
*
|
||||
* @param ttyOrig Original terminal mode to be restored
|
||||
*
|
||||
* This function resets the terminal mode when the program exits by using the
|
||||
* tcsetattr function. If the tcsetattr function returns an error, the perror
|
||||
* function is called to print an error message.
|
||||
*/
|
||||
void ttyResetExit( struct termios * ttyOrig );
|
||||
|
||||
|
||||
|
||||
#endif //LCPEX_TTY_FUNCTIONS_H
|
||||
Reference in New Issue
Block a user