#include #include #include "resource.h" #include #include #include #include #include #include #include namespace fs = std::filesystem; static std::wstring loadLocalizedString(unsigned int id) { const wchar_t* value = nullptr; const int length = LoadStringW(GetModuleHandleW(nullptr), id, reinterpret_cast(&value), 0); return length > 0 ? std::wstring(value, static_cast(length)) : std::wstring(); } static void showBootstrapError(unsigned int messageId) { const std::wstring message = loadLocalizedString(messageId); const std::wstring title = loadLocalizedString(IDS_BOOTSTRAP_ERROR_TITLE); MessageBoxW(nullptr, message.c_str(), title.c_str(), MB_ICONERROR); } static std::wstring quote(const std::wstring& value) { std::wstring result = L"\""; unsigned backslashes = 0; for (wchar_t ch : value) { if (ch == L'\\') { ++backslashes; continue; } if (ch == L'\"') { result.append(backslashes * 2 + 1, L'\\'); result.push_back(L'\"'); backslashes = 0; continue; } result.append(backslashes, L'\\'); backslashes = 0; result.push_back(ch); } result.append(backslashes * 2, L'\\'); result.push_back(L'\"'); return result; } static std::wstring fromUtf8(const std::string& value) { if (value.empty()) return {}; const int size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast(value.size()), nullptr, 0); if (size <= 0) return {}; std::wstring result(size, L'\0'); MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast(value.size()), result.data(), size); return result; } static bool safeRelative(const fs::path& path) { if (path.empty() || path.is_absolute() || path.has_root_name()) return false; for (const auto& part : path) if (part == L"..") return false; return true; } static bool copyWithRetry(const fs::path& source, const fs::path& destination) { std::error_code ec; fs::create_directories(destination.parent_path(), ec); for (int i = 0; i < 100; ++i) { ec.clear(); fs::copy_file(source, destination, fs::copy_options::overwrite_existing, ec); if (!ec) return true; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } return false; } static bool removeWithRetry(const fs::path& path) { std::error_code ec; for (int i = 0; i < 100; ++i) { ec.clear(); if (!fs::exists(path, ec)) return !ec; if (fs::remove(path, ec)) return true; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } return false; } static bool rollback(const fs::path& installDir, const fs::path& backupDir, const std::vector& paths) { bool success = true; for (const auto& relative : paths) { const fs::path destination = installDir / relative; const fs::path backup = backupDir / relative; std::error_code ec; if (fs::exists(backup, ec)) { if (!copyWithRetry(backup, destination)) success = false; } else if (fs::exists(destination, ec) && !fs::remove(destination, ec)) { success = false; } } return success; } static bool launchUpdater(const std::wstring& updater, const std::vector& updateArgs, const std::wstring& result) { std::wstring command = quote(updater); for (const auto& arg : updateArgs) command += L" " + quote(arg); command += L" " + quote(L"--bootstrap-resume=" + result); STARTUPINFOW si{}; si.cb = sizeof(si); PROCESS_INFORMATION pi{}; std::vector mutableCommand(command.begin(), command.end()); mutableCommand.push_back(L'\0'); const BOOL ok = CreateProcessW(updater.c_str(), mutableCommand.data(), nullptr, nullptr, FALSE, 0, nullptr, fs::path(updater).parent_path().c_str(), &si, &pi); if (ok) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } return ok == TRUE; } int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { int argc = 0; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); if (!argv || argc < 11) { showBootstrapError(IDS_BOOTSTRAP_ARGUMENTS_INCOMPLETE); if (argv) LocalFree(argv); return 2; } const fs::path planFile = argv[1]; const fs::path installDir = argv[2]; const fs::path stagingDir = argv[3]; const fs::path backupDir = argv[4]; const std::wstring updater = argv[5]; const DWORD updaterPid = static_cast(_wtoi(argv[6])); std::vector updateArgs{argv[7], argv[8], argv[9], argv[10]}; const std::wstring mode = argc >= 12 ? argv[11] : L"install"; LocalFree(argv); if (HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, updaterPid)) { WaitForSingleObject(process, 30000); CloseHandle(process); } struct PlanItem { wchar_t operation; fs::path relative; }; std::ifstream input(planFile, std::ios::binary); std::vector items; std::vector paths; std::string line; while (std::getline(input, line)) { if (!line.empty() && line.back() == '\r') line.pop_back(); if (line.size() < 3 || line[1] != '\t' || (line[0] != 'C' && line[0] != 'D')) { showBootstrapError(IDS_BOOTSTRAP_INVALID_PLAN_OPERATION); return 3; } const fs::path relative(fromUtf8(line.substr(2))); if (!safeRelative(relative)) { showBootstrapError(IDS_BOOTSTRAP_UNSAFE_PLAN_PATH); return 3; } items.push_back({static_cast(line[0]), relative}); paths.push_back(relative); } bool success = input.eof(); bool rolledBack = false; fs::path failedPath; if (mode == L"rollback") { rolledBack = success && rollback(installDir, backupDir, paths); success = false; } else if (success) { for (const auto& item : items) { const fs::path& relative = item.relative; if (_wcsicmp(relative.filename().c_str(), L"Bootstrap.exe") == 0) { success = false; failedPath = relative; break; } const bool itemOk = item.operation == L'C' ? copyWithRetry(stagingDir / relative, installDir / relative) : removeWithRetry(installDir / relative); if (!itemOk) { success = false; failedPath = relative; break; } } if (!success) rolledBack = rollback(installDir, backupDir, paths); } const std::wstring result = success ? L"success" : (rolledBack ? L"rolledback" : L"rollback-failed"); if (!launchUpdater(updater, updateArgs, result)) { std::wstring message = loadLocalizedString(IDS_BOOTSTRAP_RESTART_UPDATER_FAILED); if (!failedPath.empty()) message += L"\n" + loadLocalizedString(IDS_BOOTSTRAP_FAILED_FILE_PREFIX) + failedPath.wstring(); const std::wstring title = loadLocalizedString(IDS_BOOTSTRAP_ERROR_TITLE); MessageBoxW(nullptr, message.c_str(), title.c_str(), MB_ICONERROR); return 4; } return (success || rolledBack) ? 0 : 5; }