Merge pull request #1971 from angeloc/master
[mono.git] / mcs / class / corlib / System.Runtime.InteropServices / SafeHandle.cs
1 //
2 // System.Runtime.InteropServices.SafeHandle
3 //
4 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the
8 // "Software"), to deal in the Software without restriction, including
9 // without limitation the rights to use, copy, modify, merge, publish,
10 // distribute, sublicense, and/or sell copies of the Software, and to
11 // permit persons to whom the Software is furnished to do so, subject to
12 // the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be
15 // included in all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 //
25 // Notes:
26 //     This code is only API complete, but it lacks the runtime support
27 //     for CriticalFinalizerObject and any P/Invoke wrapping that might
28 //     happen.
29 //
30 //     For details, see:
31 //     http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx
32 //
33 //     CER-like behavior is implemented for Close and DangerousAddRef
34 //     via the try/finally uninterruptible pattern in case of async
35 //     exceptions like ThreadAbortException.
36 //
37 // On implementing SafeHandles:
38 //     http://blogs.msdn.com/bclteam/archive/2005/03/15/396335.aspx
39 //
40 // Issues:
41 //
42 //     TODO: Although DangerousAddRef has been implemented, I need to
43 //     find out whether the runtime performs the P/Invoke if the
44 //     handle has been disposed already.
45 //
46
47 //
48 // Copyright (c) Microsoft. All rights reserved.
49 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
50 //
51 // Files:
52 //  - mscorlib/system/runtime/interopservices/safehandle.cs
53 //
54
55 using System;
56 using System.Runtime.InteropServices;
57 using System.Runtime.ConstrainedExecution;
58 using System.Runtime.CompilerServices;
59 using System.Threading;
60
61 namespace System.Runtime.InteropServices
62 {
63         [StructLayout (LayoutKind.Sequential)]
64         public abstract partial class SafeHandle
65         {
66                 const int RefCount_Mask = 0x7ffffffc;
67                 const int RefCount_One = 0x4;
68
69                 enum State {
70                         Closed = 0x00000001,
71                         Disposed = 0x00000002,
72                 }
73
74                 /*
75                  * This should only be called for cases when you know for a fact that
76                  * your handle is invalid and you want to record that information.
77                  * An example is calling a syscall and getting back ERROR_INVALID_HANDLE.
78                  * This method will normally leak handles!
79                  */
80                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
81                 public void SetHandleAsInvalid ()
82                 {
83                         int old_state, new_state;
84
85                         do {
86                                 old_state = _state;
87                                 new_state = old_state | (int) State.Closed;
88                         } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
89
90                         GC.SuppressFinalize (this);
91                 }
92
93                 /*
94                  * Add a reason why this handle should not be relinquished (i.e. have
95                  * ReleaseHandle called on it). This method has dangerous in the name since
96                  * it must always be used carefully (e.g. called within a CER) to avoid
97                  * leakage of the handle. It returns a boolean indicating whether the
98                  * increment was actually performed to make it easy for program logic to
99                  * back out in failure cases (i.e. is a call to DangerousRelease needed).
100                  * It is passed back via a ref parameter rather than as a direct return so
101                  * that callers need not worry about the atomicity of calling the routine
102                  * and assigning the return value to a variable (the variable should be
103                  * explicitly set to false prior to the call). The only failure cases are
104                  * when the method is interrupted prior to processing by a thread abort or
105                  * when the handle has already been (or is in the process of being)
106                  * released.
107                  */
108                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
109                 public void DangerousAddRef (ref bool success)
110                 {
111                         if (!_fullyInitialized)
112                                 throw new InvalidOperationException ();
113
114                         int old_state, new_state;
115
116                         do {
117                                 old_state = _state;
118
119                                 if ((old_state & (int) State.Closed) != 0)
120                                         throw new ObjectDisposedException ("handle");
121
122                                 new_state = old_state + RefCount_One;
123                         } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
124
125                         success = true;
126                 }
127
128                 /*
129                  * Partner to DangerousAddRef. This should always be successful when used in
130                  * a correct manner (i.e. matching a successful DangerousAddRef and called
131                  * from a region such as a CER where a thread abort cannot interrupt
132                  * processing). In the same way that unbalanced DangerousAddRef calls can
133                  * cause resource leakage, unbalanced DangerousRelease calls may cause
134                  * invalid handle states to become visible to other threads. This
135                  * constitutes a potential security hole (via handle recycling) as well as a
136                  * correctness problem -- so don't ever expose Dangerous* calls out to
137                  * untrusted code.
138                  */
139                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
140                 public void DangerousRelease ()
141                 {
142                         DangerousReleaseInternal (false);
143                 }
144
145                 void InternalDispose ()
146                 {
147                         if (!_fullyInitialized)
148                                 throw new InvalidOperationException ();
149
150                         DangerousReleaseInternal (true);
151                         GC.SuppressFinalize (this);
152                 }
153
154                 void InternalFinalize ()
155                 {
156                         if (_fullyInitialized)
157                                 DangerousReleaseInternal (true);
158                 }
159
160                 void DangerousReleaseInternal (bool dispose)
161                 {
162                         if (!_fullyInitialized)
163                                 throw new InvalidOperationException ();
164
165                         int old_state, new_state;
166
167                         /* See AddRef above for the design of the synchronization here. Basically we
168                          * will try to decrement the current ref count and, if that would take us to
169                          * zero refs, set the closed state on the handle as well. */
170                         bool perform_release = false;
171
172                         do {
173                                 old_state = _state;
174
175                                 /* If this is a Dispose operation we have additional requirements (to
176                                  * ensure that Dispose happens at most once as the comments in AddRef
177                                  * detail). We must check that the dispose bit is not set in the old
178                                  * state and, in the case of successful state update, leave the disposed
179                                  * bit set. Silently do nothing if Dispose has already been called
180                                  * (because we advertise that as a semantic of Dispose). */
181                                 if (dispose && (old_state & (int) State.Disposed) != 0)
182                                         return;
183
184                                 /* We should never see a ref count of zero (that would imply we have
185                                  * unbalanced AddRef and Releases). (We might see a closed state before
186                                  * hitting zero though -- that can happen if SetHandleAsInvalid is
187                                  * used). */
188                                 if ((old_state & RefCount_Mask) == 0)
189                                         throw new ObjectDisposedException ("handle");
190
191                                 if ((old_state & RefCount_Mask) != RefCount_One)
192                                         perform_release = false;
193                                 else if ((old_state & (int) State.Closed) != 0)
194                                         perform_release = false;
195                                 else if (!_ownsHandle)
196                                         perform_release = false;
197                                 else if (IsInvalid)
198                                         perform_release = false;
199                                 else
200                                         perform_release = true;
201
202                                 /* Attempt the update to the new state, fail and retry if the initial
203                                  * state has been modified in the meantime. Decrement the ref count by
204                                  * substracting SH_RefCountOne from the state then OR in the bits for
205                                  * Dispose (if that's the reason for the Release) and closed (if the
206                                  * initial ref count was 1). */
207                                 new_state = (old_state & RefCount_Mask) - RefCount_One;
208                                 if ((old_state & RefCount_Mask) == RefCount_One)
209                                         new_state |= (int) State.Closed;
210                                 if (dispose)
211                                         new_state |= (int) State.Disposed;
212                         } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
213
214                         if (perform_release)
215                                 ReleaseHandle ();
216                 }
217
218                 /*
219                  * Implement this abstract method in your derived class to specify how to
220                  * free the handle. Be careful not write any code that's subject to faults
221                  * in this method (the runtime will prepare the infrastructure for you so
222                  * that no jit allocations etc. will occur, but don't allocate memory unless
223                  * you can deal with the failure and still free the handle).
224                  * The boolean returned should be true for success and false if the runtime
225                  * should fire a SafeHandleCriticalFailure MDA (CustomerDebugProbe) if that
226                  * MDA is enabled.
227                  */
228                 [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
229                 protected abstract bool ReleaseHandle ();
230         }
231 }