diff options
| author | Dan Engelbrecht <[email protected]> | 2025-01-15 09:30:12 +0100 |
|---|---|---|
| committer | Dan Engelbrecht <[email protected]> | 2025-01-15 09:30:12 +0100 |
| commit | 531c59032bbc46bc1f7284859fa8ff8c8b5ede61 (patch) | |
| tree | c06583ef275904824a9eeae42951fa6a48ebae20 /src/zenutil/service.cpp | |
| parent | clang format (diff) | |
| download | zen-531c59032bbc46bc1f7284859fa8ff8c8b5ede61.tar.xz zen-531c59032bbc46bc1f7284859fa8ff8c8b5ede61.zip | |
systemd unit file, incomplete
Diffstat (limited to 'src/zenutil/service.cpp')
| -rw-r--r-- | src/zenutil/service.cpp | 250 |
1 files changed, 135 insertions, 115 deletions
diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 834d8b764..820edf565 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -256,111 +256,58 @@ namespace { #endif // ZEN_PLATFORM_MAC -#if ZEN_PLATFORM_MAC +#if ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX + // TODO: Is this good enough to capture all output/errors/return codes? std::pair<int, std::string> ExecuteProgram(std::string_view Cmd) { - std::string data; - const int max_buffer = 256; - char buffer[max_buffer]; + std::string Data; + const int BufferSize = 256; + char Buffer[BufferSize]; std::string Command(Cmd); Command.append(" 2>&1"); - FILE* stream = popen(Command.c_str(), "r"); - if (stream) + ZEN_DEBUG("Running: '{}'", Command); + + FILE* Stream = popen(Command.c_str(), "r"); + if (Stream) { - while (!feof(stream)) + while (!feof(Stream)) { - if (fgets(buffer, max_buffer, stream) != NULL) + if (fgets(Buffer, BufferSize, Stream) != NULL) { - data.append(buffer); + Data.append(Buffer); } } + while(!Data.empty() && isspace(Data[Data.length() - 1])) + { + Data.pop_back(); + } + int Res = -1; - int st = pclose(stream); - if (WIFEXITED(st)) - Res = WEXITSTATUS(st); - return {Res, data}; + int Status = pclose(Stream); + if (Status < 0) + { + return {Status, Data}; + } + uint64_t WaitMS = 100; + if (!WIFEXITED(Status)) + { + Res = WEXITSTATUS(Status); + } + if (Res != 0 && Res != (128+13)) + { + return {Res, Data}; + } + return {0, Data}; } return {errno, ""}; - -# if 0 - int in[2], out[2], n, pid; - char buf[255]; - - /* In a pipe, xx[0] is for reading, xx[1] is for writing */ - - if (pipe(in) < 0) - { - return {errno, ""}; - } - if (pipe(out) < 0) - { - close(in[0]); - close(in[1]); - return {errno, ""}; - } - - if ((pid=fork()) == 0) { - /* This is the child process */ - - /* Close stdin, stdout, stderr */ - close(0); - close(1); - close(2); - /* make our pipes, our new stdin,stdout and stderr */ - dup2(in[0],0); - dup2(out[1],1); - dup2(out[1],2); - - /* Close the other ends of the pipes that the parent will use, because if - * we leave these open in the child, the child/parent will not get an EOF - * when the parent/child closes their end of the pipe. - */ - close(in[1]); - close(out[0]); - - va_list args; - va_start(args, Executable); - va_end(args); - - /* Over-write the child process with the hexdump binary */ - execl(Executable, Executable, args); - } - - /* This is the parent process */ - /* Close the pipe ends that the child uses to read from / write to so - * the when we close the others, an EOF will be transmitted properly. - */ - close(in[0]); - - /* Because of the small amount of data, the child may block unless we - * close it's input stream. This sends an EOF to the child on it's - * stdin. - */ - close(in[1]); - - /* Read back any output */ - n = read(out[0], buf, 250); - if (n == 0) - { - n = read(out[1], buf, 250); - } - buf[n] = 0; - close(out[0]); - close(out[1]); - - std::string Output(buf); - return {0, Output}; -# endif // 0 } -#endif // ZEN_PLATFORM_MAC +#endif // ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX #if ZEN_PLATFORM_LINUX - // const char* SystemInstallFolder = "/etc/systemd/system/"; - std::string GetUnitName(std::string_view ServiceName) { return fmt::format("com.epicgames.unreal.{}", ServiceName); } std::filesystem::path GetServiceUnitPath(const std::string& UnitName) @@ -374,10 +321,8 @@ namespace { std::string_view CommandLineOptions, std::string_view AliasName) { -# if 0 - ZEN_UNUSED(ServiceName, ExecutablePath, CommandLineOptions, AliasName); - return ""; -# else + // TODO: Revise to make sure the unit file is correct + // TODO: Do we need a separate config file or is that optional? return fmt::format( "[Unit]\n" "Description={}\n" @@ -396,13 +341,13 @@ namespace { "Restart=always\n" "RuntimeDirectory={}\n" "[Install]\n" - "Alias={}", + "Alias={}\n" + "WantedBy=multi-user.target", ServiceName, ExecutablePath, CommandLineOptions, ExecutablePath.parent_path(), AliasName); -# endif } #endif // ZEN_PLATFORM_LINUX @@ -440,8 +385,10 @@ ToString(ServiceStatus Status) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - // Get a handle to the SCM database. + // TODO: Is "LocalService account" the correct account? + // TODO: Is ther eother parameters/security settings that we need to set up properly? + // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS); // full access rights @@ -744,6 +691,7 @@ StopService(std::string_view ServiceName) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { + // TODO: Do we need to create a separate user for the service or is running as the default service user OK? const std::string DaemonName = GetDaemonName(ServiceName); std::string PList = BuildPlist(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, DaemonName, true); @@ -780,8 +728,6 @@ UninstallService(std::string_view ServiceName) std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - ZEN_UNUSED(ServiceName, OutInfo); - OutInfo.Status = ServiceStatus::NotInstalled; const std::string DaemonName = GetDaemonName(ServiceName); @@ -873,7 +819,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) }); } { - std::pair<int, std::string> Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); + std::pair<int, std::string> Res = ExecuteProgram(fmt::format("launchctl list {}", DaemonName)); if (Res.first == 0 && !Res.second.empty()) { ForEachStrTok(Res.second, '\n', [&](std::string_view Line) { @@ -901,12 +847,10 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - const std::string DaemonName = GetDaemonName(ServiceName); const std::filesystem::path PListPath = GetPListPath(DaemonName); - std::pair<int, std::string> Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); + std::pair<int, std::string> Res = ExecuteProgram(fmt::format("launchctl bootstrap system {}", PListPath)); if (Res.first != 0) { return MakeErrorCode(Res.first); @@ -918,12 +862,10 @@ StartService(std::string_view ServiceName) std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - const std::string DaemonName = GetDaemonName(ServiceName); const std::filesystem::path PListPath = GetPListPath(DaemonName); - std::pair<int, std::string> Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); + std::pair<int, std::string> Res = ExecuteProgram(fmt::format("launchctl bootout system ", PListPath.)); if (Res.first != 0) { return MakeErrorCode(Res.first); @@ -939,13 +881,13 @@ StopService(std::string_view ServiceName) std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec) { - ZEN_UNUSED(ServiceName, Spec); + // TODO: Do we need to create a separate user for the service or is running as root OK? const std::string UnitName = GetUnitName(ServiceName); - std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); - const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); - ZEN_INFO("Writing systemd unit file to {}", ServiceUnitPath.string()); + + std::string UnitFile = BuildUnitFile(ServiceName, Spec.ExecutablePath, Spec.CommandLineOptions, UnitName); + ZEN_DEBUG("Writing systemd unit file to {}", ServiceUnitPath.string()); try { zen::WriteFile(ServiceUnitPath, IoBuffer(IoBuffer::Wrap, UnitFile.data(), UnitFile.size())); @@ -955,20 +897,57 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) return MakeErrorCode(Ex.code().value()); } - ZEN_INFO("Changing permissions to 644 for {}", ServiceUnitPath.string()); + ZEN_DEBUG("Changing permissions to 644 for {}", ServiceUnitPath.string()); if (chmod(ServiceUnitPath.string().c_str(), 0644) == -1) { return MakeErrorCodeFromLastError(); } + std::pair<int, std::string> Res = ExecuteProgram("systemctl daemon-reload"); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl daemon-reload failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + + Res = ExecuteProgram(fmt::format("systemctl enable {}", UnitName)); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl enable failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + return {}; } std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + std::pair<int, std::string> Res = ExecuteProgram(fmt::format("systemctl disable {}", UnitName)); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl disable failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + + ZEN_DEBUG("Attempting to remove systemd unit file from {}", ServiceUnitPath.string()); + std::error_code Ec; + std::filesystem::remove(ServiceUnitPath, Ec); + if (Ec) + { + ZEN_ERROR("failed to remove {}: '{}'", ServiceUnitPath, Ec.message()); + return Ec; + } + + Res = ExecuteProgram("systemctl daemon-reload"); + if (Res.first != 0 && Res.first != -1) + { + ZEN_ERROR("systemctl daemon-reload failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } return {}; } @@ -976,8 +955,27 @@ UninstallService(std::string_view ServiceName) std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { - ZEN_UNUSED(ServiceName, OutInfo); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + OutInfo.Status = ServiceStatus::NotInstalled; + + if (std::filesystem::is_regular_file(ServiceUnitPath)) + { + OutInfo.Status = ServiceStatus::Stopped; + // TODO: Read and parse unit file ? + + std::pair<int, std::string> Res = ExecuteProgram(fmt::format("systemctl status {}", UnitName)); + if (Res.first == 0) + { + // TODO: What does status really return and what info can we use here to get the ServiceInfo complete? + OutInfo.Status = ServiceStatus::Running; + } + else + { + ZEN_DEBUG("systemctl status failed with '{}" ({}), Res.second, Res.first); + } + } return {}; } @@ -985,8 +983,20 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + // TODO: Starting the service returns -1 from ExecuteProgram, so the service won't start + // TODO: Running start from command line gives no output but the service does not start - not sure what is wrong. + // Starting the same command line for the service using `sudo` *will* start the service sucessfully so I + // assume that the Unit file or some config is wrong/missing. + + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + std::pair<int, std::string> Res = ExecuteProgram(fmt::format("systemctl start {}", UnitName)); + if (Res.first != 0) + { + ZEN_ERROR("service start failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } return {}; } @@ -994,8 +1004,18 @@ StartService(std::string_view ServiceName) std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName); - ZEN_NOT_IMPLEMENTED("linux service implementation incomplete"); + // TODO: Stopping the service returns -1 from ExecuteProgram, maybe this ie expected as I have yet to successfully start the service using systemctl start + + const std::string UnitName = GetUnitName(ServiceName); + const std::filesystem::path ServiceUnitPath = GetServiceUnitPath(UnitName); + + std::pair<int, std::string> Res = ExecuteProgram(fmt::format("systemctl stop {}", UnitName)); + if (Res.first != 0) + { + ZEN_ERROR("service stop failed with {}: '{}'", Res.first, Res.second); + return MakeErrorCode(Res.first); + } + return {}; } |