File: Compilation.EmitStream.cs
Web Access
Project: ..\..\..\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 (Stream tempStream, string tempFilePath)? _tempInfo;
 
            /// <summary>
            /// The <see cref="Stream"/> that is being emitted into. This value should _never_ be 
            /// disposed. It is either returned from the <see cref="EmitStreamProvider"/> instance in
            /// which case it is owned by that. Or it is just an alias for the value that is stored 
            /// in <see cref="_tempInfo"/> in which case it will be disposed from there.
            /// </summary>
            private Stream? _stream;
 
            internal EmitStream(
                EmitStreamProvider emitStreamProvider,
                EmitStreamSignKind emitStreamSignKind,
                StrongNameProvider? strongNameProvider)
            {
                RoslynDebug.Assert(emitStreamProvider != null);
                RoslynDebug.Assert(strongNameProvider != null || emitStreamSignKind == EmitStreamSignKind.None);
                _emitStreamProvider = emitStreamProvider;
                _emitStreamSignKind = emitStreamSignKind;
                _strongNameProvider = strongNameProvider;
            }
 
            internal Func<Stream?> GetCreateStreamFunc(DiagnosticBag diagnostics)
            {
                return () => CreateStream(diagnostics);
            }
 
            internal void Close()
            {
                // The _stream value is deliberately excluded from being disposed here. That value is not 
                // owned by this type.
                _stream = null;
 
                if (_tempInfo.HasValue)
                {
                    var (tempStream, tempFilePath) = _tempInfo.GetValueOrDefault();
                    _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>
            private Stream? CreateStream(DiagnosticBag diagnostics)
            {
                RoslynDebug.Assert(_stream == null);
                RoslynDebug.Assert(diagnostics != null);
 
                if (diagnostics.HasAnyErrors())
                {
                    return null;
                }
 
                _stream = _emitStreamProvider.GetOrCreateStream(diagnostics);
                if (_stream == null)
                {
                    return null;
                }
 
                // If the current strong name provider is the Desktop version, signing can only be done to on-disk files.
                // If this binary is configured to be signed, create a temp file, output to that
                // then stream that to the stream that this method was called with. Otherwise output to the
                // stream that this method was called with.
                if (_emitStreamSignKind == EmitStreamSignKind.SignedWithFile)
                {
                    RoslynDebug.Assert(_strongNameProvider != null);
 
                    Stream tempStream;
                    string tempFilePath;
                    try
                    {
                        var fileSystem = _strongNameProvider.FileSystem;
                        Func<string, Stream> streamConstructor = path => fileSystem.CreateFileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
 
                        var tempDir = fileSystem.GetTempPath();
                        tempFilePath = Path.Combine(tempDir, Guid.NewGuid().ToString("N"));
                        tempStream = FileUtilities.CreateFileStreamChecked(streamConstructor, tempFilePath);
                    }
                    catch (IOException e)
                    {
                        throw new Cci.PeWritingException(e);
                    }
 
                    _tempInfo = (tempStream, tempFilePath);
                    return tempStream;
                }
                else
                {
                    return _stream;
                }
            }
 
            internal bool Complete(StrongNameKeys strongNameKeys, CommonMessageProvider messageProvider, DiagnosticBag diagnostics)
            {
                RoslynDebug.Assert(_stream != null);
                RoslynDebug.Assert(_emitStreamSignKind != EmitStreamSignKind.SignedWithFile || _tempInfo.HasValue);
 
                try
                {
                    if (_tempInfo.HasValue)
                    {
                        RoslynDebug.Assert(_emitStreamSignKind == EmitStreamSignKind.SignedWithFile);
                        RoslynDebug.Assert(_strongNameProvider is object);
                        var (tempStream, tempFilePath) = _tempInfo.GetValueOrDefault();
 
                        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(_stream);
                            }
                        }
                        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;
            }
        }
    }
}