aboutsummaryrefslogtreecommitdiff
path: root/src/zenutil/service.cpp
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2025-01-15 09:30:12 +0100
committerDan Engelbrecht <[email protected]>2025-01-15 09:30:12 +0100
commit531c59032bbc46bc1f7284859fa8ff8c8b5ede61 (patch)
treec06583ef275904824a9eeae42951fa6a48ebae20 /src/zenutil/service.cpp
parentclang format (diff)
downloadzen-531c59032bbc46bc1f7284859fa8ff8c8b5ede61.tar.xz
zen-531c59032bbc46bc1f7284859fa8ff8c8b5ede61.zip
systemd unit file, incomplete
Diffstat (limited to 'src/zenutil/service.cpp')
-rw-r--r--src/zenutil/service.cpp250
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 {};
}