ProductUpgrader: platform specific upgrade directories

Different platform have different structures for where the upgrade
application and logs need to go. This change introduces the
architecture for making this platform specific.

Description of different components involved in upgrade and how they interact
with different platforms.
---

Component                | description
-----------------------  | -----------
Upgrader Application     | Temporary copy of upgrader application upgrade main application
gvfs.config              | config file for upgrade related settings
Upgrade Package Download | Location to download upgrade package
Upgrader Package Unpack  | Location to unpack the downloaded update package
Upgrader Logs            | Directory to write upgrade logs to
Upgrade Available File   | File indicating whether an upgrade is available

Component                | data scope                      | Windows                                                     | Mac
------------------------ | ------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------
Upgrader Application     | Protected                       | ProgramData\GVFS\GVFS.Upgrade\Tools                         | /usr/local/vfsforgit_upgrader/Tools
gvfs.config              | Protected                       | ProgramData\GVFS\gvfs.config                                | /usr/local/vfsforgit/gvfs.config
Upgrade Package Download | Protected                       | ProgramData\GVFS\GVFS.Upgrade\Download                      | /usr/local/vfsforgit_upgrader/Download
Upgrader Package Unpack  | Protected                       | ProgramData\GVFS\GVFS.Upgrade\Download\InstallerTemp        | /usr/local/vfsforgit_upgrader/Download/InstallerTemp
Upgrader Logs            | Writable by user account        | ProgramData\GVFS\GVFS.Upgrade\UpgraderLogs                  | ~/Library/Application Support/GVFS/GVFS.Upgrade/UpgraderLogs
Upgrade Available File   | Written by GVFS.Service process | ProgramData\GVFS\GVFS.Upgrade\Tools\HighestAvailableVersion | ~/Library/Application Support/GVFS/GVFS.Upgrade/HighestAvailableVersion
This commit is contained in:
Jameson Miller 2019-05-30 10:58:45 -04:00
Родитель 36a512adf3
Коммит 0e5fb4a76d
23 изменённых файлов: 184 добавлений и 56 удалений

Просмотреть файл

@ -72,6 +72,27 @@ namespace GVFS.Common
public abstract bool IsElevated();
public abstract string GetCurrentUser();
public abstract string GetUserIdFromLoginSessionId(int sessionId, ITracer tracer);
/// <summary>
/// Get the directory for upgrades that is permissioned to
/// require elevated privileges to modify. This can be used for
/// data that we don't want normal user accounts to modify.
/// </summary>
public abstract string GetUpgradeProtectedDataDirectory();
/// <summary>
/// Directory that upgrader log directory should be placed
/// in. There can be multiple log directories, so this is the
/// containing directory to place them in.
/// </summary>
public abstract string GetUpgradeLogDirectoryParentDirectory();
/// <summary>
/// Directory that contains the file indicating that a new
/// version is available.
/// </summary>
public abstract string GetUpgradeHighestAvailableVersionDirectory();
public abstract void ConfigureVisualStudio(string gitBinPath, ITracer tracer);
public abstract bool TryGetGVFSHooksPathAndVersion(out string hooksPaths, out string hooksVersion, out string error);

Просмотреть файл

@ -48,9 +48,6 @@ namespace GVFS.Common
: base(currentVersion, tracer, dryRun, noVerify, fileSystem)
{
this.Config = upgraderConfig;
string upgradesDirectoryPath = ProductUpgraderInfo.GetUpgradesDirectoryPath();
this.fileSystem.CreateDirectory(upgradesDirectoryPath);
}
public GitHubUpgraderConfig Config { get; private set; }

Просмотреть файл

@ -20,7 +20,6 @@ namespace GVFS.Common
public abstract class ProductUpgrader : IDisposable
{
public const string ToolsDirectory = "Tools";
protected readonly Version installedVersion;
protected readonly ITracer tracer;
protected readonly PhysicalFileSystem fileSystem;
@ -29,8 +28,6 @@ namespace GVFS.Common
protected bool dryRun;
protected ProductUpgraderPlatformStrategy productUpgraderPlatformStrategy;
private static readonly string UpgraderToolName = GVFSPlatform.Instance.Constants.GVFSUpgraderExecutableName;
protected ProductUpgrader(
string currentVersion,
ITracer tracer,
@ -168,14 +165,13 @@ namespace GVFS.Common
public abstract bool TryRunInstaller(InstallActionWrapper installActionWrapper, out string error);
public virtual bool TrySetupToolsDirectory(out string upgraderToolPath, out string error)
public virtual bool TrySetupUpgradeApplicationDirectory(out string upgradeApplicationPath, out string error)
{
string rootDirectoryPath = ProductUpgraderInfo.GetUpgradesDirectoryPath();
string toolsDirectoryPath = Path.Combine(rootDirectoryPath, ToolsDirectory);
string upgradeApplicationDirectory = ProductUpgraderInfo.GetUpgradeApplicationDirectory();
if (!this.productUpgraderPlatformStrategy.TryPrepareApplicationDirectory(out error))
{
upgraderToolPath = null;
upgradeApplicationPath = null;
return false;
}
@ -183,31 +179,33 @@ namespace GVFS.Common
error = null;
try
{
this.fileSystem.CopyDirectoryRecursive(currentPath, toolsDirectoryPath);
this.fileSystem.CopyDirectoryRecursive(currentPath, upgradeApplicationDirectory);
}
catch (UnauthorizedAccessException e)
{
error = string.Join(
Environment.NewLine,
"File copy error - " + e.Message,
$"Make sure you have write permissions to directory {toolsDirectoryPath} and run {GVFSConstants.UpgradeVerbMessages.GVFSUpgradeConfirm} again.");
$"Make sure you have write permissions to directory {upgradeApplicationDirectory} and run {GVFSConstants.UpgradeVerbMessages.GVFSUpgradeConfirm} again.");
}
catch (IOException e)
{
error = "File copy error - " + e.Message;
this.TraceException(e, nameof(this.TrySetupToolsDirectory), $"Error copying {currentPath} to {toolsDirectoryPath}.");
this.TraceException(e, nameof(this.TrySetupUpgradeApplicationDirectory), $"Error copying {currentPath} to {upgradeApplicationDirectory}.");
}
if (string.IsNullOrEmpty(error))
{
// There was no error - set upgradeToolPath and return success.
upgraderToolPath = Path.Combine(toolsDirectoryPath, UpgraderToolName);
upgradeApplicationPath = Path.Combine(
upgradeApplicationDirectory,
GVFSPlatform.Instance.Constants.GVFSUpgraderExecutableName);
return true;
}
else
{
// Encountered error - do not set upgrade tool path and return failure.
upgraderToolPath = null;
upgradeApplicationPath = null;
return false;
}
}

Просмотреть файл

@ -9,15 +9,20 @@ namespace GVFS.Common
public const string UpgradeDirectoryName = "GVFS.Upgrade";
public const string LogDirectory = "UpgraderLogs";
public const string DownloadDirectory = "Downloads";
/// <summary>
/// This is the name of the directory that the Upgrader Application is copied to
/// when running upgrade. It is called "Tools", as this is what the directory was
/// originally named, but has since been renamed in code to be more descriptive.
/// </summary>
public const string ApplicationDirectory = "Tools";
public const string HighestAvailableVersionFileName = "HighestAvailableVersion";
public static bool IsLocalUpgradeAvailable(ITracer tracer, string gvfsDataRoot)
public static bool IsLocalUpgradeAvailable(ITracer tracer, string highestAvailableVersionDirectory)
{
try
{
string upgradesDirectory = Path.Combine(gvfsDataRoot, UpgradeDirectoryName);
return File.Exists(GetHighestAvailableVersionFilePath(upgradesDirectory));
return File.Exists(GetHighestAvailableVersionFilePath(highestAvailableVersionDirectory));
}
catch (Exception ex) when (
ex is IOException ||
@ -35,9 +40,9 @@ namespace GVFS.Common
return false;
}
private static string GetHighestAvailableVersionFilePath(string upgradesDirectory)
private static string GetHighestAvailableVersionFilePath(string highestAvailableVersionDirectory)
{
return Path.Combine(upgradesDirectory, HighestAvailableVersionFileName);
return Path.Combine(highestAvailableVersionDirectory, HighestAvailableVersionFileName);
}
private static EventMetadata CreateEventMetadata(Exception e)

Просмотреть файл

@ -21,19 +21,40 @@ namespace GVFS.Common
return ProcessHelper.GetCurrentProcessVersion();
}
public static string GetUpgradesDirectoryPath()
{
return GVFSPlatform.Instance.GetDataRootForGVFSComponent(UpgradeDirectoryName);
public static string GetUpgradeProtectedDataDirectory()
{
return GVFSPlatform.Instance.GetUpgradeProtectedDataDirectory();
}
public static string GetUpgradeApplicationDirectory()
{
return Path.Combine(
GetUpgradeProtectedDataDirectory(),
ProductUpgraderInfo.ApplicationDirectory);
}
public static string GetParentLogDirectoryPath()
{
return GVFSPlatform.Instance.GetUpgradeLogDirectoryParentDirectory();
}
public static string GetLogDirectoryPath()
{
return Path.Combine(GetUpgradesDirectoryPath(), LogDirectory);
return Path.Combine(
GVFSPlatform.Instance.GetUpgradeLogDirectoryParentDirectory(),
ProductUpgraderInfo.LogDirectory);
}
public static string GetAssetDownloadsPath()
{
return Path.Combine(GetUpgradesDirectoryPath(), DownloadDirectory);
return Path.Combine(
GVFSPlatform.Instance.GetUpgradeProtectedDataDirectory(),
ProductUpgraderInfo.DownloadDirectory);
}
public static string GetHighestAvailableVersionDirectory()
{
return GVFSPlatform.Instance.GetUpgradeHighestAvailableVersionDirectory();
}
public void DeleteAllInstallerDownloads()
@ -53,7 +74,7 @@ namespace GVFS.Common
public void RecordHighestAvailableVersion(Version highestAvailableVersion)
{
string highestAvailableVersionFile = GetHighestAvailableVersionFilePath(GetUpgradesDirectoryPath());
string highestAvailableVersionFile = GetHighestAvailableVersionFilePath(GetHighestAvailableVersionDirectory());
if (highestAvailableVersion == null)
{

Просмотреть файл

@ -9,6 +9,11 @@ namespace GVFS.Hooks.HooksPlatform
return MacPlatform.GetDataRootForGVFSImplementation();
}
public static string GetUpgradeHighestAvailableVersionDirectory()
{
return MacPlatform.GetUpgradeHighestAvailableVersionDirectoryImplementation();
}
public static bool TryGetGVFSEnlistmentRoot(string directory, out string enlistmentRoot, out string errorMessage)
{
return MacPlatform.TryGetGVFSEnlistmentRootImplementation(directory, out enlistmentRoot, out errorMessage);

Просмотреть файл

@ -41,5 +41,10 @@ namespace GVFS.Hooks.HooksPlatform
{
return WindowsPlatform.GetDataRootForGVFSImplementation();
}
public static string GetUpgradeHighestAvailableVersionDirectory()
{
return WindowsPlatform.GetUpgradeHighestAvailableVersionDirectoryImplementation();
}
}
}

Просмотреть файл

@ -114,7 +114,7 @@ namespace GVFS.Hooks
int randomValue = random.Next(0, 100);
if (randomValue <= reminderFrequency &&
ProductUpgraderInfo.IsLocalUpgradeAvailable(tracer: null, gvfsDataRoot: GVFSHooksPlatform.GetDataRootForGVFS()))
ProductUpgraderInfo.IsLocalUpgradeAvailable(tracer: null, highestAvailableVersionDirectory: GVFSHooksPlatform.GetUpgradeHighestAvailableVersionDirectory()))
{
Console.WriteLine(Environment.NewLine + GVFSConstants.UpgradeVerbMessages.ReminderNotification);
}

Просмотреть файл

@ -1,5 +1,6 @@
using System;
using System.IO;
using GVFS.Common;
using GVFS.Platform.POSIX;
namespace GVFS.Platform.Mac
@ -27,9 +28,24 @@ namespace GVFS.Platform.Mac
return POSIXPlatform.TryGetGVFSEnlistmentRootImplementation(directory, DotGVFSRoot, out enlistmentRoot, out errorMessage);
}
public static string GetUpgradeHighestAvailableVersionDirectoryImplementation()
{
return GetUpgradeNonProtectedDirectoryImplementation();
}
public static string GetUpgradeNonProtectedDirectoryImplementation()
{
return Path.Combine(GetDataRootForGVFSImplementation(), ProductUpgraderInfo.UpgradeDirectoryName);
}
public static string GetNamedPipeNameImplementation(string enlistmentRoot)
{
return POSIXPlatform.GetNamedPipeNameImplementation(enlistmentRoot, DotGVFSRoot);
}
private string GetUpgradeNonProtectedDataDirectory()
{
return GetUpgradeNonProtectedDirectoryImplementation();
}
}
}

Просмотреть файл

@ -14,6 +14,8 @@ namespace GVFS.Platform.Mac
{
public partial class MacPlatform : POSIXPlatform
{
private const string UpgradeProtectedDataDirectory = "/usr/local/vfsforgit_upgrader";
public MacPlatform()
{
}
@ -65,6 +67,26 @@ namespace GVFS.Platform.Mac
{
return new MacFileBasedLock(fileSystem, tracer, lockPath);
}
public override string GetUpgradeProtectedDataDirectory()
{
return UpgradeProtectedDataDirectory;
}
public override string GetUpgradeHighestAvailableVersionDirectory()
{
return GetUpgradeHighestAvailableVersionDirectoryImplementation();
}
/// <summary>
/// This is the directory in which the upgradelogs directory should go.
/// There can be multiple logs directories, so here we return the containing
// directory.
/// </summary>
public override string GetUpgradeLogDirectoryParentDirectory()
{
return this.GetUpgradeNonProtectedDataDirectory();
}
public override Dictionary<string, string> GetPhysicalDiskInfo(string path, bool sizeStatsOnly)
{

Просмотреть файл

@ -21,20 +21,19 @@ namespace GVFS.Platform.Mac
public override bool TryPrepareApplicationDirectory(out string error)
{
string rootDirectoryPath = ProductUpgraderInfo.GetUpgradesDirectoryPath();
string toolsDirectoryPath = Path.Combine(rootDirectoryPath, ProductUpgrader.ToolsDirectory);
string upgradeApplicationDirectory = ProductUpgraderInfo.GetUpgradeApplicationDirectory();
Exception deleteDirectoryException;
if (this.FileSystem.DirectoryExists(toolsDirectoryPath) &&
!this.FileSystem.TryDeleteDirectory(toolsDirectoryPath, out deleteDirectoryException))
if (this.FileSystem.DirectoryExists(upgradeApplicationDirectory) &&
!this.FileSystem.TryDeleteDirectory(upgradeApplicationDirectory, out deleteDirectoryException))
{
error = $"Failed to delete {toolsDirectoryPath} - {deleteDirectoryException.Message}";
error = $"Failed to delete {upgradeApplicationDirectory} - {deleteDirectoryException.Message}";
this.TraceException(deleteDirectoryException, nameof(this.TryPrepareApplicationDirectory), $"Error deleting {toolsDirectoryPath}.");
this.TraceException(deleteDirectoryException, nameof(this.TryPrepareApplicationDirectory), $"Error deleting {upgradeApplicationDirectory}.");
return false;
}
this.FileSystem.CreateDirectory(toolsDirectoryPath);
this.FileSystem.CreateDirectory(upgradeApplicationDirectory);
error = null;
return true;

Просмотреть файл

@ -131,4 +131,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

Просмотреть файл

@ -111,6 +111,16 @@ namespace GVFS.Platform.Windows
return true;
}
public static string GetUpgradeProtectedDataDirectoryImplementation()
{
return Path.Combine(GetDataRootForGVFSImplementation(), ProductUpgraderInfo.UpgradeDirectoryName);
}
public static string GetUpgradeHighestAvailableVersionDirectoryImplementation()
{
return GetUpgradeProtectedDataDirectoryImplementation();
}
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(StdHandle std);

Просмотреть файл

@ -337,6 +337,21 @@ namespace GVFS.Platform.Windows
}
}
public override string GetUpgradeLogDirectoryParentDirectory()
{
return this.GetUpgradeProtectedDataDirectory();
}
public override string GetUpgradeHighestAvailableVersionDirectory()
{
return this.GetUpgradeProtectedDataDirectory();
}
public override string GetUpgradeProtectedDataDirectory()
{
return GetUpgradeProtectedDataDirectoryImplementation();
}
public override Dictionary<string, string> GetPhysicalDiskInfo(string path, bool sizeStatsOnly) => WindowsPhysicalDiskInfo.GetPhysicalDiskInfo(path, sizeStatsOnly);
public override bool IsConsoleOutputRedirectedToFile()

Просмотреть файл

@ -40,22 +40,21 @@ namespace GVFS.Platform.Windows
public override bool TryPrepareApplicationDirectory(out string error)
{
string rootDirectoryPath = ProductUpgraderInfo.GetUpgradesDirectoryPath();
string toolsDirectoryPath = Path.Combine(rootDirectoryPath, ProductUpgrader.ToolsDirectory);
string upgradeApplicationDirectory = ProductUpgraderInfo.GetUpgradeApplicationDirectory();
Exception deleteDirectoryException;
if (this.FileSystem.DirectoryExists(toolsDirectoryPath) &&
!this.FileSystem.TryDeleteDirectory(toolsDirectoryPath, out deleteDirectoryException))
if (this.FileSystem.DirectoryExists(upgradeApplicationDirectory) &&
!this.FileSystem.TryDeleteDirectory(upgradeApplicationDirectory, out deleteDirectoryException))
{
error = $"Failed to delete {toolsDirectoryPath} - {deleteDirectoryException.Message}";
error = $"Failed to delete {upgradeApplicationDirectory} - {deleteDirectoryException.Message}";
this.TraceException(deleteDirectoryException, nameof(this.TryPrepareApplicationDirectory), $"Error deleting {toolsDirectoryPath}.");
this.TraceException(deleteDirectoryException, nameof(this.TryPrepareApplicationDirectory), $"Error deleting {upgradeApplicationDirectory}.");
return false;
}
if (!this.FileSystem.TryCreateOrUpdateDirectoryToAdminModifyPermissions(
this.Tracer,
toolsDirectoryPath,
upgradeApplicationDirectory,
out error))
{
return false;

Просмотреть файл

@ -313,7 +313,7 @@ namespace GVFS.Service
// Create GVFS.Service and GVFS.Upgrade related directories (if they don't already exist)
Directory.CreateDirectory(serviceDataRootPath, serviceDataRootSecurity);
Directory.CreateDirectory(this.serviceDataLocation, serviceDataRootSecurity);
Directory.CreateDirectory(ProductUpgraderInfo.GetUpgradesDirectoryPath(), serviceDataRootSecurity);
Directory.CreateDirectory(ProductUpgraderInfo.GetUpgradeProtectedDataDirectory(), serviceDataRootSecurity);
// Ensure the ACLs are set correctly on any files or directories that were already created (e.g. after upgrading VFS4G)
Directory.SetAccessControl(serviceDataRootPath, serviceDataRootSecurity);

Просмотреть файл

@ -22,10 +22,10 @@ namespace GVFS.UnitTests.Windows.Common.Upgrader
}
[TestCase]
public void TrySetupToolsDirectoryFailsIfCreateToolsDirectoryFails()
public void TrySetupUpgradeApplicationDirectoryFailsIfCreateToolsDirectoryFails()
{
this.mockFileSystem.TryCreateOrUpdateDirectoryToAdminModifyPermissionsShouldSucceed = false;
this.upgrader.TrySetupToolsDirectory(out string _, out string _).ShouldBeFalse();
this.upgrader.TrySetupUpgradeApplicationDirectory(out string _, out string _).ShouldBeFalse();
this.mockFileSystem.TryCreateOrUpdateDirectoryToAdminModifyPermissionsShouldSucceed = true;
}

Просмотреть файл

@ -18,7 +18,9 @@ namespace GVFS.UnitTests.Common
{
private Mock<PhysicalFileSystem> mockFileSystem;
private ProductUpgraderInfo productUpgraderInfo;
private string upgradeDirectory;
private string expectedNewVersionExistsFileName = "HighestAvailableVersion";
private string expectedNewVersionExistsFilePath;
private MockTracer tracer;
@ -26,7 +28,7 @@ namespace GVFS.UnitTests.Common
[SetUp]
public void SetUp()
{
this.upgradeDirectory = GVFSPlatform.Instance.GetDataRootForGVFSComponent(ProductUpgraderInfo.UpgradeDirectoryName);
this.upgradeDirectory = ProductUpgraderInfo.GetHighestAvailableVersionDirectory();
this.expectedNewVersionExistsFilePath = Path.Combine(this.upgradeDirectory, this.expectedNewVersionExistsFileName);
this.mockFileSystem = new Mock<PhysicalFileSystem>();

Просмотреть файл

@ -93,8 +93,6 @@ namespace GVFS.UnitTests.Common
MockLocalGVFSConfig gvfsConfig = this.ConstructDefaultGitHubConfigBuilder()
.Build();
this.fileSystemMock.Setup(m => m.CreateDirectory(It.IsAny<string>()));
bool success = ProductUpgrader.TryCreateUpgrader(
this.tracer,
this.fileSystemMock.Object,

Просмотреть файл

@ -102,6 +102,21 @@ namespace GVFS.UnitTests.Mock.Common
{
return new Dictionary<string, string>();
}
public override string GetUpgradeProtectedDataDirectory()
{
return this.GetDataRootForGVFSComponent(ProductUpgraderInfo.UpgradeDirectoryName);
}
public override string GetUpgradeLogDirectoryParentDirectory()
{
return this.GetUpgradeProtectedDataDirectory();
}
public override string GetUpgradeHighestAvailableVersionDirectory()
{
return this.GetUpgradeProtectedDataDirectory();
}
public override void InitializeEnlistmentACLs(string enlistmentPath)
{

Просмотреть файл

@ -95,16 +95,16 @@ namespace GVFS.UnitTests.Mock.Upgrader
this.FakeUpgradeRelease = release;
}
public override bool TrySetupToolsDirectory(out string upgraderToolPath, out string error)
public override bool TrySetupUpgradeApplicationDirectory(out string upgradeApplicationPath, out string error)
{
if (this.failActionTypes.HasFlag(ActionType.CopyTools))
{
upgraderToolPath = null;
upgradeApplicationPath = null;
error = "Unable to copy upgrader tools";
return false;
}
upgraderToolPath = @"mock:\ProgramData\GVFS\GVFS.Upgrade\Tools\GVFS.Upgrader.exe";
upgradeApplicationPath = @"mock:\ProgramData\GVFS\GVFS.Upgrade\Tools\GVFS.Upgrader.exe";
error = null;
return true;
}

Просмотреть файл

@ -131,21 +131,21 @@ namespace GVFS.CommandLine
{
// upgrader
this.CopyAllFiles(
ProductUpgraderInfo.GetUpgradesDirectoryPath(),
ProductUpgraderInfo.GetParentLogDirectoryPath(),
archiveFolderPath,
DeprecatedUpgradeLogsDirectory,
copySubFolders: true,
targetFolderName: Path.Combine(ProductUpgraderInfo.UpgradeDirectoryName, DeprecatedUpgradeLogsDirectory));
this.CopyAllFiles(
ProductUpgraderInfo.GetUpgradesDirectoryPath(),
ProductUpgraderInfo.GetParentLogDirectoryPath(),
archiveFolderPath,
ProductUpgraderInfo.LogDirectory,
copySubFolders: true,
targetFolderName: Path.Combine(ProductUpgraderInfo.UpgradeDirectoryName, ProductUpgraderInfo.LogDirectory));
this.LogDirectoryEnumeration(
ProductUpgraderInfo.GetUpgradesDirectoryPath(),
ProductUpgraderInfo.GetUpgradeProtectedDataDirectory(),
Path.Combine(archiveFolderPath, ProductUpgraderInfo.UpgradeDirectoryName),
ProductUpgraderInfo.DownloadDirectory,
"downloaded-assets.txt");

Просмотреть файл

@ -268,7 +268,7 @@ namespace GVFS.CommandLine
using (ITracer activity = this.tracer.StartActivity(nameof(this.TryCopyUpgradeTool), EventLevel.Informational))
{
if (!this.upgrader.TrySetupToolsDirectory(out upgraderExePath, out consoleError))
if (!this.upgrader.TrySetupUpgradeApplicationDirectory(out upgraderExePath, out consoleError))
{
return false;
}