From 5e02ddf6cc3d311cafbd4fcff326ece0141a992e Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 10 Jan 2025 14:34:49 +0100 Subject: partially working service commands for macos --- src/zenutil/service.cpp | 275 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 250 insertions(+), 25 deletions(-) (limited to 'src/zenutil/service.cpp') diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index 72367a070..feebeb7f4 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -243,6 +243,110 @@ namespace { } #endif // ZEN_PLATFORM_MAC + + +#if ZEN_PLATFORM_MAC || ZEN_PLATFORM_LINUX + +std::pair ExecuteProgram(std::string_view Cmd) +{ + std::string data; + const int max_buffer = 256; + char buffer[max_buffer]; + std::string Command(Cmd); + Command.append(" 2>&1"); + + FILE * stream = popen(Command.c_str(), "r"); + if (stream) + { + while (!feof(stream)) + { + if (fgets(buffer, max_buffer, stream) != NULL) + { + data.append(buffer); + } + } + + int Res = -1; + int st = pclose(stream); + if (WIFEXITED(st)) Res = WEXITSTATUS(st); + return {Res, 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 || ZEN_PLATFORM_LINUX + + } // namespace std::string_view @@ -633,7 +737,7 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) // return MakeErrorCodeFromLastError(); // } - // System: /Library/LaunchDaemons + // System: /Library/LaunchDaemon // All users: /Library/LaunchAgents // Current user: ~/Library/LaunchAgents @@ -665,34 +769,137 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) std::error_code UninstallService(std::string_view ServiceName) { - ZEN_UNUSED(Level); - - std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; - ZEN_INFO("Attempting to remove service from {}", ServicePath.string()); - if (unlink(ServicePath.string().c_str()) == -1) - { - return MakeErrorCodeFromLastError(); - } +// std::filesystem::path ServicePath = std::filesystem::path("/usr/local/libexec") / ServiceName; +// ZEN_INFO("Attempting to remove service from {}", ServicePath.string()); +// if (unlink(ServicePath.string().c_str()) == -1) +// { +// return MakeErrorCodeFromLastError(); +// } std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); - std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemons") / (DaemonName + ".plist"); + std::filesystem::path PListPath = std::filesystem::path("/Library/LaunchDaemon") / (DaemonName + ".plist"); ZEN_INFO("Attempting to remove launchd plist from {}", PListPath.string()); - if (unlink(ServicePath.string().c_str()) == -1) - { - return MakeErrorCodeFromLastError(); - } - - return {}; + std::error_code Ec; + std::filesystem::remove(PListPath, Ec); + return Ec; + +// if (unlink(PListPath.string().c_str()) == -1) +// { +// return MakeErrorCodeFromLastError(); +// } + +// return {}; } std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) { ZEN_UNUSED(ServiceName, OutInfo); - // ZEN_NOT_IMPLEMENTED("QueryInstalledService"); - // std::filesystem::path PListFolder = "/Library/LaunchDaemon"; - // sudo launchctl list OutInfo.Status = ServiceStatus::NotInstalled; + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + + std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + + std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + if (std::filesystem::is_regular_file(PListPath)) + { + OutInfo.Status = ServiceStatus::Stopped; + + { + // Parse plist :( + IoBuffer Buffer = ReadFile(PListPath).Flatten(); + MemoryView Data = Buffer.GetView(); + std::string PList((const char*)Data.GetData(), Data.GetSize()); + ZEN_INFO("{}", PList); + } + { + std::pair Res = ExecuteProgram(std::string("launchctl list ") + DaemonName); + if (Res.first == 0 && !Res.second.empty()) + { +// bool IsParsingArguments = false; + ForEachStrTok(Res.second, '\n', [&](std::string_view Line){ +// if (IsParsingArguments) +// { +// if (Line.find(");") == Line.length() - 2) +// { +// IsParsingArguments = false; +// return true; +// } +// std::string_view::size_type ProgramArgumentsEnd = Line.find_last_of('\"'); +// std::string_view::size_type ProgramArgumentsStart = Line.find('\"') + 1; +// std::string_view ProgramArgumentsString = Line.substr(ProgramArgumentsStart, ProgramArgumentsEnd - ProgramArgumentsStart); +// if (ProgramArgumentsString != OutInfo.Spec.ExecutablePath) +// { +// if (!OutInfo.Spec.CommandLineOptions.empty()) +// { +// OutInfo.Spec.CommandLineOptions += " "; +// } +// OutInfo.Spec.CommandLineOptions += ProgramArgumentsString; +// } +// return true; +// } + if (Line.find("\"PID\"") != std::string_view::npos) + { + std::string_view::size_type PidStart = Line.find('='); + std::string_view::size_type PidEnd = Line.find(';'); + std::string_view PidString = Line.substr(PidStart+2, PidEnd - (PidStart + 2)); + if (ParseInt(PidString).has_value()) + { + OutInfo.Status = ServiceStatus::Running; + } + return false; + } +// if (Line.find("\"Program\"") != std::string_view::npos) +// { +// std::string_view::size_type ProgramEnd = Line.find_last_of('\"'); +// std::string_view::size_type ProgramStart = Line.find_last_of('\"', ProgramEnd - 1) + 1; +// std::string_view ProgramString = Line.substr(ProgramStart, ProgramEnd - ProgramStart); +// OutInfo.Spec.ExecutablePath = ProgramString; +// return true; +// } +// if (Line.find("\"ProgramArguments\"") != std::string_view::npos) +// { +// IsParsingArguments = true; +// return true; +// } + return true; + }); + // Parse installed info + } + } +#if 0 + { + std::pair Res = ExecuteProgram("launchctl list"); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + std::string ZenServerLine; + ForEachStrTok(Res.second, '\n', [&](std::string_view Line){ + if (Line.find(DaemonName) != std::string_view::npos) + { + ZenServerLine = Line; + } + return true; + }); + if (!ZenServerLine.empty()) + { + std::vector Parts; + ForEachStrTok(ZenServerLine, '\t', [&](std::string_view Line){ + Parts.push_back(Line); + return true; + }); + if (Parts.size() == 3) + { + if (Parts[0] != "-" && ParseInt(Parts[0]).has_value()) + { + OutInfo.Status = ServiceStatus::Running; + } + } + } + } +#endif // 0 + } return {}; } @@ -700,18 +907,36 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) std::error_code StartService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName, Level); - ZEN_NOT_IMPLEMENTED("StartService"); - // sudo launchctl bootstrap system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist + ZEN_UNUSED(ServiceName); + + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + + std::pair Res = ExecuteProgram(std::string("launchctl bootstrap system ") + PListPath.string()); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + return {}; } std::error_code StopService(std::string_view ServiceName) { - ZEN_UNUSED(ServiceName, Level); - ZEN_NOT_IMPLEMENTED("StopService"); - // sudo launchctl bootout system /Library/LaunchDaemon/com.epicgames.unreal.ZenServer.plist + ZEN_UNUSED(ServiceName); + + std::string DaemonName = fmt::format("com.epicgames.unreal.{}", ServiceName); + std::filesystem::path PListFolder = "/Library/LaunchDaemon"; + std::filesystem::path PListPath = PListFolder / (DaemonName + ".plist"); + + std::pair Res = ExecuteProgram(std::string("launchctl bootout system ") + PListPath.string()); + if (Res.first != 0) + { + return MakeErrorCode(Res.first); + } + return {}; } -- cgit v1.2.3