mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-08 23:59:12 +08:00
[fix] repair Linux autostart and apply daemon settings immediately, refs #147
This commit is contained in:
@@ -37,9 +37,11 @@
|
||||
|
||||
#ifdef __linux__
|
||||
#include "SimpleIni.h"
|
||||
#include "platform/linux/autostart.h"
|
||||
#include "platform/linux/headless/headless_console.h"
|
||||
#include "platform/linux/headless/headless_session.h"
|
||||
#include "platform/linux/privacy/privacy_guard.h"
|
||||
#include "platform/linux/runtime_daemon.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -365,6 +367,13 @@ static int RunApplication(
|
||||
std::string cache_path = path_manager->GetCachePath().string();
|
||||
crossdesk::ConfigCenter config_center(cache_path + "/config.ini");
|
||||
enable_daemon = config_center.IsEnableDaemon();
|
||||
#ifdef __linux__
|
||||
if (config_center.IsEnableAutostart() &&
|
||||
!crossdesk::platform::MigrateLegacyAutostart("CrossDesk")) {
|
||||
crossdesk::get_logger()->warn(
|
||||
"Could not migrate the legacy Linux autostart entry");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
@@ -373,7 +382,20 @@ static int RunApplication(
|
||||
// session instead.
|
||||
if (std::getenv("CROSSDESK_HEADLESS_ACTIVE") ||
|
||||
crossdesk::HeadlessConsole::Instance().active()) enable_daemon = false;
|
||||
#endif
|
||||
|
||||
auto& runtime_daemon = crossdesk::platform::LinuxRuntimeDaemon::Instance();
|
||||
runtime_daemon.Initialize(
|
||||
argc, argv, !std::getenv("CROSSDESK_HEADLESS_ACTIVE") &&
|
||||
!crossdesk::HeadlessConsole::Instance().active());
|
||||
std::string daemon_error;
|
||||
if (!runtime_daemon.SetEnabled(enable_daemon, daemon_error)) {
|
||||
crossdesk::get_logger()->warn("Could not start daemon: {}", daemon_error);
|
||||
}
|
||||
crossdesk::Render render;
|
||||
const int result = render.Run();
|
||||
runtime_daemon.Shutdown(result != 0);
|
||||
return result;
|
||||
#else
|
||||
|
||||
if (enable_daemon) {
|
||||
// The launcher lease stays in main for the supervisor's entire lifetime.
|
||||
@@ -406,6 +428,7 @@ static int RunApplication(
|
||||
// run without daemon: direct execution
|
||||
crossdesk::Render render;
|
||||
return render.Run();
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
@@ -421,6 +444,10 @@ int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
// Run before GUI, configuration, logging or display initialization.
|
||||
if (argc >= 2 &&
|
||||
std::strcmp(argv[1], crossdesk::platform::kLinuxDaemonArgument) == 0) {
|
||||
return crossdesk::platform::RunLinuxRuntimeDaemon(argc, argv);
|
||||
}
|
||||
if (argc == 2 &&
|
||||
std::strcmp(argv[1], crossdesk::kLinuxPrivacyGuardArgument) == 0) {
|
||||
return crossdesk::RunLinuxPrivacyGuard();
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include "rd_log.h"
|
||||
#include <minirtc.h>
|
||||
|
||||
#if defined(__linux__) && !defined(__OHOS__)
|
||||
#include "platform/linux/autostart.h"
|
||||
#include "platform/linux/runtime_daemon.h"
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
struct ConfigCenter::IniStorage : CSimpleIniA {};
|
||||
@@ -386,6 +391,14 @@ int ConfigCenter::SetSelfHosted(bool enabled) {
|
||||
|
||||
int ConfigCenter::SetAutostart(bool enabled) {
|
||||
const bool previous = IsAutostartEnabled("CrossDesk");
|
||||
#if defined(__linux__) && !defined(__OHOS__)
|
||||
// Repair old deb entries even when the saved boolean is unchanged, keeping
|
||||
// desktop-managed disabled flags and custom commands intact.
|
||||
if (enabled && previous && !platform::MigrateLegacyAutostart("CrossDesk")) {
|
||||
LOG_ERROR("Failed to migrate Linux autostart");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
// Refresh the executable path and remove legacy registrations even when the
|
||||
// boolean state is unchanged. In particular, a Run entry no longer counts
|
||||
// as enabled for an elevated Windows build, but still needs deleting.
|
||||
@@ -411,8 +424,23 @@ int ConfigCenter::SetAutostart(bool enabled) {
|
||||
}
|
||||
|
||||
int ConfigCenter::SetDaemon(bool enabled) {
|
||||
#if defined(__linux__) && !defined(__OHOS__)
|
||||
const bool previous = enable_daemon_;
|
||||
#endif
|
||||
if (StoreValues({{"enable_daemon", enabled ? "true" : "false"}}) != 0) return -1;
|
||||
enable_daemon_ = enabled;
|
||||
#if defined(__linux__) && !defined(__OHOS__)
|
||||
std::string error;
|
||||
if (!platform::LinuxRuntimeDaemon::Instance().SetEnabled(enabled, error)) {
|
||||
LOG_ERROR("Cannot apply daemon setting: {}", error);
|
||||
if (StoreValues({{"enable_daemon", previous ? "true" : "false"}}) == 0) {
|
||||
enable_daemon_ = previous;
|
||||
} else {
|
||||
LOG_ERROR("Cannot restore daemon preference after startup failure");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4985,7 +4985,13 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
||||
#if !defined(__OHOS__)
|
||||
config_center_->SetAutostart(enable_autostart_);
|
||||
#endif
|
||||
config_center_->SetDaemon(enable_daemon_);
|
||||
if (config_center_->SetDaemon(enable_daemon_) != 0) {
|
||||
enable_daemon_ = config_center_->IsEnableDaemon();
|
||||
main->set_daemon_enabled(enable_daemon_);
|
||||
main->set_settings_save_error(UiText(
|
||||
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||
return false;
|
||||
}
|
||||
if (config_center_->SetSaveRemotePreviews(main->get_save_remote_previews()) != 0) {
|
||||
LOG_ERROR("Failed to save remote preview preference");
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
#include "platform/autostart_backend.h"
|
||||
#include "platform/linux/autostart.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
namespace {
|
||||
|
||||
constexpr const char* kInstalledExecutable = "/usr/lib/crossdesk/crossdesk-bin";
|
||||
constexpr const char* kInstalledLauncher = "/usr/bin/crossdesk";
|
||||
|
||||
std::filesystem::path AutostartPath(const std::string& app_name) {
|
||||
const char* config = std::getenv("XDG_CONFIG_HOME");
|
||||
if (config && *config) return std::filesystem::path(config) / "autostart" / (app_name + ".desktop");
|
||||
@@ -19,13 +25,39 @@ std::filesystem::path AutostartPath(const std::string& app_name) {
|
||||
(app_name + ".desktop");
|
||||
}
|
||||
|
||||
bool WriteDesktopEntry(const std::filesystem::path& path,
|
||||
const std::string& contents) {
|
||||
// In particular, a failed migration must leave the previous entry intact.
|
||||
std::string temporary = path.string() + ".XXXXXX";
|
||||
const int fd = mkstemp(temporary.data());
|
||||
if (fd < 0) return false;
|
||||
std::FILE* file = fdopen(fd, "w");
|
||||
if (!file) {
|
||||
close(fd);
|
||||
std::remove(temporary.c_str());
|
||||
return false;
|
||||
}
|
||||
const bool written =
|
||||
std::fwrite(contents.data(), 1, contents.size(), file) == contents.size();
|
||||
const bool closed = std::fclose(file) == 0;
|
||||
if (written && closed && std::rename(temporary.c_str(), path.c_str()) == 0)
|
||||
return true;
|
||||
std::remove(temporary.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string GetAutostartExecutablePath() {
|
||||
char path[PATH_MAX] = {};
|
||||
const ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||||
return count > 0 ? std::string(path, static_cast<size_t>(count))
|
||||
: std::string();
|
||||
if (count <= 0 || count >= static_cast<ssize_t>(sizeof(path) - 1)) return {};
|
||||
const std::string executable(path, static_cast<size_t>(count));
|
||||
// The deb launcher sets LD_LIBRARY_PATH for the private Slint runtime.
|
||||
// /proc/self/exe resolves to its exec() target and loses that setup.
|
||||
if (executable == kInstalledExecutable && access(kInstalledLauncher, X_OK) == 0)
|
||||
return kInstalledLauncher;
|
||||
return executable;
|
||||
}
|
||||
|
||||
bool EnableAutostart(const std::string& app_name,
|
||||
@@ -37,19 +69,53 @@ bool EnableAutostart(const std::string& app_name,
|
||||
std::filesystem::create_directories(path.parent_path(), error);
|
||||
if (error) return false;
|
||||
|
||||
std::ofstream file(path);
|
||||
return WriteDesktopEntry(path,
|
||||
"[Desktop Entry]\n"
|
||||
"Type=Application\n"
|
||||
"Exec=" + executable_path + "\n"
|
||||
"Hidden=false\n"
|
||||
"NoDisplay=false\n"
|
||||
"X-GNOME-Autostart-enabled=true\n"
|
||||
"Terminal=false\n"
|
||||
"StartupNotify=false\n"
|
||||
"Name=" + app_name + "\n");
|
||||
}
|
||||
|
||||
bool MigrateLegacyAutostart(const std::string& app_name) {
|
||||
// Development/standalone copies must not take over an installed entry.
|
||||
if (GetAutostartExecutablePath() != kInstalledLauncher) return true;
|
||||
const std::filesystem::path path = AutostartPath(app_name);
|
||||
if (path.empty()) return false;
|
||||
std::error_code error;
|
||||
const bool exists = std::filesystem::exists(path, error);
|
||||
if (error) return false;
|
||||
if (!exists) return true; // Do not recreate entries removed by the user.
|
||||
if (!std::filesystem::is_regular_file(path, error) || error) return false;
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) return false;
|
||||
file << "[Desktop Entry]\n"
|
||||
<< "Type=Application\n"
|
||||
<< "Exec=" << executable_path << "\n"
|
||||
<< "Hidden=false\n"
|
||||
<< "NoDisplay=false\n"
|
||||
<< "X-GNOME-Autostart-enabled=true\n"
|
||||
<< "Terminal=false\n"
|
||||
<< "StartupNotify=false\n"
|
||||
<< "Name=" << app_name << "\n";
|
||||
std::string contents((std::istreambuf_iterator<char>(file)), {});
|
||||
if (file.bad()) return false;
|
||||
file.close();
|
||||
return file.good();
|
||||
|
||||
bool desktop_entry = false;
|
||||
for (size_t start = 0; start < contents.size();) {
|
||||
const size_t newline = contents.find('\n', start);
|
||||
const size_t end = newline == std::string::npos ? contents.size() : newline;
|
||||
size_t line_end = end;
|
||||
if (line_end > start && contents[line_end - 1] == '\r') --line_end;
|
||||
const std::string line = contents.substr(start, line_end - start);
|
||||
if (!line.empty() && line.front() == '[')
|
||||
desktop_entry = line == "[Desktop Entry]";
|
||||
if (desktop_entry && line == std::string("Exec=") + kInstalledExecutable) {
|
||||
// Preserve Hidden, desktop restrictions and all other user settings.
|
||||
contents.replace(start, line_end - start,
|
||||
std::string("Exec=") + kInstalledLauncher);
|
||||
return WriteDesktopEntry(path, contents);
|
||||
}
|
||||
start = end == contents.size() ? end : end + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisableAutostart(const std::string& app_name) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef CROSSDESK_LINUX_AUTOSTART_H_
|
||||
#define CROSSDESK_LINUX_AUTOSTART_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
|
||||
// Repair the old deb entry at normal startup without enabling missing entries
|
||||
// or overwriting desktop-managed settings and custom commands.
|
||||
bool MigrateLegacyAutostart(const std::string& app_name);
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,320 @@
|
||||
#include "platform/linux/runtime_daemon.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
extern char** environ;
|
||||
|
||||
namespace crossdesk::platform {
|
||||
namespace {
|
||||
|
||||
constexpr int kControlSocket = 3;
|
||||
constexpr int kStartupTimeoutMs = 3000;
|
||||
|
||||
// A lost GUI socket can mean either a crash or desktop logout. Remember the
|
||||
// owner's login session before it disappears, and never carry a restart into
|
||||
// another login that happens to reuse the same DISPLAY (notably under XRDP).
|
||||
class LoginSession {
|
||||
public:
|
||||
explicit LoginSession(pid_t owner) {
|
||||
library_ = dlopen("libsystemd.so.0", RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!library_) return; // Non-logind desktops retain crash monitoring.
|
||||
const auto pid_session = reinterpret_cast<int (*)(pid_t, char**)>(
|
||||
dlsym(library_, "sd_pid_get_session"));
|
||||
const auto uid_display = reinterpret_cast<int (*)(uid_t, char**)>(
|
||||
dlsym(library_, "sd_uid_get_display"));
|
||||
state_ = reinterpret_cast<int (*)(const char*, char**)>(
|
||||
dlsym(library_, "sd_session_get_state"));
|
||||
if (!state_) return;
|
||||
char* id = nullptr;
|
||||
if (pid_session && pid_session(owner, &id) >= 0 && id) {
|
||||
session_ = id;
|
||||
}
|
||||
std::free(id);
|
||||
// GNOME can start applications in its user manager rather than directly
|
||||
// in the session scope. Use the supplied session or the user's graphical
|
||||
// session in that case; a process's actual scope always takes precedence.
|
||||
if (session_.empty()) {
|
||||
if (const char* inherited = std::getenv("XDG_SESSION_ID"))
|
||||
session_ = inherited;
|
||||
}
|
||||
if (session_.empty() && uid_display) {
|
||||
id = nullptr;
|
||||
if (uid_display(geteuid(), &id) >= 0 && id) session_ = id;
|
||||
std::free(id);
|
||||
}
|
||||
}
|
||||
|
||||
~LoginSession() {
|
||||
if (library_) dlclose(library_);
|
||||
}
|
||||
|
||||
bool Alive() const {
|
||||
if (!state_ || session_.empty()) return true;
|
||||
char* state = nullptr;
|
||||
const int result = state_(session_.c_str(), &state);
|
||||
// "online" includes a locked/background session and must keep monitoring.
|
||||
// A removed or closing session must never be restarted.
|
||||
const bool alive = result >= 0 && state && std::strcmp(state, "closing") != 0;
|
||||
std::free(state);
|
||||
return alive;
|
||||
}
|
||||
|
||||
private:
|
||||
void* library_ = nullptr;
|
||||
int (*state_)(const char*, char**) = nullptr;
|
||||
std::string session_;
|
||||
};
|
||||
|
||||
bool Send(int socket, char command) {
|
||||
ssize_t count;
|
||||
do {
|
||||
count = send(socket, &command, 1, MSG_NOSIGNAL);
|
||||
} while (count < 0 && errno == EINTR);
|
||||
return count == 1;
|
||||
}
|
||||
|
||||
bool Receive(int socket, char expected) {
|
||||
pollfd fd{socket, POLLIN, 0};
|
||||
int result;
|
||||
do {
|
||||
result = poll(&fd, 1, kStartupTimeoutMs);
|
||||
} while (result < 0 && errno == EINTR);
|
||||
if (result <= 0) return false;
|
||||
char reply = 0;
|
||||
return recv(socket, &reply, 1, 0) == 1 && reply == expected;
|
||||
}
|
||||
|
||||
void Reap(int child) {
|
||||
if (child < 0) return;
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::seconds(2);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
const auto result = waitpid(child, nullptr, WNOHANG);
|
||||
if (result == child || (result < 0 && errno != EINTR)) return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
// This is an unreaped direct child, so its PID cannot have been reused.
|
||||
kill(child, SIGKILL);
|
||||
while (waitpid(child, nullptr, 0) < 0 && errno == EINTR) {}
|
||||
}
|
||||
|
||||
bool CloseInheritedDescriptors() {
|
||||
// Libraries may own descriptors without CLOEXEC. Keeping those alive in the
|
||||
// watcher could retain network ports or locks after the GUI crashes.
|
||||
DIR* directory = opendir("/proc/self/fd");
|
||||
if (!directory) return false;
|
||||
while (const auto* entry = readdir(directory)) {
|
||||
char* end = nullptr;
|
||||
const long fd = std::strtol(entry->d_name, &end, 10);
|
||||
if (*end == '\0' && fd > kControlSocket && fd != dirfd(directory))
|
||||
close(static_cast<int>(fd));
|
||||
}
|
||||
closedir(directory);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LinuxRuntimeDaemon& LinuxRuntimeDaemon::Instance() {
|
||||
static LinuxRuntimeDaemon daemon;
|
||||
return daemon;
|
||||
}
|
||||
|
||||
LinuxRuntimeDaemon::~LinuxRuntimeDaemon() { Shutdown(); }
|
||||
|
||||
void LinuxRuntimeDaemon::Initialize(int argc, char* argv[], bool allowed) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
Stop();
|
||||
allowed_ = allowed;
|
||||
arguments_.clear();
|
||||
char path[PATH_MAX]{};
|
||||
const ssize_t count = readlink("/proc/self/exe", path, sizeof(path));
|
||||
if (count <= 0 || count >= static_cast<ssize_t>(sizeof(path))) return;
|
||||
arguments_.emplace_back(path, static_cast<size_t>(count));
|
||||
arguments_.emplace_back(kLinuxDaemonArgument);
|
||||
bool no_headless = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
arguments_.emplace_back(argv[i]);
|
||||
no_headless |= arguments_.back() == "--no-headless";
|
||||
}
|
||||
// A desktop watchdog must never create a headless display after logout.
|
||||
if (!no_headless) arguments_.emplace_back("--no-headless");
|
||||
}
|
||||
|
||||
bool LinuxRuntimeDaemon::SetEnabled(bool enabled, std::string& error) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
error.clear();
|
||||
// Configuration-only callers and the headless supervisor do not own a GUI
|
||||
// watchdog. Their preference is still saved for the next desktop launch.
|
||||
if (!allowed_ || !enabled) {
|
||||
Stop();
|
||||
return true;
|
||||
}
|
||||
if (child_ >= 0) {
|
||||
pollfd fd{socket_, POLLIN, 0};
|
||||
if (poll(&fd, 1, 0) == 0) return true;
|
||||
Stop();
|
||||
}
|
||||
if (arguments_.empty()) {
|
||||
error = "Cannot resolve the executable for the daemon";
|
||||
return false;
|
||||
}
|
||||
int sockets[2];
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
|
||||
error = std::strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
posix_spawn_file_actions_t actions;
|
||||
posix_spawnattr_t attributes;
|
||||
int result = posix_spawn_file_actions_init(&actions);
|
||||
const bool actions_ready = result == 0;
|
||||
bool attributes_ready = false;
|
||||
if (result == 0) {
|
||||
result = posix_spawnattr_init(&attributes);
|
||||
attributes_ready = result == 0;
|
||||
}
|
||||
if (result == 0)
|
||||
result = posix_spawn_file_actions_adddup2(&actions, sockets[1], kControlSocket);
|
||||
if (result == 0 && sockets[0] != kControlSocket)
|
||||
result = posix_spawn_file_actions_addclose(&actions, sockets[0]);
|
||||
if (result == 0 && sockets[1] != kControlSocket)
|
||||
result = posix_spawn_file_actions_addclose(&actions, sockets[1]);
|
||||
sigset_t empty, defaults;
|
||||
sigemptyset(&empty);
|
||||
sigfillset(&defaults);
|
||||
if (result == 0) result = posix_spawnattr_setsigmask(&attributes, &empty);
|
||||
if (result == 0) result = posix_spawnattr_setsigdefault(&attributes, &defaults);
|
||||
if (result == 0)
|
||||
result = posix_spawnattr_setflags(
|
||||
&attributes, POSIX_SPAWN_SETSID | POSIX_SPAWN_SETSIGMASK |
|
||||
POSIX_SPAWN_SETSIGDEF);
|
||||
std::vector<char*> args;
|
||||
for (auto& argument : arguments_) args.push_back(argument.data());
|
||||
args.push_back(nullptr);
|
||||
pid_t child = -1;
|
||||
if (result == 0)
|
||||
result = posix_spawn(&child, "/proc/self/exe", &actions, &attributes,
|
||||
args.data(), environ);
|
||||
if (attributes_ready) posix_spawnattr_destroy(&attributes);
|
||||
if (actions_ready) posix_spawn_file_actions_destroy(&actions);
|
||||
close(sockets[1]);
|
||||
if (result != 0) {
|
||||
close(sockets[0]);
|
||||
error = "Cannot start daemon: " + std::string(std::strerror(result));
|
||||
return false;
|
||||
}
|
||||
socket_ = sockets[0];
|
||||
child_ = child;
|
||||
// Do not report an enabled setting until the helper is ready and armed.
|
||||
if (!Receive(socket_, 'R') || !Send(socket_, 'A') || !Receive(socket_, 'A')) {
|
||||
error = "Daemon did not acknowledge startup";
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LinuxRuntimeDaemon::Stop() {
|
||||
if (socket_ >= 0) {
|
||||
Send(socket_, 'S');
|
||||
close(socket_);
|
||||
socket_ = -1;
|
||||
}
|
||||
Reap(child_);
|
||||
child_ = -1;
|
||||
}
|
||||
|
||||
void LinuxRuntimeDaemon::Shutdown(bool restart) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!restart) {
|
||||
Stop();
|
||||
} else {
|
||||
if (socket_ >= 0) close(socket_);
|
||||
socket_ = -1;
|
||||
child_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int RunLinuxRuntimeDaemon(int argc, char* argv[]) {
|
||||
int type = 0;
|
||||
socklen_t size = sizeof(type);
|
||||
ucred peer{};
|
||||
socklen_t peer_size = sizeof(peer);
|
||||
if (argc < 2 || argv[0][0] != '/' ||
|
||||
getsockopt(kControlSocket, SOL_SOCKET, SO_TYPE, &type, &size) != 0 ||
|
||||
type != SOCK_SEQPACKET ||
|
||||
getsockopt(kControlSocket, SOL_SOCKET, SO_PEERCRED, &peer, &peer_size) != 0 ||
|
||||
peer.uid != geteuid() || !CloseInheritedDescriptors()) return 1;
|
||||
LoginSession session(peer.pid);
|
||||
if (!session.Alive()) return 1;
|
||||
if (fcntl(kControlSocket, F_SETFD, FD_CLOEXEC) != 0 ||
|
||||
!Send(kControlSocket, 'R')) return 1;
|
||||
|
||||
// Track the original process, not a potentially reused PID. A failed render
|
||||
// can close the socket before its destructors have released instance locks.
|
||||
int owner = -1;
|
||||
#ifdef SYS_pidfd_open
|
||||
owner = static_cast<int>(syscall(SYS_pidfd_open, peer.pid, 0));
|
||||
#endif
|
||||
|
||||
bool armed = false;
|
||||
for (;;) {
|
||||
if (!session.Alive()) return 0;
|
||||
pollfd control{kControlSocket, POLLIN, 0};
|
||||
const int ready = poll(&control, 1, 1000);
|
||||
if (ready < 0 && errno == EINTR) continue;
|
||||
if (ready < 0) return 1;
|
||||
if (ready == 0) continue;
|
||||
char command = 0;
|
||||
const auto count = recv(kControlSocket, &command, 1, 0);
|
||||
if (count < 0 && errno == EINTR) continue;
|
||||
if (count == 0 || (count < 0 && errno == ECONNRESET)) break;
|
||||
if (count < 0) return 1;
|
||||
if (command == 'S') return 0;
|
||||
if (command != 'A' || armed) return 1;
|
||||
armed = true;
|
||||
// Even if the GUI disappears before receiving this acknowledgement, keep
|
||||
// reading: a queued stop disarms us; otherwise the lost owner is restarted.
|
||||
Send(kControlSocket, 'A');
|
||||
}
|
||||
close(kControlSocket);
|
||||
if (!armed) return 0;
|
||||
if (owner >= 0) {
|
||||
pollfd fd{owner, POLLIN, 0};
|
||||
int result;
|
||||
do {
|
||||
if (!session.Alive()) return 0;
|
||||
result = poll(&fd, 1, 1000);
|
||||
} while (result == 0 || (result < 0 && errno == EINTR));
|
||||
close(owner);
|
||||
if (result < 0) return 1;
|
||||
}
|
||||
// Let process teardown release its single-instance leases, and rate-limit
|
||||
// repeated startup failures. The new GUI acquires the normal leases itself.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (!session.Alive()) return 0;
|
||||
std::vector<char*> arguments{argv[0]};
|
||||
for (int i = 2; i < argc; ++i) arguments.push_back(argv[i]);
|
||||
arguments.push_back(nullptr);
|
||||
execv(argv[0], arguments.data());
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef CROSSDESK_PLATFORM_LINUX_RUNTIME_DAEMON_H_
|
||||
#define CROSSDESK_PLATFORM_LINUX_RUNTIME_DAEMON_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
|
||||
inline constexpr char kLinuxDaemonArgument[] = "--linux-daemon";
|
||||
|
||||
// A separate process watches this GUI over a private socket. It can be attached
|
||||
// after the GUI has started, without forking its threads or restarting sessions.
|
||||
class LinuxRuntimeDaemon {
|
||||
public:
|
||||
static LinuxRuntimeDaemon& Instance();
|
||||
~LinuxRuntimeDaemon();
|
||||
|
||||
void Initialize(int argc, char* argv[], bool allowed);
|
||||
bool SetEnabled(bool enabled, std::string& error);
|
||||
// Normal exit disarms the watcher. An unsuccessful application exit lets it
|
||||
// restart, just like an unexpected loss of the control socket.
|
||||
void Shutdown(bool restart = false);
|
||||
|
||||
private:
|
||||
LinuxRuntimeDaemon() = default;
|
||||
void Stop();
|
||||
std::mutex mutex_;
|
||||
bool allowed_ = false;
|
||||
std::vector<std::string> arguments_;
|
||||
int socket_ = -1;
|
||||
int child_ = -1;
|
||||
};
|
||||
|
||||
// Internal entry point, dispatched before logging, display or instance locks.
|
||||
int RunLinuxRuntimeDaemon(int argc, char* argv[]);
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
|
||||
#endif
|
||||
@@ -509,11 +509,22 @@ function setup_targets()
|
||||
target("config_center")
|
||||
set_kind("object")
|
||||
add_deps("rd_log", "autostart")
|
||||
if is_os("linux") then
|
||||
add_deps("linux_runtime_daemon")
|
||||
end
|
||||
add_files("apps/desktop/src/config_center/*.cpp")
|
||||
add_includedirs("apps/desktop/src", "apps/desktop/src/config_center",
|
||||
"deps/submodules/minirtc/src/api",
|
||||
{public = true})
|
||||
|
||||
if is_os("linux") then
|
||||
target("linux_runtime_daemon")
|
||||
set_kind("static")
|
||||
add_includedirs("apps/desktop/src", {public = true})
|
||||
add_files("apps/desktop/src/platform/linux/runtime_daemon.cpp")
|
||||
add_syslinks("dl", {public = true})
|
||||
end
|
||||
|
||||
target("assets")
|
||||
set_kind("headeronly")
|
||||
add_includedirs("apps/desktop/src/gui/assets/localization",
|
||||
|
||||
Reference in New Issue
Block a user