//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1999.
//
//  File:       W R L O C K . C P P
//
//  Contents:   Defines the interface to the netcfg write lock used to
//              protect the network configuration information from being
//              modified by more than one writer at a time.
//
//  Notes:
//
//  Author:     shaunco   15 Jan 1999
//
//----------------------------------------------------------------------------

#include <pch.h>
#pragma hdrstop
#include "nccom.h"
#include "ncreg.h"
#include "util.h"
#include "wrlock.h"

#define MUTEX_NAME          L"Global\\NetCfgWriteLock"
#define LOCK_HOLDER_SUBKEY  L"NetCfgLockHolder"


CWriteLock::~CWriteLock ()
{
    // If we have the mutex created, release it if we own it
    // and close its handle.
    //
    if (m_hMutex)
    {
        ReleaseIfOwned ();
        CloseHandle (m_hMutex);
    }
}

HRESULT
CWriteLock::HrEnsureMutexCreated ()
{
    if (m_hMutex)
    {
        return S_OK;
    }

    // Ensure the mutex has been created.  It is important to create it
    // with a security descriptor that allows access to the world because
    // we may be running under the localsystem account and someone else
    // may be running under a user account.  If we didn't give the world
    // explicit access, the user account clients would get access denied
    // because the mutex would have inherited the security level of our
    // process.
    //
    HRESULT hr;
    Assert (!m_hMutex);
    Assert (!m_fOwned);

    hr = HrCreateMutexWithWorldAccess (
            MUTEX_NAME,
            FALSE, // not initially owned,
            NULL,
            &m_hMutex);

    TraceHr (ttidError, FAL, hr, FALSE, "CWriteLock::HrEnsureMutexCreated");
    return hr;
}

BOOL
CWriteLock::WaitToAcquire (
    IN DWORD dwMilliseconds,
    IN PCWSTR pszNewOwnerDesc,
    OUT PWSTR* ppszCurrentOwnerDesc OPTIONAL)
{
    HRESULT hr;
    BOOL fAcquired = FALSE;

    hr = HrEnsureMutexCreated ();
    if (S_OK == hr)
    {
        // Now wait for the mutext to become available.  (Pump messages while
        // waiting so we don't hang the clients UI.)
        //
        while (1)
        {
            DWORD dwWait;

            dwWait = MsgWaitForMultipleObjects (
                        1, &m_hMutex, FALSE,
                        dwMilliseconds, QS_ALLINPUT);

            if ((WAIT_OBJECT_0 + 1) == dwWait)
            {
                // We have messages to pump.
                //
                MSG msg;
                while (PeekMessage (&msg, NULL, NULL, NULL, PM_REMOVE))
                {
                    DispatchMessage (&msg);
                }
            }
            else
            {
                if (WAIT_OBJECT_0 == dwWait)
                {
                    fAcquired = TRUE;
                }
                else if (WAIT_ABANDONED_0 == dwWait)
                {
                    fAcquired = TRUE;
                    TraceTag (ttidError, "NetCfg write lock was abandoned!");
                }
                else if (WAIT_TIMEOUT == dwWait)
                {
                    hr = HRESULT_FROM_WIN32 (ERROR_TIMEOUT);
                }
                else
                {
                    hr = HrFromLastWin32Error ();
                    TraceHr (ttidError, FAL, hr, FALSE,
                        "MsgWaitForMultipleObjects");
                }

                // If we acquired the mutex, set the new owner.
                //
                if (fAcquired)
                {
                    m_fOwned = TRUE;
                    SetOrQueryLockHolder (TRUE,
                        pszNewOwnerDesc, ppszCurrentOwnerDesc);
                }
                else if (ppszCurrentOwnerDesc)
                {
                    // Query the lock holder description.
                    //
                    SetOrQueryLockHolder (FALSE,
                        NULL, ppszCurrentOwnerDesc);
                }

                break;
            }
        }
    }

    return fAcquired;
}

BOOL
CWriteLock::FIsLockedByAnyone (
    OUT PWSTR* ppszCurrentOwnerDesc OPTIONAL)
{
    // It's locked if we own it.
    //
    BOOL fLocked = m_fOwned;

    // If we don't own it, check to see if some other process does.
    //
    if (!fLocked)
    {
        HRESULT hr;

        hr = HrEnsureMutexCreated ();
        if (S_OK == hr)
        {
            DWORD dw;

            // Wait for the mutex, but with a zero timeout.  This is
            // equivalent to a quick check.  (But we still need to release
            // it if we acquire ownership.  If we timeout, it means that
            // someone else owns it.
            //
            dw = WaitForSingleObject (m_hMutex, 0);

            if (WAIT_OBJECT_0 == dw)
            {
                ReleaseMutex (m_hMutex);
            }
            else if (WAIT_TIMEOUT == dw)
            {
                // Someone else owns it.
                //
                fLocked = TRUE;
            }
        }
    }

    if (fLocked)
    {
        // Query the lock holder description.
        //
        SetOrQueryLockHolder (FALSE, NULL, ppszCurrentOwnerDesc);
    }

    return fLocked;
}

VOID
CWriteLock::ReleaseIfOwned ()
{
    if (m_fOwned)
    {
        Assert (m_hMutex);

        // Clear the lock holder now that no one is about to own it.
        //
        SetOrQueryLockHolder (TRUE, NULL, NULL);

        ReleaseMutex (m_hMutex);
        m_fOwned = FALSE;
    }
}

VOID
CWriteLock::SetOrQueryLockHolder (
    IN BOOL fSet,
    IN PCWSTR pszNewOwnerDesc OPTIONAL,
    OUT PWSTR* ppszCurrentOwnerDesc OPTIONAL)
{
    HRESULT hr;
    HKEY hkeyNetwork;
    HKEY hkeyLockHolder;
    REGSAM samDesired;
    BOOL fClear;

    // We're clearing the value if we're asked to set it to NULL.
    //
    fClear = fSet && !pszNewOwnerDesc;

    // Initialize the output parameter if specified.
    //
    if (ppszCurrentOwnerDesc)
    {
        *ppszCurrentOwnerDesc = NULL;
    }

    // If we're setting the lock holder, we need write access.  Otherwise,
    // we only need read access.
    //
    samDesired = (fSet) ? KEY_READ_WRITE_DELETE : KEY_READ;

    hr = HrOpenNetworkKey (samDesired, &hkeyNetwork);

    if (S_OK == hr)
    {
        // The lock holder is represented by the default value of a
        // volatile subkey under the Network subtree.
        //

        if (fClear)
        {
            RegDeleteKey (hkeyNetwork, LOCK_HOLDER_SUBKEY);
        }
        else if (fSet)
        {
            DWORD dwDisposition;

            Assert (pszNewOwnerDesc);

            hr = HrRegCreateKeyWithWorldAccess (
                    hkeyNetwork,
                    LOCK_HOLDER_SUBKEY,
                    REG_OPTION_VOLATILE,
                    KEY_WRITE,
                    &hkeyLockHolder,
                    &dwDisposition);

            // Set the lock holder and close the key.
            //
            if (S_OK == hr)
            {
                (VOID) HrRegSetSz (hkeyLockHolder, NULL, pszNewOwnerDesc);

                RegCloseKey (hkeyLockHolder);
            }
        }
        else
        {
            // Query for the lock holder by opening the key (if it exists)
            // and reading the default value.  We return the string
            // allocated with CoTaskMemAlloc because we use this
            // directly from the COM implementation.
            //
            Assert (ppszCurrentOwnerDesc);

            hr = HrRegOpenKeyEx (
                    hkeyNetwork,
                    LOCK_HOLDER_SUBKEY,
                    KEY_READ,
                    &hkeyLockHolder);

            if (S_OK == hr)
            {
                PWSTR pszLockHolder;

                hr = HrRegQuerySzWithAlloc (
                        hkeyLockHolder,
                        NULL,
                        &pszLockHolder);

                if (S_OK == hr)
                {
                    hr = HrCoTaskMemAllocAndDupSz (
                            pszLockHolder, ppszCurrentOwnerDesc);

                    MemFree (pszLockHolder);
                }
                RegCloseKey (hkeyLockHolder);
            }
        }

        RegCloseKey (hkeyNetwork);
    }
}