/*
 * volume.c - Volume ADT module.
 */


/* Headers
 **********/

#include "project.h"
#pragma hdrstop

#include "volume.h"


/* Constants
 ************/

/* VOLUMELIST PTRARRAY allocation parameters */

#define NUM_START_VOLUMES        (16)
#define NUM_VOLUMES_TO_ADD       (16)

/* VOLUMELIST string table allocation parameters */

#define NUM_VOLUME_HASH_BUCKETS  (31)


/* Types
 ********/

/* volume list */

typedef struct _volumelist
{
    /* array of pointers to VOLUMEs */

    HPTRARRAY hpa;

    /* table of volume root path strings */

    HSTRINGTABLE hst;

    /* flags from RESOLVELINKINFOINFLAGS */

    DWORD dwFlags;

    /*
     * handle to parent window, only valid if RLI_IFL_ALLOW_UI is set in dwFlags
     * field
     */

    HWND hwndOwner;
}
VOLUMELIST;
DECLARE_STANDARD_TYPES(VOLUMELIST);

/* VOLUME flags */

typedef enum _volumeflags
{
    /* The volume root path string indicated by hsRootPath is valid. */

    VOLUME_FL_ROOT_PATH_VALID  = 0x0001,

    /*
     * The net resource should be disconnected by calling DisconnectLinkInfo()
     * when finished.
     */

    VOLUME_FL_DISCONNECT       = 0x0002,

    /* Any cached volume information should be verified before use. */

    VOLUME_FL_VERIFY_VOLUME    = 0x0004,

    /* flag combinations */

    ALL_VOLUME_FLAGS           = (VOLUME_FL_ROOT_PATH_VALID |
            VOLUME_FL_DISCONNECT |
            VOLUME_FL_VERIFY_VOLUME)
}
VOLUMEFLAGS;

/* VOLUME states */

typedef enum _volumestate
{
    VS_UNKNOWN,

    VS_AVAILABLE,

    VS_UNAVAILABLE
}
VOLUMESTATE;
DECLARE_STANDARD_TYPES(VOLUMESTATE);

/* volume structure */

typedef struct _volume
{
    /* reference count */

    ULONG ulcLock;

    /* bit mask of flags from VOLUMEFLAGS */

    DWORD dwFlags;

    /* volume state */

    VOLUMESTATE vs;

    /* pointer to LinkInfo structure indentifying volume */

    PLINKINFO pli;

    /*
     * handle to volume root path string, only valid if
     * VOLUME_FL_ROOT_PATH_VALID is set in dwFlags field
     */

    HSTRING hsRootPath;

    /* pointer to parent volume list */

    PVOLUMELIST pvlParent;
}
VOLUME;
DECLARE_STANDARD_TYPES(VOLUME);

/* database volume list header */

typedef struct _dbvolumelistheader
{
    /* number of volumes in list */

    LONG lcVolumes;

    /* length of longest LinkInfo structure in volume list in bytes */

    UINT ucbMaxLinkInfoLen;
}
DBVOLUMELISTHEADER;
DECLARE_STANDARD_TYPES(DBVOLUMELISTHEADER);

/* database volume structure */

typedef struct _dbvolume
{
    /* old handle to volume */

    HVOLUME hvol;

    /* old LinkInfo structure follows */

    /* first DWORD of LinkInfo structure is total size in bytes */
}
DBVOLUME;
DECLARE_STANDARD_TYPES(DBVOLUME);


/***************************** Private Functions *****************************/

/* Module Prototypes
 ********************/

PRIVATE_CODE COMPARISONRESULT VolumeSortCmp(PCVOID, PCVOID);
PRIVATE_CODE COMPARISONRESULT VolumeSearchCmp(PCVOID, PCVOID);
PRIVATE_CODE BOOL SearchForVolumeByRootPathCmp(PCVOID, PCVOID);
PRIVATE_CODE BOOL UnifyVolume(PVOLUMELIST, PLINKINFO, PVOLUME *);
PRIVATE_CODE BOOL CreateVolume(PVOLUMELIST, PLINKINFO, PVOLUME *);
PRIVATE_CODE void UnlinkVolume(PCVOLUME);
PRIVATE_CODE BOOL DisconnectVolume(PVOLUME);
PRIVATE_CODE void DestroyVolume(PVOLUME);
PRIVATE_CODE void LockVolume(PVOLUME);
PRIVATE_CODE BOOL UnlockVolume(PVOLUME);
PRIVATE_CODE void InvalidateVolumeInfo(PVOLUME);
PRIVATE_CODE void ClearVolumeInfo(PVOLUME);
PRIVATE_CODE void GetUnavailableVolumeRootPath(PCLINKINFO, LPTSTR, int);
PRIVATE_CODE BOOL VerifyAvailableVolume(PVOLUME);
PRIVATE_CODE void ExpensiveResolveVolumeRootPath(PVOLUME, LPTSTR, int);
PRIVATE_CODE void ResolveVolumeRootPath(PVOLUME, LPTSTR, int);
PRIVATE_CODE VOLUMERESULT VOLUMERESULTFromLastError(VOLUMERESULT);
PRIVATE_CODE TWINRESULT WriteVolume(HCACHEDFILE, PVOLUME);
PRIVATE_CODE TWINRESULT ReadVolume(HCACHEDFILE, PVOLUMELIST, PLINKINFO, UINT, HHANDLETRANS);

#if defined(DEBUG) || defined(VSTF)

PRIVATE_CODE BOOL IsValidPCVOLUMELIST(PCVOLUMELIST);
PRIVATE_CODE BOOL IsValidVOLUMESTATE(VOLUMESTATE);
PRIVATE_CODE BOOL IsValidPCVOLUME(PCVOLUME);

#endif

#ifdef DEBUG

PRIVATE_CODE BOOL IsValidPCVOLUMEDESC(PCVOLUMEDESC);

#endif


/*
 ** VolumeSortCmp()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 **
 ** Volumes are sorted by:
 **    1) LinkInfo volume
 **    2) pointer
 */
PRIVATE_CODE COMPARISONRESULT VolumeSortCmp(PCVOID pcvol1, PCVOID pcvol2)
{
    COMPARISONRESULT cr;

    ASSERT(IS_VALID_STRUCT_PTR(pcvol1, CVOLUME));
    ASSERT(IS_VALID_STRUCT_PTR(pcvol2, CVOLUME));

    cr = CompareLinkInfoVolumes(((PCVOLUME)pcvol1)->pli,
            ((PCVOLUME)pcvol2)->pli);

    if (cr == CR_EQUAL)
        cr = ComparePointers(pcvol1, pcvol1);

    return(cr);
}


/*
 ** VolumeSearchCmp()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 **
 ** Volumes are searched by:
 **    1) LinkInfo volume
 */
PRIVATE_CODE COMPARISONRESULT VolumeSearchCmp(PCVOID pcli, PCVOID pcvol)
{
    ASSERT(IS_VALID_STRUCT_PTR(pcli, CLINKINFO));
    ASSERT(IS_VALID_STRUCT_PTR(pcvol, CVOLUME));

    return(CompareLinkInfoVolumes(pcli, ((PCVOLUME)pcvol)->pli));
}


/*
 ** SearchForVolumeByRootPathCmp()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 **
 ** Volumes are searched by:
 **    1) available volume root path
 */
PRIVATE_CODE BOOL SearchForVolumeByRootPathCmp(PCVOID pcszFullPath,
        PCVOID pcvol)
{
    BOOL bDifferent;

    ASSERT(IsFullPath(pcszFullPath));
    ASSERT(IS_VALID_STRUCT_PTR(pcvol, CVOLUME));

    if (((PCVOLUME)pcvol)->vs == VS_AVAILABLE &&
            IS_FLAG_SET(((PCVOLUME)pcvol)->dwFlags, VOLUME_FL_ROOT_PATH_VALID))
    {
        LPCTSTR pcszVolumeRootPath;

        pcszVolumeRootPath = GetString(((PCVOLUME)pcvol)->hsRootPath);

        bDifferent = MyLStrCmpNI(pcszFullPath, pcszVolumeRootPath,
                lstrlen(pcszVolumeRootPath));
    }
    else
        bDifferent = TRUE;

    return(bDifferent);
}


/*
 ** UnifyVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL UnifyVolume(PVOLUMELIST pvl, PLINKINFO pliRoot,
        PVOLUME *ppvol)
{
    BOOL bResult = FALSE;

    ASSERT(IS_VALID_STRUCT_PTR(pvl, CVOLUMELIST));
    ASSERT(IS_VALID_STRUCT_PTR(pliRoot, CLINKINFO));
    ASSERT(IS_VALID_WRITE_PTR(ppvol, PVOLUME));

    if (AllocateMemory(sizeof(**ppvol), ppvol))
    {
        if (CopyLinkInfo(pliRoot, &((*ppvol)->pli)))
        {
            ARRAYINDEX aiUnused;

            (*ppvol)->ulcLock = 0;
            (*ppvol)->dwFlags = 0;
            (*ppvol)->vs = VS_UNKNOWN;
            (*ppvol)->hsRootPath = NULL;
            (*ppvol)->pvlParent = pvl;

            if (AddPtr(pvl->hpa, VolumeSortCmp, *ppvol, &aiUnused))
                bResult = TRUE;
            else
            {
                FreeMemory((*ppvol)->pli);
UNIFYVOLUME_BAIL:
                FreeMemory(*ppvol);
            }
        }
        else
            goto UNIFYVOLUME_BAIL;
    }

    ASSERT(! bResult ||
            IS_VALID_STRUCT_PTR(*ppvol, CVOLUME));

    return(bResult);
}


/*
 ** CreateVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL CreateVolume(PVOLUMELIST pvl, PLINKINFO pliRoot,
        PVOLUME *ppvol)
{
    BOOL bResult;
    PVOLUME pvol;
    ARRAYINDEX aiFound;

    ASSERT(IS_VALID_STRUCT_PTR(pvl, CVOLUMELIST));
    ASSERT(IS_VALID_STRUCT_PTR(pliRoot, CLINKINFO));
    ASSERT(IS_VALID_WRITE_PTR(ppvol, PVOLUME));

    /* Does a volume for the given root path already exist? */

    if (SearchSortedArray(pvl->hpa, &VolumeSearchCmp, pliRoot, &aiFound))
    {
        pvol = GetPtr(pvl->hpa, aiFound);
        bResult = TRUE;
    }
    else
        bResult = UnifyVolume(pvl, pliRoot, &pvol);

    if (bResult)
    {
        LockVolume(pvol);
        *ppvol = pvol;
    }

    ASSERT(! bResult ||
            IS_VALID_STRUCT_PTR(*ppvol, CVOLUME));

    return(bResult);
}


/*
 ** UnlinkVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void UnlinkVolume(PCVOLUME pcvol)
{
    HPTRARRAY hpa;
    ARRAYINDEX aiFound;

    ASSERT(IS_VALID_STRUCT_PTR(pcvol, CVOLUME));

    hpa = pcvol->pvlParent->hpa;

    if (EVAL(SearchSortedArray(hpa, &VolumeSortCmp, pcvol, &aiFound)))
    {
        ASSERT(GetPtr(hpa, aiFound) == pcvol);

        DeletePtr(hpa, aiFound);
    }

    return;
}


/*
 ** DisconnectVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL DisconnectVolume(PVOLUME pvol)
{
    BOOL bResult;

    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    if (IS_FLAG_SET(pvol->dwFlags, VOLUME_FL_DISCONNECT))
    {
        bResult = DisconnectLinkInfo(pvol->pli);

        CLEAR_FLAG(pvol->dwFlags, VOLUME_FL_DISCONNECT);
    }
    else
        bResult = TRUE;

    return(bResult);
}


/*
 ** DestroyVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void DestroyVolume(PVOLUME pvol)
{
    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    ClearVolumeInfo(pvol);

    FreeMemory(pvol->pli);
    FreeMemory(pvol);

    return;
}


/*
 ** LockVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void LockVolume(PVOLUME pvol)
{
    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    ASSERT(pvol->ulcLock < ULONG_MAX);
    pvol->ulcLock++;

    return;
}


/*
 ** UnlockVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL UnlockVolume(PVOLUME pvol)
{
    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    if (EVAL(pvol->ulcLock > 0))
        pvol->ulcLock--;

    return(pvol->ulcLock > 0);
}


/*
 ** InvalidateVolumeInfo()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void InvalidateVolumeInfo(PVOLUME pvol)
{
    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    SET_FLAG(pvol->dwFlags, VOLUME_FL_VERIFY_VOLUME);

    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    return;
}


/*
 ** ClearVolumeInfo()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void ClearVolumeInfo(PVOLUME pvol)
{
    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    DisconnectVolume(pvol);

    if (IS_FLAG_SET(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID))
    {
        DeleteString(pvol->hsRootPath);

        CLEAR_FLAG(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID);
    }

    CLEAR_FLAG(pvol->dwFlags, VOLUME_FL_VERIFY_VOLUME);

    pvol->vs = VS_UNKNOWN;

    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    return;
}


/*
 ** GetUnavailableVolumeRootPath()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void GetUnavailableVolumeRootPath(PCLINKINFO pcli,
        LPTSTR pszRootPathBuf,
        int cchMax)
{
    LPCSTR pcszLinkInfoData;

    ASSERT(IS_VALID_STRUCT_PTR(pcli, CLINKINFO));
    ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszRootPathBuf, STR, cchMax));

    /*
     * Try unavailable volume root paths in the following order:
     *    1) last redirected device
     *    2) net resource name
     *    3) local path           ...and take the _last_ good one!
     */

    if (GetLinkInfoData(pcli, LIDT_REDIRECTED_DEVICE, &pcszLinkInfoData) ||
            GetLinkInfoData(pcli, LIDT_NET_RESOURCE, &pcszLinkInfoData) ||
            GetLinkInfoData(pcli, LIDT_LOCAL_BASE_PATH, &pcszLinkInfoData))
    {
        //ASSERT(IS_VALID_STRING_PTR(pcszLinkInfoData, CSTR));
        ASSERT(lstrlenA(pcszLinkInfoData) < MAX_PATH_LEN);

        // REARCHITECT somewhere, someone might need to handle unicode base paths 

#ifdef UNICODE
        {
            WCHAR szTmp[MAX_PATH] = TEXT("");
            MultiByteToWideChar(CP_ACP, 0, pcszLinkInfoData, -1, szTmp, ARRAYSIZE(szTmp));
            ComposePath(pszRootPathBuf, szTmp, TEXT("\\"), cchMax);
        }
#else

        ComposePath(pszRootPathBuf, pcszLinkInfoData, TEXT("\\"), cchMax);

#endif

    }
    else
    {
        pszRootPathBuf[0] = TEXT('\0');

        ERROR_OUT((TEXT("GetUnavailableVolumeRootPath(): Net resource name and local base path unavailable.  Using empty string as unavailable root path.")));
    }

    ASSERT(IsRootPath(pszRootPathBuf) &&
            EVAL(lstrlen(pszRootPathBuf) < MAX_PATH_LEN));

    return;
}


/*
 ** VerifyAvailableVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL VerifyAvailableVolume(PVOLUME pvol)
{
    BOOL bResult = FALSE;
    PLINKINFO pli;

    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    ASSERT(pvol->vs == VS_AVAILABLE);
    ASSERT(IS_FLAG_SET(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID));

    WARNING_OUT((TEXT("VerifyAvailableVolume(): Calling CreateLinkInfo() to verify volume on %s."),
                GetString(pvol->hsRootPath)));

    if (CreateLinkInfo(GetString(pvol->hsRootPath), &pli))
    {
        bResult = (CompareLinkInfoReferents(pvol->pli, pli) == CR_EQUAL);

        DestroyLinkInfo(pli);

        if (bResult)
            TRACE_OUT((TEXT("VerifyAvailableVolume(): Volume %s has not changed."),
                        GetString(pvol->hsRootPath)));
        else
            WARNING_OUT((TEXT("VerifyAvailableVolume(): Volume %s has changed."),
                        GetString(pvol->hsRootPath)));
    }
    else
        WARNING_OUT((TEXT("VerifyAvailableVolume(): CreateLinkInfo() failed for %s."),
                    GetString(pvol->hsRootPath)));

    return(bResult);
}


/*
 ** ExpensiveResolveVolumeRootPath()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void ExpensiveResolveVolumeRootPath(PVOLUME pvol,
        LPTSTR pszVolumeRootPathBuf,
        int cchMax)
{
    BOOL bResult;
    DWORD dwOutFlags;
    PLINKINFO pliUpdated;
    HSTRING hsRootPath;

    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));
    ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszVolumeRootPathBuf, STR, cchMax));

    if (pvol->vs == VS_UNKNOWN ||
            pvol->vs == VS_AVAILABLE)
    {
        /*
         * Only request a connection if connections are still permitted in this
         * volume list.
         */

        WARNING_OUT((TEXT("ExpensiveResolveVolumeRootPath(): Calling ResolveLinkInfo() to determine volume availability and root path.")));

        bResult = ResolveLinkInfo(pvol->pli, pszVolumeRootPathBuf,
                pvol->pvlParent->dwFlags,
                pvol->pvlParent->hwndOwner, &dwOutFlags,
                &pliUpdated);

        if (bResult)
        {
            pvol->vs = VS_AVAILABLE;

            if (IS_FLAG_SET(dwOutFlags, RLI_OFL_UPDATED))
            {
                PLINKINFO pliUpdatedCopy;

                ASSERT(IS_FLAG_SET(pvol->pvlParent->dwFlags, RLI_IFL_UPDATE));

                if (CopyLinkInfo(pliUpdated, &pliUpdatedCopy))
                {
                    FreeMemory(pvol->pli);
                    pvol->pli = pliUpdatedCopy;
                }

                DestroyLinkInfo(pliUpdated);

                WARNING_OUT((TEXT("ExpensiveResolveVolumeRootPath(): Updating LinkInfo for volume %s."),
                            pszVolumeRootPathBuf));
            }

            if (IS_FLAG_SET(dwOutFlags, RLI_OFL_DISCONNECT))
            {
                SET_FLAG(pvol->dwFlags, VOLUME_FL_DISCONNECT);

                WARNING_OUT((TEXT("ExpensiveResolveVolumeRootPath(): Volume %s must be disconnected when finished."),
                            pszVolumeRootPathBuf));
            }

            TRACE_OUT((TEXT("ExpensiveResolveVolumeRootPath(): Volume %s is available."),
                        pszVolumeRootPathBuf));
        }
        else
            ASSERT(GetLastError() != ERROR_INVALID_PARAMETER);
    }
    else
    {
        ASSERT(pvol->vs == VS_UNAVAILABLE);
        bResult = FALSE;
    }

    if (! bResult)
    {
        pvol->vs = VS_UNAVAILABLE;

        if (GetLastError() == ERROR_CANCELLED)
        {
            ASSERT(IS_FLAG_SET(pvol->pvlParent->dwFlags, RLI_IFL_CONNECT));

            CLEAR_FLAG(pvol->pvlParent->dwFlags, RLI_IFL_CONNECT);

            WARNING_OUT((TEXT("ExpensiveResolveVolumeRootPath(): Connection attempt cancelled.  No subsequent connections will be attempted.")));
        }

        GetUnavailableVolumeRootPath(pvol->pli, pszVolumeRootPathBuf, cchMax);

        WARNING_OUT((TEXT("ExpensiveResolveVolumeRootPath(): Using %s as unavailable volume root path."),
                    pszVolumeRootPathBuf));
    }

    /* Add volume root path string to volume list's string table. */

    if (IS_FLAG_SET(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID))
    {
        CLEAR_FLAG(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID);
        DeleteString(pvol->hsRootPath);
    }

    if (AddString(pszVolumeRootPathBuf, pvol->pvlParent->hst, GetHashBucketIndex, &hsRootPath))
    {
        SET_FLAG(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID);
        pvol->hsRootPath = hsRootPath;
    }
    else
        WARNING_OUT((TEXT("ExpensiveResolveVolumeRootPath(): Unable to save %s as volume root path."),
                    pszVolumeRootPathBuf));

    return;
}


/*
 ** ResolveVolumeRootPath()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE void ResolveVolumeRootPath(PVOLUME pvol,
        LPTSTR pszVolumeRootPathBuf,
        int cchMax)
{
    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));
    ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszVolumeRootPathBuf, STR, MAX_PATH_LEN));

    /* Do we have a cached volume root path to use? */

    if (IS_FLAG_SET(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID) &&
            (IS_FLAG_CLEAR(pvol->dwFlags, VOLUME_FL_VERIFY_VOLUME) ||
             (pvol->vs == VS_AVAILABLE &&
              VerifyAvailableVolume(pvol))))
    {
        /* Yes. */

        MyLStrCpyN(pszVolumeRootPathBuf, GetString(pvol->hsRootPath), cchMax);
        ASSERT(lstrlen(pszVolumeRootPathBuf) < MAX_PATH_LEN);

        ASSERT(pvol->vs != VS_UNKNOWN);
    }
    else
        /* No.  Welcome in I/O City. */
        ExpensiveResolveVolumeRootPath(pvol, pszVolumeRootPathBuf, cchMax);

    CLEAR_FLAG(pvol->dwFlags, VOLUME_FL_VERIFY_VOLUME);

    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    return;
}


/*
 ** VOLUMERESULTFromLastError()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE VOLUMERESULT VOLUMERESULTFromLastError(VOLUMERESULT vr)
{
    switch (GetLastError())
    {
        case ERROR_OUTOFMEMORY:
            vr = VR_OUT_OF_MEMORY;
            break;

        case ERROR_BAD_PATHNAME:
            vr = VR_INVALID_PATH;
            break;

        default:
            break;
    }

    return(vr);
}


/*
 ** WriteVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE TWINRESULT WriteVolume(HCACHEDFILE hcf, PVOLUME pvol)
{
    TWINRESULT tr;
    DBVOLUME dbvol;

    ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
    ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME));

    /* Write database volume followed by LinkInfo structure. */

    dbvol.hvol = (HVOLUME)pvol;

    if (WriteToCachedFile(hcf, (PCVOID)&dbvol, sizeof(dbvol), NULL) &&
            WriteToCachedFile(hcf, pvol->pli, *(PDWORD)(pvol->pli), NULL))
        tr = TR_SUCCESS;
    else
        tr = TR_BRIEFCASE_WRITE_FAILED;

    return(tr);
}


/*
 ** ReadVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE TWINRESULT ReadVolume(HCACHEDFILE hcf, PVOLUMELIST pvl,
        PLINKINFO pliBuf, UINT ucbLinkInfoBufLen,
        HHANDLETRANS hhtVolumes)
{
    TWINRESULT tr = TR_CORRUPT_BRIEFCASE;
    DBVOLUME dbvol;
    DWORD dwcbRead;
    UINT ucbLinkInfoLen;

    ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
    ASSERT(IS_VALID_STRUCT_PTR(pvl, CVOLUMELIST));
    ASSERT(IS_VALID_WRITE_BUFFER_PTR(pliBuf, LINKINFO, ucbLinkInfoBufLen));
    ASSERT(IS_VALID_HANDLE(hhtVolumes, HANDLETRANS));

    if (ReadFromCachedFile(hcf, &dbvol, sizeof(dbvol), &dwcbRead) &&
            dwcbRead == sizeof(dbvol) &&
            ReadFromCachedFile(hcf, &ucbLinkInfoLen, sizeof(ucbLinkInfoLen), &dwcbRead) &&
            dwcbRead == sizeof(ucbLinkInfoLen) &&
            ucbLinkInfoLen <= ucbLinkInfoBufLen)
    {
        /* Read the remainder of the LinkInfo structure into memory. */

        DWORD dwcbRemainder;

        pliBuf->ucbSize = ucbLinkInfoLen;
        dwcbRemainder = ucbLinkInfoLen - sizeof(ucbLinkInfoLen);

        if (ReadFromCachedFile(hcf, (PBYTE)pliBuf + sizeof(ucbLinkInfoLen),
                    dwcbRemainder, &dwcbRead) &&
                dwcbRead == dwcbRemainder &&
                IsValidLinkInfo(pliBuf))
        {
            PVOLUME pvol;

            if (CreateVolume(pvl, pliBuf, &pvol))
            {
                /*
                 * To leave read volumes with 0 initial lock count, we must undo
                 * the LockVolume() performed by CreateVolume().
                 */

                UnlockVolume(pvol);

                if (AddHandleToHandleTranslator(hhtVolumes,
                            (HGENERIC)(dbvol.hvol),
                            (HGENERIC)pvol))
                    tr = TR_SUCCESS;
                else
                {
                    UnlinkVolume(pvol);
                    DestroyVolume(pvol);

                    tr = TR_OUT_OF_MEMORY;
                }
            }
            else
                tr = TR_OUT_OF_MEMORY;
        }
    }

    return(tr);
}


#if defined(DEBUG) || defined(VSTF)

/*
 ** IsValidPCVOLUMELIST()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL IsValidPCVOLUMELIST(PCVOLUMELIST pcvl)
{
    return(IS_VALID_READ_PTR(pcvl, CVOLUMELIST) &&
            IS_VALID_HANDLE(pcvl->hpa, PTRARRAY) &&
            IS_VALID_HANDLE(pcvl->hst, STRINGTABLE) &&
            FLAGS_ARE_VALID(pcvl->dwFlags, ALL_RLI_IFLAGS) &&
            (IS_FLAG_CLEAR(pcvl->dwFlags, RLI_IFL_ALLOW_UI) ||
             IS_VALID_HANDLE(pcvl->hwndOwner, WND)));
}


/*
 ** IsValidVOLUMESTATE()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL IsValidVOLUMESTATE(VOLUMESTATE vs)
{
    BOOL bResult;

    switch (vs)
    {
        case VS_UNKNOWN:
        case VS_AVAILABLE:
        case VS_UNAVAILABLE:
            bResult = TRUE;
            break;

        default:
            ERROR_OUT((TEXT("IsValidVOLUMESTATE(): Invalid VOLUMESTATE %d."),
                        vs));
            bResult = FALSE;
            break;
    }

    return(bResult);
}


/*
 ** IsValidPCVOLUME()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL IsValidPCVOLUME(PCVOLUME pcvol)
{
    return(IS_VALID_READ_PTR(pcvol, CVOLUME) &&
            FLAGS_ARE_VALID(pcvol->dwFlags, ALL_VOLUME_FLAGS) &&
            EVAL(IsValidVOLUMESTATE(pcvol->vs)) &&
            IS_VALID_STRUCT_PTR(pcvol->pli, CLINKINFO) &&
            (IS_FLAG_CLEAR(pcvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID) ||
             IS_VALID_HANDLE(pcvol->hsRootPath, STRING)) &&
            IS_VALID_STRUCT_PTR(pcvol->pvlParent, CVOLUMELIST));
}

#endif


#ifdef DEBUG

/*
 ** IsValidPCVOLUMEDESC()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PRIVATE_CODE BOOL IsValidPCVOLUMEDESC(PCVOLUMEDESC pcvoldesc)
{
    /*
     * A set dwSerialNumber may be any value.  An unset dwSerialNumber must be
     * 0.  A set strings may be any valid string.  An unset string must be the
     * empty string.
     */

    return(IS_VALID_READ_PTR(pcvoldesc, CVOLUMEDESC) &&
            EVAL(pcvoldesc->ulSize == sizeof(*pcvoldesc)) &&
            FLAGS_ARE_VALID(pcvoldesc->dwFlags, ALL_VD_FLAGS) &&
            (IS_FLAG_SET(pcvoldesc->dwFlags, VD_FL_SERIAL_NUMBER_VALID) ||
             ! pcvoldesc->dwSerialNumber) &&
            ((IS_FLAG_CLEAR(pcvoldesc->dwFlags, VD_FL_VOLUME_LABEL_VALID) &&
              ! pcvoldesc->rgchVolumeLabel[0]) ||
             (IS_FLAG_SET(pcvoldesc->dwFlags, VD_FL_VOLUME_LABEL_VALID) &&
              IS_VALID_STRING_PTR(pcvoldesc->rgchVolumeLabel, CSTR) &&
              EVAL(lstrlen(pcvoldesc->rgchVolumeLabel) < ARRAYSIZE(pcvoldesc->rgchVolumeLabel)))) &&
            ((IS_FLAG_CLEAR(pcvoldesc->dwFlags, VD_FL_NET_RESOURCE_VALID) &&
              ! pcvoldesc->rgchNetResource[0]) ||
             (IS_FLAG_SET(pcvoldesc->dwFlags, VD_FL_NET_RESOURCE_VALID) &&
              IS_VALID_STRING_PTR(pcvoldesc->rgchNetResource, CSTR) &&
              EVAL(lstrlen(pcvoldesc->rgchNetResource) < ARRAYSIZE(pcvoldesc->rgchNetResource)))));
}

#endif


/****************************** Public Functions *****************************/


/*
 ** CreateVolumeList()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE BOOL CreateVolumeList(DWORD dwFlags, HWND hwndOwner,
        PHVOLUMELIST phvl)
{
    BOOL bResult = FALSE;
    PVOLUMELIST pvl;

    ASSERT(FLAGS_ARE_VALID(dwFlags, ALL_RLI_IFLAGS));
    ASSERT(IS_FLAG_CLEAR(dwFlags, RLI_IFL_ALLOW_UI) ||
            IS_VALID_HANDLE(hwndOwner, WND));
    ASSERT(IS_VALID_WRITE_PTR(phvl, HVOLUMELIST));

    if (AllocateMemory(sizeof(*pvl), &pvl))
    {
        NEWSTRINGTABLE nszt;

        /* Create string table for volume root path strngs. */

        nszt.hbc = NUM_VOLUME_HASH_BUCKETS;

        if (CreateStringTable(&nszt, &(pvl->hst)))
        {
            NEWPTRARRAY npa;

            /* Create pointer array of volumes. */

            npa.aicInitialPtrs = NUM_START_VOLUMES;
            npa.aicAllocGranularity = NUM_VOLUMES_TO_ADD;
            npa.dwFlags = NPA_FL_SORTED_ADD;

            if (CreatePtrArray(&npa, &(pvl->hpa)))
            {
                pvl->dwFlags = dwFlags;
                pvl->hwndOwner = hwndOwner;

                *phvl = (HVOLUMELIST)pvl;
                bResult = TRUE;
            }
            else
            {
                DestroyStringTable(pvl->hst);
CREATEVOLUMELIST_BAIL:
                FreeMemory(pvl);
            }
        }
        else
            goto CREATEVOLUMELIST_BAIL;
    }

    ASSERT(! bResult ||
            IS_VALID_HANDLE(*phvl, VOLUMELIST));

    return(bResult);
}


/*
 ** DestroyVolumeList()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE void DestroyVolumeList(HVOLUMELIST hvl)
{
    ARRAYINDEX aicPtrs;
    ARRAYINDEX ai;

    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));

    /* First free all volumes in array. */

    aicPtrs = GetPtrCount(((PCVOLUMELIST)hvl)->hpa);

    for (ai = 0; ai < aicPtrs; ai++)
        DestroyVolume(GetPtr(((PCVOLUMELIST)hvl)->hpa, ai));

    /* Now wipe out the array. */

    DestroyPtrArray(((PCVOLUMELIST)hvl)->hpa);

    ASSERT(! GetStringCount(((PCVOLUMELIST)hvl)->hst));
    DestroyStringTable(((PCVOLUMELIST)hvl)->hst);

    FreeMemory((PVOLUMELIST)hvl);

    return;
}


/*
 ** InvalidateVolumeListInfo()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE void InvalidateVolumeListInfo(HVOLUMELIST hvl)
{
    ARRAYINDEX aicPtrs;
    ARRAYINDEX ai;

    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));

    aicPtrs = GetPtrCount(((PCVOLUMELIST)hvl)->hpa);

    for (ai = 0; ai < aicPtrs; ai++)
        InvalidateVolumeInfo(GetPtr(((PCVOLUMELIST)hvl)->hpa, ai));

    WARNING_OUT((TEXT("InvalidateVolumeListInfo(): Volume cache invalidated.")));

    return;
}


/*
 ** ClearVolumeListInfo()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE void ClearVolumeListInfo(HVOLUMELIST hvl)
{
    ARRAYINDEX aicPtrs;
    ARRAYINDEX ai;

    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));

    aicPtrs = GetPtrCount(((PCVOLUMELIST)hvl)->hpa);

    for (ai = 0; ai < aicPtrs; ai++)
        ClearVolumeInfo(GetPtr(((PCVOLUMELIST)hvl)->hpa, ai));

    WARNING_OUT((TEXT("ClearVolumeListInfo(): Volume cache cleared.")));

    return;
}


/*
 ** AddVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE VOLUMERESULT AddVolume(HVOLUMELIST hvl, LPCTSTR pcszPath,
        PHVOLUME phvol, LPTSTR pszPathSuffixBuf,
        int cchMax)
{
    VOLUMERESULT vr;
    TCHAR rgchPath[MAX_PATH_LEN];
    LPTSTR pszFileName;
    DWORD dwPathLen;

    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));
    ASSERT(IS_VALID_STRING_PTR(pcszPath, CSTR));
    ASSERT(IS_VALID_WRITE_PTR(phvol, HVOLUME));
    ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszPathSuffixBuf, STR, cchMax));

    dwPathLen = GetFullPathName(pcszPath, ARRAYSIZE(rgchPath), rgchPath,
            &pszFileName);

    if (dwPathLen > 0 && dwPathLen < ARRAYSIZE(rgchPath))
    {
        ARRAYINDEX aiFound;

        /* Does a volume for this root path already exist? */

        if (LinearSearchArray(((PVOLUMELIST)hvl)->hpa,
                    &SearchForVolumeByRootPathCmp, rgchPath,
                    &aiFound))
        {
            PVOLUME pvol;
            LPCTSTR pcszVolumeRootPath;

            /* Yes. */

            pvol = GetPtr(((PVOLUMELIST)hvl)->hpa, aiFound);

            LockVolume(pvol);

            ASSERT(pvol->vs == VS_AVAILABLE &&
                    IS_FLAG_SET(pvol->dwFlags, VOLUME_FL_ROOT_PATH_VALID));

            pcszVolumeRootPath = GetString(pvol->hsRootPath);

            ASSERT(lstrlen(pcszVolumeRootPath) <= lstrlen(rgchPath));

            lstrcpyn(pszPathSuffixBuf, rgchPath + lstrlen(pcszVolumeRootPath), cchMax);

            *phvol = (HVOLUME)pvol;
            vr = VR_SUCCESS;
        }
        else
        {
            DWORD dwOutFlags;
            TCHAR rgchNetResource[MAX_PATH_LEN];
            LPTSTR pszRootPathSuffix;

            /* No.  Create a new volume. */

            if (GetCanonicalPathInfo(pcszPath, rgchPath, &dwOutFlags,
                        rgchNetResource, &pszRootPathSuffix))
            {
                PLINKINFO pli;

                lstrcpyn(pszPathSuffixBuf, pszRootPathSuffix, cchMax);
                *pszRootPathSuffix = TEXT('\0');

                WARNING_OUT((TEXT("AddVolume(): Creating LinkInfo for root path %s."),
                            rgchPath));

                if (CreateLinkInfo(rgchPath, &pli))
                {
                    PVOLUME pvol;

                    if (CreateVolume((PVOLUMELIST)hvl, pli, &pvol))
                    {
                        TCHAR rgchUnusedVolumeRootPath[MAX_PATH_LEN];

                        ResolveVolumeRootPath(pvol, rgchUnusedVolumeRootPath, ARRAYSIZE(rgchUnusedVolumeRootPath));

                        *phvol = (HVOLUME)pvol;
                        vr = VR_SUCCESS;
                    }
                    else
                        vr = VR_OUT_OF_MEMORY;

                    DestroyLinkInfo(pli);
                }
                else
                    /*
                     * Differentiate between VR_UNAVAILABLE_VOLUME and
                     * VR_OUT_OF_MEMORY.
                     */
                    vr = VOLUMERESULTFromLastError(VR_UNAVAILABLE_VOLUME);
            }
            else
                vr = VOLUMERESULTFromLastError(VR_INVALID_PATH);
        }
    }
    else
    {
        ASSERT(! dwPathLen);

        vr = VOLUMERESULTFromLastError(VR_INVALID_PATH);
    }

    ASSERT(vr != VR_SUCCESS ||
            (IS_VALID_HANDLE(*phvol, VOLUME) &&
             EVAL(IsValidPathSuffix(pszPathSuffixBuf))));

    return(vr);
}


/*
 ** DeleteVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE void DeleteVolume(HVOLUME hvol)
{
    ASSERT(IS_VALID_HANDLE(hvol, VOLUME));

    if (! UnlockVolume((PVOLUME)hvol))
    {
        UnlinkVolume((PVOLUME)hvol);
        DestroyVolume((PVOLUME)hvol);
    }

    return;
}


/*
 ** CompareVolumes()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE COMPARISONRESULT CompareVolumes(HVOLUME hvolFirst,
        HVOLUME hvolSecond)
{
    ASSERT(IS_VALID_HANDLE(hvolFirst, VOLUME));
    ASSERT(IS_VALID_HANDLE(hvolSecond, VOLUME));

    /* This comparison works across volume lists. */

    return(CompareLinkInfoVolumes(((PCVOLUME)hvolFirst)->pli,
                ((PCVOLUME)hvolSecond)->pli));
}


/*
 ** CopyVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE BOOL CopyVolume(HVOLUME hvolSrc, HVOLUMELIST hvlDest,
        PHVOLUME phvolCopy)
{
    BOOL bResult;
    PVOLUME pvol;

    ASSERT(IS_VALID_HANDLE(hvolSrc, VOLUME));
    ASSERT(IS_VALID_HANDLE(hvlDest, VOLUMELIST));
    ASSERT(IS_VALID_WRITE_PTR(phvolCopy, HVOLUME));

    /* Is the destination volume list the source volume's volume list? */

    if (((PCVOLUME)hvolSrc)->pvlParent == (PCVOLUMELIST)hvlDest)
    {
        /* Yes.  Use the source volume. */

        LockVolume((PVOLUME)hvolSrc);
        pvol = (PVOLUME)hvolSrc;
        bResult = TRUE;
    }
    else
        bResult = CreateVolume((PVOLUMELIST)hvlDest, ((PCVOLUME)hvolSrc)->pli,
                &pvol);

    if (bResult)
        *phvolCopy = (HVOLUME)pvol;

    ASSERT(! bResult ||
            IS_VALID_HANDLE(*phvolCopy, VOLUME));

    return(bResult);
}


/*
 ** IsVolumeAvailable()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE BOOL IsVolumeAvailable(HVOLUME hvol)
{
    TCHAR rgchUnusedVolumeRootPath[MAX_PATH_LEN];

    ASSERT(IS_VALID_HANDLE(hvol, VOLUME));

    ResolveVolumeRootPath((PVOLUME)hvol, rgchUnusedVolumeRootPath, ARRAYSIZE(rgchUnusedVolumeRootPath));

    ASSERT(IsValidVOLUMESTATE(((PCVOLUME)hvol)->vs) &&
            ((PCVOLUME)hvol)->vs != VS_UNKNOWN);

    return(((PCVOLUME)hvol)->vs == VS_AVAILABLE);
}


/*
 ** GetVolumeRootPath()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE void GetVolumeRootPath(HVOLUME hvol, LPTSTR pszRootPathBuf, int cchMax)
{
    ASSERT(IS_VALID_HANDLE(hvol, VOLUME));
    ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszRootPathBuf, STR, cchMax));

    ResolveVolumeRootPath((PVOLUME)hvol, pszRootPathBuf, cchMax);

    ASSERT(IsRootPath(pszRootPathBuf));

    return;
}


#ifdef DEBUG

/*
 ** DebugGetVolumeRootPath()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 **
 ** N.b., DebugGetVolumeRootPath() must be non-intrusive.
 */
PUBLIC_CODE LPTSTR DebugGetVolumeRootPath(HVOLUME hvol, LPTSTR pszRootPathBuf, int cchMax)
{
    ASSERT(IS_VALID_HANDLE(hvol, VOLUME));
    ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszRootPathBuf, STR, cchMax));

    if (IS_FLAG_SET(((PVOLUME)hvol)->dwFlags, VOLUME_FL_ROOT_PATH_VALID))
        MyLStrCpyN(pszRootPathBuf, GetString(((PVOLUME)hvol)->hsRootPath), cchMax);
    else
        GetUnavailableVolumeRootPath(((PVOLUME)hvol)->pli, pszRootPathBuf, cchMax);

    ASSERT(IsRootPath(pszRootPathBuf));

    return(pszRootPathBuf);
}


/*
 ** GetVolumeCount()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE ULONG GetVolumeCount(HVOLUMELIST hvl)
{
    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));

    return(GetPtrCount(((PCVOLUMELIST)hvl)->hpa));
}

#endif


/*
 ** DescribeVolume()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE void DescribeVolume(HVOLUME hvol, PVOLUMEDESC pvoldesc)
{
    PCVOID pcv;

    ASSERT(IS_VALID_HANDLE(hvol, VOLUME));
    ASSERT(IS_VALID_WRITE_PTR(pvoldesc, VOLUMEDESC));

    ASSERT(pvoldesc->ulSize == sizeof(*pvoldesc));

    pvoldesc->dwFlags = 0;

    if (GetLinkInfoData(((PCVOLUME)hvol)->pli, LIDT_VOLUME_SERIAL_NUMBER, &pcv))
    {
        pvoldesc->dwSerialNumber = *(PCDWORD)pcv;
        SET_FLAG(pvoldesc->dwFlags, VD_FL_SERIAL_NUMBER_VALID);
    }
    else
        pvoldesc->dwSerialNumber = 0;

    if (GetLinkInfoData(((PCVOLUME)hvol)->pli, LIDT_VOLUME_LABELW, &pcv) && pcv)
    {
        lstrcpyn(pvoldesc->rgchVolumeLabel, pcv, ARRAYSIZE(pvoldesc->rgchVolumeLabel));
        SET_FLAG(pvoldesc->dwFlags, VD_FL_VOLUME_LABEL_VALID);
    }
    else if (GetLinkInfoData(((PCVOLUME)hvol)->pli, LIDT_VOLUME_LABEL, &pcv) && pcv)
    {
        MultiByteToWideChar(CP_ACP, 0, pcv, -1, pvoldesc->rgchVolumeLabel, ARRAYSIZE(pvoldesc->rgchVolumeLabel));
        SET_FLAG(pvoldesc->dwFlags, VD_FL_VOLUME_LABEL_VALID);
    }
    else
    {
        pvoldesc->rgchVolumeLabel[0] = TEXT('\0');
    }

    if (GetLinkInfoData(((PCVOLUME)hvol)->pli, LIDT_NET_RESOURCEW, &pcv) && pcv)
    {
        lstrcpyn(pvoldesc->rgchNetResource, pcv, ARRAYSIZE(pvoldesc->rgchNetResource));
        SET_FLAG(pvoldesc->dwFlags, VD_FL_NET_RESOURCE_VALID);
    }
    else if (GetLinkInfoData(((PCVOLUME)hvol)->pli, LIDT_NET_RESOURCE, &pcv) && pcv)
    {
        MultiByteToWideChar(CP_ACP, 0, pcv, -1, pvoldesc->rgchNetResource, ARRAYSIZE(pvoldesc->rgchNetResource));
        SET_FLAG(pvoldesc->dwFlags, VD_FL_NET_RESOURCE_VALID);
    }
    else
        pvoldesc->rgchNetResource[0] = TEXT('\0');

    ASSERT(IS_VALID_STRUCT_PTR(pvoldesc, CVOLUMEDESC));

    return;
}


/*
 ** WriteVolumeList()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE TWINRESULT WriteVolumeList(HCACHEDFILE hcf, HVOLUMELIST hvl)
{
    TWINRESULT tr = TR_BRIEFCASE_WRITE_FAILED;
    DWORD dwcbDBVolumeListHeaderOffset;

    ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));

    /* Save initial file position. */

    dwcbDBVolumeListHeaderOffset = GetCachedFilePointerPosition(hcf);

    if (dwcbDBVolumeListHeaderOffset != INVALID_SEEK_POSITION)
    {
        DBVOLUMELISTHEADER dbvlh;

        /* Leave space for volume list header. */

        ZeroMemory(&dbvlh, sizeof(dbvlh));

        if (WriteToCachedFile(hcf, (PCVOID)&dbvlh, sizeof(dbvlh), NULL))
        {
            ARRAYINDEX aicPtrs;
            ARRAYINDEX ai;
            UINT ucbMaxLinkInfoLen = 0;
            LONG lcVolumes = 0;

            tr = TR_SUCCESS;

            aicPtrs = GetPtrCount(((PCVOLUMELIST)hvl)->hpa);

            /* Write all volumes. */

            for (ai = 0; ai < aicPtrs; ai++)
            {
                PVOLUME pvol;

                pvol = GetPtr(((PCVOLUMELIST)hvl)->hpa, ai);

                /*
                 * As a sanity check, don't save any volume with a lock count of 0.
                 * A 0 lock count implies that the volume has not been referenced
                 * since it was restored from the database, or something is broken.
                 */

                if (pvol->ulcLock > 0)
                {
                    tr = WriteVolume(hcf, pvol);

                    if (tr == TR_SUCCESS)
                    {
                        ASSERT(lcVolumes < LONG_MAX);
                        lcVolumes++;

                        if (pvol->pli->ucbSize > ucbMaxLinkInfoLen)
                            ucbMaxLinkInfoLen = pvol->pli->ucbSize;
                    }
                    else
                        break;
                }
                else
                    ERROR_OUT((TEXT("WriteVolumeList(): VOLUME has 0 lock count and will not be written.")));
            }

            /* Save volume list header. */

            if (tr == TR_SUCCESS)
            {
                dbvlh.lcVolumes = lcVolumes;
                dbvlh.ucbMaxLinkInfoLen = ucbMaxLinkInfoLen;

                tr = WriteDBSegmentHeader(hcf, dwcbDBVolumeListHeaderOffset,
                        &dbvlh, sizeof(dbvlh));

                TRACE_OUT((TEXT("WriteVolumeList(): Wrote %ld volumes; maximum LinkInfo length %u bytes."),
                            dbvlh.lcVolumes,
                            dbvlh.ucbMaxLinkInfoLen));
            }
        }
    }

    return(tr);
}


/*
 ** ReadVolumeList()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE TWINRESULT ReadVolumeList(HCACHEDFILE hcf, HVOLUMELIST hvl,
        PHHANDLETRANS phht)
{
    TWINRESULT tr;
    DBVOLUMELISTHEADER dbvlh;
    DWORD dwcbRead;

    ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));
    ASSERT(IS_VALID_WRITE_PTR(phht, HHANDLETRANS));

    if (ReadFromCachedFile(hcf, &dbvlh, sizeof(dbvlh), &dwcbRead) &&
            dwcbRead == sizeof(dbvlh))
    {
        HHANDLETRANS hht;

        tr = TR_OUT_OF_MEMORY;

        if (CreateHandleTranslator(dbvlh.lcVolumes, &hht))
        {
            PLINKINFO pliBuf;

            if (AllocateMemory(dbvlh.ucbMaxLinkInfoLen, &pliBuf))
            {
                LONG l;

                tr = TR_SUCCESS;

                TRACE_OUT((TEXT("ReadPathList(): Reading %ld volumes; maximum LinkInfo length %u bytes."),
                            dbvlh.lcVolumes,
                            dbvlh.ucbMaxLinkInfoLen));

                for (l = 0; l < dbvlh.lcVolumes; l++)
                {
                    tr = ReadVolume(hcf, (PVOLUMELIST)hvl, pliBuf,
                            dbvlh.ucbMaxLinkInfoLen, hht);

                    if (tr != TR_SUCCESS)
                    {
                        break;
                    }
                }

                if (tr == TR_SUCCESS)
                {
                    PrepareForHandleTranslation(hht);
                    *phht = hht;

                    ASSERT(IS_VALID_HANDLE(hvl, VOLUMELIST));
                    ASSERT(IS_VALID_HANDLE(*phht, HANDLETRANS));
                }
                FreeMemory(pliBuf);
            }

            if (tr != TR_SUCCESS)
            {
                DestroyHandleTranslator(hht);
                hht = NULL;
            }
        }
    }
    else
    {
        tr = TR_CORRUPT_BRIEFCASE;
    }

    ASSERT(tr != TR_SUCCESS ||
            (IS_VALID_HANDLE(hvl, VOLUMELIST) &&
             IS_VALID_HANDLE(*phht, HANDLETRANS)));

    return(tr);
}


/*
 ** IsValidHVOLUME()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE BOOL IsValidHVOLUME(HVOLUME hvol)
{
    return(IS_VALID_STRUCT_PTR((PCVOLUME)hvol, CVOLUME));
}


#if defined(DEBUG) || defined(VSTF)

/*
 ** IsValidHVOLUMELIST()
 **
 **
 **
 ** Arguments:
 **
 ** Returns:
 **
 ** Side Effects:  none
 */
PUBLIC_CODE BOOL IsValidHVOLUMELIST(HVOLUMELIST hvl)
{
    return(IS_VALID_STRUCT_PTR((PCVOLUMELIST)hvl, CVOLUMELIST));
}

#endif