From 19bf2c166d9bc1647a3d055dbdeca87e8cdf5a06 Mon Sep 17 00:00:00 2001 From: spycrab Date: Sun, 3 Mar 2019 13:56:54 +0100 Subject: [PATCH] UpdaterCommon: Move most of the programs here --- Source/Core/MacUpdater/AppDelegate.mm | 204 +------------- Source/Core/MacUpdater/MacUI.mm | 27 ++ Source/Core/Updater/Main.cpp | 203 +------------- Source/Core/Updater/Updater.vcxproj | 3 - Source/Core/Updater/WinUI.cpp | 37 ++- Source/Core/UpdaterCommon/CMakeLists.txt | 3 +- Source/Core/UpdaterCommon/UI.h | 11 +- Source/Core/UpdaterCommon/UpdaterCommon.cpp | 258 +++++++++++++++++- Source/Core/UpdaterCommon/UpdaterCommon.h | 46 +--- .../Core/UpdaterCommon/UpdaterCommon.vcxproj | 3 + 10 files changed, 337 insertions(+), 458 deletions(-) diff --git a/Source/Core/MacUpdater/AppDelegate.mm b/Source/Core/MacUpdater/AppDelegate.mm index e8bfc5c016..d260a2122a 100644 --- a/Source/Core/MacUpdater/AppDelegate.mm +++ b/Source/Core/MacUpdater/AppDelegate.mm @@ -4,100 +4,12 @@ #import "AppDelegate.h" -#include "Common/FileUtil.h" - -#include "UpdaterCommon/UI.h" #include "UpdaterCommon/UpdaterCommon.h" #include -#include -#include -#include +#include #include -namespace -{ -struct Options -{ - std::string this_manifest_url; - std::string next_manifest_url; - std::string content_store_url; - std::string install_base_path; - std::optional binary_to_restart; - std::optional parent_pid; - std::optional log_file; -}; - -std::optional ParseCommandLine(std::vector& args) -{ - using optparse::OptionParser; - - OptionParser parser = - OptionParser().prog("Dolphin Updater").description("Dolphin Updater binary"); - - parser.add_option("--this-manifest-url") - .dest("this-manifest-url") - .help("URL to the update manifest for the currently installed version.") - .metavar("URL"); - parser.add_option("--next-manifest-url") - .dest("next-manifest-url") - .help("URL to the update manifest for the to-be-installed version.") - .metavar("URL"); - parser.add_option("--content-store-url") - .dest("content-store-url") - .help("Base URL of the content store where files to download are stored.") - .metavar("URL"); - parser.add_option("--install-base-path") - .dest("install-base-path") - .help("Base path of the Dolphin install to be updated.") - .metavar("PATH"); - parser.add_option("--binary-to-restart") - .dest("binary-to-restart") - .help("Binary to restart after the update is over.") - .metavar("PATH"); - parser.add_option("--log-file") - .dest("log-file") - .help("File where to log updater debug output.") - .metavar("PATH"); - parser.add_option("--parent-pid") - .dest("parent-pid") - .type("int") - .help("(optional) PID of the parent process. The updater will wait for this process to " - "complete before proceeding.") - .metavar("PID"); - - optparse::Values options = parser.parse_args(args); - - Options opts; - - // Required arguments. - std::vector required{"this-manifest-url", "next-manifest-url", "content-store-url", - "install-base-path"}; - for (const auto& req : required) - { - if (!options.is_set(req)) - { - parser.print_help(); - return {}; - } - } - opts.this_manifest_url = options["this-manifest-url"]; - opts.next_manifest_url = options["next-manifest-url"]; - opts.content_store_url = options["content-store-url"]; - opts.install_base_path = options["install-base-path"]; - - // Optional arguments. - if (options.is_set("binary-to-restart")) - opts.binary_to_restart = options["binary-to-restart"]; - if (options.is_set("parent-pid")) - opts.parent_pid = (pid_t)options.get("parent-pid"); - if (options.is_set("log-file")) - opts.log_file = options["log-file"]; - - return opts; -} -} // namespace - @interface AppDelegate () @end @@ -106,8 +18,6 @@ std::optional ParseCommandLine(std::vector& args) - (void)applicationDidFinishLaunching:(NSNotification*)aNotification { - UI::SetVisible(false); - NSArray* arguments = [[NSProcessInfo processInfo] arguments]; __block std::vector args; @@ -116,118 +26,10 @@ std::optional ParseCommandLine(std::vector& args) args.push_back(std::string([obj UTF8String])); }]; - std::optional maybe_opts = ParseCommandLine(args); - - if (!maybe_opts) - { - [NSApp terminate:nil]; - return; - } - - Options opts = std::move(*maybe_opts); - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul); dispatch_async(queue, ^{ - if (opts.log_file) - { - log_fp = fopen(opts.log_file.value().c_str(), "w"); - if (!log_fp) - log_fp = stderr; - else - 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()); - - if (!File::IsDirectory(opts.install_base_path)) - { - FatalError("Cannot find install base path, or not a directory."); - - [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; - return; - } - - if (opts.parent_pid) - { - UI::SetDescription("Waiting for Dolphin to quit..."); - - fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid); - - auto pid = opts.parent_pid.value(); - - for (int res = kill(pid, 0); res == 0 || (res < 0 && errno == EPERM); res = kill(pid, 0)) - { - sleep(1); - } - - fprintf(log_fp, "Completed! Proceeding with update.\n"); - } - - UI::SetVisible(true); - - UI::SetDescription("Fetching and parsing manifests..."); - - Manifest this_manifest, next_manifest; - { - std::optional maybe_manifest = FetchAndParseManifest(opts.this_manifest_url); - if (!maybe_manifest) - { - FatalError("Could not fetch current manifest. Aborting."); - return; - } - this_manifest = std::move(*maybe_manifest); - - maybe_manifest = FetchAndParseManifest(opts.next_manifest_url); - if (!maybe_manifest) - { - FatalError("Could not fetch next manifest. Aborting."); - [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; - return; - } - next_manifest = std::move(*maybe_manifest); - } - - UI::SetDescription("Computing what to do..."); - - TodoList todo = ComputeActionsToDo(this_manifest, next_manifest); - todo.Log(); - - std::optional maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path); - if (!maybe_temp_dir) - return; - std::string temp_dir = std::move(*maybe_temp_dir); - - UI::SetDescription("Performing Update..."); - - bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir); - if (!ok) - FatalError("Failed to apply the update."); - - CleanUpTempDir(temp_dir, todo); - - UI::ResetCurrentProgress(); - UI::ResetTotalProgress(); - UI::SetCurrentMarquee(false); - UI::SetTotalMarquee(false); - UI::SetCurrentProgress(1, 1); - UI::SetTotalProgress(1, 1); - UI::SetDescription("Done!"); - - // Let the user process that we are done. - [NSThread sleepForTimeInterval:1.0f]; - - if (opts.binary_to_restart) - { - [[NSWorkspace sharedWorkspace] - launchApplication:[NSString stringWithCString:opts.binary_to_restart.value().c_str() - encoding:[NSString defaultCStringEncoding]]]; - } - - dispatch_sync(dispatch_get_main_queue(), ^{ - [NSApp terminate:nil]; - }); + RunUpdater(args); + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; }); } diff --git a/Source/Core/MacUpdater/MacUI.mm b/Source/Core/MacUpdater/MacUI.mm index aab655749d..d36a6a111e 100644 --- a/Source/Core/MacUpdater/MacUI.mm +++ b/Source/Core/MacUpdater/MacUI.mm @@ -7,6 +7,7 @@ #include "UpdaterCommon/UI.h" #include +#include #include @@ -107,6 +108,32 @@ void UI::SetTotalProgress(int current, int total) run_on_main([&] { [GetView() SetTotalProgress:(double)current total:(double)total]; }); } +void UI::Sleep(int seconds) +{ + [NSThread sleepForTimeInterval:static_cast(seconds)]; +} + +void UI::WaitForPID(u32 pid) +{ + for (int res = kill(pid, 0); res == 0 || (res < 0 && errno == EPERM); res = kill(pid, 0)) + { + UI::Sleep(1); + } +} + +void UI::LaunchApplication(std::string path) +{ + [[NSWorkspace sharedWorkspace] + launchApplication:[NSString stringWithCString:path.c_str() + encoding:[NSString defaultCStringEncoding]]]; +} + +// Stubs. These are only needed on Windows + +void UI::Init() +{ +} + void UI::Stop() { } diff --git a/Source/Core/Updater/Main.cpp b/Source/Core/Updater/Main.cpp index 35e868a7f1..0cb09cb966 100644 --- a/Source/Core/Updater/Main.cpp +++ b/Source/Core/Updater/Main.cpp @@ -4,36 +4,18 @@ #include #include - -#include -#include -#include #include + #include -#include #include -#include "Common/CommonPaths.h" -#include "Common/CommonTypes.h" -#include "Common/FileUtil.h" +#include "Common/StringUtil.h" #include "UpdaterCommon/UI.h" #include "UpdaterCommon/UpdaterCommon.h" namespace { -// Internal representation of options passed on the command-line. -struct Options -{ - std::string this_manifest_url; - std::string next_manifest_url; - std::string content_store_url; - std::string install_base_path; - std::optional binary_to_restart; - std::optional parent_pid; - std::optional log_file; -}; - std::vector CommandLineToUtf8Argv(PCWSTR command_line) { int nargs; @@ -50,75 +32,6 @@ std::vector CommandLineToUtf8Argv(PCWSTR command_line) LocalFree(tokenized); return argv; } - -std::optional ParseCommandLine(PCWSTR command_line) -{ - using optparse::OptionParser; - - OptionParser parser = OptionParser().prog("updater.exe").description("Dolphin Updater binary"); - - parser.add_option("--this-manifest-url") - .dest("this-manifest-url") - .help("URL to the update manifest for the currently installed version.") - .metavar("URL"); - parser.add_option("--next-manifest-url") - .dest("next-manifest-url") - .help("URL to the update manifest for the to-be-installed version.") - .metavar("URL"); - parser.add_option("--content-store-url") - .dest("content-store-url") - .help("Base URL of the content store where files to download are stored.") - .metavar("URL"); - parser.add_option("--install-base-path") - .dest("install-base-path") - .help("Base path of the Dolphin install to be updated.") - .metavar("PATH"); - parser.add_option("--binary-to-restart") - .dest("binary-to-restart") - .help("Binary to restart after the update is over.") - .metavar("PATH"); - parser.add_option("--log-file") - .dest("log-file") - .help("File where to log updater debug output.") - .metavar("PATH"); - parser.add_option("--parent-pid") - .dest("parent-pid") - .type("int") - .help("(optional) PID of the parent process. The updater will wait for this process to " - "complete before proceeding.") - .metavar("PID"); - - std::vector argv = CommandLineToUtf8Argv(command_line); - optparse::Values options = parser.parse_args(argv); - - Options opts; - - // Required arguments. - std::vector required{"this-manifest-url", "next-manifest-url", "content-store-url", - "install-base-path"}; - for (const auto& req : required) - { - if (!options.is_set(req)) - { - parser.print_help(); - return {}; - } - } - opts.this_manifest_url = options["this-manifest-url"]; - opts.next_manifest_url = options["next-manifest-url"]; - opts.content_store_url = options["content-store-url"]; - opts.install_base_path = options["install-base-path"]; - - // Optional arguments. - if (options.is_set("binary-to-restart")) - opts.binary_to_restart = options["binary-to-restart"]; - if (options.is_set("parent-pid")) - opts.parent_pid = (DWORD)options.get("parent-pid"); - if (options.is_set("log-file")) - opts.log_file = options["log-file"]; - - return opts; -} }; // namespace int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) @@ -132,58 +45,29 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine return 1; } - std::optional maybe_opts = ParseCommandLine(pCmdLine); - if (!maybe_opts) - return 1; - Options opts = std::move(*maybe_opts); - + // Test for write permissions bool need_admin = false; - if (opts.log_file) - { - log_fp = _wfopen(UTF8ToUTF16(*opts.log_file).c_str(), L"w"); - if (!log_fp) - { - log_fp = stderr; - // Failing to create the logfile for writing is a good indicator that we need administrator - // priviliges - need_admin = true; - } - else - atexit(FlushLog); - } + FILE* test_fh = fopen("Updater.log", "w"); - 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()); - - if (!File::IsDirectory(opts.install_base_path)) - { - FatalError("Cannot find install base path, or not a directory."); - return 1; - } - - if (opts.parent_pid) - { - fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid); - HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, *opts.parent_pid); - WaitForSingleObject(parent_handle, INFINITE); - CloseHandle(parent_handle); - fprintf(log_fp, "Completed! Proceeding with update.\n"); - } + if (test_fh == nullptr) + need_admin = true; + else + fclose(test_fh); if (need_admin) { if (IsUserAnAdmin()) { - FatalError("Failed to write to directory despite administrator priviliges."); + MessageBox(nullptr, L"Failed to write to directory despite administrator priviliges.", + L"Error", MB_ICONERROR); return 1; } wchar_t path[MAX_PATH]; if (GetModuleFileName(hInstance, path, sizeof(path)) == 0) { - FatalError("Failed to get updater filename."); + MessageBox(nullptr, L"Failed to get updater filename.", L"Error", MB_ICONERROR); return 1; } @@ -192,68 +76,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine return 0; } - std::thread thread(UI::MessageLoop); - thread.detach(); + std::vector args = CommandLineToUtf8Argv(pCmdLine); - UI::SetDescription("Fetching and parsing manifests..."); - - Manifest this_manifest, next_manifest; - { - std::optional maybe_manifest = FetchAndParseManifest(opts.this_manifest_url); - if (!maybe_manifest) - { - FatalError("Could not fetch current manifest. Aborting."); - return 1; - } - this_manifest = std::move(*maybe_manifest); - - maybe_manifest = FetchAndParseManifest(opts.next_manifest_url); - if (!maybe_manifest) - { - FatalError("Could not fetch next manifest. Aborting."); - return 1; - } - next_manifest = std::move(*maybe_manifest); - } - - UI::SetDescription("Computing what to do..."); - - TodoList todo = ComputeActionsToDo(this_manifest, next_manifest); - todo.Log(); - - std::optional maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path); - if (!maybe_temp_dir) - return 1; - std::string temp_dir = std::move(*maybe_temp_dir); - - UI::SetDescription("Performing Update..."); - - bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir); - if (!ok) - FatalError("Failed to apply the update."); - - CleanUpTempDir(temp_dir, todo); - - UI::ResetCurrentProgress(); - UI::ResetTotalProgress(); - UI::SetCurrentMarquee(false); - UI::SetTotalMarquee(false); - UI::SetCurrentProgress(0, 1); - UI::SetTotalProgress(1, 1); - UI::SetDescription("Done!"); - - // Let the user process that we are done. - Sleep(1000); - - if (opts.binary_to_restart) - { - // Hack: Launching the updater over the explorer ensures that admin priviliges are dropped. Why? - // Ask Microsoft. - ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToUTF16(*opts.binary_to_restart).c_str(), - nullptr, SW_SHOW); - } - - UI::Stop(); - - return !ok; + return RunUpdater(args) ? 0 : 1; } diff --git a/Source/Core/Updater/Updater.vcxproj b/Source/Core/Updater/Updater.vcxproj index cb6085f967..93e1c7ea72 100644 --- a/Source/Core/Updater/Updater.vcxproj +++ b/Source/Core/Updater/Updater.vcxproj @@ -42,9 +42,6 @@ - - {c636d9d1-82fe-42b5-9987-63b7d4836341} - {2e6c348c-c75c-4d94-8d1e-9c1fcbf3efe4} diff --git a/Source/Core/Updater/WinUI.cpp b/Source/Core/Updater/WinUI.cpp index 8d73236c29..682ea1e545 100644 --- a/Source/Core/Updater/WinUI.cpp +++ b/Source/Core/Updater/WinUI.cpp @@ -5,10 +5,12 @@ #include "UpdaterCommon/UI.h" #include +#include #include #include #include +#include #include "Common/Flag.h" #include "Common/StringUtil.h" @@ -29,7 +31,7 @@ constexpr int PROGRESSBAR_FLAGS = WS_VISIBLE | WS_CHILD | PBS_SMOOTH | PBS_SMOOT namespace UI { -bool Init() +bool InitWindow() { InitCommonControls(); @@ -168,7 +170,7 @@ void MessageLoop() request_stop.Clear(); running.Set(); - if (!Init()) + if (!InitWindow()) { running.Clear(); MessageBox(nullptr, L"Window init failed!", L"", MB_ICONERROR); @@ -192,6 +194,12 @@ void MessageLoop() Destroy(); } +void Init() +{ + std::thread thread(MessageLoop); + thread.detach(); +} + void Stop() { if (!running.IsSet()) @@ -203,4 +211,29 @@ void Stop() { } } + +void LaunchApplication(std::string path) +{ + // Hack: Launching the updater over the explorer ensures that admin priviliges are dropped. Why? + // Ask Microsoft. + ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToUTF16(path).c_str(), nullptr, SW_SHOW); +} + +void Sleep(int sleep) +{ + ::Sleep(sleep * 1000); +} + +void WaitForPID(u32 pid) +{ + HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, static_cast(pid)); + WaitForSingleObject(parent_handle, INFINITE); + CloseHandle(parent_handle); +} + +void SetVisible(bool visible) +{ + ShowWindow(window_handle, visible ? SW_SHOW : SW_HIDE); +} + }; // namespace UI diff --git a/Source/Core/UpdaterCommon/CMakeLists.txt b/Source/Core/UpdaterCommon/CMakeLists.txt index c789681ef4..8a98814e4d 100644 --- a/Source/Core/UpdaterCommon/CMakeLists.txt +++ b/Source/Core/UpdaterCommon/CMakeLists.txt @@ -5,4 +5,5 @@ target_link_libraries(updatercommon PRIVATE uicommon mbedtls z - ed25519) \ No newline at end of file + ed25519 + cpp-optparse) \ No newline at end of file diff --git a/Source/Core/UpdaterCommon/UI.h b/Source/Core/UpdaterCommon/UI.h index f542d02da0..0d4d606f37 100644 --- a/Source/Core/UpdaterCommon/UI.h +++ b/Source/Core/UpdaterCommon/UI.h @@ -6,11 +6,11 @@ #include +#include "Common/CommonTypes.h" + namespace UI { -void MessageLoop(); void Error(const std::string& text); -void Stop(); void SetDescription(const std::string& text); @@ -23,4 +23,11 @@ void ResetCurrentProgress(); void SetCurrentProgress(int current, int total); void SetVisible(bool visible); + +void Stop(); + +void Init(); +void Sleep(int seconds); +void WaitForPID(u32 pid); +void LaunchApplication(std::string path); } // namespace UI diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.cpp b/Source/Core/UpdaterCommon/UpdaterCommon.cpp index 456576d5e7..77fb317040 100644 --- a/Source/Core/UpdaterCommon/UpdaterCommon.cpp +++ b/Source/Core/UpdaterCommon/UpdaterCommon.cpp @@ -5,6 +5,9 @@ #include "UpdaterCommon/UpdaterCommon.h" #include +#include + +#include #include #include #include @@ -21,6 +24,8 @@ #include #endif +namespace +{ // Where to log updater output. FILE* log_fp = stderr; @@ -31,13 +36,48 @@ const std::array UPDATE_PUB_KEY = { const char UPDATE_TEMP_DIR[] = "TempUpdate"; -static bool ProgressCallback(double total, double now, double, double) +struct Manifest +{ + using Filename = std::string; + using Hash = std::array; + std::map entries; +}; + +// Represent the operations to be performed by the updater. +struct TodoList +{ + struct DownloadOp + { + Manifest::Filename filename; + Manifest::Hash hash; + }; + std::vector to_download; + + struct UpdateOp + { + Manifest::Filename filename; + std::optional old_hash; + Manifest::Hash new_hash; + }; + std::vector to_update; + + struct DeleteOp + { + Manifest::Filename filename; + Manifest::Hash old_hash; + }; + std::vector to_delete; + + void Log() const; +}; + +bool ProgressCallback(double total, double now, double, double) { UI::SetCurrentProgress(static_cast(now), static_cast(total)); return true; } -static std::string HexEncode(const u8* buffer, size_t size) +std::string HexEncode(const u8* buffer, size_t size) { std::string out(size * 2, '\0'); @@ -50,7 +90,7 @@ static std::string HexEncode(const u8* buffer, size_t size) return out; } -static bool HexDecode(const std::string& hex, u8* buffer, size_t size) +bool HexDecode(const std::string& hex, u8* buffer, size_t size) { if (hex.size() != size * 2) return false; @@ -79,7 +119,7 @@ static bool HexDecode(const std::string& hex, u8* buffer, size_t size) return true; } -static std::optional GzipInflate(const std::string& data) +std::optional GzipInflate(const std::string& data) { z_stream zstrm; zstrm.zalloc = nullptr; @@ -115,7 +155,7 @@ static std::optional GzipInflate(const std::string& data) return out; } -static Manifest::Hash ComputeHash(const std::string& contents) +Manifest::Hash ComputeHash(const std::string& contents) { std::array full; mbedtls_sha256(reinterpret_cast(contents.data()), contents.size(), full.data(), false); @@ -125,7 +165,7 @@ static Manifest::Hash ComputeHash(const std::string& contents) return out; } -static bool VerifySignature(const std::string& data, const std::string& b64_signature) +bool VerifySignature(const std::string& data, const std::string& b64_signature) { u8 signature[64]; // ed25519 sig size. size_t sig_size; @@ -173,8 +213,8 @@ void TodoList::Log() const } } -static bool DownloadContent(const std::vector& to_download, - const std::string& content_base_url, const std::string& temp_path) +bool DownloadContent(const std::vector& to_download, + const std::string& content_base_url, const std::string& temp_path) { Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback); @@ -314,7 +354,7 @@ void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo) File::DeleteDir(temp_dir); } -static bool BackupFile(const std::string& path) +bool BackupFile(const std::string& path) { std::string backup_path = path + ".bak"; fprintf(log_fp, "Backing up unknown pre-existing %s to .bak.\n", path.c_str()); @@ -326,8 +366,8 @@ static bool BackupFile(const std::string& path) return true; } -static bool DeleteObsoleteFiles(const std::vector& to_delete, - const std::string& install_base_path) +bool DeleteObsoleteFiles(const std::vector& to_delete, + const std::string& install_base_path) { for (const auto& op : to_delete) { @@ -359,8 +399,8 @@ static bool DeleteObsoleteFiles(const std::vector& to_delete return true; } -static bool UpdateFiles(const std::vector& to_update, - const std::string& install_base_path, const std::string& temp_path) +bool UpdateFiles(const std::vector& to_update, + const std::string& install_base_path, const std::string& temp_path) { for (const auto& op : to_update) { @@ -459,7 +499,7 @@ void FatalError(const std::string& message) UI::Stop(); } -static std::optional ParseManifest(const std::string& manifest) +std::optional ParseManifest(const std::string& manifest) { Manifest parsed; size_t pos = 0; @@ -550,3 +590,193 @@ std::optional FetchAndParseManifest(const std::string& url) return ParseManifest(decompressed); } + +struct Options +{ + std::string this_manifest_url; + std::string next_manifest_url; + std::string content_store_url; + std::string install_base_path; + std::optional binary_to_restart; + std::optional parent_pid; + std::optional log_file; +}; + +std::optional ParseCommandLine(std::vector& args) +{ + using optparse::OptionParser; + + OptionParser parser = + OptionParser().prog("Dolphin Updater").description("Dolphin Updater binary"); + + parser.add_option("--this-manifest-url") + .dest("this-manifest-url") + .help("URL to the update manifest for the currently installed version.") + .metavar("URL"); + parser.add_option("--next-manifest-url") + .dest("next-manifest-url") + .help("URL to the update manifest for the to-be-installed version.") + .metavar("URL"); + parser.add_option("--content-store-url") + .dest("content-store-url") + .help("Base URL of the content store where files to download are stored.") + .metavar("URL"); + parser.add_option("--install-base-path") + .dest("install-base-path") + .help("Base path of the Dolphin install to be updated.") + .metavar("PATH"); + parser.add_option("--binary-to-restart") + .dest("binary-to-restart") + .help("Binary to restart after the update is over.") + .metavar("PATH"); + parser.add_option("--log-file") + .dest("log-file") + .help("File where to log updater debug output.") + .metavar("PATH"); + parser.add_option("--parent-pid") + .dest("parent-pid") + .type("int") + .help("(optional) PID of the parent process. The updater will wait for this process to " + "complete before proceeding.") + .metavar("PID"); + + optparse::Values options = parser.parse_args(args); + + Options opts; + + // Required arguments. + std::vector required{"this-manifest-url", "next-manifest-url", "content-store-url", + "install-base-path"}; + for (const auto& req : required) + { + if (!options.is_set(req)) + { + parser.print_help(); + return {}; + } + } + opts.this_manifest_url = options["this-manifest-url"]; + opts.next_manifest_url = options["next-manifest-url"]; + opts.content_store_url = options["content-store-url"]; + opts.install_base_path = options["install-base-path"]; + + // Optional arguments. + if (options.is_set("binary-to-restart")) + opts.binary_to_restart = options["binary-to-restart"]; + if (options.is_set("parent-pid")) + opts.parent_pid = static_cast(options.get("parent-pid")); + if (options.is_set("log-file")) + opts.log_file = options["log-file"]; + + return opts; +} +}; // namespace + +bool RunUpdater(std::vector args) +{ + std::optional maybe_opts = ParseCommandLine(args); + + if (!maybe_opts) + { + return false; + } + + UI::Init(); + + Options opts = std::move(*maybe_opts); + + if (opts.log_file) + { + log_fp = fopen(opts.log_file.value().c_str(), "w"); + if (!log_fp) + log_fp = stderr; + else + 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()); + + if (!File::IsDirectory(opts.install_base_path)) + { + FatalError("Cannot find install base path, or not a directory."); + return false; + } + + if (opts.parent_pid) + { + UI::SetDescription("Waiting for Dolphin to quit..."); + + fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid); + + auto pid = opts.parent_pid.value(); + + UI::WaitForPID(static_cast(pid)); + + fprintf(log_fp, "Completed! Proceeding with update.\n"); + } + + UI::SetVisible(true); + + UI::SetDescription("Fetching and parsing manifests..."); + + Manifest this_manifest, next_manifest; + { + std::optional maybe_manifest = FetchAndParseManifest(opts.this_manifest_url); + if (!maybe_manifest) + { + FatalError("Could not fetch current manifest. Aborting."); + return false; + } + this_manifest = std::move(*maybe_manifest); + + maybe_manifest = FetchAndParseManifest(opts.next_manifest_url); + if (!maybe_manifest) + { + FatalError("Could not fetch next manifest. Aborting."); + return false; + } + next_manifest = std::move(*maybe_manifest); + } + + UI::SetDescription("Computing what to do..."); + + TodoList todo = ComputeActionsToDo(this_manifest, next_manifest); + todo.Log(); + + std::optional maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path); + if (!maybe_temp_dir) + return false; + std::string temp_dir = std::move(*maybe_temp_dir); + + UI::SetDescription("Performing Update..."); + + bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir); + if (!ok) + { + FatalError("Failed to apply the update."); + CleanUpTempDir(temp_dir, todo); + return false; + } + + UI::ResetCurrentProgress(); + UI::ResetTotalProgress(); + UI::SetCurrentMarquee(false); + UI::SetTotalMarquee(false); + UI::SetCurrentProgress(1, 1); + UI::SetTotalProgress(1, 1); + UI::SetDescription("Done!"); + + // Let the user process that we are done. + UI::Sleep(1); + + if (opts.binary_to_restart) + { + UI::LaunchApplication(opts.binary_to_restart.value()); + } + + UI::Stop(); + + return true; +} diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.h b/Source/Core/UpdaterCommon/UpdaterCommon.h index fb63994d80..53f37c2424 100644 --- a/Source/Core/UpdaterCommon/UpdaterCommon.h +++ b/Source/Core/UpdaterCommon/UpdaterCommon.h @@ -13,48 +13,4 @@ #include "Common/CommonTypes.h" -extern FILE* log_fp; - -struct Manifest -{ - using Filename = std::string; - using Hash = std::array; - std::map entries; -}; - -// Represent the operations to be performed by the updater. -struct TodoList -{ - struct DownloadOp - { - Manifest::Filename filename; - Manifest::Hash hash; - }; - std::vector to_download; - - struct UpdateOp - { - Manifest::Filename filename; - std::optional old_hash; - Manifest::Hash new_hash; - }; - std::vector to_update; - - struct DeleteOp - { - Manifest::Filename filename; - Manifest::Hash old_hash; - }; - std::vector to_delete; - - void Log() const; -}; - -void FatalError(const std::string& message); -std::optional FetchAndParseManifest(const std::string& url); -TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest); -std::optional FindOrCreateTempDir(const std::string& base_path); -void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo); -bool PerformUpdate(const TodoList& todo, const std::string& install_base_path, - const std::string& content_base_url, const std::string& temp_path); -void FlushLog(); +bool RunUpdater(std::vector args); diff --git a/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj b/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj index 191098f15f..77fbaf069c 100644 --- a/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj +++ b/Source/Core/UpdaterCommon/UpdaterCommon.vcxproj @@ -54,6 +54,9 @@ {ff213b23-2c26-4214-9f88-85271e557e87} + + + {c636d9d1-82fe-42b5-9987-63b7d4836341}