mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-11 02:06:38 +08:00
[fix] apply macOS daemon settings immediately
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
#include "daemon.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "platform/daemon_backend.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kRestartDelayMs = 1000;
|
||||
|
||||
} // namespace
|
||||
|
||||
Daemon::Daemon(const std::string& name) : name_(name), running_(false) {}
|
||||
|
||||
void Daemon::stop() {
|
||||
running_.store(false);
|
||||
crossdesk::platform::RequestDaemonStop();
|
||||
}
|
||||
|
||||
bool Daemon::isRunning() const {
|
||||
return running_.load() && !crossdesk::platform::DaemonStopRequested();
|
||||
}
|
||||
|
||||
bool Daemon::start(MainLoopFunc loop) {
|
||||
crossdesk::platform::ResetDaemonStop();
|
||||
if (!crossdesk::platform::PrepareDaemon()) return false;
|
||||
running_.store(true);
|
||||
return runWithRestart(std::move(loop));
|
||||
}
|
||||
|
||||
bool Daemon::runWithRestart(MainLoopFunc loop) {
|
||||
int restart_count = 0;
|
||||
const std::string executable_path =
|
||||
crossdesk::platform::GetDaemonExecutablePath();
|
||||
if (executable_path.empty()) {
|
||||
std::cerr
|
||||
<< "Failed to get executable path, falling back to direct execution"
|
||||
<< std::endl;
|
||||
while (isRunning()) {
|
||||
try {
|
||||
loop();
|
||||
break;
|
||||
} catch (...) {
|
||||
++restart_count;
|
||||
std::cerr << "Exception caught, restarting... (attempt "
|
||||
<< restart_count << ")" << std::endl;
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(kRestartDelayMs));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
while (isRunning()) {
|
||||
const crossdesk::platform::DaemonChildResult result =
|
||||
crossdesk::platform::RunDaemonChild(
|
||||
executable_path, [this] { return isRunning(); });
|
||||
if (!isRunning() || result.normal_exit) break;
|
||||
|
||||
++restart_count;
|
||||
if (!result.started) {
|
||||
std::cerr << "Failed to create child process";
|
||||
} else if (result.terminated_by_signal) {
|
||||
std::cerr << "Child process crashed with signal " << result.exit_code;
|
||||
} else {
|
||||
std::cerr << "Child process exited with code " << result.exit_code;
|
||||
}
|
||||
std::cerr << ", restarting... (attempt " << restart_count << ")"
|
||||
<< std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-11-19
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DAEMON_H_
|
||||
#define _DAEMON_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
class Daemon {
|
||||
public:
|
||||
using MainLoopFunc = std::function<void()>;
|
||||
|
||||
Daemon(const std::string& name);
|
||||
|
||||
bool start(MainLoopFunc loop);
|
||||
|
||||
void stop();
|
||||
|
||||
bool isRunning() const;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
bool runWithRestart(MainLoopFunc loop);
|
||||
|
||||
std::atomic<bool> running_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -28,7 +28,6 @@
|
||||
#endif
|
||||
|
||||
#include "config_center.h"
|
||||
#include "daemon.h"
|
||||
#include "path_manager.h"
|
||||
#include "platform/instance_activation.h"
|
||||
#include "platform/single_instance.h"
|
||||
@@ -47,6 +46,10 @@
|
||||
#include "platform/linux/runtime_daemon.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "platform/macos/runtime_daemon.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace {
|
||||
|
||||
@@ -275,9 +278,7 @@ static int ExitForExistingInstance(
|
||||
return result == crossdesk::platform::InstanceResult::kAlreadyRunning ? 0 : 1;
|
||||
}
|
||||
|
||||
static int RunApplication(
|
||||
int argc, char* argv[],
|
||||
crossdesk::platform::SingleInstanceGuard& client_instance) {
|
||||
static int RunApplication(int argc, char* argv[]) {
|
||||
// The instance guards are held before any configuration or logger is opened.
|
||||
auto path_manager = std::make_unique<crossdesk::PathManager>("CrossDesk");
|
||||
crossdesk::InitLogger(path_manager->GetLogPath().string());
|
||||
@@ -289,7 +290,7 @@ static int RunApplication(
|
||||
#endif
|
||||
|
||||
if (IsDaemonChild(argc, argv)) {
|
||||
// child process: run render directly
|
||||
// Compatibility with a supervisor from an older, still-running build.
|
||||
crossdesk::Render render;
|
||||
return render.Run();
|
||||
}
|
||||
@@ -327,62 +328,34 @@ static int RunApplication(
|
||||
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;
|
||||
#elif defined(_WIN32)
|
||||
auto& runtime_daemon = crossdesk::platform::WindowsRuntimeDaemon::Instance();
|
||||
runtime_daemon.Initialize();
|
||||
#elif defined(__APPLE__)
|
||||
auto& runtime_daemon = crossdesk::platform::MacRuntimeDaemon::Instance();
|
||||
runtime_daemon.Initialize(argc, argv);
|
||||
#endif
|
||||
#if defined(__linux__) || defined(_WIN32) || defined(__APPLE__)
|
||||
std::string daemon_error;
|
||||
if (!runtime_daemon.SetEnabled(enable_daemon, daemon_error)) {
|
||||
crossdesk::get_logger()->warn("Could not start daemon: {}", daemon_error);
|
||||
}
|
||||
#endif
|
||||
crossdesk::Render render;
|
||||
const int result = render.Run();
|
||||
#if defined(__linux__) || defined(_WIN32) || defined(__APPLE__)
|
||||
runtime_daemon.Shutdown(result != 0);
|
||||
return result;
|
||||
#else
|
||||
|
||||
if (enable_daemon) {
|
||||
// The launcher lease stays in main for the supervisor's entire lifetime.
|
||||
// Each --child must acquire its own client lease, so it is neither blocked
|
||||
// by its supervisor nor able to bypass the single-client restriction.
|
||||
client_instance.Release();
|
||||
// start daemon with restart monitoring
|
||||
Daemon daemon("CrossDesk");
|
||||
|
||||
// define main loop function: run render and stop daemon on normal exit
|
||||
Daemon::MainLoopFunc main_loop = [&daemon, &client_instance]() {
|
||||
// Executable discovery can fail and make the supervisor run the GUI in
|
||||
// process. This fallback must retain the same client ownership rule.
|
||||
if (AcquireInstance(client_instance,
|
||||
crossdesk::platform::InstanceRole::kClient) !=
|
||||
crossdesk::platform::InstanceResult::kAcquired) {
|
||||
daemon.stop();
|
||||
return;
|
||||
}
|
||||
crossdesk::Render render;
|
||||
render.Run();
|
||||
daemon.stop();
|
||||
};
|
||||
|
||||
// start daemon and return result
|
||||
bool success = daemon.start(main_loop);
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
// run without daemon: direct execution
|
||||
crossdesk::Render render;
|
||||
return render.Run();
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef __APPLE__
|
||||
if (argc >= 2 &&
|
||||
std::strcmp(argv[1], crossdesk::platform::kMacDaemonArgument) == 0) {
|
||||
return crossdesk::platform::RunMacRuntimeDaemon(argc, argv);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
if (argc >= 2 &&
|
||||
std::strcmp(argv[1], crossdesk::platform::kWindowsDaemonArgument) == 0) {
|
||||
@@ -503,7 +476,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
#ifdef __linux__
|
||||
const int exit_code = crossdesk::RunWithLinuxDisplay(
|
||||
argc, argv, [&] { return RunApplication(argc, argv, client_instance); }, [&] {
|
||||
argc, argv, [&] { return RunApplication(argc, argv); }, [&] {
|
||||
crossdesk::PathManager paths("CrossDesk");
|
||||
if (console.Enable(paths.GetLogPath(), paths.GetCachePath() / "config.ini")) return true;
|
||||
std::cerr << "无法初始化日志或控制台,启动失败。\n";
|
||||
@@ -516,6 +489,6 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
return exit_code;
|
||||
#else
|
||||
return RunApplication(argc, argv, client_instance);
|
||||
return RunApplication(argc, argv);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include "platform/windows/service/autostart_control.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "platform/macos/runtime_daemon.h"
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(__OHOS__)
|
||||
#include "platform/linux/autostart.h"
|
||||
#include "platform/linux/runtime_daemon.h"
|
||||
@@ -459,15 +463,17 @@ int ConfigCenter::SetAutostart(bool enabled) {
|
||||
}
|
||||
|
||||
int ConfigCenter::SetDaemon(bool enabled) {
|
||||
#if defined(_WIN32) || (defined(__linux__) && !defined(__OHOS__))
|
||||
#if defined(_WIN32) || defined(__APPLE__) || (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(_WIN32) || (defined(__linux__) && !defined(__OHOS__))
|
||||
#if defined(_WIN32) || defined(__APPLE__) || (defined(__linux__) && !defined(__OHOS__))
|
||||
std::string error;
|
||||
#ifdef _WIN32
|
||||
auto& daemon = platform::WindowsRuntimeDaemon::Instance();
|
||||
#elif defined(__APPLE__)
|
||||
auto& daemon = platform::MacRuntimeDaemon::Instance();
|
||||
#else
|
||||
auto& daemon = platform::LinuxRuntimeDaemon::Instance();
|
||||
#endif
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#include "platform/daemon_backend.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
|
||||
volatile std::sig_atomic_t g_daemon_stop_requested = 0;
|
||||
|
||||
void ResetDaemonStop() { g_daemon_stop_requested = 0; }
|
||||
|
||||
void RequestDaemonStop() { g_daemon_stop_requested = 1; }
|
||||
|
||||
bool DaemonStopRequested() { return g_daemon_stop_requested != 0; }
|
||||
|
||||
DaemonChildResult RunDaemonChild(const std::string& executable_path,
|
||||
const std::function<bool()>& keep_running) {
|
||||
DaemonChildResult result;
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
execl(executable_path.c_str(), executable_path.c_str(), "--child",
|
||||
nullptr);
|
||||
_exit(1);
|
||||
}
|
||||
if (child < 0) return result;
|
||||
|
||||
result.started = true;
|
||||
int status = 0;
|
||||
pid_t waited = -1;
|
||||
while (keep_running()) {
|
||||
waited = waitpid(child, &status, WNOHANG);
|
||||
if (waited == child || (waited < 0 && errno != EINTR)) break;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
}
|
||||
if (!keep_running() && waited != child) {
|
||||
kill(child, SIGTERM);
|
||||
waited = waitpid(child, &status, 0);
|
||||
}
|
||||
if (waited < 0) return result;
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
result.exit_code = WEXITSTATUS(status);
|
||||
result.normal_exit = result.exit_code == 0;
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
result.terminated_by_signal = true;
|
||||
result.exit_code = WTERMSIG(status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef CROSSDESK_PLATFORM_DAEMON_BACKEND_H_
|
||||
#define CROSSDESK_PLATFORM_DAEMON_BACKEND_H_
|
||||
|
||||
#include <csignal>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
|
||||
extern volatile std::sig_atomic_t g_daemon_stop_requested;
|
||||
|
||||
struct DaemonChildResult {
|
||||
bool started = false;
|
||||
bool normal_exit = false;
|
||||
bool terminated_by_signal = false;
|
||||
int exit_code = 0;
|
||||
};
|
||||
|
||||
void ResetDaemonStop();
|
||||
void RequestDaemonStop();
|
||||
bool DaemonStopRequested();
|
||||
bool PrepareDaemon();
|
||||
std::string GetDaemonExecutablePath();
|
||||
DaemonChildResult RunDaemonChild(const std::string& executable_path,
|
||||
const std::function<bool()>& keep_running);
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
|
||||
#endif // CROSSDESK_PLATFORM_DAEMON_BACKEND_H_
|
||||
@@ -1,58 +0,0 @@
|
||||
#include "platform/daemon_backend.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
|
||||
bool PrepareDaemon() {
|
||||
const bool from_terminal =
|
||||
isatty(STDIN_FILENO) != 0 || isatty(STDOUT_FILENO) != 0;
|
||||
pid_t process = fork();
|
||||
if (process < 0) return false;
|
||||
if (process > 0) _exit(0);
|
||||
if (setsid() < 0) return false;
|
||||
|
||||
process = fork();
|
||||
if (process < 0) return false;
|
||||
if (process > 0) _exit(0);
|
||||
|
||||
umask(0);
|
||||
if (chdir("/") != 0) {
|
||||
std::cerr << "Failed to change daemon working directory to /: "
|
||||
<< std::strerror(errno) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int null_fd = open("/dev/null", O_RDWR);
|
||||
if (null_fd >= 0) {
|
||||
dup2(null_fd, STDIN_FILENO);
|
||||
if (!from_terminal) {
|
||||
dup2(null_fd, STDOUT_FILENO);
|
||||
dup2(null_fd, STDERR_FILENO);
|
||||
}
|
||||
if (null_fd > STDERR_FILENO) close(null_fd);
|
||||
}
|
||||
|
||||
signal(SIGTERM, [](int) { g_daemon_stop_requested = 1; });
|
||||
signal(SIGINT, [](int) { g_daemon_stop_requested = 1; });
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetDaemonExecutablePath() {
|
||||
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();
|
||||
}
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "platform/daemon_backend.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
|
||||
bool PrepareDaemon() { return true; }
|
||||
|
||||
std::string GetDaemonExecutablePath() {
|
||||
char path[PATH_MAX] = {};
|
||||
uint32_t size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size) != 0) return {};
|
||||
char resolved[PATH_MAX] = {};
|
||||
return realpath(path, resolved) ? std::string(resolved) : std::string(path);
|
||||
}
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef CROSSDESK_PLATFORM_MACOS_RUNTIME_DAEMON_H_
|
||||
#define CROSSDESK_PLATFORM_MACOS_RUNTIME_DAEMON_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
|
||||
inline constexpr char kMacDaemonArgument[] = "--macos-daemon";
|
||||
|
||||
// Attach a separate crash watcher to the running GUI without restarting it or
|
||||
// forking its threads. The watcher shares the GUI's login session.
|
||||
class MacRuntimeDaemon {
|
||||
public:
|
||||
static MacRuntimeDaemon& Instance();
|
||||
~MacRuntimeDaemon();
|
||||
|
||||
void Initialize(int argc, char* argv[]);
|
||||
bool SetEnabled(bool enabled, std::string& error);
|
||||
// Normal exit disarms the watcher; a failed startup lets it restart after
|
||||
// this process has exited and released its single-instance leases.
|
||||
void Shutdown(bool restart = false);
|
||||
|
||||
private:
|
||||
MacRuntimeDaemon() = default;
|
||||
void Stop();
|
||||
std::mutex mutex_;
|
||||
bool initialized_ = false;
|
||||
std::vector<std::string> arguments_;
|
||||
int socket_ = -1;
|
||||
int child_ = -1;
|
||||
};
|
||||
|
||||
// Dispatch on the main thread before logging, GUI setup or instance locks.
|
||||
int RunMacRuntimeDaemon(int argc, char* argv[]);
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,334 @@
|
||||
#include "platform/macos/runtime_daemon.h"
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
extern char** environ;
|
||||
|
||||
namespace crossdesk::platform {
|
||||
namespace {
|
||||
|
||||
constexpr int kControlSocket = 3;
|
||||
constexpr int kStartupTimeoutMs = 3000;
|
||||
constexpr int kPollIntervalMs = 100;
|
||||
|
||||
struct Descriptor {
|
||||
int value = -1;
|
||||
~Descriptor() { if (value >= 0) close(value); }
|
||||
};
|
||||
|
||||
std::string ExecutablePath() {
|
||||
uint32_t size = 0;
|
||||
_NSGetExecutablePath(nullptr, &size);
|
||||
std::vector<char> path(size);
|
||||
if (!size || _NSGetExecutablePath(path.data(), &size) != 0) return {};
|
||||
char* resolved = realpath(path.data(), nullptr);
|
||||
if (!resolved) return {};
|
||||
const std::string result(resolved);
|
||||
std::free(resolved);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ConfigureSocket(int socket) {
|
||||
const int enabled = 1;
|
||||
return fcntl(socket, F_SETFD, FD_CLOEXEC) == 0 &&
|
||||
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &enabled,
|
||||
sizeof(enabled)) == 0;
|
||||
}
|
||||
|
||||
bool Send(int socket, char command) {
|
||||
ssize_t count;
|
||||
do {
|
||||
count = send(socket, &command, 1, 0);
|
||||
} 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));
|
||||
}
|
||||
// Still an unreaped direct child: its PID cannot have been reused.
|
||||
kill(child, SIGKILL);
|
||||
while (waitpid(child, nullptr, 0) < 0 && errno == EINTR) {}
|
||||
}
|
||||
|
||||
class LoginSession {
|
||||
public:
|
||||
LoginSession() {
|
||||
// NSWorkspace does not require creating NSApplication or another Dock icon.
|
||||
// Only logout/poweroff ends monitoring; sleep, lock and user switching do
|
||||
// not end the login session.
|
||||
center_ = NSWorkspace.sharedWorkspace.notificationCenter;
|
||||
const auto ending = ending_;
|
||||
observer_ = [center_ addObserverForName:NSWorkspaceWillPowerOffNotification
|
||||
object:nil queue:nil
|
||||
usingBlock:^(NSNotification*) {
|
||||
ending->store(true);
|
||||
}];
|
||||
}
|
||||
~LoginSession() { if (observer_) [center_ removeObserver:observer_]; }
|
||||
|
||||
bool Alive() {
|
||||
// The watcher has no GUI event loop. Deliver workspace notifications even
|
||||
// while waiting for process teardown or delaying a crash restart.
|
||||
@autoreleasepool {
|
||||
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) ==
|
||||
kCFRunLoopRunHandledSource) {}
|
||||
return observer_ != nil && !ending_->load();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<std::atomic<bool>> ending_ =
|
||||
std::make_shared<std::atomic<bool>>(false);
|
||||
NSNotificationCenter* center_ = nil;
|
||||
id observer_ = nil;
|
||||
};
|
||||
|
||||
int Watch(int argc, char* argv[]) {
|
||||
Descriptor control{kControlSocket};
|
||||
int type = 0;
|
||||
socklen_t size = sizeof(type);
|
||||
uid_t user = 0;
|
||||
gid_t group = 0;
|
||||
const pid_t owner = getppid();
|
||||
if (owner <= 1 ||
|
||||
getsockopt(control.value, SOL_SOCKET, SO_TYPE, &type, &size) != 0 ||
|
||||
type != SOCK_STREAM || getpeereid(control.value, &user, &group) != 0 ||
|
||||
user != geteuid() || !ConfigureSocket(control.value)) return 1;
|
||||
|
||||
Descriptor events{kqueue()};
|
||||
struct kevent change{};
|
||||
EV_SET(&change, owner, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, nullptr);
|
||||
if (events.value < 0 || fcntl(events.value, F_SETFD, FD_CLOEXEC) != 0 ||
|
||||
kevent(events.value, &change, 1, nullptr, 0, nullptr) != 0) return 1;
|
||||
|
||||
LoginSession session;
|
||||
if (!session.Alive() || !Send(control.value, 'R')) return 1;
|
||||
bool armed = false;
|
||||
for (;;) {
|
||||
if (!session.Alive()) return 0;
|
||||
pollfd fd{control.value, POLLIN, 0};
|
||||
const int ready = poll(&fd, 1, kPollIntervalMs);
|
||||
if (ready < 0 && errno == EINTR) continue;
|
||||
if (ready < 0) return 1;
|
||||
if (ready == 0) continue;
|
||||
char command = 0;
|
||||
const auto count = recv(control.value, &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;
|
||||
// Drain a queued stop even if the GUI disappears before receiving this.
|
||||
Send(control.value, 'A');
|
||||
}
|
||||
if (!armed) return 0;
|
||||
|
||||
// Closing the control socket can precede the GUI's destructors. kqueue
|
||||
// watches the original process instance, so PID reuse cannot fool this wait.
|
||||
for (;;) {
|
||||
if (!session.Alive()) return 0;
|
||||
struct kevent event{};
|
||||
const timespec timeout{0, kPollIntervalMs * 1000000};
|
||||
const int count = kevent(events.value, nullptr, 0, &event, 1, &timeout);
|
||||
if (count < 0 && errno == EINTR) continue;
|
||||
if (count < 0 || (count > 0 && (event.flags & EV_ERROR))) return 1;
|
||||
if (count > 0 && (event.fflags & NOTE_EXIT)) break;
|
||||
}
|
||||
const auto restart_at = std::chrono::steady_clock::now() +
|
||||
std::chrono::seconds(1);
|
||||
do {
|
||||
if (!session.Alive()) return 0;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(kPollIntervalMs));
|
||||
} while (std::chrono::steady_clock::now() < restart_at);
|
||||
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
|
||||
|
||||
MacRuntimeDaemon& MacRuntimeDaemon::Instance() {
|
||||
static MacRuntimeDaemon daemon;
|
||||
return daemon;
|
||||
}
|
||||
|
||||
MacRuntimeDaemon::~MacRuntimeDaemon() { Shutdown(); }
|
||||
|
||||
void MacRuntimeDaemon::Initialize(int argc, char* argv[]) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
Stop();
|
||||
initialized_ = true;
|
||||
arguments_.clear();
|
||||
const auto executable = ExecutablePath();
|
||||
if (executable.empty()) return;
|
||||
arguments_.push_back(executable);
|
||||
arguments_.emplace_back(kMacDaemonArgument);
|
||||
for (int i = 1; i < argc; ++i) arguments_.emplace_back(argv[i]);
|
||||
}
|
||||
|
||||
bool MacRuntimeDaemon::SetEnabled(bool enabled, std::string& error) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
error.clear();
|
||||
// Configuration-only callers do not own a GUI watcher.
|
||||
if (!initialized_) return true;
|
||||
if (!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_STREAM, 0, sockets) != 0) {
|
||||
error = std::strerror(errno);
|
||||
return false;
|
||||
}
|
||||
// Keep IPC away from stdio and the fixed child descriptor, including when
|
||||
// the application was launched with one or more standard streams closed.
|
||||
Descriptor parent{fcntl(sockets[0], F_DUPFD_CLOEXEC, kControlSocket + 1)};
|
||||
Descriptor child_socket{fcntl(sockets[1], F_DUPFD_CLOEXEC, kControlSocket + 1)};
|
||||
const int duplicate_error = errno;
|
||||
close(sockets[0]);
|
||||
close(sockets[1]);
|
||||
if (parent.value < 0 || child_socket.value < 0) {
|
||||
error = std::strerror(duplicate_error);
|
||||
return false;
|
||||
}
|
||||
if (!ConfigureSocket(parent.value) || !ConfigureSocket(child_socket.value)) {
|
||||
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, child_socket.value,
|
||||
kControlSocket);
|
||||
// Keep only the control socket and standard streams. In particular, never
|
||||
// retain GUI sockets, files or single-instance locks after a crash.
|
||||
for (int fd = 0; result == 0 && fd <= STDERR_FILENO; ++fd) {
|
||||
result = fcntl(fd, F_GETFD) >= 0
|
||||
? posix_spawn_file_actions_addinherit_np(&actions, fd)
|
||||
: posix_spawn_file_actions_addopen(&actions, fd, "/dev/null", O_RDWR, 0);
|
||||
}
|
||||
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_setpgroup(&attributes, 0);
|
||||
if (result == 0)
|
||||
result = posix_spawnattr_setflags(&attributes,
|
||||
POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETPGROUP |
|
||||
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, arguments_[0].c_str(), &actions, &attributes,
|
||||
args.data(), environ);
|
||||
if (attributes_ready) posix_spawnattr_destroy(&attributes);
|
||||
if (actions_ready) posix_spawn_file_actions_destroy(&actions);
|
||||
if (result != 0) {
|
||||
error = "Cannot start daemon: " + std::string(std::strerror(result));
|
||||
return false;
|
||||
}
|
||||
close(child_socket.value);
|
||||
child_socket.value = -1;
|
||||
socket_ = parent.value;
|
||||
parent.value = -1;
|
||||
child_ = child;
|
||||
if (!Receive(socket_, 'R') || !Send(socket_, 'A') || !Receive(socket_, 'A')) {
|
||||
error = "Daemon did not acknowledge startup";
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacRuntimeDaemon::Stop() {
|
||||
if (socket_ >= 0) {
|
||||
Send(socket_, 'S');
|
||||
close(socket_);
|
||||
socket_ = -1;
|
||||
}
|
||||
Reap(child_);
|
||||
child_ = -1;
|
||||
}
|
||||
|
||||
void MacRuntimeDaemon::Shutdown(bool restart) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (!restart) {
|
||||
Stop();
|
||||
} else {
|
||||
if (socket_ >= 0) close(socket_);
|
||||
socket_ = -1;
|
||||
child_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int RunMacRuntimeDaemon(int argc, char* argv[]) {
|
||||
if (argc < 2 || std::strcmp(argv[1], kMacDaemonArgument) != 0 ||
|
||||
argv[0][0] != '/') return 1;
|
||||
@autoreleasepool {
|
||||
return Watch(argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
@@ -1,65 +0,0 @@
|
||||
#include "platform/daemon_backend.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace crossdesk::platform {
|
||||
namespace {
|
||||
|
||||
std::atomic<bool> g_stop_requested{false};
|
||||
|
||||
} // namespace
|
||||
|
||||
void ResetDaemonStop() { g_stop_requested.store(false); }
|
||||
|
||||
void RequestDaemonStop() { g_stop_requested.store(true); }
|
||||
|
||||
bool DaemonStopRequested() { return g_stop_requested.load(); }
|
||||
|
||||
bool PrepareDaemon() { return true; }
|
||||
|
||||
std::string GetDaemonExecutablePath() {
|
||||
wchar_t path[32768] = {};
|
||||
const DWORD length = GetModuleFileNameW(nullptr, path, 32768);
|
||||
return length > 0 && length < 32768
|
||||
? std::filesystem::path(path, path + length).u8string()
|
||||
: std::string();
|
||||
}
|
||||
|
||||
DaemonChildResult RunDaemonChild(const std::string& executable_path,
|
||||
const std::function<bool()>& keep_running) {
|
||||
DaemonChildResult result;
|
||||
STARTUPINFOW startup = {sizeof(startup)};
|
||||
PROCESS_INFORMATION process = {};
|
||||
const auto path = std::filesystem::u8path(executable_path).wstring();
|
||||
std::wstring command = L"\"" + path + L"\" --child";
|
||||
if (!CreateProcessW(path.c_str(), command.data(), nullptr, nullptr, FALSE,
|
||||
0, nullptr, nullptr, &startup, &process)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.started = true;
|
||||
while (keep_running()) {
|
||||
const DWORD wait_result = WaitForSingleObject(process.hProcess, 200);
|
||||
if (wait_result == WAIT_OBJECT_0 || wait_result == WAIT_FAILED) break;
|
||||
}
|
||||
if (!keep_running()) {
|
||||
TerminateProcess(process.hProcess, 1);
|
||||
WaitForSingleObject(process.hProcess, 3000);
|
||||
}
|
||||
|
||||
DWORD exit_code = 0;
|
||||
GetExitCodeProcess(process.hProcess, &exit_code);
|
||||
result.exit_code = static_cast<int>(exit_code);
|
||||
result.normal_exit = exit_code == 0;
|
||||
CloseHandle(process.hProcess);
|
||||
CloseHandle(process.hThread);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace crossdesk::platform
|
||||
Reference in New Issue
Block a user