mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
* trigger tidy * copy&move BApplicPtr obj always commit tidy changes * move PolObject * fix house add_component * Automated clang-tidy change: performance-unnecessary-copy-initialization,performance-unnecessary-value-param * Revert "Automated clang-tidy change: performance-unnecessary-copy-initialization,performance-unnecessary-value-param" This reverts commit bb6aab13dbdfa4aaf420cdaafaf22940f241fc5b. * performance-unnecessary-value-param gives some weird suggestions * Automated clang-tidy change: performance-unnecessary-copy-initialization * minor performance improvements * added used tidy check * missing dependency * renamed build so its no longer requires? * better add docs for the sneaky fix * silence pr check, not all headers can be compiled seperatly --------- Co-authored-by: Clang Tidy <clang-tidy@users.noreply.github.com>
1644 lines
48 KiB
C++
1644 lines
48 KiB
C++
/** @file
|
|
*
|
|
* @par History
|
|
*/
|
|
|
|
#include "osmod.h"
|
|
|
|
#include "bscript/berror.h"
|
|
#include "bscript/bobject.h"
|
|
#include "bscript/bstruct.h"
|
|
#include "bscript/dict.h"
|
|
#include "bscript/impstr.h"
|
|
#include "clib/clib.h"
|
|
#include "clib/logfacility.h"
|
|
#include "clib/network/sckutil.h"
|
|
#include "clib/rawtypes.h"
|
|
#include "clib/refptr.h"
|
|
#include "clib/stlutil.h"
|
|
#include "clib/threadhelp.h"
|
|
#include "clib/weakptr.h"
|
|
#include "globals/settings.h"
|
|
#include "plib/systemstate.h"
|
|
|
|
#include "../exscrobj.h"
|
|
#include "../globals/script_internals.h"
|
|
#include "../globals/state.h"
|
|
#include "../item/item.h"
|
|
#include "../mobile/attribute.h"
|
|
#include "../mobile/charactr.h"
|
|
#include "../mobile/npc.h"
|
|
#include "../network/auxclient.h"
|
|
#include "../network/packethelper.h"
|
|
#include "../network/packets.h"
|
|
#include "../network/pktdef.h"
|
|
#include "../poldbg.h"
|
|
#include "../polsem.h"
|
|
#include "../profile.h"
|
|
#include "../schedule.h"
|
|
#include "../scrdef.h"
|
|
#include "../scrsched.h"
|
|
#include "../scrstore.h"
|
|
#include "../skills.h"
|
|
#include "../ufunc.h"
|
|
#include "../uoexec.h"
|
|
#include "npcmod.h"
|
|
#include "uomod.h"
|
|
|
|
#include <chrono>
|
|
#include <module_defs/os.h>
|
|
|
|
#ifdef _WIN32
|
|
#pragma comment( lib, "crypt32.lib" )
|
|
#endif
|
|
#include <ctime>
|
|
#include <curl/curl.h>
|
|
#include <memory>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
|
|
// environment variable for mf_GetEnvironmentVariable
|
|
#ifdef _MSC_VER
|
|
extern char** _environ;
|
|
char** environ_vars = _environ;
|
|
#else
|
|
extern char** environ;
|
|
char** environ_vars = environ;
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
class CurlStringList
|
|
{
|
|
public:
|
|
CurlStringList() : resource_( nullptr ) {}
|
|
|
|
~CurlStringList()
|
|
{
|
|
if ( resource_ )
|
|
{
|
|
curl_slist_free_all( resource_ );
|
|
}
|
|
}
|
|
|
|
void add( const std::string& str )
|
|
{
|
|
auto* new_resource = curl_slist_append( resource_, str.c_str() );
|
|
|
|
if ( new_resource != resource_ && resource_ != nullptr )
|
|
{
|
|
curl_slist_free_all( resource_ );
|
|
}
|
|
|
|
resource_ = new_resource;
|
|
}
|
|
|
|
curl_slist* get() const { return resource_; }
|
|
|
|
private:
|
|
curl_slist* resource_;
|
|
};
|
|
|
|
}; // namespace
|
|
|
|
|
|
namespace Pol::Module
|
|
{
|
|
using namespace Bscript;
|
|
|
|
unsigned int getnewpid( Core::UOExecutor* uoexec )
|
|
{
|
|
return Core::scriptScheduler.get_new_pid( uoexec );
|
|
}
|
|
void freepid( unsigned int pid )
|
|
{
|
|
Core::scriptScheduler.free_pid( pid );
|
|
}
|
|
|
|
OSExecutorModule::OSExecutorModule( Bscript::Executor& exec )
|
|
: TmplExecutorModule<OSExecutorModule, Core::PolModule>( exec ),
|
|
critical_( false ),
|
|
priority_( Plib::systemstate.config.default_priority ),
|
|
warn_on_runaway_( true ),
|
|
blocked_( false ),
|
|
sleep_until_clock_( 0 ),
|
|
hold_itr_(),
|
|
in_hold_list_( Core::HoldListType::NO_LIST ),
|
|
wait_type( Core::WAIT_TYPE::WAIT_UNKNOWN ),
|
|
pid_( getnewpid( &uoexec() ) ),
|
|
max_eventqueue_size( Core::MAX_EVENTQUEUE_SIZE ),
|
|
events_()
|
|
{
|
|
}
|
|
|
|
OSExecutorModule::~OSExecutorModule()
|
|
{
|
|
freepid( pid_ );
|
|
pid_ = 0;
|
|
while ( !events_.empty() )
|
|
{
|
|
Bscript::BObject ob( events_.front() );
|
|
events_.pop_front();
|
|
}
|
|
}
|
|
|
|
unsigned int OSExecutorModule::pid() const
|
|
{
|
|
return pid_;
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Create_Debug_Context()
|
|
{
|
|
return Core::create_debug_context();
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Debugger()
|
|
{
|
|
Core::UOExecutor* uoexec;
|
|
if ( find_uoexec( pid_, &uoexec ) )
|
|
{
|
|
return new BLong( uoexec->attach_debugger() );
|
|
}
|
|
return new BError( "Could not find UOExecutor for current process." );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_GetProcess()
|
|
{
|
|
int pid; // note that while pid's are unsigned, valid values are forced to fit within a signed
|
|
// range
|
|
if ( !getParam( 0, pid ) )
|
|
return new BError( "Invalid parameter type" );
|
|
|
|
if ( pid == -1 )
|
|
{
|
|
pid = pid_; // get executor's own pid
|
|
}
|
|
|
|
Core::UOExecutor* uoexec;
|
|
if ( find_uoexec( pid, &uoexec ) )
|
|
return new Core::ScriptExObjImp( uoexec );
|
|
return new BError( "Process not found" );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_GetPid()
|
|
{
|
|
return new BLong( pid_ );
|
|
}
|
|
/* Ok, this is going to be fun. In the case where we block,
|
|
the caller is going to take our return value and push
|
|
it on the value stack.
|
|
|
|
What we'll do is push the value that should be returned
|
|
if a timeout occurs. THis way, for timeouts, all we have
|
|
to do is move the script back into the run queue.
|
|
|
|
When we actually complete something, we'll have to
|
|
pop the value off the stack, and replace it with
|
|
the real result.
|
|
|
|
Whew!
|
|
*/
|
|
BObjectImp* OSExecutorModule::mf_Sleep()
|
|
{
|
|
int nsecs = exec.paramAsLong( 0 );
|
|
SleepFor( nsecs > 0 ? static_cast<u32>( nsecs ) : 1u );
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Sleepms()
|
|
{
|
|
int msecs = exec.paramAsLong( 0 );
|
|
SleepForMs( msecs > 0 ? static_cast<u32>( msecs ) : 1u );
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Wait_For_Event()
|
|
{
|
|
if ( !events_.empty() )
|
|
{
|
|
BObjectImp* imp = events_.front();
|
|
events_.pop_front();
|
|
return imp;
|
|
}
|
|
|
|
auto param = exec.getParamImp( 0 );
|
|
double nsecs = 0;
|
|
|
|
if ( auto* long_param = impptrIf<BLong>( param ) )
|
|
{
|
|
nsecs = long_param->value();
|
|
if ( !nsecs )
|
|
return new BLong( 0 );
|
|
if ( nsecs < 1 )
|
|
nsecs = 1;
|
|
}
|
|
else if ( auto* double_param = impptrIf<Double>( param ) )
|
|
{
|
|
nsecs = double_param->value();
|
|
if ( !nsecs )
|
|
return new BLong( 0 );
|
|
if ( nsecs < 0.01 )
|
|
nsecs = 0.01;
|
|
}
|
|
else
|
|
{
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
wait_type = Core::WAIT_TYPE::WAIT_EVENT;
|
|
blocked_ = true;
|
|
sleep_until_clock_ =
|
|
Core::polclock() + Clib::clamp_convert<u32>( nsecs * Core::POLCLOCKS_PER_SEC );
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Events_Waiting()
|
|
{
|
|
return new BLong( static_cast<int>( events_.size() ) );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Start_Script()
|
|
{
|
|
const String* scriptname_str;
|
|
if ( exec.getStringParam( 0, scriptname_str ) )
|
|
{
|
|
BObjectImp* imp = exec.getParamImp( 1 );
|
|
|
|
// FIXME needs to inherit available modules?
|
|
Core::ScriptDef sd;
|
|
if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
|
|
{
|
|
return new BError( "Error in script name" );
|
|
}
|
|
if ( !sd.exists() )
|
|
{
|
|
return new BError( "Script " + sd.name() + " does not exist." );
|
|
}
|
|
UOExecutorModule* new_uoemod = Core::start_script( sd, imp->copy() );
|
|
if ( new_uoemod == nullptr )
|
|
{
|
|
return new BError( "Unable to start script" );
|
|
}
|
|
UOExecutorModule* this_uoemod = static_cast<UOExecutorModule*>( exec.findModule( "uo" ) );
|
|
if ( new_uoemod != nullptr && this_uoemod != nullptr )
|
|
{
|
|
new_uoemod->controller_ = this_uoemod->controller_;
|
|
}
|
|
|
|
return new Core::ScriptExObjImp( &new_uoemod->uoexec() );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
|
|
BObjectImp* OSExecutorModule::mf_Start_Skill_Script()
|
|
{
|
|
Mobile::Character* chr;
|
|
const Mobile::Attribute* attr;
|
|
|
|
if ( getCharacterParam( 0, chr ) && getAttributeParam( 1, attr ) )
|
|
{
|
|
if ( !attr->disable_core_checks && !Core::CanUseSkill( chr->client ) )
|
|
return new BLong( 0 );
|
|
|
|
const String* script_name;
|
|
Core::ScriptDef script;
|
|
|
|
if ( exec.getStringParam( 2, script_name ) && ( script_name->length() > 0 ) )
|
|
{
|
|
if ( !script.config_nodie( script_name->value(), exec.prog()->pkg, "scripts/skills/" ) )
|
|
{
|
|
return new BError( "Error in script name" );
|
|
}
|
|
if ( !script.exists() )
|
|
{
|
|
return new BError( "Script " + script.name() + " does not exist." );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !attr->script_.empty() )
|
|
script = attr->script_;
|
|
else
|
|
return new BError( "No script defined for attribute " + attr->name + "." );
|
|
}
|
|
|
|
ref_ptr<EScriptProgram> prog = find_script2(
|
|
script, true,
|
|
/* complain if not found */ Plib::systemstate.config.cache_interactive_scripts );
|
|
|
|
if ( prog.get() != nullptr )
|
|
{
|
|
BObjectImp* imp = exec.getParamImp( 3 );
|
|
if ( imp )
|
|
{
|
|
if ( chr->start_script( prog.get(), true, imp->copy() ) )
|
|
{
|
|
if ( chr->hidden() && attr->unhides )
|
|
chr->unhide();
|
|
if ( attr->delay_seconds )
|
|
chr->disable_skills_until( Core::poltime() + attr->delay_seconds );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( chr->start_script( prog.get(), true ) )
|
|
{
|
|
if ( chr->hidden() && attr->unhides )
|
|
chr->unhide();
|
|
if ( attr->delay_seconds )
|
|
chr->disable_skills_until( Core::poltime() + attr->delay_seconds );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::string msg = "Unable to start skill script:";
|
|
msg += script.c_str();
|
|
Core::send_sysmessage( chr->client, msg.c_str() );
|
|
|
|
return new BLong( 0 );
|
|
}
|
|
return new BLong( 1 );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Set_Critical()
|
|
{
|
|
int crit;
|
|
if ( exec.getParam( 0, crit ) )
|
|
{
|
|
critical_ = ( crit != 0 );
|
|
return new BLong( 1 );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Is_Critical()
|
|
{
|
|
if ( critical_ )
|
|
return new BLong( 1 );
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Run_Script_To_Completion()
|
|
{
|
|
const char* scriptname = exec.paramAsString( 0 );
|
|
if ( scriptname == nullptr )
|
|
return new BLong( 0 );
|
|
|
|
// FIXME needs to inherit available modules?
|
|
Core::ScriptDef script;
|
|
|
|
if ( !script.config_nodie( scriptname, exec.prog()->pkg, "scripts/" ) )
|
|
return new BError( "Script descriptor error" );
|
|
|
|
if ( !script.exists() )
|
|
return new BError( "Script does not exist" );
|
|
|
|
return Core::run_script_to_completion( script, getParamImp( 1 ) );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Run_Script()
|
|
{
|
|
UOExecutorModule* this_uoemod = static_cast<UOExecutorModule*>( exec.findModule( "uo" ) );
|
|
Core::UOExecutor& this_uoexec = uoexec();
|
|
|
|
|
|
if ( this_uoexec.pChild == nullptr )
|
|
{
|
|
const String* scriptname_str;
|
|
if ( exec.getStringParam( 0, scriptname_str ) )
|
|
{
|
|
BObjectImp* imp = exec.getParamImp( 1 );
|
|
|
|
// FIXME needs to inherit available modules?
|
|
Core::ScriptDef sd;
|
|
if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
|
|
{
|
|
return new BError( "Error in script name" );
|
|
}
|
|
if ( !sd.exists() )
|
|
{
|
|
return new BError( "Script " + sd.name() + " does not exist." );
|
|
}
|
|
UOExecutorModule* new_uoemod = Core::start_script( sd, imp->copy() );
|
|
if ( new_uoemod == nullptr )
|
|
{
|
|
return new BError( "Unable to run script" );
|
|
}
|
|
if ( new_uoemod )
|
|
{
|
|
new_uoemod->controller_ = this_uoemod->controller_;
|
|
}
|
|
Core::UOExecutor& new_uoexec = new_uoemod->uoexec();
|
|
// OSExecutorModule* osemod = uoexec->os_module;
|
|
new_uoexec.pParent = &this_uoexec;
|
|
this_uoexec.pChild = &new_uoexec;
|
|
|
|
// we want to forcefully do this instruction over again:
|
|
this_uoexec.PC--; // run_script(
|
|
this_uoexec.ValueStack.push_back(
|
|
BObjectRef( new BObject( UninitObject::create() ) ) ); // script_name,
|
|
// No need to push on "param" since the new BLong(0) below will take care of it.// param )
|
|
|
|
// Put me on hold until my child is done.
|
|
suspend();
|
|
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
// else I am running a child script, and its ended
|
|
BObjectImp* ret;
|
|
|
|
if ( this_uoexec.pChild->ValueStack.empty() )
|
|
ret = new BLong( 1 );
|
|
else
|
|
ret = this_uoexec.pChild->ValueStack.back().get()->impptr()->copy();
|
|
|
|
this_uoexec.pChild->pParent = nullptr;
|
|
this_uoexec.pChild = nullptr;
|
|
|
|
return ret;
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Set_Debug()
|
|
{
|
|
int dbg;
|
|
if ( getParam( 0, dbg ) )
|
|
{
|
|
if ( dbg )
|
|
exec.setDebugLevel( Executor::SOURCELINES );
|
|
return new BLong( 1 );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_SysLog()
|
|
{
|
|
BObjectImp* imp = exec.getParamImp( 0 );
|
|
int log_verbose;
|
|
const String* color;
|
|
if ( !exec.getParam( 1, log_verbose ) || !exec.getStringParam( 2, color ) )
|
|
return new BError( "Invalid parameter type" );
|
|
std::string strval = imp->getStringRep();
|
|
if ( log_verbose )
|
|
{
|
|
POLLOGLN( "[{}]: {}", exec.scriptname(), strval );
|
|
if ( Plib::systemstate.config.enable_colored_output && color->length() )
|
|
{
|
|
INFO_PRINTLN( "{}syslog [{}]: {}{}", color->value(), exec.scriptname(), strval,
|
|
Clib::Logging::CONSOLE_RESET_COLOR );
|
|
}
|
|
else
|
|
{
|
|
INFO_PRINTLN( "syslog [{}]: {}", exec.scriptname(), strval );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( Plib::systemstate.config.enable_colored_output && color->length() )
|
|
{
|
|
POLLOGLN( strval );
|
|
INFO_PRINTLN( "{}{}{}", color->value(), strval, Clib::Logging::CONSOLE_RESET_COLOR );
|
|
}
|
|
else
|
|
{
|
|
POLLOG_INFOLN( strval );
|
|
}
|
|
}
|
|
return new BLong( 1 );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Set_Priority()
|
|
{
|
|
int newpri;
|
|
if ( getParam( 0, newpri, 1, 255 ) )
|
|
{
|
|
int oldpri = priority_;
|
|
priority_ = static_cast<unsigned char>( newpri );
|
|
return new BLong( oldpri );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
|
|
BObjectImp* OSExecutorModule::mf_Unload_Scripts()
|
|
{
|
|
const String* str;
|
|
if ( getStringParam( 0, str ) )
|
|
{
|
|
int n;
|
|
if ( str->length() == 0 )
|
|
n = Core::unload_all_scripts();
|
|
else
|
|
n = Core::unload_script( str->data() );
|
|
return new BLong( n );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::clear_event_queue() // DAVE
|
|
{
|
|
while ( !events_.empty() )
|
|
{
|
|
BObject ob( events_.front() );
|
|
events_.pop_front();
|
|
}
|
|
return new BLong( 1 );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Set_Event_Queue_Size() // DAVE 11/24
|
|
{
|
|
unsigned short param;
|
|
if ( getParam( 0, param ) )
|
|
{
|
|
unsigned short oldsize = max_eventqueue_size;
|
|
max_eventqueue_size = param;
|
|
return new BLong( oldsize );
|
|
}
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_OpenURL()
|
|
{
|
|
Mobile::Character* chr;
|
|
const String* str;
|
|
if ( getCharacterParam( 0, chr ) && ( ( str = getStringParam( 1 ) ) != nullptr ) )
|
|
{
|
|
if ( chr->has_active_client() )
|
|
{
|
|
Network::PktHelper::PacketOut<Network::PktOut_A5> msg;
|
|
unsigned urllen;
|
|
const char* url = str->data();
|
|
|
|
urllen = static_cast<unsigned int>( strlen( url ) );
|
|
if ( urllen > URL_MAX_LEN )
|
|
urllen = URL_MAX_LEN;
|
|
|
|
msg->WriteFlipped<u16>( urllen + 4u );
|
|
msg->Write( url, static_cast<u16>( urllen + 1 ) );
|
|
msg.Send( chr->client );
|
|
return new BLong( 1 );
|
|
}
|
|
|
|
return new BError( "No client attached" );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
|
|
BObjectImp* OSExecutorModule::mf_OpenConnection()
|
|
{
|
|
const String* host;
|
|
const String* scriptname_str;
|
|
BObjectImp* scriptparam;
|
|
unsigned short port;
|
|
int assume_string_int;
|
|
int keep_connection_int;
|
|
int ignore_line_breaks_int;
|
|
if ( !getStringParam( 0, host ) || !getParam( 1, port ) || !getStringParam( 2, scriptname_str ) ||
|
|
!getParamImp( 3, scriptparam ) || !getParam( 4, assume_string_int ) ||
|
|
!getParam( 5, keep_connection_int ) || !getParam( 6, ignore_line_breaks_int ) )
|
|
return new BError( "Invalid parameter type" );
|
|
|
|
// FIXME needs to inherit available modules?
|
|
Core::ScriptDef sd;
|
|
if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
|
|
{
|
|
return new BError( "Error in script name" );
|
|
}
|
|
if ( !sd.exists() )
|
|
{
|
|
return new BError( "Script " + sd.name() + " does not exist." );
|
|
}
|
|
Core::UOExecutor& this_uoexec = uoexec();
|
|
if ( !this_uoexec.suspend() )
|
|
{
|
|
DEBUGLOGLN(
|
|
"Script Error in '{}' PC={}: \n"
|
|
"\tThe execution of this script can't be blocked!",
|
|
this_uoexec.scriptname(), this_uoexec.PC );
|
|
return new Bscript::BError( "Script can't be blocked" );
|
|
}
|
|
|
|
weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
|
|
std::string hostname( host->value() );
|
|
bool assume_string = assume_string_int != 0;
|
|
bool keep_connection = keep_connection_int != 0;
|
|
bool ignore_line_breaks = ignore_line_breaks_int != 0;
|
|
auto* paramobjimp_raw = scriptparam->copy(); // prevent delete
|
|
Core::networkManager.auxthreadpool->push(
|
|
[uoexec_w, sd, hostname, port, paramobjimp_raw, assume_string, keep_connection,
|
|
ignore_line_breaks]()
|
|
{
|
|
Clib::Socket s;
|
|
std::unique_ptr<Network::AuxClientThread> client;
|
|
bool success_open = s.open( hostname.c_str(), port );
|
|
{
|
|
Core::PolLock lck;
|
|
std::unique_ptr<BObjectImp> paramobjimp( paramobjimp_raw );
|
|
if ( !uoexec_w.exists() )
|
|
{
|
|
DEBUGLOGLN( "OpenConnection Script has been destroyed" );
|
|
s.close();
|
|
return;
|
|
}
|
|
if ( !success_open )
|
|
{
|
|
uoexec_w.get_weakptr()->ValueStack.back().set(
|
|
new BObject( new BError( "Error connecting to client" ) ) );
|
|
uoexec_w.get_weakptr()->revive();
|
|
return;
|
|
}
|
|
uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( new BLong( 1 ) ) );
|
|
uoexec_w.get_weakptr()->revive();
|
|
client = std::make_unique<Network::AuxClientThread>(
|
|
sd, std::move( s ), paramobjimp.release(), assume_string, keep_connection,
|
|
ignore_line_breaks );
|
|
}
|
|
if ( client )
|
|
client->run();
|
|
} );
|
|
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
size_t curlWriteCallback( void* contents, size_t size, size_t nmemb, void* userp )
|
|
{
|
|
( static_cast<std::string*>( userp ) )->append( static_cast<char*>( contents ), size * nmemb );
|
|
return size * nmemb;
|
|
}
|
|
|
|
const int HTTPREQUEST_EXTENDED_RESPONSE = 0x0001;
|
|
|
|
struct CurlHeaderData
|
|
{
|
|
std::optional<std::string> statusText;
|
|
std::map<std::string, std::string> headers = {};
|
|
};
|
|
|
|
size_t curlReadHeaderCallback( char* buffer, size_t size, size_t nitems, void* userdata )
|
|
{
|
|
auto headerData = static_cast<CurlHeaderData*>( userdata );
|
|
std::string value( buffer, size * nitems );
|
|
|
|
// statusText is set after parsing the first header line
|
|
if ( !headerData->statusText.has_value() )
|
|
{
|
|
std::string statusText = "";
|
|
if ( value.rfind( "HTTP/", 0 ) == 0 )
|
|
{
|
|
// Find space after "HTTP/x" (before status code)
|
|
size_t pos = value.find( " " );
|
|
if ( pos != std::string::npos )
|
|
{
|
|
// Find space after status code (before status text)
|
|
pos = value.find( " ", pos + 1 );
|
|
if ( pos != std::string::npos )
|
|
{
|
|
statusText = Clib::strtrim( value.substr( pos + 1 ) );
|
|
}
|
|
}
|
|
}
|
|
headerData->statusText.emplace( statusText );
|
|
}
|
|
else
|
|
{
|
|
size_t pos = value.find( ":" );
|
|
if ( pos != std::string::npos )
|
|
{
|
|
std::string headerName = Clib::strlowerASCII( value.substr( 0, pos ) );
|
|
std::string headerValue = Clib::strtrim( value.substr( pos + 1 ) );
|
|
|
|
// If the header already exists, append it (comma-separated) to previous value
|
|
if ( headerData->headers.count( headerName ) )
|
|
{
|
|
auto& existing = headerData->headers.at( headerName );
|
|
existing.append( ", " );
|
|
existing.append( headerValue );
|
|
}
|
|
else
|
|
{
|
|
headerData->headers.emplace( headerName, headerValue );
|
|
}
|
|
}
|
|
}
|
|
|
|
return nitems * size;
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_HTTPRequest()
|
|
{
|
|
const String *url, *method;
|
|
BObjectImp* options;
|
|
int flags;
|
|
if ( !getStringParam( 0, url ) || !getStringParam( 1, method ) || !getParamImp( 2, options ) ||
|
|
!getParam( 3, flags ) )
|
|
return new BError( "Invalid parameter type" );
|
|
Core::UOExecutor& this_uoexec = uoexec();
|
|
weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
|
|
|
|
std::shared_ptr<CURL> curl_sp( curl_easy_init(), curl_easy_cleanup );
|
|
CURL* curl = curl_sp.get();
|
|
if ( !curl )
|
|
return new BError( "curl_easy_init() failed" );
|
|
curl_easy_setopt( curl, CURLOPT_URL, url->data() );
|
|
curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, method->data() );
|
|
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, curlWriteCallback );
|
|
curl_easy_setopt( curl, CURLOPT_ACCEPT_ENCODING,
|
|
"" ); // allow plaintext and compressed (with automatic deflate)
|
|
|
|
struct curl_slist* chunk = nullptr;
|
|
if ( options->isa( Bscript::BObjectImp::OTStruct ) )
|
|
{
|
|
Bscript::BStruct* opts = static_cast<Bscript::BStruct*>( options );
|
|
const BObjectImp* data = opts->FindMember( "data" );
|
|
if ( data != nullptr )
|
|
{
|
|
curl_easy_setopt( curl, CURLOPT_COPYPOSTFIELDS, data->getStringRep().c_str() );
|
|
}
|
|
|
|
const BObjectImp* headers_ = opts->FindMember( "headers" );
|
|
|
|
if ( headers_ != nullptr && headers_->isa( BObjectImp::OTStruct ) )
|
|
{
|
|
const BStruct* headers = static_cast<const BStruct*>( headers_ );
|
|
|
|
for ( const auto& content : headers->contents() )
|
|
{
|
|
BObjectImp* ref = content.second->impptr();
|
|
std::string header = content.first + ": " + ref->getStringRep();
|
|
chunk = curl_slist_append( chunk, header.c_str() );
|
|
}
|
|
curl_easy_setopt( curl, CURLOPT_HTTPHEADER, chunk );
|
|
}
|
|
}
|
|
|
|
if ( !this_uoexec.suspend() )
|
|
{
|
|
DEBUGLOGLN(
|
|
"Script Error in '{}' PC={}: \n"
|
|
"\tThe execution of this script can't be blocked!",
|
|
this_uoexec.scriptname(), this_uoexec.PC );
|
|
return new Bscript::BError( "Script can't be blocked" );
|
|
}
|
|
|
|
Core::networkManager.auxthreadpool->push(
|
|
[uoexec_w, curl_sp, chunk, flags]()
|
|
{
|
|
CURL* curl = curl_sp.get();
|
|
CURLcode res;
|
|
std::string readBuffer;
|
|
CurlHeaderData headerData;
|
|
|
|
if ( flags == HTTPREQUEST_EXTENDED_RESPONSE )
|
|
{
|
|
curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, curlReadHeaderCallback );
|
|
curl_easy_setopt( curl, CURLOPT_HEADERDATA, &headerData );
|
|
}
|
|
|
|
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &readBuffer );
|
|
|
|
/* Perform the request, res will get the return code */
|
|
res = curl_easy_perform( curl );
|
|
if ( chunk != nullptr )
|
|
curl_slist_free_all( chunk );
|
|
{
|
|
Core::PolLock lck;
|
|
|
|
if ( !uoexec_w.exists() )
|
|
{
|
|
DEBUGLOGLN( "HTTPRequest Script has been destroyed" );
|
|
return;
|
|
}
|
|
/* Check for errors */
|
|
if ( res != CURLE_OK )
|
|
{
|
|
uoexec_w.get_weakptr()->ValueStack.back().set(
|
|
new BObject( new BError( curl_easy_strerror( res ) ) ) );
|
|
}
|
|
else
|
|
{
|
|
if ( flags == HTTPREQUEST_EXTENDED_RESPONSE )
|
|
{
|
|
auto response = std::make_unique<Bscript::BDictionary>();
|
|
auto headers = std::make_unique<Bscript::BDictionary>();
|
|
long http_code = 0;
|
|
|
|
res = curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &http_code );
|
|
if ( res == CURLE_OK )
|
|
{
|
|
response->addMember( new String( "status" ), new BLong( http_code ) );
|
|
}
|
|
else
|
|
{
|
|
response->addMember( new String( "status" ),
|
|
new BError( curl_easy_strerror( res ) ) );
|
|
}
|
|
|
|
response->addMember( new String( "statusText" ),
|
|
new String( headerData.statusText.value_or( "" ) ) );
|
|
|
|
response->addMember( new String( "body" ), new String( readBuffer ) );
|
|
|
|
for ( auto const& [key, value] : headerData.headers )
|
|
{
|
|
headers->addMember( new String( key ), new String( value ) );
|
|
}
|
|
|
|
response->addMember( new String( "headers" ), headers.release() );
|
|
|
|
uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( response.release() ) );
|
|
}
|
|
else
|
|
{
|
|
// TODO: no sanitize happens, optional function param iso/utf8 encoding, or
|
|
// parse the header of the http answer?
|
|
uoexec_w.get_weakptr()->ValueStack.back().set(
|
|
new BObject( new String( readBuffer ) ) );
|
|
}
|
|
}
|
|
|
|
uoexec_w.get_weakptr()->revive();
|
|
}
|
|
|
|
/* always cleanup */
|
|
// curl_easy_cleanup() is performed when the shared pointer deallocates
|
|
} );
|
|
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
// signal_event() takes ownership of the pointer which is passed to it.
|
|
// Objects must not be touched or deleted after being sent here!
|
|
// TODO: Find a better way to enforce this in the codebase.
|
|
bool OSExecutorModule::signal_event( BObjectImp* imp )
|
|
{
|
|
INC_PROFILEVAR( events );
|
|
|
|
if ( blocked_ && ( wait_type == Core::WAIT_TYPE::WAIT_EVENT ) ) // already being waited for
|
|
{
|
|
/* Now, the tricky part. The value to return on an error or
|
|
completion condition has already been pushed onto the value
|
|
stack - so, we need to replace it with the real result.
|
|
*/
|
|
exec.ValueStack.back().set( new BObject( imp ) );
|
|
/* This executor will get moved to the run queue at the
|
|
next step_scripts(), where blocked is checked looking
|
|
for timed out or completed operations. */
|
|
|
|
revive();
|
|
}
|
|
else // not being waited for, so queue for later.
|
|
{
|
|
if ( events_.size() < max_eventqueue_size )
|
|
{
|
|
events_.push_back( imp );
|
|
}
|
|
else
|
|
{
|
|
if ( Plib::systemstate.config.discard_old_events )
|
|
{
|
|
BObject ob( events_.front() );
|
|
events_.pop_front();
|
|
events_.push_back( imp );
|
|
}
|
|
else
|
|
{
|
|
BObject ob( imp );
|
|
if ( Plib::systemstate.config.loglevel >= 11 )
|
|
{
|
|
INFO_PRINTLN( "Event queue for {} is full, discarding event.", exec.scriptname() );
|
|
ExecutorModule* em = exec.findModule( "npc" );
|
|
if ( em )
|
|
{
|
|
NPCExecutorModule* npcemod = static_cast<NPCExecutorModule*>( em );
|
|
INFO_PRINTLN( "NPC Serial: {:#x}{}", npcemod->controlled_npc().serial,
|
|
npcemod->controlled_npc().pos() );
|
|
}
|
|
|
|
INFO_PRINTLN( "Event: {}", ob->getStringRep() );
|
|
}
|
|
return false; // Event-queue is full
|
|
}
|
|
}
|
|
}
|
|
|
|
return true; // Event was successfully sent (perhaps by discarding old events)
|
|
}
|
|
|
|
void OSExecutorModule::SleepFor( u32 nsecs )
|
|
{
|
|
if ( !nsecs )
|
|
return;
|
|
blocked_ = true;
|
|
wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
|
|
sleep_until_clock_ = Core::polclock() + nsecs * Core::POLCLOCKS_PER_SEC;
|
|
}
|
|
|
|
void OSExecutorModule::SleepForMs( u32 msecs )
|
|
{
|
|
if ( !msecs )
|
|
return;
|
|
blocked_ = true;
|
|
wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
|
|
sleep_until_clock_ = Core::polclock() + msecs * Core::POLCLOCKS_PER_SEC / 1000;
|
|
if ( !sleep_until_clock_ )
|
|
sleep_until_clock_ = 1;
|
|
}
|
|
|
|
void OSExecutorModule::suspend()
|
|
{
|
|
blocked_ = true;
|
|
wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
|
|
sleep_until_clock_ = 0; // wait forever
|
|
}
|
|
|
|
void OSExecutorModule::revive()
|
|
{
|
|
blocked_ = false;
|
|
if ( in_hold_list_ == Core::HoldListType::TIMEOUT_LIST )
|
|
{
|
|
in_hold_list_ = Core::HoldListType::NO_LIST;
|
|
Core::scriptScheduler.revive_timeout( &uoexec(), hold_itr_ );
|
|
}
|
|
else if ( in_hold_list_ == Core::HoldListType::NOTIMEOUT_LIST )
|
|
{
|
|
in_hold_list_ = Core::HoldListType::NO_LIST;
|
|
Core::scriptScheduler.revive_notimeout( &uoexec() );
|
|
}
|
|
else if ( in_hold_list_ == Core::HoldListType::DEBUGGER_LIST )
|
|
{
|
|
// stays right where it is.
|
|
}
|
|
}
|
|
bool OSExecutorModule::in_debugger_holdlist() const
|
|
{
|
|
return ( in_hold_list_ == Core::HoldListType::DEBUGGER_LIST );
|
|
}
|
|
void OSExecutorModule::revive_debugged()
|
|
{
|
|
in_hold_list_ = Core::HoldListType::NO_LIST;
|
|
Core::scriptScheduler.revive_debugged( &uoexec() );
|
|
}
|
|
|
|
bool OSExecutorModule::critical() const
|
|
{
|
|
return critical_;
|
|
}
|
|
void OSExecutorModule::critical( bool critical )
|
|
{
|
|
critical_ = critical;
|
|
}
|
|
|
|
bool OSExecutorModule::warn_on_runaway() const
|
|
{
|
|
return warn_on_runaway_;
|
|
}
|
|
void OSExecutorModule::warn_on_runaway( bool warn_on_runaway )
|
|
{
|
|
warn_on_runaway_ = warn_on_runaway;
|
|
}
|
|
|
|
unsigned char OSExecutorModule::priority() const
|
|
{
|
|
return priority_;
|
|
}
|
|
void OSExecutorModule::priority( unsigned char priority )
|
|
{
|
|
priority_ = priority;
|
|
}
|
|
|
|
Core::polclock_t OSExecutorModule::sleep_until_clock() const
|
|
{
|
|
return sleep_until_clock_;
|
|
}
|
|
void OSExecutorModule::sleep_until_clock( Core::polclock_t sleep_until_clock )
|
|
{
|
|
sleep_until_clock_ = sleep_until_clock;
|
|
}
|
|
|
|
Core::TimeoutHandle OSExecutorModule::hold_itr() const
|
|
{
|
|
return hold_itr_;
|
|
}
|
|
void OSExecutorModule::hold_itr( Core::TimeoutHandle hold_itr )
|
|
{
|
|
hold_itr_ = hold_itr;
|
|
}
|
|
|
|
Core::HoldListType OSExecutorModule::in_hold_list() const
|
|
{
|
|
return in_hold_list_;
|
|
}
|
|
void OSExecutorModule::in_hold_list( Core::HoldListType in_hold_list )
|
|
{
|
|
in_hold_list_ = in_hold_list;
|
|
}
|
|
|
|
const int SCRIPTOPT_NO_INTERRUPT = 1;
|
|
const int SCRIPTOPT_DEBUG = 2;
|
|
const int SCRIPTOPT_NO_RUNAWAY = 3;
|
|
const int SCRIPTOPT_CAN_ACCESS_OFFLINE_MOBILES = 4;
|
|
const int SCRIPTOPT_AUXSVC_ASSUME_STRING = 5;
|
|
const int SCRIPTOPT_SURVIVE_ATTACHED_DISCONNECT = 6;
|
|
|
|
BObjectImp* OSExecutorModule::mf_Set_Script_Option()
|
|
{
|
|
int optnum;
|
|
int optval;
|
|
if ( getParam( 0, optnum ) && getParam( 1, optval ) )
|
|
{
|
|
int oldval;
|
|
switch ( optnum )
|
|
{
|
|
case SCRIPTOPT_NO_INTERRUPT:
|
|
oldval = critical_ ? 1 : 0;
|
|
critical_ = optval ? true : false;
|
|
break;
|
|
case SCRIPTOPT_DEBUG:
|
|
oldval = exec.getDebugLevel();
|
|
if ( optval )
|
|
exec.setDebugLevel( Executor::SOURCELINES );
|
|
else
|
|
exec.setDebugLevel( Executor::NONE );
|
|
break;
|
|
case SCRIPTOPT_NO_RUNAWAY:
|
|
oldval = warn_on_runaway_ ? 1 : 0;
|
|
warn_on_runaway_ = !optval;
|
|
break;
|
|
case SCRIPTOPT_CAN_ACCESS_OFFLINE_MOBILES:
|
|
{
|
|
Core::UOExecutor& uoex = uoexec();
|
|
oldval = uoex.can_access_offline_mobiles_ ? 1 : 0;
|
|
uoex.can_access_offline_mobiles_ = optval ? true : false;
|
|
}
|
|
break;
|
|
case SCRIPTOPT_AUXSVC_ASSUME_STRING:
|
|
{
|
|
Core::UOExecutor& uoex = uoexec();
|
|
oldval = uoex.auxsvc_assume_string ? 1 : 0;
|
|
uoex.auxsvc_assume_string = optval ? true : false;
|
|
}
|
|
break;
|
|
case SCRIPTOPT_SURVIVE_ATTACHED_DISCONNECT:
|
|
{
|
|
Core::UOExecutor& uoex = uoexec();
|
|
oldval = uoex.survive_attached_disconnect ? 1 : 0;
|
|
uoex.survive_attached_disconnect = optval ? true : false;
|
|
}
|
|
break;
|
|
default:
|
|
return new BError( "Unknown Script Option" );
|
|
}
|
|
return new BLong( oldval );
|
|
}
|
|
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_Clear_Event_Queue() // DAVE
|
|
{
|
|
return ( clear_event_queue() );
|
|
}
|
|
|
|
namespace
|
|
{
|
|
struct ScriptDiffData
|
|
{
|
|
std::string name;
|
|
u64 instructions;
|
|
u64 pid;
|
|
ScriptDiffData( Core::UOExecutor* ex )
|
|
: name( ex->scriptname() ), instructions( ex->instr_cycles ), pid( ex->pid() )
|
|
{
|
|
}
|
|
ScriptDiffData( Core::UOExecutor* ex, u64 instr ) : ScriptDiffData( ex )
|
|
{
|
|
instructions -= instr;
|
|
auto uoemod = static_cast<Module::UOExecutorModule*>( ex->findModule( "uo" ) );
|
|
if ( uoemod->attached_chr_ != nullptr )
|
|
name += " (" + uoemod->attached_chr_->name() + ")";
|
|
else if ( uoemod->attached_npc_ != nullptr )
|
|
name += " (" + static_cast<Mobile::NPC*>( uoemod->attached_npc_ )->templatename() + ")";
|
|
else if ( uoemod->attached_item_.get() )
|
|
name += " (" + uoemod->attached_item_->name() + ")";
|
|
}
|
|
|
|
bool operator>( const ScriptDiffData& other ) const { return instructions > other.instructions; }
|
|
};
|
|
struct PerfData
|
|
{
|
|
std::unordered_map<u64, ScriptDiffData> data;
|
|
weak_ptr<Core::UOExecutor> uoexec_w;
|
|
size_t max_scripts;
|
|
PerfData( weak_ptr<Core::UOExecutor> weak_ex, size_t max_count )
|
|
: data(), uoexec_w( std::move( weak_ex ) ), max_scripts( max_count )
|
|
{
|
|
}
|
|
static void collect_perf( PerfData* data_ptr )
|
|
{
|
|
std::unique_ptr<PerfData> data( data_ptr );
|
|
std::vector<ScriptDiffData> res;
|
|
if ( !data->uoexec_w.exists() )
|
|
{
|
|
DEBUGLOGLN( "PerformanceMeasure Script has been destroyed" );
|
|
return;
|
|
}
|
|
double sum_instr( 0 );
|
|
const auto& runlist = Core::scriptScheduler.getRunlist();
|
|
const auto& ranlist = Core::scriptScheduler.getRanlist();
|
|
const auto& holdlist = Core::scriptScheduler.getHoldlist();
|
|
const auto& notimeoutholdlist = Core::scriptScheduler.getNoTimeoutHoldlist();
|
|
auto collect = [&]( Core::UOExecutor* scr )
|
|
{
|
|
auto itr = data->data.find( scr->pid() );
|
|
if ( itr == data->data.end() )
|
|
return;
|
|
res.emplace_back( scr, itr->second.instructions );
|
|
sum_instr += res.back().instructions;
|
|
};
|
|
for ( const auto& scr : runlist )
|
|
collect( scr );
|
|
for ( const auto& scr : ranlist )
|
|
collect( scr );
|
|
for ( const auto& scr : holdlist )
|
|
collect( scr.second );
|
|
for ( const auto& scr : notimeoutholdlist )
|
|
collect( scr );
|
|
std::sort( res.begin(), res.end(), std::greater<ScriptDiffData>() );
|
|
|
|
std::unique_ptr<ObjArray> arr( new ObjArray );
|
|
for ( size_t i = 0; i < res.size() && i < data->max_scripts; ++i )
|
|
{
|
|
std::unique_ptr<BStruct> elem( new BStruct );
|
|
elem->addMember( "name", new String( res[i].name ) );
|
|
elem->addMember( "instructions", new Double( static_cast<double>( res[i].instructions ) ) );
|
|
elem->addMember( "pid", new BLong( static_cast<int>( res[i].pid ) ) );
|
|
elem->addMember( "percent", new Double( res[i].instructions / sum_instr * 100.0 ) );
|
|
arr->addElement( elem.release() );
|
|
}
|
|
std::unique_ptr<BStruct> result( new BStruct );
|
|
result->addMember( "scripts", arr.release() );
|
|
result->addMember( "total_number_observed", new BLong( static_cast<int>( res.size() ) ) );
|
|
result->addMember( "total_instructions", new Double( sum_instr ) );
|
|
data->uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( result.release() ) );
|
|
|
|
data->uoexec_w.get_weakptr()->revive();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
BObjectImp* OSExecutorModule::mf_PerformanceMeasure()
|
|
{
|
|
int second_delta, max_scripts;
|
|
if ( !getParam( 0, second_delta ) || !getParam( 1, max_scripts ) )
|
|
return new BError( "Invalid parameter type" );
|
|
|
|
auto& this_uoexec = uoexec();
|
|
|
|
if ( !this_uoexec.suspend() )
|
|
{
|
|
DEBUGLOGLN(
|
|
"Script Error in '{}' PC={}: \n"
|
|
"\tThe execution of this script can't be blocked!",
|
|
this_uoexec.scriptname(), this_uoexec.PC );
|
|
return new Bscript::BError( "Script can't be blocked" );
|
|
}
|
|
|
|
const auto& runlist = Core::scriptScheduler.getRunlist();
|
|
const auto& ranlist = Core::scriptScheduler.getRanlist();
|
|
const auto& holdlist = Core::scriptScheduler.getHoldlist();
|
|
const auto& notimeoutholdlist = Core::scriptScheduler.getNoTimeoutHoldlist();
|
|
|
|
std::unique_ptr<PerfData> perf( new PerfData( this_uoexec.weakptr, max_scripts ) );
|
|
for ( const auto& scr : runlist )
|
|
perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
|
|
for ( const auto& scr : ranlist )
|
|
perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
|
|
for ( const auto& scr : holdlist )
|
|
perf->data.insert( std::make_pair( scr.second->pid(), ScriptDiffData( scr.second ) ) );
|
|
for ( const auto& scr : notimeoutholdlist )
|
|
perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
|
|
|
|
new Core::OneShotTaskInst<PerfData*>( nullptr,
|
|
Core::polclock() + second_delta * Core::POLCLOCKS_PER_SEC,
|
|
PerfData::collect_perf, perf.release() );
|
|
|
|
return new BLong( 0 ); // dummy
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_LoadExportedScript()
|
|
{
|
|
Core::UOExecutor& this_uoexec = uoexec();
|
|
if ( this_uoexec.pChild == nullptr )
|
|
{
|
|
const String* scriptname_str;
|
|
ObjArray* arr;
|
|
if ( !exec.getStringParam( 0, scriptname_str ) || !getObjArrayParam( 1, arr ) )
|
|
return new BError( "Invalid parameter type" );
|
|
Core::ScriptDef sd;
|
|
if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
|
|
return new BError( "Error in script name" );
|
|
if ( !sd.exists() )
|
|
return new BError( "Script " + sd.name() + " does not exist." );
|
|
ref_ptr<Bscript::EScriptProgram> program = find_script2( sd );
|
|
if ( program.get() == nullptr )
|
|
{
|
|
ERROR_PRINTLN( "Error reading script {}", sd.name() );
|
|
return new Bscript::BError( "Unable to read script" );
|
|
}
|
|
Core::UOExecutor* uoexec = Core::create_script_executor();
|
|
uoexec->keep_alive( true );
|
|
Core::add_common_exmods( *uoexec );
|
|
uoexec->addModule( new Module::UOExecutorModule( *uoexec ) );
|
|
|
|
uoexec->setProgram( program.get() );
|
|
if ( program->haveProgram )
|
|
{
|
|
for ( int i = (int)( arr->ref_arr.size() ) - 1; i >= 0; --i )
|
|
uoexec->pushArg( arr->ref_arr[i].get()->impptr() );
|
|
}
|
|
if ( this_uoexec.critical() ) // execute directy
|
|
{
|
|
uoexec->exec();
|
|
BObjectImp* ret;
|
|
if ( uoexec->error() )
|
|
ret = new BLong( 0 );
|
|
else if ( uoexec->ValueStack.empty() )
|
|
ret = new BLong( 1 );
|
|
else
|
|
ret = uoexec->ValueStack.back().get()->impptr()->copy();
|
|
uoexec->set_running_to_completion( false );
|
|
uoexec->suspend();
|
|
Core::scriptScheduler.schedule( uoexec );
|
|
|
|
auto array = std::make_unique<Bscript::ObjArray>();
|
|
array->addElement( new Core::ExportScriptObjImp( uoexec ) );
|
|
array->addElement( ret );
|
|
|
|
return array.release();
|
|
}
|
|
|
|
Core::scriptScheduler.schedule( uoexec );
|
|
|
|
uoexec->pParent = &this_uoexec;
|
|
this_uoexec.pChild = uoexec;
|
|
|
|
this_uoexec.PC--;
|
|
// need to fill the valuestack equal to param count (-the return value)
|
|
this_uoexec.ValueStack.push_back( BObjectRef( new BObject( UninitObject::create() ) ) );
|
|
suspend();
|
|
|
|
return UninitObject::create();
|
|
}
|
|
// reentry
|
|
BObjectImp* ret;
|
|
if ( this_uoexec.pChild->error() )
|
|
ret = new BLong( 0 );
|
|
else if ( this_uoexec.pChild->ValueStack.empty() )
|
|
ret = new BLong( 1 );
|
|
else
|
|
{
|
|
ret = this_uoexec.pChild->ValueStack.back().get()->impptr()->copy();
|
|
this_uoexec.pChild->ValueStack.pop_back();
|
|
}
|
|
auto array = std::make_unique<Bscript::ObjArray>();
|
|
array->addElement( new Core::ExportScriptObjImp( this_uoexec.pChild ) );
|
|
array->addElement( ret );
|
|
|
|
this_uoexec.pChild->pParent = nullptr;
|
|
this_uoexec.pChild = nullptr;
|
|
|
|
return array.release();
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_GetEnvironmentVariable()
|
|
{
|
|
const auto& allowed_vars = Plib::systemstate.config.allowed_environmentvariables_access;
|
|
if ( allowed_vars.empty() )
|
|
return new BError( "Environment Variable access disallowed due to pol.cfg setting" );
|
|
const String* env_name;
|
|
if ( !exec.getStringParam( 0, env_name ) )
|
|
return new BError( "Invalid parameter type" );
|
|
|
|
bool all_allowed = allowed_vars.size() == 1 && allowed_vars[0] == "*";
|
|
|
|
if ( env_name->length() == 0 )
|
|
{
|
|
auto envs = std::make_unique<Bscript::BDictionary>();
|
|
for ( char** current = environ_vars; *current; ++current )
|
|
{
|
|
std::string_view env( *current );
|
|
size_t pos = env.find_first_of( "=" );
|
|
if ( pos == std::string_view::npos )
|
|
continue;
|
|
auto key = env.substr( 0, pos );
|
|
auto key_lowered = Clib::strlowerASCII( std::string{ key } );
|
|
auto val = env.substr( pos + 1 );
|
|
|
|
if ( all_allowed || std::find( allowed_vars.begin(), allowed_vars.end(), key_lowered ) !=
|
|
allowed_vars.end() )
|
|
{
|
|
envs->addMember( new String( key, String::Tainted::YES ),
|
|
new String( val, String::Tainted::YES ) );
|
|
}
|
|
}
|
|
return envs.release();
|
|
}
|
|
|
|
if ( !all_allowed )
|
|
{
|
|
auto name_lowered = Clib::strlowerASCII( env_name->value() );
|
|
if ( std::find( allowed_vars.begin(), allowed_vars.end(), name_lowered ) == allowed_vars.end() )
|
|
return new BError( "Environment Variable access disallowed due to pol.cfg setting" );
|
|
}
|
|
|
|
const char* env_val = std::getenv( env_name->data() );
|
|
if ( !env_val )
|
|
return new BError( "Environment variable not found" );
|
|
return new String( env_val, String::Tainted::YES );
|
|
}
|
|
|
|
BObjectImp* OSExecutorModule::mf_SendEmail()
|
|
{
|
|
if ( Core::settingsManager.email_cfg.url.empty() )
|
|
return new BError( "Email settings are not configured" );
|
|
|
|
|
|
const String *from, *subject, *body, *contentType;
|
|
BObjectImp *recipient, *bcc;
|
|
|
|
if ( !( getStringParam( 0, from ) && getParamImp( 1, recipient ) &&
|
|
getStringParam( 2, subject ) && getStringParam( 3, body ) && getParamImp( 4, bcc ) &&
|
|
getStringParam( 5, contentType ) ) )
|
|
{
|
|
return new BError( "Invalid parameter type" );
|
|
}
|
|
|
|
std::shared_ptr<CURL> curl_sp( curl_easy_init(), curl_easy_cleanup );
|
|
CURL* curl = curl_sp.get();
|
|
if ( !curl )
|
|
{
|
|
return new BError( "curl_easy_init() failed" );
|
|
}
|
|
|
|
auto headers_slist = std::make_shared<CurlStringList>();
|
|
auto recipients_slist = std::make_shared<CurlStringList>();
|
|
std::string to_header_value;
|
|
|
|
auto extract_email_address = []( const String* email_string )
|
|
{
|
|
const auto& input = email_string->value();
|
|
auto start = input.find( '<' );
|
|
auto end = input.find( '>' );
|
|
|
|
if ( start != std::string::npos && end != std::string::npos && end > start + 1 )
|
|
{
|
|
return input.substr( start + 1, end - start - 1 );
|
|
}
|
|
|
|
// No angle brackets: assume the whole input is the email address
|
|
return input;
|
|
};
|
|
|
|
auto handle_recipients = [&]( BObjectImp* string_or_array, bool is_bcc ) -> BObjectImp*
|
|
{
|
|
// The `to` or `bcc` can be falsey to specify no recipient for this field.
|
|
if ( !string_or_array->isTrue() )
|
|
return nullptr;
|
|
|
|
if ( auto* recipient_string = impptrIf<String>( string_or_array ) )
|
|
{
|
|
if ( !is_bcc )
|
|
{
|
|
if ( !to_header_value.empty() )
|
|
to_header_value += ", ";
|
|
|
|
to_header_value += recipient_string->data();
|
|
}
|
|
|
|
recipients_slist->add( extract_email_address( recipient_string ) );
|
|
}
|
|
else if ( auto* recipient_array = impptrIf<ObjArray>( string_or_array ) )
|
|
{
|
|
for ( const auto& elem : recipient_array->ref_arr )
|
|
{
|
|
if ( auto* this_recipient = elem->impptr_if<String>() )
|
|
{
|
|
// Bcc is not added to the header, and only included in CURLOPT_MAIL_RCPT
|
|
if ( !is_bcc )
|
|
{
|
|
if ( !to_header_value.empty() )
|
|
to_header_value += ", ";
|
|
|
|
to_header_value += this_recipient->data();
|
|
}
|
|
|
|
recipients_slist->add( extract_email_address( this_recipient ) );
|
|
}
|
|
else
|
|
{
|
|
return new BError( "Invalid recipient type in array" );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return new BError( "Invalid recipient type" );
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_URL, Core::settingsManager.email_cfg.url.data() );
|
|
res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_URL) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
|
|
if ( !Core::settingsManager.email_cfg.username.empty() )
|
|
{
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_USERNAME,
|
|
Core::settingsManager.email_cfg.username.c_str() );
|
|
res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_USERNAME) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
}
|
|
|
|
if ( !Core::settingsManager.email_cfg.password.empty() )
|
|
{
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_PASSWORD,
|
|
Core::settingsManager.email_cfg.password.c_str() );
|
|
res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_PASSWORD) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
}
|
|
|
|
if ( Core::settingsManager.email_cfg.use_tls )
|
|
{
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_USE_SSL, CURLUSESSL_ALL ); res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_USE_SSL) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
}
|
|
|
|
if ( !Core::settingsManager.email_cfg.ca_file.empty() )
|
|
{
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_CAINFO,
|
|
Core::settingsManager.email_cfg.ca_file.c_str() );
|
|
res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_CAINFO) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
}
|
|
|
|
if ( auto res =
|
|
curl_easy_setopt( curl, CURLOPT_MAIL_FROM, extract_email_address( from ).c_str() );
|
|
res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_MAIL_FROM) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
|
|
if ( auto* recipient_to_error = handle_recipients( recipient, false ) ) // Handle To recipients
|
|
{
|
|
return recipient_to_error;
|
|
}
|
|
|
|
if ( auto* recipient_bcc_error = handle_recipients( bcc, true ) ) // Handle Bcc recipients
|
|
{
|
|
return recipient_bcc_error;
|
|
}
|
|
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_MAIL_RCPT, recipients_slist->get() );
|
|
res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_MAIL_RCPT) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
|
|
std::string date_header_value = fmt::format(
|
|
"{:%a, %d %b %Y %H:%M:%S %z}",
|
|
std::chrono::time_point_cast<std::chrono::seconds>( std::chrono::system_clock::now() ) );
|
|
|
|
std::vector<std::string> email_headers{ "Date: " + date_header_value, //
|
|
"To: " + to_header_value, //
|
|
"From: " + from->value(), //
|
|
"Subject: " + subject->value() };
|
|
|
|
for ( const auto& header : email_headers )
|
|
headers_slist->add( header );
|
|
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_HTTPHEADER, headers_slist->get() );
|
|
res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_HTTPHEADER) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
|
|
std::shared_ptr<curl_mime> mime( curl_mime_init( curl ), curl_mime_free );
|
|
|
|
curl_mimepart* part = curl_mime_addpart( mime.get() );
|
|
|
|
if ( !part )
|
|
{
|
|
return new BError( "curl_mime_addpart() failed" );
|
|
}
|
|
|
|
// `curl_mime_data` copies the string into its internal structure, so using this local is
|
|
// okay. Ref: https://github.com/curl/curl/blob/curl-8_2_1/lib/mime.c#L1380-L1390
|
|
curl_mime_data( part, body->data(), CURL_ZERO_TERMINATED );
|
|
|
|
// `curl_mime_type` copies the string into its internal structure, so using this local is
|
|
// okay. Ref: https://github.com/curl/curl/blob/curl-8_2_1/lib/mime.c#L1461
|
|
curl_mime_type( part, contentType->data() );
|
|
|
|
if ( auto res = curl_easy_setopt( curl, CURLOPT_MIMEPOST, mime.get() ); res != CURLE_OK )
|
|
{
|
|
return new BError( "curl_easy_setopt(CURLOPT_MIMEPOST) failed: " +
|
|
std::string( curl_easy_strerror( res ) ) );
|
|
}
|
|
|
|
Core::UOExecutor& this_uoexec = uoexec();
|
|
|
|
if ( !this_uoexec.suspend() )
|
|
{
|
|
DEBUGLOGLN(
|
|
"Script Error in '{}' PC={}: \n"
|
|
"\tThe execution of this script can't be blocked!",
|
|
this_uoexec.scriptname(), this_uoexec.PC );
|
|
return new Bscript::BError( "Script can't be blocked" );
|
|
}
|
|
|
|
weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
|
|
|
|
Core::networkManager.auxthreadpool->push(
|
|
[uoexec_w, curl_sp, recipients_slist, headers_slist, mime]()
|
|
{
|
|
CURL* curl = curl_sp.get();
|
|
|
|
auto res = curl_easy_perform( curl );
|
|
|
|
{
|
|
Core::PolLock lck;
|
|
|
|
if ( !uoexec_w.exists() )
|
|
{
|
|
DEBUGLOGLN( "SendEmail Script has been destroyed" );
|
|
return;
|
|
}
|
|
if ( res != CURLE_OK )
|
|
uoexec_w.get_weakptr()->ValueStack.back().set(
|
|
new BObject( new BError( curl_easy_strerror( res ) ) ) );
|
|
else
|
|
uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( new BLong( 1 ) ) );
|
|
|
|
uoexec_w.get_weakptr()->revive();
|
|
}
|
|
}
|
|
|
|
/* always cleanup */
|
|
// curl_easy_cleanup(), curl_slist_free_all(), curl_mime_free() are
|
|
// performed when the shared pointer deallocates
|
|
);
|
|
|
|
return new BLong( 0 );
|
|
}
|
|
|
|
|
|
size_t OSExecutorModule::sizeEstimate() const
|
|
{
|
|
size_t size = sizeof( *this );
|
|
for ( const auto& obj : events_ )
|
|
{
|
|
if ( obj != nullptr )
|
|
size += obj->sizeEstimate();
|
|
}
|
|
size += Clib::memsize( events_ );
|
|
return size;
|
|
}
|
|
} // namespace Pol::Module
|