@joebigelow / wix-1 / commits / f8749f8e

Provide useful error message when file system operations fail

Fixes 5417

Rob Mensching committed Nov 4, 2022 at 01:05 UTC f8749f8e438b150884449ad3e2e13aed750de679
21 files changed +152 -61
src/api/wix/WixToolset.Extensibility/Services/IFileSystem.cs
+9 -4
@@ -4,6 +4,7 @@ namespace WixToolset.Extensibility.Services
4 {
5 using System;
6 using System.IO;
7 + using WixToolset.Data;
8
9 /// <summary>
10 /// Abstracts basic file system operations.
@@ -13,34 +14,38 @@ namespace WixToolset.Extensibility.Services
14 /// <summary>
15 /// Copies a file.
16 /// </summary>
17 + /// <param name="sourceLineNumbers">Optional source line number requiring the copy.</param>
18 /// <param name="source">The file to copy.</param>
19 /// <param name="destination">The destination file.</param>
20 /// <param name="allowHardlink">Allow hardlinks.</param>
19 - void CopyFile(string source, string destination, bool allowHardlink);
21 + void CopyFile(SourceLineNumber sourceLineNumbers, string source, string destination, bool allowHardlink);
22
23 /// <summary>
24 /// Deletes a file.
25 /// </summary>
26 + /// <param name="sourceLineNumbers">Optional source line number requiring the delete.</param>
27 /// <param name="source">The file to delete.</param>
28 /// <param name="throwOnError">Indicates the file must be deleted. Default is a best effort delete.</param>
29 /// <param name="maxRetries">Maximum retry attempts. Default is 4.</param>
27 - void DeleteFile(string source, bool throwOnError = false, int maxRetries = 4);
30 + void DeleteFile(SourceLineNumber sourceLineNumbers, string source, bool throwOnError = false, int maxRetries = 4);
31
32 /// <summary>
33 /// Moves a file.
34 /// </summary>
35 + /// <param name="sourceLineNumbers">Optional source line number requiring the move.</param>
36 /// <param name="source">The file to move.</param>
37 /// <param name="destination">The destination file.</param>
34 - void MoveFile(string source, string destination);
38 + void MoveFile(SourceLineNumber sourceLineNumbers, string source, string destination);
39
40 /// <summary>
41 /// Opens a file.
42 /// </summary>
43 + /// <param name="sourceLineNumbers">Optional source line number requiring the file.</param>
44 /// <param name="path">The file to open.</param>
45 /// <param name="mode">A System.IO.FileMode value that specifies whether a file is created if one does not exist, and determines whether the contents of existing files are retained or overwritten.</param>
46 /// <param name="access">A System.IO.FileAccess value that specifies the operations that can be performed on the file.</param>
47 /// <param name="share">A System.IO.FileShare value specifying the type of access other threads have to the file.</param>
43 - FileStream OpenFile(string path, FileMode mode, FileAccess access, FileShare share);
48 + FileStream OpenFile(SourceLineNumber sourceLineNumbers, string path, FileMode mode, FileAccess access, FileShare share);
49
50 /// <summary>
51 /// Executes an action and retries on any exception a few times with short pause
src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs
+6 -6
@@ -56,7 +56,7 @@ namespace WixToolset.Core.Burn.Bundles
56 /// <returns>Burn reader.</returns>
57 public static BurnReader Open(IMessaging messaging, IFileSystem fileSystem, string fileExe)
58 {
59 - var binaryReader = new BinaryReader(fileSystem.OpenFile(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete));
59 + var binaryReader = new BinaryReader(fileSystem.OpenFile(null, fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete));
60 var reader = new BurnReader(messaging, fileSystem, fileExe)
61 {
62 binaryReader = binaryReader,
@@ -92,7 +92,7 @@ namespace WixToolset.Core.Burn.Bundles
92 var uxContainerSlot = this.AttachedContainers[0];
93
94 this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin);
95 - using (Stream tempCab = this.fileSystem.OpenFile(tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read))
95 + using (Stream tempCab = this.fileSystem.OpenFile(null, tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read))
96 {
97 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)uxContainerSlot.Size);
98 }
@@ -100,7 +100,7 @@ namespace WixToolset.Core.Burn.Bundles
100 var cabinet = new Cabinet(tempCabPath);
101 cabinet.Extract(outputDirectory);
102
103 - this.fileSystem.MoveFile(manifestOriginalPath, manifestPath);
103 + this.fileSystem.MoveFile(null, manifestOriginalPath, manifestPath);
104
105 var document = new XmlDocument();
106 document.Load(manifestPath);
@@ -117,7 +117,7 @@ namespace WixToolset.Core.Burn.Bundles
117 var sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value);
118 var destinationPath = Path.Combine(outputDirectory, filePathNode.Value);
119
120 - this.fileSystem.MoveFile(sourcePath, destinationPath);
120 + this.fileSystem.MoveFile(null, sourcePath, destinationPath);
121 }
122
123 foreach (XmlNode payload in payloads)
@@ -169,7 +169,7 @@ namespace WixToolset.Core.Burn.Bundles
169 var tempCabPath = Path.Combine(tempDirectory, $"a{i}.cab");
170
171 this.binaryReader.BaseStream.Seek(nextAddress, SeekOrigin.Begin);
172 - using (Stream tempCab = this.fileSystem.OpenFile(tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read))
172 + using (Stream tempCab = this.fileSystem.OpenFile(null, tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read))
173 {
174 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)cntnr.Size);
175 }
@@ -185,7 +185,7 @@ namespace WixToolset.Core.Burn.Bundles
185 var sourcePath = Path.Combine(outputDirectory, (string)entry.Key);
186 var destinationPath = Path.Combine(outputDirectory, (string)entry.Value);
187
188 - this.fileSystem.MoveFile(sourcePath, destinationPath);
188 + this.fileSystem.MoveFile(null, sourcePath, destinationPath);
189 }
190
191 return true;
src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs
+5 -4
@@ -43,14 +43,14 @@ namespace WixToolset.Core.Burn.Bundles
43 {
44 var writer = new BurnWriter(messaging, fileSystem, fileExe);
45
46 - using (var binaryReader = new BinaryReader(fileSystem.OpenFile(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)))
46 + using (var binaryReader = new BinaryReader(fileSystem.OpenFile(null, fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)))
47 {
48 writer.Initialize(binaryReader);
49 }
50
51 if (!writer.Invalid)
52 {
53 - writer.binaryWriter = new BinaryWriter(fileSystem.OpenFile(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete));
53 + writer.binaryWriter = new BinaryWriter(fileSystem.OpenFile(null, fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete));
54 }
55
56 return writer;
@@ -102,12 +102,13 @@ namespace WixToolset.Core.Burn.Bundles
102 /// <summary>
103 /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it.
104 /// </summary>
105 + /// <param name="sourceLineNumbers">Source line numbers for the container.</param>
106 /// <param name="fileContainer">File path to append to the current exe.</param>
107 /// <param name="container">Container section represented by the fileContainer.</param>
108 /// <returns>true if the container data is successfully appended; false otherwise</returns>
108 - public bool AppendContainer(string fileContainer, BurnCommon.Container container)
109 + public bool AppendContainer(SourceLineNumber sourceLineNumbers, string fileContainer, BurnCommon.Container container)
110 {
110 - using (var reader = this.fileSystem.OpenFile(fileContainer, FileMode.Open, FileAccess.Read, FileShare.Read))
111 + using (var reader = this.fileSystem.OpenFile(sourceLineNumbers, fileContainer, FileMode.Open, FileAccess.Read, FileShare.Read))
112 {
113 return this.AppendContainer(reader, reader.Length, container);
114 }
src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs
+3 -3
@@ -71,7 +71,7 @@ namespace WixToolset.Core.Burn.Bundles
71
72 this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers);
73
74 - this.FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false);
74 + this.FileSystem.CopyFile(this.BundleSymbol.SourceLineNumbers, stubFile, bundleTempPath, allowHardlink: false);
75 File.SetAttributes(bundleTempPath, FileAttributes.Normal);
76
77 var fourPartVersion = this.GetFourPartVersion(this.BundleSymbol);
@@ -88,7 +88,7 @@ namespace WixToolset.Core.Burn.Bundles
88 writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId);
89
90 // Always attach the UX container first
91 - writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX);
91 + writer.AppendContainer(this.UXContainer.SourceLineNumbers, this.UXContainer.WorkingPath, BurnWriter.Container.UX);
92
93 // Now append all other attached containers
94 foreach (var container in this.Containers)
@@ -98,7 +98,7 @@ namespace WixToolset.Core.Burn.Bundles
98 // The container was only created if it had payloads.
99 if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id)
100 {
101 - writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached);
101 + writer.AppendContainer(container.SourceLineNumbers, container.WorkingPath, BurnWriter.Container.Attached);
102 }
103 }
104 }
src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs
+2 -2
@@ -38,7 +38,7 @@ namespace WixToolset.Core.Burn.Inscribe
38
39 using (var reader = BurnReader.Open(this.Messaging, this.FileSystem, this.InputFilePath))
40 {
41 - this.FileSystem.CopyFile(this.SignedEngineFile, tempFile, allowHardlink: false);
41 + this.FileSystem.CopyFile(null, this.SignedEngineFile, tempFile, allowHardlink: false);
42
43 using (var writer = BurnWriter.Open(this.Messaging, this.FileSystem, tempFile))
44 {
@@ -46,7 +46,7 @@ namespace WixToolset.Core.Burn.Inscribe
46 }
47 }
48
49 - this.FileSystem.MoveFile(tempFile, this.OutputFile);
49 + this.FileSystem.MoveFile(null, tempFile, this.OutputFile);
50
51 return inscribed;
52 }
src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs
+2 -2
@@ -33,7 +33,7 @@ namespace WixToolset.Core.Burn.Inscribe
33 var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe");
34
35 using (var reader = BurnReader.Open(this.Messaging, this.FileSystem, this.InputFilePath))
36 - using (var writer = this.FileSystem.OpenFile(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete))
36 + using (var writer = this.FileSystem.OpenFile(null, tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete))
37 {
38 reader.Stream.Seek(0, SeekOrigin.Begin);
39
@@ -58,7 +58,7 @@ namespace WixToolset.Core.Burn.Inscribe
58 // TODO: update writer with detached container signatures.
59 }
60
61 - this.FileSystem.MoveFile(tempFile, this.OutputFile);
61 + this.FileSystem.MoveFile(null, tempFile, this.OutputFile);
62 }
63 }
64 }
src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs
+1 -1
@@ -19,7 +19,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
19 {
20 try
21 {
22 - using (var stream = fileSystem.OpenFile(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read))
22 + using (var stream = fileSystem.OpenFile(sourceLineNumbers, assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read))
23 using (var peReader = new PEReader(stream))
24 {
25 var reader = peReader.GetMetadataReader();
src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+1 -1
@@ -212,7 +212,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
212
213 if (softwareTags.Any())
214 {
215 - var command = new ProcessPackageSoftwareTagsCommand(section, this.WindowsInstallerBackendHelper, softwareTags, this.IntermediateFolder);
215 + var command = new ProcessPackageSoftwareTagsCommand(section, this.WindowsInstallerBackendHelper, this.FileSystem, softwareTags, this.IntermediateFolder);
216 command.Execute();
217
218 trackedFiles.AddRange(command.TrackedFiles);
src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+1 -1
@@ -401,7 +401,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
401 {
402 if (!String.IsNullOrEmpty(emptyFile))
403 {
404 - using (var fileStream = this.FileSystem.OpenFile(emptyFile, FileMode.Create, FileAccess.Write, FileShare.None))
404 + using (var fileStream = this.FileSystem.OpenFile(null, emptyFile, FileMode.Create, FileAccess.Write, FileShare.None))
405 {
406 }
407 }
src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs
+2 -3
@@ -5,7 +5,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 - using System.Runtime.CompilerServices;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Services;
10
@@ -42,8 +41,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
41 return true;
42 }
43
45 - using (var firstStream = this.FileSystem.OpenFile(firstPath, FileMode.Open, FileAccess.Read, FileShare.Read))
46 - using (var secondStream = this.FileSystem.OpenFile(secondPath, FileMode.Open, FileAccess.Read, FileShare.Read))
44 + using (var firstStream = this.FileSystem.OpenFile(null, firstPath, FileMode.Open, FileAccess.Read, FileShare.Read))
45 + using (var secondStream = this.FileSystem.OpenFile(null, secondPath, FileMode.Open, FileAccess.Read, FileShare.Read))
46 {
47 if (firstStream.Length != secondStream.Length)
48 {
src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs
+5 -2
@@ -15,10 +15,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
15
16 internal class ProcessPackageSoftwareTagsCommand
17 {
18 - public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IBackendHelper backendHelper, IEnumerable<WixPackageTagSymbol> softwareTags, string intermediateFolder)
18 + public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IBackendHelper backendHelper, IFileSystem fileSystem, IEnumerable<WixPackageTagSymbol> softwareTags, string intermediateFolder)
19 {
20 this.Section = section;
21 this.BackendHelper = backendHelper;
22 + this.FileSystem = fileSystem;
23 this.SoftwareTags = softwareTags;
24 this.IntermediateFolder = intermediateFolder;
25 }
@@ -29,6 +30,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
30
31 private IBackendHelper BackendHelper { get; }
32
33 + private IFileSystem FileSystem { get; }
34 +
35 private IEnumerable<WixPackageTagSymbol> SoftwareTags { get; }
36
37 public IReadOnlyCollection<ITrackedFile> TrackedFiles { get; private set; }
@@ -61,7 +64,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
64
65 trackedFiles.Add(this.BackendHelper.TrackFile(fileSymbol.Source.Path, TrackedFileType.Intermediate, tagRow.SourceLineNumbers));
66
64 - using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create))
67 + using (var fs = this.FileSystem.OpenFile(fileSymbol.SourceLineNumbers, fileSymbol.Source.Path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
68 {
69 CreateTagFile(fs, uniqueId, packageSymbol.Name, packageSymbol.Version, tagRow.Regid, packageSymbol.Manufacturer, persistentId);
70 }
src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+1 -1
@@ -84,7 +84,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
84 return;
85 }
86
87 - using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
87 + using (var fileStream = this.FileSystem.OpenFile(facade.SourceLineNumber, fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
88 {
89 if (Int32.MaxValue < fileStream.Length)
90 {
src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
+5 -5
@@ -51,7 +51,7 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
51
52 if (!String.Equals(this.InputPath, this.OutputPath, StringComparison.OrdinalIgnoreCase))
53 {
54 - this.FileSystem.CopyFile(this.InputPath, this.OutputPath, allowHardlink: false);
54 + this.FileSystem.CopyFile(null, this.InputPath, this.OutputPath, allowHardlink: false);
55 }
56
57 var attributes = File.GetAttributes(databasePath);
@@ -98,7 +98,7 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
98 Directory.CreateDirectory(hashPath);
99 hashPath = Path.Combine(hashPath, hashFileName);
100
101 - using (var fs = this.FileSystem.OpenFile(hashPath, FileMode.Create, FileAccess.Write, FileShare.None))
101 + using (var fs = this.FileSystem.OpenFile(null, hashPath, FileMode.Create, FileAccess.Write, FileShare.None))
102 {
103 int bytesRead;
104 var buffer = new byte[1024 * 4];
@@ -129,7 +129,7 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
129 Directory.CreateDirectory(certPath);
130 certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer"));
131
132 - using (var fs = this.FileSystem.OpenFile(certPath, FileMode.Create, FileAccess.Write, FileShare.None))
132 + using (var fs = this.FileSystem.OpenFile(null, certPath, FileMode.Create, FileAccess.Write, FileShare.None))
133 {
134 int bytesRead;
135 var buffer = new byte[1024 * 4];
@@ -223,9 +223,9 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
223 var certPath = Path.Combine(this.IntermediateFolder, "MsiDigitalCertificate");
224 Directory.CreateDirectory(certPath);
225 certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer"));
226 - this.FileSystem.DeleteFile(certPath, true);
226 + this.FileSystem.DeleteFile(null, certPath, true);
227
228 - using (var writer = new BinaryWriter(this.FileSystem.OpenFile(certPath, FileMode.Create, FileAccess.Write, FileShare.Read)))
228 + using (var writer = new BinaryWriter(this.FileSystem.OpenFile(null, certPath, FileMode.Create, FileAccess.Write, FileShare.Read)))
229 {
230 writer.Write(cert2.RawData);
231 writer.Close();
src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs
+3 -2
@@ -97,6 +97,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
97 {
98 if (null != record)
99 {
100 + embeddedCabinetRowsByDiskId.TryGetValue(diskId, out var cabinetMediaRow);
101 +
102 // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive,
103 // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work
104 var cabinetPath = Path.Combine(this.IntermediateFolder, "Media", diskId.ToString(CultureInfo.InvariantCulture), ".cab");
@@ -104,7 +106,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
106 // ensure the parent directory exists
107 Directory.CreateDirectory(Path.GetDirectoryName(cabinetPath));
108
107 - using (var fs = this.FileSystem.OpenFile(cabinetPath, FileMode.Create, FileAccess.Write, FileShare.None))
109 + using (var fs = this.FileSystem.OpenFile(cabinetMediaRow.SourceLineNumbers, cabinetPath, FileMode.Create, FileAccess.Write, FileShare.None))
110 {
111 int bytesRead;
112 var buffer = new byte[4096];
@@ -115,7 +117,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
117 }
118 }
119
118 - embeddedCabinetRowsByDiskId.TryGetValue(diskId, out var cabinetMediaRow);
120 cabinetPathsWithMediaRow.Add(cabinetPath, cabinetMediaRow);
121 }
122 else
src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+1 -1
@@ -206,7 +206,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
206
207 Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName));
208
209 - using (var fs = this.FileSystem.OpenFile(source, FileMode.Create, FileAccess.Write, FileShare.None))
209 + using (var fs = this.FileSystem.OpenFile(null, source, FileMode.Create, FileAccess.Write, FileShare.None))
210 {
211 int bytesRead;
212 var buffer = new byte[4096];
src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
+1 -1
@@ -327,7 +327,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
327 if (null == this.EmptyFile)
328 {
329 this.EmptyFile = Path.Combine(this.IntermediateFolder, ".empty");
330 - using (var fileStream = this.FileSystem.OpenFile(this.EmptyFile, FileMode.Create, FileAccess.Write, FileShare.None))
330 + using (var fileStream = this.FileSystem.OpenFile(null, this.EmptyFile, FileMode.Create, FileAccess.Write, FileShare.None))
331 {
332 }
333 }
src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs
+2 -2
@@ -71,7 +71,7 @@ namespace WixToolset.Core.WindowsInstaller.Validate
71 var workingDatabasePath = Path.Combine(this.IntermediateFolder, workingDatabaseFilename);
72 try
73 {
74 - this.FileSystem.CopyFile(this.DatabasePath, workingDatabasePath, allowHardlink: false);
74 + this.FileSystem.CopyFile(null, this.DatabasePath, workingDatabasePath, allowHardlink: false);
75
76 var attributes = File.GetAttributes(workingDatabasePath);
77 File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly);
@@ -81,7 +81,7 @@ namespace WixToolset.Core.WindowsInstaller.Validate
81 }
82 finally
83 {
84 - this.FileSystem.DeleteFile(workingDatabasePath);
84 + this.FileSystem.DeleteFile(null, workingDatabasePath);
85 }
86
87 stopwatch.Stop();
src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs
+6 -6
@@ -53,12 +53,12 @@ namespace WixToolset.Core.Bind
53 if (fileTransfer.Move)
54 {
55 this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination));
56 - this.MoveFile(fileTransfer.Source, fileTransfer.Destination);
56 + this.MoveFile(fileTransfer.SourceLineNumbers, fileTransfer.Source, fileTransfer.Destination);
57 }
58 else
59 {
60 this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination));
61 - this.CopyFile(fileTransfer.Source, fileTransfer.Destination);
61 + this.CopyFile(fileTransfer.SourceLineNumbers, fileTransfer.Source, fileTransfer.Destination);
62 }
63
64 retry = false;
@@ -170,7 +170,7 @@ namespace WixToolset.Core.Bind
170 }
171 }
172
173 - private void CopyFile(string source, string destination)
173 + private void CopyFile(SourceLineNumber sourceLineNumbers, string source, string destination)
174 {
175 foreach (var extension in this.Extensions)
176 {
@@ -180,10 +180,10 @@ namespace WixToolset.Core.Bind
180 }
181 }
182
183 - this.FileSystem.CopyFile(source, destination, allowHardlink: true);
183 + this.FileSystem.CopyFile(sourceLineNumbers, source, destination, allowHardlink: true);
184 }
185
186 - private void MoveFile(string source, string destination)
186 + private void MoveFile(SourceLineNumber sourceLineNumbers, string source, string destination)
187 {
188 foreach (var extension in this.Extensions)
189 {
@@ -193,7 +193,7 @@ namespace WixToolset.Core.Bind
193 }
194 }
195
196 - this.FileSystem.MoveFile(source, destination);
196 + this.FileSystem.MoveFile(sourceLineNumbers, source, destination);
197 }
198
199 private void AclReset(IEnumerable<string> files)
src/wix/WixToolset.Core/CoreErrors.cs new
+42
@@ -0,0 +1,42 @@
1 +// 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.
2 +
3 +namespace WixToolset.Core
4 +{
5 + using WixToolset.Data;
6 +
7 + internal static class CoreErrors
8 + {
9 + public static Message UnableToCopyFile(SourceLineNumber sourceLineNumbers, string source, string destination, string detail)
10 + {
11 + return Message(sourceLineNumbers, Ids.UnableToCopyFile, "Unable to copy file from: {0}, to: {1}. Error detail: {2}", source, destination, detail);
12 + }
13 +
14 + public static Message UnableToDeleteFile(SourceLineNumber sourceLineNumbers, string path, string detail)
15 + {
16 + return Message(sourceLineNumbers, Ids.UnableToDeleteFile, "Unable to delete file: {0}. Error detail: {1}", path, detail);
17 + }
18 +
19 + public static Message UnableToMoveFile(SourceLineNumber sourceLineNumbers, string source, string destination, string detail)
20 + {
21 + return Message(sourceLineNumbers, Ids.UnableToMoveFile, "Unable to move file from: {0}, to: {1}. Error detail: {2}", source, destination, detail);
22 + }
23 +
24 + public static Message UnableToOpenFile(SourceLineNumber sourceLineNumbers, string path, string detail)
25 + {
26 + return Message(sourceLineNumbers, Ids.UnableToOpenFile, "Unable to open file: {0}. Error detail: {1}", path, detail);
27 + }
28 +
29 + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
30 + {
31 + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
32 + }
33 +
34 + public enum Ids
35 + {
36 + UnableToCopyFile = 7010,
37 + UnableToDeleteFile = 7011,
38 + UnableToMoveFile = 7012,
39 + UnableToOpenFile = 7013,
40 + } // last available is 7099. 7100 is WindowsInstallerBackendWarnings.
41 + }
42 +}
src/wix/WixToolset.Core/ExtensibilityServices/FileSystem.cs
+53 -13
@@ -7,19 +7,34 @@ namespace WixToolset.Core.ExtensibilityServices
7 using System.IO;
8 using System.Runtime.InteropServices;
9 using System.Threading;
10 + using WixToolset.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class FileSystem : IFileSystem
14 {
14 - public void CopyFile(string source, string destination, bool allowHardlink)
15 + public void CopyFile(SourceLineNumber sourceLineNumbers, string source, string destination, bool allowHardlink)
16 {
16 - this.EnsureDirectoryWithoutFile(destination);
17 + try
18 + {
19 + this.EnsureDirectoryWithoutFile(destination);
20 + }
21 + catch (Exception e)
22 + {
23 + throw new WixException(CoreErrors.UnableToCopyFile(sourceLineNumbers, source, destination, e.Message), e);
24 + }
25
26 var hardlinked = false;
27
28 if (allowHardlink)
29 {
22 - this.ExecuteWithRetries(() => hardlinked = CreateHardLink(destination, source, IntPtr.Zero));
30 + try
31 + {
32 + this.ExecuteWithRetries(() => hardlinked = CreateHardLink(destination, source, IntPtr.Zero));
33 + }
34 + catch
35 + {
36 + // Catch hard-link failures and fall back to copy file.
37 + }
38 }
39
40 if (!hardlinked)
@@ -28,30 +43,48 @@ namespace WixToolset.Core.ExtensibilityServices
43 var er = Marshal.GetLastWin32Error();
44 #endif
45
31 - this.ExecuteWithRetries(() => File.Copy(source, destination, overwrite: true));
46 + try
47 + {
48 + this.ExecuteWithRetries(() => File.Copy(source, destination, overwrite: true));
49 + }
50 + catch (Exception e)
51 + {
52 + throw new WixException(CoreErrors.UnableToCopyFile(sourceLineNumbers, source, destination, e.Message), e);
53 + }
54 }
55 }
56
35 - public void DeleteFile(string source, bool throwOnError = false, int maxRetries = 4)
57 + public void DeleteFile(SourceLineNumber sourceLineNumbers, string source, bool throwOnError = false, int maxRetries = 4)
58 {
59 try
60 {
61 this.ExecuteWithRetries(() => File.Delete(source), maxRetries);
62 }
41 - catch when (!throwOnError)
63 + catch (Exception e)
64 {
43 - // Do nothing on best-effort deletes.
65 + if (throwOnError)
66 + {
67 + throw new WixException(CoreErrors.UnableToDeleteFile(sourceLineNumbers, source, e.Message), e);
68 + }
69 + // else do nothing on best-effort deletes.
70 }
71 }
72
47 - public void MoveFile(string source, string destination)
73 + public void MoveFile(SourceLineNumber sourceLineNumbers, string source, string destination)
74 {
49 - this.EnsureDirectoryWithoutFile(destination);
75 + try
76 + {
77 + this.EnsureDirectoryWithoutFile(destination);
78
51 - this.ExecuteWithRetries(() => File.Move(source, destination));
79 + this.ExecuteWithRetries(() => File.Move(source, destination));
80 + }
81 + catch (Exception e)
82 + {
83 + throw new WixException(CoreErrors.UnableToMoveFile(sourceLineNumbers, source, destination, e.Message), e);
84 + }
85 }
86
54 - public FileStream OpenFile(string path, FileMode mode, FileAccess access, FileShare share)
87 + public FileStream OpenFile(SourceLineNumber sourceLineNumbers, string path, FileMode mode, FileAccess access, FileShare share)
88 {
89 const int maxRetries = 4;
90
@@ -61,9 +94,16 @@ namespace WixToolset.Core.ExtensibilityServices
94 {
95 return File.Open(path, mode, access, share);
96 }
64 - catch (Exception e) when (attempt < maxRetries && (e is IOException || e is SystemException || e is Win32Exception))
97 + catch (Exception e) when (e is IOException || e is SystemException || e is Win32Exception)
98 {
66 - Thread.Sleep(250);
99 + if (attempt < maxRetries)
100 + {
101 + Thread.Sleep(250);
102 + }
103 + else
104 + {
105 + throw new WixException(CoreErrors.UnableToOpenFile(sourceLineNumbers, path, e.Message), e);
106 + }
107 }
108 }
109
src/wix/WixToolset.Core/LayoutCreator.cs
+1 -1
@@ -114,7 +114,7 @@ namespace WixToolset.Core
114 {
115 this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders);
116
117 - this.FileSystem.DeleteFile(tempPath);
117 + this.FileSystem.DeleteFile(null, tempPath);
118 }
119
120 // Clean up empty temp folders.