From 024e03c1ae9d956834b9ccc4d4f91a991507b27c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 26 Jul 2022 17:20:48 -0700 Subject: Abstract file system to remove Core to Core.Native dependency The only dependency Core had on Core.Native was for FileSystem which would be better served as an extensibility service everywhere anyway. This moves FileSystem to Core and exposes it via IFileSystem from Extensibility for use everywhere. Core now carries no native code dependencies. --- .../Services/IFileSystem.cs | 25 +++++ .../WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 5 +- src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs | 23 ++--- .../Bundles/CreateBundleExeCommand.cs | 8 +- .../Bundles/HarvestBundlePackageCommand.cs | 5 +- .../CommandLine/ExtractSubcommand.cs | 9 +- .../Inscribe/InscribeBundleCommand.cs | 12 +-- .../Inscribe/InscribeBundleEngineCommand.cs | 10 +- src/wix/WixToolset.Core.Native/DateTimeInterop.cs | 4 +- src/wix/WixToolset.Core.Native/FileSystem.cs | 110 --------------------- .../WixToolset.Core.Native.csproj | 1 - .../WixToolset.Core.TestPackage/BundleExtractor.cs | 17 +++- .../Bind/MergeModulesCommand.cs | 22 ++++- .../CommandLine/ValidateSubcommand.cs | 5 +- .../Validate/ValidateDatabaseCommand.cs | 7 +- .../WixToolset.Core/Bind/TransferFilesCommand.cs | 25 ++++- .../ExtensibilityServices/FileSystem.cs | 79 +++++++++++++++ src/wix/WixToolset.Core/LayoutCreator.cs | 5 +- src/wix/WixToolset.Core/WixToolset.Core.csproj | 9 +- .../WixToolset.Core/WixToolsetServiceProvider.cs | 1 + 20 files changed, 216 insertions(+), 166 deletions(-) create mode 100644 src/api/wix/WixToolset.Extensibility/Services/IFileSystem.cs delete mode 100644 src/wix/WixToolset.Core.Native/FileSystem.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/FileSystem.cs (limited to 'src') diff --git a/src/api/wix/WixToolset.Extensibility/Services/IFileSystem.cs b/src/api/wix/WixToolset.Extensibility/Services/IFileSystem.cs new file mode 100644 index 00000000..d633c823 --- /dev/null +++ b/src/api/wix/WixToolset.Extensibility/Services/IFileSystem.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Extensibility.Services +{ + /// + /// Abstracts basic file system operations. + /// + public interface IFileSystem + { + /// + /// Copies a file. + /// + /// The file to copy. + /// The destination file. + /// Allow hardlinks. + void CopyFile(string source, string destination, bool allowHardlink); + + /// + /// Moves a file. + /// + /// The file to move. + /// The destination file. + void MoveFile(string source, string destination); + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 44fb84c7..db4fbf0e 100644 --- a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -28,6 +28,7 @@ namespace WixToolset.Core.Burn this.ServiceProvider = context.ServiceProvider; this.Messaging = context.ServiceProvider.GetService(); + this.FileSystem = context.ServiceProvider.GetService(); this.BackendHelper = context.ServiceProvider.GetService(); this.InternalBurnBackendHelper = context.ServiceProvider.GetService(); @@ -49,6 +50,8 @@ namespace WixToolset.Core.Burn private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private IBackendHelper BackendHelper { get; } private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } @@ -507,7 +510,7 @@ namespace WixToolset.Core.Burn } { - var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); + var command = new CreateBundleExeCommand(this.Messaging, this.FileSystem, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); command.Execute(); fileTransfers.Add(command.Transfer); diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs index e3d0f0af..e87e32c7 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs @@ -27,16 +27,19 @@ namespace WixToolset.Core.Burn.Bundles private BinaryReader binaryReader; private readonly List attachedContainerPayloadNames; + private readonly IFileSystem fileSystem; /// /// Creates a BurnReader for reading a PE file. /// - /// + /// Messaging. + /// File system. /// File to read. - private BurnReader(IMessaging messaging, string fileExe) + private BurnReader(IMessaging messaging, IFileSystem fileSystem, string fileExe) : base(messaging, fileExe) { this.attachedContainerPayloadNames = new List(); + this.fileSystem = fileSystem; } /// @@ -47,13 +50,14 @@ namespace WixToolset.Core.Burn.Bundles /// /// Opens a Burn reader. /// - /// + /// Messaging. + /// File system. /// Path to file. /// Burn reader. - public static BurnReader Open(IMessaging messaging, string fileExe) + public static BurnReader Open(IMessaging messaging, IFileSystem fileSystem, string fileExe) { var binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); - var reader = new BurnReader(messaging, fileExe) + var reader = new BurnReader(messaging, fileSystem, fileExe) { binaryReader = binaryReader, }; @@ -96,8 +100,7 @@ namespace WixToolset.Core.Burn.Bundles var cabinet = new Cabinet(tempCabPath); cabinet.Extract(outputDirectory); - Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); - FileSystem.MoveFile(manifestOriginalPath, manifestPath); + this.fileSystem.MoveFile(manifestOriginalPath, manifestPath); var document = new XmlDocument(); document.Load(manifestPath); @@ -114,8 +117,7 @@ namespace WixToolset.Core.Burn.Bundles var sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); var destinationPath = Path.Combine(outputDirectory, filePathNode.Value); - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - FileSystem.MoveFile(sourcePath, destinationPath); + this.fileSystem.MoveFile(sourcePath, destinationPath); } foreach (XmlNode payload in payloads) @@ -183,8 +185,7 @@ namespace WixToolset.Core.Burn.Bundles var sourcePath = Path.Combine(outputDirectory, (string)entry.Key); var destinationPath = Path.Combine(outputDirectory, (string)entry.Value); - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - FileSystem.MoveFile(sourcePath, destinationPath); + this.fileSystem.MoveFile(sourcePath, destinationPath); } return true; diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs index 67a6cb4a..7fe3c4ec 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -9,7 +9,6 @@ namespace WixToolset.Core.Burn.Bundles using System.Runtime.InteropServices; using System.Text; using System.Xml; - using WixToolset.Core.Native; using WixToolset.Data; using WixToolset.Data.Burn; using WixToolset.Data.Symbols; @@ -19,9 +18,10 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBundleExeCommand { - public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) + public CreateBundleExeCommand(IMessaging messaging, IFileSystem fileSystem, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) { this.Messaging = messaging; + this.FileSystem = fileSystem; this.BackendHelper = backendHelper; this.IntermediateFolder = intermediateFolder; this.OutputPath = outputPath; @@ -35,6 +35,8 @@ namespace WixToolset.Core.Burn.Bundles private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private IBackendHelper BackendHelper { get; } private string IntermediateFolder { get; } @@ -68,7 +70,7 @@ namespace WixToolset.Core.Burn.Bundles this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers); - FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false); + this.FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false); File.SetAttributes(bundleTempPath, FileAttributes.Normal); var fourPartVersion = this.GetFourPartVersion(this.BundleSymbol); diff --git a/src/wix/WixToolset.Core.Burn/Bundles/HarvestBundlePackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/HarvestBundlePackageCommand.cs index 89e0da98..bc065f2f 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/HarvestBundlePackageCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/HarvestBundlePackageCommand.cs @@ -18,6 +18,7 @@ namespace WixToolset.Core.Burn.Bundles public HarvestBundlePackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, string intermediateFolder, WixBundlePayloadSymbol payloadSymbol, WixBundleBundlePackagePayloadSymbol packagePayloadSymbol, Dictionary packagePayloadsById) { this.Messaging = serviceProvider.GetService(); + this.FileSystem = serviceProvider.GetService(); this.BackendHelper = serviceProvider.GetService(); this.BackendExtensions = backendExtensions; this.IntermediateFolder = intermediateFolder; @@ -29,6 +30,8 @@ namespace WixToolset.Core.Burn.Bundles private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private IBackendHelper BackendHelper { get; } private IEnumerable BackendExtensions { get; } @@ -66,7 +69,7 @@ namespace WixToolset.Core.Burn.Bundles var sourcePath = this.PackagePayload.SourceFile.Path; var sourceLineNumbers = this.PackagePayload.SourceLineNumbers; - using (var burnReader = BurnReader.Open(this.Messaging, sourcePath)) + using (var burnReader = BurnReader.Open(this.Messaging, this.FileSystem, sourcePath)) { if (burnReader.Invalid) { diff --git a/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs b/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs index 605ee045..5d6edc33 100644 --- a/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs +++ b/src/wix/WixToolset.Core.Burn/CommandLine/ExtractSubcommand.cs @@ -7,21 +7,20 @@ namespace WixToolset.Core.Burn.CommandLine using System.Threading; using System.Threading.Tasks; using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.Inscribe; using WixToolset.Extensibility.Services; internal class ExtractSubcommand : BurnSubcommandBase { public ExtractSubcommand(IServiceProvider serviceProvider) { - this.ServiceProvider = serviceProvider; this.Messaging = serviceProvider.GetService(); + this.FileSystem = serviceProvider.GetService(); } - private IServiceProvider ServiceProvider { get; } - private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private string InputPath { get; set; } private string IntermediateFolder { get; set; } @@ -49,7 +48,7 @@ namespace WixToolset.Core.Burn.CommandLine var uxExtractPath = Path.Combine(this.ExtractPath, "BA"); - using (var reader = BurnReader.Open(this.Messaging, this.InputPath)) + using (var reader = BurnReader.Open(this.Messaging, this.FileSystem, this.InputPath)) { reader.ExtractUXContainer(uxExtractPath, this.IntermediateFolder); diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs index 6e071fe7..d68d372c 100644 --- a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs @@ -5,7 +5,6 @@ namespace WixToolset.Core.Burn.Inscribe using System; using System.IO; using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Native; using WixToolset.Extensibility.Services; internal class InscribeBundleCommand @@ -13,6 +12,7 @@ namespace WixToolset.Core.Burn.Inscribe public InscribeBundleCommand(IServiceProvider serviceProvider, string inputPath, string signedEngineFile, string outputPath, string intermediateFolder) { this.Messaging = serviceProvider.GetService(); + this.FileSystem = serviceProvider.GetService(); this.IntermediateFolder = intermediateFolder; this.InputFilePath = inputPath; this.SignedEngineFile = signedEngineFile; @@ -21,6 +21,8 @@ namespace WixToolset.Core.Burn.Inscribe private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private string IntermediateFolder { get; } private string InputFilePath { get; } @@ -34,9 +36,9 @@ namespace WixToolset.Core.Burn.Inscribe var inscribed = false; var tempFile = Path.Combine(this.IntermediateFolder, "~bundle_engine_signed.exe"); - using (var reader = BurnReader.Open(this.Messaging, this.InputFilePath)) + using (var reader = BurnReader.Open(this.Messaging, this.FileSystem, this.InputFilePath)) { - FileSystem.CopyFile(this.SignedEngineFile, tempFile, allowHardlink: false); + this.FileSystem.CopyFile(this.SignedEngineFile, tempFile, allowHardlink: false); using (var writer = BurnWriter.Open(this.Messaging, tempFile)) { @@ -44,9 +46,7 @@ namespace WixToolset.Core.Burn.Inscribe } } - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); - - FileSystem.MoveFile(tempFile, this.OutputFile); + this.FileSystem.MoveFile(tempFile, this.OutputFile); return inscribed; } diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs index e607a28f..c00788ca 100644 --- a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs @@ -5,7 +5,6 @@ namespace WixToolset.Core.Burn.Inscribe using System; using System.IO; using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Native; using WixToolset.Extensibility.Services; internal class InscribeBundleEngineCommand @@ -13,6 +12,7 @@ namespace WixToolset.Core.Burn.Inscribe public InscribeBundleEngineCommand(IServiceProvider serviceProvider, string inputPath, string outputPath, string intermediateFolder) { this.Messaging = serviceProvider.GetService(); + this.FileSystem = serviceProvider.GetService(); this.IntermediateFolder = intermediateFolder; this.InputFilePath = inputPath; this.OutputFile = outputPath; @@ -20,6 +20,8 @@ namespace WixToolset.Core.Burn.Inscribe private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private string IntermediateFolder { get; } private string InputFilePath { get; } @@ -30,7 +32,7 @@ namespace WixToolset.Core.Burn.Inscribe { var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe"); - using (var reader = BurnReader.Open(this.Messaging, this.InputFilePath)) + using (var reader = BurnReader.Open(this.Messaging, this.FileSystem, this.InputFilePath)) using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) { reader.Stream.Seek(0, SeekOrigin.Begin); @@ -56,9 +58,7 @@ namespace WixToolset.Core.Burn.Inscribe // TODO: update writer with detached container signatures. } - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); - - FileSystem.MoveFile(tempFile, this.OutputFile); + this.FileSystem.MoveFile(tempFile, this.OutputFile); } } } diff --git a/src/wix/WixToolset.Core.Native/DateTimeInterop.cs b/src/wix/WixToolset.Core.Native/DateTimeInterop.cs index d2a0ba2b..9ab4f813 100644 --- a/src/wix/WixToolset.Core.Native/DateTimeInterop.cs +++ b/src/wix/WixToolset.Core.Native/DateTimeInterop.cs @@ -20,8 +20,8 @@ namespace WixToolset.Core.Native { // dateTime.ToLocalTime() does not match FileTimeToLocalFileTime() for some reason. // so we need to call FileTimeToLocalFileTime() from kernel32.dll. - long filetime = dateTime.ToFileTime(); - long localTime = 0; + var filetime = dateTime.ToFileTime(); + var localTime = 0L; FileTimeToLocalFileTime(ref filetime, ref localTime); FileTimeToDosDateTime(ref localTime, out cabDate, out cabTime); } diff --git a/src/wix/WixToolset.Core.Native/FileSystem.cs b/src/wix/WixToolset.Core.Native/FileSystem.cs deleted file mode 100644 index 3c59c90c..00000000 --- a/src/wix/WixToolset.Core.Native/FileSystem.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Native -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Runtime.InteropServices; - using System.Security.AccessControl; - using System.Threading; - - /// - /// File system helpers. - /// - public static class FileSystem - { - /// - /// Copies a file. - /// - /// The file to copy. - /// The destination file. - /// Allow hardlinks. - public static void CopyFile(string source, string destination, bool allowHardlink) - { - EnsureDirectoryWithoutFile(destination); - - var hardlinked = false; - - if (allowHardlink) - { - ActionWithRetries(() => hardlinked = CreateHardLink(destination, source, IntPtr.Zero)); - } - - if (!hardlinked) - { -#if DEBUG - var er = Marshal.GetLastWin32Error(); -#endif - - ActionWithRetries(() => File.Copy(source, destination, overwrite: true)); - } - } - - /// - /// Moves a file. - /// - /// The file to move. - /// The destination file. - public static void MoveFile(string source, string destination) - { - EnsureDirectoryWithoutFile(destination); - - ActionWithRetries(() => File.Move(source, destination)); - } - - /// - /// Reset the ACLs on a set of files. - /// - /// The list of file paths to set ACLs. - public static void ResetAcls(IEnumerable files) - { - var aclReset = new FileSecurity(); - aclReset.SetAccessRuleProtection(false, false); - - foreach (var file in files) - { - var fileInfo = new FileInfo(file); - ActionWithRetries(() => fileInfo.SetAccessControl(aclReset)); - } - } - - /// - /// Executes an action and retries on any exception up to a few times. Primarily - /// intended for use with file system operations that might get interrupted by - /// external systems (usually anti-virus). - /// - /// Action to execute. - /// Maximum retry attempts. - public static void ActionWithRetries(Action action, int maxRetries = 3) - { - for (var attempt = 1; attempt <= maxRetries; ++attempt) - { - try - { - action(); - break; - } - catch when (attempt < maxRetries) - { - Thread.Sleep(250); - } - } - } - - private static void EnsureDirectoryWithoutFile(string path) - { - var directory = Path.GetDirectoryName(path); - - if (!String.IsNullOrEmpty(directory)) - { - ActionWithRetries(() => Directory.CreateDirectory(directory)); - } - - ActionWithRetries(() => File.Delete(path)); - } - - [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); - } -} diff --git a/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj b/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj index 6553a276..b5c83fba 100644 --- a/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj +++ b/src/wix/WixToolset.Core.Native/WixToolset.Core.Native.csproj @@ -24,6 +24,5 @@ - diff --git a/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs index 3cc98e3a..f8cf49df 100644 --- a/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs +++ b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -39,7 +39,7 @@ namespace WixToolset.Core.TestPackage { var result = new ExtractBAContainerResult(); Directory.CreateDirectory(tempFolderPath); - using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + using (var burnReader = BurnReader.Open(messaging, new TestFileSystem(), bundleFilePath)) { result.Success = burnReader.ExtractUXContainer(baFolderPath, tempFolderPath); @@ -138,5 +138,20 @@ namespace WixToolset.Core.TestPackage document.Load(Path.Combine(baFolderPath, "manifest.xml")); return document; } + + private class TestFileSystem : IFileSystem + { + public void CopyFile(string source, string destination, bool allowHardlink) + { + Directory.CreateDirectory(Path.GetDirectoryName(destination)); + File.Copy(source, destination); + } + + public void MoveFile(string source, string destination) + { + Directory.CreateDirectory(Path.GetDirectoryName(destination)); + File.Move(source, destination); + } + } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs index 7af0ca19..7e6a7de0 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -71,10 +71,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind { merge = MsmInterop.GetMsmMerge(); - FileSystem.ActionWithRetries(() => merge.OpenLog(logPath)); + ActionWithRetries(() => merge.OpenLog(logPath)); logOpen = true; - FileSystem.ActionWithRetries(() => merge.OpenDatabase(this.OutputPath)); + ActionWithRetries(() => merge.OpenDatabase(this.OutputPath)); databaseOpen = true; var featureModulesByMergeId = this.Section.Symbols.OfType().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key); @@ -99,7 +99,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } this.Messaging.Write(VerboseMessages.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); - FileSystem.ActionWithRetries(() => merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage)); + ActionWithRetries(() => merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage)); moduleOpen = true; trackedFiles.Add(this.BackendHelper.TrackFile(wixMergeRow.SourceFile, TrackedFileType.Input, wixMergeRow.SourceLineNumbers)); @@ -340,5 +340,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.TrackedFiles = trackedFiles; } + + internal static void ActionWithRetries(Action action, int maxRetries = 3) + { + for (var attempt = 1; attempt <= maxRetries; ++attempt) + { + try + { + action(); + break; + } + catch when (attempt < maxRetries) + { + Thread.Sleep(250); + } + } + } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs index 7d77aa30..eb014086 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs @@ -16,10 +16,13 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine public ValidateSubcommand(IServiceProvider serviceProvider) { this.Messaging = serviceProvider.GetService(); + this.FileSystem = serviceProvider.GetService(); } private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private string DatabasePath { get; set; } private string WixpdbPath { get; set; } @@ -76,7 +79,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine data = WindowsInstallerData.Load(this.WixpdbPath); } - var command = new ValidateDatabaseCommand(this.Messaging, this.IntermediateFolder, this.DatabasePath, data, this.CubeFiles, this.Ices, this.SuppressIces); + var command = new ValidateDatabaseCommand(this.Messaging, this.FileSystem, this.IntermediateFolder, this.DatabasePath, data, this.CubeFiles, this.Ices, this.SuppressIces); command.Execute(); return Task.FromResult(this.Messaging.EncounteredError ? 1 : 0); diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs index fa01a119..06fa6817 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs @@ -18,9 +18,10 @@ namespace WixToolset.Core.WindowsInstaller.Validate // Set of ICEs that have equivalent-or-better checks in WiX. private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; - public ValidateDatabaseCommand(IMessaging messaging, string intermediateFolder, string databasePath, WindowsInstallerData data, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) + public ValidateDatabaseCommand(IMessaging messaging, IFileSystem fileSystem, string intermediateFolder, string databasePath, WindowsInstallerData data, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) { this.Messaging = messaging; + this.FileSystem = fileSystem; this.Data = data; this.DatabasePath = databasePath; this.CubeFiles = cubeFiles; @@ -40,6 +41,8 @@ namespace WixToolset.Core.WindowsInstaller.Validate private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private WindowsInstallerData Data { get; } private string DatabasePath { get; } @@ -71,7 +74,7 @@ namespace WixToolset.Core.WindowsInstaller.Validate var workingDatabasePath = Path.Combine(this.IntermediateFolder, workingDatabaseFilename); try { - FileSystem.CopyFile(this.DatabasePath, workingDatabasePath, allowHardlink: false); + this.FileSystem.CopyFile(this.DatabasePath, workingDatabasePath, allowHardlink: false); var attributes = File.GetAttributes(workingDatabasePath); File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); diff --git a/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs index b3b74fbc..87c2590f 100644 --- a/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs +++ b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs @@ -5,7 +5,7 @@ namespace WixToolset.Core.Bind using System; using System.Collections.Generic; using System.IO; - using WixToolset.Core.Native; + using System.Security.AccessControl; using WixToolset.Data; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; @@ -13,16 +13,19 @@ namespace WixToolset.Core.Bind internal class TransferFilesCommand { - public TransferFilesCommand(IMessaging messaging, IEnumerable extensions, IEnumerable fileTransfers, bool resetAcls) + public TransferFilesCommand(IMessaging messaging, IFileSystem fileSystem, IEnumerable extensions, IEnumerable fileTransfers, bool resetAcls) { this.Extensions = extensions; this.Messaging = messaging; + this.FileSystem = fileSystem; this.FileTransfers = fileTransfers; this.ResetAcls = resetAcls; } private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + private IEnumerable Extensions { get; } private IEnumerable FileTransfers { get; } @@ -158,7 +161,7 @@ namespace WixToolset.Core.Bind { try { - FileSystem.ResetAcls(destinationFiles); + this.AclReset(destinationFiles); } catch (Exception e) { @@ -177,7 +180,7 @@ namespace WixToolset.Core.Bind } } - FileSystem.CopyFile(source, destination, allowHardlink: true); + this.FileSystem.CopyFile(source, destination, allowHardlink: true); } private void MoveFile(string source, string destination) @@ -190,7 +193,19 @@ namespace WixToolset.Core.Bind } } - FileSystem.MoveFile(source, destination); + this.FileSystem.MoveFile(source, destination); + } + + private void AclReset(IEnumerable files) + { + var aclReset = new FileSecurity(); + aclReset.SetAccessRuleProtection(false, false); + + foreach (var file in files) + { + var fileInfo = new FileInfo(file); + ExtensibilityServices.FileSystem.ActionWithRetries(() => fileInfo.SetAccessControl(aclReset)); + } } } } diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileSystem.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileSystem.cs new file mode 100644 index 00000000..8bda4966 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileSystem.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + using System.Threading; + using WixToolset.Extensibility.Services; + + internal class FileSystem : IFileSystem + { + public void CopyFile(string source, string destination, bool allowHardlink) + { + EnsureDirectoryWithoutFile(destination); + + var hardlinked = false; + + if (allowHardlink) + { + ActionWithRetries(() => hardlinked = CreateHardLink(destination, source, IntPtr.Zero)); + } + + if (!hardlinked) + { +#if DEBUG + var er = Marshal.GetLastWin32Error(); +#endif + + ActionWithRetries(() => File.Copy(source, destination, overwrite: true)); + } + } + + public void MoveFile(string source, string destination) + { + EnsureDirectoryWithoutFile(destination); + + ActionWithRetries(() => File.Move(source, destination)); + } + + /// + /// Executes an action and retries on any exception up to a few times. Primarily + /// intended for use with file system operations that might get interrupted by + /// external systems (usually anti-virus). + /// + /// Action to execute. + /// Maximum retry attempts. + internal static void ActionWithRetries(Action action, int maxRetries = 3) + { + for (var attempt = 1; attempt <= maxRetries; ++attempt) + { + try + { + action(); + break; + } + catch when (attempt < maxRetries) + { + Thread.Sleep(250); + } + } + } + + private static void EnsureDirectoryWithoutFile(string path) + { + var directory = Path.GetDirectoryName(path); + + if (!String.IsNullOrEmpty(directory)) + { + ActionWithRetries(() => Directory.CreateDirectory(directory)); + } + + ActionWithRetries(() => File.Delete(path)); + } + + [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); + } +} diff --git a/src/wix/WixToolset.Core/LayoutCreator.cs b/src/wix/WixToolset.Core/LayoutCreator.cs index 7a143680..3e7ecf3a 100644 --- a/src/wix/WixToolset.Core/LayoutCreator.cs +++ b/src/wix/WixToolset.Core/LayoutCreator.cs @@ -21,10 +21,13 @@ namespace WixToolset.Core internal LayoutCreator(IServiceProvider serviceProvider) { this.Messaging = serviceProvider.GetService(); + this.FileSystem = serviceProvider.GetService(); } private IMessaging Messaging { get; } + private IFileSystem FileSystem { get; } + public void Layout(ILayoutContext context) { // Pre-layout. @@ -42,7 +45,7 @@ namespace WixToolset.Core { this.Messaging.Write(VerboseMessages.LayingOutMedia()); - var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.ResetAcls); + var command = new TransferFilesCommand(this.Messaging, this.FileSystem, context.Extensions, context.FileTransfers, context.ResetAcls); command.Execute(); } diff --git a/src/wix/WixToolset.Core/WixToolset.Core.csproj b/src/wix/WixToolset.Core/WixToolset.Core.csproj index c2551b21..f2ee3a2c 100644 --- a/src/wix/WixToolset.Core/WixToolset.Core.csproj +++ b/src/wix/WixToolset.Core/WixToolset.Core.csproj @@ -14,20 +14,13 @@ true - - - - - + diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs index 525b9dd9..fa6b369d 100644 --- a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs +++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs @@ -27,6 +27,7 @@ namespace WixToolset.Core this.AddService((provider, singletons) => AddSingleton(singletons, new LayoutServices(provider))); this.AddService((provider, singletons) => AddSingleton(singletons, new BackendHelper(provider))); this.AddService((provider, singletons) => AddSingleton(singletons, new PathResolver())); + this.AddService((provider, singletons) => AddSingleton(singletons, new FileSystem())); this.AddService((provider, singletons) => AddSingleton(singletons, new WixBranding())); // Transients. -- cgit v1.2.3-55-g6feb