From 6ad0fa649e837382fa7376ff2a223ee7ba4c0037 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Wed, 11 Oct 2017 12:49:14 +0200 Subject: Re-wrote the auto-updater to use process spawning instead of AppDomain loading to execute updates. --- Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs | 2 +- Duplicati/Library/AutoUpdater/UpdaterManager.cs | 113 +++++++++++++++++++++ .../Library/Localization/MoLocalizationService.cs | 9 +- Duplicati/Server/Program.cs | 22 ++-- Duplicati/Server/UpdatePollThread.cs | 5 + 5 files changed, 142 insertions(+), 9 deletions(-) (limited to 'Duplicati') diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs index d9a247204..79324e237 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs @@ -67,7 +67,7 @@ namespace Duplicati.GUI.TrayIcon List args = new List(_args); Dictionary options = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args); - if (Duplicati.Library.Utility.Utility.IsClientWindows && !Duplicati.Library.Utility.Utility.ParseBoolOption(options, DETACHED_PROCESS)) + if (Duplicati.Library.Utility.Utility.IsClientWindows && (Duplicati.Library.AutoUpdater.UpdaterManager.IsRunningInUpdateEnvironment || !Duplicati.Library.Utility.Utility.ParseBoolOption(options, DETACHED_PROCESS))) Duplicati.Library.Utility.Win32.AttachConsole(Duplicati.Library.Utility.Win32.ATTACH_PARENT_PROCESS); foreach (string s in args) diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index 6328d461e..6f0857cf8 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -18,6 +18,8 @@ using System; using System.Linq; using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; namespace Duplicati.Library.AutoUpdater { @@ -34,6 +36,11 @@ namespace Duplicati.Library.AutoUpdater public static class UpdaterManager { + /// + /// The magic exit code that signals an update has been installed and that the app should restart + /// + public const int MAGIC_EXIT_CODE = 126; + private static readonly System.Security.Cryptography.RSACryptoServiceProvider SIGN_KEY = AutoUpdateSettings.SignKey; private static readonly string[] MANIFEST_URLS = AutoUpdateSettings.URLs; private static readonly string APPNAME = AutoUpdateSettings.AppName; @@ -1002,7 +1009,113 @@ namespace Duplicati.Library.AutoUpdater } } + private static KeyValuePair GetBestUpdateVersion(bool forcecheck = false) + { + if (forcecheck) + m_hasUpdateInstalled = null; + + // Check if there are updates installed, otherwise use current + KeyValuePair best = new KeyValuePair(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, SelfVersion); + if (HasUpdateInstalled) + best = m_hasUpdateInstalled.Value; + + if (INSTALLDIR != null && System.IO.File.Exists(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE))) + { + try + { + var current = System.IO.File.ReadAllText(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE)).Trim(); + if (!string.IsNullOrWhiteSpace(current)) + { + var targetfolder = System.IO.Path.Combine(INSTALLDIR, current); + var currentmanifest = ReadInstalledManifest(targetfolder); + if (currentmanifest != null && TryParseVersion(currentmanifest.Version) > TryParseVersion(best.Value.Version) && VerifyUnpackedFolder(targetfolder, currentmanifest)) + best = new KeyValuePair(targetfolder, currentmanifest); + } + } + catch (Exception ex) + { + if (OnError != null) + OnError(ex); + } + } + + return best; + } + + public static bool IsRunningInUpdateEnvironment => !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME))); + public static int RunFromMostRecent(System.Reflection.MethodInfo method, string[] cmdargs, AutoUpdateStrategy defaultstrategy = AutoUpdateStrategy.CheckDuring) + { + return RunFromMostRecentSpawn(method, cmdargs, defaultstrategy); + } + + public static int RunFromMostRecentSpawn(System.Reflection.MethodInfo method, string[] cmdargs, AutoUpdateStrategy defaultstrategy = AutoUpdateStrategy.CheckDuring) + { + // If the update is disabled, go straight in + //if (DISABLE_UPDATE_DOMAIN) + // return RunMethod(method, cmdargs); + + // If we are not the primary entry, just execute + if (IsRunningInUpdateEnvironment) + { + int r = 0; + WrapWithUpdater(defaultstrategy, () => { + r = RunMethod(method, cmdargs); + }); + + return r; + } + + var args = Environment.CommandLine; + var app = Environment.GetCommandLineArgs().First(); + args = args.Substring(app.Length); + + if (!Path.IsPathRooted(app)) + app = Path.Combine(InstalledBaseDir, app); + + var executable = Path.GetFileName(app); + + while (true) + { + var best = GetBestUpdateVersion(true); + var folder = best.Key; + + var pi = new System.Diagnostics.ProcessStartInfo(Path.Combine(folder, executable), args) + { + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardInput = true, + RedirectStandardOutput = true, + ErrorDialog = false, + }; + pi.EnvironmentVariables.Clear(); + + var cur = Environment.GetEnvironmentVariables(); + foreach (var e in cur.Keys) + if (e is string) + pi.EnvironmentVariables[(string)e] = cur[(string)e] as string; + + pi.EnvironmentVariables[string.Format(BASEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME)] = InstalledBaseDir; + pi.EnvironmentVariables["LOCALIZATION_FOLDER"] = InstalledBaseDir; + + var proc = System.Diagnostics.Process.Start(pi); + var tasks = Task.WhenAll( + Console.OpenStandardInput().CopyToAsync(proc.StandardInput.BaseStream), + proc.StandardOutput.BaseStream.CopyToAsync(Console.OpenStandardOutput()), + proc.StandardError.BaseStream.CopyToAsync(Console.OpenStandardError()) + ); + + proc.WaitForExit(); + tasks.Wait(1000); + + if (proc.ExitCode != MAGIC_EXIT_CODE) + return proc.ExitCode; + } + + } + + public static int RunFromMostRecentAppDomain(System.Reflection.MethodInfo method, string[] cmdargs, AutoUpdateStrategy defaultstrategy = AutoUpdateStrategy.CheckDuring) { // If the update is disabled, go straight in if (DISABLE_UPDATE_DOMAIN) diff --git a/Duplicati/Library/Localization/MoLocalizationService.cs b/Duplicati/Library/Localization/MoLocalizationService.cs index 2b94d6f1c..38667a471 100644 --- a/Duplicati/Library/Localization/MoLocalizationService.cs +++ b/Duplicati/Library/Localization/MoLocalizationService.cs @@ -40,13 +40,18 @@ namespace Duplicati.Library.Localization /// public const string LOCALIZATIONDIR_ENVNAME = "LOCALIZATION_FOLDER"; + private static string LOCALIZATIONDIR_VALUE = + string.IsNullOrWhiteSpace(AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string) + ? Environment.GetEnvironmentVariable(LOCALIZATIONDIR_ENVNAME) + : AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string; + /// /// Path to search for extra .mo files in /// public static string[] SearchPaths = - string.IsNullOrWhiteSpace(AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string) + string.IsNullOrWhiteSpace(LOCALIZATIONDIR_VALUE) ? new string[] { Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) } - : (AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string).Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); + : (LOCALIZATIONDIR_VALUE).Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); /// diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs index ddc688ebe..28d116882 100644 --- a/Duplicati/Server/Program.cs +++ b/Duplicati/Server/Program.cs @@ -153,7 +153,7 @@ namespace Duplicati.Server return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, Duplicati.Library.AutoUpdater.AutoUpdateStrategy.Never); } - public static void RealMain(string[] args) + public static int RealMain(string[] args) { //If we are on Windows, append the bundled "win-tools" programs to the search path //We add it last, to allow the user to override with other versions @@ -192,7 +192,7 @@ namespace Duplicati.Server foreach(Library.Interface.ICommandLineArgument arg in SupportedCommands) Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription)); - return; + return 0; } else { @@ -246,7 +246,7 @@ namespace Duplicati.Server if (writeConsole) { Console.WriteLine(Strings.Program.StartupFailure(ex)); - return; + return 200; } throw new Exception(Strings.Program.StartupFailure(ex)); @@ -257,7 +257,7 @@ namespace Duplicati.Server if (writeConsole) { Console.WriteLine(Strings.Program.AnotherInstanceDetected); - return; + return 200; } throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected); @@ -323,7 +323,7 @@ namespace Duplicati.Server try { PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1)); - } + } catch (ArgumentOutOfRangeException) { //Bugfix for older Mono, slightly more resources used to avoid large values in the period field @@ -399,7 +399,10 @@ namespace Duplicati.Server { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString())); if (writeConsole) + { Console.WriteLine(Strings.Program.SeriousError(mex.ToString())); + return 100; + } else throw mex; } @@ -407,7 +410,10 @@ namespace Duplicati.Server { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString())); if (writeConsole) + { Console.WriteLine(Strings.Program.SeriousError(ex.ToString())); + return 100; + } else throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex); } @@ -434,8 +440,12 @@ namespace Duplicati.Server if (LogHandler != null) LogHandler.Dispose(); - } + + if (UpdatePoller != null && UpdatePoller.IsUpdateRequested) + return Library.AutoUpdater.UpdaterManager.MAGIC_EXIT_CODE; + + return 0; } public static Database.Connection GetDatabaseConnection(Dictionary commandlineOptions) diff --git a/Duplicati/Server/UpdatePollThread.cs b/Duplicati/Server/UpdatePollThread.cs index 4a66b284d..0c741aff5 100644 --- a/Duplicati/Server/UpdatePollThread.cs +++ b/Duplicati/Server/UpdatePollThread.cs @@ -35,6 +35,8 @@ namespace Duplicati.Server private AutoResetEvent m_waitSignal; private double m_downloadProgress; + public bool IsUpdateRequested { get; private set; } = false; + public UpdatePollerStates ThreadState { get; private set; } public double DownloadProgess { @@ -81,7 +83,10 @@ namespace Duplicati.Server public void ActivateUpdate() { if (Duplicati.Library.AutoUpdater.UpdaterManager.SetRunUpdate()) + { + IsUpdateRequested = true; Program.ApplicationExitEvent.Set(); + } } public void Terminate() -- cgit v1.2.3