FileStream.Windows.cs 77 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Buffers;
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using Microsoft.Win32.SafeHandles;
  10. using System.Runtime.CompilerServices;
  11. /*
  12. * Win32FileStream supports different modes of accessing the disk - async mode
  13. * and sync mode. They are two completely different codepaths in the
  14. * sync & async methods (i.e. Read/Write vs. ReadAsync/WriteAsync). File
  15. * handles in NT can be opened in only sync or overlapped (async) mode,
  16. * and we have to deal with this pain. Stream has implementations of
  17. * the sync methods in terms of the async ones, so we'll
  18. * call through to our base class to get those methods when necessary.
  19. *
  20. * Also buffering is added into Win32FileStream as well. Folded in the
  21. * code from BufferedStream, so all the comments about it being mostly
  22. * aggressive (and the possible perf improvement) apply to Win32FileStream as
  23. * well. Also added some buffering to the async code paths.
  24. *
  25. * Class Invariants:
  26. * The class has one buffer, shared for reading & writing. It can only be
  27. * used for one or the other at any point in time - not both. The following
  28. * should be true:
  29. * 0 <= _readPos <= _readLen < _bufferSize
  30. * 0 <= _writePos < _bufferSize
  31. * _readPos == _readLen && _readPos > 0 implies the read buffer is valid,
  32. * but we're at the end of the buffer.
  33. * _readPos == _readLen == 0 means the read buffer contains garbage.
  34. * Either _writePos can be greater than 0, or _readLen & _readPos can be
  35. * greater than zero, but neither can be greater than zero at the same time.
  36. *
  37. */
  38. namespace System.IO
  39. {
  40. public partial class FileStream : Stream
  41. {
  42. private bool _canSeek;
  43. private bool _isPipe; // Whether to disable async buffering code.
  44. private long _appendStart; // When appending, prevent overwriting file.
  45. private static unsafe IOCompletionCallback s_ioCallback = FileStreamCompletionSource.IOCallback;
  46. private Task _activeBufferOperation = null; // tracks in-progress async ops using the buffer
  47. private PreAllocatedOverlapped _preallocatedOverlapped; // optimization for async ops to avoid per-op allocations
  48. private FileStreamCompletionSource _currentOverlappedOwner; // async op currently using the preallocated overlapped
  49. private void Init(FileMode mode, FileShare share, string originalPath)
  50. {
  51. if (!PathInternal.IsExtended(originalPath))
  52. {
  53. // To help avoid stumbling into opening COM/LPT ports by accident, we will block on non file handles unless
  54. // we were explicitly passed a path that has \\?\. GetFullPath() will turn paths like C:\foo\con.txt into
  55. // \\.\CON, so we'll only allow the \\?\ syntax.
  56. int fileType = Interop.Kernel32.GetFileType(_fileHandle);
  57. if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK)
  58. {
  59. int errorCode = fileType == Interop.Kernel32.FileTypes.FILE_TYPE_UNKNOWN
  60. ? Marshal.GetLastWin32Error()
  61. : Interop.Errors.ERROR_SUCCESS;
  62. _fileHandle.Dispose();
  63. if (errorCode != Interop.Errors.ERROR_SUCCESS)
  64. {
  65. throw Win32Marshal.GetExceptionForWin32Error(errorCode);
  66. }
  67. throw new NotSupportedException(SR.NotSupported_FileStreamOnNonFiles);
  68. }
  69. }
  70. // This is necessary for async IO using IO Completion ports via our
  71. // managed Threadpool API's. This (theoretically) calls the OS's
  72. // BindIoCompletionCallback method, and passes in a stub for the
  73. // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
  74. // struct for this request and gets a delegate to a managed callback
  75. // from there, which it then calls on a threadpool thread. (We allocate
  76. // our native OVERLAPPED structs 2 pointers too large and store EE state
  77. // & GC handles there, one to an IAsyncResult, the other to a delegate.)
  78. if (_useAsyncIO)
  79. {
  80. try
  81. {
  82. _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle);
  83. }
  84. catch (ArgumentException ex)
  85. {
  86. throw new IOException(SR.IO_BindHandleFailed, ex);
  87. }
  88. finally
  89. {
  90. if (_fileHandle.ThreadPoolBinding == null)
  91. {
  92. // We should close the handle so that the handle is not open until SafeFileHandle GC
  93. Debug.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?");
  94. _fileHandle.Dispose();
  95. }
  96. }
  97. }
  98. _canSeek = true;
  99. // For Append mode...
  100. if (mode == FileMode.Append)
  101. {
  102. _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End);
  103. }
  104. else
  105. {
  106. _appendStart = -1;
  107. }
  108. }
  109. private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAsyncIO)
  110. {
  111. #if DEBUG
  112. bool hadBinding = handle.ThreadPoolBinding != null;
  113. try
  114. {
  115. #endif
  116. InitFromHandleImpl(handle, access, useAsyncIO);
  117. #if DEBUG
  118. }
  119. catch
  120. {
  121. Debug.Assert(hadBinding || handle.ThreadPoolBinding == null, "We should never error out with a ThreadPoolBinding we've added");
  122. throw;
  123. }
  124. #endif
  125. }
  126. private void InitFromHandleImpl(SafeFileHandle handle, FileAccess access, bool useAsyncIO)
  127. {
  128. int handleType = Interop.Kernel32.GetFileType(handle);
  129. Debug.Assert(handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!");
  130. _canSeek = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
  131. _isPipe = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE;
  132. // This is necessary for async IO using IO Completion ports via our
  133. // managed Threadpool API's. This calls the OS's
  134. // BindIoCompletionCallback method, and passes in a stub for the
  135. // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
  136. // struct for this request and gets a delegate to a managed callback
  137. // from there, which it then calls on a threadpool thread. (We allocate
  138. // our native OVERLAPPED structs 2 pointers too large and store EE
  139. // state & a handle to a delegate there.)
  140. //
  141. // If, however, we've already bound this file handle to our completion port,
  142. // don't try to bind it again because it will fail. A handle can only be
  143. // bound to a single completion port at a time.
  144. if (useAsyncIO && !(handle.IsAsync ?? false))
  145. {
  146. try
  147. {
  148. handle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(handle);
  149. }
  150. catch (Exception ex)
  151. {
  152. // If you passed in a synchronous handle and told us to use
  153. // it asynchronously, throw here.
  154. throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle), ex);
  155. }
  156. }
  157. else if (!useAsyncIO)
  158. {
  159. VerifyHandleIsSync(handle, handleType, access);
  160. }
  161. if (_canSeek)
  162. SeekCore(handle, 0, SeekOrigin.Current);
  163. else
  164. _filePosition = 0;
  165. }
  166. private static unsafe Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
  167. {
  168. Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default;
  169. if ((share & FileShare.Inheritable) != 0)
  170. {
  171. secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
  172. {
  173. nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
  174. bInheritHandle = Interop.BOOL.TRUE
  175. };
  176. }
  177. return secAttrs;
  178. }
  179. private bool HasActiveBufferOperation
  180. => _activeBufferOperation != null && !_activeBufferOperation.IsCompleted;
  181. public override bool CanSeek => _canSeek;
  182. private unsafe long GetLengthInternal()
  183. {
  184. Interop.Kernel32.FILE_STANDARD_INFO info = new Interop.Kernel32.FILE_STANDARD_INFO();
  185. if (!Interop.Kernel32.GetFileInformationByHandleEx(_fileHandle, Interop.Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, out info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO)))
  186. throw Win32Marshal.GetExceptionForLastWin32Error(_path);
  187. long len = info.EndOfFile;
  188. // If we're writing near the end of the file, we must include our
  189. // internal buffer in our Length calculation. Don't flush because
  190. // we use the length of the file in our async write method.
  191. if (_writePos > 0 && _filePosition + _writePos > len)
  192. len = _writePos + _filePosition;
  193. return len;
  194. }
  195. protected override void Dispose(bool disposing)
  196. {
  197. // Nothing will be done differently based on whether we are
  198. // disposing vs. finalizing. This is taking advantage of the
  199. // weak ordering between normal finalizable objects & critical
  200. // finalizable objects, which I included in the SafeHandle
  201. // design for Win32FileStream, which would often "just work" when
  202. // finalized.
  203. try
  204. {
  205. if (_fileHandle != null && !_fileHandle.IsClosed && _writePos > 0)
  206. {
  207. // Flush data to disk iff we were writing. After
  208. // thinking about this, we also don't need to flush
  209. // our read position, regardless of whether the handle
  210. // was exposed to the user. They probably would NOT
  211. // want us to do this.
  212. try
  213. {
  214. FlushWriteBuffer(!disposing);
  215. }
  216. catch (Exception e) when (IsIoRelatedException(e) && !disposing)
  217. {
  218. // On finalization, ignore failures from trying to flush the write buffer,
  219. // e.g. if this stream is wrapping a pipe and the pipe is now broken.
  220. }
  221. }
  222. }
  223. finally
  224. {
  225. if (_fileHandle != null && !_fileHandle.IsClosed)
  226. {
  227. _fileHandle.ThreadPoolBinding?.Dispose();
  228. _fileHandle.Dispose();
  229. }
  230. _preallocatedOverlapped?.Dispose();
  231. _canSeek = false;
  232. // Don't set the buffer to null, to avoid a NullReferenceException
  233. // when users have a race condition in their code (i.e. they call
  234. // Close when calling another method on Stream like Read).
  235. }
  236. }
  237. public override ValueTask DisposeAsync() =>
  238. GetType() == typeof(FileStream) ?
  239. DisposeAsyncCore() :
  240. base.DisposeAsync();
  241. private async ValueTask DisposeAsyncCore()
  242. {
  243. // Same logic as in Dispose(), except with async counterparts.
  244. // TODO: https://github.com/dotnet/corefx/issues/32837: FlushAsync does synchronous work.
  245. try
  246. {
  247. if (_fileHandle != null && !_fileHandle.IsClosed && _writePos > 0)
  248. {
  249. await FlushAsyncInternal(default).ConfigureAwait(false);
  250. }
  251. }
  252. finally
  253. {
  254. if (_fileHandle != null && !_fileHandle.IsClosed)
  255. {
  256. _fileHandle.ThreadPoolBinding?.Dispose();
  257. _fileHandle.Dispose();
  258. }
  259. _preallocatedOverlapped?.Dispose();
  260. _canSeek = false;
  261. GC.SuppressFinalize(this); // the handle is closed; nothing further for the finalizer to do
  262. }
  263. }
  264. private void FlushOSBuffer()
  265. {
  266. if (!Interop.Kernel32.FlushFileBuffers(_fileHandle))
  267. {
  268. throw Win32Marshal.GetExceptionForLastWin32Error(_path);
  269. }
  270. }
  271. // Returns a task that flushes the internal write buffer
  272. private Task FlushWriteAsync(CancellationToken cancellationToken)
  273. {
  274. Debug.Assert(_useAsyncIO);
  275. Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!");
  276. // If the buffer is already flushed, don't spin up the OS write
  277. if (_writePos == 0) return Task.CompletedTask;
  278. Task flushTask = WriteAsyncInternalCore(new ReadOnlyMemory<byte>(GetBuffer(), 0, _writePos), cancellationToken);
  279. _writePos = 0;
  280. // Update the active buffer operation
  281. _activeBufferOperation = HasActiveBufferOperation ?
  282. Task.WhenAll(_activeBufferOperation, flushTask) :
  283. flushTask;
  284. return flushTask;
  285. }
  286. private void FlushWriteBufferForWriteByte() => FlushWriteBuffer();
  287. // Writes are buffered. Anytime the buffer fills up
  288. // (_writePos + delta > _bufferSize) or the buffer switches to reading
  289. // and there is left over data (_writePos > 0), this function must be called.
  290. private void FlushWriteBuffer(bool calledFromFinalizer = false)
  291. {
  292. if (_writePos == 0) return;
  293. Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWrite!");
  294. if (_useAsyncIO)
  295. {
  296. Task writeTask = FlushWriteAsync(CancellationToken.None);
  297. // With our Whidbey async IO & overlapped support for AD unloads,
  298. // we don't strictly need to block here to release resources
  299. // since that support takes care of the pinning & freeing the
  300. // overlapped struct. We need to do this when called from
  301. // Close so that the handle is closed when Close returns, but
  302. // we don't need to call EndWrite from the finalizer.
  303. // Additionally, if we do call EndWrite, we block forever
  304. // because AD unloads prevent us from running the managed
  305. // callback from the IO completion port. Blocking here when
  306. // called from the finalizer during AD unload is clearly wrong,
  307. // but we can't use any sort of test for whether the AD is
  308. // unloading because if we weren't unloading, an AD unload
  309. // could happen on a separate thread before we call EndWrite.
  310. if (!calledFromFinalizer)
  311. {
  312. writeTask.GetAwaiter().GetResult();
  313. }
  314. }
  315. else
  316. {
  317. WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
  318. }
  319. _writePos = 0;
  320. }
  321. private void SetLengthInternal(long value)
  322. {
  323. // Handle buffering updates.
  324. if (_writePos > 0)
  325. {
  326. FlushWriteBuffer();
  327. }
  328. else if (_readPos < _readLength)
  329. {
  330. FlushReadBuffer();
  331. }
  332. _readPos = 0;
  333. _readLength = 0;
  334. if (_appendStart != -1 && value < _appendStart)
  335. throw new IOException(SR.IO_SetLengthAppendTruncate);
  336. SetLengthCore(value);
  337. }
  338. // We absolutely need this method broken out so that WriteInternalCoreAsync can call
  339. // a method without having to go through buffering code that might call FlushWrite.
  340. private void SetLengthCore(long value)
  341. {
  342. Debug.Assert(value >= 0, "value >= 0");
  343. long origPos = _filePosition;
  344. VerifyOSHandlePosition();
  345. if (_filePosition != value)
  346. SeekCore(_fileHandle, value, SeekOrigin.Begin);
  347. if (!Interop.Kernel32.SetEndOfFile(_fileHandle))
  348. {
  349. int errorCode = Marshal.GetLastWin32Error();
  350. if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER)
  351. throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_FileLengthTooBig);
  352. throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
  353. }
  354. // Return file pointer to where it was before setting length
  355. if (origPos != value)
  356. {
  357. if (origPos < value)
  358. SeekCore(_fileHandle, origPos, SeekOrigin.Begin);
  359. else
  360. SeekCore(_fileHandle, 0, SeekOrigin.End);
  361. }
  362. }
  363. // Instance method to help code external to this MarshalByRefObject avoid
  364. // accessing its fields by ref. This avoids a compiler warning.
  365. private FileStreamCompletionSource CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource newSource, FileStreamCompletionSource existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource);
  366. private int ReadSpan(Span<byte> destination)
  367. {
  368. Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode");
  369. Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength),
  370. "We're either reading or writing, but not both.");
  371. bool isBlocked = false;
  372. int n = _readLength - _readPos;
  373. // if the read buffer is empty, read into either user's array or our
  374. // buffer, depending on number of bytes user asked for and buffer size.
  375. if (n == 0)
  376. {
  377. if (!CanRead) throw Error.GetReadNotSupported();
  378. if (_writePos > 0) FlushWriteBuffer();
  379. if (!CanSeek || (destination.Length >= _bufferLength))
  380. {
  381. n = ReadNative(destination);
  382. // Throw away read buffer.
  383. _readPos = 0;
  384. _readLength = 0;
  385. return n;
  386. }
  387. n = ReadNative(GetBuffer());
  388. if (n == 0) return 0;
  389. isBlocked = n < _bufferLength;
  390. _readPos = 0;
  391. _readLength = n;
  392. }
  393. // Now copy min of count or numBytesAvailable (i.e. near EOF) to array.
  394. if (n > destination.Length) n = destination.Length;
  395. new ReadOnlySpan<byte>(GetBuffer(), _readPos, n).CopyTo(destination);
  396. _readPos += n;
  397. // We may have read less than the number of bytes the user asked
  398. // for, but that is part of the Stream contract. Reading again for
  399. // more data may cause us to block if we're using a device with
  400. // no clear end of file, such as a serial port or pipe. If we
  401. // blocked here & this code was used with redirected pipes for a
  402. // process's standard output, this can lead to deadlocks involving
  403. // two processes. But leave this here for files to avoid what would
  404. // probably be a breaking change. --
  405. // If we are reading from a device with no clear EOF like a
  406. // serial port or a pipe, this will cause us to block incorrectly.
  407. if (!_isPipe)
  408. {
  409. // If we hit the end of the buffer and didn't have enough bytes, we must
  410. // read some more from the underlying stream. However, if we got
  411. // fewer bytes from the underlying stream than we asked for (i.e. we're
  412. // probably blocked), don't ask for more bytes.
  413. if (n < destination.Length && !isBlocked)
  414. {
  415. Debug.Assert(_readPos == _readLength, "Read buffer should be empty!");
  416. int moreBytesRead = ReadNative(destination.Slice(n));
  417. n += moreBytesRead;
  418. // We've just made our buffer inconsistent with our position
  419. // pointer. We must throw away the read buffer.
  420. _readPos = 0;
  421. _readLength = 0;
  422. }
  423. }
  424. return n;
  425. }
  426. [Conditional("DEBUG")]
  427. private void AssertCanRead()
  428. {
  429. Debug.Assert(!_fileHandle.IsClosed, "!_fileHandle.IsClosed");
  430. Debug.Assert(CanRead, "CanRead");
  431. }
  432. /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary>
  433. private int FillReadBufferForReadByte() =>
  434. _useAsyncIO ?
  435. ReadNativeAsync(new Memory<byte>(_buffer), 0, CancellationToken.None).GetAwaiter().GetResult() :
  436. ReadNative(_buffer);
  437. private unsafe int ReadNative(Span<byte> buffer)
  438. {
  439. Debug.Assert(!_useAsyncIO, $"{nameof(ReadNative)} doesn't work on asynchronous file streams.");
  440. AssertCanRead();
  441. // Make sure we are reading from the right spot
  442. VerifyOSHandlePosition();
  443. int r = ReadFileNative(_fileHandle, buffer, null, out int errorCode);
  444. if (r == -1)
  445. {
  446. // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe.
  447. if (errorCode == ERROR_BROKEN_PIPE)
  448. {
  449. r = 0;
  450. }
  451. else
  452. {
  453. if (errorCode == ERROR_INVALID_PARAMETER)
  454. throw new ArgumentException(SR.Arg_HandleNotSync, "_fileHandle");
  455. throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
  456. }
  457. }
  458. Debug.Assert(r >= 0, "FileStream's ReadNative is likely broken.");
  459. _filePosition += r;
  460. return r;
  461. }
  462. public override long Seek(long offset, SeekOrigin origin)
  463. {
  464. if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
  465. throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
  466. if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
  467. if (!CanSeek) throw Error.GetSeekNotSupported();
  468. Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
  469. // If we've got bytes in our buffer to write, write them out.
  470. // If we've read in and consumed some bytes, we'll have to adjust
  471. // our seek positions ONLY IF we're seeking relative to the current
  472. // position in the stream. This simulates doing a seek to the new
  473. // position, then a read for the number of bytes we have in our buffer.
  474. if (_writePos > 0)
  475. {
  476. FlushWriteBuffer();
  477. }
  478. else if (origin == SeekOrigin.Current)
  479. {
  480. // Don't call FlushRead here, which would have caused an infinite
  481. // loop. Simply adjust the seek origin. This isn't necessary
  482. // if we're seeking relative to the beginning or end of the stream.
  483. offset -= (_readLength - _readPos);
  484. }
  485. _readPos = _readLength = 0;
  486. // Verify that internal position is in sync with the handle
  487. VerifyOSHandlePosition();
  488. long oldPos = _filePosition + (_readPos - _readLength);
  489. long pos = SeekCore(_fileHandle, offset, origin);
  490. // Prevent users from overwriting data in a file that was opened in
  491. // append mode.
  492. if (_appendStart != -1 && pos < _appendStart)
  493. {
  494. SeekCore(_fileHandle, oldPos, SeekOrigin.Begin);
  495. throw new IOException(SR.IO_SeekAppendOverwrite);
  496. }
  497. // We now must update the read buffer. We can in some cases simply
  498. // update _readPos within the buffer, copy around the buffer so our
  499. // Position property is still correct, and avoid having to do more
  500. // reads from the disk. Otherwise, discard the buffer's contents.
  501. if (_readLength > 0)
  502. {
  503. // We can optimize the following condition:
  504. // oldPos - _readPos <= pos < oldPos + _readLen - _readPos
  505. if (oldPos == pos)
  506. {
  507. if (_readPos > 0)
  508. {
  509. Buffer.BlockCopy(GetBuffer(), _readPos, GetBuffer(), 0, _readLength - _readPos);
  510. _readLength -= _readPos;
  511. _readPos = 0;
  512. }
  513. // If we still have buffered data, we must update the stream's
  514. // position so our Position property is correct.
  515. if (_readLength > 0)
  516. SeekCore(_fileHandle, _readLength, SeekOrigin.Current);
  517. }
  518. else if (oldPos - _readPos < pos && pos < oldPos + _readLength - _readPos)
  519. {
  520. int diff = (int)(pos - oldPos);
  521. Buffer.BlockCopy(GetBuffer(), _readPos + diff, GetBuffer(), 0, _readLength - (_readPos + diff));
  522. _readLength -= (_readPos + diff);
  523. _readPos = 0;
  524. if (_readLength > 0)
  525. SeekCore(_fileHandle, _readLength, SeekOrigin.Current);
  526. }
  527. else
  528. {
  529. // Lose the read buffer.
  530. _readPos = 0;
  531. _readLength = 0;
  532. }
  533. Debug.Assert(_readLength >= 0 && _readPos <= _readLength, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen");
  534. Debug.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled.");
  535. }
  536. return pos;
  537. }
  538. // This doesn't do argument checking. Necessary for SetLength, which must
  539. // set the file pointer beyond the end of the file. This will update the
  540. // internal position
  541. private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, bool closeInvalidHandle = false)
  542. {
  543. Debug.Assert(!fileHandle.IsClosed && _canSeek, "!fileHandle.IsClosed && _canSeek");
  544. Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End, "origin >= SeekOrigin.Begin && origin <= SeekOrigin.End");
  545. if (!Interop.Kernel32.SetFilePointerEx(fileHandle, offset, out long ret, (uint)origin))
  546. {
  547. if (closeInvalidHandle)
  548. {
  549. throw Win32Marshal.GetExceptionForWin32Error(GetLastWin32ErrorAndDisposeHandleIfInvalid(), _path);
  550. }
  551. else
  552. {
  553. throw Win32Marshal.GetExceptionForLastWin32Error(_path);
  554. }
  555. }
  556. _filePosition = ret;
  557. return ret;
  558. }
  559. partial void OnBufferAllocated()
  560. {
  561. Debug.Assert(_buffer != null);
  562. Debug.Assert(_preallocatedOverlapped == null);
  563. if (_useAsyncIO)
  564. _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer);
  565. }
  566. private void WriteSpan(ReadOnlySpan<byte> source)
  567. {
  568. Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode");
  569. if (_writePos == 0)
  570. {
  571. // Ensure we can write to the stream, and ready buffer for writing.
  572. if (!CanWrite) throw Error.GetWriteNotSupported();
  573. if (_readPos < _readLength) FlushReadBuffer();
  574. _readPos = 0;
  575. _readLength = 0;
  576. }
  577. // If our buffer has data in it, copy data from the user's array into
  578. // the buffer, and if we can fit it all there, return. Otherwise, write
  579. // the buffer to disk and copy any remaining data into our buffer.
  580. // The assumption here is memcpy is cheaper than disk (or net) IO.
  581. // (10 milliseconds to disk vs. ~20-30 microseconds for a 4K memcpy)
  582. // So the extra copying will reduce the total number of writes, in
  583. // non-pathological cases (i.e. write 1 byte, then write for the buffer
  584. // size repeatedly)
  585. if (_writePos > 0)
  586. {
  587. int numBytes = _bufferLength - _writePos; // space left in buffer
  588. if (numBytes > 0)
  589. {
  590. if (numBytes >= source.Length)
  591. {
  592. source.CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
  593. _writePos += source.Length;
  594. return;
  595. }
  596. else
  597. {
  598. source.Slice(0, numBytes).CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
  599. _writePos += numBytes;
  600. source = source.Slice(numBytes);
  601. }
  602. }
  603. // Reset our buffer. We essentially want to call FlushWrite
  604. // without calling Flush on the underlying Stream.
  605. WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
  606. _writePos = 0;
  607. }
  608. // If the buffer would slow writes down, avoid buffer completely.
  609. if (source.Length >= _bufferLength)
  610. {
  611. Debug.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted.");
  612. WriteCore(source);
  613. return;
  614. }
  615. else if (source.Length == 0)
  616. {
  617. return; // Don't allocate a buffer then call memcpy for 0 bytes.
  618. }
  619. // Copy remaining bytes into buffer, to write at a later date.
  620. source.CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
  621. _writePos = source.Length;
  622. return;
  623. }
  624. private unsafe void WriteCore(ReadOnlySpan<byte> source)
  625. {
  626. Debug.Assert(!_useAsyncIO);
  627. Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
  628. Debug.Assert(CanWrite, "_parent.CanWrite");
  629. Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
  630. // Make sure we are writing to the position that we think we are
  631. VerifyOSHandlePosition();
  632. int errorCode = 0;
  633. int r = WriteFileNative(_fileHandle, source, null, out errorCode);
  634. if (r == -1)
  635. {
  636. // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
  637. if (errorCode == ERROR_NO_DATA)
  638. {
  639. r = 0;
  640. }
  641. else
  642. {
  643. // ERROR_INVALID_PARAMETER may be returned for writes
  644. // where the position is too large or for synchronous writes
  645. // to a handle opened asynchronously.
  646. if (errorCode == ERROR_INVALID_PARAMETER)
  647. throw new IOException(SR.IO_FileTooLongOrHandleNotSync);
  648. throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
  649. }
  650. }
  651. Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken.");
  652. _filePosition += r;
  653. return;
  654. }
  655. private Task<int> ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken, out int synchronousResult)
  656. {
  657. Debug.Assert(_useAsyncIO);
  658. if (!CanRead) throw Error.GetReadNotSupported();
  659. Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
  660. if (_isPipe)
  661. {
  662. // Pipes are tricky, at least when you have 2 different pipes
  663. // that you want to use simultaneously. When redirecting stdout
  664. // & stderr with the Process class, it's easy to deadlock your
  665. // parent & child processes when doing writes 4K at a time. The
  666. // OS appears to use a 4K buffer internally. If you write to a
  667. // pipe that is full, you will block until someone read from
  668. // that pipe. If you try reading from an empty pipe and
  669. // Win32FileStream's ReadAsync blocks waiting for data to fill it's
  670. // internal buffer, you will be blocked. In a case where a child
  671. // process writes to stdout & stderr while a parent process tries
  672. // reading from both, you can easily get into a deadlock here.
  673. // To avoid this deadlock, don't buffer when doing async IO on
  674. // pipes. But don't completely ignore buffered data either.
  675. if (_readPos < _readLength)
  676. {
  677. int n = Math.Min(_readLength - _readPos, destination.Length);
  678. new Span<byte>(GetBuffer(), _readPos, n).CopyTo(destination.Span);
  679. _readPos += n;
  680. synchronousResult = n;
  681. return null;
  682. }
  683. else
  684. {
  685. Debug.Assert(_writePos == 0, "Win32FileStream must not have buffered write data here! Pipes should be unidirectional.");
  686. synchronousResult = 0;
  687. return ReadNativeAsync(destination, 0, cancellationToken);
  688. }
  689. }
  690. Debug.Assert(!_isPipe, "Should not be a pipe.");
  691. // Handle buffering.
  692. if (_writePos > 0) FlushWriteBuffer();
  693. if (_readPos == _readLength)
  694. {
  695. // I can't see how to handle buffering of async requests when
  696. // filling the buffer asynchronously, without a lot of complexity.
  697. // The problems I see are issuing an async read, we do an async
  698. // read to fill the buffer, then someone issues another read
  699. // (either synchronously or asynchronously) before the first one
  700. // returns. This would involve some sort of complex buffer locking
  701. // that we probably don't want to get into, at least not in V1.
  702. // If we did a sync read to fill the buffer, we could avoid the
  703. // problem, and any async read less than 64K gets turned into a
  704. // synchronous read by NT anyways... --
  705. if (destination.Length < _bufferLength)
  706. {
  707. Task<int> readTask = ReadNativeAsync(new Memory<byte>(GetBuffer()), 0, cancellationToken);
  708. _readLength = readTask.GetAwaiter().GetResult();
  709. int n = Math.Min(_readLength, destination.Length);
  710. new Span<byte>(GetBuffer(), 0, n).CopyTo(destination.Span);
  711. _readPos = n;
  712. synchronousResult = n;
  713. return null;
  714. }
  715. else
  716. {
  717. // Here we're making our position pointer inconsistent
  718. // with our read buffer. Throw away the read buffer's contents.
  719. _readPos = 0;
  720. _readLength = 0;
  721. synchronousResult = 0;
  722. return ReadNativeAsync(destination, 0, cancellationToken);
  723. }
  724. }
  725. else
  726. {
  727. int n = Math.Min(_readLength - _readPos, destination.Length);
  728. new Span<byte>(GetBuffer(), _readPos, n).CopyTo(destination.Span);
  729. _readPos += n;
  730. if (n == destination.Length)
  731. {
  732. // Return a completed task
  733. synchronousResult = n;
  734. return null;
  735. }
  736. else
  737. {
  738. // For streams with no clear EOF like serial ports or pipes
  739. // we cannot read more data without causing an app to block
  740. // incorrectly. Pipes don't go down this path
  741. // though. This code needs to be fixed.
  742. // Throw away read buffer.
  743. _readPos = 0;
  744. _readLength = 0;
  745. synchronousResult = 0;
  746. return ReadNativeAsync(destination.Slice(n), n, cancellationToken);
  747. }
  748. }
  749. }
  750. private unsafe Task<int> ReadNativeAsync(Memory<byte> destination, int numBufferedBytesRead, CancellationToken cancellationToken)
  751. {
  752. AssertCanRead();
  753. Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!");
  754. // Create and store async stream class library specific data in the async result
  755. FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, numBufferedBytesRead, destination);
  756. NativeOverlapped* intOverlapped = completionSource.Overlapped;
  757. // Calculate position in the file we should be at after the read is done
  758. if (CanSeek)
  759. {
  760. long len = Length;
  761. // Make sure we are reading from the position that we think we are
  762. VerifyOSHandlePosition();
  763. if (_filePosition + destination.Length > len)
  764. {
  765. if (_filePosition <= len)
  766. {
  767. destination = destination.Slice(0, (int)(len - _filePosition));
  768. }
  769. else
  770. {
  771. destination = default;
  772. }
  773. }
  774. // Now set the position to read from in the NativeOverlapped struct
  775. // For pipes, we should leave the offset fields set to 0.
  776. intOverlapped->OffsetLow = unchecked((int)_filePosition);
  777. intOverlapped->OffsetHigh = (int)(_filePosition >> 32);
  778. // When using overlapped IO, the OS is not supposed to
  779. // touch the file pointer location at all. We will adjust it
  780. // ourselves. This isn't threadsafe.
  781. // WriteFile should not update the file pointer when writing
  782. // in overlapped mode, according to MSDN. But it does update
  783. // the file pointer when writing to a UNC path!
  784. // So changed the code below to seek to an absolute
  785. // location, not a relative one. ReadFile seems consistent though.
  786. SeekCore(_fileHandle, destination.Length, SeekOrigin.Current);
  787. }
  788. // queue an async ReadFile operation and pass in a packed overlapped
  789. int errorCode = 0;
  790. int r = ReadFileNative(_fileHandle, destination.Span, intOverlapped, out errorCode);
  791. // ReadFile, the OS version, will return 0 on failure. But
  792. // my ReadFileNative wrapper returns -1. My wrapper will return
  793. // the following:
  794. // On error, r==-1.
  795. // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING
  796. // on async requests that completed sequentially, r==0
  797. // You will NEVER RELIABLY be able to get the number of bytes
  798. // read back from this call when using overlapped structures! You must
  799. // not pass in a non-null lpNumBytesRead to ReadFile when using
  800. // overlapped structures! This is by design NT behavior.
  801. if (r == -1)
  802. {
  803. // For pipes, when they hit EOF, they will come here.
  804. if (errorCode == ERROR_BROKEN_PIPE)
  805. {
  806. // Not an error, but EOF. AsyncFSCallback will NOT be
  807. // called. Call the user callback here.
  808. // We clear the overlapped status bit for this special case.
  809. // Failure to do so looks like we are freeing a pending overlapped later.
  810. intOverlapped->InternalLow = IntPtr.Zero;
  811. completionSource.SetCompletedSynchronously(0);
  812. }
  813. else if (errorCode != ERROR_IO_PENDING)
  814. {
  815. if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere.
  816. {
  817. SeekCore(_fileHandle, 0, SeekOrigin.Current);
  818. }
  819. completionSource.ReleaseNativeResource();
  820. if (errorCode == ERROR_HANDLE_EOF)
  821. {
  822. throw Error.GetEndOfFile();
  823. }
  824. else
  825. {
  826. throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
  827. }
  828. }
  829. else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING
  830. {
  831. // Only once the IO is pending do we register for cancellation
  832. completionSource.RegisterForCancellation(cancellationToken);
  833. }
  834. }
  835. else
  836. {
  837. // Due to a workaround for a race condition in NT's ReadFile &
  838. // WriteFile routines, we will always be returning 0 from ReadFileNative
  839. // when we do async IO instead of the number of bytes read,
  840. // irregardless of whether the operation completed
  841. // synchronously or asynchronously. We absolutely must not
  842. // set asyncResult._numBytes here, since will never have correct
  843. // results.
  844. }
  845. return completionSource.Task;
  846. }
  847. private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
  848. {
  849. Debug.Assert(_useAsyncIO);
  850. Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
  851. Debug.Assert(!_isPipe || (_readPos == 0 && _readLength == 0), "Win32FileStream must not have buffered data here! Pipes should be unidirectional.");
  852. if (!CanWrite) throw Error.GetWriteNotSupported();
  853. bool writeDataStoredInBuffer = false;
  854. if (!_isPipe) // avoid async buffering with pipes, as doing so can lead to deadlocks (see comments in ReadInternalAsyncCore)
  855. {
  856. // Ensure the buffer is clear for writing
  857. if (_writePos == 0)
  858. {
  859. if (_readPos < _readLength)
  860. {
  861. FlushReadBuffer();
  862. }
  863. _readPos = 0;
  864. _readLength = 0;
  865. }
  866. // Determine how much space remains in the buffer
  867. int remainingBuffer = _bufferLength - _writePos;
  868. Debug.Assert(remainingBuffer >= 0);
  869. // Simple/common case:
  870. // - The write is smaller than our buffer, such that it's worth considering buffering it.
  871. // - There's no active flush operation, such that we don't have to worry about the existing buffer being in use.
  872. // - And the data we're trying to write fits in the buffer, meaning it wasn't already filled by previous writes.
  873. // In that case, just store it in the buffer.
  874. if (source.Length < _bufferLength && !HasActiveBufferOperation && source.Length <= remainingBuffer)
  875. {
  876. source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length));
  877. _writePos += source.Length;
  878. writeDataStoredInBuffer = true;
  879. // There is one special-but-common case, common because devs often use
  880. // byte[] sizes that are powers of 2 and thus fit nicely into our buffer, which is
  881. // also a power of 2. If after our write the buffer still has remaining space,
  882. // then we're done and can return a completed task now. But if we filled the buffer
  883. // completely, we want to do the asynchronous flush/write as part of this operation
  884. // rather than waiting until the next write that fills the buffer.
  885. if (source.Length != remainingBuffer)
  886. return default;
  887. Debug.Assert(_writePos == _bufferLength);
  888. }
  889. }
  890. // At this point, at least one of the following is true:
  891. // 1. There was an active flush operation (it could have completed by now, though).
  892. // 2. The data doesn't fit in the remaining buffer (or it's a pipe and we chose not to try).
  893. // 3. We wrote all of the data to the buffer, filling it.
  894. //
  895. // If there's an active operation, we can't touch the current buffer because it's in use.
  896. // That gives us a choice: we can either allocate a new buffer, or we can skip the buffer
  897. // entirely (even if the data would otherwise fit in it). For now, for simplicity, we do
  898. // the latter; it could also have performance wins due to OS-level optimizations, and we could
  899. // potentially add support for PreAllocatedOverlapped due to having a single buffer. (We can
  900. // switch to allocating a new buffer, potentially experimenting with buffer pooling, should
  901. // performance data suggest it's appropriate.)
  902. //
  903. // If the data doesn't fit in the remaining buffer, it could be because it's so large
  904. // it's greater than the entire buffer size, in which case we'd always skip the buffer,
  905. // or it could be because there's more data than just the space remaining. For the latter
  906. // case, we need to issue an asynchronous write to flush that data, which then turns this into
  907. // the first case above with an active operation.
  908. //
  909. // If we already stored the data, then we have nothing additional to write beyond what
  910. // we need to flush.
  911. //
  912. // In any of these cases, we have the same outcome:
  913. // - If there's data in the buffer, flush it by writing it out asynchronously.
  914. // - Then, if there's any data to be written, issue a write for it concurrently.
  915. // We return a Task that represents one or both.
  916. // Flush the buffer asynchronously if there's anything to flush
  917. Task flushTask = null;
  918. if (_writePos > 0)
  919. {
  920. flushTask = FlushWriteAsync(cancellationToken);
  921. // If we already copied all of the data into the buffer,
  922. // simply return the flush task here. Same goes for if the task has
  923. // already completed and was unsuccessful.
  924. if (writeDataStoredInBuffer ||
  925. flushTask.IsFaulted ||
  926. flushTask.IsCanceled)
  927. {
  928. return new ValueTask(flushTask);
  929. }
  930. }
  931. Debug.Assert(!writeDataStoredInBuffer);
  932. Debug.Assert(_writePos == 0);
  933. // Finally, issue the write asynchronously, and return a Task that logically
  934. // represents the write operation, including any flushing done.
  935. Task writeTask = WriteAsyncInternalCore(source, cancellationToken);
  936. return new ValueTask(
  937. (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask :
  938. (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask :
  939. Task.WhenAll(flushTask, writeTask));
  940. }
  941. private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
  942. {
  943. Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
  944. Debug.Assert(CanWrite, "_parent.CanWrite");
  945. Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
  946. Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!");
  947. // Create and store async stream class library specific data in the async result
  948. FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, 0, source);
  949. NativeOverlapped* intOverlapped = completionSource.Overlapped;
  950. if (CanSeek)
  951. {
  952. // Make sure we set the length of the file appropriately.
  953. long len = Length;
  954. // Make sure we are writing to the position that we think we are
  955. VerifyOSHandlePosition();
  956. if (_filePosition + source.Length > len)
  957. {
  958. SetLengthCore(_filePosition + source.Length);
  959. }
  960. // Now set the position to read from in the NativeOverlapped struct
  961. // For pipes, we should leave the offset fields set to 0.
  962. intOverlapped->OffsetLow = (int)_filePosition;
  963. intOverlapped->OffsetHigh = (int)(_filePosition >> 32);
  964. // When using overlapped IO, the OS is not supposed to
  965. // touch the file pointer location at all. We will adjust it
  966. // ourselves. This isn't threadsafe.
  967. SeekCore(_fileHandle, source.Length, SeekOrigin.Current);
  968. }
  969. int errorCode = 0;
  970. // queue an async WriteFile operation and pass in a packed overlapped
  971. int r = WriteFileNative(_fileHandle, source.Span, intOverlapped, out errorCode);
  972. // WriteFile, the OS version, will return 0 on failure. But
  973. // my WriteFileNative wrapper returns -1. My wrapper will return
  974. // the following:
  975. // On error, r==-1.
  976. // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING
  977. // On async requests that completed sequentially, r==0
  978. // You will NEVER RELIABLY be able to get the number of bytes
  979. // written back from this call when using overlapped IO! You must
  980. // not pass in a non-null lpNumBytesWritten to WriteFile when using
  981. // overlapped structures! This is ByDesign NT behavior.
  982. if (r == -1)
  983. {
  984. // For pipes, when they are closed on the other side, they will come here.
  985. if (errorCode == ERROR_NO_DATA)
  986. {
  987. // Not an error, but EOF. AsyncFSCallback will NOT be called.
  988. // Completing TCS and return cached task allowing the GC to collect TCS.
  989. completionSource.SetCompletedSynchronously(0);
  990. return Task.CompletedTask;
  991. }
  992. else if (errorCode != ERROR_IO_PENDING)
  993. {
  994. if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere.
  995. {
  996. SeekCore(_fileHandle, 0, SeekOrigin.Current);
  997. }
  998. completionSource.ReleaseNativeResource();
  999. if (errorCode == ERROR_HANDLE_EOF)
  1000. {
  1001. throw Error.GetEndOfFile();
  1002. }
  1003. else
  1004. {
  1005. throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
  1006. }
  1007. }
  1008. else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING
  1009. {
  1010. // Only once the IO is pending do we register for cancellation
  1011. completionSource.RegisterForCancellation(cancellationToken);
  1012. }
  1013. }
  1014. else
  1015. {
  1016. // Due to a workaround for a race condition in NT's ReadFile &
  1017. // WriteFile routines, we will always be returning 0 from WriteFileNative
  1018. // when we do async IO instead of the number of bytes written,
  1019. // irregardless of whether the operation completed
  1020. // synchronously or asynchronously. We absolutely must not
  1021. // set asyncResult._numBytes here, since will never have correct
  1022. // results.
  1023. }
  1024. return completionSource.Task;
  1025. }
  1026. // Windows API definitions, from winbase.h and others
  1027. private const int FILE_ATTRIBUTE_NORMAL = 0x00000080;
  1028. private const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000;
  1029. private const int FILE_FLAG_OVERLAPPED = 0x40000000;
  1030. internal const int GENERIC_READ = unchecked((int)0x80000000);
  1031. private const int GENERIC_WRITE = 0x40000000;
  1032. private const int FILE_BEGIN = 0;
  1033. private const int FILE_CURRENT = 1;
  1034. private const int FILE_END = 2;
  1035. // Error codes (not HRESULTS), from winerror.h
  1036. internal const int ERROR_BROKEN_PIPE = 109;
  1037. internal const int ERROR_NO_DATA = 232;
  1038. private const int ERROR_HANDLE_EOF = 38;
  1039. private const int ERROR_INVALID_PARAMETER = 87;
  1040. private const int ERROR_IO_PENDING = 997;
  1041. // __ConsoleStream also uses this code.
  1042. private unsafe int ReadFileNative(SafeFileHandle handle, Span<byte> bytes, NativeOverlapped* overlapped, out int errorCode)
  1043. {
  1044. Debug.Assert(handle != null, "handle != null");
  1045. Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to ReadFileNative.");
  1046. int r;
  1047. int numBytesRead = 0;
  1048. fixed (byte* p = &MemoryMarshal.GetReference(bytes))
  1049. {
  1050. r = _useAsyncIO ?
  1051. Interop.Kernel32.ReadFile(handle, p, bytes.Length, IntPtr.Zero, overlapped) :
  1052. Interop.Kernel32.ReadFile(handle, p, bytes.Length, out numBytesRead, IntPtr.Zero);
  1053. }
  1054. if (r == 0)
  1055. {
  1056. errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
  1057. return -1;
  1058. }
  1059. else
  1060. {
  1061. errorCode = 0;
  1062. return numBytesRead;
  1063. }
  1064. }
  1065. private unsafe int WriteFileNative(SafeFileHandle handle, ReadOnlySpan<byte> buffer, NativeOverlapped* overlapped, out int errorCode)
  1066. {
  1067. Debug.Assert(handle != null, "handle != null");
  1068. Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to WriteFileNative.");
  1069. int numBytesWritten = 0;
  1070. int r;
  1071. fixed (byte* p = &MemoryMarshal.GetReference(buffer))
  1072. {
  1073. r = _useAsyncIO ?
  1074. Interop.Kernel32.WriteFile(handle, p, buffer.Length, IntPtr.Zero, overlapped) :
  1075. Interop.Kernel32.WriteFile(handle, p, buffer.Length, out numBytesWritten, IntPtr.Zero);
  1076. }
  1077. if (r == 0)
  1078. {
  1079. errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
  1080. return -1;
  1081. }
  1082. else
  1083. {
  1084. errorCode = 0;
  1085. return numBytesWritten;
  1086. }
  1087. }
  1088. private int GetLastWin32ErrorAndDisposeHandleIfInvalid()
  1089. {
  1090. int errorCode = Marshal.GetLastWin32Error();
  1091. // If ERROR_INVALID_HANDLE is returned, it doesn't suffice to set
  1092. // the handle as invalid; the handle must also be closed.
  1093. //
  1094. // Marking the handle as invalid but not closing the handle
  1095. // resulted in exceptions during finalization and locked column
  1096. // values (due to invalid but unclosed handle) in SQL Win32FileStream
  1097. // scenarios.
  1098. //
  1099. // A more mainstream scenario involves accessing a file on a
  1100. // network share. ERROR_INVALID_HANDLE may occur because the network
  1101. // connection was dropped and the server closed the handle. However,
  1102. // the client side handle is still open and even valid for certain
  1103. // operations.
  1104. //
  1105. // Note that _parent.Dispose doesn't throw so we don't need to special case.
  1106. // SetHandleAsInvalid only sets _closed field to true (without
  1107. // actually closing handle) so we don't need to call that as well.
  1108. if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
  1109. {
  1110. _fileHandle.Dispose();
  1111. }
  1112. return errorCode;
  1113. }
  1114. public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
  1115. {
  1116. // If we're in sync mode, just use the shared CopyToAsync implementation that does
  1117. // typical read/write looping. We also need to take this path if this is a derived
  1118. // instance from FileStream, as a derived type could have overridden ReadAsync, in which
  1119. // case our custom CopyToAsync implementation isn't necessarily correct.
  1120. if (!_useAsyncIO || GetType() != typeof(FileStream))
  1121. {
  1122. return base.CopyToAsync(destination, bufferSize, cancellationToken);
  1123. }
  1124. StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize);
  1125. // Bail early for cancellation if cancellation has been requested
  1126. if (cancellationToken.IsCancellationRequested)
  1127. {
  1128. return Task.FromCanceled<int>(cancellationToken);
  1129. }
  1130. // Fail if the file was closed
  1131. if (_fileHandle.IsClosed)
  1132. {
  1133. throw Error.GetFileNotOpen();
  1134. }
  1135. // Do the async copy, with differing implementations based on whether the FileStream was opened as async or sync
  1136. Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
  1137. return AsyncModeCopyToAsync(destination, bufferSize, cancellationToken);
  1138. }
  1139. private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
  1140. {
  1141. Debug.Assert(_useAsyncIO, "This implementation is for async mode only");
  1142. Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
  1143. Debug.Assert(CanRead, "_parent.CanRead");
  1144. // Make sure any pending writes have been flushed before we do a read.
  1145. if (_writePos > 0)
  1146. {
  1147. await FlushWriteAsync(cancellationToken).ConfigureAwait(false);
  1148. }
  1149. // Typically CopyToAsync would be invoked as the only "read" on the stream, but it's possible some reading is
  1150. // done and then the CopyToAsync is issued. For that case, see if we have any data available in the buffer.
  1151. if (GetBuffer() != null)
  1152. {
  1153. int bufferedBytes = _readLength - _readPos;
  1154. if (bufferedBytes > 0)
  1155. {
  1156. await destination.WriteAsync(new ReadOnlyMemory<byte>(GetBuffer(), _readPos, bufferedBytes), cancellationToken).ConfigureAwait(false);
  1157. _readPos = _readLength = 0;
  1158. }
  1159. }
  1160. // For efficiency, we avoid creating a new task and associated state for each asynchronous read.
  1161. // Instead, we create a single reusable awaitable object that will be triggered when an await completes
  1162. // and reset before going again.
  1163. var readAwaitable = new AsyncCopyToAwaitable(this);
  1164. // Make sure we are reading from the position that we think we are.
  1165. // Only set the position in the awaitable if we can seek (e.g. not for pipes).
  1166. bool canSeek = CanSeek;
  1167. if (canSeek)
  1168. {
  1169. VerifyOSHandlePosition();
  1170. readAwaitable._position = _filePosition;
  1171. }
  1172. // Get the buffer to use for the copy operation, as the base CopyToAsync does. We don't try to use
  1173. // _buffer here, even if it's not null, as concurrent operations are allowed, and another operation may
  1174. // actually be using the buffer already. Plus, it'll be rare for _buffer to be non-null, as typically
  1175. // CopyToAsync is used as the only operation performed on the stream, and the buffer is lazily initialized.
  1176. // Further, typically the CopyToAsync buffer size will be larger than that used by the FileStream, such that
  1177. // we'd likely be unable to use it anyway. Instead, we rent the buffer from a pool.
  1178. byte[] copyBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
  1179. // Allocate an Overlapped we can use repeatedly for all operations
  1180. var awaitableOverlapped = new PreAllocatedOverlapped(AsyncCopyToAwaitable.s_callback, readAwaitable, copyBuffer);
  1181. var cancellationReg = default(CancellationTokenRegistration);
  1182. try
  1183. {
  1184. // Register for cancellation. We do this once for the whole copy operation, and just try to cancel
  1185. // whatever read operation may currently be in progress, if there is one. It's possible the cancellation
  1186. // request could come in between operations, in which case we flag that with explicit calls to ThrowIfCancellationRequested
  1187. // in the read/write copy loop.
  1188. if (cancellationToken.CanBeCanceled)
  1189. {
  1190. cancellationReg = cancellationToken.UnsafeRegister(s =>
  1191. {
  1192. var innerAwaitable = (AsyncCopyToAwaitable)s;
  1193. unsafe
  1194. {
  1195. lock (innerAwaitable.CancellationLock) // synchronize with cleanup of the overlapped
  1196. {
  1197. if (innerAwaitable._nativeOverlapped != null)
  1198. {
  1199. // Try to cancel the I/O. We ignore the return value, as cancellation is opportunistic and we
  1200. // don't want to fail the operation because we couldn't cancel it.
  1201. Interop.Kernel32.CancelIoEx(innerAwaitable._fileStream._fileHandle, innerAwaitable._nativeOverlapped);
  1202. }
  1203. }
  1204. }
  1205. }, readAwaitable);
  1206. }
  1207. // Repeatedly read from this FileStream and write the results to the destination stream.
  1208. while (true)
  1209. {
  1210. cancellationToken.ThrowIfCancellationRequested();
  1211. readAwaitable.ResetForNextOperation();
  1212. try
  1213. {
  1214. bool synchronousSuccess;
  1215. int errorCode;
  1216. unsafe
  1217. {
  1218. // Allocate a native overlapped for our reusable overlapped, and set position to read based on the next
  1219. // desired address stored in the awaitable. (This position may be 0, if either we're at the beginning or
  1220. // if the stream isn't seekable.)
  1221. readAwaitable._nativeOverlapped = _fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(awaitableOverlapped);
  1222. if (canSeek)
  1223. {
  1224. readAwaitable._nativeOverlapped->OffsetLow = unchecked((int)readAwaitable._position);
  1225. readAwaitable._nativeOverlapped->OffsetHigh = (int)(readAwaitable._position >> 32);
  1226. }
  1227. // Kick off the read.
  1228. synchronousSuccess = ReadFileNative(_fileHandle, copyBuffer, readAwaitable._nativeOverlapped, out errorCode) >= 0;
  1229. }
  1230. // If the operation did not synchronously succeed, it either failed or initiated the asynchronous operation.
  1231. if (!synchronousSuccess)
  1232. {
  1233. switch (errorCode)
  1234. {
  1235. case ERROR_IO_PENDING:
  1236. // Async operation in progress.
  1237. break;
  1238. case ERROR_BROKEN_PIPE:
  1239. case ERROR_HANDLE_EOF:
  1240. // We're at or past the end of the file, and the overlapped callback
  1241. // won't be raised in these cases. Mark it as completed so that the await
  1242. // below will see it as such.
  1243. readAwaitable.MarkCompleted();
  1244. break;
  1245. default:
  1246. // Everything else is an error (and there won't be a callback).
  1247. throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
  1248. }
  1249. }
  1250. // Wait for the async operation (which may or may not have already completed), then throw if it failed.
  1251. await readAwaitable;
  1252. switch (readAwaitable._errorCode)
  1253. {
  1254. case 0: // success
  1255. Debug.Assert(readAwaitable._numBytes >= 0, $"Expected non-negative numBytes, got {readAwaitable._numBytes}");
  1256. break;
  1257. case ERROR_BROKEN_PIPE: // logically success with 0 bytes read (write end of pipe closed)
  1258. case ERROR_HANDLE_EOF: // logically success with 0 bytes read (read at end of file)
  1259. Debug.Assert(readAwaitable._numBytes == 0, $"Expected 0 bytes read, got {readAwaitable._numBytes}");
  1260. break;
  1261. case Interop.Errors.ERROR_OPERATION_ABORTED: // canceled
  1262. throw new OperationCanceledException(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true));
  1263. default: // error
  1264. throw Win32Marshal.GetExceptionForWin32Error((int)readAwaitable._errorCode, _path);
  1265. }
  1266. // Successful operation. If we got zero bytes, we're done: exit the read/write loop.
  1267. int numBytesRead = (int)readAwaitable._numBytes;
  1268. if (numBytesRead == 0)
  1269. {
  1270. break;
  1271. }
  1272. // Otherwise, update the read position for next time accordingly.
  1273. if (canSeek)
  1274. {
  1275. readAwaitable._position += numBytesRead;
  1276. }
  1277. }
  1278. finally
  1279. {
  1280. // Free the resources for this read operation
  1281. unsafe
  1282. {
  1283. NativeOverlapped* overlapped;
  1284. lock (readAwaitable.CancellationLock) // just an Exchange, but we need this to be synchronized with cancellation, so using the same lock
  1285. {
  1286. overlapped = readAwaitable._nativeOverlapped;
  1287. readAwaitable._nativeOverlapped = null;
  1288. }
  1289. if (overlapped != null)
  1290. {
  1291. _fileHandle.ThreadPoolBinding.FreeNativeOverlapped(overlapped);
  1292. }
  1293. }
  1294. }
  1295. // Write out the read data.
  1296. await destination.WriteAsync(new ReadOnlyMemory<byte>(copyBuffer, 0, (int)readAwaitable._numBytes), cancellationToken).ConfigureAwait(false);
  1297. }
  1298. }
  1299. finally
  1300. {
  1301. // Cleanup from the whole copy operation
  1302. cancellationReg.Dispose();
  1303. awaitableOverlapped.Dispose();
  1304. ArrayPool<byte>.Shared.Return(copyBuffer);
  1305. // Make sure the stream's current position reflects where we ended up
  1306. if (!_fileHandle.IsClosed && CanSeek)
  1307. {
  1308. SeekCore(_fileHandle, 0, SeekOrigin.End);
  1309. }
  1310. }
  1311. }
  1312. /// <summary>Used by CopyToAsync to enable awaiting the result of an overlapped I/O operation with minimal overhead.</summary>
  1313. private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion
  1314. {
  1315. /// <summary>Sentinel object used to indicate that the I/O operation has completed before being awaited.</summary>
  1316. private readonly static Action s_sentinel = () => { };
  1317. /// <summary>Cached delegate to IOCallback.</summary>
  1318. internal static readonly IOCompletionCallback s_callback = IOCallback;
  1319. /// <summary>The FileStream that owns this instance.</summary>
  1320. internal readonly FileStream _fileStream;
  1321. /// <summary>Tracked position representing the next location from which to read.</summary>
  1322. internal long _position;
  1323. /// <summary>The current native overlapped pointer. This changes for each operation.</summary>
  1324. internal NativeOverlapped* _nativeOverlapped;
  1325. /// <summary>
  1326. /// null if the operation is still in progress,
  1327. /// s_sentinel if the I/O operation completed before the await,
  1328. /// s_callback if it completed after the await yielded.
  1329. /// </summary>
  1330. internal Action _continuation;
  1331. /// <summary>Last error code from completed operation.</summary>
  1332. internal uint _errorCode;
  1333. /// <summary>Last number of read bytes from completed operation.</summary>
  1334. internal uint _numBytes;
  1335. /// <summary>Lock object used to protect cancellation-related access to _nativeOverlapped.</summary>
  1336. internal object CancellationLock => this;
  1337. /// <summary>Initialize the awaitable.</summary>
  1338. internal unsafe AsyncCopyToAwaitable(FileStream fileStream)
  1339. {
  1340. _fileStream = fileStream;
  1341. }
  1342. /// <summary>Reset state to prepare for the next read operation.</summary>
  1343. internal void ResetForNextOperation()
  1344. {
  1345. Debug.Assert(_position >= 0, $"Expected non-negative position, got {_position}");
  1346. _continuation = null;
  1347. _errorCode = 0;
  1348. _numBytes = 0;
  1349. }
  1350. /// <summary>Overlapped callback: store the results, then invoke the continuation delegate.</summary>
  1351. internal static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP)
  1352. {
  1353. var awaitable = (AsyncCopyToAwaitable)ThreadPoolBoundHandle.GetNativeOverlappedState(pOVERLAP);
  1354. Debug.Assert(!ReferenceEquals(awaitable._continuation, s_sentinel), "Sentinel must not have already been set as the continuation");
  1355. awaitable._errorCode = errorCode;
  1356. awaitable._numBytes = numBytes;
  1357. (awaitable._continuation ?? Interlocked.CompareExchange(ref awaitable._continuation, s_sentinel, null))?.Invoke();
  1358. }
  1359. /// <summary>
  1360. /// Called when it's known that the I/O callback for an operation will not be invoked but we'll
  1361. /// still be awaiting the awaitable.
  1362. /// </summary>
  1363. internal void MarkCompleted()
  1364. {
  1365. Debug.Assert(_continuation == null, "Expected null continuation");
  1366. _continuation = s_sentinel;
  1367. }
  1368. public AsyncCopyToAwaitable GetAwaiter() => this;
  1369. public bool IsCompleted => ReferenceEquals(_continuation, s_sentinel);
  1370. public void GetResult() { }
  1371. public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation);
  1372. public void UnsafeOnCompleted(Action continuation)
  1373. {
  1374. if (ReferenceEquals(_continuation, s_sentinel) ||
  1375. Interlocked.CompareExchange(ref _continuation, continuation, null) != null)
  1376. {
  1377. Debug.Assert(ReferenceEquals(_continuation, s_sentinel), $"Expected continuation set to s_sentinel, got ${_continuation}");
  1378. Task.Run(continuation);
  1379. }
  1380. }
  1381. }
  1382. // Unlike Flush(), FlushAsync() always flushes to disk. This is intentional.
  1383. // Legend is that we chose not to flush the OS file buffers in Flush() in fear of
  1384. // perf problems with frequent, long running FlushFileBuffers() calls. But we don't
  1385. // have that problem with FlushAsync() because we will call FlushFileBuffers() in the background.
  1386. private Task FlushAsyncInternal(CancellationToken cancellationToken)
  1387. {
  1388. if (cancellationToken.IsCancellationRequested)
  1389. return Task.FromCanceled(cancellationToken);
  1390. if (_fileHandle.IsClosed)
  1391. throw Error.GetFileNotOpen();
  1392. // TODO: https://github.com/dotnet/corefx/issues/32837 (stop doing this synchronous work).
  1393. // The always synchronous data transfer between the OS and the internal buffer is intentional
  1394. // because this is needed to allow concurrent async IO requests. Concurrent data transfer
  1395. // between the OS and the internal buffer will result in race conditions. Since FlushWrite and
  1396. // FlushRead modify internal state of the stream and transfer data between the OS and the
  1397. // internal buffer, they cannot be truly async. We will, however, flush the OS file buffers
  1398. // asynchronously because it doesn't modify any internal state of the stream and is potentially
  1399. // a long running process.
  1400. try
  1401. {
  1402. FlushInternalBuffer();
  1403. }
  1404. catch (Exception e)
  1405. {
  1406. return Task.FromException(e);
  1407. }
  1408. if (CanWrite)
  1409. {
  1410. return Task.Factory.StartNew(
  1411. state => ((FileStream)state).FlushOSBuffer(),
  1412. this,
  1413. cancellationToken,
  1414. TaskCreationOptions.DenyChildAttach,
  1415. TaskScheduler.Default);
  1416. }
  1417. else
  1418. {
  1419. return Task.CompletedTask;
  1420. }
  1421. }
  1422. private void LockInternal(long position, long length)
  1423. {
  1424. int positionLow = unchecked((int)(position));
  1425. int positionHigh = unchecked((int)(position >> 32));
  1426. int lengthLow = unchecked((int)(length));
  1427. int lengthHigh = unchecked((int)(length >> 32));
  1428. if (!Interop.Kernel32.LockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
  1429. {
  1430. throw Win32Marshal.GetExceptionForLastWin32Error(_path);
  1431. }
  1432. }
  1433. private void UnlockInternal(long position, long length)
  1434. {
  1435. int positionLow = unchecked((int)(position));
  1436. int positionHigh = unchecked((int)(position >> 32));
  1437. int lengthLow = unchecked((int)(length));
  1438. int lengthHigh = unchecked((int)(length >> 32));
  1439. if (!Interop.Kernel32.UnlockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
  1440. {
  1441. throw Win32Marshal.GetExceptionForLastWin32Error(_path);
  1442. }
  1443. }
  1444. private SafeFileHandle ValidateFileHandle(SafeFileHandle fileHandle)
  1445. {
  1446. if (fileHandle.IsInvalid)
  1447. {
  1448. // Return a meaningful exception with the full path.
  1449. // NT5 oddity - when trying to open "C:\" as a Win32FileStream,
  1450. // we usually get ERROR_PATH_NOT_FOUND from the OS. We should
  1451. // probably be consistent w/ every other directory.
  1452. int errorCode = Marshal.GetLastWin32Error();
  1453. if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && _path.Length == PathInternal.GetRootLength(_path))
  1454. errorCode = Interop.Errors.ERROR_ACCESS_DENIED;
  1455. throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
  1456. }
  1457. fileHandle.IsAsync = _useAsyncIO;
  1458. return fileHandle;
  1459. }
  1460. }
  1461. }