FileStream.Unix.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  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 Microsoft.Win32.SafeHandles;
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. namespace System.IO
  10. {
  11. /// <summary>Provides an implementation of a file stream for Unix files.</summary>
  12. public partial class FileStream : Stream
  13. {
  14. /// <summary>File mode.</summary>
  15. private FileMode _mode;
  16. /// <summary>Advanced options requested when opening the file.</summary>
  17. private FileOptions _options;
  18. /// <summary>If the file was opened with FileMode.Append, the length of the file when opened; otherwise, -1.</summary>
  19. private long _appendStart = -1;
  20. /// <summary>
  21. /// Extra state used by the file stream when _useAsyncIO is true. This includes
  22. /// the semaphore used to serialize all operation, the buffer/offset/count provided by the
  23. /// caller for ReadAsync/WriteAsync operations, and the last successful task returned
  24. /// synchronously from ReadAsync which can be reused if the count matches the next request.
  25. /// Only initialized when <see cref="_useAsyncIO"/> is true.
  26. /// </summary>
  27. private AsyncState _asyncState;
  28. /// <summary>Lazily-initialized value for whether the file supports seeking.</summary>
  29. private bool? _canSeek;
  30. private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
  31. {
  32. // FileStream performs most of the general argument validation. We can assume here that the arguments
  33. // are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.)
  34. // Store the arguments
  35. _mode = mode;
  36. _options = options;
  37. if (_useAsyncIO)
  38. _asyncState = new AsyncState();
  39. // Translate the arguments into arguments for an open call.
  40. Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, _access, share, options);
  41. // If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and
  42. // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
  43. // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
  44. // actual permissions will typically be less than what we select here.
  45. const Interop.Sys.Permissions OpenPermissions =
  46. Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
  47. Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
  48. Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
  49. // Open the file and store the safe handle.
  50. return SafeFileHandle.Open(_path, openFlags, (int)OpenPermissions);
  51. }
  52. private static bool GetDefaultIsAsync(SafeFileHandle handle) => handle.IsAsync ?? DefaultIsAsync;
  53. /// <summary>Initializes a stream for reading or writing a Unix file.</summary>
  54. /// <param name="mode">How the file should be opened.</param>
  55. /// <param name="share">What other access to the file should be allowed. This is currently ignored.</param>
  56. private void Init(FileMode mode, FileShare share, string originalPath)
  57. {
  58. _fileHandle.IsAsync = _useAsyncIO;
  59. // Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive
  60. // lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory,
  61. // and not atomic with file opening, it's better than nothing.
  62. Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
  63. if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
  64. {
  65. // The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
  66. // else and we would block trying to access it. Other errors, such as ENOTSUP (locking isn't supported) or
  67. // EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
  68. // given again that this is only advisory / best-effort.
  69. Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
  70. if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
  71. {
  72. throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
  73. }
  74. }
  75. // These provide hints around how the file will be accessed. Specifying both RandomAccess
  76. // and Sequential together doesn't make sense as they are two competing options on the same spectrum,
  77. // so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
  78. Interop.Sys.FileAdvice fadv =
  79. (_options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
  80. (_options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
  81. 0;
  82. if (fadv != 0)
  83. {
  84. CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv),
  85. ignoreNotSupported: true); // just a hint.
  86. }
  87. if (_mode == FileMode.Append)
  88. {
  89. // Jump to the end of the file if opened as Append.
  90. _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End);
  91. }
  92. else if (mode == FileMode.Create || mode == FileMode.Truncate)
  93. {
  94. // Truncate the file now if the file mode requires it. This ensures that the file only will be truncated
  95. // if opened successfully.
  96. if (Interop.Sys.FTruncate(_fileHandle, 0) < 0)
  97. {
  98. Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
  99. if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL)
  100. {
  101. // We know the file descriptor is valid and we know the size argument to FTruncate is correct,
  102. // so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be
  103. // truncated. Ignore the error in such cases; in all others, throw.
  104. throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
  105. }
  106. }
  107. }
  108. }
  109. /// <summary>Initializes a stream from an already open file handle (file descriptor).</summary>
  110. private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAsyncIO)
  111. {
  112. if (useAsyncIO)
  113. _asyncState = new AsyncState();
  114. if (CanSeekCore(handle)) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor
  115. SeekCore(handle, 0, SeekOrigin.Current);
  116. }
  117. /// <summary>Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.</summary>
  118. /// <param name="mode">The FileMode provided to the stream's constructor.</param>
  119. /// <param name="access">The FileAccess provided to the stream's constructor</param>
  120. /// <param name="share">The FileShare provided to the stream's constructor</param>
  121. /// <param name="options">The FileOptions provided to the stream's constructor</param>
  122. /// <returns>The flags value to be passed to the open system call.</returns>
  123. private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileShare share, FileOptions options)
  124. {
  125. // Translate FileMode. Most of the values map cleanly to one or more options for open.
  126. Interop.Sys.OpenFlags flags = default(Interop.Sys.OpenFlags);
  127. switch (mode)
  128. {
  129. default:
  130. case FileMode.Open: // Open maps to the default behavior for open(...). No flags needed.
  131. case FileMode.Truncate: // We truncate the file after getting the lock
  132. break;
  133. case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
  134. case FileMode.OpenOrCreate:
  135. case FileMode.Create: // We truncate the file after getting the lock
  136. flags |= Interop.Sys.OpenFlags.O_CREAT;
  137. break;
  138. case FileMode.CreateNew:
  139. flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
  140. break;
  141. }
  142. // Translate FileAccess. All possible values map cleanly to corresponding values for open.
  143. switch (access)
  144. {
  145. case FileAccess.Read:
  146. flags |= Interop.Sys.OpenFlags.O_RDONLY;
  147. break;
  148. case FileAccess.ReadWrite:
  149. flags |= Interop.Sys.OpenFlags.O_RDWR;
  150. break;
  151. case FileAccess.Write:
  152. flags |= Interop.Sys.OpenFlags.O_WRONLY;
  153. break;
  154. }
  155. // Handle Inheritable, other FileShare flags are handled by Init
  156. if ((share & FileShare.Inheritable) == 0)
  157. {
  158. flags |= Interop.Sys.OpenFlags.O_CLOEXEC;
  159. }
  160. // Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
  161. // - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
  162. // - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
  163. // - Encrypted: No equivalent on Unix and is ignored
  164. // - RandomAccess: Implemented after open if posix_fadvise is available
  165. // - SequentialScan: Implemented after open if posix_fadvise is available
  166. // - WriteThrough: Handled here
  167. if ((options & FileOptions.WriteThrough) != 0)
  168. {
  169. flags |= Interop.Sys.OpenFlags.O_SYNC;
  170. }
  171. return flags;
  172. }
  173. /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
  174. public override bool CanSeek => CanSeekCore(_fileHandle);
  175. /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
  176. /// <remarks>
  177. /// Separated out of CanSeek to enable making non-virtual call to this logic.
  178. /// We also pass in the file handle to allow the constructor to use this before it stashes the handle.
  179. /// </remarks>
  180. private bool CanSeekCore(SafeFileHandle fileHandle)
  181. {
  182. if (fileHandle.IsClosed)
  183. {
  184. return false;
  185. }
  186. if (!_canSeek.HasValue)
  187. {
  188. // Lazily-initialize whether we're able to seek, tested by seeking to our current location.
  189. _canSeek = Interop.Sys.LSeek(fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;
  190. }
  191. return _canSeek.GetValueOrDefault();
  192. }
  193. private long GetLengthInternal()
  194. {
  195. // Get the length of the file as reported by the OS
  196. Interop.Sys.FileStatus status;
  197. CheckFileCall(Interop.Sys.FStat(_fileHandle, out status));
  198. long length = status.Size;
  199. // But we may have buffered some data to be written that puts our length
  200. // beyond what the OS is aware of. Update accordingly.
  201. if (_writePos > 0 && _filePosition + _writePos > length)
  202. {
  203. length = _writePos + _filePosition;
  204. }
  205. return length;
  206. }
  207. /// <summary>Releases the unmanaged resources used by the stream.</summary>
  208. /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
  209. protected override void Dispose(bool disposing)
  210. {
  211. try
  212. {
  213. if (_fileHandle != null && !_fileHandle.IsClosed)
  214. {
  215. // Flush any remaining data in the file
  216. try
  217. {
  218. FlushWriteBuffer();
  219. }
  220. catch (Exception e) when (IsIoRelatedException(e) && !disposing)
  221. {
  222. // On finalization, ignore failures from trying to flush the write buffer,
  223. // e.g. if this stream is wrapping a pipe and the pipe is now broken.
  224. }
  225. // If DeleteOnClose was requested when constructed, delete the file now.
  226. // (Unix doesn't directly support DeleteOnClose, so we mimic it here.)
  227. if (_path != null && (_options & FileOptions.DeleteOnClose) != 0)
  228. {
  229. // Since we still have the file open, this will end up deleting
  230. // it (assuming we're the only link to it) once it's closed, but the
  231. // name will be removed immediately.
  232. Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist
  233. }
  234. }
  235. }
  236. finally
  237. {
  238. if (_fileHandle != null && !_fileHandle.IsClosed)
  239. {
  240. _fileHandle.Dispose();
  241. }
  242. base.Dispose(disposing);
  243. }
  244. }
  245. public override ValueTask DisposeAsync()
  246. {
  247. // On Unix, we don't have any special support for async I/O, simply queueing writes
  248. // rather than doing them synchronously. As such, if we're "using async I/O" and we
  249. // have something to flush, queue the call to Dispose, so that we end up queueing whatever
  250. // write work happens to flush the buffer. Otherwise, just delegate to the base implementation,
  251. // which will synchronously invoke Dispose. We don't need to factor in the current type
  252. // as we're using the virtual Dispose either way, and therefore factoring in whatever
  253. // override may already exist on a derived type.
  254. if (_useAsyncIO && _writePos > 0)
  255. {
  256. return new ValueTask(Task.Factory.StartNew(s => ((FileStream)s).Dispose(), this,
  257. CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
  258. }
  259. return base.DisposeAsync();
  260. }
  261. /// <summary>Flushes the OS buffer. This does not flush the internal read/write buffer.</summary>
  262. private void FlushOSBuffer()
  263. {
  264. if (Interop.Sys.FSync(_fileHandle) < 0)
  265. {
  266. Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
  267. switch (errorInfo.Error)
  268. {
  269. case Interop.Error.EROFS:
  270. case Interop.Error.EINVAL:
  271. case Interop.Error.ENOTSUP:
  272. // Ignore failures due to the FileStream being bound to a special file that
  273. // doesn't support synchronization. In such cases there's nothing to flush.
  274. break;
  275. default:
  276. throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
  277. }
  278. }
  279. }
  280. private void FlushWriteBufferForWriteByte()
  281. {
  282. _asyncState?.Wait();
  283. try { FlushWriteBuffer(); }
  284. finally { _asyncState?.Release(); }
  285. }
  286. /// <summary>Writes any data in the write buffer to the underlying stream and resets the buffer.</summary>
  287. private void FlushWriteBuffer()
  288. {
  289. AssertBufferInvariants();
  290. if (_writePos > 0)
  291. {
  292. WriteNative(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
  293. _writePos = 0;
  294. }
  295. }
  296. /// <summary>Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device.</summary>
  297. /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
  298. /// <returns>A task that represents the asynchronous flush operation.</returns>
  299. private Task FlushAsyncInternal(CancellationToken cancellationToken)
  300. {
  301. if (cancellationToken.IsCancellationRequested)
  302. {
  303. return Task.FromCanceled(cancellationToken);
  304. }
  305. if (_fileHandle.IsClosed)
  306. {
  307. throw Error.GetFileNotOpen();
  308. }
  309. // As with Win32FileStream, flush the buffers synchronously to avoid race conditions.
  310. try
  311. {
  312. FlushInternalBuffer();
  313. }
  314. catch (Exception e)
  315. {
  316. return Task.FromException(e);
  317. }
  318. // We then separately flush to disk asynchronously. This is only
  319. // necessary if we support writing; otherwise, we're done.
  320. if (CanWrite)
  321. {
  322. return Task.Factory.StartNew(
  323. state => ((FileStream)state).FlushOSBuffer(),
  324. this,
  325. cancellationToken,
  326. TaskCreationOptions.DenyChildAttach,
  327. TaskScheduler.Default);
  328. }
  329. else
  330. {
  331. return Task.CompletedTask;
  332. }
  333. }
  334. /// <summary>Sets the length of this stream to the given value.</summary>
  335. /// <param name="value">The new length of the stream.</param>
  336. private void SetLengthInternal(long value)
  337. {
  338. FlushInternalBuffer();
  339. if (_appendStart != -1 && value < _appendStart)
  340. {
  341. throw new IOException(SR.IO_SetLengthAppendTruncate);
  342. }
  343. long origPos = _filePosition;
  344. VerifyOSHandlePosition();
  345. if (_filePosition != value)
  346. {
  347. SeekCore(_fileHandle, value, SeekOrigin.Begin);
  348. }
  349. CheckFileCall(Interop.Sys.FTruncate(_fileHandle, value));
  350. // Return file pointer to where it was before setting length
  351. if (origPos != value)
  352. {
  353. if (origPos < value)
  354. {
  355. SeekCore(_fileHandle, origPos, SeekOrigin.Begin);
  356. }
  357. else
  358. {
  359. SeekCore(_fileHandle, 0, SeekOrigin.End);
  360. }
  361. }
  362. }
  363. /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
  364. private int ReadSpan(Span<byte> destination)
  365. {
  366. PrepareForReading();
  367. // Are there any bytes available in the read buffer? If yes,
  368. // we can just return from the buffer. If the buffer is empty
  369. // or has no more available data in it, we can either refill it
  370. // (and then read from the buffer into the user's buffer) or
  371. // we can just go directly into the user's buffer, if they asked
  372. // for more data than we'd otherwise buffer.
  373. int numBytesAvailable = _readLength - _readPos;
  374. bool readFromOS = false;
  375. if (numBytesAvailable == 0)
  376. {
  377. // If we're not able to seek, then we're not able to rewind the stream (i.e. flushing
  378. // a read buffer), in which case we don't want to use a read buffer. Similarly, if
  379. // the user has asked for more data than we can buffer, we also want to skip the buffer.
  380. if (!CanSeek || (destination.Length >= _bufferLength))
  381. {
  382. // Read directly into the user's buffer
  383. _readPos = _readLength = 0;
  384. return ReadNative(destination);
  385. }
  386. else
  387. {
  388. // Read into our buffer.
  389. _readLength = numBytesAvailable = ReadNative(GetBuffer());
  390. _readPos = 0;
  391. if (numBytesAvailable == 0)
  392. {
  393. return 0;
  394. }
  395. // Note that we did an OS read as part of this Read, so that later
  396. // we don't try to do one again if what's in the buffer doesn't
  397. // meet the user's request.
  398. readFromOS = true;
  399. }
  400. }
  401. // Now that we know there's data in the buffer, read from it into the user's buffer.
  402. Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here");
  403. int bytesRead = Math.Min(numBytesAvailable, destination.Length);
  404. new Span<byte>(GetBuffer(), _readPos, bytesRead).CopyTo(destination);
  405. _readPos += bytesRead;
  406. // We may not have had enough data in the buffer to completely satisfy the user's request.
  407. // While Read doesn't require that we return as much data as the user requested (any amount
  408. // up to the requested count is fine), FileStream on Windows tries to do so by doing a
  409. // subsequent read from the file if we tried to satisfy the request with what was in the
  410. // buffer but the buffer contained less than the requested count. To be consistent with that
  411. // behavior, we do the same thing here on Unix. Note that we may still get less the requested
  412. // amount, as the OS may give us back fewer than we request, either due to reaching the end of
  413. // file, or due to its own whims.
  414. if (!readFromOS && bytesRead < destination.Length)
  415. {
  416. Debug.Assert(_readPos == _readLength, "bytesToRead should only be < destination.Length if numBytesAvailable < destination.Length");
  417. _readPos = _readLength = 0; // no data left in the read buffer
  418. bytesRead += ReadNative(destination.Slice(bytesRead));
  419. }
  420. return bytesRead;
  421. }
  422. /// <summary>Unbuffered, reads a block of bytes from the file handle into the given buffer.</summary>
  423. /// <param name="buffer">The buffer into which data from the file is read.</param>
  424. /// <returns>
  425. /// The total number of bytes read into the buffer. This might be less than the number of bytes requested
  426. /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
  427. /// </returns>
  428. private unsafe int ReadNative(Span<byte> buffer)
  429. {
  430. FlushWriteBuffer(); // we're about to read; dump the write buffer
  431. VerifyOSHandlePosition();
  432. int bytesRead;
  433. fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
  434. {
  435. bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr, buffer.Length));
  436. Debug.Assert(bytesRead <= buffer.Length);
  437. }
  438. _filePosition += bytesRead;
  439. return bytesRead;
  440. }
  441. /// <summary>
  442. /// Asynchronously reads a sequence of bytes from the current stream and advances
  443. /// the position within the stream by the number of bytes read.
  444. /// </summary>
  445. /// <param name="destination">The buffer to write the data into.</param>
  446. /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
  447. /// <param name="synchronousResult">If the operation completes synchronously, the number of bytes read.</param>
  448. /// <returns>A task that represents the asynchronous read operation.</returns>
  449. private Task<int> ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken, out int synchronousResult)
  450. {
  451. Debug.Assert(_useAsyncIO);
  452. if (!CanRead) // match Windows behavior; this gets thrown synchronously
  453. {
  454. throw Error.GetReadNotSupported();
  455. }
  456. // Serialize operations using the semaphore.
  457. Task waitTask = _asyncState.WaitAsync();
  458. // If we got ownership immediately, and if there's enough data in our buffer
  459. // to satisfy the full request of the caller, hand back the buffered data.
  460. // While it would be a legal implementation of the Read contract, we don't
  461. // hand back here less than the amount requested so as to match the behavior
  462. // in ReadCore that will make a native call to try to fulfill the remainder
  463. // of the request.
  464. if (waitTask.Status == TaskStatus.RanToCompletion)
  465. {
  466. int numBytesAvailable = _readLength - _readPos;
  467. if (numBytesAvailable >= destination.Length)
  468. {
  469. try
  470. {
  471. PrepareForReading();
  472. new Span<byte>(GetBuffer(), _readPos, destination.Length).CopyTo(destination.Span);
  473. _readPos += destination.Length;
  474. synchronousResult = destination.Length;
  475. return null;
  476. }
  477. catch (Exception exc)
  478. {
  479. synchronousResult = 0;
  480. return Task.FromException<int>(exc);
  481. }
  482. finally
  483. {
  484. _asyncState.Release();
  485. }
  486. }
  487. }
  488. // Otherwise, issue the whole request asynchronously.
  489. synchronousResult = 0;
  490. _asyncState.Memory = destination;
  491. return waitTask.ContinueWith((t, s) =>
  492. {
  493. // The options available on Unix for writing asynchronously to an arbitrary file
  494. // handle typically amount to just using another thread to do the synchronous write,
  495. // which is exactly what this implementation does. This does mean there are subtle
  496. // differences in certain FileStream behaviors between Windows and Unix when multiple
  497. // asynchronous operations are issued against the stream to execute concurrently; on
  498. // Unix the operations will be serialized due to the usage of a semaphore, but the
  499. // position /length information won't be updated until after the write has completed,
  500. // whereas on Windows it may happen before the write has completed.
  501. Debug.Assert(t.Status == TaskStatus.RanToCompletion);
  502. var thisRef = (FileStream)s;
  503. try
  504. {
  505. Memory<byte> memory = thisRef._asyncState.Memory;
  506. thisRef._asyncState.Memory = default(Memory<byte>);
  507. return thisRef.ReadSpan(memory.Span);
  508. }
  509. finally { thisRef._asyncState.Release(); }
  510. }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
  511. }
  512. /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary>
  513. private int FillReadBufferForReadByte()
  514. {
  515. _asyncState?.Wait();
  516. try { return ReadNative(_buffer); }
  517. finally { _asyncState?.Release(); }
  518. }
  519. /// <summary>Writes a block of bytes to the file stream.</summary>
  520. /// <param name="source">The buffer containing data to write to the stream.</param>
  521. private void WriteSpan(ReadOnlySpan<byte> source)
  522. {
  523. PrepareForWriting();
  524. // If no data is being written, nothing more to do.
  525. if (source.Length == 0)
  526. {
  527. return;
  528. }
  529. // If there's already data in our write buffer, then we need to go through
  530. // our buffer to ensure data isn't corrupted.
  531. if (_writePos > 0)
  532. {
  533. // If there's space remaining in the buffer, then copy as much as
  534. // we can from the user's buffer into ours.
  535. int spaceRemaining = _bufferLength - _writePos;
  536. if (spaceRemaining >= source.Length)
  537. {
  538. source.CopyTo(GetBuffer().AsSpan(_writePos));
  539. _writePos += source.Length;
  540. return;
  541. }
  542. else if (spaceRemaining > 0)
  543. {
  544. source.Slice(0, spaceRemaining).CopyTo(GetBuffer().AsSpan(_writePos));
  545. _writePos += spaceRemaining;
  546. source = source.Slice(spaceRemaining);
  547. }
  548. // At this point, the buffer is full, so flush it out.
  549. FlushWriteBuffer();
  550. }
  551. // Our buffer is now empty. If using the buffer would slow things down (because
  552. // the user's looking to write more data than we can store in the buffer),
  553. // skip the buffer. Otherwise, put the remaining data into the buffer.
  554. Debug.Assert(_writePos == 0);
  555. if (source.Length >= _bufferLength)
  556. {
  557. WriteNative(source);
  558. }
  559. else
  560. {
  561. source.CopyTo(new Span<byte>(GetBuffer()));
  562. _writePos = source.Length;
  563. }
  564. }
  565. /// <summary>Unbuffered, writes a block of bytes to the file stream.</summary>
  566. /// <param name="source">The buffer containing data to write to the stream.</param>
  567. private unsafe void WriteNative(ReadOnlySpan<byte> source)
  568. {
  569. VerifyOSHandlePosition();
  570. fixed (byte* bufPtr = &MemoryMarshal.GetReference(source))
  571. {
  572. int offset = 0;
  573. int count = source.Length;
  574. while (count > 0)
  575. {
  576. int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count));
  577. _filePosition += bytesWritten;
  578. offset += bytesWritten;
  579. count -= bytesWritten;
  580. }
  581. }
  582. }
  583. /// <summary>
  584. /// Asynchronously writes a sequence of bytes to the current stream, advances
  585. /// the current position within this stream by the number of bytes written, and
  586. /// monitors cancellation requests.
  587. /// </summary>
  588. /// <param name="source">The buffer to write data from.</param>
  589. /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
  590. /// <returns>A task that represents the asynchronous write operation.</returns>
  591. private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
  592. {
  593. Debug.Assert(_useAsyncIO);
  594. if (cancellationToken.IsCancellationRequested)
  595. return new ValueTask(Task.FromCanceled(cancellationToken));
  596. if (_fileHandle.IsClosed)
  597. throw Error.GetFileNotOpen();
  598. if (!CanWrite) // match Windows behavior; this gets thrown synchronously
  599. {
  600. throw Error.GetWriteNotSupported();
  601. }
  602. // Serialize operations using the semaphore.
  603. Task waitTask = _asyncState.WaitAsync();
  604. // If we got ownership immediately, and if there's enough space in our buffer
  605. // to buffer the entire write request, then do so and we're done.
  606. if (waitTask.Status == TaskStatus.RanToCompletion)
  607. {
  608. int spaceRemaining = _bufferLength - _writePos;
  609. if (spaceRemaining >= source.Length)
  610. {
  611. try
  612. {
  613. PrepareForWriting();
  614. source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length));
  615. _writePos += source.Length;
  616. return default;
  617. }
  618. catch (Exception exc)
  619. {
  620. return new ValueTask(Task.FromException(exc));
  621. }
  622. finally
  623. {
  624. _asyncState.Release();
  625. }
  626. }
  627. }
  628. // Otherwise, issue the whole request asynchronously.
  629. _asyncState.ReadOnlyMemory = source;
  630. return new ValueTask(waitTask.ContinueWith((t, s) =>
  631. {
  632. // The options available on Unix for writing asynchronously to an arbitrary file
  633. // handle typically amount to just using another thread to do the synchronous write,
  634. // which is exactly what this implementation does. This does mean there are subtle
  635. // differences in certain FileStream behaviors between Windows and Unix when multiple
  636. // asynchronous operations are issued against the stream to execute concurrently; on
  637. // Unix the operations will be serialized due to the usage of a semaphore, but the
  638. // position/length information won't be updated until after the write has completed,
  639. // whereas on Windows it may happen before the write has completed.
  640. Debug.Assert(t.Status == TaskStatus.RanToCompletion);
  641. var thisRef = (FileStream)s;
  642. try
  643. {
  644. ReadOnlyMemory<byte> readOnlyMemory = thisRef._asyncState.ReadOnlyMemory;
  645. thisRef._asyncState.ReadOnlyMemory = default(ReadOnlyMemory<byte>);
  646. thisRef.WriteSpan(readOnlyMemory.Span);
  647. }
  648. finally { thisRef._asyncState.Release(); }
  649. }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default));
  650. }
  651. /// <summary>Sets the current position of this stream to the given value.</summary>
  652. /// <param name="offset">The point relative to origin from which to begin seeking. </param>
  653. /// <param name="origin">
  654. /// Specifies the beginning, the end, or the current position as a reference
  655. /// point for offset, using a value of type SeekOrigin.
  656. /// </param>
  657. /// <returns>The new position in the stream.</returns>
  658. public override long Seek(long offset, SeekOrigin origin)
  659. {
  660. if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
  661. {
  662. throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
  663. }
  664. if (_fileHandle.IsClosed)
  665. {
  666. throw Error.GetFileNotOpen();
  667. }
  668. if (!CanSeek)
  669. {
  670. throw Error.GetSeekNotSupported();
  671. }
  672. VerifyOSHandlePosition();
  673. // Flush our write/read buffer. FlushWrite will output any write buffer we have and reset _bufferWritePos.
  674. // We don't call FlushRead, as that will do an unnecessary seek to rewind the read buffer, and since we're
  675. // about to seek and update our position, we can simply update the offset as necessary and reset our read
  676. // position and length to 0. (In the future, for some simple cases we could potentially add an optimization
  677. // here to just move data around in the buffer for short jumps, to avoid re-reading the data from disk.)
  678. FlushWriteBuffer();
  679. if (origin == SeekOrigin.Current)
  680. {
  681. offset -= (_readLength - _readPos);
  682. }
  683. _readPos = _readLength = 0;
  684. // Keep track of where we were, in case we're in append mode and need to verify
  685. long oldPos = 0;
  686. if (_appendStart >= 0)
  687. {
  688. oldPos = SeekCore(_fileHandle, 0, SeekOrigin.Current);
  689. }
  690. // Jump to the new location
  691. long pos = SeekCore(_fileHandle, offset, origin);
  692. // Prevent users from overwriting data in a file that was opened in append mode.
  693. if (_appendStart != -1 && pos < _appendStart)
  694. {
  695. SeekCore(_fileHandle, oldPos, SeekOrigin.Begin);
  696. throw new IOException(SR.IO_SeekAppendOverwrite);
  697. }
  698. // Return the new position
  699. return pos;
  700. }
  701. /// <summary>Sets the current position of this stream to the given value.</summary>
  702. /// <param name="offset">The point relative to origin from which to begin seeking. </param>
  703. /// <param name="origin">
  704. /// Specifies the beginning, the end, or the current position as a reference
  705. /// point for offset, using a value of type SeekOrigin.
  706. /// </param>
  707. /// <returns>The new position in the stream.</returns>
  708. private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin)
  709. {
  710. Debug.Assert(!fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeekCore(fileHandle))); // verify that we can seek, but only if CanSeek won't be a virtual call (which could happen in the ctor)
  711. Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End);
  712. long pos = CheckFileCall(Interop.Sys.LSeek(fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values
  713. _filePosition = pos;
  714. return pos;
  715. }
  716. private long CheckFileCall(long result, bool ignoreNotSupported = false)
  717. {
  718. if (result < 0)
  719. {
  720. Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
  721. if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
  722. {
  723. throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
  724. }
  725. }
  726. return result;
  727. }
  728. private int CheckFileCall(int result, bool ignoreNotSupported = false)
  729. {
  730. CheckFileCall((long)result, ignoreNotSupported);
  731. return result;
  732. }
  733. /// <summary>State used when the stream is in async mode.</summary>
  734. private sealed class AsyncState : SemaphoreSlim
  735. {
  736. internal ReadOnlyMemory<byte> ReadOnlyMemory;
  737. internal Memory<byte> Memory;
  738. /// <summary>Initialize the AsyncState.</summary>
  739. internal AsyncState() : base(initialCount: 1, maxCount: 1) { }
  740. }
  741. }
  742. }