diff --git a/agents/wnx/src/common/wtools.cpp b/agents/wnx/src/common/wtools.cpp index 521601eba75e7..a72f5ac0de10a 100644 --- a/agents/wnx/src/common/wtools.cpp +++ b/agents/wnx/src/common/wtools.cpp @@ -4,6 +4,7 @@ #define WIN32_LEAN_AND_MEAN #include +#include #include #include @@ -96,17 +97,18 @@ void WINAPI ServiceController::ServiceMain(DWORD Argc, wchar_t** Argv) { // no return from here // can print on screen -bool ServiceController::registerAndRun(const wchar_t* ServiceName, // - bool CanStop, // t - bool CanShutdown, // t - bool CanPauseContinue) { // t +ServiceController::StopType ServiceController::registerAndRun( + const wchar_t* ServiceName, // + bool CanStop, // t + bool CanShutdown, // t + bool CanPauseContinue) { // t if (!processor_) { XLOG::l.bp("No processor"); - return false; + return StopType::fail; } if (!ServiceName) { XLOG::l.bp("No Service name"); - return false; + return StopType::fail; } // strange code below @@ -128,23 +130,30 @@ bool ServiceController::registerAndRun(const wchar_t* ServiceName, // // returns when the service has stopped. The process should simply // terminate when the call returns. Two words: Blocks Here try { - auto ret = StartServiceCtrlDispatcher(serviceTable); + auto ret = ::StartServiceCtrlDispatcher(serviceTable); if (!ret) { + auto error = GetLastError(); + + // this normal situation when we are starting service from command + // line without parameters + if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) + return StopType::no_connect; + XLOG::l(XLOG::kStdio) .crit("Cannot Start Service '{}' error = [{}]", - ConvertToUTF8(ServiceName), GetLastError()); - return false; + ConvertToUTF8(ServiceName), error); + return StopType::fail; } - return true; + return StopType::normal; } catch (std::exception& e) { XLOG::l(XLOG::kStdio) - .crit("Exception '{}' in Service start with error {}", e.what(), + .crit("Exception '{}' in Service start with error [{}]", e.what(), GetLastError()); } catch (...) { XLOG::l(XLOG::kStdio) - .crit("Exception in Service start with error {}", GetLastError()); + .crit("Exception in Service start with error [{}]", GetLastError()); } - return false; + return StopType::fail; } // @@ -209,13 +218,20 @@ bool InstallService(const wchar_t* ServiceName, const wchar_t* DisplayName, Password // Password of the account ); if (!service) { - XLOG::l(XLOG::kStdio) - .crit("CreateService failed w/err {}", GetLastError()); - return false; + auto error = GetLastError(); + if (error == ERROR_SERVICE_EXISTS) { + XLOG::l(XLOG::kStdio) + .crit("The Service '{}' already exists", + wtools::ConvertToUTF8(ServiceName)); + return false; + } + XLOG::l(XLOG::kStdio).crit("CreateService failed w/err {}", error); } ON_OUT_OF_SCOPE(CloseServiceHandle(service);); - XLOG::l(XLOG::kStdio).i("'{}' is installed.", ConvertToUTF8(ServiceName)); + XLOG::l(XLOG::kStdio) + .i("The Service '{}' is installed.", ConvertToUTF8(ServiceName)); + return true; } @@ -232,33 +248,43 @@ bool InstallService(const wchar_t* ServiceName, const wchar_t* DisplayName, // error in the standard output stream for users to diagnose the problem. // // StopService by default is true, use false only during testing -bool UninstallService(const wchar_t* ServiceName, UninstallServiceMode Mode) { +bool UninstallService(const wchar_t* service_name, + UninstallServiceMode uninstall_mode) { XLOG::setup::ColoredOutputOnStdio(true); + if (service_name == nullptr) { + XLOG::l(XLOG::kStdio).crit("Parameter is null"); + return false; + } + auto name = wtools::ConvertToUTF8(service_name); // Open the local default service control manager database auto service_manager = ::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (!service_manager) { XLOG::l(XLOG::kStdio) - .crit("OpenSCManager failed w/err {:#X}", GetLastError()); + .crit("OpenSCManager failed, [{}]", GetLastError()); return false; } ON_OUT_OF_SCOPE(::CloseServiceHandle(service_manager);); // Open the service with delete, stop, and query status permissions - auto service = ::OpenService(service_manager, ServiceName, + auto service = ::OpenService(service_manager, service_name, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE); if (!service) { - XLOG::l(XLOG::kStdio) - .crit("OpenService failed w/err {:#X}", GetLastError()); + auto error = GetLastError(); + if (error == ERROR_SERVICE_DOES_NOT_EXIST) { + XLOG::l(XLOG::kStdio).crit("The Service '{}' doesn't exist", name); + return false; + } + + XLOG::l(XLOG::kStdio).crit("OpenService '{}' failed, [{}]", name); return false; } ON_OUT_OF_SCOPE(::CloseServiceHandle(service);); - if (Mode == UninstallServiceMode::normal) { + if (uninstall_mode == UninstallServiceMode::normal) { // Try to stop the service SERVICE_STATUS ssSvcStatus = {}; - auto service_name = wtools::ConvertToUTF8(ServiceName); if (::ControlService(service, SERVICE_CONTROL_STOP, &ssSvcStatus)) { - XLOG::l(XLOG::kStdio).i("Stopping '{}'.", service_name); + XLOG::l(XLOG::kStdio).i("Stopping '{}'.", name); Sleep(1000); while (::QueryServiceStatus(service, &ssSvcStatus)) { @@ -270,9 +296,9 @@ bool UninstallService(const wchar_t* ServiceName, UninstallServiceMode Mode) { } if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED) { - XLOG::l(XLOG::kStdio).i("\n{} is stopped.", service_name); + XLOG::l(XLOG::kStdio).i("\n{} is stopped.", name); } else { - XLOG::l(XLOG::kStdio).i("\n{} failed to stop.", service_name); + XLOG::l(XLOG::kStdio).i("\n{} failed to stop.", name); } } } @@ -280,12 +306,12 @@ bool UninstallService(const wchar_t* ServiceName, UninstallServiceMode Mode) { // Now remove the service by calling DeleteService. if (!::DeleteService(service)) { XLOG::l(XLOG::kStdio) - .i("DeleteService failed w/err {:#X}\n", GetLastError()); + .i("DeleteService for '{}' failed [{}]\n", name, GetLastError()); return false; } XLOG::l(XLOG::kStdio) - .i("%s is removed.\n", wtools::ConvertToUTF8(ServiceName)); + .i("The Service '{}' is successfully removed.\n", name); return true; } diff --git a/agents/wnx/src/common/wtools.h b/agents/wnx/src/common/wtools.h index dc547fdaa2ebc..a14ef8c5905a9 100644 --- a/agents/wnx/src/common/wtools.h +++ b/agents/wnx/src/common/wtools.h @@ -73,8 +73,9 @@ bool InstallService(const wchar_t* ServiceName, const wchar_t* DisplayName, // error in the standard output stream for users to diagnose the problem. // enum class UninstallServiceMode { normal, test }; -bool UninstallService(const wchar_t* ServiceName, - UninstallServiceMode Mode = UninstallServiceMode::normal); +bool UninstallService( + const wchar_t* service_name, + UninstallServiceMode uninstall_mode = UninstallServiceMode::normal); // Abstract Interface template for SERVICE PROCESSOR: // WE ARE NOT GOING TO USE AT ALL. @@ -363,9 +364,10 @@ class ServiceController { } // no return from here till service ends - bool registerAndRun(const wchar_t* ServiceName, // - bool CanStop = true, bool CanShutdown = true, - bool CanPauseContinue = true); + enum class StopType { normal, no_connect, fail }; + StopType registerAndRun(const wchar_t* ServiceName, // + bool CanStop = true, bool CanShutdown = true, + bool CanPauseContinue = true); protected: // diff --git a/agents/wnx/src/engine/windows_service_api.cpp b/agents/wnx/src/engine/windows_service_api.cpp index 46ed7e071e509..493c8c1da1eaf 100644 --- a/agents/wnx/src/engine/windows_service_api.cpp +++ b/agents/wnx/src/engine/windows_service_api.cpp @@ -191,6 +191,10 @@ int TestMainServiceSelf(int Interval) { asio::ip::tcp::socket socket(ios); std::error_code ec; + // give some time to start main thread + // this is tesing routine ergo so primitive method is ok + cma::tools::sleep(1000); + while (!stop) { auto enc = cma::cfg::groups::global.globalEncrypt(); auto password = enc ? cma::cfg::groups::global.password() : ""; @@ -239,6 +243,7 @@ int TestMainServiceSelf(int Interval) { if (Interval == 0) break; } XLOG::l.i("Leaving testing thread"); + if (Interval == 0) XLOG::l.i("\n\nPress any key to end program\n\n"); }); ExecMainService(StdioLog::no); // blocking call waiting for keypress @@ -252,10 +257,10 @@ int TestMainServiceSelf(int Interval) { return 0; } -// on -test +// on check int TestMainService(const std::wstring& What, int Interval) { using namespace std::chrono; - if (What == L"port") { + if (What == L"io") { // simple test for ExternalPort. will be disabled in production. try { cma::world::ExternalPort port(nullptr); @@ -690,9 +695,18 @@ int ServiceAsService( cma::srv::kServiceName); // we will stay here till // service will be stopped // itself or from outside - XLOG::l.i("Service is stopped {}", - ret ? "as usually" : "due to abnormal situation"); - return ret ? 0 : -1; + switch (ret) { + case wtools::ServiceController::StopType::normal: + XLOG::l.i("Service is stopped normally"); + return 0; + + case wtools::ServiceController::StopType::fail: + XLOG::l.i("Service is stopped due to abnormal situation"); + return -1; + case wtools::ServiceController::StopType::no_connect: + // may happen when we try to call usual exe + return 0; + } } catch (const std::exception& e) { XLOG::l.crit("Exception hit {} in ServiceAsService", e.what()); } catch (...) { diff --git a/agents/wnx/src/main/check_mk_service.cpp b/agents/wnx/src/main/check_mk_service.cpp index 8df79983db355..65d854f299755 100644 --- a/agents/wnx/src/main/check_mk_service.cpp +++ b/agents/wnx/src/main/check_mk_service.cpp @@ -38,14 +38,13 @@ void PrintMain() { using namespace xlog::internal; PrintBlock("Normal Usage:\n", Colors::kGreen, []() { return fmt::format( - "\t{1} <{2}|{3}|{4}>\n" - "\t{2:<{0}} - install as a service, Administrative Rights are required\n" - "\t{3:<{0}} - remove service, Administrative Rights are required\n" - "\t{4:<{0}} - usage\n", + "\t{1} <{2}|{3}>\n" + "\t{2:<{0}} - usage\n" + "\t{3:<{0}} - test\n", kParamShift, kServiceExeName, // service name from th project definitions // first Row - kInstallParam, kRemoveParam, kHelpParam); + kLegacyTestParam, kHelpParam); }); } @@ -56,10 +55,10 @@ void PrintSelfCheck() { "\t{1} {2} <{3}|{4}|{5} [number of seconds]>\n" "\t{2:<{0}} - check test\n" "\t\t{3:<{0}} - main thread test\n" - "\t\t{4:<{0}} - internal port test \n" - "\t\t{5:<{0}} - simulates connection after expiring 'seconds' interval\n", + "\t\t{4:<{0}} - simple self test of internal and external transport\n" + "\t\t{5:<{0}} - simulates periodical connection from Check MK Site, for example '{1} {2} {5} 13'\n", kParamShift, kServiceExeName, kCheckParam, kCheckParamMt, - kCheckParamPort, kCheckParamSelf); + kCheckParamIo, kCheckParamSelf); }); } @@ -78,6 +77,7 @@ void PrintAdHoc() { }); } +// obsolete void PrintLegacyTesting() { using namespace xlog::internal; PrintBlock("Classic/Legacy Testing:\n", Colors::kCyan, []() { @@ -90,6 +90,22 @@ void PrintLegacyTesting() { }); } +void PrintInstallUninstall() { + using namespace xlog::internal; + PrintBlock( + "To install or remove service: for Experienced Users only:\n", + Colors::kPink, []() { + return fmt::format( + "\t{1} <{2}|{3}>\n" + "\t{2:<{0}} - install as a service, Administrative Rights are required\n" + "\t{3:<{0}} - remove service, Administrative Rights are required\n", + kParamShift, + kServiceExeName, // service name from th project definitions + // first Row + kInstallParam, kRemoveParam); + }); +} + void PrintShowConfig() { using namespace xlog::internal; PrintBlock( @@ -206,7 +222,6 @@ static void ServiceUsage(std::wstring_view comment) { PrintMain(); PrintSelfCheck(); PrintAdHoc(); - PrintLegacyTesting(); PrintRealtimeTesting(); PrintShowConfig(); PrintCvt(); @@ -214,6 +229,7 @@ static void ServiceUsage(std::wstring_view comment) { PrintUpgrade(); PrintCap(); PrintSectionTesting(); + PrintInstallUninstall(); } catch (const std::exception &e) { XLOG::l("Exception is '{}'", e.what()); // } @@ -284,7 +300,7 @@ int MainFunction(int argc, wchar_t const *Argv[]) { cma::details::G_Service = true; // we know that we are service - return cma::srv::ServiceAsService(1000ms, [](const void *) { + auto ret = cma::srv::ServiceAsService(1000ms, [](const void *) { // optional commands listed here // ******** // 1. Auto Update when MSI file is located by specified address @@ -298,6 +314,9 @@ int MainFunction(int argc, wchar_t const *Argv[]) { GetUserInstallDir()); // dir where file to backup return true; }); + if (ret == 0) ServiceUsage(L""); + + return ret == 0 ? 0 : 1; } std::wstring param(Argv[1]); diff --git a/agents/wnx/src/main/check_mk_service.h b/agents/wnx/src/main/check_mk_service.h index 70545e7e1a9c5..cc955b70c74a4 100644 --- a/agents/wnx/src/main/check_mk_service.h +++ b/agents/wnx/src/main/check_mk_service.h @@ -19,7 +19,7 @@ constexpr std::string_view kLegacyTestParam = "test"; constexpr std::string_view kCheckParam = "check"; constexpr std::string_view kCheckParamSelf = "-self"; constexpr std::string_view kCheckParamMt = "-mt"; -constexpr std::string_view kCheckParamPort = "-port"; +constexpr std::string_view kCheckParamIo = "-io"; constexpr std::string_view kRealtimeParam = "rt"; constexpr std::string_view kHelpParam = "help"; diff --git a/agents/wnx/watest/test-service.cpp b/agents/wnx/watest/test-service.cpp index b1d1e4de73853..59ba3c561608c 100644 --- a/agents/wnx/watest/test-service.cpp +++ b/agents/wnx/watest/test-service.cpp @@ -91,7 +91,9 @@ TEST(ServiceControllerTest, StartStop) { EXPECT_EQ(controller.can_shutdown_, false); EXPECT_EQ(controller.can_pause_continue_, false); EXPECT_NE(controller.processor_, nullptr); - if (0) { + + // special case with "no connect" case + { auto ret = wtools::InstallService(test_service_name, // name of service L"Test Name", // service name to display @@ -101,19 +103,18 @@ TEST(ServiceControllerTest, StartStop) { nullptr // no password ); EXPECT_TRUE(ret); + if (ret) { - bool success = false; + ON_OUT_OF_SCOPE(wtools::UninstallService(test_service_name)); + auto success = wtools::ServiceController::StopType::fail; std::thread t([&]() { success = controller.registerAndRun(test_service_name, true, true, true); }); - EXPECT_TRUE(success); - std::this_thread::sleep_until(steady_clock::now() + - 500ms); // wait for thread - EXPECT_TRUE(counter > 3); + if (t.joinable()) t.join(); - EXPECT_TRUE(ret); - if (ret) wtools::UninstallService(test_service_name); + EXPECT_EQ(success, wtools::ServiceController::StopType::no_connect); + EXPECT_EQ(counter, 0); } } }