Merge pull request #11636 from shuffle2/updater-test

Add test for Updater
This commit is contained in:
Pierre Bourdon
2023-03-13 15:47:37 +01:00
committed by GitHub
10 changed files with 371 additions and 86 deletions

View File

@ -3,6 +3,7 @@
#include "DolphinQt/Updater.h"
#include <cstdlib>
#include <utility>
#include <QCheckBox>
@ -41,6 +42,16 @@ void Updater::CheckForUpdate()
void Updater::OnUpdateAvailable(const NewVersionInformation& info)
{
if (std::getenv("DOLPHIN_UPDATE_SERVER_URL"))
{
TriggerUpdate(info, AutoUpdateChecker::RestartMode::RESTART_AFTER_UPDATE);
RunOnObject(m_parent, [this] {
m_parent->close();
return 0;
});
return;
}
bool later = false;
std::optional<int> choice = RunOnObject(m_parent, [&] {

View File

@ -140,8 +140,7 @@ void UI::Init()
}
bool Platform::VersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
const std::string& install_base_path, const std::string& temp_dir,
FILE* log_fp)
const std::string& install_base_path, const std::string& temp_dir)
{
const auto op_it = std::find_if(to_update.cbegin(), to_update.cend(), [&](const auto& op) {
return op.filename == "Dolphin.app/Contents/Info.plist";
@ -155,7 +154,7 @@ bool Platform::VersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithCString:plist_path.c_str()]];
if (!data)
{
fprintf(log_fp, "Failed to read %s, skipping platform version check.\n", plist_path.c_str());
LogToFile("Failed to read %s, skipping platform version check.\n", plist_path.c_str());
return true;
}
@ -167,13 +166,13 @@ bool Platform::VersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
error:&error];
if (error)
{
fprintf(log_fp, "Failed to parse %s, skipping platform version check.\n", plist_path.c_str());
LogToFile("Failed to parse %s, skipping platform version check.\n", plist_path.c_str());
return true;
}
NSString* min_version_str = info_dict[@"LSMinimumSystemVersion"];
if (!min_version_str)
{
fprintf(log_fp, "LSMinimumSystemVersion key missing, skipping platform version check.\n");
LogToFile("LSMinimumSystemVersion key missing, skipping platform version check.\n");
return true;
}
@ -181,9 +180,8 @@ bool Platform::VersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
NSOperatingSystemVersion next_version{
[components[0] integerValue], [components[1] integerValue], [components[2] integerValue]};
fprintf(log_fp, "Platform version check: next_version=%ld.%ld.%ld\n",
(long)next_version.majorVersion, (long)next_version.minorVersion,
(long)next_version.patchVersion);
LogToFile("Platform version check: next_version=%ld.%ld.%ld\n", (long)next_version.majorVersion,
(long)next_version.minorVersion, (long)next_version.patchVersion);
if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:next_version])
{

View File

@ -3,6 +3,7 @@
#include "UICommon/AutoUpdate.h"
#include <cstdlib>
#include <string>
#include <fmt/format.h>
@ -19,12 +20,13 @@
#ifdef _WIN32
#include <Windows.h>
#else
#include <sys/types.h>
#include <unistd.h>
#endif
#ifdef __APPLE__
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#if defined(_WIN32) || defined(__APPLE__)
@ -160,6 +162,23 @@ static std::string GetPlatformID()
#endif
}
static std::string GetUpdateServerUrl()
{
auto server_url = std::getenv("DOLPHIN_UPDATE_SERVER_URL");
if (server_url)
return server_url;
return "https://dolphin-emu.org";
}
static u32 GetOwnProcessId()
{
#ifdef _WIN32
return GetCurrentProcessId();
#else
return getpid();
#endif
}
void AutoUpdateChecker::CheckForUpdate(std::string_view update_track,
std::string_view hash_override, const CheckType check_type)
{
@ -172,7 +191,7 @@ void AutoUpdateChecker::CheckForUpdate(std::string_view update_track,
#endif
std::string_view version_hash = hash_override.empty() ? Common::GetScmRevGitStr() : hash_override;
std::string url = fmt::format("https://dolphin-emu.org/update/check/v1/{}/{}/{}", update_track,
std::string url = fmt::format("{}/update/check/v1/{}/{}/{}", GetUpdateServerUrl(), update_track,
version_hash, GetPlatformID());
const bool is_manual_check = check_type == CheckType::Manual;
@ -215,7 +234,15 @@ void AutoUpdateChecker::CheckForUpdate(std::string_view update_track,
// TODO: generate the HTML changelog from the JSON information.
nvi.changelog_html = GenerateChangelog(obj["changelog"].get<picojson::array>());
OnUpdateAvailable(nvi);
if (std::getenv("DOLPHIN_UPDATE_TEST_DONE"))
{
// We are at end of updater test flow, send a message to server, which will kill us.
req.Get(fmt::format("{}/update-test-done/{}", GetUpdateServerUrl(), GetOwnProcessId()));
}
else
{
OnUpdateAvailable(nvi);
}
}
void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInformation& info,
@ -234,11 +261,7 @@ void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInforma
updater_flags["this-manifest-url"] = info.this_manifest_url;
updater_flags["next-manifest-url"] = info.next_manifest_url;
updater_flags["content-store-url"] = info.content_store_url;
#ifdef _WIN32
updater_flags["parent-pid"] = std::to_string(GetCurrentProcessId());
#else
updater_flags["parent-pid"] = std::to_string(getpid());
#endif
updater_flags["parent-pid"] = std::to_string(GetOwnProcessId());
updater_flags["install-base-path"] = File::GetExeDirectory();
updater_flags["log-file"] = File::GetUserPath(D_LOGS_IDX) + UPDATER_LOG_FILE;

View File

@ -15,5 +15,5 @@
namespace Platform
{
bool VersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
const std::string& install_base_path, const std::string& temp_dir, FILE* log_fp);
const std::string& install_base_path, const std::string& temp_dir);
} // namespace Platform

View File

@ -29,4 +29,6 @@ void Init();
void Sleep(int seconds);
void WaitForPID(u32 pid);
void LaunchApplication(std::string path);
bool IsTestMode();
} // namespace UI

View File

@ -34,13 +34,28 @@
// Refer to docs/autoupdate_overview.md for a detailed overview of the autoupdate process
// Where to log updater output.
static FILE* log_fp = stderr;
// Public key used to verify update manifests.
const std::array<u8, 32> UPDATE_PUB_KEY = {
0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c, 0x54, 0xdf, 0x54, 0xf4, 0x42,
0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70, 0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d};
// The private key for UPDATE_PUB_KEY_TEST is in Tools/test-updater.py
const std::array<u8, 32> UPDATE_PUB_KEY_TEST = {
0x0c, 0x5f, 0xdc, 0xd1, 0x15, 0x71, 0xfb, 0x86, 0x4f, 0x9e, 0x6d, 0xe6, 0x65, 0x39, 0x43, 0xe1,
0x9e, 0xe0, 0x9b, 0x28, 0xc9, 0x1a, 0x60, 0xb7, 0x67, 0x1c, 0xf3, 0xf6, 0xca, 0x1b, 0xdd, 0x1a};
// Where to log updater output.
static FILE* log_fp = stderr;
void LogToFile(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(log_fp, fmt, args);
fflush(log_fp);
va_end(args);
}
bool ProgressCallback(double total, double now, double, double)
{
@ -120,7 +135,7 @@ std::optional<std::string> GzipInflate(const std::string& data)
if (ret != Z_STREAM_END)
{
fprintf(log_fp, "Could not read the data as gzip: error %d.\n", ret);
LogToFile("Could not read the data as gzip: error %d.\n", ret);
return {};
}
@ -148,12 +163,13 @@ bool VerifySignature(const std::string& data, const std::string& b64_signature)
b64_signature.size()) ||
sig_size != sizeof(signature))
{
fprintf(log_fp, "Invalid base64: %s\n", b64_signature.c_str());
LogToFile("Invalid base64: %s\n", b64_signature.c_str());
return false;
}
const auto& pub_key = UI::IsTestMode() ? UPDATE_PUB_KEY_TEST : UPDATE_PUB_KEY;
return ed25519_verify(signature, reinterpret_cast<const u8*>(data.data()), data.size(),
UPDATE_PUB_KEY.data());
pub_key.data());
}
void FlushLog()
@ -166,22 +182,22 @@ void TodoList::Log() const
{
if (to_update.size())
{
fprintf(log_fp, "Updating:\n");
LogToFile("Updating:\n");
for (const auto& op : to_update)
{
std::string old_desc =
op.old_hash ? HexEncode(op.old_hash->data(), op.old_hash->size()) : "(new)";
fprintf(log_fp, " - %s: %s -> %s\n", op.filename.c_str(), old_desc.c_str(),
HexEncode(op.new_hash.data(), op.new_hash.size()).c_str());
LogToFile(" - %s: %s -> %s\n", op.filename.c_str(), old_desc.c_str(),
HexEncode(op.new_hash.data(), op.new_hash.size()).c_str());
}
}
if (to_delete.size())
{
fprintf(log_fp, "Deleting:\n");
LogToFile("Deleting:\n");
for (const auto& op : to_delete)
{
fprintf(log_fp, " - %s (%s)\n", op.filename.c_str(),
HexEncode(op.old_hash.data(), op.old_hash.size()).c_str());
LogToFile(" - %s (%s)\n", op.filename.c_str(),
HexEncode(op.old_hash.data(), op.old_hash.size()).c_str());
}
}
}
@ -215,7 +231,7 @@ bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
content_store_path.insert(2, "/");
std::string url = content_base_url + content_store_path;
fprintf(log_fp, "Downloading %s ...\n", url.c_str());
LogToFile("Downloading %s ...\n", url.c_str());
auto resp = req.Get(url);
if (!resp)
@ -234,14 +250,14 @@ bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
Manifest::Hash contents_hash = ComputeHash(decompressed);
if (contents_hash != download.hash)
{
fprintf(log_fp, "Wrong hash on downloaded content %s.\n", url.c_str());
LogToFile("Wrong hash on downloaded content %s.\n", url.c_str());
return false;
}
const std::string out = temp_path + DIR_SEP + hash_filename;
if (!File::WriteStringToFile(out, decompressed))
{
fprintf(log_fp, "Could not write cache file %s.\n", out.c_str());
LogToFile("Could not write cache file %s.\n", out.c_str());
return false;
}
}
@ -252,7 +268,7 @@ bool PlatformVersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
const std::string& install_base_path, const std::string& temp_dir)
{
UI::SetDescription("Checking platform...");
return Platform::VersionCheck(to_update, install_base_path, temp_dir, log_fp);
return Platform::VersionCheck(to_update, install_base_path, temp_dir);
}
TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest)
@ -310,10 +326,10 @@ void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo)
bool BackupFile(const std::string& path)
{
std::string backup_path = path + ".bak";
fprintf(log_fp, "Backing up existing %s to .bak.\n", path.c_str());
LogToFile("Backing up existing %s to .bak.\n", path.c_str());
if (!File::Rename(path, backup_path))
{
fprintf(log_fp, "Cound not rename %s to %s for backup.\n", path.c_str(), backup_path.c_str());
LogToFile("Cound not rename %s to %s for backup.\n", path.c_str(), backup_path.c_str());
return false;
}
return true;
@ -328,7 +344,7 @@ bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
if (!File::Exists(path))
{
fprintf(log_fp, "File %s is already missing.\n", op.filename.c_str());
LogToFile("File %s is already missing.\n", op.filename.c_str());
continue;
}
else
@ -336,7 +352,7 @@ bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
std::string contents;
if (!File::ReadFileToString(path, contents))
{
fprintf(log_fp, "Could not read file planned for deletion: %s.\n", op.filename.c_str());
LogToFile("Could not read file planned for deletion: %s.\n", op.filename.c_str());
return false;
}
Manifest::Hash contents_hash = ComputeHash(contents);
@ -365,7 +381,7 @@ bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
std::string path = install_base_path + DIR_SEP + op.filename;
if (!File::CreateFullPath(path))
{
fprintf(log_fp, "Could not create directory structure for %s.\n", op.filename.c_str());
LogToFile("Could not create directory structure for %s.\n", op.filename.c_str());
return false;
}
@ -385,7 +401,7 @@ bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
if (S_ISLNK(file_stats.st_mode))
{
fprintf(log_fp, "%s is symlink, skipping\n", path.c_str());
LogToFile("%s is symlink, skipping\n", path.c_str());
continue;
}
@ -408,13 +424,13 @@ bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
std::string contents;
if (!File::ReadFileToString(path, contents))
{
fprintf(log_fp, "Could not read existing file %s.\n", op.filename.c_str());
LogToFile("Could not read existing file %s.\n", op.filename.c_str());
return false;
}
Manifest::Hash contents_hash = ComputeHash(contents);
if (contents_hash == op.new_hash)
{
fprintf(log_fp, "File %s was already up to date. Partial update?\n", op.filename.c_str());
LogToFile("File %s was already up to date. Partial update?\n", op.filename.c_str());
continue;
}
else if (!op.old_hash || contents_hash != *op.old_hash || is_self)
@ -426,8 +442,8 @@ bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
// Now we can safely move the new contents to the location.
std::string content_filename = HexEncode(op.new_hash.data(), op.new_hash.size());
fprintf(log_fp, "Updating file %s from content %s...\n", op.filename.c_str(),
content_filename.c_str());
LogToFile("Updating file %s from content %s...\n", op.filename.c_str(),
content_filename.c_str());
#ifdef __APPLE__
// macOS caches the code signature of Mach-O executables when they're first loaded.
// Unfortunately, there is a quirk in the kernel with how it handles the cache: if the file is
@ -440,8 +456,7 @@ bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
const std::string temporary_file = temp_path + DIR_SEP + "temporary_file";
if (!File::CopyRegularFile(temp_path + DIR_SEP + content_filename, temporary_file))
{
fprintf(log_fp, "Could not copy %s to %s.\n", content_filename.c_str(),
temporary_file.c_str());
LogToFile("Could not copy %s to %s.\n", content_filename.c_str(), temporary_file.c_str());
return false;
}
@ -450,7 +465,7 @@ bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
if (!File::CopyRegularFile(temp_path + DIR_SEP + content_filename, path))
#endif
{
fprintf(log_fp, "Could not update file %s.\n", op.filename.c_str());
LogToFile("Could not update file %s.\n", op.filename.c_str());
return false;
}
@ -465,32 +480,32 @@ bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
const std::string& content_base_url, const std::string& temp_path)
{
fprintf(log_fp, "Starting download step...\n");
LogToFile("Starting download step...\n");
if (!DownloadContent(todo.to_download, content_base_url, temp_path))
return false;
fprintf(log_fp, "Download step completed.\n");
LogToFile("Download step completed.\n");
fprintf(log_fp, "Starting platform version check step...\n");
LogToFile("Starting platform version check step...\n");
if (!PlatformVersionCheck(todo.to_update, install_base_path, temp_path))
return false;
fprintf(log_fp, "Platform version check step completed.\n");
LogToFile("Platform version check step completed.\n");
fprintf(log_fp, "Starting update step...\n");
LogToFile("Starting update step...\n");
if (!UpdateFiles(todo.to_update, install_base_path, temp_path))
return false;
fprintf(log_fp, "Update step completed.\n");
LogToFile("Update step completed.\n");
fprintf(log_fp, "Starting deletion step...\n");
LogToFile("Starting deletion step...\n");
if (!DeleteObsoleteFiles(todo.to_delete, install_base_path))
return false;
fprintf(log_fp, "Deletion step completed.\n");
LogToFile("Deletion step completed.\n");
return true;
}
void FatalError(const std::string& message)
{
fprintf(log_fp, "%s\n", message.c_str());
LogToFile("%s\n", message.c_str());
UI::SetVisible(true);
UI::Error(message);
@ -506,13 +521,13 @@ std::optional<Manifest> ParseManifest(const std::string& manifest)
size_t filename_end_pos = manifest.find('\t', pos);
if (filename_end_pos == std::string::npos)
{
fprintf(log_fp, "Manifest entry %zu: could not find filename end.\n", parsed.entries.size());
LogToFile("Manifest entry %zu: could not find filename end.\n", parsed.entries.size());
return {};
}
size_t hash_end_pos = manifest.find('\n', filename_end_pos);
if (hash_end_pos == std::string::npos)
{
fprintf(log_fp, "Manifest entry %zu: could not find hash end.\n", parsed.entries.size());
LogToFile("Manifest entry %zu: could not find hash end.\n", parsed.entries.size());
return {};
}
@ -520,16 +535,14 @@ std::optional<Manifest> ParseManifest(const std::string& manifest)
std::string hash = manifest.substr(filename_end_pos + 1, hash_end_pos - filename_end_pos - 1);
if (hash.size() != 32)
{
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
hash.c_str());
LogToFile("Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(), hash.c_str());
return {};
}
Manifest::Hash decoded_hash;
if (!HexDecode(hash, decoded_hash.data(), decoded_hash.size()))
{
fprintf(log_fp, "Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(),
hash.c_str());
LogToFile("Manifest entry %zu: invalid hash: \"%s\".\n", parsed.entries.size(), hash.c_str());
return {};
}
@ -548,7 +561,7 @@ std::optional<Manifest> FetchAndParseManifest(const std::string& url)
Common::HttpRequest::Response resp = http.Get(url);
if (!resp)
{
fprintf(log_fp, "Manifest download failed.\n");
LogToFile("Manifest download failed.\n");
return {};
}
@ -562,7 +575,7 @@ std::optional<Manifest> FetchAndParseManifest(const std::string& url)
size_t boundary = decompressed.rfind("\n\n");
if (boundary == std::string::npos)
{
fprintf(log_fp, "No signature was found in manifest.\n");
LogToFile("No signature was found in manifest.\n");
return {};
}
@ -581,7 +594,7 @@ std::optional<Manifest> FetchAndParseManifest(const std::string& url)
}
if (!found_valid_signature)
{
fprintf(log_fp, "Could not verify signature of the manifest.\n");
LogToFile("Could not verify signature of the manifest.\n");
return {};
}
@ -692,9 +705,9 @@ bool RunUpdater(std::vector<std::string> args)
atexit(FlushLog);
}
fprintf(log_fp, "Updating from: %s\n", opts.this_manifest_url.c_str());
fprintf(log_fp, "Updating to: %s\n", opts.next_manifest_url.c_str());
fprintf(log_fp, "Install path: %s\n", opts.install_base_path.c_str());
LogToFile("Updating from: %s\n", opts.this_manifest_url.c_str());
LogToFile("Updating to: %s\n", opts.next_manifest_url.c_str());
LogToFile("Install path: %s\n", opts.install_base_path.c_str());
if (!File::IsDirectory(opts.install_base_path))
{
@ -704,13 +717,13 @@ bool RunUpdater(std::vector<std::string> args)
if (opts.parent_pid)
{
fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid);
LogToFile("Waiting for parent PID %d to complete...\n", *opts.parent_pid);
auto pid = opts.parent_pid.value();
UI::WaitForPID(static_cast<u32>(pid));
fprintf(log_fp, "Completed! Proceeding with update.\n");
LogToFile("Completed! Proceeding with update.\n");
}
UI::SetVisible(true);

View File

@ -49,6 +49,7 @@ struct TodoList
void Log() const;
};
void LogToFile(const char* fmt, ...);
std::string HexEncode(const u8* buffer, size_t size);
Manifest::Hash ComputeHash(const std::string& contents);
bool RunUpdater(std::vector<std::string> args);

View File

@ -103,7 +103,10 @@ private:
auto key_it = map.find(key);
if (key_it == map.end())
continue;
key_it->second = line.substr(equals_index + 1);
auto val_start = equals_index + 1;
auto eol = line.find('\r', val_start);
auto val_size = (eol == line.npos) ? line.npos : eol - val_start;
key_it->second = line.substr(val_start, val_size);
}
}
Map map;
@ -194,9 +197,12 @@ static bool VCRuntimeUpdate(const BuildInfo& build_info)
Common::ScopeGuard redist_deleter([&] { File::Delete(redist_path_u8); });
// The installer also supports /passive and /quiet. We pass neither to allow the user to see and
// interact with the installer.
// The installer also supports /passive and /quiet. We normally pass neither (the
// exception being test automation) to allow the user to see and interact with the installer.
std::wstring cmdline = redist_path.filename().wstring() + L" /install /norestart";
if (UI::IsTestMode())
cmdline += L" /passive /quiet";
STARTUPINFOW startup_info{.cb = sizeof(startup_info)};
PROCESS_INFORMATION process_info;
if (!CreateProcessW(redist_path.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr,
@ -213,7 +219,8 @@ static bool VCRuntimeUpdate(const BuildInfo& build_info)
CloseHandle(process_info.hProcess);
// NOTE: Some nonzero exit codes can still be considered success (e.g. if installation was
// bypassed because the same version already installed).
return has_exit_code && exit_code == EXIT_SUCCESS;
return has_exit_code &&
(exit_code == ERROR_SUCCESS || exit_code == ERROR_SUCCESS_REBOOT_REQUIRED);
}
static BuildVersion CurrentOSVersion()
@ -241,7 +248,7 @@ static VersionCheckResult OSVersionCheck(const BuildInfo& build_info)
std::optional<BuildInfos> InitBuildInfos(const std::vector<TodoList::UpdateOp>& to_update,
const std::string& install_base_path,
const std::string& temp_dir, FILE* log_fp)
const std::string& temp_dir)
{
const auto op_it = std::find_if(to_update.cbegin(), to_update.cend(),
[&](const auto& op) { return op.filename == "build_info.txt"; });
@ -255,7 +262,7 @@ std::optional<BuildInfos> InitBuildInfos(const std::vector<TodoList::UpdateOp>&
if (!File::ReadFileToString(build_info_path, build_info_content) ||
op.new_hash != ComputeHash(build_info_content))
{
fprintf(log_fp, "Failed to read %s\n.", build_info_path.c_str());
LogToFile("Failed to read %s\n.", build_info_path.c_str());
return {};
}
BuildInfos build_infos;
@ -266,7 +273,7 @@ std::optional<BuildInfos> InitBuildInfos(const std::vector<TodoList::UpdateOp>&
if (File::ReadFileToString(build_info_path, build_info_content))
{
if (op.old_hash != ComputeHash(build_info_content))
fprintf(log_fp, "Using modified existing BuildInfo %s.\n", build_info_path.c_str());
LogToFile("Using modified existing BuildInfo %s.\n", build_info_path.c_str());
build_infos.current = Platform::BuildInfo(build_info_content);
}
return build_infos;
@ -287,11 +294,16 @@ bool CheckBuildInfo(const BuildInfos& build_infos)
// Check if application being launched needs more recent version of VC Redist. If so, download
// latest updater and execute it.
auto vc_check = VCRuntimeVersionCheck(build_infos);
if (vc_check.status != VersionCheckStatus::NothingToDo)
const auto is_test_mode = UI::IsTestMode();
if (vc_check.status != VersionCheckStatus::NothingToDo || is_test_mode)
{
// Don't bother checking status of the install itself, just check if we actually see the new
// version.
VCRuntimeUpdate(build_infos.next);
auto update_ok = VCRuntimeUpdate(build_infos.next);
if (!update_ok && is_test_mode)
{
// For now, only check return value when test automation is running.
// The vc_redist exe may return other non-zero status that we don't check for, yet.
return false;
}
vc_check = VCRuntimeVersionCheck(build_infos);
if (vc_check.status == VersionCheckStatus::UpdateRequired)
{
@ -305,9 +317,9 @@ bool CheckBuildInfo(const BuildInfos& build_infos)
}
bool VersionCheck(const std::vector<TodoList::UpdateOp>& to_update,
const std::string& install_base_path, const std::string& temp_dir, FILE* log_fp)
const std::string& install_base_path, const std::string& temp_dir)
{
auto build_infos = InitBuildInfos(to_update, install_base_path, temp_dir, log_fp);
auto build_infos = InitBuildInfos(to_update, install_base_path, temp_dir);
// If there's no build info, it means the check should be skipped.
if (!build_infos.has_value())
{

View File

@ -3,12 +3,14 @@
#include "UpdaterCommon/UI.h"
#include <cstdlib>
#include <string>
#include <thread>
#include <Windows.h>
#include <CommCtrl.h>
#include <ShObjIdl.h>
#include <ShlObj.h>
#include <shellapi.h>
#include <wrl/client.h>
@ -253,11 +255,34 @@ void Stop()
ui_thread.join();
}
bool IsTestMode()
{
return std::getenv("DOLPHIN_UPDATE_SERVER_URL") != nullptr;
}
void LaunchApplication(std::string path)
{
// Indirectly start the application via explorer. This effectively drops admin priviliges because
// explorer is running as current user.
ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToWString(path).c_str(), nullptr, SW_SHOW);
const auto wpath = UTF8ToWString(path);
if (IsUserAnAdmin())
{
// Indirectly start the application via explorer. This effectively drops admin privileges
// because explorer is running as current user.
ShellExecuteW(nullptr, nullptr, L"explorer.exe", wpath.c_str(), nullptr, SW_SHOW);
}
else
{
std::wstring cmdline = wpath;
STARTUPINFOW startup_info{.cb = sizeof(startup_info)};
PROCESS_INFORMATION process_info;
if (IsTestMode())
SetEnvironmentVariableA("DOLPHIN_UPDATE_TEST_DONE", "1");
if (CreateProcessW(wpath.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr,
&startup_info, &process_info))
{
CloseHandle(process_info.hThread);
CloseHandle(process_info.hProcess);
}
}
}
void Sleep(int sleep)