| File: Compilation.EmitStream.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.IO; using System.Reflection.PortableExecutable; using Microsoft.CodeAnalysis.Interop; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { public abstract partial class Compilation { /// <summary> /// Describes the kind of real signing that is being done during Emit. In the case of public signing /// this value will be <see cref="None"/>. /// </summary> internal enum EmitStreamSignKind { None, /// <summary> /// This form of signing occurs in memory using the <see cref="PEBuilder"/> APIs. This is the default /// form of signing and will be used when a strong name key is provided in a file on disk. /// </summary> SignedWithBuilder, /// <summary> /// This form of signing occurs using the <see cref="IClrStrongName"/> COM APIs. This form of signing /// requires the unsigned PE to be written to disk before it can be signed (typically by writing it /// out to the %TEMP% folder). This signing is used when the key in a key container, the signing /// requires a counter signature or customers opted in via the UseLegacyStrongNameProvider feature /// flag. /// </summary> SignedWithFile, } /// <summary> /// This type abstracts away the legacy COM based signing implementation for PE streams. Under the hood /// a temporary file must be created on disk (at the last possible moment), emitted to, signed on disk /// and then copied back to the original <see cref="Stream"/>. Only when legacy signing is enabled though. /// </summary> internal sealed class EmitStream { private readonly EmitStreamProvider _emitStreamProvider; private readonly EmitStreamSignKind _emitStreamSignKind; private readonly StrongNameProvider? _strongNameProvider; private readonly StrongNameKeys _strongNameKeys; private (Stream emitStream, Stream tempStream, string tempFilePath)? _tempInfo; private bool _created; internal EmitStream( EmitStreamProvider emitStreamProvider, EmitStreamSignKind emitStreamSignKind, StrongNameKeys strongNameKeys, StrongNameProvider? strongNameProvider) { RoslynDebug.Assert(emitStreamProvider != null); RoslynDebug.Assert(strongNameProvider != null || emitStreamSignKind == EmitStreamSignKind.None); _emitStreamProvider = emitStreamProvider; _emitStreamSignKind = emitStreamSignKind; _strongNameProvider = strongNameProvider; _strongNameKeys = strongNameKeys; } internal Func<Stream?> GetCreateStreamFunc(CommonMessageProvider messageProvider, DiagnosticBag diagnostics) { return () => CreateStream(messageProvider, diagnostics); } internal void Close() { // The emitStream tuple element is _deliberately_ not disposed here. That is owned by // the EmitStreamProvider not us. if (_tempInfo is (Stream _, Stream tempStream, string tempFilePath)) { _tempInfo = null; try { tempStream.Dispose(); } finally { try { File.Delete(tempFilePath); } catch { // Not much to do if we can't delete from the temp directory } } } } /// <summary> /// Create the stream which should be used for Emit. This should only be called one time. /// </summary> /// <remarks> /// The <see cref="Stream"/> returned here is owned by this type and should not be disposed /// by the caller. /// </remarks> private Stream? CreateStream(CommonMessageProvider messageProvider, DiagnosticBag diagnostics) { RoslynDebug.Assert(!_created); RoslynDebug.Assert(diagnostics != null); _created = true; if (diagnostics.HasAnyErrors()) { return null; } if (_emitStreamSignKind == EmitStreamSignKind.SignedWithFile) { // Signing is going to be done with on disk files and that requires us to manage // multiple Stream instances. One for the on disk file and one for the actual emit // stream the final PE should be written to. RoslynDebug.Assert(_strongNameProvider != null); var fileSystem = _strongNameProvider.FileSystem; var tempDir = fileSystem.GetSigningTempPath(); if (tempDir is null) { diagnostics.Add(StrongNameKeys.GetError( _strongNameKeys.KeyFilePath, _strongNameKeys.KeyContainer, new CodeAnalysisResourcesLocalizableErrorArgument(nameof(CodeAnalysisResources.SigningTempPathUnavailable)), messageProvider)); return null; } var emitStream = _emitStreamProvider.GetOrCreateStream(diagnostics); if (emitStream is null) { return null; } Stream tempStream; string tempFilePath; try { Func<string, Stream> streamConstructor = path => fileSystem.CreateFileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); tempFilePath = Path.Combine(tempDir, Guid.NewGuid().ToString("N")); tempStream = FileUtilities.CreateFileStreamChecked(streamConstructor, tempFilePath); } catch (IOException e) { throw new Cci.PeWritingException(e); } _tempInfo = (emitStream, tempStream, tempFilePath); return tempStream; } else { // Signing, if it occurs, will be done in memory so we can just return the final // Stream directly here. return _emitStreamProvider.GetOrCreateStream(diagnostics); } } internal bool Complete(CommonMessageProvider messageProvider, DiagnosticBag diagnostics) { RoslynDebug.Assert(_created); RoslynDebug.Assert(_emitStreamSignKind != EmitStreamSignKind.SignedWithFile || _tempInfo.HasValue); try { if (_tempInfo is (Stream emitStream, Stream tempStream, string tempFilePath)) { RoslynDebug.Assert(_emitStreamSignKind == EmitStreamSignKind.SignedWithFile); RoslynDebug.Assert(_strongNameProvider is object); try { // Dispose the temp stream to ensure all of the contents are written to // disk. tempStream.Dispose(); _strongNameProvider.SignFile(_strongNameKeys, tempFilePath); using (var tempFileStream = new FileStream(tempFilePath, FileMode.Open)) { tempFileStream.CopyTo(emitStream); } } catch (DesktopStrongNameProvider.ClrStrongNameMissingException) { diagnostics.Add(StrongNameKeys.GetError(_strongNameKeys.KeyFilePath, _strongNameKeys.KeyContainer, new CodeAnalysisResourcesLocalizableErrorArgument(nameof(CodeAnalysisResources.AssemblySigningNotSupported)), messageProvider)); return false; } catch (IOException ex) { diagnostics.Add(StrongNameKeys.GetError(_strongNameKeys.KeyFilePath, _strongNameKeys.KeyContainer, ex.Message, messageProvider)); return false; } } } finally { Close(); } return true; } } } }