mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-23 20:36:33 +01:00
CP-39382: Lock msi file while verifying and launching install process (#2973)
* CP-39382 adds lock around msi file while being verified and launched. Improves naming of variables in line with conventions. Signed-off-by: Chris Lancaster <Christopher.Lancaste1@citrix.com> * CP-39382 removes unnessessary usings, fixes background tasks running check. Tidies up structure. Signed-off-by: Chris Lancaster <Christopher.Lancaste1@citrix.com> * Further modifications. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com> * CP-39382 adds back in messages lost in merge conflict resolution Signed-off-by: Christophe25 <christopher.lancaste1@citrix.com> Co-authored-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
This commit is contained in:
parent
4656114963
commit
5b0b27eca1
@ -36,6 +36,7 @@ using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using XenAdmin.Actions;
|
||||
using XenAdmin.Actions.GUIActions;
|
||||
using XenAdmin.Actions.Updates;
|
||||
using XenAdmin.Core;
|
||||
using XenAdmin.Dialogs;
|
||||
|
||||
@ -135,25 +136,24 @@ namespace XenAdmin.Alerts
|
||||
|
||||
if (currentTasks)
|
||||
{
|
||||
if (new Dialogs.WarningDialogs.CloseXenCenterWarningDialog(true).ShowDialog() != DialogResult.OK)
|
||||
if (new Dialogs.WarningDialogs.CloseXenCenterWarningDialog(true).ShowDialog(parent) != DialogResult.OK)
|
||||
return;
|
||||
}
|
||||
|
||||
// Install the msi
|
||||
try
|
||||
{
|
||||
// Start the install process, it will handle closing of application.
|
||||
Process.Start(outputPathAndFileName);
|
||||
log.DebugFormat("Update {0} found and install started", updateAlert.Name);
|
||||
downloadAndInstallClientAction.ReleaseInstaller();
|
||||
Application.Exit();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (File.Exists(outputPathAndFileName))
|
||||
File.Delete(outputPathAndFileName);
|
||||
|
||||
log.Error("Exception occurred when starting the installation process.", e);
|
||||
throw;
|
||||
downloadAndInstallClientAction.ReleaseInstaller(true);
|
||||
|
||||
using (var dlg = new ErrorDialog(Messages.UPDATE_CLIENT_FAILED_INSTALLER_LAUNCH))
|
||||
dlg.ShowDialog(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using XenCenterLib;
|
||||
|
||||
namespace XenAdmin.Actions
|
||||
namespace XenAdmin.Actions.Updates
|
||||
{
|
||||
public class DownloadAndUpdateClientAction : AsyncAction, IByteProgressAction
|
||||
{
|
||||
@ -51,14 +51,15 @@ namespace XenAdmin.Actions
|
||||
//If you consider increasing this for any reason (I think 5 is already more than enough), have a look at the usage of SLEEP_TIME_BEFORE_RETRY_MS in DownloadFile() as well.
|
||||
private const int MAX_NUMBER_OF_TRIES = 5;
|
||||
|
||||
private readonly Uri address;
|
||||
private readonly string outputPathAndFileName;
|
||||
private readonly string updateName;
|
||||
private readonly bool downloadUpdate;
|
||||
private DownloadState updateDownloadState;
|
||||
private Exception updateDownloadError;
|
||||
private string checksum;
|
||||
private WebClient client;
|
||||
private readonly Uri _address;
|
||||
private readonly string _outputPathAndFileName;
|
||||
private readonly string _updateName;
|
||||
private readonly bool _downloadUpdate;
|
||||
private DownloadState _updateDownloadState;
|
||||
private Exception _updateDownloadError;
|
||||
private readonly string _checksum;
|
||||
private WebClient _client;
|
||||
private FileStream _msiStream;
|
||||
|
||||
public string ByteProgressDescription { get; set; }
|
||||
|
||||
@ -66,11 +67,11 @@ namespace XenAdmin.Actions
|
||||
: base(null, string.Format(Messages.DOWNLOAD_CLIENT_INSTALLER_ACTION_TITLE, updateName),
|
||||
string.Empty, true)
|
||||
{
|
||||
this.updateName = updateName;
|
||||
address = uri;
|
||||
downloadUpdate = address != null;
|
||||
this.outputPathAndFileName = outputFileName;
|
||||
this.checksum = checksum;
|
||||
_updateName = updateName;
|
||||
_address = uri;
|
||||
_downloadUpdate = _address != null;
|
||||
_outputPathAndFileName = outputFileName;
|
||||
_checksum = checksum;
|
||||
}
|
||||
|
||||
private void DownloadFile()
|
||||
@ -78,9 +79,9 @@ namespace XenAdmin.Actions
|
||||
int errorCount = 0;
|
||||
bool needToRetry = false;
|
||||
|
||||
client = new WebClient();
|
||||
client.DownloadProgressChanged += client_DownloadProgressChanged;
|
||||
client.DownloadFileCompleted += client_DownloadFileCompleted;
|
||||
_client = new WebClient();
|
||||
_client.DownloadProgressChanged += client_DownloadProgressChanged;
|
||||
_client.DownloadFileCompleted += client_DownloadFileCompleted;
|
||||
|
||||
// register event handler to detect changes in network connectivity
|
||||
NetworkChange.NetworkAvailabilityChanged += NetworkAvailabilityChanged;
|
||||
@ -94,31 +95,31 @@ namespace XenAdmin.Actions
|
||||
|
||||
needToRetry = false;
|
||||
|
||||
client.Proxy = XenAdminConfigManager.Provider.GetProxyFromSettings(null, false);
|
||||
_client.Proxy = XenAdminConfigManager.Provider.GetProxyFromSettings(null, false);
|
||||
|
||||
//start the download
|
||||
updateDownloadState = DownloadState.InProgress;
|
||||
client.DownloadFileAsync(address, outputPathAndFileName);
|
||||
_updateDownloadState = DownloadState.InProgress;
|
||||
_client.DownloadFileAsync(_address, _outputPathAndFileName);
|
||||
|
||||
bool updateDownloadCancelling = false;
|
||||
|
||||
//wait for the file to be downloaded
|
||||
while (updateDownloadState == DownloadState.InProgress)
|
||||
while (_updateDownloadState == DownloadState.InProgress)
|
||||
{
|
||||
if (!updateDownloadCancelling && (Cancelling || Cancelled))
|
||||
{
|
||||
Description = Messages.DOWNLOAD_AND_EXTRACT_ACTION_DOWNLOAD_CANCELLED_DESC;
|
||||
client.CancelAsync();
|
||||
_client.CancelAsync();
|
||||
updateDownloadCancelling = true;
|
||||
}
|
||||
|
||||
Thread.Sleep(SLEEP_TIME_TO_CHECK_DOWNLOAD_STATUS_MS);
|
||||
}
|
||||
|
||||
if (updateDownloadState == DownloadState.Cancelled)
|
||||
if (_updateDownloadState == DownloadState.Cancelled)
|
||||
throw new CancelledException();
|
||||
|
||||
if (updateDownloadState == DownloadState.Error)
|
||||
if (_updateDownloadState == DownloadState.Error)
|
||||
{
|
||||
needToRetry = true;
|
||||
|
||||
@ -128,100 +129,121 @@ namespace XenAdmin.Actions
|
||||
// logging only, it will retry again.
|
||||
log.ErrorFormat(
|
||||
"Error while downloading from '{0}'. Number of errors so far (including this): {1}. Trying maximum {2} times.",
|
||||
address, errorCount, MAX_NUMBER_OF_TRIES);
|
||||
_address, errorCount, MAX_NUMBER_OF_TRIES);
|
||||
|
||||
if (updateDownloadError == null)
|
||||
if (_updateDownloadError == null)
|
||||
log.Error("An unknown error occurred.");
|
||||
else
|
||||
log.Error(updateDownloadError);
|
||||
log.Error(_updateDownloadError);
|
||||
}
|
||||
} while (errorCount < MAX_NUMBER_OF_TRIES && needToRetry);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.DownloadProgressChanged -= client_DownloadProgressChanged;
|
||||
client.DownloadFileCompleted -= client_DownloadFileCompleted;
|
||||
_client.DownloadProgressChanged -= client_DownloadProgressChanged;
|
||||
_client.DownloadFileCompleted -= client_DownloadFileCompleted;
|
||||
|
||||
NetworkChange.NetworkAvailabilityChanged -= NetworkAvailabilityChanged;
|
||||
|
||||
client.Dispose();
|
||||
_client.Dispose();
|
||||
}
|
||||
|
||||
//if this is still the case after having retried MAX_NUMBER_OF_TRIES number of times.
|
||||
if (updateDownloadState == DownloadState.Error)
|
||||
if (_updateDownloadState == DownloadState.Error)
|
||||
{
|
||||
log.ErrorFormat("Giving up - Maximum number of retries ({0}) has been reached.", MAX_NUMBER_OF_TRIES);
|
||||
throw updateDownloadError ?? new Exception(Messages.ERROR_UNKNOWN);
|
||||
throw _updateDownloadError ?? new Exception(Messages.ERROR_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
private void NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
|
||||
{
|
||||
if (!e.IsAvailable && client != null && updateDownloadState == DownloadState.InProgress)
|
||||
if (!e.IsAvailable && _client != null && _updateDownloadState == DownloadState.InProgress)
|
||||
{
|
||||
updateDownloadError = new WebException(Messages.NETWORK_CONNECTIVITY_ERROR);
|
||||
updateDownloadState = DownloadState.Error;
|
||||
client.CancelAsync();
|
||||
_updateDownloadError = new WebException(Messages.NETWORK_CONNECTIVITY_ERROR);
|
||||
_updateDownloadState = DownloadState.Error;
|
||||
_client.CancelAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Run()
|
||||
{
|
||||
if (downloadUpdate)
|
||||
{
|
||||
log.InfoFormat("Downloading '{0}' installer (from '{1}') to '{2}'", updateName, address, outputPathAndFileName);
|
||||
Description = string.Format(Messages.DOWNLOAD_CLIENT_INSTALLER_ACTION_DESCRIPTION, updateName);
|
||||
LogDescriptionChanges = false;
|
||||
DownloadFile();
|
||||
LogDescriptionChanges = true;
|
||||
if (!_downloadUpdate)
|
||||
return;
|
||||
|
||||
if (IsCompleted || Cancelled)
|
||||
return;
|
||||
log.InfoFormat("Downloading '{0}' installer (from '{1}') to '{2}'", _updateName, _address, _outputPathAndFileName);
|
||||
Description = string.Format(Messages.DOWNLOAD_CLIENT_INSTALLER_ACTION_DESCRIPTION, _updateName);
|
||||
LogDescriptionChanges = false;
|
||||
DownloadFile();
|
||||
LogDescriptionChanges = true;
|
||||
|
||||
if (Cancelling)
|
||||
throw new CancelledException();
|
||||
}
|
||||
if (IsCompleted || Cancelled)
|
||||
return;
|
||||
|
||||
if (Cancelling)
|
||||
throw new CancelledException();
|
||||
|
||||
if (!File.Exists(_outputPathAndFileName))
|
||||
throw new Exception(Messages.DOWNLOAD_CLIENT_INSTALLER_MSI_NOT_FOUND);
|
||||
|
||||
ValidateMsi();
|
||||
|
||||
if (!File.Exists(outputPathAndFileName))
|
||||
throw new Exception(Messages.DOWNLOAD_CLIENT_INSTALLER_MSI_NOT_FOUND);
|
||||
|
||||
Description = Messages.COMPLETED;
|
||||
}
|
||||
|
||||
protected override void CleanOnError()
|
||||
{
|
||||
ReleaseInstaller(true);
|
||||
}
|
||||
|
||||
public void ReleaseInstaller(bool deleteMsi = false)
|
||||
{
|
||||
_msiStream?.Dispose();
|
||||
|
||||
if (!deleteMsi)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(_outputPathAndFileName))
|
||||
File.Delete(_outputPathAndFileName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateMsi()
|
||||
{
|
||||
using (FileStream stream = new FileStream(outputPathAndFileName, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
var calculatedChecksum = string.Empty;
|
||||
Description = Messages.UPDATE_CLIENT_VALIDATING_INSTALLER;
|
||||
|
||||
var hash = StreamUtilities.ComputeHash(stream, out _);
|
||||
if (hash != null)
|
||||
calculatedChecksum = string.Join("", hash.Select(b => $"{b:x2}"));
|
||||
_msiStream = new FileStream(_outputPathAndFileName, FileMode.Open, FileAccess.Read);
|
||||
|
||||
// Check if calculatedChecksum matches what is in chcupdates.xml
|
||||
if (!checksum.Equals(calculatedChecksum, StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new Exception(Messages.UPDATE_CLIENT_INVALID_CHECKSUM );
|
||||
}
|
||||
var calculatedChecksum = string.Empty;
|
||||
|
||||
bool valid = false;
|
||||
var hash = StreamUtilities.ComputeHash(_msiStream, out _);
|
||||
if (hash != null)
|
||||
calculatedChecksum = string.Join(string.Empty, hash.Select(b => $"{b:x2}"));
|
||||
|
||||
// Check if calculatedChecksum matches what is in chcupdates.xml
|
||||
if (!_checksum.Equals(calculatedChecksum, StringComparison.InvariantCultureIgnoreCase))
|
||||
throw new Exception(Messages.UPDATE_CLIENT_INVALID_CHECKSUM );
|
||||
|
||||
bool valid;
|
||||
try
|
||||
{
|
||||
// Check digital signature of .msi
|
||||
using (var basicSigner = X509Certificate.CreateFromSignedFile(outputPathAndFileName))
|
||||
using (var basicSigner = X509Certificate.CreateFromSignedFile(_outputPathAndFileName))
|
||||
{
|
||||
using (var cert = new X509Certificate2(basicSigner))
|
||||
{
|
||||
valid = cert.Verify();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception(Messages.UPDATE_CLIENT_FAILED_CERTIFICATE_CHECK, e);
|
||||
}
|
||||
|
||||
|
||||
if (!valid)
|
||||
throw new Exception(Messages.UPDATE_CLIENT_INVALID_DIGITAL_CERTIFICATE);
|
||||
@ -230,7 +252,7 @@ namespace XenAdmin.Actions
|
||||
private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
|
||||
{
|
||||
int pc = (int)(95.0 * e.BytesReceived / e.TotalBytesToReceive);
|
||||
var descr = string.Format(Messages.DOWNLOAD_CLIENT_INSTALLER_ACTION_PROGRESS_DESCRIPTION, updateName,
|
||||
var descr = string.Format(Messages.DOWNLOAD_CLIENT_INSTALLER_ACTION_PROGRESS_DESCRIPTION, _updateName,
|
||||
Util.DiskSizeString(e.BytesReceived, "F1"),
|
||||
Util.DiskSizeString(e.TotalBytesToReceive));
|
||||
ByteProgressDescription = descr;
|
||||
@ -239,31 +261,31 @@ namespace XenAdmin.Actions
|
||||
|
||||
private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
|
||||
{
|
||||
if (e.Cancelled && updateDownloadState == DownloadState.Error) // cancelled due to network connectivity issue (see NetworkAvailabilityChanged)
|
||||
if (e.Cancelled && _updateDownloadState == DownloadState.Error) // cancelled due to network connectivity issue (see NetworkAvailabilityChanged)
|
||||
return;
|
||||
|
||||
if (e.Cancelled)
|
||||
{
|
||||
updateDownloadState = DownloadState.Cancelled;
|
||||
log.DebugFormat("Client update '{0}' download cancelled by the user", updateName);
|
||||
_updateDownloadState = DownloadState.Cancelled;
|
||||
log.DebugFormat("Client update '{0}' download cancelled by the user", _updateName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Error != null)
|
||||
{
|
||||
updateDownloadError = e.Error;
|
||||
log.DebugFormat("Client update '{0}' download failed", updateName);
|
||||
updateDownloadState = DownloadState.Error;
|
||||
_updateDownloadError = e.Error;
|
||||
log.DebugFormat("Client update '{0}' download failed", _updateName);
|
||||
_updateDownloadState = DownloadState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
updateDownloadState = DownloadState.Completed;
|
||||
log.DebugFormat("Client update '{0}' download completed successfully", updateName);
|
||||
_updateDownloadState = DownloadState.Completed;
|
||||
log.DebugFormat("Client update '{0}' download completed successfully", _updateName);
|
||||
}
|
||||
|
||||
public override void RecomputeCanCancel()
|
||||
{
|
||||
CanCancel = !Cancelling && !IsCompleted && (updateDownloadState == DownloadState.InProgress);
|
||||
CanCancel = !Cancelling && !IsCompleted && (_updateDownloadState == DownloadState.InProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
XenModel/Messages.Designer.cs
generated
18
XenModel/Messages.Designer.cs
generated
@ -37240,6 +37240,15 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An error occurred when launching the downloaded installer. Please see the logs for more information..
|
||||
/// </summary>
|
||||
public static string UPDATE_CLIENT_FAILED_INSTALLER_LAUNCH {
|
||||
get {
|
||||
return ResourceManager.GetString("UPDATE_CLIENT_FAILED_INSTALLER_LAUNCH", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The checksum of the downloaded installer does not match the expected value..
|
||||
/// </summary>
|
||||
@ -37258,6 +37267,15 @@ namespace XenAdmin {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Validating the downloaded installer....
|
||||
/// </summary>
|
||||
public static string UPDATE_CLIENT_VALIDATING_INSTALLER {
|
||||
get {
|
||||
return ResourceManager.GetString("UPDATE_CLIENT_VALIDATING_INSTALLER", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You have applied filters to the list of update notifications. Do you want to dismiss all update notifications for every connected server, or only the update notifications you have chosen to view?
|
||||
///
|
||||
|
@ -12874,12 +12874,18 @@ Verify that the file is a valid {2} export.</value>
|
||||
<data name="UPDATE_CLIENT_FAILED_CERTIFICATE_CHECK" xml:space="preserve">
|
||||
<value>Could not validate the certificate associated with the downloaded installer.</value>
|
||||
</data>
|
||||
<data name="UPDATE_CLIENT_FAILED_INSTALLER_LAUNCH" xml:space="preserve">
|
||||
<value>An error occurred when launching the downloaded installer. Please see the logs for more information.</value>
|
||||
</data>
|
||||
<data name="UPDATE_CLIENT_INVALID_CHECKSUM" xml:space="preserve">
|
||||
<value>The checksum of the downloaded installer does not match the expected value.</value>
|
||||
</data>
|
||||
<data name="UPDATE_CLIENT_INVALID_DIGITAL_CERTIFICATE" xml:space="preserve">
|
||||
<value>Invalid digital signature on msi.</value>
|
||||
</data>
|
||||
<data name="UPDATE_CLIENT_VALIDATING_INSTALLER" xml:space="preserve">
|
||||
<value>Validating the downloaded installer...</value>
|
||||
</data>
|
||||
<data name="UPDATE_DISMISS_ALL_CONTINUE" xml:space="preserve">
|
||||
<value>You have applied filters to the list of update notifications. Do you want to dismiss all update notifications for every connected server, or only the update notifications you have chosen to view?
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user