From 50f689ff80ecf9192de1867fb8b9745a06bfbee4 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 23 Apr 2023 18:09:13 -0500 Subject: [PATCH] Implement restart support for all platforms --- src/confighttp.cpp | 27 +---------- src/main.cpp | 11 +++++ src/main.h | 2 + src/platform/common.h | 4 +- src/platform/linux/misc.cpp | 32 ++++++++++--- src/platform/macos/misc.mm | 32 ++++++++++--- src/platform/windows/misc.cpp | 48 +++++++++++++++---- src/system_tray.cpp | 12 +++++ src_assets/common/assets/web/config.html | 16 +++---- .../common/assets/web/troubleshooting.html | 26 ++-------- 10 files changed, 128 insertions(+), 82 deletions(-) diff --git a/src/confighttp.cpp b/src/confighttp.cpp index cd1d7ee8..e442d6b0 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -544,7 +544,6 @@ namespace confighttp { outputTree.put("status", "true"); outputTree.put("platform", SUNSHINE_PLATFORM); outputTree.put("version", PROJECT_VER); - outputTree.put("restart_supported", platf::restart_supported()); auto vars = config::parse_config(read_file(config::sunshine.config_file.c_str())); @@ -595,30 +594,8 @@ namespace confighttp { print_req(request); - std::stringstream ss; - std::stringstream configStream; - ss << request->content.rdbuf(); - pt::ptree outputTree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_json(data, outputTree); - response->write(data.str()); - }); - - if (!platf::restart_supported()) { - outputTree.put("status", false); - outputTree.put("error", "Restart is not currently supported on this platform"); - return; - } - - if (!platf::restart()) { - outputTree.put("status", false); - outputTree.put("error", "Restart failed"); - return; - } - - outputTree.put("status", true); + // We may not return from this call + platf::restart(); } void diff --git a/src/main.cpp b/src/main.cpp index 0733778f..e87dabc3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -108,6 +108,7 @@ namespace version { } // namespace version namespace lifetime { + static char **argv; static std::atomic_int desired_exit_code; /** @@ -130,6 +131,14 @@ namespace lifetime { std::this_thread::sleep_for(1s); } } + + /** + * @brief Gets the argv array passed to main() + */ + char ** + get_argv() { + return argv; + } } // namespace lifetime /** @@ -207,6 +216,8 @@ SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { */ int main(int argc, char *argv[]) { + lifetime::argv = argv; + task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; #ifdef _WIN32 diff --git a/src/main.h b/src/main.h index 1a892386..8e74c709 100644 --- a/src/main.h +++ b/src/main.h @@ -69,6 +69,8 @@ namespace mail { namespace lifetime { void exit_sunshine(int exit_code, bool async); + char ** + get_argv(); } // namespace lifetime #endif // SUNSHINE_MAIN_H diff --git a/src/platform/common.h b/src/platform/common.h index 6b6f3f1e..3d611179 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -399,9 +399,7 @@ namespace platf { void streaming_will_stop(); - bool - restart_supported(); - bool + void restart(); struct batched_send_info_t { diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 63179277..62fe3b4d 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -193,16 +193,34 @@ namespace platf { // Nothing to do } - bool - restart_supported() { - // Restart not supported yet - return false; + void + restart_on_exit() { + char executable[PATH_MAX]; + ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1); + if (len == -1) { + BOOST_LOG(fatal) << "readlink() failed: "sv << errno; + return; + } + executable[len] = '\0'; + + // ASIO doesn't use O_CLOEXEC, so we have to close all fds ourselves + int openmax = (int) sysconf(_SC_OPEN_MAX); + for (int fd = STDERR_FILENO + 1; fd < openmax; fd++) { + close(fd); + } + + // Re-exec ourselves with the same arguments + if (execv(executable, lifetime::get_argv()) < 0) { + BOOST_LOG(fatal) << "execv() failed: "sv << errno; + return; + } } - bool + void restart() { - // Restart not supported yet - return false; + // Gracefully clean up and restart ourselves instead of exiting + atexit(restart_on_exit); + lifetime::exit_sunshine(0, true); } bool diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 2f16f4ba..eff4bab1 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -192,16 +193,33 @@ namespace platf { // Nothing to do } - bool - restart_supported() { - // Restart not supported yet - return false; + void + restart_on_exit() { + char executable[2048]; + uint32_t size = sizeof(executable); + if (_NSGetExecutablePath(executable, &size) < 0) { + BOOST_LOG(fatal) << "NSGetExecutablePath() failed: "sv << errno; + return; + } + + // ASIO doesn't use O_CLOEXEC, so we have to close all fds ourselves + int openmax = (int) sysconf(_SC_OPEN_MAX); + for (int fd = STDERR_FILENO + 1; fd < openmax; fd++) { + close(fd); + } + + // Re-exec ourselves with the same arguments + if (execv(executable, lifetime::get_argv()) < 0) { + BOOST_LOG(fatal) << "execv() failed: "sv << errno; + return; + } } - bool + void restart() { - // Restart not supported yet - return false; + // Gracefully clean up and restart ourselves instead of exiting + atexit(restart_on_exit); + lifetime::exit_sunshine(0, true); } bool diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index af6b4a26..67dae2e1 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -809,19 +809,49 @@ namespace platf { } } - bool - restart_supported() { - // Restart is supported if we're running from the service - return (GetConsoleWindow() == NULL); + void + restart_on_exit() { + STARTUPINFOEXW startup_info {}; + startup_info.StartupInfo.cb = sizeof(startup_info); + + WCHAR executable[MAX_PATH]; + if (GetModuleFileNameW(NULL, executable, ARRAYSIZE(executable)) == 0) { + auto winerr = GetLastError(); + BOOST_LOG(fatal) << "Failed to get Sunshine path: "sv << winerr; + return; + } + + PROCESS_INFORMATION process_info; + if (!CreateProcessW(executable, + GetCommandLineW(), + nullptr, + nullptr, + false, + CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT, + nullptr, + nullptr, + (LPSTARTUPINFOW) &startup_info, + &process_info)) { + auto winerr = GetLastError(); + BOOST_LOG(fatal) << "Unable to restart Sunshine: "sv << winerr; + return; + } + + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); } - bool + void restart() { - // Gracefully exit. The service will restart us in a few seconds. - // We use an async exit call here because we can't block the - // HTTP thread or we'll hang shutdown. + // If we're running standalone, we have to respawn ourselves via CreateProcess(). + // If we're running from the service, we should just exit and let it respawn us. + if (GetConsoleWindow() != NULL) { + // Avoid racing with the new process by waiting until we're exiting to start it. + atexit(restart_on_exit); + } + + // We use an async exit call here because we can't block the HTTP thread or we'll hang shutdown. lifetime::exit_sunshine(0, true); - return true; } SOCKADDR_IN diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 8de4baf5..fde76c2e 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -123,6 +123,17 @@ namespace system_tray { open_url("https://www.paypal.com/paypalme/ReenigneArcher"); } + /** + * @brief Callback for restarting Sunshine from the system tray. + * @param item The tray menu item. + */ + void + tray_restart_cb(struct tray_menu *item) { + BOOST_LOG(info) << "Restarting from system tray"sv; + + platf::restart(); + } + /** * @brief Callback for exiting Sunshine from the system tray. * @param item The tray menu item. @@ -162,6 +173,7 @@ namespace system_tray { { .text = "PayPal", .cb = tray_donate_paypal_cb }, { .text = nullptr } } }, { .text = "-" }, + { .text = "Restart", .cb = tray_restart_cb }, { .text = "Quit", .cb = tray_quit_cb }, { .text = nullptr } }, }; diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index cb660516..d6f0a803 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -965,18 +965,15 @@ -
+
Success! Click 'Apply' to restart Sunshine and apply changes. This will terminate any running sessions.
-
- Success! Restart Sunshine to apply changes. -
Success! Sunshine is restarting to apply changes.
- +
@@ -1023,7 +1020,6 @@ data() { return { platform: "", - restart_supported: false, saved: false, restarted: false, config: null, @@ -1087,7 +1083,6 @@ .then((r) => { this.config = r; this.platform = this.config.platform; - this.restart_supported = (this.config.restart_supported === "true"); var app = document.getElementById("app"); if (this.platform === "windows") { @@ -1108,7 +1103,6 @@ // remove values we don't want in the config file delete this.config.platform; - delete this.config.restart_supported; delete this.config.status; delete this.config.version; //Populate default values if not present in config @@ -1197,10 +1191,12 @@ saved.then((result) => { if (result === true) { + this.restarted = true; + setTimeout(() => { + this.saved = this.restarted = false; + }, 5000); fetch("/api/restart", { method: "POST" - }).then((r) => { - if (r.status === 200) this.restarted = true; }); } }); diff --git a/src_assets/common/assets/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html index f517348e..3fe0ada0 100644 --- a/src_assets/common/assets/web/troubleshooting.html +++ b/src_assets/common/assets/web/troubleshooting.html @@ -23,7 +23,7 @@ -
+

Restart Sunshine


@@ -31,12 +31,9 @@ If Sunshine isn't working properly, you can try restarting it. This will terminate any running sessions.

-
+
Sunshine is restarting
-
- Error restarting Sunshine -