mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
loader: keep the output and log of a game LoadExec relaunches (#1480)
* loader: keep the output of a process LoadExec relaunches LoadExec on Windows starts a new emulator process and exits the current one. The new process was created without a console and without the standard handles, so everything it printed was lost: a redirected log of a game that relaunches itself ended at the relaunch. Hand the new process the standard input, output and error handles, inheriting only those, and let it share the console when there is one. * loader: flush output before LoadExec starts the new program On Windows, LoadExec flushed the C streams only after the new process had been created, so output the old process still held could appear after the new one's first lines. On Linux they were not flushed at all before exec, which discards them. Flush the log and every stream before starting the new program. * loader: continue the log file across LoadExec With --printf-direction File, the program LoadExec starts is given the same --printf-output-file and opened it anew, emptying it. Everything the first program had logged, its LoadExec line included, was lost. LoadExec now sets KYTY_RELAUNCHED for the program it starts, which then appends to the log file instead of replacing it. On Linux the variable goes into the environment passed to exec rather than this process's own, which other threads may be reading. * loader: show LoadExec switches on the console LoadExec logged the program it switched to only when guest output was logged, and then only into the log: on a console the new program's startup output followed the old one's without anything marking where one program ended and the next began. Write the switch to the console as well as the log, with the program's arguments.
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
#include "common/emulatorConfig.h"
|
#include "common/emulatorConfig.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -45,8 +46,8 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
|||||||
std::filesystem::create_directories(parent);
|
std::filesystem::create_directories(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sink =
|
const bool truncate = std::getenv(Log::RelaunchedVariable) == nullptr;
|
||||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), true);
|
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), truncate);
|
||||||
return MakeLogger(std::move(name), std::move(sink));
|
return MakeLogger(std::move(name), std::move(sink));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ struct Lifecycle {
|
|||||||
|
|
||||||
enum class Direction { Silent, Console, File };
|
enum class Direction { Silent, Console, File };
|
||||||
|
|
||||||
|
// Set by LoadExec for the program it starts, which continues the log file of the one it replaces
|
||||||
|
// instead of starting it over.
|
||||||
|
inline constexpr const char* RelaunchedVariable = "KYTY_RELAUNCHED";
|
||||||
|
|
||||||
Direction GetDirection();
|
Direction GetDirection();
|
||||||
bool IsSilent();
|
bool IsSilent();
|
||||||
void Write(std::string_view text);
|
void Write(std::string_view text);
|
||||||
|
|||||||
+70
-8
@@ -4,7 +4,9 @@
|
|||||||
#include "kernel/fileSystem.h"
|
#include "kernel/fileSystem.h"
|
||||||
#include "libs/errno.h"
|
#include "libs/errno.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -56,8 +58,16 @@ int LoadExec(const char* path, const char* const argv[]) {
|
|||||||
bytes += size;
|
bytes += size;
|
||||||
arguments.emplace_back(argv[i], size - 1);
|
arguments.emplace_back(argv[i], size - 1);
|
||||||
}
|
}
|
||||||
LOGF("LoadExec: %s\n", path);
|
// Shown on the console too: the output that follows comes from the new program.
|
||||||
|
auto message = fmt::format("LoadExec: switching to {}", path);
|
||||||
|
for (size_t i = 2; i < arguments.size(); ++i) {
|
||||||
|
message += fmt::format(" \"{}\"", arguments[i]);
|
||||||
|
}
|
||||||
|
Log::WriteToConsoleAndLog(message + "\n");
|
||||||
|
// The new program writes to the same outputs, so everything written so far goes out first.
|
||||||
|
// Neither exec nor quick_exit writes out buffered streams.
|
||||||
Log::Flush();
|
Log::Flush();
|
||||||
|
std::fflush(nullptr);
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
wchar_t executable[32768] {};
|
wchar_t executable[32768] {};
|
||||||
const auto length = GetModuleFileNameW(nullptr, executable, 32768);
|
const auto length = GetModuleFileNameW(nullptr, executable, 32768);
|
||||||
@@ -88,17 +98,57 @@ int LoadExec(const char* path, const char* const argv[]) {
|
|||||||
wide.resize(size - 1);
|
wide.resize(size - 1);
|
||||||
command += L" " + QuoteArgument(wide);
|
command += L" " + QuoteArgument(wide);
|
||||||
}
|
}
|
||||||
STARTUPINFOW startup {};
|
// The new process writes where this one does: to its console, or to the files or pipes its
|
||||||
startup.cb = sizeof(startup);
|
// output was redirected to. Only the standard handles are inherited.
|
||||||
|
HANDLE std_handles[3] = {GetStdHandle(STD_INPUT_HANDLE), GetStdHandle(STD_OUTPUT_HANDLE),
|
||||||
|
GetStdHandle(STD_ERROR_HANDLE)};
|
||||||
|
std::vector<HANDLE> inherited;
|
||||||
|
for (auto& handle: std_handles) {
|
||||||
|
if (handle == nullptr || handle == INVALID_HANDLE_VALUE ||
|
||||||
|
!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
|
||||||
|
handle = nullptr;
|
||||||
|
} else if (std::find(inherited.begin(), inherited.end(), handle) == inherited.end()) {
|
||||||
|
inherited.push_back(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SIZE_T attributes_size = 0;
|
||||||
|
InitializeProcThreadAttributeList(nullptr, 1, 0, &attributes_size);
|
||||||
|
std::vector<uint8_t> attributes_storage(attributes_size);
|
||||||
|
auto* attributes = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(attributes_storage.data());
|
||||||
|
const bool inherit =
|
||||||
|
!inherited.empty() && InitializeProcThreadAttributeList(attributes, 1, 0, &attributes_size);
|
||||||
|
const bool listed =
|
||||||
|
inherit && UpdateProcThreadAttribute(attributes, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
||||||
|
inherited.data(), inherited.size() * sizeof(HANDLE),
|
||||||
|
nullptr, nullptr);
|
||||||
|
STARTUPINFOEXW startup {};
|
||||||
|
startup.StartupInfo.cb = sizeof(startup);
|
||||||
|
if (listed) {
|
||||||
|
startup.lpAttributeList = attributes;
|
||||||
|
startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||||
|
startup.StartupInfo.hStdInput = std_handles[0];
|
||||||
|
startup.StartupInfo.hStdOutput = std_handles[1];
|
||||||
|
startup.StartupInfo.hStdError = std_handles[2];
|
||||||
|
}
|
||||||
|
// A process with a console shares it with the new one; without one, none is created.
|
||||||
|
const DWORD flags = (listed ? EXTENDED_STARTUPINFO_PRESENT : 0u) |
|
||||||
|
(GetConsoleWindow() != nullptr ? 0u : static_cast<DWORD>(CREATE_NO_WINDOW));
|
||||||
|
// The new process continues this one's log file; it inherits the environment.
|
||||||
|
(void)SetEnvironmentVariableA(Log::RelaunchedVariable, "1");
|
||||||
PROCESS_INFORMATION process {};
|
PROCESS_INFORMATION process {};
|
||||||
if (!CreateProcessW(executable, command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
|
const bool created =
|
||||||
nullptr, nullptr, &startup, &process)) {
|
CreateProcessW(executable, command.data(), nullptr, nullptr, listed ? TRUE : FALSE, flags,
|
||||||
LOGF("LoadExec: process creation failed: %lu\n", GetLastError());
|
nullptr, nullptr, &startup.StartupInfo, &process) != 0;
|
||||||
|
const auto error = GetLastError();
|
||||||
|
if (inherit) {
|
||||||
|
DeleteProcThreadAttributeList(attributes);
|
||||||
|
}
|
||||||
|
if (!created) {
|
||||||
|
LOGF("LoadExec: process creation failed: %lu\n", error);
|
||||||
return SYSTEM_SERVICE_ERROR_INTERNAL;
|
return SYSTEM_SERVICE_ERROR_INTERNAL;
|
||||||
}
|
}
|
||||||
CloseHandle(process.hThread);
|
CloseHandle(process.hThread);
|
||||||
CloseHandle(process.hProcess);
|
CloseHandle(process.hProcess);
|
||||||
std::fflush(nullptr);
|
|
||||||
std::quick_exit(0);
|
std::quick_exit(0);
|
||||||
#else
|
#else
|
||||||
std::ifstream command("/proc/self/cmdline", std::ios::binary);
|
std::ifstream command("/proc/self/cmdline", std::ios::binary);
|
||||||
@@ -116,8 +166,20 @@ int LoadExec(const char* path, const char* const argv[]) {
|
|||||||
host_argv.push_back(argument.data());
|
host_argv.push_back(argument.data());
|
||||||
}
|
}
|
||||||
host_argv.push_back(nullptr);
|
host_argv.push_back(nullptr);
|
||||||
|
// The new program continues this one's log file. It is given its environment explicitly:
|
||||||
|
// setenv is not safe while other threads may read the environment.
|
||||||
|
const std::string relaunched = std::string(Log::RelaunchedVariable) + "=";
|
||||||
|
std::string relaunched_entry = relaunched + "1";
|
||||||
|
std::vector<char*> environment;
|
||||||
|
for (char** variable = environ; variable != nullptr && *variable != nullptr; ++variable) {
|
||||||
|
if (!std::string_view(*variable).starts_with(relaunched)) {
|
||||||
|
environment.push_back(*variable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment.push_back(relaunched_entry.data());
|
||||||
|
environment.push_back(nullptr);
|
||||||
if (close_range(3, ~0u, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC) == 0) {
|
if (close_range(3, ~0u, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC) == 0) {
|
||||||
execvp(host_argv[0], host_argv.data());
|
execvpe(host_argv[0], host_argv.data(), environment.data());
|
||||||
}
|
}
|
||||||
LOGF("LoadExec failed: %s\n", std::strerror(errno));
|
LOGF("LoadExec failed: %s\n", std::strerror(errno));
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user