File: compmod\system\codedom\compiler\TempFiles.cs
Project: ndp\fx\src\System.csproj (System)
//------------------------------------------------------------------------------
// <copyright file="TempFiles.cs" company="Microsoft">
// 
// <OWNER>Microsoft</OWNER>
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.CodeDom.Compiler {
    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;
    using Microsoft.Win32;
    using Microsoft.Win32.SafeHandles;
    using System.Security;
    using System.Security.Permissions;
    using System.Security.Principal;
    using System.Threading.Tasks;
    using System.ComponentModel;
    using System.Security.Cryptography;
    using System.Globalization;
    using System.Runtime.Versioning;
 
    /// <devdoc>
    ///    <para>Represents a collection of temporary file names that are all based on a
    ///       single base filename located in a temporary directory.</para>
    /// </devdoc>
    [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
    [PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")]
    [Serializable]
    public class TempFileCollection : ICollection, IDisposable {
        string basePath;
        string tempDir;
        bool keepFiles;
        Hashtable files;
        [NonSerialized]
        private WindowsIdentity currentIdentity = null;
        [NonSerialized]
        private string highIntegrityDirectory = null;
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public TempFileCollection() : this(null, false) {
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public TempFileCollection(string tempDir) : this(tempDir, false) { 
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
#if !FEATURE_PAL
        [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Assert, ControlPrincipal = true)]
#endif
        public TempFileCollection(string tempDir, bool keepFiles) {
            this.keepFiles = keepFiles;
            this.tempDir = tempDir;
#if !FEATURE_CASE_SENSITIVE_FILESYSTEM            
            files = new Hashtable(StringComparer.OrdinalIgnoreCase);
#else
            files = new Hashtable();
#endif
#if !FEATURE_PAL
            WindowsImpersonationContext impersonation = Executor.RevertImpersonation();
            try
            {
                currentIdentity = WindowsIdentity.GetCurrent();
            }
            finally
            {
                Executor.ReImpersonate(impersonation);
            }
#endif
        }
 
        /// <internalonly/>
        /// <devdoc>
        /// <para> To allow it's stuff to be cleaned up</para>
        /// </devdoc>
        void IDisposable.Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing) {
            // It is safe to call Delete from here even if Dispose is called from Finalizer
            // because the graph of objects is guaranteed to be there and
            // neither Hashtable nor String have a finalizer of their own that could 
            // be called before TempFileCollection Finalizer
            Delete();
            DeleteHighIntegrityDirectory();
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        ~TempFileCollection() {
            Dispose(false);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public string AddExtension(string fileExtension) {
            return AddExtension(fileExtension, keepFiles);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public string AddExtension(string fileExtension, bool keepFile) {
            if (fileExtension == null || fileExtension.Length == 0)
                throw new ArgumentException(SR.GetString(SR.InvalidNullEmptyArgument, "fileExtension"), "fileExtension");  // fileExtension not specified
            string fileName = BasePath + "." + fileExtension;
            AddFile(fileName, keepFile);
            return fileName;
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void AddFile(string fileName, bool keepFile) {
            if (fileName == null || fileName.Length == 0)
                throw new ArgumentException(SR.GetString(SR.InvalidNullEmptyArgument, "fileName"), "fileName");  // fileName not specified
 
            if (files[fileName] != null)
                throw new ArgumentException(SR.GetString(SR.DuplicateFileName, fileName), "fileName");  // duplicate fileName
            files.Add(fileName, (object)keepFile);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public IEnumerator GetEnumerator() {
            return files.Keys.GetEnumerator();
        }
 
        /// <internalonly/>
        IEnumerator IEnumerable.GetEnumerator() {
            return files.Keys.GetEnumerator();
        }
 
        /// <internalonly/>
        void ICollection.CopyTo(Array array, int start) {
            files.Keys.CopyTo(array, start);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void CopyTo(string[] fileNames, int start) {
            files.Keys.CopyTo(fileNames, start);
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public int Count {
            get {
                return files.Count;
            }
        }
 
        /// <internalonly/>
        int ICollection.Count {
            get { return files.Count; }
        }
 
        /// <internalonly/>
        object ICollection.SyncRoot {
            get { return null; }
        }
 
        /// <internalonly/>
        bool ICollection.IsSynchronized {
            get { return false; }
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public string TempDir {
            get { return tempDir == null ? string.Empty : tempDir; }
        }
                                              
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public string BasePath {
            get {
                EnsureTempNameCreated();
                return basePath;
            }
        }
 
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        void EnsureTempNameCreated() {
            if (basePath == null) {
 
                string tempFileName = null;
                FileStream tempFileStream;
                bool uniqueFile = false;
                int retryCount = 5000;
                do {
                    try {
                        basePath = GetTempFileName(TempDir);
 
                        string full = Path.GetFullPath(basePath);
 
                        new FileIOPermission(FileIOPermissionAccess.AllAccess, full).Demand();
 
                        // make sure the filename is unique. 
                        tempFileName = basePath + ".tmp";
                        using (tempFileStream = new FileStream(tempFileName, FileMode.CreateNew, FileAccess.Write)) { }
                        uniqueFile = true;
                    }
                    catch (IOException e) {
                        retryCount--;
 
                        uint HR_ERROR_FILE_EXISTS = unchecked(((uint)0x80070000) | NativeMethods.ERROR_FILE_EXISTS);
                        if (retryCount == 0 || Marshal.GetHRForException(e) != HR_ERROR_FILE_EXISTS)
                            throw;
 
                        uniqueFile = false;
                    }
                }while (!uniqueFile);
                files.Add(tempFileName, keepFiles);
            }
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public bool KeepFiles {
            get { return keepFiles; }
            set { keepFiles = value; }
        }
 
        bool KeepFile(string fileName) {
            object keep = files[fileName];
            if (keep == null) return false;
            return (bool)keep; 
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public void Delete() {
            if (files != null && files.Count > 0) {
                string[] fileNames = new string[files.Count];
                files.Keys.CopyTo(fileNames, 0);
                foreach (string fileName in fileNames) {
                    if (!KeepFile(fileName)) {
                        Delete(fileName);
                        files.Remove(fileName);
                    }
                }
            }
        }
 
        private void DeleteHighIntegrityDirectory()
        {
            try
            {
                if (currentIdentity != null && Directory.Exists(highIntegrityDirectory))
                {
                    // Remove the no delete policy from the directory.
                    RemoveAceOnTempDirectory(highIntegrityDirectory, currentIdentity.User.ToString());
                    if (Directory.GetFiles(highIntegrityDirectory).Length == 0)
                    {
                        // Delete the high integrity directory if all files are deleted.
                        Directory.Delete(highIntegrityDirectory, true);
                    }
                }
            }
            catch { } // Ignore all errors.
        }
 
        // This function deletes files after reverting impersonation.
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
        internal void SafeDelete() {
#if !FEATURE_PAL
            WindowsImpersonationContext impersonation = Executor.RevertImpersonation();
#endif
            try{
                Delete();
            }
            finally {               
#if !FEATURE_PAL
                Executor.ReImpersonate(impersonation);
#endif
            }
        }
 
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        void Delete(string fileName) {
            try {
                File.Delete(fileName);
            }
            catch {
                // Ignore all exceptions
            }
        }
 
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        string GetTempFileName(string tempDir) {
            string fileName;
            string randomFileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());
 
            if (String.IsNullOrEmpty(tempDir)) {
                tempDir = Path.GetTempPath();
 
                if (!LocalAppContextSwitches.DisableTempFileCollectionDirectoryFeature && currentIdentity != null &&
                    new WindowsPrincipal(currentIdentity).IsInRole(WindowsBuiltInRole.Administrator))
                {
                    tempDir = Path.Combine(tempDir, randomFileName);
 
                    // Create high integrity dir and set no delete policy for all files under the directory.
                    // In case of failure, throw exception.
                    CreateTempDirectoryWithAce(tempDir, currentIdentity.User.ToString());
                    highIntegrityDirectory = tempDir;
                }
            }
 
            if (tempDir.EndsWith("\\", StringComparison.Ordinal))
                fileName = tempDir + randomFileName;
            else
                fileName = tempDir + "\\" + randomFileName;
 
            return fileName;
        }
 
        private static void CreateTempDirectoryWithAce(string directory, string identity)
        {
            // Dacl Sddl string:
            // D: Dacl type
            // D; Deny access
            // OI; Object inherit ace
            // SD; Standard delete function
            // wIdentity.User Sid of the given user.
            // A; Allow access
            // OICI; Object inherit, container inherit
            // FA File access
            // BA Built-in administrators
            // S: Sacl type
            // ML;; Mandatory Label
            // NW;;; No write policy
            // HI High integrity processes only
            string sddl = "D:(D;OI;SD;;;" + identity + ")(A;OICI;FA;;;BA)S:(ML;OI;NW;;;HI)";
 
            SafeLocalMemHandle acl = null;
            SafeLocalMemHandle.ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, NativeMethods.SDDL_REVISION_1, out acl, IntPtr.Zero);
 
            // Create the directory with the acl
            NativeMethods.CreateDirectory(directory, acl);
        }
 
        private static void RemoveAceOnTempDirectory(string directory, string identity)
        {
            string sddl = "D:(A;OICI;FA;;;" + identity + ")(A;OICI;FA;;;BA)";
 
            SafeLocalMemHandle acl = null;
            SafeLocalMemHandle.ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, NativeMethods.SDDL_REVISION_1, out acl, IntPtr.Zero);
 
            NativeMethods.SetNamedSecurityInfo(directory, acl);
        }
    }
}