• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 16683711876

01 Aug 2025 07:40PM UTC coverage: 59.756% (-0.03%) from 59.79%
16683711876

push

github

web-flow
use c++20 (#799)

* use c++20
* increased used clang version
* compiler report and logfacility use now compile time formatting, which
means that the formatstring gets checked at compile time. (which found 2 errors)
adapted a few places since report only accepts formatting arguments
adapted a few places with logging since char[] is compile time
formatting and string or chr* is runtime formatting
* use std::ranges instead of boost
* disabled pragma_assume vs specific macro (I guess noone cares)
* needed to fix ancient ms exception code
* modernized SpinLock
* removed unused code in ECompile
* replaced std::filesystem::path::u8string with string. It now returns an actual u8string type
* cleanup layers.h added the defines for other layers which where before
  only defined in the pkt
* osmod::OpenConnection and HTTPRequest cleanup: early outs, dont check for pChild which is
  only needed for startscript and placed suspend at the very last
  position

* fix warning

* rebuild cache with new compiler version

* define c++ standard for external libs where possible

* added fixme

156 of 212 new or added lines in 26 files covered. (73.58%)

45 existing lines in 19 files now uncovered.

43621 of 72999 relevant lines covered (59.76%)

409874.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.5
/pol-core/pol/module/osmod.cpp
1
/** @file
2
 *
3
 * @par History
4
 */
5

6
#include "osmod.h"
7

8
#include "bscript/berror.h"
9
#include "bscript/bobject.h"
10
#include "bscript/bstruct.h"
11
#include "bscript/dict.h"
12
#include "bscript/impstr.h"
13
#include "clib/clib.h"
14
#include "clib/logfacility.h"
15
#include "clib/network/sckutil.h"
16
#include "clib/rawtypes.h"
17
#include "clib/refptr.h"
18
#include "clib/stlutil.h"
19
#include "clib/threadhelp.h"
20
#include "clib/weakptr.h"
21
#include "globals/settings.h"
22
#include "plib/systemstate.h"
23

24
#include "../exscrobj.h"
25
#include "../globals/script_internals.h"
26
#include "../globals/state.h"
27
#include "../item/item.h"
28
#include "../mobile/attribute.h"
29
#include "../mobile/charactr.h"
30
#include "../mobile/npc.h"
31
#include "../network/auxclient.h"
32
#include "../network/packethelper.h"
33
#include "../network/packets.h"
34
#include "../network/pktdef.h"
35
#include "../poldbg.h"
36
#include "../polsem.h"
37
#include "../profile.h"
38
#include "../schedule.h"
39
#include "../scrdef.h"
40
#include "../scrsched.h"
41
#include "../scrstore.h"
42
#include "../skills.h"
43
#include "../ufunc.h"
44
#include "../uoexec.h"
45
#include "npcmod.h"
46
#include "uomod.h"
47

48
#include <chrono>
49
#include <module_defs/os.h>
50

51
#ifdef _WIN32
52
#pragma comment( lib, "crypt32.lib" )
53
#endif
54
#include <ctime>
55
#include <curl/curl.h>
56
#include <memory>
57
#include <stdlib.h>
58
#include <string.h>
59
#include <string>
60
#include <string_view>
61
#include <unordered_map>
62

63
// environment variable for mf_GetEnvironmentVariable
64
#ifdef _MSC_VER
65
extern char** _environ;
66
char** environ_vars = _environ;
67
#else
68
extern char** environ;
69
char** environ_vars = environ;
70
#endif
71

72
namespace
73
{
74
class CurlStringList
75
{
76
public:
77
  CurlStringList() : resource_( nullptr ) {}
16✔
78

79
  ~CurlStringList()
16✔
80
  {
81
    if ( resource_ )
16✔
82
    {
83
      curl_slist_free_all( resource_ );
15✔
84
    }
85
  }
16✔
86

87
  void add( const std::string& str )
38✔
88
  {
89
    auto* new_resource = curl_slist_append( resource_, str.c_str() );
38✔
90

91
    if ( new_resource != resource_ && resource_ != nullptr )
38✔
92
    {
93
      curl_slist_free_all( resource_ );
×
94
    }
95

96
    resource_ = new_resource;
38✔
97
  }
38✔
98

99
  curl_slist* get() const { return resource_; }
14✔
100

101
private:
102
  curl_slist* resource_;
103
};
104

105
};  // namespace
106

107
namespace Pol
108
{
109
namespace Module
110
{
111
using namespace Bscript;
112

113
unsigned int getnewpid( Core::UOExecutor* uoexec )
280✔
114
{
115
  return Core::scriptScheduler.get_new_pid( uoexec );
280✔
116
}
117
void freepid( unsigned int pid )
280✔
118
{
119
  Core::scriptScheduler.free_pid( pid );
280✔
120
}
280✔
121

122
OSExecutorModule::OSExecutorModule( Bscript::Executor& exec )
280✔
123
    : TmplExecutorModule<OSExecutorModule, Core::PolModule>( exec ),
124
      critical_( false ),
280✔
125
      priority_( 1 ),
280✔
126
      warn_on_runaway_( true ),
280✔
127
      blocked_( false ),
280✔
128
      sleep_until_clock_( 0 ),
280✔
129
      hold_itr_(),
280✔
130
      in_hold_list_( Core::HoldListType::NO_LIST ),
280✔
131
      wait_type( Core::WAIT_TYPE::WAIT_UNKNOWN ),
280✔
132
      pid_( getnewpid( &uoexec() ) ),
280✔
133
      max_eventqueue_size( Core::MAX_EVENTQUEUE_SIZE ),
280✔
134
      events_()
560✔
135
{
136
}
280✔
137

138
OSExecutorModule::~OSExecutorModule()
560✔
139
{
140
  freepid( pid_ );
280✔
141
  pid_ = 0;
280✔
142
  while ( !events_.empty() )
285✔
143
  {
144
    Bscript::BObject ob( events_.front() );
5✔
145
    events_.pop_front();
5✔
146
  }
5✔
147
}
560✔
148

149
unsigned int OSExecutorModule::pid() const
32✔
150
{
151
  return pid_;
32✔
152
}
153

154
BObjectImp* OSExecutorModule::mf_Create_Debug_Context()
×
155
{
156
  return Core::create_debug_context();
×
157
}
158

159
BObjectImp* OSExecutorModule::mf_Debugger()
2✔
160
{
161
  Core::UOExecutor* uoexec;
162
  if ( find_uoexec( pid_, &uoexec ) )
2✔
163
  {
164
    return new BLong( uoexec->attach_debugger() );
2✔
165
  }
166
  else
167
    return new BError( "Could not find UOExecutor for current process." );
×
168
}
169

170
BObjectImp* OSExecutorModule::mf_GetProcess()
130✔
171
{
172
  int pid;  // note that while pid's are unsigned, valid values are forced to fit within a signed
173
            // range
174
  if ( !getParam( 0, pid ) )
130✔
175
    return new BError( "Invalid parameter type" );
2✔
176

177
  if ( pid == -1 )
128✔
178
  {
179
    pid = pid_;  // get executor's own pid
60✔
180
  }
181

182
  Core::UOExecutor* uoexec;
183
  if ( find_uoexec( pid, &uoexec ) )
128✔
184
    return new Core::ScriptExObjImp( uoexec );
126✔
185
  else
186
    return new BError( "Process not found" );
2✔
187
}
188

189
BObjectImp* OSExecutorModule::mf_GetPid()
34✔
190
{
191
  return new BLong( pid_ );
34✔
192
}
193
/*  Ok, this is going to be fun.  In the case where we block,
194
the caller is going to take our return value and push
195
it on the value stack.
196

197
What we'll do is push the value that should be returned
198
if a timeout occurs.  THis way, for timeouts, all we have
199
to do is move the script back into the run queue.
200

201
When we actually complete something, we'll have to
202
pop the value off the stack, and replace it with
203
the real result.
204

205
Whew!
206
*/
207
BObjectImp* OSExecutorModule::mf_Sleep()
13✔
208
{
209
  int nsecs = exec.paramAsLong( 0 );
13✔
210
  SleepFor( nsecs > 0 ? static_cast<u32>( nsecs ) : 1u );
13✔
211
  return new BLong( 0 );
13✔
212
}
213

214
BObjectImp* OSExecutorModule::mf_Sleepms()
15,712,303✔
215
{
216
  int msecs = exec.paramAsLong( 0 );
15,712,303✔
217
  SleepForMs( msecs > 0 ? static_cast<u32>( msecs ) : 1u );
15,712,303✔
218
  return new BLong( 0 );
15,712,303✔
219
}
220

221
BObjectImp* OSExecutorModule::mf_Wait_For_Event()
1,695✔
222
{
223
  if ( !events_.empty() )
1,695✔
224
  {
225
    BObjectImp* imp = events_.front();
485✔
226
    events_.pop_front();
485✔
227
    return imp;
485✔
228
  }
229
  else
230
  {
231
    auto param = exec.getParamImp( 0 );
1,210✔
232
    double nsecs = 0;
1,210✔
233

234
    if ( auto* long_param = impptrIf<BLong>( param ) )
1,210✔
235
    {
236
      nsecs = long_param->value();
1,205✔
237
      if ( !nsecs )
1,205✔
238
        return new BLong( 0 );
56✔
239
      if ( nsecs < 1 )
1,149✔
240
        nsecs = 1;
×
241
    }
242
    else if ( auto* double_param = impptrIf<Double>( param ) )
5✔
243
    {
244
      nsecs = double_param->value();
4✔
245
      if ( !nsecs )
4✔
246
        return new BLong( 0 );
1✔
247
      if ( nsecs < 0.01 )
3✔
248
        nsecs = 0.01;
2✔
249
    }
250
    else
251
    {
252
      return new BLong( 0 );
1✔
253
    }
254

255
    wait_type = Core::WAIT_TYPE::WAIT_EVENT;
1,152✔
256
    blocked_ = true;
1,152✔
257
    sleep_until_clock_ =
1,152✔
258
        Core::polclock() + Clib::clamp_convert<u32>( nsecs * Core::POLCLOCKS_PER_SEC );
1,152✔
259
    return new BLong( 0 );
1,152✔
260
  }
261
}
262

263
BObjectImp* OSExecutorModule::mf_Events_Waiting()
×
264
{
265
  return new BLong( static_cast<int>( events_.size() ) );
×
266
}
267

268
BObjectImp* OSExecutorModule::mf_Start_Script()
27✔
269
{
270
  const String* scriptname_str;
271
  if ( exec.getStringParam( 0, scriptname_str ) )
27✔
272
  {
273
    BObjectImp* imp = exec.getParamImp( 1 );
27✔
274

275
    // FIXME needs to inherit available modules?
276
    Core::ScriptDef sd;
27✔
277
    if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
27✔
278
    {
279
      return new BError( "Error in script name" );
×
280
    }
281
    if ( !sd.exists() )
27✔
282
    {
283
      return new BError( "Script " + sd.name() + " does not exist." );
×
284
    }
285
    UOExecutorModule* new_uoemod = Core::start_script( sd, imp->copy() );
27✔
286
    if ( new_uoemod == nullptr )
27✔
287
    {
288
      return new BError( "Unable to start script" );
×
289
    }
290
    UOExecutorModule* this_uoemod = static_cast<UOExecutorModule*>( exec.findModule( "uo" ) );
27✔
291
    if ( new_uoemod != nullptr && this_uoemod != nullptr )
27✔
292
    {
293
      new_uoemod->controller_ = this_uoemod->controller_;
27✔
294
    }
295

296
    return new Core::ScriptExObjImp( &new_uoemod->uoexec() );
27✔
297
  }
27✔
298
  else
299
  {
300
    return new BError( "Invalid parameter type" );
×
301
  }
302
}
303

304

305
BObjectImp* OSExecutorModule::mf_Start_Skill_Script()
×
306
{
307
  Mobile::Character* chr;
308
  const Mobile::Attribute* attr;
309

310
  if ( getCharacterParam( 0, chr ) && getAttributeParam( 1, attr ) )
×
311
  {
312
    if ( !attr->disable_core_checks && !Core::CanUseSkill( chr->client ) )
×
313
      return new BLong( 0 );
×
314
    else
315
    {
316
      const String* script_name;
317
      Core::ScriptDef script;
×
318

319
      if ( exec.getStringParam( 2, script_name ) && ( script_name->length() > 0 ) )
×
320
      {
321
        if ( !script.config_nodie( script_name->value(), exec.prog()->pkg, "scripts/skills/" ) )
×
322
        {
323
          return new BError( "Error in script name" );
×
324
        }
325
        if ( !script.exists() )
×
326
        {
327
          return new BError( "Script " + script.name() + " does not exist." );
×
328
        }
329
      }
330
      else
331
      {
332
        if ( !attr->script_.empty() )
×
333
          script = attr->script_;
×
334
        else
335
          return new BError( "No script defined for attribute " + attr->name + "." );
×
336
      }
337

338
      ref_ptr<EScriptProgram> prog = find_script2(
339
          script, true,
340
          /* complain if not found */ Plib::systemstate.config.cache_interactive_scripts );
×
341

342
      if ( prog.get() != nullptr )
×
343
      {
344
        BObjectImp* imp = exec.getParamImp( 3 );
×
345
        if ( imp )
×
346
        {
347
          if ( chr->start_script( prog.get(), true, imp->copy() ) )
×
348
          {
349
            if ( chr->hidden() && attr->unhides )
×
350
              chr->unhide();
×
351
            if ( attr->delay_seconds )
×
352
              chr->disable_skills_until( Core::poltime() + attr->delay_seconds );
×
353
          }
354
        }
355
        else
356
        {
357
          if ( chr->start_script( prog.get(), true ) )
×
358
          {
359
            if ( chr->hidden() && attr->unhides )
×
360
              chr->unhide();
×
361
            if ( attr->delay_seconds )
×
362
              chr->disable_skills_until( Core::poltime() + attr->delay_seconds );
×
363
          }
364
        }
365
      }
366
      else
367
      {
368
        std::string msg = "Unable to start skill script:";
×
369
        msg += script.c_str();
×
370
        Core::send_sysmessage( chr->client, msg.c_str() );
×
371

372
        return new BLong( 0 );
×
373
      }
×
374
      return new BLong( 1 );
×
375
    }
×
376
  }
377
  else
378
  {
379
    return new BError( "Invalid parameter type" );
×
380
  }
381
}
382

383
BObjectImp* OSExecutorModule::mf_Set_Critical()
10✔
384
{
385
  int crit;
386
  if ( exec.getParam( 0, crit ) )
10✔
387
  {
388
    critical_ = ( crit != 0 );
10✔
389
    return new BLong( 1 );
10✔
390
  }
391
  else
392
  {
393
    return new BError( "Invalid parameter type" );
×
394
  }
395
}
396

397
BObjectImp* OSExecutorModule::mf_Is_Critical()
×
398
{
399
  if ( critical_ )
×
400
    return new BLong( 1 );
×
401
  else
402
    return new BLong( 0 );
×
403
}
404

405
BObjectImp* OSExecutorModule::mf_Run_Script_To_Completion()
9✔
406
{
407
  const char* scriptname = exec.paramAsString( 0 );
9✔
408
  if ( scriptname == nullptr )
9✔
409
    return new BLong( 0 );
×
410

411
  // FIXME needs to inherit available modules?
412
  Core::ScriptDef script;
9✔
413

414
  if ( !script.config_nodie( scriptname, exec.prog()->pkg, "scripts/" ) )
9✔
415
    return new BError( "Script descriptor error" );
×
416

417
  if ( !script.exists() )
9✔
418
    return new BError( "Script does not exist" );
×
419

420
  return Core::run_script_to_completion( script, getParamImp( 1 ) );
9✔
421
}
9✔
422

423
BObjectImp* OSExecutorModule::mf_Run_Script()
26✔
424
{
425
  UOExecutorModule* this_uoemod = static_cast<UOExecutorModule*>( exec.findModule( "uo" ) );
26✔
426
  Core::UOExecutor& this_uoexec = uoexec();
26✔
427

428

429
  if ( this_uoexec.pChild == nullptr )
26✔
430
  {
431
    const String* scriptname_str;
432
    if ( exec.getStringParam( 0, scriptname_str ) )
13✔
433
    {
434
      BObjectImp* imp = exec.getParamImp( 1 );
13✔
435

436
      // FIXME needs to inherit available modules?
437
      Core::ScriptDef sd;
13✔
438
      if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
13✔
439
      {
440
        return new BError( "Error in script name" );
×
441
      }
442
      if ( !sd.exists() )
13✔
443
      {
444
        return new BError( "Script " + sd.name() + " does not exist." );
×
445
      }
446
      UOExecutorModule* new_uoemod = Core::start_script( sd, imp->copy() );
13✔
447
      if ( new_uoemod == nullptr )
13✔
448
      {
449
        return new BError( "Unable to run script" );
×
450
      }
451
      if ( new_uoemod )
13✔
452
      {
453
        new_uoemod->controller_ = this_uoemod->controller_;
13✔
454
      }
455
      Core::UOExecutor& new_uoexec = new_uoemod->uoexec();
13✔
456
      //      OSExecutorModule* osemod = uoexec->os_module;
457
      new_uoexec.pParent = &this_uoexec;
13✔
458
      this_uoexec.pChild = &new_uoexec;
13✔
459

460
      // we want to forcefully do this instruction over again:
461
      this_uoexec.PC--;  // run_script(
13✔
462
      this_uoexec.ValueStack.push_back(
13✔
463
          BObjectRef( new BObject( UninitObject::create() ) ) );  //   script_name,
26✔
464
      // No need to push on "param" since the new BLong(0) below will take care of it.//   param )
465

466
      // Put me on hold until my child is done.
467
      suspend();
13✔
468

469
      return new BLong( 0 );
13✔
470
    }
13✔
471
    else
472
    {
473
      return new BError( "Invalid parameter type" );
×
474
    }
475
  }
476

477
  // else I am running a child script, and its ended
478
  BObjectImp* ret;
479

480
  if ( this_uoexec.pChild->ValueStack.empty() )
13✔
481
    ret = new BLong( 1 );
8✔
482
  else
483
    ret = this_uoexec.pChild->ValueStack.back().get()->impptr()->copy();
5✔
484

485
  this_uoexec.pChild->pParent = nullptr;
13✔
486
  this_uoexec.pChild = nullptr;
13✔
487

488
  return ret;
13✔
489
}
490

491
BObjectImp* OSExecutorModule::mf_Set_Debug()
×
492
{
493
  int dbg;
494
  if ( getParam( 0, dbg ) )
×
495
  {
496
    if ( dbg )
×
497
      exec.setDebugLevel( Executor::SOURCELINES );
×
498
    return new BLong( 1 );
×
499
  }
500
  else
501
  {
502
    return new BError( "Invalid parameter type" );
×
503
  }
504
}
505

506
BObjectImp* OSExecutorModule::mf_SysLog()
1,529✔
507
{
508
  BObjectImp* imp = exec.getParamImp( 0 );
1,529✔
509
  int log_verbose;
510
  const String* color;
511
  if ( !exec.getParam( 1, log_verbose ) || !exec.getStringParam( 2, color ) )
1,529✔
512
    return new BError( "Invalid parameter type" );
×
513
  std::string strval = imp->getStringRep();
1,529✔
514
  if ( log_verbose )
1,529✔
515
  {
516
    POLLOGLN( "[{}]: {}", exec.scriptname(), strval );
×
517
    if ( Plib::systemstate.config.enable_colored_output && color->length() )
×
518
    {
519
      INFO_PRINTLN( "{}syslog [{}]: {}{}", color->value(), exec.scriptname(), strval,
×
520
                    Clib::Logging::CONSOLE_RESET_COLOR );
521
    }
522
    else
523
    {
524
      INFO_PRINTLN( "syslog [{}]: {}", exec.scriptname(), strval );
×
525
    }
526
  }
527
  else
528
  {
529
    if ( Plib::systemstate.config.enable_colored_output && color->length() )
1,529✔
530
    {
531
      POLLOGLN( strval );
762✔
532
      INFO_PRINTLN( "{}{}{}", color->value(), strval, Clib::Logging::CONSOLE_RESET_COLOR );
762✔
533
    }
534
    else
535
    {
536
      POLLOG_INFOLN( strval );
767✔
537
    }
538
  }
539
  return new BLong( 1 );
1,529✔
540
}
1,529✔
541

542
BObjectImp* OSExecutorModule::mf_Set_Priority()
×
543
{
544
  int newpri;
545
  if ( getParam( 0, newpri, 1, 255 ) )
×
546
  {
547
    int oldpri = priority_;
×
548
    priority_ = static_cast<unsigned char>( newpri );
×
549
    return new BLong( oldpri );
×
550
  }
551
  else
552
  {
553
    return new BError( "Invalid parameter type" );
×
554
  }
555
}
556

557

558
BObjectImp* OSExecutorModule::mf_Unload_Scripts()
×
559
{
560
  const String* str;
561
  if ( getStringParam( 0, str ) )
×
562
  {
563
    int n;
564
    if ( str->length() == 0 )
×
565
      n = Core::unload_all_scripts();
×
566
    else
567
      n = Core::unload_script( str->data() );
×
568
    return new BLong( n );
×
569
  }
570
  else
571
  {
572
    return new BError( "Invalid parameter type" );
×
573
  }
574
}
575

576
BObjectImp* OSExecutorModule::clear_event_queue()  // DAVE
109✔
577
{
578
  while ( !events_.empty() )
133✔
579
  {
580
    BObject ob( events_.front() );
24✔
581
    events_.pop_front();
24✔
582
  }
24✔
583
  return new BLong( 1 );
109✔
584
}
585

586
BObjectImp* OSExecutorModule::mf_Set_Event_Queue_Size()  // DAVE 11/24
16✔
587
{
588
  unsigned short param;
589
  if ( getParam( 0, param ) )
16✔
590
  {
591
    unsigned short oldsize = max_eventqueue_size;
16✔
592
    max_eventqueue_size = param;
16✔
593
    return new BLong( oldsize );
16✔
594
  }
595
  else
596
    return new BError( "Invalid parameter type" );
×
597
}
598

599
BObjectImp* OSExecutorModule::mf_OpenURL()
×
600
{
601
  Mobile::Character* chr;
602
  const String* str;
603
  if ( getCharacterParam( 0, chr ) && ( ( str = getStringParam( 1 ) ) != nullptr ) )
×
604
  {
605
    if ( chr->has_active_client() )
×
606
    {
607
      Network::PktHelper::PacketOut<Network::PktOut_A5> msg;
×
608
      unsigned urllen;
609
      const char* url = str->data();
×
610

611
      urllen = static_cast<unsigned int>( strlen( url ) );
×
612
      if ( urllen > URL_MAX_LEN )
×
613
        urllen = URL_MAX_LEN;
×
614

615
      msg->WriteFlipped<u16>( urllen + 4u );
×
616
      msg->Write( url, static_cast<u16>( urllen + 1 ) );
×
617
      msg.Send( chr->client );
×
618
      return new BLong( 1 );
×
619
    }
×
620
    else
621
    {
622
      return new BError( "No client attached" );
×
623
    }
624
  }
625
  else
626
  {
627
    return new BError( "Invalid parameter type" );
×
628
  }
629
}
630

631

632
BObjectImp* OSExecutorModule::mf_OpenConnection()
4✔
633
{
634
  const String* host;
635
  const String* scriptname_str;
636
  BObjectImp* scriptparam;
637
  unsigned short port;
638
  int assume_string_int;
639
  int keep_connection_int;
640
  int ignore_line_breaks_int;
641
  if ( !getStringParam( 0, host ) || !getParam( 1, port ) || !getStringParam( 2, scriptname_str ) ||
8✔
642
       !getParamImp( 3, scriptparam ) || !getParam( 4, assume_string_int ) ||
4✔
643
       !getParam( 5, keep_connection_int ) || !getParam( 6, ignore_line_breaks_int ) )
8✔
NEW
644
    return new BError( "Invalid parameter type" );
×
645

646
  // FIXME needs to inherit available modules?
647
  Core::ScriptDef sd;
4✔
648
  if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
4✔
649
  {
NEW
650
    return new BError( "Error in script name" );
×
651
  }
652
  if ( !sd.exists() )
4✔
653
  {
NEW
654
    return new BError( "Script " + sd.name() + " does not exist." );
×
655
  }
656
  Core::UOExecutor& this_uoexec = uoexec();
4✔
657
  if ( !this_uoexec.suspend() )
4✔
658
  {
NEW
659
    DEBUGLOGLN(
×
660
        "Script Error in '{}' PC={}: \n"
661
        "\tThe execution of this script can't be blocked!",
NEW
662
        this_uoexec.scriptname(), this_uoexec.PC );
×
NEW
663
    return new Bscript::BError( "Script can't be blocked" );
×
664
  }
665

666
  weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
4✔
667
  std::string hostname( host->value() );
4✔
668
  bool assume_string = assume_string_int != 0;
4✔
669
  bool keep_connection = keep_connection_int != 0;
4✔
670
  bool ignore_line_breaks = ignore_line_breaks_int != 0;
4✔
671
  auto* paramobjimp_raw = scriptparam->copy();  // prevent delete
4✔
672
  Core::networkManager.auxthreadpool->push(
8✔
673
      [uoexec_w, sd, hostname, port, paramobjimp_raw, assume_string, keep_connection,
8✔
674
       ignore_line_breaks]()
12✔
675
      {
676
        Clib::Socket s;
4✔
677
        std::unique_ptr<Network::AuxClientThread> client;
4✔
678
        bool success_open = s.open( hostname.c_str(), port );
4✔
679
        {
680
          Core::PolLock lck;
4✔
681
          std::unique_ptr<BObjectImp> paramobjimp( paramobjimp_raw );
4✔
682
          if ( !uoexec_w.exists() )
4✔
683
          {
NEW
684
            DEBUGLOGLN( "OpenConnection Script has been destroyed" );
×
NEW
685
            s.close();
×
NEW
686
            return;
×
687
          }
688
          if ( !success_open )
4✔
689
          {
NEW
690
            uoexec_w.get_weakptr()->ValueStack.back().set(
×
NEW
691
                new BObject( new BError( "Error connecting to client" ) ) );
×
NEW
692
            uoexec_w.get_weakptr()->revive();
×
NEW
693
            return;
×
694
          }
695
          uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( new BLong( 1 ) ) );
4✔
696
          uoexec_w.get_weakptr()->revive();
4✔
697
          client.reset( new Network::AuxClientThread( sd, std::move( s ), paramobjimp.release(),
8✔
698
                                                      assume_string, keep_connection,
699
                                                      ignore_line_breaks ) );
12✔
700
        }
4✔
701
        if ( client )
4✔
702
          client->run();
4✔
703
      } );
4✔
704

705
  return new BLong( 0 );
4✔
706
}
4✔
707

708
size_t curlWriteCallback( void* contents, size_t size, size_t nmemb, void* userp )
12✔
709
{
710
  ( static_cast<std::string*>( userp ) )->append( static_cast<char*>( contents ), size * nmemb );
12✔
711
  return size * nmemb;
12✔
712
}
713

714
const int HTTPREQUEST_EXTENDED_RESPONSE = 0x0001;
715

716
struct CurlHeaderData
717
{
718
  std::optional<std::string> statusText;
719
  std::map<std::string, std::string> headers = {};
720
};
721

722
size_t curlReadHeaderCallback( char* buffer, size_t size, size_t nitems, void* userdata )
18✔
723
{
724
  auto headerData = static_cast<CurlHeaderData*>( userdata );
18✔
725
  std::string value( buffer, size * nitems );
18✔
726

727
  // statusText is set after parsing the first header line
728
  if ( !headerData->statusText.has_value() )
18✔
729
  {
730
    std::string statusText = "";
5✔
731
    if ( value.rfind( "HTTP/", 0 ) == 0 )
5✔
732
    {
733
      // Find space after "HTTP/x" (before status code)
734
      size_t pos = value.find( " " );
5✔
735
      if ( pos != std::string::npos )
5✔
736
      {
737
        // Find space after status code (before status text)
738
        pos = value.find( " ", pos + 1 );
5✔
739
        if ( pos != std::string::npos )
5✔
740
        {
741
          statusText = Clib::strtrim( value.substr( pos + 1 ) );
5✔
742
        }
743
      }
744
    }
745
    headerData->statusText.emplace( statusText );
5✔
746
  }
5✔
747
  else
748
  {
749
    size_t pos = value.find( ":" );
13✔
750
    if ( pos != std::string::npos )
13✔
751
    {
752
      std::string headerName = Clib::strlowerASCII( value.substr( 0, pos ) );
8✔
753
      std::string headerValue = Clib::strtrim( value.substr( pos + 1 ) );
8✔
754

755
      // If the header already exists, append it (comma-separated) to previous value
756
      if ( headerData->headers.count( headerName ) )
8✔
757
      {
758
        auto& existing = headerData->headers.at( headerName );
1✔
759
        existing.append( ", " );
1✔
760
        existing.append( headerValue );
1✔
761
      }
762
      else
763
      {
764
        headerData->headers.emplace( headerName, headerValue );
7✔
765
      }
766
    }
8✔
767
  }
768

769
  return nitems * size;
18✔
770
}
18✔
771

772
BObjectImp* OSExecutorModule::mf_HTTPRequest()
18✔
773
{
774
  const String *url, *method;
775
  BObjectImp* options;
776
  int flags;
777
  if ( !getStringParam( 0, url ) || !getStringParam( 1, method ) || !getParamImp( 2, options ) ||
36✔
778
       !getParam( 3, flags ) )
18✔
NEW
779
    return new BError( "Invalid parameter type" );
×
780
  Core::UOExecutor& this_uoexec = uoexec();
18✔
781
  weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
18✔
782

783
  std::shared_ptr<CURL> curl_sp( curl_easy_init(), curl_easy_cleanup );
18✔
784
  CURL* curl = curl_sp.get();
18✔
785
  if ( !curl )
18✔
NEW
786
    return new BError( "curl_easy_init() failed" );
×
787
  curl_easy_setopt( curl, CURLOPT_URL, url->data() );
18✔
788
  curl_easy_setopt( curl, CURLOPT_CUSTOMREQUEST, method->data() );
18✔
789
  curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, curlWriteCallback );
18✔
790
  curl_easy_setopt( curl, CURLOPT_ACCEPT_ENCODING,
18✔
791
                    "" );  // allow plaintext and compressed (with automatic deflate)
792

793
  struct curl_slist* chunk = nullptr;
18✔
794
  if ( options->isa( Bscript::BObjectImp::OTStruct ) )
18✔
795
  {
796
    Bscript::BStruct* opts = static_cast<Bscript::BStruct*>( options );
18✔
797
    const BObjectImp* data = opts->FindMember( "data" );
18✔
798
    if ( data != nullptr )
18✔
799
    {
NEW
800
      curl_easy_setopt( curl, CURLOPT_COPYPOSTFIELDS, data->getStringRep().c_str() );
×
801
    }
802

803
    const BObjectImp* headers_ = opts->FindMember( "headers" );
18✔
804

805
    if ( headers_ != nullptr && headers_->isa( BObjectImp::OTStruct ) )
18✔
806
    {
NEW
807
      const BStruct* headers = static_cast<const BStruct*>( headers_ );
×
808

NEW
809
      for ( const auto& content : headers->contents() )
×
810
      {
NEW
811
        BObjectImp* ref = content.second->impptr();
×
NEW
812
        std::string header = content.first + ": " + ref->getStringRep();
×
NEW
813
        chunk = curl_slist_append( chunk, header.c_str() );
×
UNCOV
814
      }
×
NEW
815
      curl_easy_setopt( curl, CURLOPT_HTTPHEADER, chunk );
×
816
    }
817
  }
818

819
  if ( !this_uoexec.suspend() )
18✔
820
  {
NEW
821
    DEBUGLOGLN(
×
822
        "Script Error in '{}' PC={}: \n"
823
        "\tThe execution of this script can't be blocked!",
NEW
824
        this_uoexec.scriptname(), this_uoexec.PC );
×
NEW
825
    return new Bscript::BError( "Script can't be blocked" );
×
826
  }
827

828
  Core::networkManager.auxthreadpool->push(
36✔
829
      [uoexec_w, curl_sp, chunk, flags]()
36✔
830
      {
831
        CURL* curl = curl_sp.get();
18✔
832
        CURLcode res;
833
        std::string readBuffer;
18✔
834
        CurlHeaderData headerData;
18✔
835

836
        if ( flags == HTTPREQUEST_EXTENDED_RESPONSE )
18✔
837
        {
838
          curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, curlReadHeaderCallback );
5✔
839
          curl_easy_setopt( curl, CURLOPT_HEADERDATA, &headerData );
5✔
840
        }
841

842
        curl_easy_setopt( curl, CURLOPT_WRITEDATA, &readBuffer );
18✔
843

844
        /* Perform the request, res will get the return code */
845
        res = curl_easy_perform( curl );
18✔
846
        if ( chunk != nullptr )
18✔
NEW
847
          curl_slist_free_all( chunk );
×
848
        {
849
          Core::PolLock lck;
18✔
850

851
          if ( !uoexec_w.exists() )
18✔
852
          {
NEW
853
            DEBUGLOGLN( "HTTPRequest Script has been destroyed" );
×
NEW
854
            return;
×
855
          }
856
          /* Check for errors */
857
          if ( res != CURLE_OK )
18✔
858
          {
NEW
859
            uoexec_w.get_weakptr()->ValueStack.back().set(
×
NEW
860
                new BObject( new BError( curl_easy_strerror( res ) ) ) );
×
861
          }
862
          else
863
          {
864
            if ( flags == HTTPREQUEST_EXTENDED_RESPONSE )
18✔
865
            {
866
              auto response = std::make_unique<Bscript::BDictionary>();
5✔
867
              auto headers = std::make_unique<Bscript::BDictionary>();
5✔
868
              long http_code = 0;
5✔
869

870
              res = curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &http_code );
5✔
871
              if ( res == CURLE_OK )
5✔
872
              {
873
                response->addMember( new String( "status" ), new BLong( http_code ) );
5✔
874
              }
875
              else
876
              {
NEW
877
                response->addMember( new String( "status" ),
×
NEW
878
                                     new BError( curl_easy_strerror( res ) ) );
×
879
              }
880

881
              response->addMember( new String( "statusText" ),
10✔
882
                                   new String( headerData.statusText.value_or( "" ) ) );
10✔
883

884
              response->addMember( new String( "body" ), new String( readBuffer ) );
5✔
885

886
              for ( auto const& [key, value] : headerData.headers )
12✔
887
              {
888
                headers->addMember( new String( key ), new String( value ) );
7✔
889
              }
890

891
              response->addMember( new String( "headers" ), headers.release() );
5✔
892

893
              uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( response.release() ) );
5✔
894
            }
5✔
895
            else
896
            {
897
              // TODO: no sanitize happens, optional function param iso/utf8 encoding, or
898
              // parse the header of the http answer?
899
              uoexec_w.get_weakptr()->ValueStack.back().set(
26✔
900
                  new BObject( new String( readBuffer ) ) );
13✔
901
            }
902
          }
903

904
          uoexec_w.get_weakptr()->revive();
18✔
905
        }
18✔
906

907
        /* always cleanup */
908
        // curl_easy_cleanup() is performed when the shared pointer deallocates
909
      } );
18✔
910

911
  return new BLong( 0 );
18✔
912
}
18✔
913

914
// signal_event() takes ownership of the pointer which is passed to it.
915
// Objects must not be touched or deleted after being sent here!
916
// TODO: Find a better way to enforce this in the codebase.
917
bool OSExecutorModule::signal_event( BObjectImp* imp )
1,593✔
918
{
919
  INC_PROFILEVAR( events );
1,593✔
920

921
  if ( blocked_ && ( wait_type == Core::WAIT_TYPE::WAIT_EVENT ) )  // already being waited for
1,593✔
922
  {
923
    /* Now, the tricky part.  The value to return on an error or
924
    completion condition has already been pushed onto the value
925
    stack - so, we need to replace it with the real result.
926
    */
927
    exec.ValueStack.back().set( new BObject( imp ) );
1,078✔
928
    /* This executor will get moved to the run queue at the
929
    next step_scripts(), where blocked is checked looking
930
    for timed out or completed operations. */
931

932
    revive();
1,078✔
933
  }
934
  else  // not being waited for, so queue for later.
935
  {
936
    if ( events_.size() < max_eventqueue_size )
515✔
937
    {
938
      events_.push_back( imp );
514✔
939
    }
940
    else
941
    {
942
      if ( Plib::systemstate.config.discard_old_events )
1✔
943
      {
944
        BObject ob( events_.front() );
×
945
        events_.pop_front();
×
946
        events_.push_back( imp );
×
947
      }
×
948
      else
949
      {
950
        BObject ob( imp );
1✔
951
        if ( Plib::systemstate.config.loglevel >= 11 )
1✔
952
        {
953
          INFO_PRINTLN( "Event queue for {} is full, discarding event.", exec.scriptname() );
1✔
954
          ExecutorModule* em = exec.findModule( "npc" );
1✔
955
          if ( em )
1✔
956
          {
957
            NPCExecutorModule* npcemod = static_cast<NPCExecutorModule*>( em );
1✔
958
            INFO_PRINTLN( "NPC Serial: {:#x}{}", npcemod->controlled_npc().serial,
1✔
959
                          npcemod->controlled_npc().pos() );
1✔
960
          }
961

962
          INFO_PRINTLN( "Event: {}", ob->getStringRep() );
1✔
963
        }
964
        return false;  // Event-queue is full
1✔
965
      }
1✔
966
    }
967
  }
968

969
  return true;  // Event was successfully sent (perhaps by discarding old events)
1,592✔
970
}
971

972
void OSExecutorModule::SleepFor( u32 nsecs )
13✔
973
{
974
  if ( !nsecs )
13✔
975
    return;
×
976
  blocked_ = true;
13✔
977
  wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
13✔
978
  sleep_until_clock_ = Core::polclock() + nsecs * Core::POLCLOCKS_PER_SEC;
13✔
979
}
980

981
void OSExecutorModule::SleepForMs( u32 msecs )
15,712,304✔
982
{
983
  if ( !msecs )
15,712,304✔
984
    return;
×
985
  blocked_ = true;
15,712,304✔
986
  wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
15,712,304✔
987
  sleep_until_clock_ = Core::polclock() + msecs * Core::POLCLOCKS_PER_SEC / 1000;
15,712,304✔
988
  if ( !sleep_until_clock_ )
15,712,304✔
989
    sleep_until_clock_ = 1;
2✔
990
}
991

992
void OSExecutorModule::suspend()
426✔
993
{
994
  blocked_ = true;
426✔
995
  wait_type = Core::WAIT_TYPE::WAIT_SLEEP;
426✔
996
  sleep_until_clock_ = 0;  // wait forever
426✔
997
}
426✔
998

999
void OSExecutorModule::revive()
15,714,251✔
1000
{
1001
  blocked_ = false;
15,714,251✔
1002
  if ( in_hold_list_ == Core::HoldListType::TIMEOUT_LIST )
15,714,251✔
1003
  {
1004
    in_hold_list_ = Core::HoldListType::NO_LIST;
15,713,463✔
1005
    Core::scriptScheduler.revive_timeout( &uoexec(), hold_itr_ );
15,713,463✔
1006
  }
1007
  else if ( in_hold_list_ == Core::HoldListType::NOTIMEOUT_LIST )
788✔
1008
  {
1009
    in_hold_list_ = Core::HoldListType::NO_LIST;
786✔
1010
    Core::scriptScheduler.revive_notimeout( &uoexec() );
786✔
1011
  }
1012
  else if ( in_hold_list_ == Core::HoldListType::DEBUGGER_LIST )
2✔
1013
  {
1014
    // stays right where it is.
1015
  }
1016
}
15,714,251✔
1017
bool OSExecutorModule::in_debugger_holdlist() const
107✔
1018
{
1019
  return ( in_hold_list_ == Core::HoldListType::DEBUGGER_LIST );
107✔
1020
}
1021
void OSExecutorModule::revive_debugged()
9✔
1022
{
1023
  in_hold_list_ = Core::HoldListType::NO_LIST;
9✔
1024
  Core::scriptScheduler.revive_debugged( &uoexec() );
9✔
1025
}
9✔
1026

1027
bool OSExecutorModule::critical() const
47,555,253✔
1028
{
1029
  return critical_;
47,555,253✔
1030
}
1031
void OSExecutorModule::critical( bool critical )
×
1032
{
1033
  critical_ = critical;
×
1034
}
×
1035

1036
bool OSExecutorModule::warn_on_runaway() const
24✔
1037
{
1038
  return warn_on_runaway_;
24✔
1039
}
1040
void OSExecutorModule::warn_on_runaway( bool warn_on_runaway )
×
1041
{
1042
  warn_on_runaway_ = warn_on_runaway;
×
1043
}
×
1044

1045
unsigned char OSExecutorModule::priority() const
63,268,647✔
1046
{
1047
  return priority_;
63,268,647✔
1048
}
1049
void OSExecutorModule::priority( unsigned char priority )
14✔
1050
{
1051
  priority_ = priority;
14✔
1052
}
14✔
1053

1054
Core::polclock_t OSExecutorModule::sleep_until_clock() const
188,553,197✔
1055
{
1056
  return sleep_until_clock_;
188,553,197✔
1057
}
1058
void OSExecutorModule::sleep_until_clock( Core::polclock_t sleep_until_clock )
×
1059
{
1060
  sleep_until_clock_ = sleep_until_clock;
×
1061
}
×
1062

1063
Core::TimeoutHandle OSExecutorModule::hold_itr() const
×
1064
{
1065
  return hold_itr_;
×
1066
}
1067
void OSExecutorModule::hold_itr( Core::TimeoutHandle hold_itr )
15,713,469✔
1068
{
1069
  hold_itr_ = hold_itr;
15,713,469✔
1070
}
15,713,469✔
1071

1072
Core::HoldListType OSExecutorModule::in_hold_list() const
15,714,455✔
1073
{
1074
  return in_hold_list_;
15,714,455✔
1075
}
1076
void OSExecutorModule::in_hold_list( Core::HoldListType in_hold_list )
15,714,264✔
1077
{
1078
  in_hold_list_ = in_hold_list;
15,714,264✔
1079
}
15,714,264✔
1080

1081
const int SCRIPTOPT_NO_INTERRUPT = 1;
1082
const int SCRIPTOPT_DEBUG = 2;
1083
const int SCRIPTOPT_NO_RUNAWAY = 3;
1084
const int SCRIPTOPT_CAN_ACCESS_OFFLINE_MOBILES = 4;
1085
const int SCRIPTOPT_AUXSVC_ASSUME_STRING = 5;
1086
const int SCRIPTOPT_SURVIVE_ATTACHED_DISCONNECT = 6;
1087

1088
BObjectImp* OSExecutorModule::mf_Set_Script_Option()
×
1089
{
1090
  int optnum;
1091
  int optval;
1092
  if ( getParam( 0, optnum ) && getParam( 1, optval ) )
×
1093
  {
1094
    int oldval;
1095
    switch ( optnum )
×
1096
    {
1097
    case SCRIPTOPT_NO_INTERRUPT:
×
1098
      oldval = critical_ ? 1 : 0;
×
1099
      critical_ = optval ? true : false;
×
1100
      break;
×
1101
    case SCRIPTOPT_DEBUG:
×
1102
      oldval = exec.getDebugLevel();
×
1103
      if ( optval )
×
1104
        exec.setDebugLevel( Executor::SOURCELINES );
×
1105
      else
1106
        exec.setDebugLevel( Executor::NONE );
×
1107
      break;
×
1108
    case SCRIPTOPT_NO_RUNAWAY:
×
1109
      oldval = warn_on_runaway_ ? 1 : 0;
×
1110
      warn_on_runaway_ = !optval;
×
1111
      break;
×
1112
    case SCRIPTOPT_CAN_ACCESS_OFFLINE_MOBILES:
×
1113
    {
1114
      Core::UOExecutor& uoex = uoexec();
×
1115
      oldval = uoex.can_access_offline_mobiles_ ? 1 : 0;
×
1116
      uoex.can_access_offline_mobiles_ = optval ? true : false;
×
1117
    }
1118
    break;
×
1119
    case SCRIPTOPT_AUXSVC_ASSUME_STRING:
×
1120
    {
1121
      Core::UOExecutor& uoex = uoexec();
×
1122
      oldval = uoex.auxsvc_assume_string ? 1 : 0;
×
1123
      uoex.auxsvc_assume_string = optval ? true : false;
×
1124
    }
1125
    break;
×
1126
    case SCRIPTOPT_SURVIVE_ATTACHED_DISCONNECT:
×
1127
    {
1128
      Core::UOExecutor& uoex = uoexec();
×
1129
      oldval = uoex.survive_attached_disconnect ? 1 : 0;
×
1130
      uoex.survive_attached_disconnect = optval ? true : false;
×
1131
    }
1132
    break;
×
1133
    default:
×
1134
      return new BError( "Unknown Script Option" );
×
1135
    }
1136
    return new BLong( oldval );
×
1137
  }
1138
  else
1139
  {
1140
    return new BError( "Invalid parameter type" );
×
1141
  }
1142
}
1143

1144
BObjectImp* OSExecutorModule::mf_Clear_Event_Queue()  // DAVE
109✔
1145
{
1146
  return ( clear_event_queue() );
109✔
1147
}
1148

1149
namespace
1150
{
1151
struct ScriptDiffData
1152
{
1153
  std::string name;
1154
  u64 instructions;
1155
  u64 pid;
1156
  ScriptDiffData( Core::UOExecutor* ex )
×
1157
      : name( ex->scriptname() ), instructions( ex->instr_cycles ), pid( ex->pid() )
×
1158
  {
1159
  }
×
1160
  ScriptDiffData( Core::UOExecutor* ex, u64 instr ) : ScriptDiffData( ex )
×
1161
  {
1162
    instructions -= instr;
×
1163
    auto uoemod = static_cast<Module::UOExecutorModule*>( ex->findModule( "uo" ) );
×
1164
    if ( uoemod->attached_chr_ != nullptr )
×
1165
      name += " (" + uoemod->attached_chr_->name() + ")";
×
1166
    else if ( uoemod->attached_npc_ != nullptr )
×
1167
      name += " (" + static_cast<Mobile::NPC*>( uoemod->attached_npc_ )->templatename() + ")";
×
1168
    else if ( uoemod->attached_item_.get() )
×
1169
      name += " (" + uoemod->attached_item_->name() + ")";
×
1170
  }
×
1171

1172
  bool operator>( const ScriptDiffData& other ) const { return instructions > other.instructions; }
×
1173
};
1174
struct PerfData
1175
{
1176
  std::unordered_map<u64, ScriptDiffData> data;
1177
  weak_ptr<Core::UOExecutor> uoexec_w;
1178
  size_t max_scripts;
1179
  PerfData( weak_ptr<Core::UOExecutor> weak_ex, size_t max_count )
×
1180
      : data(), uoexec_w( weak_ex ), max_scripts( max_count )
×
1181
  {
1182
  }
×
1183
  static void collect_perf( PerfData* data_ptr )
×
1184
  {
1185
    std::unique_ptr<PerfData> data( data_ptr );
×
1186
    std::vector<ScriptDiffData> res;
×
1187
    if ( !data->uoexec_w.exists() )
×
1188
    {
1189
      DEBUGLOGLN( "PerformanceMeasure Script has been destroyed" );
×
1190
      return;
×
1191
    }
1192
    double sum_instr( 0 );
×
1193
    const auto& runlist = Core::scriptScheduler.getRunlist();
×
1194
    const auto& ranlist = Core::scriptScheduler.getRanlist();
×
1195
    const auto& holdlist = Core::scriptScheduler.getHoldlist();
×
1196
    const auto& notimeoutholdlist = Core::scriptScheduler.getNoTimeoutHoldlist();
×
1197
    auto collect = [&]( Core::UOExecutor* scr )
×
1198
    {
1199
      auto itr = data->data.find( scr->pid() );
×
1200
      if ( itr == data->data.end() )
×
1201
        return;
×
1202
      res.emplace_back( scr, itr->second.instructions );
×
1203
      sum_instr += res.back().instructions;
×
1204
    };
×
1205
    for ( const auto& scr : runlist )
×
1206
      collect( scr );
×
1207
    for ( const auto& scr : ranlist )
×
1208
      collect( scr );
×
1209
    for ( const auto& scr : holdlist )
×
1210
      collect( scr.second );
×
1211
    for ( const auto& scr : notimeoutholdlist )
×
1212
      collect( scr );
×
1213
    std::sort( res.begin(), res.end(), std::greater<ScriptDiffData>() );
×
1214

1215
    std::unique_ptr<ObjArray> arr( new ObjArray );
×
1216
    for ( size_t i = 0; i < res.size() && i < data->max_scripts; ++i )
×
1217
    {
1218
      std::unique_ptr<BStruct> elem( new BStruct );
×
1219
      elem->addMember( "name", new String( res[i].name ) );
×
1220
      elem->addMember( "instructions", new Double( static_cast<double>( res[i].instructions ) ) );
×
1221
      elem->addMember( "pid", new BLong( static_cast<int>( res[i].pid ) ) );
×
1222
      elem->addMember( "percent", new Double( res[i].instructions / sum_instr * 100.0 ) );
×
1223
      arr->addElement( elem.release() );
×
1224
    }
×
1225
    std::unique_ptr<BStruct> result( new BStruct );
×
1226
    result->addMember( "scripts", arr.release() );
×
1227
    result->addMember( "total_number_observed", new BLong( static_cast<int>( res.size() ) ) );
×
1228
    result->addMember( "total_instructions", new Double( sum_instr ) );
×
1229
    data->uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( result.release() ) );
×
1230

1231
    data->uoexec_w.get_weakptr()->revive();
×
1232
  }
×
1233
};
1234

1235
}  // namespace
1236

1237
BObjectImp* OSExecutorModule::mf_PerformanceMeasure()
×
1238
{
1239
  int second_delta, max_scripts;
1240
  if ( !getParam( 0, second_delta ) || !getParam( 1, max_scripts ) )
×
1241
    return new BError( "Invalid parameter type" );
×
1242

1243
  auto& this_uoexec = uoexec();
×
1244

1245
  if ( !this_uoexec.suspend() )
×
1246
  {
1247
    DEBUGLOGLN(
×
1248
        "Script Error in '{}' PC={}: \n"
1249
        "\tThe execution of this script can't be blocked!",
1250
        this_uoexec.scriptname(), this_uoexec.PC );
×
1251
    return new Bscript::BError( "Script can't be blocked" );
×
1252
  }
1253

1254
  const auto& runlist = Core::scriptScheduler.getRunlist();
×
1255
  const auto& ranlist = Core::scriptScheduler.getRanlist();
×
1256
  const auto& holdlist = Core::scriptScheduler.getHoldlist();
×
1257
  const auto& notimeoutholdlist = Core::scriptScheduler.getNoTimeoutHoldlist();
×
1258

1259
  std::unique_ptr<PerfData> perf( new PerfData( this_uoexec.weakptr, max_scripts ) );
×
1260
  for ( const auto& scr : runlist )
×
1261
    perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
×
1262
  for ( const auto& scr : ranlist )
×
1263
    perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
×
1264
  for ( const auto& scr : holdlist )
×
1265
    perf->data.insert( std::make_pair( scr.second->pid(), ScriptDiffData( scr.second ) ) );
×
1266
  for ( const auto& scr : notimeoutholdlist )
×
1267
    perf->data.insert( std::make_pair( scr->pid(), ScriptDiffData( scr ) ) );
×
1268

1269
  new Core::OneShotTaskInst<PerfData*>( nullptr,
×
1270
                                        Core::polclock() + second_delta * Core::POLCLOCKS_PER_SEC,
×
1271
                                        PerfData::collect_perf, perf.release() );
×
1272

1273
  return new BLong( 0 );  // dummy
×
1274
}
×
1275

1276
BObjectImp* OSExecutorModule::mf_LoadExportedScript()
176✔
1277
{
1278
  Core::UOExecutor& this_uoexec = uoexec();
176✔
1279
  if ( this_uoexec.pChild == nullptr )
176✔
1280
  {
1281
    const String* scriptname_str;
1282
    ObjArray* arr;
1283
    if ( !exec.getStringParam( 0, scriptname_str ) || !getObjArrayParam( 1, arr ) )
89✔
1284
      return new BError( "Invalid parameter type" );
×
1285
    Core::ScriptDef sd;
89✔
1286
    if ( !sd.config_nodie( scriptname_str->value(), exec.prog()->pkg, "scripts/" ) )
89✔
1287
      return new BError( "Error in script name" );
×
1288
    if ( !sd.exists() )
89✔
1289
      return new BError( "Script " + sd.name() + " does not exist." );
×
1290
    ref_ptr<Bscript::EScriptProgram> program = find_script2( sd );
89✔
1291
    if ( program.get() == nullptr )
89✔
1292
    {
1293
      ERROR_PRINTLN( "Error reading script {}", sd.name() );
×
1294
      return new Bscript::BError( "Unable to read script" );
×
1295
    }
1296
    Core::UOExecutor* uoexec = Core::create_script_executor();
89✔
1297
    uoexec->keep_alive( true );
89✔
1298
    Core::add_common_exmods( *uoexec );
89✔
1299
    uoexec->addModule( new Module::UOExecutorModule( *uoexec ) );
89✔
1300

1301
    uoexec->setProgram( program.get() );
89✔
1302
    if ( program->haveProgram )
89✔
1303
    {
1304
      for ( int i = (int)( arr->ref_arr.size() ) - 1; i >= 0; --i )
91✔
1305
        uoexec->pushArg( arr->ref_arr[i].get()->impptr() );
2✔
1306
    }
1307
    if ( this_uoexec.critical() )  // execute directy
89✔
1308
    {
1309
      uoexec->exec();
2✔
1310
      BObjectImp* ret;
1311
      if ( uoexec->error() )
2✔
1312
        ret = new BLong( 0 );
×
1313
      else if ( uoexec->ValueStack.empty() )
2✔
1314
        ret = new BLong( 1 );
×
1315
      else
1316
        ret = uoexec->ValueStack.back().get()->impptr()->copy();
2✔
1317
      uoexec->set_running_to_completion( false );
2✔
1318
      uoexec->suspend();
2✔
1319
      Core::scriptScheduler.schedule( uoexec );
2✔
1320

1321
      auto array = std::make_unique<Bscript::ObjArray>();
2✔
1322
      array->addElement( new Core::ExportScriptObjImp( uoexec ) );
2✔
1323
      array->addElement( ret );
2✔
1324

1325
      return array.release();
2✔
1326
    }
2✔
1327
    else
1328
    {
1329
      Core::scriptScheduler.schedule( uoexec );
87✔
1330

1331
      uoexec->pParent = &this_uoexec;
87✔
1332
      this_uoexec.pChild = uoexec;
87✔
1333

1334
      this_uoexec.PC--;
87✔
1335
      // need to fill the valuestack equal to param count (-the return value)
1336
      this_uoexec.ValueStack.push_back( BObjectRef( new BObject( UninitObject::create() ) ) );
87✔
1337
      suspend();
87✔
1338

1339
      return UninitObject::create();
87✔
1340
    }
1341
  }
89✔
1342
  else  // reentry
1343
  {
1344
    BObjectImp* ret;
1345
    if ( this_uoexec.pChild->error() )
87✔
1346
      ret = new BLong( 0 );
×
1347
    else if ( this_uoexec.pChild->ValueStack.empty() )
87✔
1348
      ret = new BLong( 1 );
7✔
1349
    else
1350
    {
1351
      ret = this_uoexec.pChild->ValueStack.back().get()->impptr()->copy();
80✔
1352
      this_uoexec.pChild->ValueStack.pop_back();
80✔
1353
    }
1354
    auto array = std::make_unique<Bscript::ObjArray>();
87✔
1355
    array->addElement( new Core::ExportScriptObjImp( this_uoexec.pChild ) );
87✔
1356
    array->addElement( ret );
87✔
1357

1358
    this_uoexec.pChild->pParent = nullptr;
87✔
1359
    this_uoexec.pChild = nullptr;
87✔
1360

1361
    return array.release();
87✔
1362
  }
87✔
1363
}
1364

1365
BObjectImp* OSExecutorModule::mf_GetEnvironmentVariable()
52✔
1366
{
1367
  const auto& allowed_vars = Plib::systemstate.config.allowed_environmentvariables_access;
52✔
1368
  if ( allowed_vars.empty() )
52✔
1369
    return new BError( "Environment Variable access disallowed due to pol.cfg setting" );
×
1370
  const String* env_name;
1371
  if ( !exec.getStringParam( 0, env_name ) )
52✔
1372
    return new BError( "Invalid parameter type" );
×
1373

1374
  bool all_allowed = allowed_vars.size() == 1 && allowed_vars[0] == "*";
52✔
1375

1376
  if ( env_name->length() == 0 )
52✔
1377
  {
1378
    auto envs = std::make_unique<Bscript::BDictionary>();
1✔
1379
    for ( char** current = environ_vars; *current; ++current )
119✔
1380
    {
1381
      std::string_view env( *current );
118✔
1382
      size_t pos = env.find_first_of( "=" );
118✔
1383
      if ( pos == std::string_view::npos )
118✔
1384
        continue;
×
1385
      auto key = env.substr( 0, pos );
118✔
1386
      auto key_lowered = Clib::strlowerASCII( std::string{ key } );
118✔
1387
      auto val = env.substr( pos + 1 );
118✔
1388

1389
      if ( all_allowed || std::find( allowed_vars.begin(), allowed_vars.end(), key_lowered ) !=
354✔
1390
                              allowed_vars.end() )
354✔
1391
      {
1392
        envs->addMember( new String( key, String::Tainted::YES ),
10✔
1393
                         new String( val, String::Tainted::YES ) );
5✔
1394
      }
1395
    }
118✔
1396
    return envs.release();
1✔
1397
  }
1✔
1398
  else
1399
  {
1400
    if ( !all_allowed )
51✔
1401
    {
1402
      auto name_lowered = Clib::strlowerASCII( env_name->value() );
51✔
1403
      if ( std::find( allowed_vars.begin(), allowed_vars.end(), name_lowered ) ==
51✔
1404
           allowed_vars.end() )
102✔
1405
        return new BError( "Environment Variable access disallowed due to pol.cfg setting" );
2✔
1406
    }
51✔
1407

1408
    const char* env_val = std::getenv( env_name->data() );
49✔
1409
    if ( !env_val )
49✔
1410
      return new BError( "Environment variable not found" );
3✔
1411
    return new String( env_val, String::Tainted::YES );
46✔
1412
  }
1413
}
1414

1415
BObjectImp* OSExecutorModule::mf_SendEmail()
9✔
1416
{
1417
  if ( Core::settingsManager.email_cfg.url.empty() )
9✔
1418
    return new BError( "Email settings are not configured" );
×
1419

1420

1421
  const String *from, *subject, *body, *contentType;
1422
  BObjectImp *recipient, *bcc;
1423

1424
  if ( !( getStringParam( 0, from ) && getParamImp( 1, recipient ) &&
17✔
1425
          getStringParam( 2, subject ) && getStringParam( 3, body ) && getParamImp( 4, bcc ) &&
8✔
1426
          getStringParam( 5, contentType ) ) )
8✔
1427
  {
1428
    return new BError( "Invalid parameter type" );
1✔
1429
  }
1430

1431
  std::shared_ptr<CURL> curl_sp( curl_easy_init(), curl_easy_cleanup );
8✔
1432
  CURL* curl = curl_sp.get();
8✔
1433
  if ( !curl )
8✔
1434
  {
1435
    return new BError( "curl_easy_init() failed" );
×
1436
  }
1437

1438
  auto headers_slist = std::make_shared<CurlStringList>();
8✔
1439
  auto recipients_slist = std::make_shared<CurlStringList>();
8✔
1440
  std::string to_header_value;
8✔
1441

1442
  auto extract_email_address = []( const String* email_string )
18✔
1443
  {
1444
    const auto& input = email_string->value();
18✔
1445
    auto start = input.find( '<' );
18✔
1446
    auto end = input.find( '>' );
18✔
1447

1448
    if ( start != std::string::npos && end != std::string::npos && end > start + 1 )
18✔
1449
    {
1450
      return input.substr( start + 1, end - start - 1 );
3✔
1451
    }
1452

1453
    // No angle brackets: assume the whole input is the email address
1454
    return input;
15✔
1455
  };
1456

1457
  auto handle_recipients = [&]( BObjectImp* string_or_array, bool is_bcc ) -> BObjectImp*
16✔
1458
  {
1459
    // The `to` or `bcc` can be falsey to specify no recipient for this field.
1460
    if ( !string_or_array->isTrue() )
16✔
1461
      return nullptr;
8✔
1462

1463
    if ( auto* recipient_string = impptrIf<String>( string_or_array ) )
8✔
1464
    {
1465
      if ( !is_bcc )
5✔
1466
      {
1467
        if ( !to_header_value.empty() )
4✔
1468
          to_header_value += ", ";
×
1469

1470
        to_header_value += recipient_string->data();
4✔
1471
      }
1472

1473
      recipients_slist->add( extract_email_address( recipient_string ) );
5✔
1474
    }
1475
    else if ( auto* recipient_array = impptrIf<ObjArray>( string_or_array ) )
3✔
1476
    {
1477
      for ( const auto& elem : recipient_array->ref_arr )
8✔
1478
      {
1479
        if ( auto* this_recipient = elem->impptr_if<String>() )
6✔
1480
        {
1481
          // Bcc is not added to the header, and only included in CURLOPT_MAIL_RCPT
1482
          if ( !is_bcc )
5✔
1483
          {
1484
            if ( !to_header_value.empty() )
2✔
1485
              to_header_value += ", ";
1✔
1486

1487
            to_header_value += this_recipient->data();
2✔
1488
          }
1489

1490
          recipients_slist->add( extract_email_address( this_recipient ) );
5✔
1491
        }
1492
        else
1493
        {
1494
          return new BError( "Invalid recipient type in array" );
1✔
1495
        }
1496
      }
1497
    }
1498
    else
1499
    {
1500
      return new BError( "Invalid recipient type" );
×
1501
    }
1502

1503
    return nullptr;
7✔
1504
  };
8✔
1505

1506
  if ( auto res = curl_easy_setopt( curl, CURLOPT_URL, Core::settingsManager.email_cfg.url.data() );
8✔
1507
       res != CURLE_OK )
1508
  {
1509
    return new BError( "curl_easy_setopt(CURLOPT_URL) failed: " +
×
1510
                       std::string( curl_easy_strerror( res ) ) );
×
1511
  }
1512

1513
  if ( !Core::settingsManager.email_cfg.username.empty() )
8✔
1514
  {
1515
    if ( auto res = curl_easy_setopt( curl, CURLOPT_USERNAME,
8✔
1516
                                      Core::settingsManager.email_cfg.username.c_str() );
1517
         res != CURLE_OK )
1518
    {
1519
      return new BError( "curl_easy_setopt(CURLOPT_USERNAME) failed: " +
×
1520
                         std::string( curl_easy_strerror( res ) ) );
×
1521
    }
1522
  }
1523

1524
  if ( !Core::settingsManager.email_cfg.password.empty() )
8✔
1525
  {
1526
    if ( auto res = curl_easy_setopt( curl, CURLOPT_PASSWORD,
8✔
1527
                                      Core::settingsManager.email_cfg.password.c_str() );
1528
         res != CURLE_OK )
1529
    {
1530
      return new BError( "curl_easy_setopt(CURLOPT_PASSWORD) failed: " +
×
1531
                         std::string( curl_easy_strerror( res ) ) );
×
1532
    }
1533
  }
1534

1535
  if ( Core::settingsManager.email_cfg.use_tls )
8✔
1536
  {
1537
    if ( auto res = curl_easy_setopt( curl, CURLOPT_USE_SSL, CURLUSESSL_ALL ); res != CURLE_OK )
8✔
1538
    {
1539
      return new BError( "curl_easy_setopt(CURLOPT_USE_SSL) failed: " +
×
1540
                         std::string( curl_easy_strerror( res ) ) );
×
1541
    }
1542
  }
1543

1544
  if ( !Core::settingsManager.email_cfg.ca_file.empty() )
8✔
1545
  {
1546
    if ( auto res = curl_easy_setopt( curl, CURLOPT_CAINFO,
8✔
1547
                                      Core::settingsManager.email_cfg.ca_file.c_str() );
1548
         res != CURLE_OK )
1549
    {
1550
      return new BError( "curl_easy_setopt(CURLOPT_CAINFO) failed: " +
×
1551
                         std::string( curl_easy_strerror( res ) ) );
×
1552
    }
1553
  }
1554

1555
  if ( auto res =
8✔
1556
           curl_easy_setopt( curl, CURLOPT_MAIL_FROM, extract_email_address( from ).c_str() );
8✔
1557
       res != CURLE_OK )
1558
  {
1559
    return new BError( "curl_easy_setopt(CURLOPT_MAIL_FROM) failed: " +
×
1560
                       std::string( curl_easy_strerror( res ) ) );
×
1561
  }
1562

1563
  if ( auto* recipient_to_error = handle_recipients( recipient, false ) )  // Handle To recipients
8✔
1564
  {
1565
    return recipient_to_error;
×
1566
  }
1567

1568
  if ( auto* recipient_bcc_error = handle_recipients( bcc, true ) )  // Handle Bcc recipients
8✔
1569
  {
1570
    return recipient_bcc_error;
1✔
1571
  }
1572

1573
  if ( auto res = curl_easy_setopt( curl, CURLOPT_MAIL_RCPT, recipients_slist->get() );
7✔
1574
       res != CURLE_OK )
1575
  {
1576
    return new BError( "curl_easy_setopt(CURLOPT_MAIL_RCPT) failed: " +
×
1577
                       std::string( curl_easy_strerror( res ) ) );
×
1578
  }
1579

1580
  std::string date_header_value = fmt::format(
1581
      "{:%a, %d %b %Y %H:%M:%S %z}",
1582
      std::chrono::time_point_cast<std::chrono::seconds>( std::chrono::system_clock::now() ) );
7✔
1583

1584
  std::vector<std::string> email_headers{ "Date: " + date_header_value,  //
1585
                                          "To: " + to_header_value,      //
1586
                                          "From: " + from->value(),      //
7✔
1587
                                          "Subject: " + subject->value() };
35✔
1588

1589
  for ( const auto& header : email_headers )
35✔
1590
    headers_slist->add( header );
28✔
1591

1592
  if ( auto res = curl_easy_setopt( curl, CURLOPT_HTTPHEADER, headers_slist->get() );
7✔
1593
       res != CURLE_OK )
1594
  {
1595
    return new BError( "curl_easy_setopt(CURLOPT_HTTPHEADER) failed: " +
×
1596
                       std::string( curl_easy_strerror( res ) ) );
×
1597
  }
1598

1599
  std::shared_ptr<curl_mime> mime( curl_mime_init( curl ), curl_mime_free );
7✔
1600

1601
  curl_mimepart* part = curl_mime_addpart( mime.get() );
7✔
1602

1603
  if ( !part )
7✔
1604
  {
1605
    return new BError( "curl_mime_addpart() failed" );
×
1606
  }
1607

1608
  // `curl_mime_data` copies the string into its internal structure, so using this local is
1609
  // okay. Ref: https://github.com/curl/curl/blob/curl-8_2_1/lib/mime.c#L1380-L1390
1610
  curl_mime_data( part, body->data(), CURL_ZERO_TERMINATED );
7✔
1611

1612
  // `curl_mime_type` copies the string into its internal structure, so using this local is
1613
  // okay. Ref: https://github.com/curl/curl/blob/curl-8_2_1/lib/mime.c#L1461
1614
  curl_mime_type( part, contentType->data() );
7✔
1615

1616
  if ( auto res = curl_easy_setopt( curl, CURLOPT_MIMEPOST, mime.get() ); res != CURLE_OK )
7✔
1617
  {
1618
    return new BError( "curl_easy_setopt(CURLOPT_MIMEPOST) failed: " +
×
1619
                       std::string( curl_easy_strerror( res ) ) );
×
1620
  }
1621

1622
  Core::UOExecutor& this_uoexec = uoexec();
7✔
1623

1624
  if ( !this_uoexec.suspend() )
7✔
1625
  {
1626
    DEBUGLOGLN(
×
1627
        "Script Error in '{}' PC={}: \n"
1628
        "\tThe execution of this script can't be blocked!",
1629
        this_uoexec.scriptname(), this_uoexec.PC );
×
1630
    return new Bscript::BError( "Script can't be blocked" );
×
1631
  }
1632

1633
  weak_ptr<Core::UOExecutor> uoexec_w = this_uoexec.weakptr;
7✔
1634

1635
  Core::networkManager.auxthreadpool->push(
14✔
1636
      [uoexec_w, curl_sp, recipients_slist, headers_slist, mime]()
14✔
1637
      {
1638
        CURL* curl = curl_sp.get();
7✔
1639

1640
        auto res = curl_easy_perform( curl );
7✔
1641

1642
        {
1643
          Core::PolLock lck;
7✔
1644

1645
          if ( !uoexec_w.exists() )
7✔
1646
          {
1647
            DEBUGLOGLN( "SendEmail Script has been destroyed" );
×
1648
            return;
×
1649
          }
1650
          if ( res != CURLE_OK )
7✔
1651
            uoexec_w.get_weakptr()->ValueStack.back().set(
2✔
1652
                new BObject( new BError( curl_easy_strerror( res ) ) ) );
1✔
1653
          else
1654
            uoexec_w.get_weakptr()->ValueStack.back().set( new BObject( new BLong( 1 ) ) );
6✔
1655

1656
          uoexec_w.get_weakptr()->revive();
7✔
1657
        }
7✔
1658
      }
1659

1660
      /* always cleanup */
1661
      // curl_easy_cleanup(), curl_slist_free_all(), curl_mime_free() are
1662
      // performed when the shared pointer deallocates
1663
  );
1664

1665
  return new BLong( 0 );
7✔
1666
}
15✔
1667

1668

1669
size_t OSExecutorModule::sizeEstimate() const
14✔
1670
{
1671
  size_t size = sizeof( *this );
14✔
1672
  for ( const auto& obj : events_ )
14✔
1673
  {
1674
    if ( obj != nullptr )
×
1675
      size += obj->sizeEstimate();
×
1676
  }
1677
  size += Clib::memsize( events_ );
14✔
1678
  return size;
14✔
1679
}
1680
}  // namespace Module
1681
}  // namespace Pol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc