Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Frameworks/Service/Main.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <algorithm>
7#include <cstdlib>
8#include <fstream>
9#include <sstream>
10
11#if qStroika_Foundation_Common_Platform_POSIX
12#include <fcntl.h>
13#include <sys/stat.h>
14#include <sys/types.h>
15#include <unistd.h>
16#endif
17
18#if qStroika_Foundation_Common_Platform_Windows
19#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
20#endif
21
23#include "Stroika/Foundation/Characters/SDKString.h"
25#include "Stroika/Foundation/Containers/Common.h"
28#include "Stroika/Foundation/Execution/CommandLine.h"
29#include "Stroika/Foundation/Execution/Exceptions.h"
31#include "Stroika/Foundation/Execution/Module.h"
32#include "Stroika/Foundation/Execution/OperationNotSupportedException.h"
33#include "Stroika/Foundation/Execution/Process.h"
34#include "Stroika/Foundation/Execution/ProcessRunner.h"
35#include "Stroika/Foundation/Execution/SignalHandlers.h"
38#include "Stroika/Foundation/Execution/Throw.h"
39#include "Stroika/Foundation/Execution/TimeOutException.h"
41#include "Stroika/Foundation/Streams/iostream/FStreamSupport.h"
42
43#include "Main.h"
44
45using namespace Stroika::Foundation;
48using namespace Stroika::Foundation::Execution;
49using namespace Stroika::Foundation::Memory;
50
51using namespace Stroika::Frameworks;
52using namespace Stroika::Frameworks::Service;
53
54// Comment this in to turn on aggressive noisy DbgTrace in this module
55//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
56
57namespace {
58 // safe to declare here because we cannot start the threads before main...
59 const String kServiceRunThreadName_ = "Service 'Run' thread"sv;
60}
61
62/*
63 ********************************************************************************
64 *********************** Service::Main::CommandArgs *****************************
65 ********************************************************************************
66 */
67Main::CommandArgs::CommandArgs (const CommandLine& cmdLine)
68{
69 static const Mapping<CommandLine::Option, MajorOperation> kMap2Major_{
70 {CommandOptions::kInstall, MajorOperation::eInstall},
71 {CommandOptions::kUnInstall, MajorOperation::eUnInstall},
72 {CommandOptions::kRunAsService, MajorOperation::eRunServiceMain},
73 {CommandOptions::kRunDirectly, MajorOperation::eRunDirectly},
74 {CommandOptions::kStart, MajorOperation::eStart},
75 {CommandOptions::kStop, MajorOperation::eStop},
76 {CommandOptions::kForcedStop, MajorOperation::eForcedStop},
77 {CommandOptions::kRestart, MajorOperation::eRestart},
78 {CommandOptions::kForcedRestart, MajorOperation::eForcedRestart},
79 {CommandOptions::kReloadConfiguration, MajorOperation::eReloadConfiguration},
80 {CommandOptions::kPause, MajorOperation::ePause},
81 {CommandOptions::kContinue, MajorOperation::eContinue},
82 };
83
84 Iterable<CommandLine::Option> allMajorsSet = kMap2Major_.Keys ().Where ([&] (CommandLine::Option o) { return cmdLine.Has (o); });
85 switch (allMajorsSet.size ()) {
86 case 0:
87 break; // current code doesn't throw here but maybe should?
88 case 1:
89 fMajorOperation = kMap2Major_[Memory::ValueOf (allMajorsSet.First ())];
90 break;
91 default:
92 Throw (InvalidCommandLineArgument{"Only one major command-line option can be specified at a time"sv});
93 }
94 if (auto o = cmdLine.GetArgument (CommandOptions::kRunFor)) {
95 fRunFor = Time::Duration{FloatConversion::ToFloat<Time::DurationSeconds::rep> (*o)};
96 }
97}
98
99/*
100 ********************************************************************************
101 ******************* Service::Main::IApplicationRep *****************************
102 ********************************************************************************
103 */
105{
106 return false;
107}
108
109void Main::IApplicationRep::OnReReadConfigurationRequest ()
110{
111 // fMustReReadConfig = true;
112}
113
114String Main::IApplicationRep::GetServiceStatusMessage () const
115{
116 return String{};
117}
118
119/*
120 ********************************************************************************
121 ********************* Main::IServiceIntegrationRep *****************************
122 ********************************************************************************
123 */
125{
126 return _GetAttachedAppRep ()->HandleCommandLineArgument (s);
127}
128
129/*
130 ********************************************************************************
131 ********************************* Service::Main ********************************
132 ********************************************************************************
133 */
134shared_ptr<Main::IServiceIntegrationRep> Main::mkDefaultServiceIntegrationRep ()
135{
136#if qStroika_Foundation_Common_Platform_POSIX
137 return make_shared<BasicUNIXServiceImpl> ();
138#elif qStroika_Foundation_Common_Platform_Windows
139 return make_shared<WindowsService> ();
140#else
141 return make_shared<RunNoFrillsService> ();
142#endif
143}
144
145Main::Main (const shared_ptr<IApplicationRep>& rep, const shared_ptr<IServiceIntegrationRep>& serviceIntegrationRep)
146 : fServiceRep_{serviceIntegrationRep}
147{
148 RequireNotNull (rep);
149 RequireNotNull (serviceIntegrationRep);
150 serviceIntegrationRep->_Attach (rep);
151}
152
153Main::~Main ()
154{
155 fServiceRep_->_Attach (nullptr);
156}
157
159{
160 for (const String& i : args.fUnusedArguments) {
161 fServiceRep_->HandleCommandLineArgument (i);
162 }
163 if (not args.fMajorOperation.has_value ()) {
164 Throw (InvalidCommandLineArgument{"No recognized operation"sv});
165 }
166 switch (*args.fMajorOperation) {
167 case CommandArgs::MajorOperation::eInstall: {
168 if (out != nullptr) {
169 out.Write ("Installing..."sv);
170 }
171 Install ();
172 if (out != nullptr) {
173 out.Write ("done\n"sv);
174 }
175 } break;
176 case CommandArgs::MajorOperation::eUnInstall: {
177 if (out != nullptr) {
178 out.Write ("UnInstalling..."sv);
179 }
180 UnInstall ();
181 if (out != nullptr) {
182 out.Write ("done\n"sv);
183 }
184 } break;
185 case CommandArgs::MajorOperation::eRunServiceMain: {
186 RunAsService ();
187 } break;
188 case CommandArgs::MajorOperation::eRunDirectly: {
189 RunDirectly (args.fRunFor);
190 } break;
191 case CommandArgs::MajorOperation::eStart: {
192 if (out != nullptr) {
193 out.Write ("Starting..."sv);
194 }
195 constexpr Time::DurationSeconds kTimeOut_{30.0s}; // a vaguely reasonable default - apps can override by handling before calling Run
196 Start (kTimeOut_);
197 if (out != nullptr) {
198 out.Write ("done\n"sv);
199 }
200 } break;
201 case CommandArgs::MajorOperation::eStop: {
202 if (out != nullptr) {
203 out.Write ("Stopping..."sv);
204 }
205 constexpr Time::DurationSeconds kTimeOut_{30.0s}; // a vaguely reasonable default - apps can override by handling before calling Run
206 Stop (kTimeOut_);
207 if (out != nullptr) {
208 out.Write ("done\n"sv);
209 }
210 } break;
211 case CommandArgs::MajorOperation::eForcedStop: {
213 //ForcedStop ();
214 } break;
215 case CommandArgs::MajorOperation::eRestart: {
216 if (out != nullptr) {
217 out.Write ("Restarting..."sv);
218 }
219 constexpr Time::DurationSeconds kTimeOut_{30.0}; // a vaguely reasonable default - apps can override by handling before calling Run
220 Restart (kTimeOut_);
221 if (out != nullptr) {
222 out.Write ("done\n"sv);
223 }
224 } break;
225 case CommandArgs::MajorOperation::eForcedRestart: {
226 ForcedRestart ();
227 } break;
228 case CommandArgs::MajorOperation::eReloadConfiguration: {
229 ReReadConfiguration ();
230 } break;
231 case CommandArgs::MajorOperation::ePause: {
232 Pause ();
233 } break;
234 case CommandArgs::MajorOperation::eContinue: {
235 Continue ();
236 } break;
237 default: {
239 } break;
240 }
241}
242
244{
245 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::GetServiceStatusMessage"};
246 const wchar_t kTAB[] = L" "; // use spaces instead of tab so formatting independent of tabstop settings
247 ServiceDescription svd = GetServiceDescription ();
248 wstringstream tmp;
249 tmp << L"Service '" << svd.fPrettyName.As<wstring> () << "'" << endl;
250 switch (GetState ()) {
251 case State::eStopped:
252 tmp << kTAB << L"State: " << kTAB << kTAB << kTAB << kTAB << "STOPPED" << endl;
253 break;
254 case State::eRunning:
255 tmp << kTAB << L"State: " << kTAB << kTAB << kTAB << kTAB << "Running" << endl;
256 if (GetServiceIntegrationFeatures ().Contains (ServiceIntegrationFeatures::eGetServicePID)) {
257 tmp << kTAB << L"PID: " << kTAB << kTAB << kTAB << kTAB << GetServicePID () << endl;
258 }
259 break;
260 case State::ePaused:
261 tmp << kTAB << L"State: " << kTAB << kTAB << kTAB << kTAB << "PAUSED" << endl;
262 if (GetServiceIntegrationFeatures ().Contains (ServiceIntegrationFeatures::eGetServicePID)) {
263 tmp << kTAB << L"PID: " << kTAB << kTAB << kTAB << kTAB << GetServicePID () << endl;
264 }
265 break;
266 default:
268 }
269 DbgTrace ("returning status: ({})"_f, tmp.str ());
270 return tmp.str ();
271}
272
274{
275 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::RunAsService"};
276 GetServiceRep_ ()._RunAsService ();
277}
278
279void Main::RunDirectly (const optional<Time::Duration>& runFor)
280{
281 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::RunDirectly"};
282 GetServiceRep_ ()._RunDirectly (runFor);
283}
284
285void Main::ForcedRestart ([[maybe_unused]] Time::DurationSeconds timeout, [[maybe_unused]] Time::DurationSeconds unforcedStopTimeout)
286{
288}
289
290void Main::ReReadConfiguration ()
291{
292 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::ReReadConfiguration"};
293#if qStroika_Foundation_Common_Platform_Windows
295#elif qStroika_Foundation_Common_Platform_POSIX
296 [[maybe_unused]] pid_t pid = GetServicePID ();
297 Assert (pid != 0); // maybe throw if non-zero???
298 ThrowPOSIXErrNoIfNegative (::kill (GetServicePID (), Main::BasicUNIXServiceImpl::kSIG_ReReadConfiguration));
299#else
301#endif
302}
303
304void Main::Pause ()
305{
307}
308
309void Main::Continue ()
310{
312}
313
314Main::ServiceDescription Main::GetServiceDescription () const
315{
316 return GetAppRep_ ().GetServiceDescription ();
317}
318
319void Main::Restart (Time::DurationSeconds timeout)
320{
321 Debug::TraceContextBumper ctx{"Stroika::Frameworks::Service::Main::Restart", "timeout = {}"_f, timeout};
322
323 /////// @TODO FIX - WRONG HANDLING OF TIMEOUT
324 Stop (timeout);
325 Start (timeout);
326#if 0
327 Time::DurationSeconds endAt = Time::GetTickCount () + timeout;
328 IgnoreExceptionsForCall (Stop (timeout));
329#if qStroika_Foundation_Common_Platform_POSIX
330 // REALY should WAIT for server to stop and only do this it fails -
331 unlink (_sAppRep->_GetPIDFileName ().AsSDKString ().c_str ());
332#endif
333 Start (endAt - Time::GetTickCount ());
334#endif
335}
336
337/*
338 ********************************************************************************
339 **************** Service::Main::LoggerServiceWrapper ***************************
340 ********************************************************************************
341 */
342Main::LoggerServiceWrapper::LoggerServiceWrapper (const shared_ptr<Main::IServiceIntegrationRep>& delegateTo)
343 : fDelegateTo_{delegateTo}
344{
345 RequireNotNull (delegateTo);
346}
347
348void Main::LoggerServiceWrapper::_Attach (const shared_ptr<IApplicationRep>& appRep)
349{
350 fDelegateTo_->_Attach (appRep);
351}
352
353shared_ptr<Main::IApplicationRep> Main::LoggerServiceWrapper::_GetAttachedAppRep () const
354{
355 return fDelegateTo_->_GetAttachedAppRep ();
356}
357
358Set<Main::ServiceIntegrationFeatures> Main::LoggerServiceWrapper::_GetSupportedFeatures () const
359{
360 return fDelegateTo_->_GetSupportedFeatures ();
361}
362
363Main::State Main::LoggerServiceWrapper::_GetState () const
364{
365 return fDelegateTo_->_GetState ();
366}
367
369{
370 Logger::sThe.Log (Logger::eNotice, "Installing service..."_f);
371 try {
372 fDelegateTo_->_Install ();
373 }
374 catch (...) {
375 Logger::sThe.Log (Logger::eError, "Failed to install - {} - aborting..."_f, current_exception ());
376 ReThrow ();
377 }
378}
379
381{
382 Logger::sThe.Log (Logger::eNotice, "UnInstalling service..."_f);
383 try {
384 fDelegateTo_->_UnInstall ();
385 }
386 catch (...) {
387 Logger::sThe.Log (Logger::eError, "Failed to uninstall - {} - aborting..."_f, current_exception ());
388 ReThrow ();
389 }
390}
391
392void Main::LoggerServiceWrapper::_RunAsService ()
393{
394 Logger::sThe.Log (Logger::eInfo, "Service starting..."_f); // only info level cuz inside app RunAs
395 try {
396 fDelegateTo_->_RunAsService ();
397 }
398 catch (...) {
399 Logger::sThe.Log (Logger::eError, L"Exception running service - {} - aborting..."_f, current_exception ());
400 ReThrow ();
401 }
402 Logger::sThe.Log (Logger::eNotice, "Service stopped normally"_f);
403}
404
405void Main::LoggerServiceWrapper::_RunDirectly (const optional<Time::Duration>& runFor)
406{
407 Logger::sThe.Log (Logger::eNotice, "Service starting in Run-Direct (non service) mode."_f);
408 try {
409 fDelegateTo_->_RunDirectly (runFor);
410 }
411 catch (...) {
412 Logger::sThe.Log (Logger::eError, "Exception running service in direct mode - {} - aborting..."_f, current_exception ());
413 ReThrow ();
414 }
415 Logger::sThe.Log (Logger::eNotice, "Service stopped normally"_f);
416}
417
418void Main::LoggerServiceWrapper::_Start (Time::DurationSeconds timeout)
419{
420 fDelegateTo_->_Start (timeout);
421}
422
423void Main::LoggerServiceWrapper::_Stop (Time::DurationSeconds timeout)
424{
425 fDelegateTo_->_Stop (timeout);
426}
427
428void Main::LoggerServiceWrapper::_ForcedStop (Time::DurationSeconds timeout)
429{
430 fDelegateTo_->_ForcedStop (timeout);
431}
432
433pid_t Main::LoggerServiceWrapper::_GetServicePID () const
434{
435 return fDelegateTo_->_GetServicePID ();
436}
437
438#if qStroika_Foundation_Common_Platform_POSIX
439/*
440 ********************************************************************************
441 ******************* Service::Main::BasicUNIXServiceImpl ************************
442 ********************************************************************************
443 */
444Main::BasicUNIXServiceImpl::BasicUNIXServiceImpl ()
445 : fOurSignalHandler_{[this] (SignalID signum) { SignalHandler_ (signum); }}
446{
447#if USE_NOISY_TRACE_IN_THIS_MODULE_
448 DbgTrace ("Main::BasicUNIXServiceImpl::BasicUNIXServiceImpl: this={}"_f, static_cast<const void*> (this));
449#endif
450}
451
452Main::BasicUNIXServiceImpl::~BasicUNIXServiceImpl ()
453{
454#if USE_NOISY_TRACE_IN_THIS_MODULE_
455 DbgTrace ("Main::BasicUNIXServiceImpl::~BasicUNIXServiceImpl: this={}"_f, static_cast<const void*> (this));
456#endif
457 Require (fAppRep_.load () == nullptr);
458}
459
460void Main::BasicUNIXServiceImpl::_Attach (const shared_ptr<IApplicationRep>& appRep)
461{
462#if USE_NOISY_TRACE_IN_THIS_MODULE_
463 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::BasicUNIXServiceImpl::_Attach"};
464#endif
465 Thread::SuppressInterruptionInContext suppressInterruption; // this must run to completion - it only blocks waiting for subsidiary thread to finish
466 Require ((appRep == nullptr and fAppRep_.load () != nullptr) or (fAppRep_.load () == nullptr and fAppRep_.load () != appRep));
467 Thread::Ptr p = fRunThread_.load ();
468 if (p != nullptr) {
470 fRunThread_.store (nullptr);
471 }
472 fAppRep_ = appRep;
473}
474
475shared_ptr<Main::IApplicationRep> Main::BasicUNIXServiceImpl::_GetAttachedAppRep () const
476{
477 return fAppRep_;
478}
479
480Set<Main::ServiceIntegrationFeatures> Main::BasicUNIXServiceImpl::_GetSupportedFeatures () const
481{
483 result.Add (Main::ServiceIntegrationFeatures::eGetServicePID);
484 return result;
485}
486
487Main::State Main::BasicUNIXServiceImpl::_GetState () const
488{
489#if USE_NOISY_TRACE_IN_THIS_MODULE_
490 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::_GetState"};
491#endif
492 // @todo - maybe not qutie right - but a good approx ... review...
493 if (_GetServicePID () > 0) {
494#if USE_NOISY_TRACE_IN_THIS_MODULE_
495 DbgTrace ("State::eRunning");
496#endif
497 return State::eRunning;
498 }
499#if USE_NOISY_TRACE_IN_THIS_MODULE_
500 DbgTrace ("State::eStopped");
501#endif
502 return State::eStopped;
503}
504
505void Main::BasicUNIXServiceImpl::_Install ()
506{
508}
509
510void Main::BasicUNIXServiceImpl::_UnInstall ()
511{
512 Throw (OperationNotSupportedException{"UnInstall"sv});
513}
514
515void Main::BasicUNIXServiceImpl::_RunAsService ()
516{
517 Debug::TraceContextBumper ctx{"Stroika::Frameworks::Service::Main::BasicUNIXServiceImpl::_RunAsService"};
518 if (_GetServicePID () > 0) {
519 Throw (Exception{"Service Already Running"sv});
520 }
521
522 shared_ptr<IApplicationRep> appRep = fAppRep_;
523 RequireNotNull (appRep);
524
525 [[maybe_unused]] auto&& cleanupSigHanders = Execution::Finally ([this] () noexcept {
526 Thread::SuppressInterruptionInContext suppressThreadInterupts;
527 SetupSignalHanlders_ (false);
528 });
529 SetupSignalHanlders_ (true);
530
531 RequireNotNull (appRep); // must call Attach_ first
532 fRunThread_.store (Thread::New ([appRep] () { appRep->MainLoop ([] () {}); }, Thread::eAutoStart, kServiceRunThreadName_));
533 [[maybe_unused]] auto&& cleanup = Finally ([this] () noexcept {
534 Thread::SuppressInterruptionInContext suppressThreadInterupts;
535 (void)::unlink (_GetPIDFileName ().c_str ());
536 });
537 {
538 ofstream out;
539 Streams::iostream::OpenOutputFileStream (&out, _GetPIDFileName ());
540 out << GetCurrentProcessID () << endl;
541 }
542 if (_GetServicePID () <= 0) {
543 Throw (Exception{"Unable to create process ID tracking file {}"_f(_GetPIDFileName ())});
544 }
545 if (_GetServicePID () != GetCurrentProcessID ()) {
546 Throw (Exception{"Unable to create process ID tracking file {} (race?)"_f(_GetPIDFileName ())});
547 }
548 fRunThread_.load ().Join ();
549}
550
551void Main::BasicUNIXServiceImpl::_RunDirectly (const optional<Time::Duration>& runFor)
552{
553 shared_ptr<IApplicationRep> appRep = fAppRep_;
554 RequireNotNull (appRep); // must call Attach_ first
555 fRunThread_.store (Thread::New ([appRep] () { appRep->MainLoop ([] () {}); }, Thread::eAutoStart, kServiceRunThreadName_));
556 Thread::Ptr t = fRunThread_.load ();
557 Thread::CleanupPtr stopper{Thread::CleanupPtr::eAbortBeforeWaiting}; // another thread to stop the mainloop after runFor
558 if (runFor) {
559 stopper = Thread::New (
560 [t, &runFor] () {
561 Sleep (*runFor);
562 t.Abort ();
563 },
564 Thread::eAutoStart);
565 }
566 t.Join ();
567}
568
569void Main::BasicUNIXServiceImpl::_Start (Time::DurationSeconds timeout)
570{
571 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::Start", "timeout = {}"_f, timeout};
572
573 Time::TimePointSeconds timeoutAt = Time::GetTickCount () + timeout;
574
575 // REALLY should use GETSTATE - and return state based on if PID file exists...
576 if (_GetServicePID () > 0) {
577 Throw (Exception{"Cannot Start service because its already running"sv});
578 }
579
580#if 1
581 filesystem::path exePath = GetEXEPath ();
582 ProcessRunner{exePath, Sequence<String>{{String{exePath}, "--"sv + String{CommandNames::kRunAsService}}}, ProcessRunner::Options{.fDetached = true}}
583 .RunInBackground ();
584#else
585 (void)Execution::DetachedProcessRunner (Execution::GetEXEPath (), Sequence<String>{{String{}, ("--"sv + String{CommandNames::kRunAsService})}});
586#endif
587
588 while (_GetServicePID () <= 0) {
589 Sleep (500ms);
590 ThrowTimeoutExceptionAfter (timeoutAt);
591 }
592}
593
594void Main::BasicUNIXServiceImpl::_Stop (Time::DurationSeconds timeout)
595{
596#if USE_NOISY_TRACE_IN_THIS_MODULE_
597 Debug::TraceContextBumper traceCtx{L"Stroika::Frameworks::Service::Main::BasicUNIXServiceImpl::_Stop", L"timeout=%e", timeout};
598#endif
599 bool kInProc_ = false;
600 if (kInProc_) {
601 /// kill running....
602 }
603 else {
604 Time::TimePointSeconds timeoutAt = Time::GetTickCount () + timeout;
605 // Send signal to server to stop
606 if (_GetServicePID () > 0) {
607#if USE_NOISY_TRACE_IN_THIS_MODULE_
608 DbgTrace ("Service running - so sending SIGTERM signal");
609#endif
610 ThrowPOSIXErrNoIfNegative (::kill (_GetServicePID (), SIGTERM));
611
612 Time::DurationSeconds waitFor = 0.001s; // wait just a little at first but then progressively longer (avoid busy wait)
613 while (_GetServicePID () > 0) {
614 Sleep (waitFor);
615 if (waitFor < timeout and waitFor < 5s) {
616 waitFor *= 2;
617 }
618#if USE_NOISY_TRACE_IN_THIS_MODULE_
619 DbgTrace ("still waiting for timeout/completion");
620#endif
621 ThrowTimeoutExceptionAfter (timeoutAt);
622 }
623 }
624 // in case not cleanly stopped before
625 // @todo RETHINK - not really necessary and a possible race (if lots of starts/stops done)
626 (void)::unlink (_GetPIDFileName ().c_str ());
627 }
628}
629
630void Main::BasicUNIXServiceImpl::_ForcedStop (Time::DurationSeconds timeout)
631{
632 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::BasicUNIXServiceImpl::_ForcedStop"};
633 // Send signal to server to stop
634 pid_t svcPID = _GetServicePID ();
635 if (svcPID > 0) {
636 ThrowPOSIXErrNoIfNegative (::kill (_GetServicePID (), SIGKILL));
637 }
638 // REALY should WAIT for server to stop and only do this it fails -
639 (void)::unlink (_GetPIDFileName ().c_str ());
640}
641
642pid_t Main::BasicUNIXServiceImpl::_GetServicePID () const
643{
644 ifstream in (_GetPIDFileName ().c_str ());
645 if (in) {
646 pid_t n = 0;
647 in >> n;
648 if (IsProcessRunning (n)) {
649 return n;
650 }
651 }
652 return 0;
653}
654
655void Main::BasicUNIXServiceImpl::SetupSignalHanlders_ (bool install)
656{
657 if (install) {
658 SignalHandlerRegistry::Get ().AddSignalHandler (SIGINT, fOurSignalHandler_);
659 SignalHandlerRegistry::Get ().AddSignalHandler (SIGTERM, fOurSignalHandler_);
660 SignalHandlerRegistry::Get ().AddSignalHandler (kSIG_ReReadConfiguration, fOurSignalHandler_);
661 }
662 else {
663 SignalHandlerRegistry::Get ().RemoveSignalHandler (SIGINT, fOurSignalHandler_);
664 SignalHandlerRegistry::Get ().RemoveSignalHandler (SIGTERM, fOurSignalHandler_);
665 SignalHandlerRegistry::Get ().RemoveSignalHandler (kSIG_ReReadConfiguration, fOurSignalHandler_);
666 }
667}
668
669filesystem::path Main::BasicUNIXServiceImpl::_GetPIDFileName () const
670{
672 (fAppRep_.load ()->GetServiceDescription ().fRegistrationName + ".pid"sv).As<filesystem::path> ();
673}
674
675void Main::BasicUNIXServiceImpl::_CleanupDeadService ()
676{
677 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::_CleanupDeadService"};
678 // REALY should WAIT for server to stop and only do this it fails -
679 (void)::unlink (_GetPIDFileName ().c_str ());
680}
681
682void Main::BasicUNIXServiceImpl::SignalHandler_ (SignalID signum)
683{
684 // NOTE - this is only safe due to the use of SignalHandlerRegistry::SafeSignalsManager
686 "Stroika::Frameworks::Service::Main::BasicUNIXServiceImpl::SignalHandler_", "signal = {}"_f, SignalToName (signum))};
687 // VERY PRIMITIVE IMPL FOR NOW -- LGP 2011-09-24
688 switch (signum) {
689 case SIGINT:
690 case SIGTERM: {
691 DbgTrace ("Calling sigHandlerThread2Abort (thread: {}).Abort ()"_f, fRunThread_.load ().ToString ());
692 fRunThread_.load ().Abort ();
693 } break;
694 case kSIG_ReReadConfiguration: {
695 DbgTrace ("Invoking fAppRep->OnReReadConfigurationRequest ()"_f);
696 fAppRep_.load ()->OnReReadConfigurationRequest ();
697 } break;
698 }
699}
700#endif
701
702#if qStroika_Foundation_Common_Platform_Windows
703/*
704 ********************************************************************************
705 ************************* Service::Main::WindowsService ************************
706 ********************************************************************************
707 */
708Main::WindowsService* Main::WindowsService::s_SvcRunningTHIS_ = nullptr;
709
710Main::WindowsService::WindowsService ()
711 : fServiceStatus_{}
712{
713 fServiceStatus_.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
714 fServiceStatus_.dwCurrentState = SERVICE_STOPPED;
715 fServiceStatus_.dwControlsAccepted = SERVICE_ACCEPT_STOP;
716}
717
718void Main::WindowsService::_Attach (const shared_ptr<IApplicationRep>& appRep)
719{
720 Require ((appRep == nullptr and fAppRep_ != nullptr) or (fAppRep_ == nullptr and fAppRep_ != appRep));
721 fAppRep_ = appRep;
722}
723
724shared_ptr<Main::IApplicationRep> Main::WindowsService::_GetAttachedAppRep () const
725{
726 return fAppRep_;
727}
728
729Set<Main::ServiceIntegrationFeatures> Main::WindowsService::_GetSupportedFeatures () const
730{
732 result.Add (Main::ServiceIntegrationFeatures::eGetServicePID);
733 result.Add (Main::ServiceIntegrationFeatures::eInstall);
734 return result;
735}
736
737Main::State Main::WindowsService::_GetState () const
738{
739 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::_GetState"};
740 const DWORD kServiceMgrAccessPrivs = SERVICE_QUERY_STATUS;
741 SC_HANDLE hSCM = ::OpenSCManager (NULL, NULL, kServiceMgrAccessPrivs);
743 [[maybe_unused]] auto&& cleanup1 = Finally ([hSCM] () noexcept {
744 AssertNotNull (hSCM);
745 ::CloseServiceHandle (hSCM);
746 });
747 SC_HANDLE hService = ::OpenService (hSCM, GetSvcName_ ().c_str (), kServiceMgrAccessPrivs);
749 [[maybe_unused]] auto&& cleanup2 = Finally ([hService] () noexcept {
750 AssertNotNull (hService);
751 ::CloseServiceHandle (hService);
752 });
753
754 const bool kUseQueryServiceStatusEx_ = false;
755 if (kUseQueryServiceStatusEx_) {
756 SERVICE_STATUS_PROCESS serviceProcess{};
757 DWORD ignored = 0;
759 hService, SC_STATUS_PROCESS_INFO, reinterpret_cast<LPBYTE> (&serviceProcess), sizeof (serviceProcess), &ignored));
760 switch (serviceProcess.dwCurrentState) {
761 case SERVICE_RUNNING:
762 return Main::State::eRunning;
763 case SERVICE_PAUSED:
764 return Main::State::ePaused;
765 }
766 }
767 else {
768 SERVICE_STATUS serviceStatus{};
769 Execution::Platform::Windows::ThrowIfZeroGetLastError (::QueryServiceStatus (hService, &serviceStatus));
770 switch (serviceStatus.dwCurrentState) {
771 case SERVICE_RUNNING:
772 return Main::State::eRunning;
773 case SERVICE_PAUSED:
774 return Main::State::ePaused;
775 }
776 }
777 return Main::State::eStopped;
778}
779
780void Main::WindowsService::_Install ()
781{
782 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::_Install"};
783
784 const DWORD kServiceMgrAccessPrivs = SC_MANAGER_CREATE_SERVICE;
785 String cmdLineForRunSvc = "\""sv + String{GetEXEPath ()} + "\" --"sv + CommandNames::kRunAsService;
786 SC_HANDLE hSCM = ::OpenSCManager (NULL, NULL, kServiceMgrAccessPrivs);
788 [[maybe_unused]] auto&& cleanup = Execution::Finally ([hSCM] () noexcept {
789 AssertNotNull (hSCM);
790 ::CloseServiceHandle (hSCM);
791 });
792
793 DbgTrace (L"registering with command-line: '{}', serviceName: '{}'"_f, cmdLineForRunSvc, GetSvcName_ ());
794 SC_HANDLE hService = ::CreateService (hSCM, GetSvcName_ ().c_str (), fAppRep_->GetServiceDescription ().fPrettyName.AsSDKString ().c_str (),
795 kServiceMgrAccessPrivs, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
796 cmdLineForRunSvc.AsSDKString ().c_str (), NULL, NULL, _T("RPCSS\0"), NULL, NULL);
798}
799
800void Main::WindowsService::_UnInstall ()
801{
802 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::_UnInstall"};
803
804 const DWORD kServiceMgrAccessPrivs = SERVICE_STOP | DELETE;
805 SC_HANDLE hSCM = ::OpenSCManager (NULL, NULL, kServiceMgrAccessPrivs);
807 [[maybe_unused]] auto&& cleanup1 = Execution::Finally ([hSCM] () noexcept {
808 AssertNotNull (hSCM);
809 ::CloseServiceHandle (hSCM);
810 });
811
812 SC_HANDLE hService = ::OpenService (hSCM, GetSvcName_ ().c_str (), kServiceMgrAccessPrivs);
814 [[maybe_unused]] auto&& cleanup2 = Execution::Finally ([hService] () noexcept {
815 AssertNotNull (hService);
816 ::CloseServiceHandle (hService);
817 });
818
819 {
820 SERVICE_STATUS status;
821 if (not::ControlService (hService, SERVICE_CONTROL_STOP, &status)) {
822 DWORD e = ::GetLastError ();
823 if (e != ERROR_SERVICE_NOT_ACTIVE) {
824 Execution::ThrowSystemErrNo (e);
825 }
826 }
827 }
828
830}
831
832void Main::WindowsService::_RunAsService ()
833{
834 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::_RunAsService"};
835 Assert (s_SvcRunningTHIS_ == nullptr);
836 s_SvcRunningTHIS_ = this;
837
838 // MSFT docs unclear on lifetime requirements on these args but for now assume data copied...
839 SDKString svcName = GetSvcName_ ();
840 SERVICE_TABLE_ENTRY st[] = {{const_cast<TCHAR*> (svcName.c_str ()), StaticServiceMain_}, {nullptr, nullptr}};
841 if (::StartServiceCtrlDispatcher (st) == FALSE) {
842 fServiceStatus_.dwWin32ExitCode = ::GetLastError ();
843 if (fServiceStatus_.dwWin32ExitCode == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
844 DbgTrace ("fServiceStatus_.dwWin32ExitCode = ERROR_FAILED_SERVICE_CONTROLLER_CONNECT"_f);
845 }
846 Execution::ThrowSystemErrNo (fServiceStatus_.dwWin32ExitCode); // nb: set to getlasterror result above
847 }
848}
849
850void Main::WindowsService::_RunDirectly (const optional<Time::Duration>& runFor)
851{
852 shared_ptr<IApplicationRep> appRep = fAppRep_;
853 fRunThread_ = Execution::Thread::New ([appRep] () { appRep->MainLoop ([] () {}); }, Execution::Thread::eAutoStart, kServiceRunThreadName_);
854 Thread::CleanupPtr stopper{Thread::CleanupPtr::eAbortBeforeWaiting}; // another thread to stop the mainloop after runFor
855 if (runFor) {
856 stopper = Execution::Thread::New (
857 [&] () {
858 Execution::Sleep (*runFor);
859 fRunThread_.Abort ();
860 },
861 Execution::Thread::eAutoStart);
862 }
863 fRunThread_.Join ();
864}
865
866void Main::WindowsService::_Start (Time::DurationSeconds timeout)
867{
868 // @todo - timeout not supported
869 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::Start", "timeout = {}"_f, timeout};
870
871 const DWORD kServiceMgrAccessPrivs = SERVICE_START;
872 SC_HANDLE hSCM = ::OpenSCManager (nullptr, nullptr, kServiceMgrAccessPrivs);
874 [[maybe_unused]] auto&& cleanup1 = Execution::Finally ([hSCM] () noexcept {
875 AssertNotNull (hSCM);
876 ::CloseServiceHandle (hSCM);
877 });
878 SC_HANDLE hService = ::OpenService (hSCM, GetSvcName_ ().c_str (), kServiceMgrAccessPrivs);
880 [[maybe_unused]] auto&& cleanup2 = Execution::Finally ([hService] () noexcept {
881 AssertNotNull (hService);
882 ::CloseServiceHandle (hService);
883 });
884
885 DWORD dwNumServiceArgs = 0;
886 LPCTSTR* lpServiceArgVectors = nullptr;
887 Execution::Platform::Windows::ThrowIfZeroGetLastError (::StartService (hService, dwNumServiceArgs, lpServiceArgVectors));
888}
889
890void Main::WindowsService::_Stop ([[maybe_unused]] Time::DurationSeconds timeout)
891{
892 // @todo - timeout not supported
893 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::_Stop"};
894 const DWORD kServiceMgrAccessPrivs = SERVICE_STOP;
895 SC_HANDLE hSCM = ::OpenSCManager (NULL, NULL, kServiceMgrAccessPrivs);
897 [[maybe_unused]] auto&& cleanup1 = Execution::Finally ([hSCM] () noexcept {
898 AssertNotNull (hSCM);
899 ::CloseServiceHandle (hSCM);
900 });
901
902 SC_HANDLE hService = ::OpenService (hSCM, GetSvcName_ ().c_str (), kServiceMgrAccessPrivs);
904 [[maybe_unused]] auto&& cleanup2 = Execution::Finally ([hService] () noexcept {
905 AssertNotNull (hService);
906 ::CloseServiceHandle (hService);
907 });
908
909 {
910 SERVICE_STATUS status;
911 if (not::ControlService (hService, SERVICE_CONTROL_STOP, &status)) {
912 DWORD e = ::GetLastError ();
913 if (e != ERROR_SERVICE_NOT_ACTIVE) {
914 Execution::ThrowSystemErrNo (e);
915 }
916 }
917 }
918}
919
920void Main::WindowsService::_ForcedStop ([[maybe_unused]] Time::DurationSeconds timeout)
921{
923}
924
925pid_t Main::WindowsService::_GetServicePID () const
926{
927 const DWORD kServiceMgrAccessPrivs = SERVICE_QUERY_STATUS;
928 SC_HANDLE hSCM = ::OpenSCManager (NULL, NULL, kServiceMgrAccessPrivs);
930 [[maybe_unused]] auto&& cleanup1 = Execution::Finally ([hSCM] () noexcept {
931 AssertNotNull (hSCM);
932 ::CloseServiceHandle (hSCM);
933 });
934 SC_HANDLE hService = ::OpenService (hSCM, GetSvcName_ ().c_str (), kServiceMgrAccessPrivs);
936 [[maybe_unused]] auto&& cleanup2 = Execution::Finally ([hService] () noexcept {
937 AssertNotNull (hService);
938 ::CloseServiceHandle (hService);
939 });
940
941 SERVICE_STATUS_PROCESS serviceProcess{};
942 DWORD ignored = 0;
944 ::QueryServiceStatusEx (hService, SC_STATUS_PROCESS_INFO, reinterpret_cast<LPBYTE> (&serviceProcess), sizeof (serviceProcess), &ignored));
945 return serviceProcess.dwProcessId;
946}
947
948SDKString Main::WindowsService::GetSvcName_ () const
949{
950 RequireNotNull (fAppRep_); // must attach first
951 return fAppRep_->GetServiceDescription ().fRegistrationName.AsSDKString ();
952}
953
954bool Main::WindowsService::IsInstalled_ () const noexcept
955{
956 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::IsInstalled_"};
957 const DWORD kServiceMgrAccessPrivs = SERVICE_QUERY_CONFIG;
958 SC_HANDLE hSCM = ::OpenSCManager (NULL, NULL, kServiceMgrAccessPrivs);
960 [[maybe_unused]] auto&& cleanup1 = Execution::Finally ([hSCM] () noexcept {
961 AssertNotNull (hSCM);
962 ::CloseServiceHandle (hSCM);
963 });
964
965 SC_HANDLE hService = ::OpenService (hSCM, GetSvcName_ ().c_str (), kServiceMgrAccessPrivs);
967 [[maybe_unused]] auto&& cleanup2 = Execution::Finally ([hService] () noexcept {
968 AssertNotNull (hService);
969 ::CloseServiceHandle (hService);
970 });
971 return hService != NULL;
972}
973
974void Main::WindowsService::SetServiceStatus_ (DWORD dwState) noexcept
975{
976 DbgTrace ("SetServiceStatus_ ({})"_f, dwState);
977 Assert (fServiceStatusHandle_ != nullptr);
978 fServiceStatus_.dwCurrentState = dwState;
979 ::SetServiceStatus (fServiceStatusHandle_, &fServiceStatus_);
980}
981
982void Main::WindowsService::ServiceMain_ ([[maybe_unused]] DWORD dwArgc, [[maybe_unused]] LPTSTR* lpszArgv) noexcept
983{
984 Debug::TraceContextBumper traceCtx{"Stroika::Frameworks::Service::Main::WindowsService::ServiceMain_"};
985 ///@TODO - FIXUP EXCEPTION HANLDING HERE!!!
986
987 // do file create stuff here
988 //Logger::sThe.Log (Logger::eInfo, L"entering ServiceMain_");
989
990 // Register the control request handler
991 fServiceStatus_.dwCurrentState = SERVICE_START_PENDING;
992 fServiceStatusHandle_ = ::RegisterServiceCtrlHandler (GetSvcName_ ().c_str (), StaticHandler_);
994 SetServiceStatus_ (SERVICE_START_PENDING);
995
996 fServiceStatus_.dwWin32ExitCode = S_OK;
997 fServiceStatus_.dwCheckPoint = 0;
998 fServiceStatus_.dwWaitHint = 0;
999
1000 // When the Run function returns, the service has stopped.
1001 // about like this - FIX - KEEP SOMETHING SIMIALR
1002 shared_ptr<IApplicationRep> appRep = fAppRep_;
1003 fRunThread_ = Execution::Thread::New ([appRep] () { appRep->MainLoop ([] () {}); }, Execution::Thread::eAutoStart, kServiceRunThreadName_);
1004 //Logger::sThe.Log (Logger::eInfo, L"in ServiceMain_ about to set SERVICE_RUNNING");
1005 SetServiceStatus_ (SERVICE_RUNNING);
1006
1007 try {
1008 fRunThread_.Join ();
1009 }
1010 catch (...) {
1011 DbgTrace ("mapping run-thread.Join () exception {} to dwWin32ExitCode=1"_f, current_exception ());
1012 fServiceStatus_.dwWin32ExitCode = 1; // some non-zero exit code
1013 }
1014 SetServiceStatus_ (SERVICE_STOPPED);
1015}
1016
1017void WINAPI Main::WindowsService::StaticServiceMain_ (DWORD dwArgc, LPTSTR* lpszArgv) noexcept
1018{
1019 AssertNotNull (s_SvcRunningTHIS_);
1020 s_SvcRunningTHIS_->ServiceMain_ (dwArgc, lpszArgv);
1021}
1022
1023void Main::WindowsService::OnStopRequest_ () noexcept
1024{
1025 /*
1026 * WARNING - this maybe a race about setting status!!! - what if we get stop request when already stopped.
1027 * THIS CODE NEEDS THREAD LOCKS!!!
1028 */
1029 if (fServiceStatus_.dwCurrentState == SERVICE_RUNNING) {
1030 SetServiceStatus_ (SERVICE_STOP_PENDING);
1031 fRunThread_.Abort ();
1032 }
1033}
1034
1035void Main::WindowsService::Handler_ (DWORD dwOpcode) noexcept
1036{
1037 switch (dwOpcode) {
1038 case SERVICE_CONTROL_STOP:
1039 OnStopRequest_ ();
1040 break;
1041 default:
1042 // others ignored for now
1043 break;
1044 }
1045}
1046
1047void WINAPI Main::WindowsService::StaticHandler_ (DWORD dwOpcode) noexcept
1048{
1049 AssertNotNull (s_SvcRunningTHIS_);
1050 s_SvcRunningTHIS_->Handler_ (dwOpcode);
1051}
1052#endif
#define AssertNotNull(p)
Definition Assertions.h:333
#define AssertNotImplemented()
Definition Assertions.h:401
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual SDKString AsSDKString() const
Definition String.inl:802
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Definition Set.h:105
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
nonvirtual optional< String > GetArgument(const Option &o) const
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
void Log(Priority logLevel, const wchar_t *format,...)
Definition Logger.inl:27
Run the given command, and optionally support stdin/stdout/stderr as streams (either sync with Run,...
nonvirtual void AddSignalHandler(SignalID signal, const SignalHandler &handler)
nonvirtual void RemoveSignalHandler(SignalID signal, const SignalHandler &handler)
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
nonvirtual void Join(Time::DurationSeconds timeout=Time::kInfinity) const
Wait for the pointed-to thread to be done. If the thread completed with an exception (other than thre...
Definition Thread.inl:276
nonvirtual void Abort() const
Abort gracefully shuts down and terminates the given thread (using cooperative multitasking).
Definition Thread.cpp:809
nonvirtual void AbortAndWaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Abort () the thread, and then WaitForDone () - but if doesn't finish fast enough, send extra aborts (...
Definition Thread.inl:291
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
nonvirtual void Write(span< ELEMENT_TYPE2, EXTENT_2 > elts) const
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
nonvirtual optional< T > First() const
return first element in iterable, or if 'that' specified, first where 'that' is true,...
Definition Iterable.inl:829
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:300
nonvirtual RESULT_CONTAINER Where(INCLUDE_PREDICATE &&includeIfTrue) const
produce a subset of this iterable where argument function returns true
virtual void _Attach(const shared_ptr< IApplicationRep > &appRep) override
static shared_ptr< IServiceIntegrationRep > mkDefaultServiceIntegrationRep()
nonvirtual Containers::Set< ServiceIntegrationFeatures > GetServiceIntegrationFeatures() const
Definition Main.inl:32
nonvirtual pid_t GetServicePID() const
Definition Main.inl:74
nonvirtual void RunDirectly(const optional< Time::Duration > &runFor={})
nonvirtual void ForcedRestart(Time::DurationSeconds timeout=Time::kInfinity, Time::DurationSeconds unforcedStopTimeout=Time::kInfinity)
nonvirtual void Run(const CommandArgs &args, const Streams::OutputStream::Ptr< Characters::Character > &out=nullptr)
nonvirtual String GetServiceStatusMessage() const
basic_string< SDKChar > SDKString
Definition SDKString.h:38
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.cpp:18
void ThrowTimeoutExceptionAfter(Time::TimePointSeconds afterTickCount, EXCEPTION &&exception2Throw)
Throw TimeOutException if the @Time::GetTickCount () is >= the given value.
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
int pid_t
TODO - maybe move this to configuraiotn module???
Definition Module.h:34
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)
filesystem::path GetEXEPath()
Definition Module.cpp:53
pid_t DetachedProcessRunner(const filesystem::path &executable, const Containers::Sequence< String > &args)
DEPRECATED.