Files
redAcore/SelectableCore.cpp
Charl Wentzel 22a05ebd4e Important update:
- Bug fix: Set constant default address/port values for
    LinePrinter, TCPserver & TCPclient
2018-10-23 11:44:38 +01:00

2146 lines
71 KiB
C++

/*
* SelectableCore.cpp
*
* Created on: 20 May 2016
* Author: wentzelc
*/
// Standard C/C++ Libraries
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
// redA Libraries
#include "ApplicationCore.h"
#include "SelectableCore.h"
//---------------------------------------------------------------------------
// Global Vars
//extern char * ProcessName;
extern CApplication * Application;
//---------------------------------------------------------------------------
CSelectableCore::CSelectableCore( const char * pName, const char * pType ) :
CFunctionCore( pName, pType )
{
// Quick access
Selector = Application->Selector;
// Handles
FirstHandle = NULL;
}
//---------------------------------------------------------------------------
CSelectableCore::~CSelectableCore()
{
THandle * NextHandle = NULL;
// Destroy File Handles
while (FirstHandle)
{
// Close handle if open
if ((FirstHandle->State == csOpen) || (FirstHandle->State == csWaitingtoOpen)) {
Close( FirstHandle, false );
}
NextHandle = FirstHandle->Next;
DestroyHandle( FirstHandle );
FirstHandle = NextHandle;
}
}
//---------------------------------------------------------------------------
bool CSelectableCore::LoadConfigData()
{
TDataMember * TempMember;
TDataMember * SerialConfig;
THandle * Handle;
char * Type;
char * Name;
char Path[100];
char * Address;
char * Port;
char * ParityText;
char * FlowCtrlText;
short Parity;
short FlowCtrl;
long Delay;
// Call Previous load config
CFunctionCore::LoadConfigData();
// Load Handles
TempMember = DataTree->GetFirstChild( DataTree->GetMember( ConfigMember, "Handles", false ) );
while (TempMember)
{
// Check if name is valid
if (!TempMember->Name || !*TempMember->Name)
continue;
// Create Handle and channel link
Handle = CreateHandle( TempMember->Name, false );
Handle->Channel = GetChannel( DataTree->GetStr( TempMember, "Channel" ) );
Type = (char*)DataTree->GetStr( TempMember, "Type", "TCPclient", true );
if (!strcasecmp( Type, "Serial" ))
{
if ((Name = (char*)DataTree->GetStr( TempMember, "Port/Name", NULL ))) {
sprintf( Path, "Address/%s/Address", Name );
Address = (char*)DataTree->GetStr( NULL, Path, Address, true ); // Get address list value
}
else {
Address = (char*)DataTree->GetStr( TempMember, "Port/Address", NULL, true ); // Get default value
}
SetSerialHandle( Handle, Address );
// Update configuration if specified
if ((SerialConfig = DataTree->GetMember( TempMember, "Port/SerialConfig", false )))
{
ParityText = (char*)DataTree->GetStr( SerialConfig, "Parity", "none", true );
if (!strcasecmp( ParityText, "none" ))
Parity = NO_PARITY;
else if (!strcasecmp( ParityText, "odd" ))
Parity = ODD_PARITY;
else if (!strcasecmp( ParityText, "even" ))
Parity = EVEN_PARITY;
else if (!strcasecmp( ParityText, "mark" ))
Parity = MARK_PARITY;
FlowCtrlText = (char*)DataTree->GetStr( SerialConfig, "FlowCtrl", "none", true );
if (!strcasecmp( FlowCtrlText, "none" ))
FlowCtrl = NO_FLOWCTRL;
else if (!strcasecmp( FlowCtrlText, "hardware" ))
FlowCtrl = HW_FLOWCTRL;
else if (!strcasecmp( FlowCtrlText, "software" ))
FlowCtrl = SW_FLOWCTRL;
Handle->SerialConfig = true;
SetSerialHandleConfig( Handle, DataTree->GetInt( SerialConfig, "BaudRate", 19200, true ),
DataTree->GetInt( SerialConfig, "DataBits", 8, true ),
Parity, DataTree->GetInt( SerialConfig, "StopBits", 1, true ),
FlowCtrl, DataTree->GetInt( SerialConfig, "DataWait", 0, true ));
}
}
else if (!strcasecmp( Type, "LinePrinter" ))
{
if ((Name = (char*)DataTree->GetStr( TempMember, "Port/Name", NULL ))) {
sprintf( Path, "Address/%s/Address", Name );
Address = (char*)DataTree->GetStr( NULL, Path, NULL, true ); // Get address list value
}
else {
Address = (char*)DataTree->GetStr( TempMember, "Port/Address", NULL, true ); // Get default value
}
SetLinePrinterHandle( Handle, Address );
}
else if (!strcasecmp( Type, "TCPserver" ))
{
if ((Name = (char*)DataTree->GetStr( TempMember, "Socket/Name", NULL ))) {
sprintf( Path, "Address/%s/Address", Name );
Address = (char*)DataTree->GetStr( NULL, Path, NULL, true ); // Get AddressList Address value
sprintf( Path, "Address/%s/Port", Name );
Port = (char*)DataTree->GetStr( NULL, Path, "0", true ); // Get AddressList Port value
}
else {
Address = (char*)DataTree->GetStr( TempMember, "Socket/Address", NULL, true ); // Get default Address value
Port = (char*)DataTree->GetStr( TempMember, "Socket/Port", "0", true ); // Get default Port value
}
Delay = DataTree->GetInt( TempMember, "Socket/ResolveDelay", 0, true );
SetSocketHandle( Handle, ctServer, Address, strlcase(Port), Delay );
}
else if (!strcasecmp( Type, "TCPclient" ))
{
if ((Name = (char*)DataTree->GetStr( TempMember, "Socket/Name", NULL ))) {
sprintf( Path, "Address/%s/Address", Name );
Address = (char*)DataTree->GetStr( NULL, Path, NULL, true ); // Get AddressList Address value
sprintf( Path, "Address/%s/Port", Name );
Port = (char*)DataTree->GetStr( NULL, Path, "0", true ); // Get AddressList Port value
}
else {
Address = (char*)DataTree->GetStr( TempMember, "Socket/Address", NULL, true ); // Get default Address value
Port = (char*)DataTree->GetStr( TempMember, "Socket/Port", "0", true ); // Get default Port value
}
Delay = DataTree->GetInt( TempMember, "Socket/ResolveDelay", 0, true );
SetSocketHandle( Handle, ctClient, Address, strlcase(Port), Delay );
}
else if (!strcasecmp( Type, "ForkPipe" )) {
Address = (char*)DataTree->GetStr( TempMember, "Fork/ExecPath", NULL, true ); // Get default value
SetForkPipeHandle( Handle, Address );
}
// Set Auto Mange
SetAutoManage( Handle, DataTree->GetBool( TempMember, "AutoManage/Enabled", true, true ),
DataTree->GetBool( TempMember, "AutoManage/Persistent", false, true ),
DataTree->GetInt( TempMember, "AutoManage/ReopenDelay", 2000, true ),
DataTree->GetInt( TempMember, "AutoManage/CloseTimeout", 2000, true ));
// Input buffer
SetInBuffer( Handle, DataTree->GetInt( TempMember, "InputBuffer/Size", 0 ),
DataTree->GetInt( TempMember, "InputBuffer/Timeout", 250 ),
DataTree->GetStr( TempMember, "InputBuffer/Marker", "" ),
DataTree->GetInt( TempMember, "InputBuffer/MarkerLen", 0 ) );
SetOutBuffer( Handle, DataTree->GetInt( TempMember, "OutputBuffer/Size", 0 ) );
// Next
TempMember = DataTree->GetNextChild( TempMember );
}
return true;
}
//---------------------------------------------------------------------------
THandle * CSelectableCore::CreateHandle( const char * HandleName, bool CreateChannel )
{
THandle ** Handle = NULL;
// Find Handle by Name or get end of list
Handle = &FirstHandle;
while ( *Handle && strcmp( HandleName, (*Handle)->Name ))
Handle = &((*Handle)->Next);
// Create if necessary
if (!*Handle)
{
// Create File handle at end of list
*Handle = (THandle*)calloc( 1, sizeof(THandle) );
// Set name
if (HandleName) {
(*Handle)->Name = (char*)malloc( strlen(HandleName)+1 );
strcpy( (*Handle)->Name, HandleName );
}
// Set File Descriptor
(*Handle)->FD = -1;
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Created", Name, HandleName );
}
// Create Matching Channel
if (CreateChannel) {
(*Handle)->Channel = AddChannel( HandleName );
}
return *Handle;
}
//---------------------------------------------------------------------------
bool CSelectableCore::RemoveHandle( THandle * Handle )
{
THandle ** HandlePtr = NULL;
// Validate
if (!Handle || (Handle->State == csOpen) || (Handle->State == csWaitingtoOpen)) {
return false;
}
// Find in List
HandlePtr = &FirstHandle;
while (*HandlePtr && (*HandlePtr != Handle)) {
HandlePtr = &((*HandlePtr)->Next);
}
// Remove from list if found
if (*HandlePtr) {
*HandlePtr = (*HandlePtr)->Next;
}
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Removed", Name, Handle->Name );
// Destroy Child handle
DestroyHandle( Handle );
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::DestroyHandle( THandle * Handle )
{
// Validate Handle
if (!Handle)
return false;
// Clear parameters
if (Handle->Name)
free( Handle->Name );
if (Handle->Path)
free( Handle->Path );
if (Handle->HostName)
free( Handle->HostName );
if (Handle->PortName)
free( Handle->PortName );
if (Handle->AddressInfo)
freeaddrinfo( Handle->AddressList );
// Destroy Buffers
if (Handle->InBuffer)
delete Handle->InBuffer;
if (Handle->OutBuffer)
delete Handle->OutBuffer;
// Clear Input Markers
if (Handle->InMarker)
free( Handle->InMarker );
// Destroy Pointer
free( Handle );
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetSerialHandle( THandle * Handle, const char * FileName )
{
// Validate
if (!Handle || ((Handle->Type != ctNone) && (Handle->Type != ctSerial)) || !FileName) {
return false;
}
// Set Type
Handle->Type = ctSerial;
// Clear File Name
if (Handle->Path) {
free( Handle->Path );
}
// Set name
Handle->Path = (char*)malloc( strlen(FileName)+1 );
strcpy( Handle->Path, FileName );
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Set as Port [%s]", Name, Handle->Name, FileName );
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetSerialHandleConfig( THandle * Handle, int Baudrate, short DataBits, short Parity, short StopBits, short FlowCtrl, int DataWait )
{
// Validate
if (!Handle || (Handle->Type != ctSerial)) {
return false;
}
Handle->InBaudrate = Baudrate;
Handle->OutBaudrate = Baudrate;
Handle->DataBits = DataBits;
Handle->Parity = Parity;
Handle->StopBits = StopBits;
Handle->FlowCtrl = FlowCtrl;
Handle->DataWait = DataWait;
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetLinePrinterHandle( THandle * Handle, const char * FileName )
{
// Validate
if (!Handle || ((Handle->Type != ctNone) && (Handle->Type != ctLinePrinter)) || !FileName) {
return false;
}
// Set Type
Handle->Type = ctLinePrinter;
// Clear File Name
if (Handle->Path) {
free( Handle->Path );
}
// Set name
Handle->Path = (char*)malloc( strlen(FileName)+1 );
strcpy( Handle->Path, FileName );
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Set as Port [%s]", Name, Handle->Name, FileName );
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetForkPipeHandle( THandle * Handle, const char * ExecPath )
{
// Validate
if (!Handle || ((Handle->Type != ctNone) && (Handle->Type != ctForkPipe)) || !ExecPath) {
return false;
}
// Set Type
Handle->Type = ctForkPipe;
// Clear File Name
if (Handle->Path) {
free( Handle->Path );
}
// Set name
Handle->Path = (char*)malloc( strlen(ExecPath)+1 );
strcpy( Handle->Path, ExecPath );
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Set as ForkPipe [%s]", Name, Handle->Name, ExecPath );
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetSocketHandle( THandle * Handle, EConnectType Type, const char * HostName, const char * PortName, long ResolveDelay )
{
// Validate
if (!Handle || ((Handle->Type != ctNone) && (Handle->Type != ctServer) && (Handle->Type != ctClient) && (Handle->Type != ctRemoteClient)) ||
!((Type == ctServer) || (Type == ctClient) || (Type == ctRemoteClient)) || !HostName || !PortName ) {
return false;
}
// Set Type
Handle->Type = Type;
Handle->ResolveDelay = ResolveDelay;
// Clear HostName & Port Name
if (Handle->HostName)
free( Handle->HostName );
if (Handle->PortName)
free( Handle->PortName );
if (Handle->AddressList)
freeaddrinfo( Handle->AddressList );
// Set HostName & Port
Handle->HostName = (char*)malloc( strlen(HostName)+1 );
strcpy( Handle->HostName, HostName );
Handle->PortName = (char*)malloc( strlen(PortName)+1 );
strcpy( Handle->PortName, PortName );
Handle->AddressList = NULL;
Handle->AddressInfo = NULL;
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Set as %s [%s:%s]", Name, Handle->Name, ConnectTypeName[Type], HostName, PortName );
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::ClearHandle( THandle * Handle )
{
// Validate
if (!Handle) {
return false;
}
// Reset Type related parameters
if (Handle->Path) {
free( Handle->Path );
Handle->Path = NULL;
}
if (Handle->HostName) {
free( Handle->HostName );
Handle->HostName = NULL;
}
if (Handle->PortName) {
free( Handle->PortName );
Handle->PortName = NULL;
}
if (Handle->AddressList) {
freeaddrinfo( Handle->AddressList );
Handle->AddressList = NULL;
Handle->AddressInfo = NULL;
}
// Reset Parameters
Handle->Type = ctNone;
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Set as None", Name, Handle->Name );
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetCallback( THandle * Handle, EConnectState pState, FHandleCallback pCallback )
{
// Validate
if (!Handle) {
return false;
}
// Set callback
Handle->StateCallback[ (int)pState ] = pCallback;
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetAutoManage( THandle * Handle, bool AutoManage, bool Persistent, int ReopenDelay, int CloseTimeout )
{
// Validate
if (!Handle) {
return false;
}
// Set params
Handle->AutoManage = AutoManage;
Handle->Persistent = Persistent;
Handle->ReopenDelay = ReopenDelay;
Handle->CloseTimeout = CloseTimeout;
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetInBuffer( THandle * Handle, int InBufSize, int InTimeout, const char * InMarker, int InMarkerLen )
{
// Validate
if (!Handle) {
return false;
}
// Input Buffer
if (Handle->InBuffer) {
delete Handle->InBuffer;
Handle->InBuffer = NULL;
}
if (InBufSize) {
Handle->InBuffer = (CRollingBuffer*) new CRollingBuffer( InBufSize );
}
// Set Input Timeout
Handle->InTimeout = InTimeout;
// Set Input Markers
if (InMarkerLen && InMarker) {
Handle->InMarkerLen = InMarkerLen;
Handle->InMarker = (char *)malloc( InMarkerLen+1 );
memcpy( Handle->InMarker, InMarker, InMarkerLen );
Handle->InMarker[InMarkerLen] = 0;
}
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::SetOutBuffer( THandle * Handle, int OutBufSize )
{
// Validate
if (!Handle) {
return false;
}
// Output Buffer
if (Handle->OutBuffer) {
delete Handle->OutBuffer;
Handle->OutBuffer = NULL;
}
if (OutBufSize) {
Handle->OutBuffer = (CRollingBuffer*) new CRollingBuffer( OutBufSize );
}
return true;
}
//---------------------------------------------------------------------------
int CSelectableCore::OpenSerialPort( THandle * Handle )
{
// Validate
if (!Handle || (Handle->Type == ctNone)) {
return -1;
} else if (Handle->State == csOpen) {
return Handle->FD;
}
// Check if port exits
if (access( Handle->Path, F_OK ) != 0)
{
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Serial Port not found [%s]", Name, Handle->Name, Handle->Path );
return -1;
}
// Open Port
Handle->FD = open( Handle->Path, O_RDWR );
if (Handle->FD == -1)
{
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Could not open Serial Port [%s]", Name, Handle->Name, Handle->Path );
return -1;
}
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Serial Port opened [%s]", Name, Handle->Name, Handle->Path );
// Update port configuration
if (Handle->SerialConfig)
WriteSerialConfig( Handle );
else
ReadSerialConfig( Handle );
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Serial Port config, IB:%d, OB:%d, D:%d, P:%d, S:%d, F:%d", Name, Handle->Name,
Handle->InBaudrate, Handle->OutBaudrate, Handle->DataBits, Handle->Parity, Handle->StopBits, Handle->FlowCtrl );
// Add to Select Lists
if (Selector) {
Selector->Add( Handle->FD, true, false, this );
}
// Set state
ChangeState( Handle, csOpen );
return Handle->FD;
}
//---------------------------------------------------------------------------
int CSelectableCore::OpenLinePrinterPort( THandle * Handle )
{
// Validate
if (!Handle || (Handle->Type == ctNone)) {
return -1;
} else if (Handle->State == csOpen) {
return Handle->FD;
}
// Check if port exits
if (access( Handle->Path, F_OK ) != 0)
{
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Printer Port not found [%s]", Name, Handle->Name, Handle->Path );
return -1;
}
// Open Port
Handle->FD = open( Handle->Path, O_RDWR );
if (Handle->FD == -1)
{
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Could not open Printer Port [%s]", Name, Handle->Name, Handle->Path );
return -1;
}
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Printer Port opened [%s]", Name, Handle->Name, Handle->Path );
// Add to Select Lists
if (Selector) {
Selector->Add( Handle->FD, true, false, this );
}
// Set state
ChangeState( Handle, csOpen );
return Handle->FD;
}
//---------------------------------------------------------------------------
int CSelectableCore::OpenForkPipe( THandle * Handle )
{
int pipefd[2];
int newfd;
char * Args[50];
int Count = 0;
// Validate
if (!Handle || (Handle->Type == ctNone)) {
return -1;
} else if (Handle->State == csOpen) {
return Handle->FD;
}
// Validate Exec path
if (!Handle->Path || !*(Handle->Path))
{
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Invalid path for Exec", Name, Handle->Name );
return -1;
}
// Create Pipe
if ((pipe( pipefd ) == -1) || (pipefd[0] == -1) || (pipefd[1] == -1))
{
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Could not open Pipe (%s)", Name, Handle->Name, strerror(errno) );
return -1;
}
// Fork process
Handle->ChildPID = fork();
if (Handle->ChildPID < 0)
{
// Fork has failed
// Close both ends of the pipe
close( pipefd[0] );
close( pipefd[1] );
Handle->FD = -1;
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Could not Fork process, %s", Name, Handle->Name, strerror(errno) );
return -1;
}
else if (Handle->ChildPID > 0)
{
// Fork success - this is parent
// Close Read-end of pipe, but keep the Write-end
close( pipefd[0] );
Handle->FD = pipefd[1];
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Process forked successfully", Name, Handle->Name );
}
else
{
// Fork success - this is child
// Close Write-end of pipe
close( pipefd[1] );
// Replace stdin with Read-end of pipe
close( 0 );
newfd = dup2( pipefd[0], 0 );
close( pipefd[0] );
if (newfd != 0) {
// Replace failed, exit immediately
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Stdin redirect failed on forked process", Name, Handle->Name );
exit( EXIT_FAILURE );
}
// Fork : Replace Processing image
BuildArgs( Handle->Path, Count, Args );
// printf( "Execute params [%d]:\n", Count );
// int i = 0;
// while (Args[i]) {
// printf( "%d: %s\n", i, Args[i] );
// i++;
// }
execvp( Args[0], Args );
// Replace failed, exit immediately
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Exec on forked process failed", Name, Handle->Name );
exit( EXIT_FAILURE );
}
// Add to Select Lists
if (Selector) {
Selector->Add( Handle->FD, false, true, this );
}
// Set state
ChangeState( Handle, csOpen );
return Handle->FD;
}
//---------------------------------------------------------------------------
bool CSelectableCore::ResolveAddress( THandle * Handle, bool DelayResolve )
{
struct addrinfo hints;
int result;
// Check if resolved address available
if (Handle->AddressInfo)
{
// Return if address still valid
if (!Handle->AddressFailed)
return true;
// Get next address
Handle->AddressInfo = Handle->AddressInfo->ai_next;
Handle->AddressFailed = false;
}
// Dismiss invalid "0.0.0.0" address
if (Handle->AddressInfo && !strcmp( inet_ntoa(((struct sockaddr_in *)Handle->AddressInfo->ai_addr)->sin_addr), "0.0.0.0" ))
Handle->AddressInfo = Handle->AddressInfo->ai_next;
// Check valid address
if (Handle->AddressInfo) {
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Use next resolved Address [%s:%s]->[%s:%u]",
Name, Handle->Name, Handle->HostName, Handle->PortName,
inet_ntoa(((struct sockaddr_in *)Handle->AddressInfo->ai_addr)->sin_addr),
ntohs(((struct sockaddr_in *)Handle->AddressInfo->ai_addr)->sin_port) );
return true;
}
// Clear existing address list
if (Handle->AddressList) {
freeaddrinfo( Handle->AddressList );
Handle->AddressList = NULL;
Handle->AddressInfo = NULL;
}
// Set address specification
memset( &hints, 0, sizeof hints );
hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
hints.ai_socktype = SOCK_STREAM;
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Resolving Host name [%s:%s]...",
Name, Handle->Name, Handle->HostName, Handle->PortName );
// Should address be resolved later during process()
if (DelayResolve)
{
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Delay resolving of Host Name [%s:%s]",
Name, Handle->Name, Handle->HostName, Handle->PortName );
ChangeState( Handle, csOpenRequest );
return false;
}
// Resolve Host & Port Names
if ((result = getaddrinfo( Handle->HostName, Handle->PortName, &hints, &(Handle->AddressList))) != 0)
{
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Failed to resolve Host Name [%s:%s] (%s)",
Name, Handle->Name, Handle->HostName, Handle->PortName, gai_strerror(result) );
ChangeState( Handle, csFailed );
return false;
}
// Select first address, skip "0.0.0.0"
Handle->AddressInfo = Handle->AddressList;
if (!strcmp( inet_ntoa(((struct sockaddr_in *)Handle->AddressInfo->ai_addr)->sin_addr), "0.0.0.0" ))
Handle->AddressInfo = Handle->AddressInfo->ai_next;
if (!Handle->AddressInfo) {
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Failed to resolve Host Name [%s:%s] (%s)",
Name, Handle->Name, Handle->HostName, Handle->PortName, gai_strerror(result) );
freeaddrinfo( Handle->AddressList );
Handle->AddressList = NULL;
Handle->AddressInfo = NULL;
ChangeState( Handle, csFailed );
return false;
}
// Return address
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Host name resolved [%s:%s]->[%s:%u]",
Name, Handle->Name, Handle->HostName, Handle->PortName,
inet_ntoa(((struct sockaddr_in *)Handle->AddressInfo->ai_addr)->sin_addr),
ntohs(((struct sockaddr_in *)Handle->AddressInfo->ai_addr)->sin_port) );
return true;
}
//---------------------------------------------------------------------------
int CSelectableCore::OpenServerSocket( THandle * Handle, bool DelayResolve )
{
// Socket options
struct linger ServerLinger_opt;
ServerLinger_opt.l_onoff = 1;
ServerLinger_opt.l_linger = 5;
int Reuse_opt = 1;
int KeepAlive_opt = 1;
int TCPidle_opt = 60;
int TCPint_opt = 15;
int TCPcnt_opt = 3;
// Validate Handle
if (Handle->Type != ctServer) {
return false;
}
// Resolve Host & Port Names
if (!ResolveAddress( Handle, DelayResolve ))
return -1;
// Create socket
if ((Handle->FD = socket( Handle->AddressInfo->ai_family, Handle->AddressInfo->ai_socktype, Handle->AddressInfo->ai_protocol )) < 0)
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Failed to create TCP Server socket [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Set state
ChangeState( Handle, csFailed );
return -1;
};
// Configure connection
if ((setsockopt( Handle->FD, SOL_SOCKET, SO_LINGER, &ServerLinger_opt, sizeof(ServerLinger_opt)) == -1) ||
(setsockopt( Handle->FD, SOL_SOCKET, SO_REUSEADDR, &Reuse_opt, sizeof(Reuse_opt)) == -1))
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Could not set socket options [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Set state
ChangeState( Handle, csFailed );
return -1;
}
// Configure TCP keep alive settings
if ((setsockopt( Handle->FD, SOL_SOCKET, SO_KEEPALIVE, &KeepAlive_opt, sizeof(KeepAlive_opt)) == -1) ||
(setsockopt( Handle->FD, SOL_TCP, TCP_KEEPIDLE, &TCPidle_opt, sizeof(TCPidle_opt)) == -1) ||
(setsockopt( Handle->FD, SOL_TCP, TCP_KEEPCNT, &TCPcnt_opt, sizeof(TCPcnt_opt)) == -1) ||
(setsockopt( Handle->FD, SOL_TCP, TCP_KEEPINTVL, &TCPint_opt, sizeof(TCPint_opt)) == -1) )
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Could not set KeepAlive options [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Set state
ChangeState( Handle, csFailed );
return -1;
}
// Set non-blocking flag
int flags = fcntl( Handle->FD, F_GETFL, 0 );
fcntl( Handle->FD, F_SETFL, flags | O_NONBLOCK );
// Bind socket
if (bind( Handle->FD, Handle->AddressInfo->ai_addr, Handle->AddressInfo->ai_addrlen ) < 0)
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Failed to bind TCP Server socket [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Set state
close( Handle->FD );
Handle->FD = -1;
ChangeState( Handle, csFailed );
Handle->AddressFailed = true;
return -1;
};
// Create que for 5 connections
if (listen( Handle->FD, 5 ) < 0)
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Failed to listen on TCP Server socket [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Set state
close( Handle->FD );
Handle->FD = -1;
ChangeState( Handle, csFailed );
Handle->AddressFailed = true;
return -1;
};
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Server binded and listening [%s:%s]", Name, Handle->Name, Handle->HostName, Handle->PortName );
// Add to Select Lists
if (Selector) {
Selector->Add( Handle->FD, true, false, this );
}
// Set state
ChangeState( Handle, csOpen );
return Handle->FD;
}
//---------------------------------------------------------------------------
int CSelectableCore::OpenRemoteClientSocket( THandle * Handle )
{
THandle ** RemoteClient;
int ClientFD;
char ClientAddress[50];
char ClientPort[20];
char ClientName[100];
socklen_t addr_len;
struct sockaddr_in address;
// Validate
if (!Handle) {
return -1;
}
// Check Handle type
if (Handle->Type == ctServer)
{
// Accept connection on current socket
addr_len = sizeof( address );
if ((ClientFD = accept( Handle->FD, (struct sockaddr *)&address, &addr_len)) == -1)
{
// Log Event
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Server failed to accept blocking connection (%s)", Name, Handle->Name, strerror(errno) );
}
else {
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Server failed to accept connection (%s)", Name, Handle->Name, strerror(errno) );
}
return -1;
}
// Set non-blocking flag
int flags = fcntl( ClientFD, F_GETFL, 0 );
fcntl( ClientFD, F_SETFL, flags | O_NONBLOCK );
// Return client address
strcpy( ClientAddress, inet_ntoa(address.sin_addr) );
sprintf( ClientPort, "%d", ntohs(address.sin_port) );
// Get end of client list
RemoteClient = &FirstHandle;
while (*RemoteClient) {
RemoteClient = &((*RemoteClient)->Next);
}
// Create Remote Client Handle
sprintf( ClientName, "%s-%d", Handle->Name, ClientFD );
*RemoteClient = CreateHandle( ClientName, false );
if (!SetSocketHandle( *RemoteClient, ctRemoteClient, ClientAddress, ClientPort, 0 )) {
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Server failed to configure Remote TCP Client connection (%s)", Name, Handle->Name, strerror(errno) );
return -1;
}
// Copy Parent Buffer setup
SetInBuffer( *RemoteClient, ((Handle->InBuffer)? Handle->InBuffer->Size() : 0),
Handle->InTimeout, Handle->InMarker, Handle->InMarkerLen );
SetOutBuffer( *RemoteClient, ((Handle->OutBuffer)? Handle->OutBuffer->Size() : 0) );
// Set Key parameters
(*RemoteClient)->FD = ClientFD;
(*RemoteClient)->Parent = Handle;
(*RemoteClient)->State = csWaitingtoOpen;
// Reset Timer
SetStartTime( &((*RemoteClient)->LastAction) );
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Server accepted Remote TCP Client connection [%s]", Name, Handle->Name, ClientAddress );
// Add to Select Lists
if (Selector) {
Selector->Add( (*RemoteClient)->FD, true, true, this );
}
return (*RemoteClient)->FD;
}
else if (Handle->Type == ctRemoteClient)
{
// Check state
if (Handle->State == csOpen)
{
// Already open
return Handle->FD;
}
else if (Handle->State == csWaitingtoOpen)
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Remote TCP Client connection open [%s]", Name, Handle->Name, Handle->HostName );
// Update state
ChangeState( Handle, csOpen );
return Handle->FD;
}
}
return -1;
}
//---------------------------------------------------------------------------
int CSelectableCore::OpenClientSocket( THandle * Handle, bool DelayResolve )
{
// Socket options
int KeepAlive_opt = 1;
int TCPidle_opt = 5;
int TCPcnt_opt = 3;
int TCPint_opt = 2;
// Check state
if (Handle->State == csOpen) {
// Already open
return Handle->FD;
}
if (Handle->State != csWaitingtoOpen)
{
// Resolve IP Address
if (!ResolveAddress( Handle, DelayResolve ))
return -1;
// Create File descriptor
if ((Handle->FD = socket( Handle->AddressInfo->ai_family, Handle->AddressInfo->ai_socktype, Handle->AddressInfo->ai_protocol )) < 0)
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Failed to create TCP Client socket [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Set Status
ChangeState( Handle, csFailed );
return -1;
};
// Set Non blocking open
int flags = fcntl( Handle->FD, F_GETFL, 0 );
fcntl( Handle->FD, F_SETFL, O_NONBLOCK|flags );
// Configure TCP keep alive settings
if ((setsockopt( Handle->FD, SOL_SOCKET, SO_KEEPALIVE, &KeepAlive_opt, sizeof(KeepAlive_opt)) == -1) ||
(setsockopt( Handle->FD, SOL_TCP, TCP_KEEPIDLE, &TCPidle_opt, sizeof(TCPidle_opt)) == -1) ||
(setsockopt( Handle->FD, SOL_TCP, TCP_KEEPCNT, &TCPcnt_opt, sizeof(TCPcnt_opt)) == -1) ||
(setsockopt( Handle->FD, SOL_TCP, TCP_KEEPINTVL, &TCPint_opt, sizeof(TCPint_opt)) == -1) )
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Could not set KeepAlive options [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Set State
close( Handle->FD );
Handle->FD = -1;
ChangeState( Handle, csFailed );
return -1;
}
}
// Try to connect to address
if (!connect( Handle->FD, Handle->AddressInfo->ai_addr, Handle->AddressInfo->ai_addrlen ))
{
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Client connected [%s:%s]", Name, Handle->Name, Handle->HostName, Handle->PortName );
// Add to Select Lists
if (Selector) {
Selector->Add( Handle->FD, true, true, this );
}
// Set status
ChangeState( Handle, csOpen );
return Handle->FD;
}
else if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINPROGRESS) || (errno == EALREADY))
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Client waiting to connect [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Add to Select Lists
if (Selector) {
Selector->Add( Handle->FD, true, true, this );
}
// Set status
ChangeState( Handle, csWaitingtoOpen );
return Handle->FD;
}
else
{
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Client could not connect [%s:%s] (%s)", Name, Handle->Name, Handle->HostName, Handle->PortName, strerror(errno) );
// Remove from Select List
if (Selector) {
Selector->Remove( Handle->FD, true, true );
}
// Close socket
close( Handle->FD );
ChangeState( Handle, csFailed );
Handle->AddressFailed = true;
// Reset Handle
Handle->FD = -1;
return -1;
}
}
//---------------------------------------------------------------------------
int CSelectableCore::Open( THandle * Handle, bool DelayResolve )
{
int FD = -1;
// Validate
if (!Handle) {
return -1;
}
// Open correctly
switch (Handle->Type) {
case ctSerial :
FD = OpenSerialPort( Handle );
break;
case ctLinePrinter :
FD = OpenLinePrinterPort( Handle );
break;
case ctForkPipe :
FD = OpenForkPipe( Handle );
break;
case ctServer :
FD = OpenServerSocket( Handle, DelayResolve );
break;
case ctClient :
FD = OpenClientSocket( Handle, DelayResolve );
break;
case ctRemoteClient :
FD = OpenRemoteClientSocket( Handle );
break;
default:
FD = -1;
}
// Set timer (for re-open or auto-close)
SetStartTime( &Handle->LastAction );
return FD;
};
//---------------------------------------------------------------------------
// Delete socket
bool CSelectableCore::Close( THandle * Handle, bool QuickReopen )
{
bool Fail;
THandle * ChildHandle = NULL;
THandle * NextHandle = NULL;
// Validate
if (!Handle || (Handle->FD == -1))
return false;
// Close Children
if (Handle->Type == ctServer)
{
ChildHandle = FirstHandle;
while (ChildHandle)
{
if (ChildHandle->Parent == Handle)
{
// Close and remove handle
NextHandle = ChildHandle->Next;
Close( ChildHandle, false );
ChildHandle = NextHandle;
}
else
{
// Skip Handle
ChildHandle = ChildHandle->Next;
}
}
}
// Close Handle
Fail = (close( Handle->FD ))? true : false;
ChangeState( Handle, ((Fail)? csFailed : csClosed) );
// Start timer (for re-open)
if (QuickReopen)
ClearStartTime( &(Handle->LastAction) );
else
SetStartTime( &(Handle->LastAction) );
// Show action
switch (Handle->Type)
{
case ctSerial:
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Serial Port %s [%s]", Name, Handle->Name, ((Fail)? "failed" : "closed"), Handle->Path );
break;
case ctLinePrinter:
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Printer Port %s [%s]", Name, Handle->Name, ((Fail)? "failed" : "closed"), Handle->Path );
break;
case ctForkPipe:
// Log Event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Forked Pipe %s", Name, Handle->Name, ((Fail)? "failed" : "closed"));
break;
case ctServer:
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Server %s [%s:%s]", Name, Handle->Name, ((Fail)? "failed" : "closed"), Handle->HostName, Handle->PortName );
break;
case ctRemoteClient:
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Remote TCP Client connection %s [%s]", Name, Handle->Name, ((Fail)? "failed" : "closed"), Handle->HostName );
break;
case ctClient:
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - TCP Client connection %s [%s:%s]", Name, Handle->Name, ((Fail)? "failed" : "closed"), Handle->HostName, Handle->PortName );
break;
case ctNone:
default:
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - %s, invalid Handle type", Name, Handle->Name, ((Fail)? "failed" : "closed") );
break;
};
// Remove from Select List
if (!Fail && Selector) {
Selector->Remove( Handle->FD, true, true );
}
// Reset FD
Handle->FD = ((Fail)? Handle->FD : -1);
return true;
}
//---------------------------------------------------------------------------
// Device Interface
bool CSelectableCore::Read( THandle * Handle )
{
int ClientFD = -1;
int BytesRead = 0;
int BytesWaiting = -1;
// Validate
if (!Handle || (Handle->State == csNone) || (Handle->State == csFailed) || (Handle->State == csClosed)) {
return false;
}
// Log Read Event
if (Log) Log->Message( LogLevel, dlHigh, "%s: Handle '%s' - Read Event", Name, Handle->Name );
// Check for closing/opening event on Socket
if (Handle->Type == ctServer)
{
// Incoming client request
ClientFD = OpenRemoteClientSocket( Handle );
if (ClientFD == -1) {
return false;
}
// Reset Timer
SetStartTime( &(Handle->LastAction) );
// Add to Select Lists
if (Selector) {
Selector->Add( ClientFD, true, true, this );
}
return true;
}
else if ((Handle->Type == ctRemoteClient) || (Handle->Type == ctClient))
{
// Check if socket ready (non-block open in progress)
if (Handle->State == csWaitingtoOpen)
{
if (Handle->Type == ctRemoteClient) {
OpenRemoteClientSocket( Handle );
} else if (Handle->Type == ctClient) {
OpenClientSocket( Handle, true );
}
// Reset Timer (for auto-close)
SetStartTime( &(Handle->LastAction) );
return true;
}
// Check if anything to read
ioctl( Handle->FD, FIONREAD, &BytesWaiting );
// Error or EOF from server
if (BytesWaiting < 1)
{
// Log if there is an error
if (Log && (BytesWaiting < 1))
Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Data waiting error (%s)", Name, Handle->Name, strerror(errno) );
// Close Handle
Close( Handle, false );
// Destroy Client
if (Handle->Type == ctRemoteClient) {
RemoveHandle( Handle );
}
return false;
}
}
else if (Handle->Type == ctSerial)
{
// Check if anything to read
ioctl( Handle->FD, FIONREAD, &BytesWaiting );
// Error on port
if (BytesWaiting < 0)
{
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Data waiting error (%s)", Name, Handle->Name, strerror(errno) );
// Close Handle
Close( Handle, false );
return false;
}
}
else if (Handle->Type == ctLinePrinter)
{
// // Check if anything to read
// ioctl( Handle->FD, FIONREAD, &BytesWaiting ); <-- Not valid ioctl for lp port
//
// // Error on port
// if (BytesWaiting < 0)
// {
// if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Data waiting error (%s)", Name, Handle->Name, strerror(errno) );
//
// // Close Handle
// Close( Handle, false );
// return false;
// }
}
// Validate
if (Handle->State != csOpen) {
return false;
}
// Read File directly into buffer
if (Handle->InBuffer && (BytesRead = Handle->InBuffer->ReadFromFD( Handle->FD, BytesWaiting )))
{
// Process Buffer
ProcessInputBuffer( Handle, false );
}
// Reset timer
SetStartTime( &(Handle->InStart) );
return (bool)BytesRead;
}
//---------------------------------------------------------------------------
bool CSelectableCore::Write( THandle * Handle )
{
int Len = 0;
char * Data = NULL;
int BytesWritten = 0;
// Validate
if (!Handle)
return false;
// Is Handle open?
if ((Handle->State == csNone) || (Handle->State == csFailed) || (Handle->State == csClosed))
{
// May it be opened?
if (!Handle->AutoManage)
{
// Must be opened manually
return false;
}
else if (Timeout( Handle->LastAction, Handle->ReopenDelay ))
{
// Attempt to re-open port
Open( Handle, true );
}
}
// Log Ready for Write Event
if (Log) Log->Message( LogLevel, dlHigh, "%s: Handle '%s' - Write Event", Name, Handle->Name );
if (Handle->State == csWaitingtoOpen)
{
// Complete socket open process
if (Handle->Type == ctRemoteClient) {
OpenRemoteClientSocket( Handle );
} else if (Handle->Type == ctClient) {
OpenClientSocket( Handle, true );
}
// Reset Timer (for auto-close)
SetStartTime( &(Handle->LastAction) );
// Remove from set for select write
if (Selector) {
Selector->Remove( Handle->FD, false, true );
}
return true;
}
else if (Handle->State == csOpen)
{
if (Handle->OutBuffer)
{
// Write to FD directly from output buffer
if ((BytesWritten = Handle->OutBuffer->WriteToFD( Handle->FD )))
{
if (LogLevel >= dlHigh) {
// Show event
Len = Handle->OutBuffer->Peek( &Data );
if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s: Handle '%s' - OUT:", Name, Handle->Name );
}
// Update Buffer
Handle->OutBuffer->Clear( BytesWritten );
// Reset Timer
SetStartTime( &(Handle->LastAction) );
}
// Check if Buffer emtpy
if (!Handle->OutBuffer->Len()) {
// Add to Select Write list
if (Selector) {
Selector->Remove( Handle->FD, false, true );
}
}
}
else
{
// No Output buffer to write from, so remove from Write list
if (Selector) {
Selector->Remove( Handle->FD, false, true );
}
}
return true;
}
return false;
}
//---------------------------------------------------------------------------
bool CSelectableCore::ProcessInputBuffer( THandle * Handle, bool Force )
{
int Pos = 0;
int Len = 0;
char * Data = NULL;
// Check if buffered data
if (!Handle || !Handle->InBuffer || !Handle->InBuffer->Len()) {
return false;
}
// Check if forced processed
if (Force || !Handle->InMarkerLen)
{
// Show Packet
Len = Handle->InBuffer->Peek( &Data );
if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s: Handle '%s' - IN-T:", Name, Handle->Name );
// Write buffer to Outputs
if (Handle->Type == ctRemoteClient) {
Output( Handle->Parent->Channel, Data, Len );
} else {
Output( Handle->Channel, Data, Len );
}
// Clear processed bytes from buffer
Handle->InBuffer->Clear( Len );
}
else
{
// Search for end of packet marker
while (Handle->InBuffer->FindStr( Handle->InMarker, Handle->InMarkerLen, Pos ))
{
// Show Packet
Len = Handle->InBuffer->Peek( &Data, 0, Pos+Handle->InMarkerLen );
if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s: Handle '%s' - IN-M:", Name, Handle->Name );
// Write buffer to Outputs
if (Handle->Type == ctRemoteClient) {
Output( Handle->Parent->Channel, Data, Len );
} else {
Output( Handle->Channel, Data, Len );
}
// Clear processed bytes from buffer
Handle->InBuffer->Clear( Len );
}
}
return true;
}
//---------------------------------------------------------------------------
int CSelectableCore::ReadFromFD( int FD, char * Data, int MaxLen )
{
int BytesRead = 0;
int TotalRead = 0;
int DataRemain = 0;
// Check if buffer created
if ((FD == -1) || !Data) {
return 0;
}
// Read Data into buffer
DataRemain = (MaxLen == -1)? strlen(Data) : MaxLen;
while (DataRemain)
{
// Read from file descriptor
BytesRead = read( FD, &Data[TotalRead], DataRemain );
if ((BytesRead <= 0)) {
break;
}
// Update Data Pointers
TotalRead += BytesRead;
DataRemain -= BytesRead;
}
return TotalRead;
}
//---------------------------------------------------------------------------
int CSelectableCore::WriteToFD( int FD, const char * Data, int Len, bool Force )
{
int BytesWritten = 0;
int TotalWritten = 0;
int DataRemain = 0;
// Check if buffer created
if ((FD == -1) || !Data) {
return 0;
}
// Read Data into buffer
DataRemain = (Len == -1)? strlen(Data) : Len;
while (DataRemain)
{
// Read from file descriptor
BytesWritten = write( FD, &Data[TotalWritten], DataRemain );
if ((BytesWritten <= 0) && (!Force || (errno != EAGAIN))) {
break;
}
// Update Data Pointers
TotalWritten += BytesWritten;
DataRemain -= BytesWritten;
}
return TotalWritten;
}
//---------------------------------------------------------------------------
int CSelectableCore::Input( const char * ChannelName, const char * Data, int Len )
{
TChannel * Channel = NULL;
THandle * Handle = NULL;
int HandleCount = 0;
int TempWritten = 0;
int BytesWritten = 0;
// Validate
if (!ChannelName || !Data) {
return 0;
}
// Get Channel
if (!(Channel = GetChannel( ChannelName ))) {
// Channel not found
if (Log) Log->Message( LogLevel, dlHigh, "%s: Channel '%s' - Input rejected, Channel not found", Name, ChannelName );
return 0;
}
else if (!Channel->InputEnabled) {
// Channel disabled
if (Log) Log->Message( LogLevel, dlHigh, "%s: Channel '%s' - Input rejected, Channel input disabled", Name, ChannelName );
return 0;
}
// Log event
if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s: Channel '%s' - IN:", Name, ChannelName );
// Find Linked handle
Handle = FirstHandle;
while( Handle )
{
if (Handle->Channel && !strcasecmp( ChannelName, Handle->Channel->Name ))
{
// Input to Handle
TempWritten = OutputHandle( Handle, Data, Len );
BytesWritten = (TempWritten > BytesWritten)? TempWritten : BytesWritten;
HandleCount++;
}
Handle = Handle->Next;
}
if (!HandleCount) {
// Handle not found
if (Log) Log->Message( LogLevel, dlHigh, "%s: Channel '%s' - Input rejected, No Handles not found", Name, ChannelName );
return 0;
}
return BytesWritten;
}
//---------------------------------------------------------------------------
int CSelectableCore::OutputHandle( THandle * Handle, const char * Data, int Len )
{
THandle * ChildHandle = NULL;
int BytesWritten = 0;
if ((Handle->State != csOpen))
{
// Check if auto-managed handle
if (!Handle->AutoManage)
{
// Handle is not open or auto-managed
if (Log) Log->Message( LogLevel, dlHigh, "%s: Handle '%s' - Input rejected, Handle not Open (not auto-managed)", Name, Handle->Name );
return 0;
}
else if (Timeout( Handle->LastAction, Handle->ReopenDelay ))
{
// Complete opening process
Open( Handle, true );
// Check if Handle is open
if (Handle->State == csOpenRequest) {
if (Log) Log->Message( LogLevel, dlHigh, "%s: Handle '%s' - Input rejected, Request to resolve (auto-managed) Handle", Name, Handle->Name );
return 0;
}
else if (Handle->State == csWaitingtoOpen) {
if (Log) Log->Message( LogLevel, dlHigh, "%s: Handle '%s' - Input rejected, Waiting to open (auto-managed) Handle", Name, Handle->Name );
return 0;
}
else if (Handle->State != csOpen) {
if (Log) Log->Message( LogLevel, dlHigh, "%s: Handle '%s' - Input rejected, (auto-managed) Handle failed to Open", Name, Handle->Name );
return 0;
}
}
else
{
if (Log) Log->Message( LogLevel, dlHigh, "%s: Handle '%s' - Input rejected, Retry (auto-managed) Handle re-open in %d ms", Name, Handle->Name,
TimeLeft( Handle->LastAction, Handle->ReopenDelay) );
return 0;
}
}
// Check packet length
if (Len == -1) {
Len = strlen( Data );
}
if (Handle->Type == ctServer)
{
// Cannot write to server socket, so Update Remote Client connections individually
ChildHandle = FirstHandle;
while (ChildHandle)
{
// Check if child
if (ChildHandle->Parent == Handle)
{
// Decide where to put data
if (ChildHandle->OutBuffer)
{
// Write to buffer
BytesWritten = ChildHandle->OutBuffer->Push( true, Data, Len );
// Add to select write list
if (BytesWritten && Selector) {
Selector->Add( ChildHandle->FD, false, true, this );
}
}
else
{
// Show event
if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s: Handle '%s' - OUT:", Name, ChildHandle->Name );
// Write directly to handle
BytesWritten = WriteToFD( ChildHandle->FD, Data, Len, true );
// Reset Timer
SetStartTime( &(Handle->LastAction) );
}
}
// Next
ChildHandle = ChildHandle->Next;
}
// Cannot verify individually, so assume all bytes was written
BytesWritten = Len;
}
else
{
// Decide where to put data
if (Handle->OutBuffer)
{
// Write to buffer
BytesWritten = Handle->OutBuffer->Push( true, Data, Len );
// Add to select write list
if (BytesWritten && Selector) {
Selector->Add( Handle->FD, false, true, this );
}
}
else
{
// Show event
if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s: Handle '%s' - OUT:", Name, Handle->Name );
// Write directly to handle
if ((BytesWritten = WriteToFD( Handle->FD, Data, Len, true )))
{
// Reset Timer
SetStartTime( &(Handle->LastAction) );
}
}
}
return BytesWritten;
}
//---------------------------------------------------------------------------
bool CSelectableCore::Process()
{
THandle * Handle = NULL;
// Check all handles
Handle = FirstHandle;
while (Handle)
{
// Auto manage handles
if ((Handle->State == csOpenRequest))
{
// Resolve then open socket
if (Timeout( Handle->LastAction, Handle->ResolveDelay )) {
Open( Handle, false );
}
}
else if (((Handle->State != csOpen) && Handle->AutoManage && Handle->Persistent) )
{
// Try to re-open port after delay
if (Timeout( Handle->LastAction, Handle->ReopenDelay )) {
Open( Handle, false );
}
}
// Check Input buffers
if (Handle->InBuffer && (Handle->InBuffer->Len() > 0))
{
// Check duration since last PortIn
if (Timeout( Handle->InStart, Handle->InTimeout ))
{
// Process Input
ProcessInputBuffer( Handle, true );
// Reset timer
ClearStartTime( &(Handle->InStart) );
}
}
// Check for auto close (but not on servers)
if ((Handle->State == csOpen) && (Handle->Type != ctServer) && Handle->AutoManage && !Handle->Persistent)
{
// Close port after timeout
if (Timeout( Handle->LastAction, Handle->CloseTimeout )) {
Close( Handle, true );
}
}
Handle = Handle->Next;
}
return true;
}
//---------------------------------------------------------------------------
// Set serial port configuration parameters
bool CSelectableCore::WriteSerialConfig( THandle * Handle )
{
struct termios newtio;
int flags = 0;
speed_t inbaud = 0;
speed_t outbaud = 0;
int mcs = 0;
// Get Handle
if (!Handle || (Handle->Type != ctSerial)) {
return false;
}
// Flush Data from port
tcflush( Handle->FD, TCIFLUSH );
// Set Open flags
flags = fcntl( Handle->FD, F_GETFL, 0 );
fcntl( Handle->FD, F_SETFL, flags & ~O_NDELAY );
// Read options
if (tcgetattr( Handle->FD, &newtio ) != 0)
return false;
// Process the baud rate
switch (Handle->InBaudrate)
{
case 921600: inbaud = B921600; break;
case 576000: inbaud = B576000; break;
case 460800: inbaud = B460800; break;
case 230400: inbaud = B230400; break;
//case 128000: _inbaud = B128000; break;
case 115200: inbaud = B115200; break;
//case 76800: _inbaud = B76800; break;
case 57600: inbaud = B57600; break;
case 38400: inbaud = B38400; break;
//case 28800: _inbaud = B28800; break;
case 19200: inbaud = B19200; break;
//case 14400: _inbaud = B14400; break;
case 9600: inbaud = B9600; break;
case 4800: inbaud = B4800; break;
case 2400: inbaud = B2400; break;
case 1800: inbaud = B1800; break;
case 1200: inbaud = B1200; break;
case 600: inbaud = B600; break;
case 300: inbaud = B300; break;
case 200: inbaud = B200; break;
case 150: inbaud = B150; break;
case 134: inbaud = B134; break;
case 110: inbaud = B110; break;
case 75: inbaud = B75; break;
case 50: inbaud = B50; break;
default: inbaud = B9600; break;
}
switch (Handle->OutBaudrate)
{
case 921600: outbaud = B921600; break;
case 576000: outbaud = B576000; break;
case 460800: outbaud = B460800; break;
case 230400: outbaud = B230400; break;
//case 128000: _outbaud = B128000; break;
case 115200: outbaud = B115200; break;
//case 76800: _outbaud = B76800; break;
case 57600: outbaud = B57600; break;
case 38400: outbaud = B38400; break;
//case 28800: _outbaud = B28800; break;
case 19200: outbaud = B19200; break;
//case 14400: _outbaud = B14400; break;
case 9600: outbaud = B9600; break;
case 4800: outbaud = B4800; break;
case 2400: outbaud = B2400; break;
case 1800: outbaud = B1800; break;
case 1200: outbaud = B1200; break;
case 600: outbaud = B600; break;
case 300: outbaud = B300; break;
case 200: outbaud = B200; break;
case 150: outbaud = B150; break;
case 134: outbaud = B134; break;
case 110: outbaud = B110; break;
case 75: outbaud = B75; break;
case 50: outbaud = B50; break;
default: outbaud = B9600; break;
}
// Set Baud rate
cfsetispeed( &newtio, (speed_t)inbaud );
cfsetospeed( &newtio, (speed_t)outbaud );
// Generate Mark/Space parity
if ((Handle->DataBits == 7) && ((Handle->Parity == MARK_PARITY) || (Handle->Parity == SPACE_PARITY)))
Handle->DataBits = 8;
// Process the data bits
newtio.c_cflag &= ~CSIZE;
switch (Handle->DataBits)
{
case 5: newtio.c_cflag |= CS5; break;
case 6: newtio.c_cflag |= CS6; break;
case 7: newtio.c_cflag |= CS7; break;
case 8: newtio.c_cflag |= CS8; break;
default: newtio.c_cflag |= CS8; break;
}
// Set other flags
newtio.c_cflag |= CLOCAL | CREAD;
// Process Parity
newtio.c_cflag &= ~(PARENB | PARODD);
if (Handle->Parity == EVEN_PARITY)
newtio.c_cflag |= PARENB;
else if (Handle->Parity == ODD_PARITY)
newtio.c_cflag |= (PARENB | PARODD);
// Flow Control (for now)
newtio.c_cflag &= ~CRTSCTS;
// Process Stop Bits
if (Handle->StopBits == 2)
newtio.c_cflag |= CSTOPB;
else
newtio.c_cflag &= ~CSTOPB;
newtio.c_iflag = IGNBRK;
// Software Flow Control
if (Handle->FlowCtrl == SW_FLOWCTRL)
newtio.c_iflag |= IXON | IXOFF;
else
newtio.c_iflag &= ~(IXON | IXOFF | IXANY);
// Set RAW input & output
newtio.c_lflag=0;
newtio.c_oflag=0;
// Set wait parameters
newtio.c_cc[VTIME] = Handle->DataWait; // Blocking: Allow at least a tenth of a second to wait for data
newtio.c_cc[VMIN] = 0; // Non-blocking: Don't set min bytes to receive
// Set Options (first time)
//tcflush( Handle->FD, TCIFLUSH);
if (tcsetattr( Handle->FD, TCSANOW, &newtio)!=0)
return false;
// Set Terminal options
ioctl( Handle->FD, TIOCMGET, &mcs);
mcs |= TIOCM_RTS;
ioctl( Handle->FD, TIOCMSET, &mcs);
// Get Options (again)
if (tcgetattr( Handle->FD, &newtio) != 0)
return false;
// Process Hardware Flow Control
if (Handle->FlowCtrl == HW_FLOWCTRL)
newtio.c_cflag |= CRTSCTS;
else
newtio.c_cflag &= ~CRTSCTS;
// Set Options (second time)
if (tcsetattr( Handle->FD, TCSANOW, &newtio ) != 0)
{
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Port not configured", Name, Handle->Name );
return false;
}
// Port configured
// Log event
if (Log) Log->Message( LogLevel, dlMedium, "%s: Handle '%s' - Port configured", Name, Handle->Name );
return true;
}
//---------------------------------------------------------------------------
// Get serial port configuration parameters
bool CSelectableCore::ReadSerialConfig( THandle * Handle )
{
int inspeed;
int outspeed;
unsigned char bits;
struct termios getTermios;
// Validate handle
if (!Handle || (Handle->Type != ctSerial)) {
return false;
}
// Get port setup
tcgetattr( Handle->FD, &getTermios );
inspeed = cfgetispeed( &getTermios );
outspeed = cfgetospeed( &getTermios );
// Get Baud Rate
switch (inspeed)
{
case B0 : Handle->InBaudrate = 0; break;
case B50 : Handle->InBaudrate = 50; break;
case B75 : Handle->InBaudrate = 75; break;
case B110 : Handle->InBaudrate = 110; break;
case B134 : Handle->InBaudrate = 134; break;
case B150 : Handle->InBaudrate = 150; break;
case B200 : Handle->InBaudrate = 200; break;
case B300 : Handle->InBaudrate = 300; break;
case B600 : Handle->InBaudrate = 600; break;
case B1200 : Handle->InBaudrate = 1200; break;
case B1800: Handle->InBaudrate = 1800; break;
case B2400: Handle->InBaudrate = 2400; break;
case B4800: Handle->InBaudrate = 4800; break;
case B9600: Handle->InBaudrate = 9600; break;
case B19200: Handle->InBaudrate = 19200; break;
case B38400: Handle->InBaudrate = 38400; break;
case B57600: Handle->InBaudrate = 57600; break;
case B115200: Handle->InBaudrate = 115200; break;
case B230400: Handle->InBaudrate = 230400; break;
default: Handle->InBaudrate = 0; break;
}
switch (outspeed)
{
case B0 : Handle->OutBaudrate = 0; break;
case B50 : Handle->OutBaudrate = 50; break;
case B75 : Handle->OutBaudrate = 75; break;
case B110 : Handle->OutBaudrate = 110; break;
case B134 : Handle->OutBaudrate = 134; break;
case B150 : Handle->OutBaudrate = 150; break;
case B200 : Handle->OutBaudrate = 200; break;
case B300 : Handle->OutBaudrate = 300; break;
case B600 : Handle->OutBaudrate = 600; break;
case B1200 : Handle->OutBaudrate = 1200; break;
case B1800: Handle->OutBaudrate = 1800; break;
case B2400: Handle->OutBaudrate = 2400; break;
case B4800: Handle->OutBaudrate = 4800; break;
case B9600: Handle->OutBaudrate = 9600; break;
case B19200: Handle->OutBaudrate = 19200; break;
case B38400: Handle->OutBaudrate = 38400; break;
case B57600: Handle->OutBaudrate = 57600; break;
case B115200: Handle->OutBaudrate = 115200; break;
case B230400: Handle->OutBaudrate = 230400; break;
default: Handle->OutBaudrate = 0 ; break;
}
// Get Data Bits
bits = getTermios.c_cflag & CSIZE;
switch (bits)
{
case CS5: Handle->DataBits = 5; break;
case CS6: Handle->DataBits = 6; break;
case CS7: Handle->DataBits = 7; break;
case CS8: Handle->DataBits = 8; break;
default: Handle->DataBits = 255; break;
}
// Get Stop Bits
if (getTermios.c_cflag & CSTOPB)
Handle->StopBits = 2;
else
Handle->StopBits = 1;
// Get Parity
if (getTermios.c_iflag & (PARODD | PARENB))
Handle->Parity = ODD_PARITY;
else if (getTermios.c_iflag & PARENB)
Handle->Parity = EVEN_PARITY;
else
Handle->Parity = NO_PARITY;
// Get Flow Control
if (getTermios.c_cflag & CRTSCTS)
Handle->FlowCtrl = true;
else
Handle->FlowCtrl = false;
return true;
}
//---------------------------------------------------------------------------
bool CSelectableCore::BuildArgs( const char * ExecPath, int &Count, char * Args[] )
{
bool ParamStarted = false;
bool OpenQuotes = false;
char * MatchPos = NULL;
char * StartPos = NULL;
int Len;
// Validate
if (!ExecPath || !*ExecPath) {
return false;
}
// Split params
MatchPos = (char*)ExecPath;
for (;;)
{
// Look for whitespace
if (!ParamStarted)
{
// Quotes starts quoted parameter
if (*MatchPos == '"') {
ParamStarted = true;
OpenQuotes = true;
StartPos = MatchPos+1; // Skip starting quote
}
// Non-whitespace starts normal parameter
else if ((*MatchPos != ' ') && (*MatchPos != 0)) {
ParamStarted = true;
StartPos = MatchPos;
}
}
else if (OpenQuotes)
{
// Another quote ends parameter
if (*MatchPos == '"') {
Len = MatchPos-StartPos-1; // Skip end quote
Args[Count] = (char*)malloc( Len+1 );
strncpy( Args[Count], StartPos, Len );
Args[Count][Len] = 0;
Count++;
ParamStarted = false;
OpenQuotes = false;
}
}
else
{
// Whitespace ends parameter
if ((*MatchPos == ' ') || (*MatchPos == 0)) {
Len = MatchPos-StartPos;
Args[Count] = (char*)malloc( Len+1 );
strncpy( Args[Count], StartPos, Len );
Args[Count][Len] = 0;
Count++;
ParamStarted = false;
}
}
// Next char, unless NULL
if (*MatchPos)
MatchPos++;
else
break;
}
// Check all parameters closed
if (ParamStarted) {
Count = 0;
Args[0] = NULL;
return false;
}
// Set last Param to NULL
Args[Count] = NULL;
return true;
}
//---------------------------------------------------------------------------