/* * 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); PRIVATE_CODE BOOL VerifyAvailableVolume(PVOLUME); PRIVATE_CODE void ExpensiveResolveVolumeRootPath(PVOLUME, LPTSTR); PRIVATE_CODE void ResolveVolumeRootPath(PVOLUME, LPTSTR); 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) { LPCSTR pcszLinkInfoData; ASSERT(IS_VALID_STRUCT_PTR(pcli, CLINKINFO)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszRootPathBuf, STR, MAX_PATH_LEN)); /* * 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); // BUGBUG somewhere, someone might need to handle unicode base paths #ifdef UNICODE { TCHAR szTmp[MAX_PATH] = TEXT(""); MultiByteToWideChar(CP_ACP, 0, pcszLinkInfoData, -1, szTmp, MAX_PATH); ComposePath(pszRootPathBuf, szTmp, TEXT("\\")); } #else ComposePath(pszRootPathBuf, pcszLinkInfoData, TEXT("\\")); #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) { BOOL bResult; DWORD dwOutFlags; PLINKINFO pliUpdated; HSTRING hsRootPath; ASSERT(IS_VALID_STRUCT_PTR(pvol, CVOLUME)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszVolumeRootPathBuf, STR, MAX_PATH_LEN)); 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); 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) { 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), MAX_PATH_LEN); ASSERT(lstrlen(pszVolumeRootPathBuf) < MAX_PATH_LEN); ASSERT(pvol->vs != VS_UNKNOWN); } else /* No. Welcome in I/O City. */ ExpensiveResolveVolumeRootPath(pvol, pszVolumeRootPathBuf); 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) { 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, MAX_PATH_LEN)); 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)); lstrcpy(pszPathSuffixBuf, rgchPath + lstrlen(pcszVolumeRootPath)); *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; lstrcpy(pszPathSuffixBuf, pszRootPathSuffix); *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); *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); 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) { ASSERT(IS_VALID_HANDLE(hvol, VOLUME)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszRootPathBuf, STR, MAX_PATH_LEN)); ResolveVolumeRootPath((PVOLUME)hvol, pszRootPathBuf); 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) { ASSERT(IS_VALID_HANDLE(hvol, VOLUME)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszRootPathBuf, STR, MAX_PATH_LEN)); if (IS_FLAG_SET(((PVOLUME)hvol)->dwFlags, VOLUME_FL_ROOT_PATH_VALID)) MyLStrCpyN(pszRootPathBuf, GetString(((PVOLUME)hvol)->hsRootPath), MAX_PATH_LEN); else GetUnavailableVolumeRootPath(((PVOLUME)hvol)->pli, pszRootPathBuf); 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) { lstrcpy(pvoldesc->rgchVolumeLabel, pcv); 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, MAX_PATH); 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) { lstrcpy(pvoldesc->rgchNetResource, pcv); 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, MAX_PATH); 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)); } else DestroyHandleTranslator(hht); FreeMemory(pliBuf); } } } 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