//
// System.Runtime.InteropServices.SafeHandle
//
// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Notes:
//     This code is only API complete, but it lacks the runtime support
//     for CriticalFinalizerObject and any P/Invoke wrapping that might
//     happen.
//
//     For details, see:
//     http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx
//
//     CER-like behavior is implemented for Close and DangerousAddRef
//     via the try/finally uninterruptible pattern in case of async
//     exceptions like ThreadAbortException.
//
// On implementing SafeHandles:
//     http://blogs.msdn.com/bclteam/archive/2005/03/15/396335.aspx
//
// Issues:
//
//     TODO: Although DangerousAddRef has been implemented, I need to
//     find out whether the runtime performs the P/Invoke if the
//     handle has been disposed already.
//

//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// Files:
//  - mscorlib/system/runtime/interopservices/safehandle.cs
//

using System;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.CompilerServices;
using System.Threading;

namespace System.Runtime.InteropServices
{
	[StructLayout (LayoutKind.Sequential)]
	public abstract partial class SafeHandle
	{
		const int RefCount_Mask = 0x7ffffffc;
		const int RefCount_One = 0x4;

		enum State {
			Closed = 0x00000001,
			Disposed = 0x00000002,
		}

		/*
		 * This should only be called for cases when you know for a fact that
		 * your handle is invalid and you want to record that information.
		 * An example is calling a syscall and getting back ERROR_INVALID_HANDLE.
		 * This method will normally leak handles!
		 */
		[ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
		public void SetHandleAsInvalid ()
		{
			try {}
			finally {
				int old_state, new_state;

				do {
					old_state = _state;
					new_state = old_state | (int) State.Closed;
				} while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);

				GC.SuppressFinalize (this);
			}
		}

		/*
		 * Add a reason why this handle should not be relinquished (i.e. have
		 * ReleaseHandle called on it). This method has dangerous in the name since
		 * it must always be used carefully (e.g. called within a CER) to avoid
		 * leakage of the handle. It returns a boolean indicating whether the
		 * increment was actually performed to make it easy for program logic to
		 * back out in failure cases (i.e. is a call to DangerousRelease needed).
		 * It is passed back via a ref parameter rather than as a direct return so
		 * that callers need not worry about the atomicity of calling the routine
		 * and assigning the return value to a variable (the variable should be
		 * explicitly set to false prior to the call). The only failure cases are
		 * when the method is interrupted prior to processing by a thread abort or
		 * when the handle has already been (or is in the process of being)
		 * released.
		 */
		[ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
		public void DangerousAddRef (ref bool success)
		{
			try {}
			finally {
				if (!_fullyInitialized)
					throw new InvalidOperationException ();

				int old_state, new_state;

				do {
					old_state = _state;

					if ((old_state & (int) State.Closed) != 0)
						throw new ObjectDisposedException ("handle");

					new_state = old_state + RefCount_One;
				} while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);

				success = true;
			}
		}

		/*
		 * Partner to DangerousAddRef. This should always be successful when used in
		 * a correct manner (i.e. matching a successful DangerousAddRef and called
		 * from a region such as a CER where a thread abort cannot interrupt
		 * processing). In the same way that unbalanced DangerousAddRef calls can
		 * cause resource leakage, unbalanced DangerousRelease calls may cause
		 * invalid handle states to become visible to other threads. This
		 * constitutes a potential security hole (via handle recycling) as well as a
		 * correctness problem -- so don't ever expose Dangerous* calls out to
		 * untrusted code.
		 */
		[ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
		public void DangerousRelease ()
		{
			DangerousReleaseInternal (false);
		}

		void InternalDispose ()
		{
			if (!_fullyInitialized)
				throw new InvalidOperationException ();

			DangerousReleaseInternal (true);
			GC.SuppressFinalize (this);
		}

		void InternalFinalize ()
		{
			if (_fullyInitialized)
				DangerousReleaseInternal (true);
		}

		void DangerousReleaseInternal (bool dispose)
		{
			try {}
			finally {
				if (!_fullyInitialized)
					throw new InvalidOperationException ();

				int old_state, new_state;

				/* See AddRef above for the design of the synchronization here. Basically we
				 * will try to decrement the current ref count and, if that would take us to
				 * zero refs, set the closed state on the handle as well. */
				bool perform_release = false;

				do {
					old_state = _state;

					/* If this is a Dispose operation we have additional requirements (to
					 * ensure that Dispose happens at most once as the comments in AddRef
					 * detail). We must check that the dispose bit is not set in the old
					 * state and, in the case of successful state update, leave the disposed
					 * bit set. Silently do nothing if Dispose has already been called
					 * (because we advertise that as a semantic of Dispose). */
					if (dispose && (old_state & (int) State.Disposed) != 0) {
						/* we cannot use `return` in a finally block, so we have to ensure
						 * that we are not releasing the handle */
						perform_release = false;
						break;
					}

					/* We should never see a ref count of zero (that would imply we have
					 * unbalanced AddRef and Releases). (We might see a closed state before
					 * hitting zero though -- that can happen if SetHandleAsInvalid is
					 * used). */
					if ((old_state & RefCount_Mask) == 0)
						throw new ObjectDisposedException ("handle");

					if ((old_state & RefCount_Mask) != RefCount_One)
						perform_release = false;
					else if ((old_state & (int) State.Closed) != 0)
						perform_release = false;
					else if (!_ownsHandle)
						perform_release = false;
					else if (IsInvalid)
						perform_release = false;
					else
						perform_release = true;

					/* Attempt the update to the new state, fail and retry if the initial
					 * state has been modified in the meantime. Decrement the ref count by
					 * substracting SH_RefCountOne from the state then OR in the bits for
					 * Dispose (if that's the reason for the Release) and closed (if the
					 * initial ref count was 1). */
					new_state = (old_state & RefCount_Mask) - RefCount_One;
					if ((old_state & RefCount_Mask) == RefCount_One)
						new_state |= (int) State.Closed;
					if (dispose)
						new_state |= (int) State.Disposed;
				} while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);

				if (perform_release)
					ReleaseHandle ();
			}
		}
	}
}
