//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1995 - 1995.
//
//  File:       cache.cxx
//
//  Contents:   Functions to manage a cache of shares
//
//  History:    11-Apr-95    BruceFo  Created
//              21-Aug-95    BruceFo  Created CShareCache class to clean up
//                                      resource usage of resources protected
//                                      by critical section.
//
//----------------------------------------------------------------------------

#include "headers.hxx"
#pragma hdrstop

#include "critsec.hxx"
#include "cache.hxx"
#include "dllmain.hxx"
#include "shrinfo.hxx"
#include "strhash.hxx"
#include "util.hxx"

//////////////////////////////////////////////////////////////////////////////

#if DBG == 1
VOID
DumpNetEnum(
    IN LPVOID pBufShares,
    IN ULONG entriesRead
    );
#endif // DBG == 1

//////////////////////////////////////////////////////////////////////////////

CShareCache g_ShareCache;   // the main share cache

//////////////////////////////////////////////////////////////////////////////

//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::CShareCache
//
//  Synopsis:   Constructor.
//
//  History:    21-Aug-95    BruceFo  Created
//
//--------------------------------------------------------------------------

CShareCache::CShareCache(
    VOID
    )
    :
    m_cShares(0),
    m_pBufShares(NULL),
    m_pHash(NULL)
{
    InitializeCriticalSection(&m_csBuf);
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::~CShareCache
//
//  Synopsis:   Destructor
//
//  History:    21-Aug-95    BruceFo  Created
//
//--------------------------------------------------------------------------

CShareCache::~CShareCache()
{
    Delete();
    DeleteCriticalSection(&m_csBuf);
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::Delete
//
//  Synopsis:   Gets rid of cached memory.
//
//  History:    21-Aug-95    BruceFo  Created
//
//--------------------------------------------------------------------------

VOID
CShareCache::Delete(
    VOID
    )
{
    CTakeCriticalSection t(&m_csBuf);
    if (NULL != m_pBufShares)
    {
        NetApiBufferFree(m_pBufShares);
    }
    m_pBufShares = NULL;
    delete m_pHash;
    m_pHash = NULL;
    m_cShares = 0;
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::IsPathShared
//
//  Synopsis:   See ::IsPathShared.
//
//  History:    21-Aug-95    BruceFo  Created
//
//--------------------------------------------------------------------------

BOOL
CShareCache::IsPathShared(
    LPCTSTR lpPath,
    BOOL fRefresh
    )
{
    BOOL bOldSharingEnabled = g_fSharingEnabled;
    BOOL bRet = FALSE;

    {
        // scope the critical section taking

        CTakeCriticalSection t(&m_csBuf);

        // For plug and play: if the server service starts
        // or stops, we get a refresh call.  If sharing is not currently
        // enabled but a refresh is request, see if sharing has just become
        // available.

        if (fRefresh)
        {
            appDebugOut((DEB_TRACE, "Forced cache refresh!\n"));

            RefreshNoCritSec();
        }

        if (CacheOK())
        {
            appAssert(NULL != m_pHash);
            bRet = m_pHash->IsMember(lpPath);
        }
        else
        {
            // the server doesn't seem to be running...
            bRet = FALSE;
        }
    }

    if (bOldSharingEnabled != g_fSharingEnabled)
    {
        // The server either came up or went down, and we refreshed based on
        // that fact. Force the shell/explorer to redraw *all* views.

        appDebugOut((DEB_TRACE, "Forcing the shell to redraw *all* views!\n"));

        SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
    }

    return bRet;
}



//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::Refresh
//
//  Synopsis:   Refreshes the cache of shares
//
//  History:    21-Aug-95    BruceFo  Created
//
//  Note:       Sets g_fSharingEnabled
//
//--------------------------------------------------------------------------

VOID
CShareCache::Refresh(
    VOID
    )
{
    CTakeCriticalSection t(&m_csBuf);
    RefreshNoCritSec();
}

// in:
//      pShare		  share to inspect
//      bIncludeHidden -> admin shares (X$, ADMIN$) will not be skipped
//                        otherwise they are included

BOOL ShouldSkipShare(SHARE_INFO_502* pShare, BOOL bIncludeHidden)
{
    // needs to have an associated path
    // needs to be STYPE_DISK (to skip IPC$)
    // STYPE_SPECIAL indicates hidden admin share 
    
    return (pShare->shi502_path == NULL) ||
           (pShare->shi502_path[0] == 0) ||
           ((pShare->shi502_type & ~STYPE_SPECIAL) != STYPE_DISKTREE) ||
           (bIncludeHidden ? FALSE : (pShare->shi502_type & STYPE_SPECIAL));
}



//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::RefreshNoCritSec
//
//  Synopsis:   Refreshes the cache of shares: the critical section must
//              already taken!
//
//  History:    18-Aug-95    BruceFo  Created
//
//  Note:       Sets g_fSharingEnabled
//
//--------------------------------------------------------------------------

VOID
CShareCache::RefreshNoCritSec(
    VOID
    )
{
    Delete();

    DWORD entriesRead, totalEntries;
    DWORD err = ::NetShareEnum(
                        NULL,   // local computer
                        502,
                        &m_pBufShares,
                        0xffffffff,     // no buffer limit; get them all!
                        &entriesRead,
                        &totalEntries,
                        NULL);  // no resume handle 'cause we're getting all
    if (err != NERR_Success)
    {
        appDebugOut((DEB_ERROR,
            "Error enumerating shares: 0x%08lx\n",
            err));

        m_pBufShares = NULL;    // just in case NetShareEnum munged it
        Delete();
    }
    else
    {
        appAssert(entriesRead == totalEntries);
        m_cShares = entriesRead;
    }

    if (m_cShares > 0)
    {
        //
        // Now, create a hash table and put all the shares into it (strings are
        // cached; don't copy any data)
        //

        m_pHash = new CStrHashTable(m_cShares * 2 - 1);
        if ((NULL == m_pHash) || FAILED(m_pHash->QueryError()))
        {
            // out of memory; delete everything
            Delete();
        }
        else
        {
            SHARE_INFO_502* pShareBase = (SHARE_INFO_502 *)m_pBufShares;

            for (UINT iShare = 0; iShare < m_cShares; iShare++)
            {
                SHARE_INFO_502* pShare = &pShareBase[iShare];

                if (ShouldSkipShare(pShare, FALSE)) // don't include hidden
                    continue;

                HRESULT hr = m_pHash->Insert(pShare->shi502_path);
                if (FAILED(hr))
                {
                    // out of memory; delete everything
                    Delete();
                    break;
                }
            }
        }

#if DBG == 1
        if (NULL != m_pHash)
        {
            // if everything hasn't been deleted because of a memory problem...
            m_pHash->Print();
        }
#endif // DBG == 1

    }

    g_fSharingEnabled = CacheOK();
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::IsShareNameUsed
//
//  Synopsis:   Returns TRUE if the share name in question is already used
//
//  History:    4-Apr-95    BruceFo  Created
//
//--------------------------------------------------------------------------

BOOL
CShareCache::IsShareNameUsed(
    IN PWSTR pszShareName
    )
{
    CTakeCriticalSection t(&m_csBuf);

    if (!CacheOK())
    {
        return FALSE;
    }

    SHARE_INFO_502* pShareBase = (SHARE_INFO_502 *)m_pBufShares;

    for (UINT iShare = 0; iShare < m_cShares; iShare++)
    {
        SHARE_INFO_502* pShare = &pShareBase[iShare];
        if (0 == _wcsicmp(pszShareName, pShare->shi502_netname))
        {
            return TRUE;
        }
    }

    return FALSE;
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::IsExistingShare
//
//  Synopsis:   Finds out if a share name is already in use with a different
//              path.
//
//  Arguments:  [pszShareName] - name of share being replaced
//              [pszPath] - path to compare against
//              [pszOldPath] - If not null, filled with path of the share,
//                             if found
//
//  Returns:    Returns TRUE if found and the paths are different,
//              FALSE otherwise
//
//  History:    4-May-95   BruceFo     Stolen
//
//--------------------------------------------------------------------------

BOOL
CShareCache::IsExistingShare(
    IN PCWSTR pszShareName,
    IN PCWSTR pszPath,
    OUT PWSTR pszOldPath
    )
{
    appAssert(NULL != pszShareName);

    CTakeCriticalSection t(&m_csBuf);

    if (!CacheOK())
    {
        return FALSE;
    }

    SHARE_INFO_502* pShareBase = (SHARE_INFO_502 *)m_pBufShares;

    for (UINT iShare = 0; iShare < m_cShares; iShare++)
    {
        SHARE_INFO_502* pShare = &pShareBase[iShare];
        if (0 == _wcsicmp(pszShareName, pShare->shi502_netname))
        {
            if (pszOldPath != NULL)
            {
                wcscpy(pszOldPath, pShare->shi502_path);
            }

            return TRUE;
        }
    }

    return FALSE;
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::ConstructList
//
//  Synopsis:   Construct a list of shares for a particular path
//
//  Arguments:
//
//  Returns:    hresult
//
//  History:    21-Aug-95   BruceFo     Created
//
//--------------------------------------------------------------------------

HRESULT
CShareCache::ConstructList(
    IN PCWSTR          pszPath,
    IN OUT CShareInfo* pShareList,
    OUT ULONG*         pcShares
    )
{
    CTakeCriticalSection t(&m_csBuf);

    SHARE_INFO_502* pShareBase = (SHARE_INFO_502 *)m_pBufShares;

    HRESULT hr;
    ULONG cShares = 0;

    for (UINT iShare = 0; iShare < m_cShares; iShare++)
    {
        SHARE_INFO_502* pShare = &pShareBase[iShare];

        if (0 == _wcsicmp(pszPath, pShare->shi502_path))
        {
            if (ShouldSkipShare(pShare, TRUE))  // include hidden
                continue;
            //
            // We found one!
            //

            appDebugOut((DEB_ITRACE,
                "ConstructList: adding %ws\n",
                pShare->shi502_netname));

            CShareInfo* pNewInfo = new CShareInfo();
            if (NULL == pNewInfo)
            {
                return E_OUTOFMEMORY;
            }

            hr = pNewInfo->InitInstance();
            if (FAILED(hr))
            {
                delete pNewInfo;
                return hr;
            }

            // We can't point into the data protected by a critical section,
            // so we must copy it.
            hr = pNewInfo->Copy(pShare);
            if (FAILED(hr))
            {
                delete pNewInfo;
                return hr;
            }

            NET_API_STATUS    ret = pNewInfo->ReadCacheFlags ();
            if ( NERR_Success != ret )
            {
                delete pNewInfo;
                return HRESULT_FROM_WIN32 (ret);
            }

            pNewInfo->InsertBefore(pShareList); // add to end of list

            ++cShares;
        }
    }

    *pcShares = cShares;
    return S_OK;
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::ConstructParentWarnList
//
//  Synopsis:   Construct a new list of shares that are children or descendants
//              of the path passed in.
//
//  Arguments:  [pszPath] - the prefix path to check for
//              [ppShareList] - new share list, if success. Caller must delete
//                  it using 'delete' on each element. This list is
//                  doubly-linked with a dummy head node. NOTE: As an
//                  optimization, this is set to NULL if there is no share.
//                  This avoids allocating and deleting memory unless there
//                  is something to warn the user about.
//
//  Returns:    hresult
//
//  History:    21-Aug-95   BruceFo     Created
//
//--------------------------------------------------------------------------

HRESULT
CShareCache::ConstructParentWarnList(
    IN PCWSTR        pszPath,
    OUT CShareInfo** ppShareList
    )
{
    CTakeCriticalSection t(&m_csBuf);

    HRESULT hr;
    CShareInfo* pShareList = NULL;
    SHARE_INFO_502* pShareBase = (SHARE_INFO_502 *)m_pBufShares;
    INT cchPath = wcslen(pszPath);

    for (UINT iShare = 0; iShare < m_cShares; iShare++)
    {
        SHARE_INFO_502* pShare = &pShareBase[iShare];

        PWSTR pszSharePath = pShare->shi502_path;
        INT cchSharePath = wcslen(pszSharePath);

        if (cchSharePath >= cchPath)
        {
            // WARNING - the following won't work with LFN/shortname differences

            // PERF: we're doing a prefix match of the current directory
            // name on the set of share names. This could be expensive with
            // a linear search!

            if (0 == _wcsnicmp(pszSharePath, pszPath, cchPath)
                && (    *(pszSharePath + cchPath) == TEXT('\\')
                     || *(pszSharePath + cchPath) == TEXT('\0')
                   )
               )
            {
                appDebugOut((DEB_TRACE,
                    "ConstructParentWarnList, share %ws, file %ws. Found a prefix!\n",
                    pszSharePath, pszPath));

                if (NULL == pShareList)
                {
                    // do the lazy dummy head node creation if this is the
                    // first prefix match

                    pShareList = new CShareInfo();  // dummy head node
                    if (NULL == pShareList)
                    {
                        return E_OUTOFMEMORY;
                    }
                }

                CShareInfo* pNewInfo = new CShareInfo();
                if (NULL == pNewInfo)
                {
                    hr = E_OUTOFMEMORY;
                }
                else
                {
                    hr = pNewInfo->InitInstance();
                    if (SUCCEEDED(hr))
                    {
                        // We can't point into the data protected by a
                        // critical section, so we must copy it.
                        hr = pNewInfo->Copy(pShare);
                        if ( SUCCEEDED (hr) )
                        {
                            NET_API_STATUS    ret = pNewInfo->ReadCacheFlags ();
                            if ( NERR_Success != ret )
                            {
                                delete pNewInfo;
                                return HRESULT_FROM_WIN32 (ret);
                            }
                        }
                    }
                }

                if (FAILED(hr))
                {
                    delete pNewInfo;
                    DeleteShareInfoList(pShareList, TRUE);

                    return hr;
                }

                pNewInfo->InsertBefore(pShareList); // add to end of list
            }
        }
    }

    *ppShareList = pShareList;
    return S_OK;
}


//+-------------------------------------------------------------------------
//
//  Member:     CShareCache::CacheOK
//
//  Synopsis:   Returns TRUE if the cache contains valid data.
//
//  History:    24-Sep-95    BruceFo  Created
//
//  Note:       The critical section must be held when calling this function
//
//--------------------------------------------------------------------------

BOOL
CShareCache::CacheOK(
    VOID
    )
{
    // either both are valid or both are invalid
    appAssert(
        ((NULL != m_pHash) && (NULL != m_pBufShares)) ||
        ((NULL == m_pHash) && (NULL == m_pBufShares))
        );

    return (NULL != m_pHash);
}


#if DBG == 1

//+-------------------------------------------------------------------------
//
//  Function:   DumpNetEnum
//
//  Synopsis:   Dumps an array of SHARE_INFO_502 structures.
//
//  History:    4-Apr-95    BruceFo  Created
//
//--------------------------------------------------------------------------

VOID
DumpNetEnum(
    IN LPVOID pBufShares,
    IN ULONG entriesRead
    )
{
    SHARE_INFO_502* pBase = (SHARE_INFO_502*) pBufShares;

    appDebugOut((DEB_TRACE,
        "DumpNetEnum: %d entries\n",
        entriesRead));

    for (ULONG i = 0; i < entriesRead; i++)
    {
        SHARE_INFO_502* p = &(pBase[i]);

        appDebugOut((DEB_TRACE | DEB_NOCOMPNAME,
"\t Share name: %ws\n"
"\t       Type: %d (0x%08lx)\n"
"\t    Comment: %ws\n"
"\tPermissions: %d (0x%08lx)\n"
"\t   Max uses: %d\n"
"\t       Path: %ws\n"
"\t   Password: %ws\n"
"\t   Reserved: %d\n"
"\t   Security? %ws\n"
"\n"
,
p->shi502_netname,
p->shi502_type, p->shi502_type,
p->shi502_remark,
p->shi502_permissions, p->shi502_permissions,
p->shi502_max_uses,
p->shi502_path,
(NULL == p->shi502_passwd) ? L"none" : p->shi502_passwd,
p->shi502_reserved,
(NULL == p->shi502_security_descriptor) ? L"No" : L"Yes"
));

    }
}

#endif // DBG == 1
