2020-07-31 23:18:01 +02:00
//
// Created by WolverinDEV on 31/07/2020.
//
# include "PipedTerminal.h"
# include "CommandHandler.h"
# include <event.h>
# include <sys/stat.h>
# include <thread>
# include <log/LogUtils.h>
# include <StringVariable.h>
std : : string pipe_path_in { } , pipe_path_out { } ;
int file_descriptor_in { 0 } , file_descriptor_out { 0 } ;
std : : thread event_loop_dispatcher { } ;
: : event_base * event_base { nullptr } ;
event * event_read { nullptr } ;
event * event_write { nullptr } ;
void event_loop_executor ( void * ) ;
void event_read_callback ( int , short , void * ) ;
void event_write_callback ( int , short , void * ) ;
void terminal : : initialize_pipe ( const std : : string & pipe_path ) {
{
std : : string path ;
if ( pipe_path . empty ( ) ) {
path = " /tmp/teaspeak_${pid}_${direction}.term " ;
} else {
path = pipe_path ;
}
pipe_path_in = strvar : : transform ( path , strvar : : StringValue { " pid " , std : : to_string ( getpid ( ) ) } , strvar : : StringValue { " direction " , " in " } ) ;
pipe_path_out = strvar : : transform ( path , strvar : : StringValue { " pid " , std : : to_string ( getpid ( ) ) } , strvar : : StringValue { " direction " , " out " } ) ;
}
auto result = mkfifo ( pipe_path_in . c_str ( ) , 0666 ) ;
if ( result ! = 0 ) {
logWarning ( LOG_INSTANCE , " Failed to create incoming terminal pipe ({}/{}) " , errno , strerror ( errno ) ) ;
finalize_pipe ( ) ;
return ;
}
file_descriptor_in = open ( pipe_path_in . c_str ( ) , ( unsigned ) O_NONBLOCK | ( unsigned ) O_RDONLY ) ;
if ( file_descriptor_in < = 0 ) {
logWarning ( LOG_INSTANCE , " Failed to open incoming terminal pipe ({}/{}) " , errno , strerror ( errno ) ) ;
finalize_pipe ( ) ;
return ;
}
result = mkfifo ( pipe_path_out . c_str ( ) , 0666 ) ;
if ( result ! = 0 ) {
logWarning ( LOG_INSTANCE , " Failed to create outgoing terminal pipe ({}/{}) " , errno , strerror ( errno ) ) ;
finalize_pipe ( ) ;
return ;
}
/* we can't do a write only open, else along with the O_NONBLOCK we'll get No such device or address */
file_descriptor_out = open ( pipe_path_out . c_str ( ) , ( unsigned ) O_NONBLOCK | ( unsigned ) O_RDWR ) ;
if ( file_descriptor_out < = 0 ) {
logWarning ( LOG_INSTANCE , " Failed to open outgoing terminal pipe ({}/{}) " , errno , strerror ( errno ) ) ;
finalize_pipe ( ) ;
return ;
}
event_base = event_base_new ( ) ;
if ( ! event_base ) {
logWarning ( LOG_INSTANCE , " Failed to open terminal pipe ({}/{}) " , errno , strerror ( errno ) ) ;
finalize_pipe ( ) ;
return ;
}
event_loop_dispatcher = std : : thread { event_loop_executor , event_base } ;
event_read = event_new ( event_base , file_descriptor_in , ( unsigned ) EV_READ | ( unsigned ) EV_PERSIST , event_read_callback , nullptr ) ;
event_write = event_new ( event_base , file_descriptor_out , EV_WRITE , event_write_callback , nullptr ) ;
event_add ( event_read , nullptr ) ;
logMessage ( LOG_INSTANCE , " Terminal pipe started (Incoming: {}, Outgoing: {}). " , pipe_path_in , pipe_path_out ) ;
}
void terminal : : finalize_pipe ( ) {
2020-12-14 20:33:36 +01:00
if ( auto ev_base { std : : exchange ( event_base , nullptr ) } ; ev_base ) {
event_base_loopexit ( ev_base , nullptr ) ;
2020-07-31 23:18:01 +02:00
if ( event_loop_dispatcher . joinable ( ) & & std : : this_thread : : get_id ( ) ! = event_loop_dispatcher . get_id ( ) )
event_loop_dispatcher . join ( ) ;
/* events get deleted when the base gets freed */
event_read = nullptr ;
event_write = nullptr ;
}
if ( file_descriptor_in > 0 ) {
close ( file_descriptor_in ) ;
file_descriptor_in = 0 ;
remove ( pipe_path_in . c_str ( ) ) ;
}
if ( file_descriptor_out > 0 ) {
close ( file_descriptor_out ) ;
file_descriptor_out = 0 ;
remove ( pipe_path_out . c_str ( ) ) ;
}
}
void event_loop_executor ( void * ptr_event_base ) {
auto base = ( struct event_base * ) ptr_event_base ;
while ( ! event_base_got_exit ( base ) )
event_base_loop ( base , EVLOOP_NO_EXIT_ON_EMPTY ) ;
event_base_free ( base ) ;
}
/* no buffer lock needed since we're only accessing them via one thread */
constexpr static auto kReadBufferSize { 8 * 1024 } ;
char read_buffer [ kReadBufferSize ] ;
size_t read_buffer_index { 0 } ;
constexpr static auto kWriteBufferSize { 64 * 1024 } ;
char write_buffer [ kWriteBufferSize ] ;
size_t write_buffer_index { 0 } ;
void append_write_buffer ( std : : string_view message ) {
if ( message . length ( ) > kWriteBufferSize ) {
logWarning ( LOG_INSTANCE , " Trying to write a too long message to the terminal pipe. Truncating {} bytes from the beginning. " , message . length ( ) - kWriteBufferSize ) ;
message = message . substr ( message . length ( ) - kWriteBufferSize ) ;
}
if ( write_buffer_index + message . length ( ) > kWriteBufferSize ) {
logWarning ( LOG_INSTANCE , " Encountering a write buffer overflow. Truncating bytes form the beginning. " ) ;
auto offset = message . length ( ) + write_buffer_index - kWriteBufferSize ;
if ( write_buffer_index > offset ) {
memcpy ( write_buffer , write_buffer + offset , write_buffer_index - offset ) ;
write_buffer_index = write_buffer_index - offset ;
} else {
write_buffer_index = 0 ;
}
}
memcpy ( write_buffer + write_buffer_index , message . data ( ) , message . length ( ) ) ;
write_buffer_index + = message . length ( ) ;
event_add ( event_write , nullptr ) ;
}
bool process_next_command ( ) {
auto new_line_index = ( char * ) memchr ( read_buffer , ' \n ' , read_buffer_index ) ;
if ( ! new_line_index ) {
return false ;
}
std : : string command { read_buffer , ( size_t ) ( new_line_index - read_buffer ) } ;
if ( new_line_index = = read_buffer + read_buffer_index ) {
read_buffer_index = 0 ;
} else {
auto length_left = read_buffer_index - ( new_line_index - read_buffer + 1 ) ;
memcpy ( read_buffer , new_line_index + 1 , length_left ) ;
read_buffer_index = length_left ;
}
if ( command . find_first_not_of ( ' ' ) = = std : : string : : npos ) {
process_next_command ( ) ;
return false ;
}
logMessage ( LOG_INSTANCE , " Dispatching command received via pipe \" {} \" . " , command ) ;
terminal : : chandler : : CommandHandle handle { } ;
handle . command = command ;
if ( ! terminal : : chandler : : handleCommand ( handle ) ) {
append_write_buffer ( " error \n " ) ;
} else {
append_write_buffer ( " ok \n " ) ;
}
2021-04-14 23:12:51 +02:00
for ( const auto & line : handle . response ) {
2020-07-31 23:18:01 +02:00
append_write_buffer ( line + " \n " ) ;
2021-04-14 23:12:51 +02:00
}
2020-07-31 23:18:01 +02:00
append_write_buffer ( " \r \n " ) ;
append_write_buffer ( " \r \n " ) ;
return true ;
}
void event_read_callback ( int fd , short events , void * ) {
if ( ( unsigned ) events & ( unsigned ) EV_READ ) {
while ( true ) {
if ( kReadBufferSize = = read_buffer_index ) {
logWarning ( LOG_INSTANCE , " Terminal pipe line buffer overflow. Flushing buffer. " ) ;
read_buffer_index = 0 ;
}
auto read = : : read ( fd , read_buffer + read_buffer_index , kReadBufferSize - read_buffer_index ) ;
if ( read < 0 ) {
if ( errno = = EAGAIN ) {
event_add ( event_read , nullptr ) ;
return ;
}
logError ( LOG_INSTANCE , " Terminal pipe encountered a read error: {}/{}. Closing terminal. " , errno , strerror ( errno ) ) ;
terminal : : finalize_pipe ( ) ;
return ;
} else if ( read = = 0 ) {
return ;
}
read_buffer_index + = read ;
if ( process_next_command ( ) )
return ;
}
}
}
void event_write_callback ( int fd , short events , void * ) {
if ( ( unsigned ) events & ( unsigned ) EV_WRITE ) {
while ( true ) {
auto written = : : write ( fd , write_buffer , write_buffer_index ) ;
if ( written < 0 ) {
if ( errno = = EAGAIN ) {
event_add ( event_write , nullptr ) ;
return ;
}
logError ( LOG_INSTANCE , " Terminal pipe encountered a write error: {}/{}. Closing terminal. " , errno , strerror ( errno ) ) ;
terminal : : finalize_pipe ( ) ;
return ;
} else if ( written = = 0 ) {
return ;
} else if ( written = = write_buffer_index ) {
write_buffer_index = 0 ;
return ;
} else {
memcpy ( write_buffer , write_buffer + written , write_buffer_index - written ) ;
write_buffer_index - = written ;
}
}
}
}