mirror of https://github.com/dahall/Vanara.git
804 lines
29 KiB
C#
804 lines
29 KiB
C#
#if NET20 || NET35
|
|
using System.Diagnostics.Contracts;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Vanara.Extensions;
|
|
|
|
namespace System.IO
|
|
{
|
|
/// <summary>Provides access to unmanaged blocks of memory from managed code.</summary>
|
|
/// <seealso cref="System.IO.Stream"/>
|
|
public class UnmanagedMemoryStream : Stream
|
|
{
|
|
internal bool _isOpen;
|
|
private FileAccess _access;
|
|
[SecurityCritical]
|
|
private SafeBuffer _buffer;
|
|
private long _capacity;
|
|
[NonSerialized]
|
|
private Task<int> _lastReadTask;
|
|
private long _length;
|
|
[SecurityCritical]
|
|
private unsafe byte* _mem;
|
|
private long _offset;
|
|
private long _position;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class in a safe buffer with a specified offset and length..
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to contain the unmanaged memory stream.</param>
|
|
/// <param name="offset">The byte position in the buffer at which to start the unmanaged memory stream.</param>
|
|
/// <param name="length">The length of the unmanaged memory stream.</param>
|
|
[SecuritySafeCritical]
|
|
public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length) => Initialize(buffer, offset, length, FileAccess.Read, false);
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class in a safe buffer with a specified offset, length, and
|
|
/// file access.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to contain the unmanaged memory stream.</param>
|
|
/// <param name="offset">The byte position in the buffer at which to start the unmanaged memory stream.</param>
|
|
/// <param name="length">The length of the unmanaged memory stream.</param>
|
|
/// <param name="access">The mode of file access to the unmanaged memory stream.</param>
|
|
[SecuritySafeCritical]
|
|
public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access) => Initialize(buffer, offset, length, access, false);
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class using the specified location and memory length.
|
|
/// </summary>
|
|
/// <param name="pointer">A pointer to an unmanaged memory location.</param>
|
|
/// <param name="length">The length of the memory to use.</param>
|
|
[SecurityCritical]
|
|
public unsafe UnmanagedMemoryStream(byte* pointer, long length) => Initialize(pointer, length, length, FileAccess.Read, false);
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class using the specified location, memory length, total
|
|
/// amount of memory, and file access values.
|
|
/// </summary>
|
|
/// <param name="pointer">A pointer to an unmanaged memory location.</param>
|
|
/// <param name="length">The length of the memory to use.</param>
|
|
/// <param name="capacity">The total amount of memory assigned to the stream.</param>
|
|
/// <param name="access">One of the <see cref="FileAccess"/> values.</param>
|
|
[SecurityCritical]
|
|
public unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access) => Initialize(pointer, length, capacity, access, false);
|
|
|
|
[SecurityCritical]
|
|
internal UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck) => Initialize(buffer, offset, length, access, skipSecurityCheck);
|
|
|
|
[SecurityCritical]
|
|
internal unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck) => Initialize(pointer, length, capacity, access, skipSecurityCheck);
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class.</summary>
|
|
[SecuritySafeCritical]
|
|
protected UnmanagedMemoryStream()
|
|
{
|
|
unsafe
|
|
{
|
|
_mem = null;
|
|
}
|
|
_isOpen = false;
|
|
}
|
|
|
|
/// <summary>Gets a value indicating whether a stream supports reading.</summary>
|
|
public override bool CanRead
|
|
{
|
|
[Pure]
|
|
get => _isOpen && (_access & FileAccess.Read) != 0;
|
|
}
|
|
|
|
/// <summary>Gets a value indicating whether a stream supports seeking.</summary>
|
|
public override bool CanSeek
|
|
{
|
|
[Pure]
|
|
get => _isOpen;
|
|
}
|
|
|
|
/// <summary>Gets a value indicating whether a stream supports writing.</summary>
|
|
public override bool CanWrite
|
|
{
|
|
[Pure]
|
|
get => _isOpen && (_access & FileAccess.Write) != 0;
|
|
}
|
|
|
|
/// <summary>Gets the stream length (size) or the total amount of memory assigned to a stream (capacity).</summary>
|
|
/// <value>The size or capacity of the stream.</value>
|
|
public long Capacity
|
|
{
|
|
get
|
|
{
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
return _capacity;
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets the length of the data in a stream.</summary>
|
|
/// <value>The length of the data in the stream.</value>
|
|
public override long Length
|
|
{
|
|
get
|
|
{
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
return Interlocked.Read(ref _length);
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets or sets the position within the current stream.</summary>
|
|
public override long Position
|
|
{
|
|
get
|
|
{
|
|
if (!CanSeek) ErrorStreamIsClosed();
|
|
Contract.EndContractBlock();
|
|
return Interlocked.Read(ref _position);
|
|
}
|
|
[SecuritySafeCritical]
|
|
set
|
|
{
|
|
if (value < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(value), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
Contract.EndContractBlock();
|
|
if (!CanSeek) ErrorStreamIsClosed();
|
|
|
|
unsafe
|
|
{
|
|
// On 32 bit machines, ensure we don't wrap around.
|
|
if (value > int.MaxValue || _mem + value < _mem)
|
|
throw new ArgumentOutOfRangeException(nameof(value), ResourceHelper.GetString("ArgumentOutOfRange_StreamLength"));
|
|
}
|
|
|
|
Interlocked.Exchange(ref _position, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets or sets a byte pointer to a stream based on the current position in the stream.</summary>
|
|
/// <value>A byte pointer.</value>
|
|
public unsafe byte* PositionPointer
|
|
{
|
|
[SecurityCritical] // auto-generated_required
|
|
get
|
|
{
|
|
if (_buffer != null)
|
|
throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer"));
|
|
|
|
// Use a temp to avoid a race
|
|
var pos = Interlocked.Read(ref _position);
|
|
if (pos > _capacity)
|
|
throw new IndexOutOfRangeException(ResourceHelper.GetString("IndexOutOfRange_UMSPosition"));
|
|
var ptr = _mem + pos;
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
return ptr;
|
|
}
|
|
[SecurityCritical] // auto-generated_required
|
|
set
|
|
{
|
|
if (_buffer != null)
|
|
throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer"));
|
|
if (!_isOpen)
|
|
ErrorStreamIsClosed();
|
|
if (value < _mem)
|
|
throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin"));
|
|
|
|
Interlocked.Exchange(ref _position, value - _mem);
|
|
}
|
|
}
|
|
|
|
internal unsafe byte* Pointer
|
|
{
|
|
[SecurityCritical]
|
|
get => _buffer == null ? _mem : throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer"));
|
|
}
|
|
|
|
/// <summary>Clears all buffers for this stream and causes any buffered data to be written to the underlying device.</summary>
|
|
public override void Flush()
|
|
{
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors
|
|
/// cancellation requests.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.</param>
|
|
/// <returns>A task that represents the asynchronous flush operation.</returns>
|
|
[HostProtection(ExternalThreading = true)]
|
|
[ComVisible(false)]
|
|
public virtual Task FlushAsync(CancellationToken cancellationToken)
|
|
{
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return Task.FromCanceled(cancellationToken);
|
|
|
|
try
|
|
{
|
|
Flush();
|
|
return Task.CompletedTask;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Task.FromException(ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream
|
|
/// by the number of bytes read.
|
|
/// </summary>
|
|
/// <param name="buffer">
|
|
/// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between
|
|
/// <paramref name="offset"/> and ( <paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the
|
|
/// current source.
|
|
/// </param>
|
|
/// <param name="offset">
|
|
/// The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.
|
|
/// </param>
|
|
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
|
/// <returns>
|
|
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not
|
|
/// currently available, or zero (0) if the end of the stream has been reached.
|
|
/// </returns>
|
|
[SecuritySafeCritical]
|
|
public override int Read([In, Out] byte[] buffer, int offset, int count)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer"));
|
|
if (offset < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (buffer.Length - offset < count)
|
|
throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen"));
|
|
Contract.EndContractBlock(); // Keep this in sync with contract validation in ReadAsync
|
|
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
if (!CanRead) ErrorReadNotSupported();
|
|
|
|
// Use a local variable to avoid a race where another thread changes our position after we decide we can read some bytes.
|
|
var pos = Interlocked.Read(ref _position);
|
|
var len = Interlocked.Read(ref _length);
|
|
var n = len - pos;
|
|
if (n > count)
|
|
n = count;
|
|
if (n <= 0)
|
|
return 0;
|
|
|
|
var nInt = (int)n; // Safe because n <= count, which is an Int32
|
|
if (nInt < 0)
|
|
nInt = 0; // _position could be beyond EOF
|
|
Contract.Assert(pos + nInt >= 0, "_position + n >= 0"); // len is less than 2^63 -1.
|
|
|
|
if (_buffer != null)
|
|
{
|
|
unsafe
|
|
{
|
|
byte* pointer = null;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
_buffer.AcquirePointer(ref pointer);
|
|
BufferMemcpy(buffer, offset, pointer + pos + _offset, 0, nInt);
|
|
}
|
|
finally
|
|
{
|
|
if (pointer != null)
|
|
{
|
|
_buffer.ReleasePointer();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsafe
|
|
{
|
|
BufferMemcpy(buffer, offset, _mem + pos, 0, nInt);
|
|
}
|
|
}
|
|
Interlocked.Exchange(ref _position, pos + n);
|
|
return nInt;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously reads a sequence of bytes from the current stream and advances the position within the stream by the number of
|
|
/// bytes read.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to write the data into.</param>
|
|
/// <param name="offset">The byte offset in buffer at which to begin writing data from the stream.</param>
|
|
/// <param name="count">The maximum number of bytes to read.</param>
|
|
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous read operation. The value of the TResult parameter contains the total number of bytes
|
|
/// read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available
|
|
/// is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached.
|
|
/// </returns>
|
|
[HostProtection(ExternalThreading = true)]
|
|
[ComVisible(false)]
|
|
public virtual Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer"));
|
|
if (offset < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (buffer.Length - offset < count)
|
|
throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen"));
|
|
Contract.EndContractBlock(); // contract validation copied from Read(...)
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return Task.FromCanceled<int>(cancellationToken);
|
|
|
|
try
|
|
{
|
|
var n = Read(buffer, offset, count);
|
|
var t = _lastReadTask;
|
|
return t != null && t.Result == n ? t : _lastReadTask = Task.FromResult(n);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Contract.Assert(!(ex is OperationCanceledException));
|
|
return Task.FromException<int>(ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
|
|
/// </summary>
|
|
/// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
|
|
[SecuritySafeCritical]
|
|
public override int ReadByte()
|
|
{
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
if (!CanRead) ErrorReadNotSupported();
|
|
|
|
var pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
|
|
var len = Interlocked.Read(ref _length);
|
|
if (pos >= len)
|
|
return -1;
|
|
Interlocked.Exchange(ref _position, pos + 1);
|
|
int result;
|
|
if (_buffer != null)
|
|
{
|
|
unsafe
|
|
{
|
|
byte* pointer = null;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
_buffer.AcquirePointer(ref pointer);
|
|
result = *(pointer + pos + _offset);
|
|
}
|
|
finally
|
|
{
|
|
if (pointer != null)
|
|
{
|
|
_buffer.ReleasePointer();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsafe
|
|
{
|
|
result = _mem[pos];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>When overridden in a derived class, sets the position within the current stream.</summary>
|
|
/// <param name="offset">A byte offset relative to the origin parameter.</param>
|
|
/// <param name="loc">A value of type SeekOrigin indicating the reference point used to obtain the new position.</param>
|
|
/// <returns>The new position within the current stream.</returns>
|
|
public override long Seek(long offset, SeekOrigin loc)
|
|
{
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
switch (loc)
|
|
{
|
|
case SeekOrigin.Begin:
|
|
if (offset < 0)
|
|
throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin"));
|
|
Interlocked.Exchange(ref _position, offset);
|
|
break;
|
|
|
|
case SeekOrigin.Current:
|
|
var pos = Interlocked.Read(ref _position);
|
|
if (offset + pos < 0)
|
|
throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin"));
|
|
Interlocked.Exchange(ref _position, offset + pos);
|
|
break;
|
|
|
|
case SeekOrigin.End:
|
|
var len = Interlocked.Read(ref _length);
|
|
if (len + offset < 0)
|
|
throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin"));
|
|
Interlocked.Exchange(ref _position, len + offset);
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidSeekOrigin"));
|
|
}
|
|
|
|
var finalPos = Interlocked.Read(ref _position);
|
|
Contract.Assert(finalPos >= 0, "_position >= 0");
|
|
return finalPos;
|
|
}
|
|
|
|
/// <summary>When overridden in a derived class, sets the length of the current stream.</summary>
|
|
/// <param name="value">The desired length of the current stream in bytes.</param>
|
|
[SecuritySafeCritical]
|
|
public override void SetLength(long value)
|
|
{
|
|
if (value < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(value), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
Contract.EndContractBlock();
|
|
if (_buffer != null)
|
|
throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer"));
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
if (!CanWrite) ErrorWriteNotSupported();
|
|
|
|
if (value > _capacity)
|
|
throw new IOException(ResourceHelper.GetString("IO.IO_FixedCapacity"));
|
|
|
|
var pos = Interlocked.Read(ref _position);
|
|
var len = Interlocked.Read(ref _length);
|
|
if (value > len)
|
|
{
|
|
unsafe
|
|
{
|
|
BufferZeroMemory(_mem + len, value - len);
|
|
}
|
|
}
|
|
Interlocked.Exchange(ref _length, value);
|
|
if (pos > value)
|
|
{
|
|
Interlocked.Exchange(ref _position, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within
|
|
/// this stream by the number of bytes written.
|
|
/// </summary>
|
|
/// <param name="buffer">
|
|
/// An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.
|
|
/// </param>
|
|
/// <param name="offset">
|
|
/// The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.
|
|
/// </param>
|
|
/// <param name="count">The number of bytes to be written to the current stream.</param>
|
|
[SecuritySafeCritical]
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer"));
|
|
if (offset < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (buffer.Length - offset < count)
|
|
throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen"));
|
|
Contract.EndContractBlock(); // Keep contract validation in sync with WriteAsync(..)
|
|
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
if (!CanWrite) ErrorWriteNotSupported();
|
|
|
|
var pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
|
|
var len = Interlocked.Read(ref _length);
|
|
var n = pos + count;
|
|
// Check for overflow
|
|
if (n < 0)
|
|
throw new IOException(ResourceHelper.GetString("IO.IO_StreamTooLong"));
|
|
|
|
if (n > _capacity)
|
|
{
|
|
throw new NotSupportedException(ResourceHelper.GetString("IO.IO_FixedCapacity"));
|
|
}
|
|
|
|
if (_buffer == null)
|
|
{
|
|
// Check to see whether we are now expanding the stream and must zero any memory in the middle.
|
|
if (pos > len)
|
|
{
|
|
unsafe
|
|
{
|
|
BufferZeroMemory(_mem + len, pos - len);
|
|
}
|
|
}
|
|
|
|
// set length after zeroing memory to avoid race condition of accessing unzeroed memory
|
|
if (n > len)
|
|
{
|
|
Interlocked.Exchange(ref _length, n);
|
|
}
|
|
}
|
|
|
|
if (_buffer != null)
|
|
{
|
|
var bytesLeft = _capacity - pos;
|
|
if (bytesLeft < count)
|
|
{
|
|
throw new ArgumentException(ResourceHelper.GetString("Arg_BufferTooSmall"));
|
|
}
|
|
|
|
unsafe
|
|
{
|
|
byte* pointer = null;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
_buffer.AcquirePointer(ref pointer);
|
|
BufferMemcpy(pointer + pos + _offset, 0, buffer, offset, count);
|
|
}
|
|
finally
|
|
{
|
|
if (pointer != null)
|
|
{
|
|
_buffer.ReleasePointer();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsafe
|
|
{
|
|
BufferMemcpy(_mem + pos, 0, buffer, offset, count);
|
|
}
|
|
}
|
|
Interlocked.Exchange(ref _position, n);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number
|
|
/// of bytes written, and monitors cancellation requests.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to write data from.</param>
|
|
/// <param name="offset">The zero-based byte offset in buffer from which to begin copying bytes to the stream.</param>
|
|
/// <param name="count">The maximum number of bytes to write.</param>
|
|
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.</param>
|
|
/// <returns>A task that represents the asynchronous write operation.</returns>
|
|
[HostProtection(ExternalThreading = true)]
|
|
[ComVisible(false)]
|
|
public virtual Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer"));
|
|
if (offset < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (buffer.Length - offset < count)
|
|
throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen"));
|
|
Contract.EndContractBlock(); // contract validation copied from Write(..)
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return Task.FromCanceled(cancellationToken);
|
|
|
|
try
|
|
{
|
|
Write(buffer, offset, count);
|
|
return Task.CompletedTask;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Contract.Assert(!(ex is OperationCanceledException));
|
|
return Task.FromException<int>(ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>Writes a byte to the current position in the stream and advances the position within the stream by one byte.</summary>
|
|
/// <param name="value">The byte to write to the stream.</param>
|
|
[SecuritySafeCritical]
|
|
public override void WriteByte(byte value)
|
|
{
|
|
if (!_isOpen) ErrorStreamIsClosed();
|
|
if (!CanWrite) ErrorWriteNotSupported();
|
|
|
|
var pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
|
|
var len = Interlocked.Read(ref _length);
|
|
var n = pos + 1;
|
|
if (pos >= len)
|
|
{
|
|
// Check for overflow
|
|
if (n < 0)
|
|
throw new IOException(ResourceHelper.GetString("IO.IO_StreamTooLong"));
|
|
|
|
if (n > _capacity)
|
|
throw new NotSupportedException(ResourceHelper.GetString("IO.IO_FixedCapacity"));
|
|
|
|
// Check to see whether we are now expanding the stream and must zero any memory in the middle. don't do if created from SafeBuffer
|
|
if (_buffer == null)
|
|
{
|
|
if (pos > len)
|
|
{
|
|
unsafe
|
|
{
|
|
BufferZeroMemory(_mem + len, pos - len);
|
|
}
|
|
}
|
|
|
|
// set length after zeroing memory to avoid race condition of accessing unzeroed memory
|
|
Interlocked.Exchange(ref _length, n);
|
|
}
|
|
}
|
|
|
|
if (_buffer != null)
|
|
{
|
|
unsafe
|
|
{
|
|
byte* pointer = null;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
_buffer.AcquirePointer(ref pointer);
|
|
*(pointer + pos + _offset) = value;
|
|
}
|
|
finally
|
|
{
|
|
if (pointer != null)
|
|
{
|
|
_buffer.ReleasePointer();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsafe
|
|
{
|
|
_mem[pos] = value;
|
|
}
|
|
}
|
|
Interlocked.Exchange(ref _position, n);
|
|
}
|
|
|
|
[SecurityCritical]
|
|
internal void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck)
|
|
{
|
|
if (buffer == null)
|
|
throw new ArgumentNullException(nameof(buffer));
|
|
if (offset < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (length < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(length), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (buffer.ByteLength < (ulong)(offset + length))
|
|
throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidSafeBufferOffLen"));
|
|
if (access < FileAccess.Read || access > FileAccess.ReadWrite)
|
|
throw new ArgumentOutOfRangeException(nameof(access));
|
|
Contract.EndContractBlock();
|
|
|
|
if (_isOpen)
|
|
throw new InvalidOperationException(ResourceHelper.GetString("InvalidOperation_CalledTwice"));
|
|
if (!skipSecurityCheck)
|
|
#pragma warning disable 618
|
|
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
|
|
#pragma warning restore 618
|
|
|
|
// check for wraparound
|
|
unsafe
|
|
{
|
|
byte* pointer = null;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
buffer.AcquirePointer(ref pointer);
|
|
if (pointer + offset + length < pointer)
|
|
{
|
|
throw new ArgumentException(ResourceHelper.GetString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround"));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (pointer != null)
|
|
{
|
|
buffer.ReleasePointer();
|
|
}
|
|
}
|
|
}
|
|
|
|
_offset = offset;
|
|
_buffer = buffer;
|
|
_length = length;
|
|
_capacity = length;
|
|
_access = access;
|
|
_isOpen = true;
|
|
}
|
|
|
|
[SecurityCritical]
|
|
internal unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck)
|
|
{
|
|
if (pointer == null)
|
|
throw new ArgumentNullException(nameof(pointer));
|
|
if (length < 0 || capacity < 0)
|
|
throw new ArgumentOutOfRangeException(length < 0 ? "length" : "capacity", ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum"));
|
|
if (length > capacity)
|
|
throw new ArgumentOutOfRangeException(nameof(length), ResourceHelper.GetString("ArgumentOutOfRange_LengthGreaterThanCapacity"));
|
|
Contract.EndContractBlock();
|
|
// Check for wraparound.
|
|
if ((byte*)((long)pointer + capacity) < pointer)
|
|
throw new ArgumentOutOfRangeException(nameof(capacity), ResourceHelper.GetString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround"));
|
|
if (access < FileAccess.Read || access > FileAccess.ReadWrite)
|
|
throw new ArgumentOutOfRangeException(nameof(access), ResourceHelper.GetString("ArgumentOutOfRange_Enum"));
|
|
if (_isOpen)
|
|
throw new InvalidOperationException(ResourceHelper.GetString("InvalidOperation_CalledTwice"));
|
|
|
|
if (!skipSecurityCheck)
|
|
#pragma warning disable 618
|
|
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
|
|
#pragma warning restore 618
|
|
|
|
_mem = pointer;
|
|
_offset = 0;
|
|
_length = length;
|
|
_capacity = capacity;
|
|
_access = access;
|
|
_isOpen = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream"/> and optionally releases the managed resources.
|
|
/// </summary>
|
|
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
|
|
[SecuritySafeCritical]
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
_isOpen = false;
|
|
unsafe { _mem = null; }
|
|
|
|
// Stream allocates WaitHandles for async calls. So for correctness call base.Dispose(disposing) for better perf, avoiding
|
|
// waiting for the finalizers to run on those types.
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the UnmanagedMemoryStream class in a safe buffer with a specified offset, length, and file access.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to contain the unmanaged memory stream.</param>
|
|
/// <param name="offset">The byte position in the buffer at which to start the unmanaged memory stream.</param>
|
|
/// <param name="length">The length of the unmanaged memory stream.</param>
|
|
/// <param name="access">The mode of file access to the unmanaged memory stream.</param>
|
|
[SecuritySafeCritical]
|
|
protected void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access) => Initialize(buffer, offset, length, access, false);
|
|
|
|
/// <summary>Initializes a new instance of the UnmanagedMemoryStream class by using a pointer to an unmanaged memory location.</summary>
|
|
/// <param name="pointer">A pointer to an unmanaged memory location.</param>
|
|
/// <param name="length">The length of the memory to use.</param>
|
|
/// <param name="capacity">The total amount of memory assigned to the stream.</param>
|
|
/// <param name="access">One of the <see cref="FileAccess"/> values.</param>
|
|
[SecurityCritical]
|
|
protected unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access) => Initialize(pointer, length, capacity, access, false);
|
|
|
|
[SecurityCritical]
|
|
private static unsafe void BufferMemcpy(byte[] dest, int destIndex, byte* src, int srcIndex, int len)
|
|
{
|
|
Contract.Assert(srcIndex >= 0 && destIndex >= 0 && len >= 0, "Index and length must be non-negative!");
|
|
Contract.Assert(dest.Length - destIndex >= len, "not enough bytes in dest");
|
|
if (len == 0)
|
|
return;
|
|
Marshal.Copy((IntPtr)src, dest, srcIndex, len);
|
|
}
|
|
|
|
[SecurityCritical]
|
|
private static unsafe void BufferMemcpy(byte* dest, int destIndex, byte[] src, int srcIndex, int len)
|
|
{
|
|
Contract.Assert(srcIndex >= 0 && destIndex >= 0 && len >= 0, "Index and length must be non-negative!");
|
|
Contract.Assert(src.Length - srcIndex >= len, "not enough bytes in src");
|
|
if (len == 0)
|
|
return;
|
|
Marshal.Copy(src, srcIndex, (IntPtr)dest, len);
|
|
}
|
|
|
|
private static unsafe void BufferZeroMemory(byte* src, long len) => ((IntPtr)src).FillMemory(0, len);
|
|
|
|
private static void ErrorReadNotSupported() => throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UnreadableStream"));
|
|
|
|
private static void ErrorStreamIsClosed() => throw new ObjectDisposedException(null, ResourceHelper.GetString("ObjectDisposed_StreamClosed"));
|
|
|
|
private static void ErrorWriteNotSupported() => throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UnwritableStream"));
|
|
}
|
|
}
|
|
#endif |