LogStream.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. // ==++==
  2. //
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. //
  5. // ==--==
  6. /*============================================================
  7. **
  8. ** Class: LogStream
  9. **
  10. ===========================================================*/
  11. using System;
  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;
  22. using System.Diagnostics;
  23. using System.Diagnostics.Contracts;
  24. namespace System.IO {
  25. // This stream has very limited support to enable EventSchemaTraceListener
  26. // Eventually we might want to add more functionality and expose this type
  27. internal class LogStream : BufferedStream2
  28. {
  29. internal const long DefaultFileSize = 10*1000*1024;
  30. internal const int DefaultNumberOfFiles = 2;
  31. internal const LogRetentionOption DefaultRetention = LogRetentionOption.SingleFileUnboundedSize;
  32. // Retention policy
  33. private const int _retentionRetryThreshold = 2;
  34. private LogRetentionOption _retention;
  35. private long _maxFileSize = DefaultFileSize;
  36. private int _maxNumberOfFiles = DefaultNumberOfFiles;
  37. private int _currentFileNum = 1;
  38. bool _disableLogging;
  39. int _retentionRetryCount;
  40. private bool _canRead;
  41. private bool _canWrite;
  42. private bool _canSeek;
  43. [SecurityCritical]
  44. private SafeFileHandle _handle;
  45. private String _fileName; // Fully qualified file name.
  46. string _fileNameWithoutExt;
  47. string _fileExt;
  48. // Save input for retention
  49. string _pathSav;
  50. int _fAccessSav;
  51. FileShare _shareSav;
  52. UnsafeNativeMethods.SECURITY_ATTRIBUTES _secAttrsSav;
  53. FileIOPermissionAccess _secAccessSav;
  54. FileMode _modeSav;
  55. int _flagsAndAttributesSav;
  56. bool _seekToEndSav;
  57. private readonly object m_lockObject = new Object();
  58. //Limited to immediate internal need from EventSchemaTraceListener
  59. //Not param validation done!!
  60. [ResourceExposure(ResourceScope.Machine)]
  61. [ResourceConsumption(ResourceScope.Machine)]
  62. [System.Security.SecurityCritical]
  63. internal LogStream(String path, int bufferSize, LogRetentionOption retention, long maxFileSize, int maxNumOfFiles)
  64. {
  65. Debug.Assert(!String.IsNullOrEmpty(path));
  66. // Get absolute path - Security needs this to prevent something
  67. // like trying to create a file in c:\tmp with the name
  68. // "..\WinNT\System32\ntoskrnl.exe". Store it for user convenience.
  69. //String filePath = Path.GetFullPathInternal(path);
  70. String filePath = Path.GetFullPath(path);
  71. _fileName = filePath;
  72. // Prevent access to your disk drives as raw block devices.
  73. if (filePath.StartsWith("\\\\.\\", StringComparison.Ordinal))
  74. throw new NotSupportedException(SR.GetString(SR.NotSupported_IONonFileDevices));
  75. UnsafeNativeMethods.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(FileShare.Read);
  76. // For mitigating local elevation of privilege attack through named pipes
  77. // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
  78. // named pipe server can't impersonate a high privileged client security context
  79. int flagsAndAttributes = (int)FileOptions.None | (UnsafeNativeMethods.SECURITY_SQOS_PRESENT | UnsafeNativeMethods.SECURITY_ANONYMOUS);
  80. // Only write is enabled
  81. //_canRead = false;
  82. //_canSeek = false;
  83. _canWrite = true;
  84. _pathSav = filePath;
  85. _fAccessSav = UnsafeNativeMethods.GENERIC_WRITE;
  86. _shareSav = FileShare.Read;
  87. _secAttrsSav = secAttrs;
  88. _secAccessSav = FileIOPermissionAccess.Write;
  89. _modeSav = (retention != LogRetentionOption.SingleFileUnboundedSize)? FileMode.Create : FileMode.OpenOrCreate;
  90. _flagsAndAttributesSav = flagsAndAttributes;
  91. _seekToEndSav = (retention != LogRetentionOption.SingleFileUnboundedSize)? false : true;
  92. this.bufferSize = bufferSize;
  93. _retention = retention;
  94. _maxFileSize = maxFileSize;
  95. _maxNumberOfFiles = maxNumOfFiles;
  96. _Init(filePath, _fAccessSav, _shareSav, _secAttrsSav, _secAccessSav, _modeSav, _flagsAndAttributesSav, _seekToEndSav);
  97. }
  98. [System.Security.SecurityCritical]
  99. internal void _Init(String path, int fAccess, FileShare share, UnsafeNativeMethods.SECURITY_ATTRIBUTES secAttrs, FileIOPermissionAccess secAccess,
  100. FileMode mode, int flagsAndAttributes, bool seekToEnd)
  101. {
  102. String filePath = Path.GetFullPath(path);
  103. _fileName = filePath;
  104. new FileIOPermission(secAccess, new String[] { filePath }).Demand();
  105. // Don't pop up a dialog for reading from an emtpy floppy drive
  106. int oldMode = UnsafeNativeMethods.SetErrorMode(UnsafeNativeMethods.SEM_FAILCRITICALERRORS);
  107. try {
  108. _handle = UnsafeNativeMethods.SafeCreateFile(filePath, fAccess, share, secAttrs, mode, flagsAndAttributes, UnsafeNativeMethods.NULL);
  109. int errorCode = Marshal.GetLastWin32Error();
  110. if (_handle.IsInvalid) {
  111. // Return a meaningful exception, using the RELATIVE path to
  112. // the file to avoid returning extra information to the caller
  113. // unless they have path discovery permission, in which case
  114. // the full path is fine & useful.
  115. // We need to give an exception, and preferably it would include
  116. // the fully qualified path name. Do security check here. If
  117. // we fail, give back the msgPath, which should not reveal much.
  118. // While this logic is largely duplicated in
  119. // __Error.WinIOError, we need this for
  120. // IsolatedStorageLogFileStream.
  121. bool canGiveFullPath = false;
  122. try {
  123. new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }).Demand();
  124. canGiveFullPath = true;
  125. }
  126. catch(SecurityException) {}
  127. if (canGiveFullPath)
  128. __Error.WinIOError(errorCode, _fileName);
  129. else
  130. __Error.WinIOError(errorCode, Path.GetFileName(_fileName));
  131. }
  132. }
  133. finally {
  134. UnsafeNativeMethods.SetErrorMode(oldMode);
  135. }
  136. Debug.Assert(UnsafeNativeMethods.GetFileType(_handle) == UnsafeNativeMethods.FILE_TYPE_DISK, "did someone accidentally removed the device type check from SafeCreateFile P/Invoke wrapper?");
  137. pos = 0;
  138. // For Append mode...
  139. if (seekToEnd) {
  140. SeekCore(0, SeekOrigin.End);
  141. }
  142. }
  143. public override bool CanRead {
  144. [Pure]
  145. get { return _canRead; }
  146. }
  147. public override bool CanWrite {
  148. [Pure]
  149. get { return _canWrite; }
  150. }
  151. public override bool CanSeek {
  152. [Pure]
  153. get { return _canSeek; }
  154. }
  155. public override long Length {
  156. get {
  157. throw new NotSupportedException();
  158. }
  159. }
  160. public override long Position {
  161. get {
  162. throw new NotSupportedException();
  163. }
  164. set {
  165. throw new NotSupportedException();
  166. }
  167. }
  168. public override void SetLength(long value)
  169. {
  170. throw new NotSupportedException();
  171. }
  172. public override long Seek(long offset, SeekOrigin origin)
  173. {
  174. throw new NotSupportedException();
  175. }
  176. public override int Read(byte[] array, int offset, int count)
  177. {
  178. throw new NotSupportedException();
  179. }
  180. [System.Security.SecurityCritical]
  181. protected override unsafe void WriteCore(byte[] buffer, int offset, int count, bool blockForWrite, out long streamPos) {
  182. Debug.Assert(CanWrite, "CanWrite");
  183. Debug.Assert(buffer != null, "buffer != null");
  184. Debug.Assert(offset >= 0, "offset is negative");
  185. Debug.Assert(count >= 0, "count is negative");
  186. int hr = 0;
  187. int r = WriteFileNative(buffer, offset, count, null, out hr);
  188. if (r == -1) {
  189. // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
  190. if (hr == UnsafeNativeMethods.ERROR_NO_DATA) {
  191. r = 0;
  192. }
  193. else {
  194. // ERROR_INVALID_PARAMETER may be returned for writes
  195. // where the position is too large (ie, writing at Int64.MaxValue
  196. // on Win9x) OR for synchronous writes to a handle opened
  197. // asynchronously.
  198. if (hr == UnsafeNativeMethods.ERROR_INVALID_PARAMETER)
  199. throw new IOException(SR.GetString(SR.IO_FileTooLongOrHandleNotSync));
  200. __Error.WinIOError(hr, String.Empty);
  201. }
  202. }
  203. Debug.Assert(r >= 0, "WriteCore is likely broken.");
  204. // update cached position
  205. streamPos = AddUnderlyingStreamPosition((long)r);
  206. EnforceRetentionPolicy(_handle, streamPos);
  207. streamPos = pos;
  208. return;
  209. }
  210. [System.Security.SecurityCritical]
  211. unsafe private int WriteFileNative(byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) {
  212. if (_handle.IsClosed) __Error.FileNotOpen();
  213. if (_disableLogging) {
  214. hr = 0;
  215. return 0;
  216. }
  217. Debug.Assert(offset >= 0, "offset >= 0");
  218. Debug.Assert(count >= 0, "count >= 0");
  219. Debug.Assert(bytes != null, "bytes != null");
  220. // Don't corrupt memory when multiple threads are erroneously writing
  221. // to this stream simultaneously. (the OS is reading from
  222. // the array we pass to WriteFile, but if we read beyond the end and
  223. // that memory isn't allocated, we could get an AV.)
  224. if (bytes.Length - offset < count)
  225. throw new IndexOutOfRangeException(SR.GetString(SR.IndexOutOfRange_IORaceCondition));
  226. // You can't use the fixed statement on an array of length 0.
  227. if (bytes.Length==0) {
  228. hr = 0;
  229. return 0;
  230. }
  231. int numBytesWritten = 0;
  232. int r = 0;
  233. fixed(byte* p = bytes) {
  234. r = UnsafeNativeMethods.WriteFile(_handle, p + offset, count, out numBytesWritten, overlapped);
  235. }
  236. if (r == 0) {
  237. // We should never silently ---- an error here without some
  238. // extra work. We must make sure that BeginWriteCore won't return an
  239. // IAsyncResult that will cause EndWrite to block, since the OS won't
  240. // call AsyncFSCallback for us.
  241. hr = Marshal.GetLastWin32Error();
  242. // For invalid handles, detect the error and mark our handle
  243. // as closed to give slightly better error messages. Also
  244. // help ensure we avoid handle recycling bugs.
  245. if (hr == UnsafeNativeMethods.ERROR_INVALID_HANDLE)
  246. _handle.SetHandleAsInvalid();
  247. return -1;
  248. }
  249. else
  250. hr = 0;
  251. return numBytesWritten;
  252. }
  253. // This doesn't do argument checking. Necessary for SetLength, which must
  254. // set the file pointer beyond the end of the file. This will update the
  255. // internal position
  256. [System.Security.SecurityCritical]
  257. private long SeekCore(long offset, SeekOrigin origin)
  258. {
  259. Debug.Assert(!_handle.IsClosed, "!_handle.IsClosed");
  260. Debug.Assert(origin>=SeekOrigin.Begin && origin<=SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End");
  261. int hr = 0;
  262. long ret = 0;
  263. ret = UnsafeNativeMethods.SetFilePointer(_handle, offset, origin, out hr);
  264. if (ret == -1) {
  265. // For invalid handles, detect the error and mark our handle
  266. // as closed to give slightly better error messages. Also
  267. // help ensure we avoid handle recycling bugs.
  268. if (hr == UnsafeNativeMethods.ERROR_INVALID_HANDLE)
  269. _handle.SetHandleAsInvalid();
  270. __Error.WinIOError(hr, String.Empty);
  271. }
  272. UnderlyingStreamPosition = ret;
  273. return ret;
  274. }
  275. [System.Security.SecurityCritical]
  276. protected override void Dispose(bool disposing)
  277. {
  278. // Nothing will be done differently based on whether we are
  279. // disposing vs. finalizing. This is taking advantage of the
  280. // weak ordering between normal finalizable objects & critical
  281. // finalizable objects, which I included in the SafeHandle
  282. // design for LogStream, which would often "just work" when
  283. // finalized.
  284. try {
  285. if (_handle == null || _handle.IsClosed) {
  286. // Make sure BufferedStream doesn't try to flush data on a closed handle
  287. DiscardBuffer();
  288. }
  289. }
  290. finally {
  291. try {
  292. // Cleanup base streams
  293. base.Dispose(disposing);
  294. }
  295. finally {
  296. if (_handle != null && !_handle.IsClosed)
  297. _handle.Dispose();
  298. _handle = null;
  299. _canRead = false;
  300. _canWrite = false;
  301. _canSeek = false;
  302. }
  303. }
  304. }
  305. [System.Security.SecurityCritical]
  306. ~LogStream()
  307. {
  308. if (_handle != null) {
  309. Dispose(false);
  310. }
  311. }
  312. [System.Security.SecurityCritical]
  313. private void EnforceRetentionPolicy(SafeFileHandle handle, long lastPos)
  314. {
  315. switch (_retention) {
  316. case LogRetentionOption.LimitedSequentialFiles:
  317. case LogRetentionOption.UnlimitedSequentialFiles:
  318. case LogRetentionOption.LimitedCircularFiles:
  319. if ((lastPos >= _maxFileSize) && (handle == _handle)){
  320. lock (m_lockObject) {
  321. if ((handle != _handle) || (lastPos < _maxFileSize))
  322. return;
  323. _currentFileNum++;
  324. if ((_retention == LogRetentionOption.LimitedCircularFiles) && (_currentFileNum > _maxNumberOfFiles)) {
  325. _currentFileNum = 1;
  326. }
  327. else if ((_retention == LogRetentionOption.LimitedSequentialFiles) && (_currentFileNum > _maxNumberOfFiles)) {
  328. _DisableLogging();
  329. return;
  330. }
  331. if (_fileNameWithoutExt == null) {
  332. _fileNameWithoutExt = Path.Combine(Path.GetDirectoryName(_pathSav), Path.GetFileNameWithoutExtension(_pathSav));
  333. _fileExt = Path.GetExtension(_pathSav);
  334. }
  335. string path = (_currentFileNum == 1)?_pathSav: _fileNameWithoutExt + _currentFileNum.ToString(CultureInfo.InvariantCulture) + _fileExt;
  336. try {
  337. _Init(path, _fAccessSav, _shareSav, _secAttrsSav, _secAccessSav, _modeSav, _flagsAndAttributesSav, _seekToEndSav);
  338. // Dispose the old handle and release the file write lock
  339. // No need to flush the buffer as we just came off a write
  340. if (handle != null && !handle.IsClosed) {
  341. handle.Dispose();
  342. }
  343. }
  344. catch (IOException ) {
  345. // Should we do this only for ERROR_SHARING_VIOLATION?
  346. //if (UnsafeNativeMethods.MakeErrorCodeFromHR(Marshal.GetHRForException(ioexc)) != InternalResources.ERROR_SHARING_VIOLATION) break;
  347. // Possible sharing violation - ----? Let the next iteration try again
  348. // For now revert the handle to the original one
  349. _handle = handle;
  350. _retentionRetryCount++;
  351. if (_retentionRetryCount >= _retentionRetryThreshold) {
  352. _DisableLogging();
  353. }
  354. #if DEBUG
  355. throw;
  356. #endif
  357. }
  358. catch (UnauthorizedAccessException ) {
  359. // Indicative of ACL issues
  360. _DisableLogging();
  361. #if DEBUG
  362. throw;
  363. #endif
  364. }
  365. catch (Exception ) {
  366. _DisableLogging();
  367. #if DEBUG
  368. throw;
  369. #endif
  370. }
  371. }
  372. }
  373. break;
  374. case LogRetentionOption.SingleFileBoundedSize:
  375. if (lastPos >= _maxFileSize)
  376. _DisableLogging();
  377. break;
  378. case LogRetentionOption.SingleFileUnboundedSize:
  379. break;
  380. }
  381. }
  382. // When we enable this class widely, we need to raise an
  383. // event when we disable logging due to rention policy or
  384. // error such as ACL that is preventing retention
  385. [MethodImplAttribute(MethodImplOptions.Synchronized)]
  386. private void _DisableLogging()
  387. {
  388. // Discard write buffer?
  389. _disableLogging = true;
  390. }
  391. [System.Security.SecurityCritical]
  392. private static UnsafeNativeMethods.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
  393. {
  394. UnsafeNativeMethods.SECURITY_ATTRIBUTES secAttrs = null;
  395. if ((share & FileShare.Inheritable) != 0) {
  396. secAttrs = new UnsafeNativeMethods.SECURITY_ATTRIBUTES();
  397. secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);
  398. secAttrs.bInheritHandle = 1;
  399. }
  400. return secAttrs;
  401. }
  402. }
  403. }