| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- // ==++==
- //
- // Copyright (c) Microsoft Corporation. All rights reserved.
- //
- // ==--==
- /*============================================================
- **
- ** Class: LogStream
- **
- ===========================================================*/
- using System;
- using Microsoft.Win32;
- using Microsoft.Win32.SafeHandles;
- using System.Security;
- using System.Security.Permissions;
- using System.Threading;
- using System.Runtime.InteropServices;
- using System.Runtime.Remoting.Messaging;
- using System.Runtime.CompilerServices;
- using System.Globalization;
- using System.Runtime.Versioning;
- using System.Diagnostics;
- using System.Diagnostics.Contracts;
- namespace System.IO {
- // This stream has very limited support to enable EventSchemaTraceListener
- // Eventually we might want to add more functionality and expose this type
- internal class LogStream : BufferedStream2
- {
- internal const long DefaultFileSize = 10*1000*1024;
- internal const int DefaultNumberOfFiles = 2;
- internal const LogRetentionOption DefaultRetention = LogRetentionOption.SingleFileUnboundedSize;
- // Retention policy
- private const int _retentionRetryThreshold = 2;
- private LogRetentionOption _retention;
- private long _maxFileSize = DefaultFileSize;
- private int _maxNumberOfFiles = DefaultNumberOfFiles;
- private int _currentFileNum = 1;
- bool _disableLogging;
- int _retentionRetryCount;
- private bool _canRead;
- private bool _canWrite;
- private bool _canSeek;
- [SecurityCritical]
- private SafeFileHandle _handle;
-
- private String _fileName; // Fully qualified file name.
- string _fileNameWithoutExt;
- string _fileExt;
-
- // Save input for retention
- string _pathSav;
- int _fAccessSav;
- FileShare _shareSav;
- UnsafeNativeMethods.SECURITY_ATTRIBUTES _secAttrsSav;
- FileIOPermissionAccess _secAccessSav;
- FileMode _modeSav;
- int _flagsAndAttributesSav;
- bool _seekToEndSav;
- private readonly object m_lockObject = new Object();
- //Limited to immediate internal need from EventSchemaTraceListener
- //Not param validation done!!
- [ResourceExposure(ResourceScope.Machine)]
- [ResourceConsumption(ResourceScope.Machine)]
- [System.Security.SecurityCritical]
- internal LogStream(String path, int bufferSize, LogRetentionOption retention, long maxFileSize, int maxNumOfFiles)
- {
- Debug.Assert(!String.IsNullOrEmpty(path));
- // Get absolute path - Security needs this to prevent something
- // like trying to create a file in c:\tmp with the name
- // "..\WinNT\System32\ntoskrnl.exe". Store it for user convenience.
- //String filePath = Path.GetFullPathInternal(path);
- String filePath = Path.GetFullPath(path);
- _fileName = filePath;
- // Prevent access to your disk drives as raw block devices.
- if (filePath.StartsWith("\\\\.\\", StringComparison.Ordinal))
- throw new NotSupportedException(SR.GetString(SR.NotSupported_IONonFileDevices));
- UnsafeNativeMethods.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(FileShare.Read);
-
- // For mitigating local elevation of privilege attack through named pipes
- // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
- // named pipe server can't impersonate a high privileged client security context
- int flagsAndAttributes = (int)FileOptions.None | (UnsafeNativeMethods.SECURITY_SQOS_PRESENT | UnsafeNativeMethods.SECURITY_ANONYMOUS);
-
- // Only write is enabled
- //_canRead = false;
- //_canSeek = false;
- _canWrite = true;
- _pathSav = filePath;
- _fAccessSav = UnsafeNativeMethods.GENERIC_WRITE;
- _shareSav = FileShare.Read;
- _secAttrsSav = secAttrs;
- _secAccessSav = FileIOPermissionAccess.Write;
- _modeSav = (retention != LogRetentionOption.SingleFileUnboundedSize)? FileMode.Create : FileMode.OpenOrCreate;
- _flagsAndAttributesSav = flagsAndAttributes;
- _seekToEndSav = (retention != LogRetentionOption.SingleFileUnboundedSize)? false : true;
-
- this.bufferSize = bufferSize;
- _retention = retention;
- _maxFileSize = maxFileSize;
- _maxNumberOfFiles = maxNumOfFiles;
- _Init(filePath, _fAccessSav, _shareSav, _secAttrsSav, _secAccessSav, _modeSav, _flagsAndAttributesSav, _seekToEndSav);
- }
- [System.Security.SecurityCritical]
- internal void _Init(String path, int fAccess, FileShare share, UnsafeNativeMethods.SECURITY_ATTRIBUTES secAttrs, FileIOPermissionAccess secAccess,
- FileMode mode, int flagsAndAttributes, bool seekToEnd)
- {
- String filePath = Path.GetFullPath(path);
- _fileName = filePath;
- new FileIOPermission(secAccess, new String[] { filePath }).Demand();
- // Don't pop up a dialog for reading from an emtpy floppy drive
- int oldMode = UnsafeNativeMethods.SetErrorMode(UnsafeNativeMethods.SEM_FAILCRITICALERRORS);
- try {
- _handle = UnsafeNativeMethods.SafeCreateFile(filePath, fAccess, share, secAttrs, mode, flagsAndAttributes, UnsafeNativeMethods.NULL);
- int errorCode = Marshal.GetLastWin32Error();
- if (_handle.IsInvalid) {
- // Return a meaningful exception, using the RELATIVE path to
- // the file to avoid returning extra information to the caller
- // unless they have path discovery permission, in which case
- // the full path is fine & useful.
- // We need to give an exception, and preferably it would include
- // the fully qualified path name. Do security check here. If
- // we fail, give back the msgPath, which should not reveal much.
- // While this logic is largely duplicated in
- // __Error.WinIOError, we need this for
- // IsolatedStorageLogFileStream.
- bool canGiveFullPath = false;
- try {
- new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }).Demand();
- canGiveFullPath = true;
- }
- catch(SecurityException) {}
- if (canGiveFullPath)
- __Error.WinIOError(errorCode, _fileName);
- else
- __Error.WinIOError(errorCode, Path.GetFileName(_fileName));
- }
- }
- finally {
- UnsafeNativeMethods.SetErrorMode(oldMode);
- }
- Debug.Assert(UnsafeNativeMethods.GetFileType(_handle) == UnsafeNativeMethods.FILE_TYPE_DISK, "did someone accidentally removed the device type check from SafeCreateFile P/Invoke wrapper?");
- pos = 0;
- // For Append mode...
- if (seekToEnd) {
- SeekCore(0, SeekOrigin.End);
- }
- }
- public override bool CanRead {
- [Pure]
- get { return _canRead; }
- }
- public override bool CanWrite {
- [Pure]
- get { return _canWrite; }
- }
- public override bool CanSeek {
- [Pure]
- get { return _canSeek; }
- }
- public override long Length {
- get {
- throw new NotSupportedException();
- }
- }
- public override long Position {
- get {
- throw new NotSupportedException();
- }
- set {
- throw new NotSupportedException();
- }
- }
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
- public override int Read(byte[] array, int offset, int count)
- {
- throw new NotSupportedException();
- }
- [System.Security.SecurityCritical]
- protected override unsafe void WriteCore(byte[] buffer, int offset, int count, bool blockForWrite, out long streamPos) {
- Debug.Assert(CanWrite, "CanWrite");
- Debug.Assert(buffer != null, "buffer != null");
- Debug.Assert(offset >= 0, "offset is negative");
- Debug.Assert(count >= 0, "count is negative");
-
- int hr = 0;
- int r = WriteFileNative(buffer, offset, count, null, out hr);
- if (r == -1) {
- // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
- if (hr == UnsafeNativeMethods.ERROR_NO_DATA) {
- r = 0;
- }
- else {
- // ERROR_INVALID_PARAMETER may be returned for writes
- // where the position is too large (ie, writing at Int64.MaxValue
- // on Win9x) OR for synchronous writes to a handle opened
- // asynchronously.
- if (hr == UnsafeNativeMethods.ERROR_INVALID_PARAMETER)
- throw new IOException(SR.GetString(SR.IO_FileTooLongOrHandleNotSync));
- __Error.WinIOError(hr, String.Empty);
- }
- }
- Debug.Assert(r >= 0, "WriteCore is likely broken.");
- // update cached position
- streamPos = AddUnderlyingStreamPosition((long)r);
- EnforceRetentionPolicy(_handle, streamPos);
- streamPos = pos;
- return;
- }
- [System.Security.SecurityCritical]
- unsafe private int WriteFileNative(byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) {
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- if (_disableLogging) {
- hr = 0;
- return 0;
- }
- Debug.Assert(offset >= 0, "offset >= 0");
- Debug.Assert(count >= 0, "count >= 0");
- Debug.Assert(bytes != null, "bytes != null");
- // Don't corrupt memory when multiple threads are erroneously writing
- // to this stream simultaneously. (the OS is reading from
- // the array we pass to WriteFile, but if we read beyond the end and
- // that memory isn't allocated, we could get an AV.)
- if (bytes.Length - offset < count)
- throw new IndexOutOfRangeException(SR.GetString(SR.IndexOutOfRange_IORaceCondition));
- // You can't use the fixed statement on an array of length 0.
- if (bytes.Length==0) {
- hr = 0;
- return 0;
- }
- int numBytesWritten = 0;
- int r = 0;
-
- fixed(byte* p = bytes) {
- r = UnsafeNativeMethods.WriteFile(_handle, p + offset, count, out numBytesWritten, overlapped);
- }
- if (r == 0) {
- // We should never silently ---- an error here without some
- // extra work. We must make sure that BeginWriteCore won't return an
- // IAsyncResult that will cause EndWrite to block, since the OS won't
- // call AsyncFSCallback for us.
- hr = Marshal.GetLastWin32Error();
- // For invalid handles, detect the error and mark our handle
- // as closed to give slightly better error messages. Also
- // help ensure we avoid handle recycling bugs.
- if (hr == UnsafeNativeMethods.ERROR_INVALID_HANDLE)
- _handle.SetHandleAsInvalid();
- return -1;
- }
- else
- hr = 0;
- return numBytesWritten;
- }
- // This doesn't do argument checking. Necessary for SetLength, which must
- // set the file pointer beyond the end of the file. This will update the
- // internal position
- [System.Security.SecurityCritical]
- private long SeekCore(long offset, SeekOrigin origin)
- {
- Debug.Assert(!_handle.IsClosed, "!_handle.IsClosed");
- Debug.Assert(origin>=SeekOrigin.Begin && origin<=SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End");
- int hr = 0;
- long ret = 0;
-
- ret = UnsafeNativeMethods.SetFilePointer(_handle, offset, origin, out hr);
- if (ret == -1) {
- // For invalid handles, detect the error and mark our handle
- // as closed to give slightly better error messages. Also
- // help ensure we avoid handle recycling bugs.
- if (hr == UnsafeNativeMethods.ERROR_INVALID_HANDLE)
- _handle.SetHandleAsInvalid();
- __Error.WinIOError(hr, String.Empty);
- }
-
- UnderlyingStreamPosition = ret;
- return ret;
- }
- [System.Security.SecurityCritical]
- protected override void Dispose(bool disposing)
- {
- // Nothing will be done differently based on whether we are
- // disposing vs. finalizing. This is taking advantage of the
- // weak ordering between normal finalizable objects & critical
- // finalizable objects, which I included in the SafeHandle
- // design for LogStream, which would often "just work" when
- // finalized.
- try {
- if (_handle == null || _handle.IsClosed) {
- // Make sure BufferedStream doesn't try to flush data on a closed handle
- DiscardBuffer();
- }
- }
- finally {
- try {
- // Cleanup base streams
- base.Dispose(disposing);
- }
- finally {
- if (_handle != null && !_handle.IsClosed)
- _handle.Dispose();
- _handle = null;
- _canRead = false;
- _canWrite = false;
- _canSeek = false;
- }
- }
- }
- [System.Security.SecurityCritical]
- ~LogStream()
- {
- if (_handle != null) {
- Dispose(false);
- }
- }
- [System.Security.SecurityCritical]
- private void EnforceRetentionPolicy(SafeFileHandle handle, long lastPos)
- {
- switch (_retention) {
- case LogRetentionOption.LimitedSequentialFiles:
- case LogRetentionOption.UnlimitedSequentialFiles:
- case LogRetentionOption.LimitedCircularFiles:
- if ((lastPos >= _maxFileSize) && (handle == _handle)){
- lock (m_lockObject) {
- if ((handle != _handle) || (lastPos < _maxFileSize))
- return;
-
- _currentFileNum++;
- if ((_retention == LogRetentionOption.LimitedCircularFiles) && (_currentFileNum > _maxNumberOfFiles)) {
- _currentFileNum = 1;
- }
- else if ((_retention == LogRetentionOption.LimitedSequentialFiles) && (_currentFileNum > _maxNumberOfFiles)) {
- _DisableLogging();
- return;
- }
- if (_fileNameWithoutExt == null) {
- _fileNameWithoutExt = Path.Combine(Path.GetDirectoryName(_pathSav), Path.GetFileNameWithoutExtension(_pathSav));
- _fileExt = Path.GetExtension(_pathSav);
- }
- string path = (_currentFileNum == 1)?_pathSav: _fileNameWithoutExt + _currentFileNum.ToString(CultureInfo.InvariantCulture) + _fileExt;
- try {
- _Init(path, _fAccessSav, _shareSav, _secAttrsSav, _secAccessSav, _modeSav, _flagsAndAttributesSav, _seekToEndSav);
-
- // Dispose the old handle and release the file write lock
- // No need to flush the buffer as we just came off a write
- if (handle != null && !handle.IsClosed) {
- handle.Dispose();
- }
- }
- catch (IOException ) {
- // Should we do this only for ERROR_SHARING_VIOLATION?
- //if (UnsafeNativeMethods.MakeErrorCodeFromHR(Marshal.GetHRForException(ioexc)) != InternalResources.ERROR_SHARING_VIOLATION) break;
-
- // Possible sharing violation - ----? Let the next iteration try again
- // For now revert the handle to the original one
- _handle = handle;
-
- _retentionRetryCount++;
- if (_retentionRetryCount >= _retentionRetryThreshold) {
- _DisableLogging();
- }
- #if DEBUG
- throw;
- #endif
- }
- catch (UnauthorizedAccessException ) {
- // Indicative of ACL issues
- _DisableLogging();
- #if DEBUG
- throw;
- #endif
- }
- catch (Exception ) {
- _DisableLogging();
- #if DEBUG
- throw;
- #endif
- }
- }
- }
- break;
-
- case LogRetentionOption.SingleFileBoundedSize:
- if (lastPos >= _maxFileSize)
- _DisableLogging();
- break;
- case LogRetentionOption.SingleFileUnboundedSize:
- break;
- }
- }
- // When we enable this class widely, we need to raise an
- // event when we disable logging due to rention policy or
- // error such as ACL that is preventing retention
- [MethodImplAttribute(MethodImplOptions.Synchronized)]
- private void _DisableLogging()
- {
- // Discard write buffer?
- _disableLogging = true;
- }
- [System.Security.SecurityCritical]
- private static UnsafeNativeMethods.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
- {
- UnsafeNativeMethods.SECURITY_ATTRIBUTES secAttrs = null;
- if ((share & FileShare.Inheritable) != 0) {
- secAttrs = new UnsafeNativeMethods.SECURITY_ATTRIBUTES();
- secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);
- secAttrs.bInheritHandle = 1;
- }
- return secAttrs;
- }
- }
- }
|