FileStream.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  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.Diagnostics;
  5. using System.Runtime.InteropServices;
  6. using System.Runtime.Serialization;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using Microsoft.Win32.SafeHandles;
  10. namespace System.IO
  11. {
  12. public partial class FileStream : Stream
  13. {
  14. private const FileShare DefaultShare = FileShare.Read;
  15. private const bool DefaultIsAsync = false;
  16. internal const int DefaultBufferSize = 4096;
  17. private byte[]? _buffer;
  18. private int _bufferLength;
  19. private readonly SafeFileHandle _fileHandle; // only ever null if ctor throws
  20. /// <summary>Whether the file is opened for reading, writing, or both.</summary>
  21. private readonly FileAccess _access;
  22. /// <summary>The path to the opened file.</summary>
  23. private readonly string? _path;
  24. /// <summary>The next available byte to be read from the _buffer.</summary>
  25. private int _readPos;
  26. /// <summary>The number of valid bytes in _buffer.</summary>
  27. private int _readLength;
  28. /// <summary>The next location in which a write should occur to the buffer.</summary>
  29. private int _writePos;
  30. /// <summary>
  31. /// Whether asynchronous read/write/flush operations should be performed using async I/O.
  32. /// On Windows FileOptions.Asynchronous controls how the file handle is configured,
  33. /// and then as a result how operations are issued against that file handle. On Unix,
  34. /// there isn't any distinction around how file descriptors are created for async vs
  35. /// sync, but we still differentiate how the operations are issued in order to provide
  36. /// similar behavioral semantics and performance characteristics as on Windows. On
  37. /// Windows, if non-async, async read/write requests just delegate to the base stream,
  38. /// and no attempt is made to synchronize between sync and async operations on the stream;
  39. /// if async, then async read/write requests are implemented specially, and sync read/write
  40. /// requests are coordinated with async ones by implementing the sync ones over the async
  41. /// ones. On Unix, we do something similar. If non-async, async read/write requests just
  42. /// delegate to the base stream, and no attempt is made to synchronize. If async, we use
  43. /// a semaphore to coordinate both sync and async operations.
  44. /// </summary>
  45. private readonly bool _useAsyncIO;
  46. /// <summary>cached task for read ops that complete synchronously</summary>
  47. private Task<int>? _lastSynchronouslyCompletedTask = null;
  48. /// <summary>
  49. /// Currently cached position in the stream. This should always mirror the underlying file's actual position,
  50. /// and should only ever be out of sync if another stream with access to this same file manipulates it, at which
  51. /// point we attempt to error out.
  52. /// </summary>
  53. private long _filePosition;
  54. /// <summary>Whether the file stream's handle has been exposed.</summary>
  55. private bool _exposedHandle;
  56. /// <summary>Caches whether Serialization Guard has been disabled for file writes</summary>
  57. private static int s_cachedSerializationSwitch = 0;
  58. [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. https://go.microsoft.com/fwlink/?linkid=14202")]
  59. public FileStream(IntPtr handle, FileAccess access)
  60. : this(handle, access, true, DefaultBufferSize, false)
  61. {
  62. }
  63. [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
  64. public FileStream(IntPtr handle, FileAccess access, bool ownsHandle)
  65. : this(handle, access, ownsHandle, DefaultBufferSize, false)
  66. {
  67. }
  68. [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
  69. public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
  70. : this(handle, access, ownsHandle, bufferSize, false)
  71. {
  72. }
  73. [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
  74. public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
  75. {
  76. SafeFileHandle safeHandle = new SafeFileHandle(handle, ownsHandle: ownsHandle);
  77. try
  78. {
  79. ValidateAndInitFromHandle(safeHandle, access, bufferSize, isAsync);
  80. }
  81. catch
  82. {
  83. // We don't want to take ownership of closing passed in handles
  84. // *unless* the constructor completes successfully.
  85. GC.SuppressFinalize(safeHandle);
  86. // This would also prevent Close from being called, but is unnecessary
  87. // as we've removed the object from the finalizer queue.
  88. //
  89. // safeHandle.SetHandleAsInvalid();
  90. throw;
  91. }
  92. // Note: Cleaner to set the following fields in ValidateAndInitFromHandle,
  93. // but we can't as they're readonly.
  94. _access = access;
  95. _useAsyncIO = isAsync;
  96. // As the handle was passed in, we must set the handle field at the very end to
  97. // avoid the finalizer closing the handle when we throw errors.
  98. _fileHandle = safeHandle;
  99. }
  100. public FileStream(SafeFileHandle handle, FileAccess access)
  101. : this(handle, access, DefaultBufferSize)
  102. {
  103. }
  104. public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
  105. : this(handle, access, bufferSize, GetDefaultIsAsync(handle))
  106. {
  107. }
  108. private void ValidateAndInitFromHandle(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
  109. {
  110. if (handle.IsInvalid)
  111. throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle));
  112. if (access < FileAccess.Read || access > FileAccess.ReadWrite)
  113. throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
  114. if (bufferSize <= 0)
  115. throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
  116. if (handle.IsClosed)
  117. throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
  118. if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.GetValueOrDefault())
  119. throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle));
  120. _exposedHandle = true;
  121. _bufferLength = bufferSize;
  122. InitFromHandle(handle, access, isAsync);
  123. }
  124. public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
  125. {
  126. ValidateAndInitFromHandle(handle, access, bufferSize, isAsync);
  127. // Note: Cleaner to set the following fields in ValidateAndInitFromHandle,
  128. // but we can't as they're readonly.
  129. _access = access;
  130. _useAsyncIO = isAsync;
  131. // As the handle was passed in, we must set the handle field at the very end to
  132. // avoid the finalizer closing the handle when we throw errors.
  133. _fileHandle = handle;
  134. }
  135. public FileStream(string path, FileMode mode) :
  136. this(path, mode, mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite, DefaultShare, DefaultBufferSize, DefaultIsAsync)
  137. { }
  138. public FileStream(string path, FileMode mode, FileAccess access) :
  139. this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync)
  140. { }
  141. public FileStream(string path, FileMode mode, FileAccess access, FileShare share) :
  142. this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync)
  143. { }
  144. public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) :
  145. this(path, mode, access, share, bufferSize, DefaultIsAsync)
  146. { }
  147. public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) :
  148. this(path, mode, access, share, bufferSize, useAsync ? FileOptions.Asynchronous : FileOptions.None)
  149. { }
  150. public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
  151. {
  152. if (path == null)
  153. throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path);
  154. if (path.Length == 0)
  155. throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
  156. // don't include inheritable in our bounds check for share
  157. FileShare tempshare = share & ~FileShare.Inheritable;
  158. string? badArg = null;
  159. if (mode < FileMode.CreateNew || mode > FileMode.Append)
  160. badArg = nameof(mode);
  161. else if (access < FileAccess.Read || access > FileAccess.ReadWrite)
  162. badArg = nameof(access);
  163. else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
  164. badArg = nameof(share);
  165. if (badArg != null)
  166. throw new ArgumentOutOfRangeException(badArg, SR.ArgumentOutOfRange_Enum);
  167. // NOTE: any change to FileOptions enum needs to be matched here in the error validation
  168. if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0)
  169. throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum);
  170. if (bufferSize <= 0)
  171. throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
  172. // Write access validation
  173. if ((access & FileAccess.Write) == 0)
  174. {
  175. if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
  176. {
  177. // No write access, mode and access disagree but flag access since mode comes first
  178. throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access));
  179. }
  180. }
  181. if ((access & FileAccess.Read) != 0 && mode == FileMode.Append)
  182. throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
  183. string fullPath = Path.GetFullPath(path);
  184. _path = fullPath;
  185. _access = access;
  186. _bufferLength = bufferSize;
  187. if ((options & FileOptions.Asynchronous) != 0)
  188. _useAsyncIO = true;
  189. if ((access & FileAccess.Write) == FileAccess.Write)
  190. {
  191. SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch);
  192. }
  193. _fileHandle = OpenHandle(mode, share, options);
  194. try
  195. {
  196. Init(mode, share, path);
  197. }
  198. catch
  199. {
  200. // If anything goes wrong while setting up the stream, make sure we deterministically dispose
  201. // of the opened handle.
  202. _fileHandle.Dispose();
  203. _fileHandle = null!;
  204. throw;
  205. }
  206. }
  207. [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. https://go.microsoft.com/fwlink/?linkid=14202")]
  208. public virtual IntPtr Handle => SafeFileHandle.DangerousGetHandle();
  209. public virtual void Lock(long position, long length)
  210. {
  211. if (position < 0 || length < 0)
  212. {
  213. throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
  214. }
  215. if (_fileHandle.IsClosed)
  216. {
  217. throw Error.GetFileNotOpen();
  218. }
  219. LockInternal(position, length);
  220. }
  221. public virtual void Unlock(long position, long length)
  222. {
  223. if (position < 0 || length < 0)
  224. {
  225. throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
  226. }
  227. if (_fileHandle.IsClosed)
  228. {
  229. throw Error.GetFileNotOpen();
  230. }
  231. UnlockInternal(position, length);
  232. }
  233. public override Task FlushAsync(CancellationToken cancellationToken)
  234. {
  235. // If we have been inherited into a subclass, the following implementation could be incorrect
  236. // since it does not call through to Flush() which a subclass might have overridden. To be safe
  237. // we will only use this implementation in cases where we know it is safe to do so,
  238. // and delegate to our base class (which will call into Flush) when we are not sure.
  239. if (GetType() != typeof(FileStream))
  240. return base.FlushAsync(cancellationToken);
  241. return FlushAsyncInternal(cancellationToken);
  242. }
  243. public override int Read(byte[] array, int offset, int count)
  244. {
  245. ValidateReadWriteArgs(array, offset, count);
  246. return _useAsyncIO ?
  247. ReadAsyncTask(array, offset, count, CancellationToken.None).GetAwaiter().GetResult() :
  248. ReadSpan(new Span<byte>(array, offset, count));
  249. }
  250. public override int Read(Span<byte> buffer)
  251. {
  252. if (GetType() == typeof(FileStream) && !_useAsyncIO)
  253. {
  254. if (_fileHandle.IsClosed)
  255. {
  256. throw Error.GetFileNotOpen();
  257. }
  258. return ReadSpan(buffer);
  259. }
  260. else
  261. {
  262. // This type is derived from FileStream and/or the stream is in async mode. If this is a
  263. // derived type, it may have overridden Read(byte[], int, int) prior to this Read(Span<byte>)
  264. // overload being introduced. In that case, this Read(Span<byte>) overload should use the behavior
  265. // of Read(byte[],int,int) overload. Or if the stream is in async mode, we can't call the
  266. // synchronous ReadSpan, so we similarly call the base Read, which will turn delegate to
  267. // Read(byte[],int,int), which will do the right thing if we're in async mode.
  268. return base.Read(buffer);
  269. }
  270. }
  271. public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
  272. {
  273. if (buffer == null)
  274. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  275. if (offset < 0)
  276. throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
  277. if (count < 0)
  278. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  279. if (buffer.Length - offset < count)
  280. throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
  281. if (GetType() != typeof(FileStream))
  282. {
  283. // If we have been inherited into a subclass, the following implementation could be incorrect
  284. // since it does not call through to Read() which a subclass might have overridden.
  285. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  286. // and delegate to our base class (which will call into Read/ReadAsync) when we are not sure.
  287. return base.ReadAsync(buffer, offset, count, cancellationToken);
  288. }
  289. if (cancellationToken.IsCancellationRequested)
  290. return Task.FromCanceled<int>(cancellationToken);
  291. if (IsClosed)
  292. throw Error.GetFileNotOpen();
  293. if (!_useAsyncIO)
  294. {
  295. // If we weren't opened for asynchronous I/O, we still call to the base implementation so that
  296. // Read is invoked asynchronously. But we can do so using the base Stream's internal helper
  297. // that bypasses delegating to BeginRead, since we already know this is FileStream rather
  298. // than something derived from it and what our BeginRead implementation is going to do.
  299. return (Task<int>)base.BeginReadInternal(buffer, offset, count, null, null, serializeAsynchronously: true, apm: false);
  300. }
  301. return ReadAsyncTask(buffer, offset, count, cancellationToken);
  302. }
  303. public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
  304. {
  305. if (GetType() != typeof(FileStream))
  306. {
  307. // If this isn't a concrete FileStream, a derived type may have overridden ReadAsync(byte[],...),
  308. // which was introduced first, so delegate to the base which will delegate to that.
  309. return base.ReadAsync(buffer, cancellationToken);
  310. }
  311. if (cancellationToken.IsCancellationRequested)
  312. {
  313. return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken));
  314. }
  315. if (IsClosed)
  316. {
  317. throw Error.GetFileNotOpen();
  318. }
  319. if (!_useAsyncIO)
  320. {
  321. // If we weren't opened for asynchronous I/O, we still call to the base implementation so that
  322. // Read is invoked asynchronously. But if we have a byte[], we can do so using the base Stream's
  323. // internal helper that bypasses delegating to BeginRead, since we already know this is FileStream
  324. // rather than something derived from it and what our BeginRead implementation is going to do.
  325. return MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment) ?
  326. new ValueTask<int>((Task<int>)base.BeginReadInternal(segment.Array!, segment.Offset, segment.Count, null, null, serializeAsynchronously: true, apm: false)) :
  327. base.ReadAsync(buffer, cancellationToken);
  328. }
  329. Task<int>? t = ReadAsyncInternal(buffer, cancellationToken, out int synchronousResult);
  330. return t != null ?
  331. new ValueTask<int>(t) :
  332. new ValueTask<int>(synchronousResult);
  333. }
  334. private Task<int> ReadAsyncTask(byte[] array, int offset, int count, CancellationToken cancellationToken)
  335. {
  336. Task<int>? t = ReadAsyncInternal(new Memory<byte>(array, offset, count), cancellationToken, out int synchronousResult);
  337. if (t == null)
  338. {
  339. t = _lastSynchronouslyCompletedTask;
  340. Debug.Assert(t == null || t.IsCompletedSuccessfully, "Cached task should have completed successfully");
  341. if (t == null || t.Result != synchronousResult)
  342. {
  343. _lastSynchronouslyCompletedTask = t = Task.FromResult(synchronousResult);
  344. }
  345. }
  346. return t;
  347. }
  348. public override void Write(byte[] array, int offset, int count)
  349. {
  350. ValidateReadWriteArgs(array, offset, count);
  351. if (_useAsyncIO)
  352. {
  353. WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, count), CancellationToken.None).GetAwaiter().GetResult();
  354. }
  355. else
  356. {
  357. WriteSpan(new ReadOnlySpan<byte>(array, offset, count));
  358. }
  359. }
  360. public override void Write(ReadOnlySpan<byte> buffer)
  361. {
  362. if (GetType() == typeof(FileStream) && !_useAsyncIO)
  363. {
  364. if (_fileHandle.IsClosed)
  365. {
  366. throw Error.GetFileNotOpen();
  367. }
  368. WriteSpan(buffer);
  369. }
  370. else
  371. {
  372. // This type is derived from FileStream and/or the stream is in async mode. If this is a
  373. // derived type, it may have overridden Write(byte[], int, int) prior to this Write(ReadOnlySpan<byte>)
  374. // overload being introduced. In that case, this Write(ReadOnlySpan<byte>) overload should use the behavior
  375. // of Write(byte[],int,int) overload. Or if the stream is in async mode, we can't call the
  376. // synchronous WriteSpan, so we similarly call the base Write, which will turn delegate to
  377. // Write(byte[],int,int), which will do the right thing if we're in async mode.
  378. base.Write(buffer);
  379. }
  380. }
  381. public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
  382. {
  383. if (buffer == null)
  384. throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
  385. if (offset < 0)
  386. throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
  387. if (count < 0)
  388. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  389. if (buffer.Length - offset < count)
  390. throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
  391. if (GetType() != typeof(FileStream))
  392. {
  393. // If we have been inherited into a subclass, the following implementation could be incorrect
  394. // since it does not call through to Write() or WriteAsync() which a subclass might have overridden.
  395. // To be safe we will only use this implementation in cases where we know it is safe to do so,
  396. // and delegate to our base class (which will call into Write/WriteAsync) when we are not sure.
  397. return base.WriteAsync(buffer, offset, count, cancellationToken);
  398. }
  399. if (cancellationToken.IsCancellationRequested)
  400. return Task.FromCanceled(cancellationToken);
  401. if (IsClosed)
  402. throw Error.GetFileNotOpen();
  403. if (!_useAsyncIO)
  404. {
  405. // If we weren't opened for asynchronous I/O, we still call to the base implementation so that
  406. // Write is invoked asynchronously. But we can do so using the base Stream's internal helper
  407. // that bypasses delegating to BeginWrite, since we already know this is FileStream rather
  408. // than something derived from it and what our BeginWrite implementation is going to do.
  409. return (Task)base.BeginWriteInternal(buffer, offset, count, null, null, serializeAsynchronously: true, apm: false);
  410. }
  411. return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
  412. }
  413. public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
  414. {
  415. if (GetType() != typeof(FileStream))
  416. {
  417. // If this isn't a concrete FileStream, a derived type may have overridden WriteAsync(byte[],...),
  418. // which was introduced first, so delegate to the base which will delegate to that.
  419. return base.WriteAsync(buffer, cancellationToken);
  420. }
  421. if (cancellationToken.IsCancellationRequested)
  422. {
  423. return new ValueTask(Task.FromCanceled<int>(cancellationToken));
  424. }
  425. if (IsClosed)
  426. {
  427. throw Error.GetFileNotOpen();
  428. }
  429. if (!_useAsyncIO)
  430. {
  431. // If we weren't opened for asynchronous I/O, we still call to the base implementation so that
  432. // Write is invoked asynchronously. But if we have a byte[], we can do so using the base Stream's
  433. // internal helper that bypasses delegating to BeginWrite, since we already know this is FileStream
  434. // rather than something derived from it and what our BeginWrite implementation is going to do.
  435. return MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment) ?
  436. new ValueTask((Task)BeginWriteInternal(segment.Array!, segment.Offset, segment.Count, null, null, serializeAsynchronously: true, apm: false)) :
  437. base.WriteAsync(buffer, cancellationToken);
  438. }
  439. return WriteAsyncInternal(buffer, cancellationToken);
  440. }
  441. /// <summary>
  442. /// Clears buffers for this stream and causes any buffered data to be written to the file.
  443. /// </summary>
  444. public override void Flush()
  445. {
  446. // Make sure that we call through the public virtual API
  447. Flush(flushToDisk: false);
  448. }
  449. /// <summary>
  450. /// Clears buffers for this stream, and if <param name="flushToDisk"/> is true,
  451. /// causes any buffered data to be written to the file.
  452. /// </summary>
  453. public virtual void Flush(bool flushToDisk)
  454. {
  455. if (IsClosed) throw Error.GetFileNotOpen();
  456. FlushInternalBuffer();
  457. if (flushToDisk && CanWrite)
  458. {
  459. FlushOSBuffer();
  460. }
  461. }
  462. /// <summary>Gets a value indicating whether the current stream supports reading.</summary>
  463. public override bool CanRead => !_fileHandle.IsClosed && (_access & FileAccess.Read) != 0;
  464. /// <summary>Gets a value indicating whether the current stream supports writing.</summary>
  465. public override bool CanWrite => !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0;
  466. /// <summary>Validates arguments to Read and Write and throws resulting exceptions.</summary>
  467. /// <param name="array">The buffer to read from or write to.</param>
  468. /// <param name="offset">The zero-based offset into the array.</param>
  469. /// <param name="count">The maximum number of bytes to read or write.</param>
  470. private void ValidateReadWriteArgs(byte[] array, int offset, int count)
  471. {
  472. if (array == null)
  473. throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
  474. if (offset < 0)
  475. throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
  476. if (count < 0)
  477. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
  478. if (array.Length - offset < count)
  479. throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
  480. if (_fileHandle.IsClosed)
  481. throw Error.GetFileNotOpen();
  482. }
  483. /// <summary>Sets the length of this stream to the given value.</summary>
  484. /// <param name="value">The new length of the stream.</param>
  485. public override void SetLength(long value)
  486. {
  487. if (value < 0)
  488. throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
  489. if (_fileHandle.IsClosed)
  490. throw Error.GetFileNotOpen();
  491. if (!CanSeek)
  492. throw Error.GetSeekNotSupported();
  493. if (!CanWrite)
  494. throw Error.GetWriteNotSupported();
  495. SetLengthInternal(value);
  496. }
  497. public virtual SafeFileHandle SafeFileHandle
  498. {
  499. get
  500. {
  501. Flush();
  502. _exposedHandle = true;
  503. return _fileHandle;
  504. }
  505. }
  506. /// <summary>Gets the path that was passed to the constructor.</summary>
  507. public virtual string Name => _path ?? SR.IO_UnknownFileName;
  508. /// <summary>Gets a value indicating whether the stream was opened for I/O to be performed synchronously or asynchronously.</summary>
  509. public virtual bool IsAsync => _useAsyncIO;
  510. /// <summary>Gets the length of the stream in bytes.</summary>
  511. public override long Length
  512. {
  513. get
  514. {
  515. if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
  516. if (!CanSeek) throw Error.GetSeekNotSupported();
  517. return GetLengthInternal();
  518. }
  519. }
  520. /// <summary>
  521. /// Verify that the actual position of the OS's handle equals what we expect it to.
  522. /// This will fail if someone else moved the UnixFileStream's handle or if
  523. /// our position updating code is incorrect.
  524. /// </summary>
  525. private void VerifyOSHandlePosition()
  526. {
  527. bool verifyPosition = _exposedHandle; // in release, only verify if we've given out the handle such that someone else could be manipulating it
  528. #if DEBUG
  529. verifyPosition = true; // in debug, always make sure our position matches what the OS says it should be
  530. #endif
  531. if (verifyPosition && CanSeek)
  532. {
  533. long oldPos = _filePosition; // SeekCore will override the current _position, so save it now
  534. long curPos = SeekCore(_fileHandle, 0, SeekOrigin.Current);
  535. if (oldPos != curPos)
  536. {
  537. // For reads, this is non-fatal but we still could have returned corrupted
  538. // data in some cases, so discard the internal buffer. For writes,
  539. // this is a problem; discard the buffer and error out.
  540. _readPos = _readLength = 0;
  541. if (_writePos > 0)
  542. {
  543. _writePos = 0;
  544. throw new IOException(SR.IO_FileStreamHandlePosition);
  545. }
  546. }
  547. }
  548. }
  549. /// <summary>Verifies that state relating to the read/write buffer is consistent.</summary>
  550. [Conditional("DEBUG")]
  551. private void AssertBufferInvariants()
  552. {
  553. // Read buffer values must be in range: 0 <= _bufferReadPos <= _bufferReadLength <= _bufferLength
  554. Debug.Assert(0 <= _readPos && _readPos <= _readLength && _readLength <= _bufferLength);
  555. // Write buffer values must be in range: 0 <= _bufferWritePos <= _bufferLength
  556. Debug.Assert(0 <= _writePos && _writePos <= _bufferLength);
  557. // Read buffering and write buffering can't both be active
  558. Debug.Assert((_readPos == 0 && _readLength == 0) || _writePos == 0);
  559. }
  560. /// <summary>Validates that we're ready to read from the stream.</summary>
  561. private void PrepareForReading()
  562. {
  563. if (_fileHandle.IsClosed)
  564. throw Error.GetFileNotOpen();
  565. if (_readLength == 0 && !CanRead)
  566. throw Error.GetReadNotSupported();
  567. AssertBufferInvariants();
  568. }
  569. /// <summary>Gets or sets the position within the current stream</summary>
  570. public override long Position
  571. {
  572. get
  573. {
  574. if (_fileHandle.IsClosed)
  575. throw Error.GetFileNotOpen();
  576. if (!CanSeek)
  577. throw Error.GetSeekNotSupported();
  578. AssertBufferInvariants();
  579. VerifyOSHandlePosition();
  580. // We may have read data into our buffer from the handle, such that the handle position
  581. // is artificially further along than the consumer's view of the stream's position.
  582. // Thus, when reading, our position is really starting from the handle position negatively
  583. // offset by the number of bytes in the buffer and positively offset by the number of
  584. // bytes into that buffer we've read. When writing, both the read length and position
  585. // must be zero, and our position is just the handle position offset positive by how many
  586. // bytes we've written into the buffer.
  587. return (_filePosition - _readLength) + _readPos + _writePos;
  588. }
  589. set
  590. {
  591. if (value < 0)
  592. throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
  593. Seek(value, SeekOrigin.Begin);
  594. }
  595. }
  596. internal virtual bool IsClosed => _fileHandle.IsClosed;
  597. private static bool IsIoRelatedException(Exception e) =>
  598. // These all derive from IOException
  599. // DirectoryNotFoundException
  600. // DriveNotFoundException
  601. // EndOfStreamException
  602. // FileLoadException
  603. // FileNotFoundException
  604. // PathTooLongException
  605. // PipeException
  606. e is IOException ||
  607. // Note that SecurityException is only thrown on runtimes that support CAS
  608. // e is SecurityException ||
  609. e is UnauthorizedAccessException ||
  610. e is NotSupportedException ||
  611. (e is ArgumentException && !(e is ArgumentNullException));
  612. /// <summary>
  613. /// Gets the array used for buffering reading and writing.
  614. /// If the array hasn't been allocated, this will lazily allocate it.
  615. /// </summary>
  616. /// <returns>The buffer.</returns>
  617. private byte[] GetBuffer()
  618. {
  619. Debug.Assert(_buffer == null || _buffer.Length == _bufferLength);
  620. if (_buffer == null)
  621. {
  622. _buffer = new byte[_bufferLength];
  623. OnBufferAllocated();
  624. }
  625. return _buffer;
  626. }
  627. partial void OnBufferAllocated();
  628. /// <summary>
  629. /// Flushes the internal read/write buffer for this stream. If write data has been buffered,
  630. /// that data is written out to the underlying file. Or if data has been buffered for
  631. /// reading from the stream, the data is dumped and our position in the underlying file
  632. /// is rewound as necessary. This does not flush the OS buffer.
  633. /// </summary>
  634. private void FlushInternalBuffer()
  635. {
  636. AssertBufferInvariants();
  637. if (_writePos > 0)
  638. {
  639. FlushWriteBuffer();
  640. }
  641. else if (_readPos < _readLength && CanSeek)
  642. {
  643. FlushReadBuffer();
  644. }
  645. }
  646. /// <summary>Dumps any read data in the buffer and rewinds our position in the stream, accordingly, as necessary.</summary>
  647. private void FlushReadBuffer()
  648. {
  649. // Reading is done by blocks from the file, but someone could read
  650. // 1 byte from the buffer then write. At that point, the OS's file
  651. // pointer is out of sync with the stream's position. All write
  652. // functions should call this function to preserve the position in the file.
  653. AssertBufferInvariants();
  654. Debug.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushReadBuffer!");
  655. int rewind = _readPos - _readLength;
  656. if (rewind != 0)
  657. {
  658. Debug.Assert(CanSeek, "FileStream will lose buffered read data now.");
  659. SeekCore(_fileHandle, rewind, SeekOrigin.Current);
  660. }
  661. _readPos = _readLength = 0;
  662. }
  663. /// <summary>
  664. /// Reads a byte from the file stream. Returns the byte cast to an int
  665. /// or -1 if reading from the end of the stream.
  666. /// </summary>
  667. public override int ReadByte()
  668. {
  669. PrepareForReading();
  670. byte[] buffer = GetBuffer();
  671. if (_readPos == _readLength)
  672. {
  673. FlushWriteBuffer();
  674. _readLength = FillReadBufferForReadByte();
  675. _readPos = 0;
  676. if (_readLength == 0)
  677. {
  678. return -1;
  679. }
  680. }
  681. return buffer[_readPos++];
  682. }
  683. /// <summary>
  684. /// Writes a byte to the current position in the stream and advances the position
  685. /// within the stream by one byte.
  686. /// </summary>
  687. /// <param name="value">The byte to write to the stream.</param>
  688. public override void WriteByte(byte value)
  689. {
  690. PrepareForWriting();
  691. // Flush the write buffer if it's full
  692. if (_writePos == _bufferLength)
  693. FlushWriteBufferForWriteByte();
  694. // We now have space in the buffer. Store the byte.
  695. GetBuffer()[_writePos++] = value;
  696. }
  697. /// <summary>
  698. /// Validates that we're ready to write to the stream,
  699. /// including flushing a read buffer if necessary.
  700. /// </summary>
  701. private void PrepareForWriting()
  702. {
  703. if (_fileHandle.IsClosed)
  704. throw Error.GetFileNotOpen();
  705. // Make sure we're good to write. We only need to do this if there's nothing already
  706. // in our write buffer, since if there is something in the buffer, we've already done
  707. // this checking and flushing.
  708. if (_writePos == 0)
  709. {
  710. if (!CanWrite) throw Error.GetWriteNotSupported();
  711. FlushReadBuffer();
  712. Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
  713. }
  714. }
  715. ~FileStream()
  716. {
  717. // Preserved for compatibility since FileStream has defined a
  718. // finalizer in past releases and derived classes may depend
  719. // on Dispose(false) call.
  720. Dispose(false);
  721. }
  722. public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback callback, object? state)
  723. {
  724. if (array == null)
  725. throw new ArgumentNullException(nameof(array));
  726. if (offset < 0)
  727. throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
  728. if (numBytes < 0)
  729. throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
  730. if (array.Length - offset < numBytes)
  731. throw new ArgumentException(SR.Argument_InvalidOffLen);
  732. if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
  733. if (!CanRead) throw new NotSupportedException(SR.NotSupported_UnreadableStream);
  734. if (!IsAsync)
  735. return base.BeginRead(array, offset, numBytes, callback, state);
  736. else
  737. return TaskToApm.Begin(ReadAsyncTask(array, offset, numBytes, CancellationToken.None), callback, state);
  738. }
  739. public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback callback, object? state)
  740. {
  741. if (array == null)
  742. throw new ArgumentNullException(nameof(array));
  743. if (offset < 0)
  744. throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
  745. if (numBytes < 0)
  746. throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
  747. if (array.Length - offset < numBytes)
  748. throw new ArgumentException(SR.Argument_InvalidOffLen);
  749. if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
  750. if (!CanWrite) throw new NotSupportedException(SR.NotSupported_UnwritableStream);
  751. if (!IsAsync)
  752. return base.BeginWrite(array, offset, numBytes, callback, state);
  753. else
  754. return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, numBytes), CancellationToken.None).AsTask(), callback, state);
  755. }
  756. public override int EndRead(IAsyncResult asyncResult)
  757. {
  758. if (asyncResult == null)
  759. throw new ArgumentNullException(nameof(asyncResult));
  760. if (!IsAsync)
  761. return base.EndRead(asyncResult);
  762. else
  763. return TaskToApm.End<int>(asyncResult);
  764. }
  765. public override void EndWrite(IAsyncResult asyncResult)
  766. {
  767. if (asyncResult == null)
  768. throw new ArgumentNullException(nameof(asyncResult));
  769. if (!IsAsync)
  770. base.EndWrite(asyncResult);
  771. else
  772. TaskToApm.End(asyncResult);
  773. }
  774. }
  775. }