FileStream.Unix.cs 38 KB

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