3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
10 ===========================================================*/
12 using Microsoft
.Win32
;
13 using Microsoft
.Win32
.SafeHandles
;
14 using System
.Security
;
15 using System
.Security
.Permissions
;
16 using System
.Threading
;
17 using System
.Runtime
.InteropServices
;
18 using System
.Runtime
.Remoting
.Messaging
;
19 using System
.Runtime
.CompilerServices
;
20 using System
.Globalization
;
21 using System
.Runtime
.Versioning
;
23 using System
.Diagnostics
;
24 using System
.Diagnostics
.Contracts
;
28 // This stream has very limited support to enable EventSchemaTraceListener
29 // Eventually we might want to add more functionality and expose this type
30 internal class LogStream
: BufferedStream2
32 internal const long DefaultFileSize
= 10*1000*1024;
33 internal const int DefaultNumberOfFiles
= 2;
34 internal const LogRetentionOption DefaultRetention
= LogRetentionOption
.SingleFileUnboundedSize
;
37 private const int _retentionRetryThreshold
= 2;
38 private LogRetentionOption _retention
;
39 private long _maxFileSize
= DefaultFileSize
;
40 private int _maxNumberOfFiles
= DefaultNumberOfFiles
;
41 private int _currentFileNum
= 1;
43 int _retentionRetryCount
;
45 private bool _canRead
;
46 private bool _canWrite
;
47 private bool _canSeek
;
49 private SafeFileHandle _handle
;
51 private String _fileName
; // Fully qualified file name.
52 string _fileNameWithoutExt
;
55 // Save input for retention
59 UnsafeNativeMethods
.SECURITY_ATTRIBUTES _secAttrsSav
;
60 FileIOPermissionAccess _secAccessSav
;
62 int _flagsAndAttributesSav
;
65 private readonly object m_lockObject
= new Object();
67 //Limited to immediate internal need from EventSchemaTraceListener
68 //Not param validation done!!
69 [ResourceExposure(ResourceScope
.Machine
)]
70 [ResourceConsumption(ResourceScope
.Machine
)]
71 [System
.Security
.SecurityCritical
]
72 internal LogStream(String path
, int bufferSize
, LogRetentionOption retention
, long maxFileSize
, int maxNumOfFiles
)
74 Debug
.Assert(!String
.IsNullOrEmpty(path
));
76 // Get absolute path - Security needs this to prevent something
77 // like trying to create a file in c:\tmp with the name
78 // "..\WinNT\System32\ntoskrnl.exe". Store it for user convenience.
79 //String filePath = Path.GetFullPathInternal(path);
80 String filePath
= Path
.GetFullPath(path
);
83 // Prevent access to your disk drives as raw block devices.
84 if (filePath
.StartsWith("\\\\.\\", StringComparison
.Ordinal
))
85 throw new NotSupportedException(SR
.GetString(SR
.NotSupported_IONonFileDevices
));
87 UnsafeNativeMethods
.SECURITY_ATTRIBUTES secAttrs
= GetSecAttrs(FileShare
.Read
);
89 // For mitigating local elevation of privilege attack through named pipes
90 // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
91 // named pipe server can't impersonate a high privileged client security context
92 int flagsAndAttributes
= (int)FileOptions
.None
| (UnsafeNativeMethods
.SECURITY_SQOS_PRESENT
| UnsafeNativeMethods
.SECURITY_ANONYMOUS
);
94 // Only write is enabled
100 _fAccessSav
= UnsafeNativeMethods
.GENERIC_WRITE
;
101 _shareSav
= FileShare
.Read
;
102 _secAttrsSav
= secAttrs
;
103 _secAccessSav
= FileIOPermissionAccess
.Write
;
104 _modeSav
= (retention
!= LogRetentionOption
.SingleFileUnboundedSize
)? FileMode
.Create
: FileMode
.OpenOrCreate
;
105 _flagsAndAttributesSav
= flagsAndAttributes
;
106 _seekToEndSav
= (retention
!= LogRetentionOption
.SingleFileUnboundedSize
)? false : true;
108 this.bufferSize
= bufferSize
;
109 _retention
= retention
;
110 _maxFileSize
= maxFileSize
;
111 _maxNumberOfFiles
= maxNumOfFiles
;
113 _Init(filePath
, _fAccessSav
, _shareSav
, _secAttrsSav
, _secAccessSav
, _modeSav
, _flagsAndAttributesSav
, _seekToEndSav
);
116 [System
.Security
.SecurityCritical
]
117 internal void _Init(String path
, int fAccess
, FileShare share
, UnsafeNativeMethods
.SECURITY_ATTRIBUTES secAttrs
, FileIOPermissionAccess secAccess
,
118 FileMode mode
, int flagsAndAttributes
, bool seekToEnd
)
120 String filePath
= Path
.GetFullPath(path
);
121 _fileName
= filePath
;
123 new FileIOPermission(secAccess
, new String
[] { filePath }
).Demand();
125 // Don't pop up a dialog for reading from an emtpy floppy drive
126 int oldMode
= UnsafeNativeMethods
.SetErrorMode(UnsafeNativeMethods
.SEM_FAILCRITICALERRORS
);
128 _handle
= UnsafeNativeMethods
.SafeCreateFile(filePath
, fAccess
, share
, secAttrs
, mode
, flagsAndAttributes
, UnsafeNativeMethods
.NULL
);
129 int errorCode
= Marshal
.GetLastWin32Error();
131 if (_handle
.IsInvalid
) {
132 // Return a meaningful exception, using the RELATIVE path to
133 // the file to avoid returning extra information to the caller
134 // unless they have path discovery permission, in which case
135 // the full path is fine & useful.
137 // We need to give an exception, and preferably it would include
138 // the fully qualified path name. Do security check here. If
139 // we fail, give back the msgPath, which should not reveal much.
140 // While this logic is largely duplicated in
141 // __Error.WinIOError, we need this for
142 // IsolatedStorageLogFileStream.
143 bool canGiveFullPath
= false;
146 new FileIOPermission(FileIOPermissionAccess
.PathDiscovery
, new String
[] { _fileName }
).Demand();
147 canGiveFullPath
= true;
149 catch(SecurityException
) {}
152 __Error
.WinIOError(errorCode
, _fileName
);
154 __Error
.WinIOError(errorCode
, Path
.GetFileName(_fileName
));
158 UnsafeNativeMethods
.SetErrorMode(oldMode
);
160 Debug
.Assert(UnsafeNativeMethods
.GetFileType(_handle
) == UnsafeNativeMethods
.FILE_TYPE_DISK
, "did someone accidentally removed the device type check from SafeCreateFile P/Invoke wrapper?");
164 // For Append mode...
166 SeekCore(0, SeekOrigin
.End
);
170 public override bool CanRead
{
172 get { return _canRead; }
175 public override bool CanWrite
{
177 get { return _canWrite; }
180 public override bool CanSeek
{
182 get { return _canSeek; }
185 public override long Length
{
187 throw new NotSupportedException();
191 public override long Position
{
193 throw new NotSupportedException();
196 throw new NotSupportedException();
200 public override void SetLength(long value)
202 throw new NotSupportedException();
205 public override long Seek(long offset
, SeekOrigin origin
)
207 throw new NotSupportedException();
210 public override int Read(byte[] array
, int offset
, int count
)
212 throw new NotSupportedException();
215 [System
.Security
.SecurityCritical
]
216 protected override unsafe void WriteCore(byte[] buffer
, int offset
, int count
, bool blockForWrite
, out long streamPos
) {
217 Debug
.Assert(CanWrite
, "CanWrite");
218 Debug
.Assert(buffer
!= null, "buffer != null");
219 Debug
.Assert(offset
>= 0, "offset is negative");
220 Debug
.Assert(count
>= 0, "count is negative");
223 int r
= WriteFileNative(buffer
, offset
, count
, null, out hr
);
225 // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
226 if (hr
== UnsafeNativeMethods
.ERROR_NO_DATA
) {
230 // ERROR_INVALID_PARAMETER may be returned for writes
231 // where the position is too large (ie, writing at Int64.MaxValue
232 // on Win9x) OR for synchronous writes to a handle opened
234 if (hr
== UnsafeNativeMethods
.ERROR_INVALID_PARAMETER
)
235 throw new IOException(SR
.GetString(SR
.IO_FileTooLongOrHandleNotSync
));
236 __Error
.WinIOError(hr
, String
.Empty
);
239 Debug
.Assert(r
>= 0, "WriteCore is likely broken.");
240 // update cached position
241 streamPos
= AddUnderlyingStreamPosition((long)r
);
242 EnforceRetentionPolicy(_handle
, streamPos
);
247 [System
.Security
.SecurityCritical
]
248 unsafe private int WriteFileNative(byte[] bytes
, int offset
, int count
, NativeOverlapped
* overlapped
, out int hr
) {
249 if (_handle
.IsClosed
) __Error
.FileNotOpen();
251 if (_disableLogging
) {
256 Debug
.Assert(offset
>= 0, "offset >= 0");
257 Debug
.Assert(count
>= 0, "count >= 0");
258 Debug
.Assert(bytes
!= null, "bytes != null");
260 // Don't corrupt memory when multiple threads are erroneously writing
261 // to this stream simultaneously. (the OS is reading from
262 // the array we pass to WriteFile, but if we read beyond the end and
263 // that memory isn't allocated, we could get an AV.)
264 if (bytes
.Length
- offset
< count
)
265 throw new IndexOutOfRangeException(SR
.GetString(SR
.IndexOutOfRange_IORaceCondition
));
267 // You can't use the fixed statement on an array of length 0.
268 if (bytes
.Length
==0) {
273 int numBytesWritten
= 0;
276 fixed(byte* p
= bytes
) {
277 r
= UnsafeNativeMethods
.WriteFile(_handle
, p
+ offset
, count
, out numBytesWritten
, overlapped
);
281 // We should never silently swallow an error here without some
282 // extra work. We must make sure that BeginWriteCore won't return an
283 // IAsyncResult that will cause EndWrite to block, since the OS won't
284 // call AsyncFSCallback for us.
285 hr
= Marshal
.GetLastWin32Error();
287 // For invalid handles, detect the error and mark our handle
288 // as closed to give slightly better error messages. Also
289 // help ensure we avoid handle recycling bugs.
290 if (hr
== UnsafeNativeMethods
.ERROR_INVALID_HANDLE
)
291 _handle
.SetHandleAsInvalid();
297 return numBytesWritten
;
300 // This doesn't do argument checking. Necessary for SetLength, which must
301 // set the file pointer beyond the end of the file. This will update the
303 [System
.Security
.SecurityCritical
]
304 private long SeekCore(long offset
, SeekOrigin origin
)
306 Debug
.Assert(!_handle
.IsClosed
, "!_handle.IsClosed");
307 Debug
.Assert(origin
>=SeekOrigin
.Begin
&& origin
<=SeekOrigin
.End
, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End");
311 ret
= UnsafeNativeMethods
.SetFilePointer(_handle
, offset
, origin
, out hr
);
313 // For invalid handles, detect the error and mark our handle
314 // as closed to give slightly better error messages. Also
315 // help ensure we avoid handle recycling bugs.
316 if (hr
== UnsafeNativeMethods
.ERROR_INVALID_HANDLE
)
317 _handle
.SetHandleAsInvalid();
318 __Error
.WinIOError(hr
, String
.Empty
);
321 UnderlyingStreamPosition
= ret
;
325 [System
.Security
.SecurityCritical
]
326 protected override void Dispose(bool disposing
)
328 // Nothing will be done differently based on whether we are
329 // disposing vs. finalizing. This is taking advantage of the
330 // weak ordering between normal finalizable objects & critical
331 // finalizable objects, which I included in the SafeHandle
332 // design for LogStream, which would often "just work" when
335 if (_handle
== null || _handle
.IsClosed
) {
336 // Make sure BufferedStream doesn't try to flush data on a closed handle
342 // Cleanup base streams
343 base.Dispose(disposing
);
346 if (_handle
!= null && !_handle
.IsClosed
)
357 [System
.Security
.SecurityCritical
]
360 if (_handle
!= null) {
365 [System
.Security
.SecurityCritical
]
366 private void EnforceRetentionPolicy(SafeFileHandle handle
, long lastPos
)
368 switch (_retention
) {
369 case LogRetentionOption
.LimitedSequentialFiles
:
370 case LogRetentionOption
.UnlimitedSequentialFiles
:
371 case LogRetentionOption
.LimitedCircularFiles
:
372 if ((lastPos
>= _maxFileSize
) && (handle
== _handle
)){
373 lock (m_lockObject
) {
374 if ((handle
!= _handle
) || (lastPos
< _maxFileSize
))
378 if ((_retention
== LogRetentionOption
.LimitedCircularFiles
) && (_currentFileNum
> _maxNumberOfFiles
)) {
381 else if ((_retention
== LogRetentionOption
.LimitedSequentialFiles
) && (_currentFileNum
> _maxNumberOfFiles
)) {
386 if (_fileNameWithoutExt
== null) {
387 _fileNameWithoutExt
= Path
.Combine(Path
.GetDirectoryName(_pathSav
), Path
.GetFileNameWithoutExtension(_pathSav
));
388 _fileExt
= Path
.GetExtension(_pathSav
);
391 string path
= (_currentFileNum
== 1)?_pathSav
: _fileNameWithoutExt
+ _currentFileNum
.ToString(CultureInfo
.InvariantCulture
) + _fileExt
;
393 _Init(path
, _fAccessSav
, _shareSav
, _secAttrsSav
, _secAccessSav
, _modeSav
, _flagsAndAttributesSav
, _seekToEndSav
);
395 // Dispose the old handle and release the file write lock
396 // No need to flush the buffer as we just came off a write
397 if (handle
!= null && !handle
.IsClosed
) {
401 catch (IOException
) {
402 // Should we do this only for ERROR_SHARING_VIOLATION?
403 //if (UnsafeNativeMethods.MakeErrorCodeFromHR(Marshal.GetHRForException(ioexc)) != InternalResources.ERROR_SHARING_VIOLATION) break;
405 // Possible sharing violation - ----? Let the next iteration try again
406 // For now revert the handle to the original one
409 _retentionRetryCount
++;
410 if (_retentionRetryCount
>= _retentionRetryThreshold
) {
417 catch (UnauthorizedAccessException
) {
418 // Indicative of ACL issues
434 case LogRetentionOption
.SingleFileBoundedSize
:
435 if (lastPos
>= _maxFileSize
)
439 case LogRetentionOption
.SingleFileUnboundedSize
:
444 // When we enable this class widely, we need to raise an
445 // event when we disable logging due to rention policy or
446 // error such as ACL that is preventing retention
447 [MethodImplAttribute(MethodImplOptions
.Synchronized
)]
448 private void _DisableLogging()
450 // Discard write buffer?
451 _disableLogging
= true;
454 [System
.Security
.SecurityCritical
]
455 private static UnsafeNativeMethods
.SECURITY_ATTRIBUTES
GetSecAttrs(FileShare share
)
457 UnsafeNativeMethods
.SECURITY_ATTRIBUTES secAttrs
= null;
458 if ((share
& FileShare
.Inheritable
) != 0) {
459 secAttrs
= new UnsafeNativeMethods
.SECURITY_ATTRIBUTES();
460 secAttrs
.nLength
= (int)Marshal
.SizeOf(secAttrs
);
462 secAttrs
.bInheritHandle
= 1;