diff --git a/src/common/logging/log.cpp b/src/common/logging/log.cpp index 4a1a95d68..2f91894a0 100644 --- a/src/common/logging/log.cpp +++ b/src/common/logging/log.cpp @@ -5,6 +5,7 @@ #include "common/emulatorConfig.h" #include +#include #include #include #include @@ -45,8 +46,8 @@ std::shared_ptr MakeFileLogger(std::string name std::filesystem::create_directories(parent); } - auto sink = - std::make_shared(path.native(), true); + const bool truncate = std::getenv(Log::RelaunchedVariable) == nullptr; + auto sink = std::make_shared(path.native(), truncate); return MakeLogger(std::move(name), std::move(sink)); } diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 56416cbc8..d332d96f4 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -21,6 +21,10 @@ struct Lifecycle { 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(); bool IsSilent(); void Write(std::string_view text); diff --git a/src/loader/loadExec.cpp b/src/loader/loadExec.cpp index 5d3f48680..862fa842c 100644 --- a/src/loader/loadExec.cpp +++ b/src/loader/loadExec.cpp @@ -4,7 +4,9 @@ #include "kernel/fileSystem.h" #include "libs/errno.h" +#include #include +#include #include #include #include @@ -56,8 +58,16 @@ int LoadExec(const char* path, const char* const argv[]) { bytes += size; 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(); + std::fflush(nullptr); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS wchar_t 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); command += L" " + QuoteArgument(wide); } - STARTUPINFOW startup {}; - startup.cb = sizeof(startup); + // The new process writes where this one does: to its console, or to the files or pipes its + // 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 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 attributes_storage(attributes_size); + auto* attributes = reinterpret_cast(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(CREATE_NO_WINDOW)); + // The new process continues this one's log file; it inherits the environment. + (void)SetEnvironmentVariableA(Log::RelaunchedVariable, "1"); PROCESS_INFORMATION process {}; - if (!CreateProcessW(executable, command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, - nullptr, nullptr, &startup, &process)) { - LOGF("LoadExec: process creation failed: %lu\n", GetLastError()); + const bool created = + CreateProcessW(executable, command.data(), nullptr, nullptr, listed ? TRUE : FALSE, flags, + 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; } CloseHandle(process.hThread); CloseHandle(process.hProcess); - std::fflush(nullptr); std::quick_exit(0); #else 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(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 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) { - execvp(host_argv[0], host_argv.data()); + execvpe(host_argv[0], host_argv.data(), environment.data()); } LOGF("LoadExec failed: %s\n", std::strerror(errno)); #endif