3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
8 ** Class: UnmanagedMemoryStream
10 ** <OWNER>[....]</OWNER>
12 ** Purpose: Create a stream over unmanaged memory, mostly
13 ** useful for memory-mapped files.
15 ** Date: October 20, 2000 (made public August 4, 2003)
17 ===========================================================*/
20 using System.Runtime.CompilerServices;
21 using System.Runtime.InteropServices;
22 using System.Security;
23 using System.Security.Permissions;
24 using System.Threading;
25 using System.Diagnostics.Contracts;
26 #if !FEATURE_PAL && FEATURE_ASYNC_IO || MONO
27 using System.Threading.Tasks;
28 #endif // !FEATURE_PAL && FEATURE_ASYNC_IO
34 * This class is used to access a contiguous block of memory, likely outside
35 * the GC heap (or pinned in place in the GC heap, but a MemoryStream may
36 * make more sense in those cases). It's great if you have a pointer and
37 * a length for a section of memory mapped in by someone else and you don't
38 * want to copy this into the GC heap. UnmanagedMemoryStream assumes these
41 * 1) All the memory in the specified block is readable or writable,
42 * depending on the values you pass to the constructor.
43 * 2) The lifetime of the block of memory is at least as long as the lifetime
44 * of the UnmanagedMemoryStream.
45 * 3) You clean up the memory when appropriate. The UnmanagedMemoryStream
46 * currently will do NOTHING to free this memory.
47 * 4) All calls to Write and WriteByte may not be threadsafe currently.
49 * It may become necessary to add in some sort of
50 * DeallocationMode enum, specifying whether we unmap a section of memory,
51 * call free, run a user-provided delegate to free the memory, etc etc.
52 * We'll suggest user write a subclass of UnmanagedMemoryStream that uses
53 * a SafeHandle subclass to hold onto the memory.
54 * Check for problems when using this in the negative parts of a
55 * process's address space. We may need to use unsigned longs internally
56 * and change the overflow detection logic.
58 * -----SECURITY MODEL AND SILVERLIGHT-----
59 * A few key notes about exposing UMS in silverlight:
60 * 1. No ctors are exposed to transparent code. This version of UMS only
61 * supports byte* (not SafeBuffer). Therefore, framework code can create
62 * a UMS and hand it to transparent code. Transparent code can use most
63 * operations on a UMS, but not operations that directly expose a
66 * 2. Scope of "unsafe" and non-CLS compliant operations reduced: The
67 * Whidbey version of this class has CLSCompliant(false) at the class
68 * level and unsafe modifiers at the method level. These were reduced to
69 * only where the unsafe operation is performed -- i.e. immediately
70 * around the pointer manipulation. Note that this brings UMS in line
71 * with recent changes in pu/clr to support SafeBuffer.
73 * 3. Currently, the only caller that creates a UMS is ResourceManager,
74 * which creates read-only UMSs, and therefore operations that can
75 * change the length will throw because write isn't supported. A
76 * conservative option would be to formalize the concept that _only_
77 * read-only UMSs can be creates, and enforce this by making WriteX and
78 * SetLength SecurityCritical. However, this is a violation of
79 * security inheritance rules, so we must keep these safe. The
80 * following notes make this acceptable for future use.
81 * a. a race condition in WriteX that could have allowed a thread to
82 * read from unzeroed memory was fixed
83 * b. memory region cannot be expanded beyond _capacity; in other
84 * words, a UMS creator is saying a writeable UMS is safe to
85 * write to anywhere in the memory range up to _capacity, specified
86 * in the ctor. Even if the caller doesn't specify a capacity, then
87 * length is used as the capacity.
89 public class UnmanagedMemoryStream : Stream
93 private const long UnmanagedMemStreamMaxLength = Int64.MaxValue;
95 [System.Security.SecurityCritical] // auto-generated
96 private SafeBuffer _buffer;
98 private unsafe byte* _mem;
100 private long _capacity;
101 private long _position;
102 private long _offset;
103 private FileAccess _access;
104 internal bool _isOpen;
105 #if !FEATURE_PAL && FEATURE_ASYNC_IO || MONO
107 private Task<Int32> _lastReadTask; // The last successful task returned from ReadAsync
108 #endif // FEATURE_PAL && FEATURE_ASYNC_IO
111 // Needed for subclasses that need to map a file, etc.
112 [System.Security.SecuritySafeCritical] // auto-generated
113 protected UnmanagedMemoryStream()
121 [System.Security.SecuritySafeCritical] // auto-generated
122 public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length) {
123 Initialize(buffer, offset, length, FileAccess.Read, false);
126 [System.Security.SecuritySafeCritical] // auto-generated
127 public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access) {
128 Initialize(buffer, offset, length, access, false);
131 // We must create one of these without doing a security check. This
132 // class is created while security is trying to start up. Plus, doing
133 // a Demand from Assembly.GetManifestResourceStream isn't useful.
134 [System.Security.SecurityCritical] // auto-generated
135 internal UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck) {
136 Initialize(buffer, offset, length, access, skipSecurityCheck);
139 [System.Security.SecuritySafeCritical] // auto-generated
140 protected void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access) {
141 Initialize(buffer, offset, length, access, false);
144 [System.Security.SecurityCritical] // auto-generated
145 internal void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck) {
146 if (buffer == null) {
147 throw new ArgumentNullException("buffer");
150 throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
153 throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
155 if (buffer.ByteLength < (ulong)(offset + length)) {
156 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSafeBufferOffLen"));
158 if (access < FileAccess.Read || access > FileAccess.ReadWrite) {
159 throw new ArgumentOutOfRangeException("access");
161 Contract.EndContractBlock();
164 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CalledTwice"));
167 if (!skipSecurityCheck) {
168 #pragma warning disable 618
169 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
170 #pragma warning restore 618
174 // check for wraparound
176 byte* pointer = null;
177 RuntimeHelpers.PrepareConstrainedRegions();
179 buffer.AcquirePointer(ref pointer);
180 if ( (pointer + offset + length) < pointer) {
181 throw new ArgumentException(Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround"));
185 if (pointer != null) {
186 buffer.ReleasePointer();
199 [System.Security.SecurityCritical] // auto-generated
200 [CLSCompliant(false)]
201 public unsafe UnmanagedMemoryStream(byte* pointer, long length)
203 Initialize(pointer, length, length, FileAccess.Read, false);
206 [System.Security.SecurityCritical] // auto-generated
207 [CLSCompliant(false)]
208 public unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access)
210 Initialize(pointer, length, capacity, access, false);
213 // We must create one of these without doing a security check. This
214 // class is created while security is trying to start up. Plus, doing
215 // a Demand from Assembly.GetManifestResourceStream isn't useful.
216 [System.Security.SecurityCritical] // auto-generated
217 internal unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck)
219 Initialize(pointer, length, capacity, access, skipSecurityCheck);
222 [System.Security.SecurityCritical] // auto-generated
223 [CLSCompliant(false)]
224 protected unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access)
226 Initialize(pointer, length, capacity, access, false);
229 [System.Security.SecurityCritical] // auto-generated
230 internal unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck)
233 throw new ArgumentNullException("pointer");
234 if (length < 0 || capacity < 0)
235 throw new ArgumentOutOfRangeException((length < 0) ? "length" : "capacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
236 if (length > capacity)
237 throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_LengthGreaterThanCapacity"));
238 Contract.EndContractBlock();
239 // Check for wraparound.
240 if (((byte*) ((long)pointer + capacity)) < pointer)
241 throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround"));
242 if (access < FileAccess.Read || access > FileAccess.ReadWrite)
243 throw new ArgumentOutOfRangeException("access", Environment.GetResourceString("ArgumentOutOfRange_Enum"));
245 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CalledTwice"));
248 if (!skipSecurityCheck)
249 #pragma warning disable 618
250 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
251 #pragma warning restore 618
256 _capacity = capacity;
261 public override bool CanRead {
263 get { return _isOpen && (_access & FileAccess.Read) != 0; }
266 public override bool CanSeek {
268 get { return _isOpen; }
271 public override bool CanWrite {
273 get { return _isOpen && (_access & FileAccess.Write) != 0; }
276 [System.Security.SecuritySafeCritical] // auto-generated
277 protected override void Dispose(bool disposing)
280 unsafe { _mem = null; }
282 // Stream allocates WaitHandles for async calls. So for correctness
283 // call base.Dispose(disposing) for better perf, avoiding waiting
284 // for the finalizers to run on those types.
285 base.Dispose(disposing);
288 public override void Flush() {
289 if (!_isOpen) __Error.StreamIsClosed();
292 #if !FEATURE_PAL && FEATURE_ASYNC_IO || MONO
293 [HostProtection(ExternalThreading=true)]
295 public override Task FlushAsync(CancellationToken cancellationToken) {
297 if (cancellationToken.IsCancellationRequested)
298 return Task.FromCancellation(cancellationToken);
303 return Task.CompletedTask;
305 } catch(Exception ex) {
307 return Task.FromException(ex);
310 #endif // !FEATURE_PAL && FEATURE_ASYNC_IO
313 public override long Length {
315 if (!_isOpen) __Error.StreamIsClosed();
316 return Interlocked.Read(ref _length);
320 public long Capacity {
322 if (!_isOpen) __Error.StreamIsClosed();
327 public override long Position {
329 if (!CanSeek) __Error.StreamIsClosed();
330 Contract.EndContractBlock();
331 return Interlocked.Read(ref _position);
333 [System.Security.SecuritySafeCritical] // auto-generated
336 throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
337 Contract.EndContractBlock();
338 if (!CanSeek) __Error.StreamIsClosed();
342 // On 32 bit machines, ensure we don't wrap around.
343 if (value > (long) Int32.MaxValue || _mem + value < _mem)
344 throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_StreamLength"));
347 Interlocked.Exchange(ref _position, value);
351 [CLSCompliant(false)]
352 public unsafe byte* PositionPointer {
353 [System.Security.SecurityCritical] // auto-generated_required
355 if (_buffer != null) {
356 throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer"));
359 // Use a temp to avoid a race
360 long pos = Interlocked.Read(ref _position);
362 throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_UMSPosition"));
363 byte * ptr = _mem + pos;
364 if (!_isOpen) __Error.StreamIsClosed();
367 [System.Security.SecurityCritical] // auto-generated_required
370 throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer"));
371 if (!_isOpen) __Error.StreamIsClosed();
373 // Note: subtracting pointers returns an Int64. Working around
374 // to avoid hitting compiler warning CS0652 on this line.
375 if (new IntPtr(value - _mem).ToInt64() > UnmanagedMemStreamMaxLength)
376 throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamLength"));
378 throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
380 Interlocked.Exchange(ref _position, value - _mem);
384 internal unsafe byte* Pointer {
385 [System.Security.SecurityCritical] // auto-generated
388 throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer"));
394 [System.Security.SecuritySafeCritical] // auto-generated
395 public override int Read([In, Out] byte[] buffer, int offset, int count) {
397 throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
399 throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
401 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
402 if (buffer.Length - offset < count)
403 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
404 Contract.EndContractBlock(); // Keep this in [....] with contract validation in ReadAsync
406 if (!_isOpen) __Error.StreamIsClosed();
407 if (!CanRead) __Error.ReadNotSupported();
409 // Use a local variable to avoid a race where another thread
410 // changes our position after we decide we can read some bytes.
411 long pos = Interlocked.Read(ref _position);
412 long len = Interlocked.Read(ref _length);
419 int nInt = (int) n; // Safe because n <= count, which is an Int32
421 nInt = 0; // _position could be beyond EOF
422 Contract.Assert(pos + nInt >= 0, "_position + n >= 0"); // len is less than 2^63 -1.
424 if (_buffer != null) {
426 byte* pointer = null;
427 RuntimeHelpers.PrepareConstrainedRegions();
429 _buffer.AcquirePointer(ref pointer);
430 Buffer.Memcpy(buffer, offset, pointer + pos + _offset, 0, nInt);
433 if (pointer != null) {
434 _buffer.ReleasePointer();
441 Buffer.Memcpy(buffer, offset, _mem + pos, 0, nInt);
444 Interlocked.Exchange(ref _position, pos + n);
448 #if !FEATURE_PAL && FEATURE_ASYNC_IO || MONO
449 [HostProtection(ExternalThreading = true)]
451 public override Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) {
453 throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
455 throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
457 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
458 if (buffer.Length - offset < count)
459 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
460 Contract.EndContractBlock(); // contract validation copied from Read(...)
462 if (cancellationToken.IsCancellationRequested)
463 return Task.FromCancellation<Int32>(cancellationToken);
467 Int32 n = Read(buffer, offset, count);
468 Task<Int32> t = _lastReadTask;
469 return (t != null && t.Result == n) ? t : (_lastReadTask = Task.FromResult<Int32>(n));
471 } catch (Exception ex) {
473 Contract.Assert(! (ex is OperationCanceledException));
474 return Task.FromException<Int32>(ex);
477 #endif // !FEATURE_PAL && FEATURE_ASYNC_IO
479 [System.Security.SecuritySafeCritical] // auto-generated
480 public override int ReadByte() {
481 if (!_isOpen) __Error.StreamIsClosed();
482 if (!CanRead) __Error.ReadNotSupported();
484 long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
485 long len = Interlocked.Read(ref _length);
488 Interlocked.Exchange(ref _position, pos + 1);
490 if (_buffer != null) {
492 byte* pointer = null;
493 RuntimeHelpers.PrepareConstrainedRegions();
495 _buffer.AcquirePointer(ref pointer);
496 result = *(pointer + pos + _offset);
499 if (pointer != null) {
500 _buffer.ReleasePointer();
513 public override long Seek(long offset, SeekOrigin loc) {
514 if (!_isOpen) __Error.StreamIsClosed();
515 if (offset > UnmanagedMemStreamMaxLength)
516 throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamLength"));
518 case SeekOrigin.Begin:
520 throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
521 Interlocked.Exchange(ref _position, offset);
524 case SeekOrigin.Current:
525 long pos = Interlocked.Read(ref _position);
526 if (offset + pos < 0)
527 throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
528 Interlocked.Exchange(ref _position, offset + pos);
532 long len = Interlocked.Read(ref _length);
533 if (len + offset < 0)
534 throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin"));
535 Interlocked.Exchange(ref _position, len + offset);
539 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin"));
542 long finalPos = Interlocked.Read(ref _position);
543 Contract.Assert(finalPos >= 0, "_position >= 0");
547 [System.Security.SecuritySafeCritical] // auto-generated
548 public override void SetLength(long value) {
550 throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
551 Contract.EndContractBlock();
553 throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer"));
554 if (!_isOpen) __Error.StreamIsClosed();
555 if (!CanWrite) __Error.WriteNotSupported();
557 if (value > _capacity)
558 throw new IOException(Environment.GetResourceString("IO.IO_FixedCapacity"));
560 long pos = Interlocked.Read(ref _position);
561 long len = Interlocked.Read(ref _length);
564 Buffer.ZeroMemory(_mem+len, value-len);
567 Interlocked.Exchange(ref _length, value);
569 Interlocked.Exchange(ref _position, value);
573 [System.Security.SecuritySafeCritical] // auto-generated
574 public override void Write(byte[] buffer, int offset, int count) {
576 throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
578 throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
580 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
581 if (buffer.Length - offset < count)
582 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
583 Contract.EndContractBlock(); // Keep contract validation in [....] with WriteAsync(..)
585 if (!_isOpen) __Error.StreamIsClosed();
586 if (!CanWrite) __Error.WriteNotSupported();
588 long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
589 long len = Interlocked.Read(ref _length);
590 long n = pos + count;
591 // Check for overflow
593 throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
596 throw new NotSupportedException(Environment.GetResourceString("IO.IO_FixedCapacity"));
599 if (_buffer == null) {
600 // Check to see whether we are now expanding the stream and must
601 // zero any memory in the middle.
604 Buffer.ZeroMemory(_mem+len, pos-len);
608 // set length after zeroing memory to avoid race condition of accessing unzeroed memory
610 Interlocked.Exchange(ref _length, n);
614 if (_buffer != null) {
616 long bytesLeft = _capacity - pos;
617 if (bytesLeft < count) {
618 throw new ArgumentException(Environment.GetResourceString("Arg_BufferTooSmall"));
622 byte* pointer = null;
623 RuntimeHelpers.PrepareConstrainedRegions();
625 _buffer.AcquirePointer(ref pointer);
626 Buffer.Memcpy(pointer + pos + _offset, 0, buffer, offset, count);
629 if (pointer != null) {
630 _buffer.ReleasePointer();
637 Buffer.Memcpy(_mem + pos, 0, buffer, offset, count);
640 Interlocked.Exchange(ref _position, n);
644 #if !FEATURE_PAL && FEATURE_ASYNC_IO || MONO
645 [HostProtection(ExternalThreading = true)]
647 public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) {
650 throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
652 throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
654 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
655 if (buffer.Length - offset < count)
656 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
657 Contract.EndContractBlock(); // contract validation copied from Write(..)
659 if (cancellationToken.IsCancellationRequested)
660 return Task.FromCancellation(cancellationToken);
664 Write(buffer, offset, count);
665 return Task.CompletedTask;
667 } catch (Exception ex) {
669 Contract.Assert(! (ex is OperationCanceledException));
670 return Task.FromException<Int32>(ex);
673 #endif // !FEATURE_PAL && FEATURE_ASYNC_IO
676 [System.Security.SecuritySafeCritical] // auto-generated
677 public override void WriteByte(byte value) {
678 if (!_isOpen) __Error.StreamIsClosed();
679 if (!CanWrite) __Error.WriteNotSupported();
681 long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition
682 long len = Interlocked.Read(ref _length);
685 // Check for overflow
687 throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
690 throw new NotSupportedException(Environment.GetResourceString("IO.IO_FixedCapacity"));
692 // Check to see whether we are now expanding the stream and must
693 // zero any memory in the middle.
694 // don't do if created from SafeBuffer
695 if (_buffer == null) {
698 Buffer.ZeroMemory(_mem+len, pos-len);
702 // set length after zeroing memory to avoid race condition of accessing unzeroed memory
703 Interlocked.Exchange(ref _length, n);
707 if (_buffer != null) {
709 byte* pointer = null;
710 RuntimeHelpers.PrepareConstrainedRegions();
712 _buffer.AcquirePointer(ref pointer);
713 *(pointer + pos + _offset) = value;
716 if (pointer != null) {
717 _buffer.ReleasePointer();
727 Interlocked.Exchange(ref _position, n);