SqlFileStream.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Data / System / Data / SQLTypes / SqlFileStream.cs / 1305376 / SqlFileStream.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
//  
// [....] 
// [....]
// [....] 
//----------------------------------------------------------------------------- 

using System; 
using System.Data.Common;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Globalization; 
using System.IO;
using System.Security.Permissions; 
using Microsoft.Win32.SafeHandles; 
using System.Diagnostics;
using System.Security; 
using System.Runtime.Versioning;

namespace System.Data.SqlTypes
{ 
    sealed public class SqlFileStream : System.IO.Stream
    { 
        // NOTE: if we ever unseal this class, be sure to specify the Name, SafeFileHandle, and 
        //   TransactionContext accessors as virtual methods. Doing so now on a sealed class
        //   generates a compiler error (CS0549) 

        // For BID tracing output
        private  static int     _objectTypeCount; // Bid counter
        internal readonly int   ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); 

        // from System.IO.FileStream implementation 
        //   DefaultBufferSize = 4096; 
        // SQLBUVSTS# 193123 - disable lazy flushing of written data in order to prevent
        //   potential exceptions during Close/Finalization. Since System.IO.FileStream will 
        //   not allow for a zero byte buffer, we'll create a one byte buffer which, in normal
        //   usage, will not be used and the user buffer will automatically flush directly to
        //   the disk cache. In pathological scenarios where the client is writing a single
        //   byte at a time, we'll explicitly call flush ourselves. 
        internal const int DefaultBufferSize = 1;
 
        private const ushort IoControlCodeFunctionCode = 2392; 

        private System.IO.FileStream m_fs; 
        private string m_path;
        private byte[] m_txn;
        private bool m_disposed;
 
        public SqlFileStream
            ( 
                string path, 
                byte[] transactionContext,
                System.IO.FileAccess access 
            )
            : this ( path, transactionContext, access, System.IO.FileOptions.None, 0 )
        {
        } 

        public SqlFileStream 
            ( 
                string path,
                byte[] transactionContext, 
                System.IO.FileAccess access,
                System.IO.FileOptions options,
                Int64 allocationSize
            ) 
        {
 
            IntPtr hscp; 
            Bid.ScopeEnter ( out hscp, " %d# access=%d options=%d path='%ls' ", ObjectID, (int) access, (int) options, path );
 
            try
            {
                //-----------------------------------------------------------------
                // precondition validation 

                if (transactionContext == null) 
                    throw ADP.ArgumentNull("transactionContext"); 

                if ( path == null ) 
                    throw ADP.ArgumentNull ( "path" );

                //-----------------------------------------------------------------
 
                m_disposed = false;
                m_fs = null; 
 
                OpenSqlFileStream(path, transactionContext, access, options, allocationSize);
 
                // only set internal state once the file has actually been successfully opened
                this.Name = path;
                this.TransactionContext = transactionContext;
            } 
            finally
            { 
                Bid.ScopeLeave(ref hscp); 
            }
        } 

        #region destructor/dispose code

        // NOTE: this destructor will only be called only if the Dispose 
        //   method is not called by a client, giving the class a chance
        //   to finalize properly (i.e., free unmanaged resources) 
        ~SqlFileStream() 
        {
            Dispose(false); 
        }

        protected override void Dispose(bool disposing)
        { 
            try
            { 
                if (!m_disposed) 
                {
                    try 
                    {
                        if (disposing)
                        {
                            if (m_fs != null) 
                            {
                                m_fs.Close(); 
                                m_fs = null; 
                            }
                        } 
                    }
                    finally
                    {
                        m_disposed = true; 
                    }
                } 
            } 
            finally
            { 
                base.Dispose(disposing);
            }
        }
        #endregion 

        public string Name 
        { 
            get
            { 
                // assert that path has been properly processed via GetFullPathInternal
                //   (e.g. m_path hasn't been set directly)
                AssertPathFormat ( m_path );
                return m_path; 
            }
            [ResourceExposure(ResourceScope.None)] // SxS: the file name is not exposed 
            [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] 
            private set
            { 
                // should be validated by callers of this method
                Debug.Assert ( value != null );
                Debug.Assert ( !m_disposed );
 
                m_path = GetFullPathInternal ( value );
            } 
        } 

        public byte[] TransactionContext 
        {
            get
            {
                if ( m_txn == null ) 
                    return null;
 
                return (byte[]) m_txn.Clone(); 
            }
            private set 
            {
                // should be validated by callers of this method
                Debug.Assert ( value != null );
                Debug.Assert ( !m_disposed ); 

                m_txn = (byte[]) value.Clone(); 
            } 
        }
 
        #region System.IO.Stream methods

        public override bool CanRead
        { 
            get
            { 
                if ( m_disposed ) 
                    throw ADP.ObjectDisposed ( this );
 
                return m_fs.CanRead;
            }
        }
 
        // If CanSeek is false, Position, Seek, Length, and SetLength should throw.
        public override bool CanSeek 
        { 
            get
            { 
                if ( m_disposed )
                    throw ADP.ObjectDisposed ( this );

                return m_fs.CanSeek; 
            }
        } 
 
        [ComVisible(false)]
        public override bool CanTimeout 
        {
            get
            {
                if ( m_disposed ) 
                    throw ADP.ObjectDisposed ( this );
 
                return m_fs.CanTimeout; 
            }
        } 

        public override bool CanWrite
        {
            get 
            {
                if ( m_disposed ) 
                    throw ADP.ObjectDisposed ( this ); 

                return m_fs.CanWrite; 
            }
        }

        public override long Length 
        {
            get 
            { 
                if ( m_disposed )
                    throw ADP.ObjectDisposed ( this ); 

                return m_fs.Length;
            }
        } 

        public override long Position 
        { 
            get
            { 
                if ( m_disposed )
                    throw ADP.ObjectDisposed ( this );

                return m_fs.Position; 
            }
            set 
            { 
                if ( m_disposed )
                    throw ADP.ObjectDisposed ( this ); 

                m_fs.Position = value;
            }
        } 

        [ComVisible(false)] 
        public override int ReadTimeout 
        {
            get 
            {
                if ( m_disposed )
                    throw ADP.ObjectDisposed ( this );
 
                return m_fs.ReadTimeout;
            } 
            set 
            {
                if ( m_disposed ) 
                    throw ADP.ObjectDisposed ( this );

                m_fs.ReadTimeout = value;
            } 
        }
 
        [ComVisible(false)] 
        public override int WriteTimeout
        { 
            get
            {
                if ( m_disposed )
                    throw ADP.ObjectDisposed ( this ); 

                return m_fs.WriteTimeout; 
            } 
            set
            { 
                if ( m_disposed )
                    throw ADP.ObjectDisposed ( this );

                m_fs.WriteTimeout = value; 
            }
        } 
 
        public override void Flush()
        { 
            if ( m_disposed )
                throw ADP.ObjectDisposed ( this );

            m_fs.Flush(); 
        }
 
        [HostProtection(ExternalThreading=true)] 
        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
        { 
            if ( m_disposed )
                throw ADP.ObjectDisposed ( this );

            return m_fs.BeginRead(buffer, offset, count, callback, state); 
        }
 
        public override int EndRead(IAsyncResult asyncResult) 
        {
            if ( m_disposed ) 
                throw ADP.ObjectDisposed ( this );

            return m_fs.EndRead(asyncResult);
        } 

        [HostProtection(ExternalThreading=true)] 
        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) 
        {
            if ( m_disposed ) 
                throw ADP.ObjectDisposed ( this );

            IAsyncResult asyncResult = m_fs.BeginWrite(buffer, offset, count, callback, state);
 
            // SQLBUVSTS# 193123 - disable lazy flushing of written data in order to prevent
            //   potential exceptions during Close/Finalization. Since System.IO.FileStream will 
            //   not allow for a zero byte buffer, we'll create a one byte buffer which, in normal 
            //   usage, will not be used and the user buffer will automatically flush directly to
            //   the disk cache. In pathological scenarios where the client is writing a single 
            //   byte at a time, we'll explicitly call flush ourselves.
            if ( count == 1 )
            {
                // calling flush here will mimic the internal control flow of System.IO.FileStream 
                m_fs.Flush();
            } 
 
            return asyncResult;
        } 

        public override void EndWrite(IAsyncResult asyncResult)
        {
            if ( m_disposed ) 
                throw ADP.ObjectDisposed ( this );
 
            m_fs.EndWrite(asyncResult); 
        }
 
        public override long Seek(long offset, SeekOrigin origin)
        {
            if ( m_disposed )
                throw ADP.ObjectDisposed ( this ); 

            return m_fs.Seek(offset, origin); 
        } 

        public override void SetLength(long value) 
        {
            if ( m_disposed )
                throw ADP.ObjectDisposed ( this );
 
            m_fs.SetLength(value);
        } 
 
        public override int Read([In, Out] byte[] buffer, int offset, int count)
        { 
            if ( m_disposed )
                throw ADP.ObjectDisposed ( this );

            return m_fs.Read(buffer, offset, count); 
        }
 
        public override int ReadByte() 
        {
            if ( m_disposed ) 
                throw ADP.ObjectDisposed ( this );

            return m_fs.ReadByte();
        } 

        public override void Write(byte[] buffer, int offset, int count) 
        { 
            if ( m_disposed )
                throw ADP.ObjectDisposed ( this ); 

            m_fs.Write(buffer, offset, count);

            // SQLBUVSTS# 193123 - disable lazy flushing of written data in order to prevent 
            //   potential exceptions during Close/Finalization. Since System.IO.FileStream will
            //   not allow for a zero byte buffer, we'll create a one byte buffer which, in normal 
            //   usage, will cause System.IO.FileStream to utilize the user-supplied buffer and 
            //   automatically flush the data directly to the disk cache. In pathological scenarios
            //   where the user is writing a single byte at a time, we'll explicitly call flush 
            //   ourselves.
            if ( count == 1 )
            {
                // calling flush here will mimic the internal control flow of System.IO.FileStream 
                m_fs.Flush();
            } 
        } 

        public override void WriteByte(byte value) 
        {
            if ( m_disposed )
                throw ADP.ObjectDisposed ( this );
 
            m_fs.WriteByte(value);
 
            // SQLBUVSTS# 193123 - disable lazy flushing of written data in order to prevent 
            //   potential exceptions during Close/Finalization. Since our internal buffer is
            //   only a single byte in length, the provided user data will always be cached. 
            //   As a result, we need to be sure to flush the data to disk ourselves.

            // calling flush here will mimic the internal control flow of System.IO.FileStream
            m_fs.Flush(); 
        }
 
        #endregion 

        static private readonly char[] InvalidPathChars = Path.GetInvalidPathChars(); 

        // path length limitations:
        // 1. path length storage (in bytes) in UNICODE_STRING is limited to UInt16.MaxValue bytes = Int16.MaxValue chars
        // 2. GetFullPathName API of kernel32 does not accept paths with length (in chars) greater than 32766 
        //    (32766 is actually Int16.MaxValue - 1, while (-1) is for NULL termination)
        // We must check for the lowest value between the the two 
        static private readonly int MaxWin32PathLength = Int16.MaxValue - 1; 

        [ConditionalAttribute("DEBUG")] 
        static private void AssertPathFormat(string path)
        {
            Debug.Assert ( path != null );
            Debug.Assert ( path == path.Trim() ); 
            Debug.Assert ( path.Length > 0 );
            Debug.Assert(path.Length <= MaxWin32PathLength); 
            Debug.Assert(path.IndexOfAny(InvalidPathChars) < 0); 
            Debug.Assert ( path.StartsWith ( @"\\", StringComparison.OrdinalIgnoreCase ) );
            Debug.Assert ( !path.StartsWith ( @"\\.\", StringComparison.Ordinal ) ); 
        }

        // SQLBUVSTS01 bugs 192677 and 193221: we cannot use System.IO.Path.GetFullPath for two reasons:
        // * it requires PathDiscovery permissions, which is unnecessary for SqlFileStream since we 
        //   are dealing with network path
        // * it is limited to 260 length while in our case file path can be much longer 
        // To overcome the above limitations we decided to use GetFullPathName function from kernel32.dll 
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)] 
        static private string GetFullPathInternal(string path)
        {
            //-----------------------------------------------------------------
            // precondition validation 

            // should be validated by callers of this method 
            // NOTE: if this method moves elsewhere, this assert should become an actual runtime check 
            //   as the implicit assumptions here cannot be relied upon in an inter-class context
            Debug.Assert ( path != null ); 

            // remove leading and trailing whitespace
            path = path.Trim();
            if (path.Length == 0) 
            {
                throw ADP.Argument(Res.GetString(Res.SqlFileStream_InvalidPath), "path"); 
            } 

            // check for the path length before we normalize it with GetFullPathName 
            if (path.Length > MaxWin32PathLength)
            {
                // cannot use PathTooLongException here since our length limit is 32K while
                // PathTooLongException error message states that the path should be limited to 260 
                throw ADP.Argument(Res.GetString(Res.SqlFileStream_InvalidPath), "path");
            } 
 
            // GetFullPathName does not check for invalid characters so we still have to validate them before
            if (path.IndexOfAny(InvalidPathChars) >= 0) 
            {
                throw ADP.Argument(Res.GetString(Res.SqlFileStream_InvalidPath), "path");
            }
 
            // make sure path is a UNC path
            if (!path.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase)) 
            { 
                throw ADP.Argument(Res.GetString(Res.SqlFileStream_InvalidPath), "path");
            } 

            //-----------------------------------------------------------------

            // normalize the path 
            path = UnsafeNativeMethods.SafeGetFullPathName(path);
 
            // we do not expect windows API to return invalid paths 
            Debug.Assert(path.Length <= MaxWin32PathLength, "GetFullPathName returns path longer than max expected!");
 
            // CONSIDER: is this a precondition validation that can be done above? Or must the path be normalized first?
            // after normalization, we have to ensure that the path does not attempt to refer to a root device, etc.
            if (path.StartsWith(@"\\.\", StringComparison.Ordinal))
            { 
                throw ADP.Argument(Res.GetString(Res.SqlFileStream_PathNotValidDiskResource), "path");
            } 
 
            return path;
        } 

        static private void DemandAccessPermission
            (
                string path, 
                System.IO.FileAccess access
            ) 
        { 
            // ensure we demand on valid path
            AssertPathFormat ( path ); 

            FileIOPermissionAccess demandPermissions;
            switch (access)
            { 
                case FileAccess.Read:
                    demandPermissions = FileIOPermissionAccess.Read; 
                    break; 

                case FileAccess.Write: 
                    demandPermissions = FileIOPermissionAccess.Write;
                    break;

                case FileAccess.ReadWrite: 
                default:
                    // the caller have to validate the value of 'access' parameter 
                    Debug.Assert(access == System.IO.FileAccess.ReadWrite); 
                    demandPermissions = FileIOPermissionAccess.Read | FileIOPermissionAccess.Write;
                    break; 
            }

            FileIOPermission filePerm;
            bool pathTooLong = false; 

            // check for read and/or write permissions 
            try 
            {
                filePerm = new FileIOPermission(demandPermissions, path); 
                filePerm.Demand();
            }
            catch (PathTooLongException e)
            { 
                pathTooLong = true;
                ADP.TraceExceptionWithoutRethrow(e); 
            } 

            if (pathTooLong) 
            {
                // SQLBUVSTS bugs 192677 and 203422: currently, FileIOPermission does not support path longer than MAX_PATH (260)
                // so we cannot demand permissions for long files. We are going to open bug for FileIOPermission to
                // support this. 

                // In the meanwhile, we agreed to have try-catch block on the permission demand instead of checking the path length. 
                // This way, if/when the 260-chars limitation is fixed in FileIOPermission, we will not need to change our code 

                // since we do not want to relax security checks, we have to demand this permission for AllFiles in order to continue! 
                // Note: demand for AllFiles will fail in scenarios where the running code does not have this permission (such as ASP.Net)
                // and the only workaround will be reducing the total path length, which means reducing the length of SqlFileStream path
                // components, such as instance name, table name, etc.. to fit into 260 characters
                filePerm = new FileIOPermission(PermissionState.Unrestricted); 
                filePerm.AllFiles = demandPermissions;
 
                filePerm.Demand(); 
            }
        } 

        // SxS: SQL File Stream is a database resource, not a local machine one
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] 
        private void OpenSqlFileStream
            ( 
                string path, 
                byte[] transactionContext,
                System.IO.FileAccess access, 
                System.IO.FileOptions options,
                Int64 allocationSize
            )
        { 
            //------------------------------------------------------------------
            // precondition validation 
 
            // these should be checked by any caller of this method
 
            // ensure we have validated and normalized the path before
            Debug.Assert ( path != null );
            Debug.Assert (transactionContext != null);
 
            if (access != FileAccess.Read && access != FileAccess.Write && access != FileAccess.ReadWrite)
                throw ADP.ArgumentOutOfRange ("access"); 
 
            // FileOptions is a set of flags, so AND the given value against the set of values we do not support
            if ( ( options & ~( FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.SequentialScan ) ) != 0 ) 
                throw ADP.ArgumentOutOfRange ( "options" );

            //-----------------------------------------------------------------
 
            // normalize the provided path
            //   * compress path to remove any occurences of '.' or '..' 
            //   * trim whitespace from the beginning and end of the path 
            //   * ensure that the path starts with '\\'
            //   * ensure that the path does not start with '\\.\' 
            //   * ensure that the path is not longer than Int16.MaxValue
            path = GetFullPathInternal ( path );

            // ensure the running code has permission to read/write the file 
            DemandAccessPermission(path, access);
 
            FileFullEaInformation eaBuffer = null; 
            SecurityQualityOfService qos = null;
            UnicodeString objectName = null; 

            Microsoft.Win32.SafeHandles.SafeFileHandle hFile = null;

            int nDesiredAccess = UnsafeNativeMethods.FILE_READ_ATTRIBUTES | UnsafeNativeMethods.SYNCHRONIZE; 

            UInt32 dwCreateOptions = 0; 
            UInt32 dwCreateDisposition = 0; 

            System.IO.FileShare shareAccess = System.IO.FileShare.None; 

            switch (access)
            {
                case System.IO.FileAccess.Read: 
                    nDesiredAccess |= UnsafeNativeMethods.FILE_READ_DATA;
                    shareAccess = System.IO.FileShare.Delete | System.IO.FileShare.ReadWrite; 
                    dwCreateDisposition = (uint) UnsafeNativeMethods.CreationDisposition.FILE_OPEN; 
                    break;
 
                case System.IO.FileAccess.Write:
                    nDesiredAccess |= UnsafeNativeMethods.FILE_WRITE_DATA;
                    shareAccess = System.IO.FileShare.Delete | System.IO.FileShare.Read;
                    dwCreateDisposition = (uint) UnsafeNativeMethods.CreationDisposition.FILE_OVERWRITE; 
                    break;
 
                case System.IO.FileAccess.ReadWrite: 
                default:
                    // we validate the value of 'access' parameter in the beginning of this method 
                    Debug.Assert(access == System.IO.FileAccess.ReadWrite);

                    nDesiredAccess |= UnsafeNativeMethods.FILE_READ_DATA | UnsafeNativeMethods.FILE_WRITE_DATA;
                    shareAccess = System.IO.FileShare.Delete | System.IO.FileShare.Read; 
                    dwCreateDisposition = (uint) UnsafeNativeMethods.CreationDisposition.FILE_OVERWRITE;
                    break; 
            } 

            if ((options & System.IO.FileOptions.WriteThrough) != 0) 
            {
                dwCreateOptions |= (uint) UnsafeNativeMethods.CreateOption.FILE_WRITE_THROUGH;
            }
 
            if ((options & System.IO.FileOptions.Asynchronous) == 0)
            { 
                dwCreateOptions |= (uint) UnsafeNativeMethods.CreateOption.FILE_SYNCHRONOUS_IO_NONALERT; 
            }
 
            if ((options & System.IO.FileOptions.SequentialScan) != 0)
            {
                dwCreateOptions |= (uint) UnsafeNativeMethods.CreateOption.FILE_SEQUENTIAL_ONLY;
            } 

            if ( (options & System.IO.FileOptions.RandomAccess) != 0) 
            { 
                dwCreateOptions |= (uint) UnsafeNativeMethods.CreateOption.FILE_RANDOM_ACCESS;
            } 

            try
            {
                eaBuffer = new FileFullEaInformation(transactionContext); 

                qos = new SecurityQualityOfService(UnsafeNativeMethods.SecurityImpersonationLevel.SecurityAnonymous, 
                    false, false); 

                // NOTE: the Name property is intended to reveal the publicly available moniker for the 
                //   FILESTREAM attributed column data. We will not surface the internal processing that
                //   takes place to create the mappedPath.
                string mappedPath = InitializeNtPath(path);
                objectName = new UnicodeString(mappedPath); 

                UnsafeNativeMethods.OBJECT_ATTRIBUTES oa; 
                    oa.length = Marshal.SizeOf(typeof(UnsafeNativeMethods.OBJECT_ATTRIBUTES)); 
                oa.rootDirectory = IntPtr.Zero;
                oa.attributes = (int)UnsafeNativeMethods.Attributes.CaseInsensitive; 
                oa.securityDescriptor = IntPtr.Zero;
                oa.securityQualityOfService = qos;
                oa.objectName = objectName;
 
                UnsafeNativeMethods.IO_STATUS_BLOCK ioStatusBlock;
 
                uint oldMode = UnsafeNativeMethods.SetErrorMode ( UnsafeNativeMethods.SEM_FAILCRITICALERRORS ); 
                uint retval = 0;
 
                try
                {
                    Bid.Trace(" %d#, desiredAccess=0x%08x, allocationSize=%I64d, fileAttributes=0x%08x, shareAccess=0x%08x, dwCreateDisposition=0x%08x, createOptions=0x%08x\n",
                        ObjectID, (int) nDesiredAccess, allocationSize, 0, (int) shareAccess, dwCreateDisposition, dwCreateOptions ); 

                    retval = UnsafeNativeMethods.NtCreateFile(out hFile, nDesiredAccess, 
                        ref oa, out ioStatusBlock, ref allocationSize, 
                        0, shareAccess, dwCreateDisposition, dwCreateOptions,
                        eaBuffer, (uint) eaBuffer.Length); 
                }
                finally
                {
                    UnsafeNativeMethods.SetErrorMode ( oldMode ); 
                }
 
                switch ( retval ) 
                {
                    case 0: 
                        break;

                    case UnsafeNativeMethods.STATUS_SHARING_VIOLATION:
                        throw ADP.InvalidOperation ( Res.GetString ( Res.SqlFileStream_FileAlreadyInTransaction ) ); 

                    case UnsafeNativeMethods.STATUS_INVALID_PARAMETER: 
                        throw ADP.Argument ( Res.GetString ( Res.SqlFileStream_InvalidParameter ) ); 

                    case UnsafeNativeMethods.STATUS_OBJECT_NAME_NOT_FOUND: 
                        {
                            System.IO.DirectoryNotFoundException e = new System.IO.DirectoryNotFoundException();
                            ADP.TraceExceptionAsReturnValue ( e );
                            throw e; 
                        }
                    default: 
                        { 
                            uint error = UnsafeNativeMethods.RtlNtStatusToDosError ( retval );
                            if ( error == UnsafeNativeMethods.ERROR_MR_MID_NOT_FOUND ) 
                            {
                                // status code could not be mapped to a Win32 error code
                                error = retval;
                            } 

                            System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception ( unchecked ( (int) error ) ); 
                            ADP.TraceExceptionAsReturnValue ( e ); 
                            throw e;
                        } 
                }

                if ( hFile.IsInvalid )
                { 
                    System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception ( UnsafeNativeMethods.ERROR_INVALID_HANDLE );
                    ADP.TraceExceptionAsReturnValue ( e ); 
                    throw e; 
                }
 
                UnsafeNativeMethods.FileType fileType = UnsafeNativeMethods.GetFileType(hFile);
                if (fileType != UnsafeNativeMethods.FileType.Disk)
                {
                    hFile.Dispose(); 
                    throw ADP.Argument ( Res.GetString ( Res.SqlFileStream_PathNotValidDiskResource ) );
                } 
 
                // if the user is opening the SQL FileStream in read/write mode, we assume that they want to scan
                //   through current data and then append new data to the end, so we need to tell SQL Server to preserve 
                //   the existing file contents.
                if ( access == System.IO.FileAccess.ReadWrite )
                {
                    uint ioControlCode = UnsafeNativeMethods.CTL_CODE ( UnsafeNativeMethods.FILE_DEVICE_FILE_SYSTEM, 
                        IoControlCodeFunctionCode, (byte) UnsafeNativeMethods.Method.METHOD_BUFFERED,
                        (byte) UnsafeNativeMethods.Access.FILE_ANY_ACCESS); 
                    uint cbBytesReturned = 0; 

                    if ( !UnsafeNativeMethods.DeviceIoControl ( hFile, ioControlCode, IntPtr.Zero, 0, IntPtr.Zero, 0, out cbBytesReturned, IntPtr.Zero ) ) 
                    {
                        System.ComponentModel.Win32Exception e = new System.ComponentModel.Win32Exception ( Marshal.GetLastWin32Error() );
                        ADP.TraceExceptionAsReturnValue ( e );
                        throw e; 
                    }
                } 
 
                // now that we've successfully opened a handle on the path and verified that it is a file,
                //   use the SafeFileHandle to initialize our internal System.IO.FileStream instance 
                // NOTE: need to assert UnmanagedCode permissions for this constructor. This is relatively benign
                //   in that we've done much the same validation as in the FileStream(string path, ...) ctor case
                //   most notably, validating that the handle type corresponds to an on-disk file.
                bool bRevertAssert = false; 
                try
                { 
                    SecurityPermission sp = new SecurityPermission ( SecurityPermissionFlag.UnmanagedCode ); 
                    sp.Assert();
                    bRevertAssert = true; 

                    System.Diagnostics.Debug.Assert ( m_fs == null );
                    m_fs = new System.IO.FileStream ( hFile, access, DefaultBufferSize, ( ( options & System.IO.FileOptions.Asynchronous ) != 0 ) );
                } 
                finally
                { 
                    if ( bRevertAssert ) 
                        SecurityPermission.RevertAssert();
                } 

            }
            catch
            { 
                if ( hFile != null && !hFile.IsInvalid )
                    hFile.Dispose(); 
 
                throw;
            } 
            finally
            {
                if (eaBuffer != null)
                { 
                    eaBuffer.Dispose();
                    eaBuffer = null; 
                } 

                if (qos != null) 
                {
                    qos.Dispose();
                    qos = null;
                } 

                if (objectName != null) 
                { 
                    objectName.Dispose();
                    objectName = null; 
                }
            }
        }
 
        #region private helper methods
 
        // This method exists to ensure that the requested path name is unique so that SMB/DNS is prevented 
        //   from collapsing a file open request to a file handle opened previously. In the SQL FILESTREAM case,
        //   this would likely be a file open in another transaction, so this mechanism ensures isolation. 
        static private string InitializeNtPath(string path)
        {
            // ensure we have validated and normalized the path before
            AssertPathFormat ( path ); 

            string formatPath = @"\??\UNC\{0}\{1}"; 
 
            string uniqueId = Guid.NewGuid().ToString("N");
            return String.Format ( CultureInfo.InvariantCulture, formatPath, path.Trim('\\'), uniqueId); 
        }

        #endregion
 
    }
 
    //-------------------------------------------------------------------------- 
    // UnicodeString
    // 
    // Description: this class encapsulates the marshalling of data from a
    //   managed representation of the UNICODE_STRING struct into native code.
    //   As part of this task, it manages memory that is allocated in the
    //   native heap into which the managed representation is blitted. The 
    //   class also implements a SafeHandle pattern to ensure that memory is
    //   not leaked in "exceptional" circumstances such as Thread.Abort(). 
    // 
    //--------------------------------------------------------------------------
 
    internal class UnicodeString : SafeHandleZeroOrMinusOneIsInvalid
    {
        public UnicodeString(string path)
            : base (true) 
        {
            Initialize(path); 
        } 

        // NOTE: SafeHandle's critical finalizer will call ReleaseHandle for us 
        protected override bool ReleaseHandle()
        {
            if (base.handle == IntPtr.Zero)
                return true; 

            Marshal.FreeHGlobal(base.handle); 
            base.handle = IntPtr.Zero; 

            return true; 
        }

        private void Initialize(string path)
        { 
            // pre-condition should be validated in public interface
            System.Diagnostics.Debug.Assert( path.Length <= ( UInt16.MaxValue / sizeof(char) ) ); 
 
            UnsafeNativeMethods.UNICODE_STRING objectName;
            objectName.length = (UInt16)(path.Length * sizeof(char)); 
            objectName.maximumLength = (UInt16)(path.Length * sizeof(char));
            objectName.buffer = path;

            IntPtr pbBuffer = IntPtr.Zero; 
            RuntimeHelpers.PrepareConstrainedRegions();
            try 
            { 
            }
            finally 
            {
                pbBuffer = Marshal.AllocHGlobal ( Marshal.SizeOf ( objectName ) );
                if ( pbBuffer != IntPtr.Zero )
                    SetHandle ( pbBuffer ); 
            }
 
            bool mustRelease = false; 
            RuntimeHelpers.PrepareConstrainedRegions();
            try 
            {
                DangerousAddRef ( ref mustRelease );
                IntPtr ptr = DangerousGetHandle();
 
                Marshal.StructureToPtr ( objectName, ptr, false );
            } 
            finally 
            {
                if ( mustRelease ) 
                    DangerousRelease();
            }
        }
    } 

    //------------------------------------------------------------------------- 
    // SecurityQualityOfService 
    //
    // Description: this class encapsulates the marshalling of data from a 
    //   managed representation of the SECURITY_QUALITY_OF_SERVICE struct into
    //   native code. As part of this task, it pins the struct in the managed
    //   heap to ensure that it is not moved around (since the struct consists
    //   of simple types, the type does not need to be blitted into native 
    //   memory). The class also implements a SafeHandle pattern to ensure that
    //   the struct is unpinned in "exceptional" circumstances such as 
    //   Thread.Abort(). 
    //
    //-------------------------------------------------------------------------- 

    internal class SecurityQualityOfService : SafeHandleZeroOrMinusOneIsInvalid
    {
        UnsafeNativeMethods.SECURITY_QUALITY_OF_SERVICE m_qos; 
        private GCHandle m_hQos;
 
        public SecurityQualityOfService 
            (
                UnsafeNativeMethods.SecurityImpersonationLevel impersonationLevel, 
                bool effectiveOnly,
                bool dynamicTrackingMode
            )
            : base (true) 
        {
            Initialize (impersonationLevel, effectiveOnly, dynamicTrackingMode); 
        } 

        protected override bool ReleaseHandle() 
        {
            if ( m_hQos.IsAllocated )
                m_hQos.Free();
 
            base.handle = IntPtr.Zero;
 
            return true; 
        }
 
        internal void Initialize
            (
                UnsafeNativeMethods.SecurityImpersonationLevel impersonationLevel,
                bool effectiveOnly, 
                bool dynamicTrackingMode
            ) 
        { 
            m_qos.length = (uint)Marshal.SizeOf(typeof(UnsafeNativeMethods.SECURITY_QUALITY_OF_SERVICE));
            // VSTFDevDiv # 547461 [Backport SqlFileStream fix on Win7 to QFE branch] 
            // Win7 enforces correct values for the _SECURITY_QUALITY_OF_SERVICE.qos member.
            m_qos.impersonationLevel = (int)impersonationLevel;

            m_qos.effectiveOnly = effectiveOnly ? (byte) 1 : (byte) 0; 
            m_qos.contextDynamicTrackingMode = dynamicTrackingMode ? (byte) 1 : (byte) 0;
 
            IntPtr pbBuffer = IntPtr.Zero; 
            RuntimeHelpers.PrepareConstrainedRegions();
            try 
            {
            }
            finally
            { 
                // pin managed objects
                m_hQos = GCHandle.Alloc(m_qos, GCHandleType.Pinned); 
 
                pbBuffer = m_hQos.AddrOfPinnedObject();
 
                if ( pbBuffer != IntPtr.Zero )
                    SetHandle ( pbBuffer );
            }
        } 
    }
 
    //------------------------------------------------------------------------- 
    // FileFullEaInformation
    // 
    // Description: this class encapsulates the marshalling of data from a
    //   managed representation of the FILE_FULL_EA_INFORMATION struct into
    //   native code. As part of this task, it manages memory that is allocated
    //   in the native heap into which the managed representation is blitted. 
    //   The class also implements a SafeHandle pattern to ensure that memory
    //   is not leaked in "exceptional" circumstances such as Thread.Abort(). 
    // 
    //-------------------------------------------------------------------------
 
    internal class FileFullEaInformation : SafeHandleZeroOrMinusOneIsInvalid
    {
        private string EA_NAME_STRING = "Filestream_Transaction_Tag";
        private int m_cbBuffer; 

        public FileFullEaInformation(byte[] transactionContext) 
            : base (true) 
        {
            m_cbBuffer = 0; 
            InitializeEaBuffer(transactionContext);
        }

        protected override bool ReleaseHandle() 
        {
            m_cbBuffer = 0; 
 
            if (base.handle == IntPtr.Zero)
                return true; 

            Marshal.FreeHGlobal(base.handle);
            base.handle = IntPtr.Zero;
 
            return true;
        } 
 
        public int Length
        { 
            get
            {
                return m_cbBuffer;
            } 
        }
 
        private void InitializeEaBuffer(byte[] transactionContext) 
        {
            if (transactionContext.Length >= UInt16.MaxValue) 
                throw ADP.ArgumentOutOfRange ( "transactionContext" );

            UnsafeNativeMethods.FILE_FULL_EA_INFORMATION eaBuffer;
            eaBuffer.nextEntryOffset = 0; 
            eaBuffer.flags = 0;
            eaBuffer.EaName = 0; 
 
            // string will be written as ANSI chars, so Length == ByteLength in this case
            eaBuffer.EaNameLength = (byte) EA_NAME_STRING.Length; 
            eaBuffer.EaValueLength = (ushort) transactionContext.Length;

            // allocate sufficient memory to contain the FILE_FULL_EA_INFORMATION struct and
            //   the contiguous name/value pair in eaName (note: since the struct already 
            //   contains one byte for eaName, we don't need to allocate a byte for the
            //   null character separator). 
            m_cbBuffer = Marshal.SizeOf(eaBuffer) + eaBuffer.EaNameLength + eaBuffer.EaValueLength; 

            IntPtr pbBuffer = IntPtr.Zero; 
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
            } 
            finally
            { 
                pbBuffer = Marshal.AllocHGlobal(m_cbBuffer); 
                if ( pbBuffer != IntPtr.Zero )
                    SetHandle ( pbBuffer ); 
            }

            bool mustRelease = false;
            RuntimeHelpers.PrepareConstrainedRegions(); 
            try
            { 
                DangerousAddRef ( ref mustRelease ); 
                IntPtr ptr = DangerousGetHandle();
 
                // write struct into buffer
                Marshal.StructureToPtr(eaBuffer, ptr, false);

                // write property name into buffer 
                System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding();
                byte [] asciiName = ascii.GetBytes(EA_NAME_STRING); 
 
                // calculate offset at which to write the name/value pair
                System.Diagnostics.Debug.Assert(Marshal.OffsetOf(typeof(UnsafeNativeMethods.FILE_FULL_EA_INFORMATION), "EaName").ToInt64() <= (Int64) Int32.MaxValue); 
                int cbOffset = Marshal.OffsetOf(typeof(UnsafeNativeMethods.FILE_FULL_EA_INFORMATION), "EaName").ToInt32();
                for (int i = 0; cbOffset < m_cbBuffer && i < eaBuffer.EaNameLength; i++, cbOffset++)
                {
                    Marshal.WriteByte(ptr, cbOffset, asciiName[i]); 
                }
 
                System.Diagnostics.Debug.Assert ( cbOffset < m_cbBuffer ); 

                // write null character separator 
                Marshal.WriteByte(ptr, cbOffset, 0);
                cbOffset++;

                System.Diagnostics.Debug.Assert ( cbOffset < m_cbBuffer || transactionContext.Length == 0 && cbOffset == m_cbBuffer ); 

                // write transaction context ID 
                for (int i = 0; cbOffset < m_cbBuffer && i < eaBuffer.EaValueLength; i++, cbOffset++) 
                {
                    Marshal.WriteByte(ptr, cbOffset, transactionContext[i]); 
                }
            }
            finally
            { 
                if ( mustRelease )
                    DangerousRelease(); 
            } 
        }
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK