/* * path.c - Path ADT module. */ /* Headers **********/ #include "project.h" #pragma hdrstop #include "volume.h" /* Constants ************/ /* PATHLIST PTRARRAY allocation parameters */ #define NUM_START_PATHS (32) #define NUM_PATHS_TO_ADD (32) /* PATHLIST string table allocation parameters */ #define NUM_PATH_HASH_BUCKETS (67) /* Types ********/ /* path list */ typedef struct _pathlist { /* array of pointers to PATHs */ HPTRARRAY hpa; /* list of volumes */ HVOLUMELIST hvl; /* table of path suffix strings */ HSTRINGTABLE hst; } PATHLIST; DECLARE_STANDARD_TYPES(PATHLIST); /* path structure */ typedef struct _path { /* reference count */ ULONG ulcLock; /* handle to parent volume */ HVOLUME hvol; /* handle to path suffix string */ HSTRING hsPathSuffix; /* pointer to PATH's parent PATHLIST */ PPATHLIST pplParent; } PATH; DECLARE_STANDARD_TYPES(PATH); /* PATH search structure used by PathSearchCmp() */ typedef struct _pathsearchinfo { HVOLUME hvol; LPCTSTR pcszPathSuffix; } PATHSEARCHINFO; DECLARE_STANDARD_TYPES(PATHSEARCHINFO); /* database path list header */ typedef struct _dbpathlistheader { /* number of paths in list */ LONG lcPaths; } DBPATHLISTHEADER; DECLARE_STANDARD_TYPES(DBPATHLISTHEADER); /* database path structure */ typedef struct _dbpath { /* old handle to path */ HPATH hpath; /* old handle to parent volume */ HVOLUME hvol; /* old handle to path suffix string */ HSTRING hsPathSuffix; } DBPATH; DECLARE_STANDARD_TYPES(DBPATH); /***************************** Private Functions *****************************/ /* Module Prototypes ********************/ PRIVATE_CODE COMPARISONRESULT PathSortCmp(PCVOID, PCVOID); PRIVATE_CODE COMPARISONRESULT PathSearchCmp(PCVOID, PCVOID); PRIVATE_CODE BOOL UnifyPath(PPATHLIST, HVOLUME, LPCTSTR, PPATH *); PRIVATE_CODE BOOL CreatePath(PPATHLIST, HVOLUME, LPCTSTR, PPATH *); PRIVATE_CODE void DestroyPath(PPATH); PRIVATE_CODE void UnlinkPath(PCPATH); PRIVATE_CODE void LockPath(PPATH); PRIVATE_CODE BOOL UnlockPath(PPATH); PRIVATE_CODE PATHRESULT TranslateVOLUMERESULTToPATHRESULT(VOLUMERESULT); PRIVATE_CODE TWINRESULT WritePath(HCACHEDFILE, PPATH); PRIVATE_CODE TWINRESULT ReadPath(HCACHEDFILE, PPATHLIST, HHANDLETRANS, HHANDLETRANS, HHANDLETRANS); #if defined(DEBUG) || defined(VSTF) PRIVATE_CODE BOOL IsValidPCPATHLIST(PCPATHLIST); PRIVATE_CODE BOOL IsValidPCPATH(PCPATH); #endif #if defined(DEBUG) PRIVATE_CODE BOOL IsValidPCPATHSEARCHINFO(PCPATHSEARCHINFO); #endif /* ** PathSortCmp() ** ** Pointer comparison function used to sort the module array of paths. ** ** Arguments: pcpath1 - pointer to first path ** pcpath2 - pointer to second path ** ** Returns: ** ** Side Effects: none ** ** The internal paths are sorted by: ** 1) volume ** 2) path suffix ** 3) pointer value */ PRIVATE_CODE COMPARISONRESULT PathSortCmp(PCVOID pcpath1, PCVOID pcpath2) { COMPARISONRESULT cr; ASSERT(IS_VALID_STRUCT_PTR(pcpath1, CPATH)); ASSERT(IS_VALID_STRUCT_PTR(pcpath2, CPATH)); cr = CompareVolumes(((PCPATH)pcpath1)->hvol, ((PCPATH)pcpath2)->hvol); if (cr == CR_EQUAL) { cr = ComparePathStringsByHandle(((PCPATH)pcpath1)->hsPathSuffix, ((PCPATH)pcpath2)->hsPathSuffix); if (cr == CR_EQUAL) cr = ComparePointers(pcpath1, pcpath2); } return(cr); } /* ** PathSearchCmp() ** ** Pointer comparison function used to search for a path. ** ** Arguments: pcpathsi - pointer to PATHSEARCHINFO describing path to ** search for ** pcpath - pointer to path to examine ** ** Returns: ** ** Side Effects: none ** ** The internal paths are searched by: ** 1) volume ** 2) path suffix string */ PRIVATE_CODE COMPARISONRESULT PathSearchCmp(PCVOID pcpathsi, PCVOID pcpath) { COMPARISONRESULT cr; ASSERT(IS_VALID_STRUCT_PTR(pcpathsi, CPATHSEARCHINFO)); ASSERT(IS_VALID_STRUCT_PTR(pcpath, CPATH)); cr = CompareVolumes(((PCPATHSEARCHINFO)pcpathsi)->hvol, ((PCPATH)pcpath)->hvol); if (cr == CR_EQUAL) cr = ComparePathStrings(((PCPATHSEARCHINFO)pcpathsi)->pcszPathSuffix, GetString(((PCPATH)pcpath)->hsPathSuffix)); return(cr); } /* ** UnifyPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE BOOL UnifyPath(PPATHLIST ppl, HVOLUME hvol, LPCTSTR pcszPathSuffix, PPATH *pppath) { BOOL bResult = FALSE; ASSERT(IS_VALID_STRUCT_PTR(ppl, CPATHLIST)); ASSERT(IS_VALID_HANDLE(hvol, VOLUME)); ASSERT(IsValidPathSuffix(pcszPathSuffix)); ASSERT(IS_VALID_WRITE_PTR(pppath, PPATH)); /* Allocate space for PATH structure. */ if (AllocateMemory(sizeof(**pppath), pppath)) { if (CopyVolume(hvol, ppl->hvl, &((*pppath)->hvol))) { if (AddString(pcszPathSuffix, ppl->hst, GetHashBucketIndex, &((*pppath)->hsPathSuffix))) { ARRAYINDEX aiUnused; /* Initialize remaining PATH fields. */ (*pppath)->ulcLock = 0; (*pppath)->pplParent = ppl; /* Add new PATH to array. */ if (AddPtr(ppl->hpa, PathSortCmp, *pppath, &aiUnused)) bResult = TRUE; else { DeleteString((*pppath)->hsPathSuffix); UNIFYPATH_BAIL1: DeleteVolume((*pppath)->hvol); UNIFYPATH_BAIL2: FreeMemory(*pppath); } } else goto UNIFYPATH_BAIL1; } else goto UNIFYPATH_BAIL2; } ASSERT(! bResult || IS_VALID_STRUCT_PTR(*pppath, CPATH)); return(bResult); } /* ** CreatePath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE BOOL CreatePath(PPATHLIST ppl, HVOLUME hvol, LPCTSTR pcszPathSuffix, PPATH *pppath) { BOOL bResult; ARRAYINDEX aiFound; PATHSEARCHINFO pathsi; ASSERT(IS_VALID_STRUCT_PTR(ppl, CPATHLIST)); ASSERT(IS_VALID_HANDLE(hvol, VOLUME)); ASSERT(IsValidPathSuffix(pcszPathSuffix)); ASSERT(IS_VALID_WRITE_PTR(pppath, CPATH)); /* Does a path for the given volume and path suffix already exist? */ pathsi.hvol = hvol; pathsi.pcszPathSuffix = pcszPathSuffix; bResult = SearchSortedArray(ppl->hpa, &PathSearchCmp, &pathsi, &aiFound); if (bResult) /* Yes. Return it. */ *pppath = GetPtr(ppl->hpa, aiFound); else bResult = UnifyPath(ppl, hvol, pcszPathSuffix, pppath); if (bResult) LockPath(*pppath); ASSERT(! bResult || IS_VALID_STRUCT_PTR(*pppath, CPATH)); return(bResult); } /* ** DestroyPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE void DestroyPath(PPATH ppath) { ASSERT(IS_VALID_STRUCT_PTR(ppath, CPATH)); DeleteVolume(ppath->hvol); DeleteString(ppath->hsPathSuffix); FreeMemory(ppath); return; } /* ** UnlinkPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE void UnlinkPath(PCPATH pcpath) { HPTRARRAY hpa; ARRAYINDEX aiFound; ASSERT(IS_VALID_STRUCT_PTR(pcpath, CPATH)); hpa = pcpath->pplParent->hpa; if (EVAL(SearchSortedArray(hpa, &PathSortCmp, pcpath, &aiFound))) { ASSERT(GetPtr(hpa, aiFound) == pcpath); DeletePtr(hpa, aiFound); } return; } /* ** LockPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE void LockPath(PPATH ppath) { ASSERT(IS_VALID_STRUCT_PTR(ppath, CPATH)); ASSERT(ppath->ulcLock < ULONG_MAX); ppath->ulcLock++; return; } /* ** UnlockPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE BOOL UnlockPath(PPATH ppath) { ASSERT(IS_VALID_STRUCT_PTR(ppath, CPATH)); if (EVAL(ppath->ulcLock > 0)) ppath->ulcLock--; return(ppath->ulcLock > 0); } /* ** TranslateVOLUMERESULTToPATHRESULT() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE PATHRESULT TranslateVOLUMERESULTToPATHRESULT(VOLUMERESULT vr) { PATHRESULT pr; switch (vr) { case VR_SUCCESS: pr = PR_SUCCESS; break; case VR_UNAVAILABLE_VOLUME: pr = PR_UNAVAILABLE_VOLUME; break; case VR_OUT_OF_MEMORY: pr = PR_OUT_OF_MEMORY; break; default: ASSERT(vr == VR_INVALID_PATH); pr = PR_INVALID_PATH; break; } return(pr); } /* ** WritePath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE TWINRESULT WritePath(HCACHEDFILE hcf, PPATH ppath) { TWINRESULT tr; DBPATH dbpath; ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE)); ASSERT(IS_VALID_STRUCT_PTR(ppath, CPATH)); /* Write database path. */ dbpath.hpath = (HPATH)ppath; dbpath.hvol = ppath->hvol; dbpath.hsPathSuffix = ppath->hsPathSuffix; if (WriteToCachedFile(hcf, (PCVOID)&dbpath, sizeof(dbpath), NULL)) tr = TR_SUCCESS; else tr = TR_BRIEFCASE_WRITE_FAILED; return(tr); } /* ** ReadPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE TWINRESULT ReadPath(HCACHEDFILE hcf, PPATHLIST ppl, HHANDLETRANS hhtVolumes, HHANDLETRANS hhtStrings, HHANDLETRANS hhtPaths) { TWINRESULT tr; DBPATH dbpath; DWORD dwcbRead; HVOLUME hvol; HSTRING hsPathSuffix; ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE)); ASSERT(IS_VALID_STRUCT_PTR(ppl, CPATHLIST)); ASSERT(IS_VALID_HANDLE(hhtVolumes, HANDLETRANS)); ASSERT(IS_VALID_HANDLE(hhtStrings, HANDLETRANS)); ASSERT(IS_VALID_HANDLE(hhtPaths, HANDLETRANS)); if (ReadFromCachedFile(hcf, &dbpath, sizeof(dbpath), &dwcbRead) && dwcbRead == sizeof(dbpath) && TranslateHandle(hhtVolumes, (HGENERIC)(dbpath.hvol), (PHGENERIC)&hvol) && TranslateHandle(hhtStrings, (HGENERIC)(dbpath.hsPathSuffix), (PHGENERIC)&hsPathSuffix)) { PPATH ppath; if (CreatePath(ppl, hvol, GetString(hsPathSuffix), &ppath)) { /* * To leave read paths with 0 initial lock count, we must undo * the LockPath() performed by CreatePath(). */ UnlockPath(ppath); if (AddHandleToHandleTranslator(hhtPaths, (HGENERIC)(dbpath.hpath), (HGENERIC)ppath)) tr = TR_SUCCESS; else { UnlinkPath(ppath); DestroyPath(ppath); tr = TR_OUT_OF_MEMORY; } } else tr = TR_OUT_OF_MEMORY; } else tr = TR_CORRUPT_BRIEFCASE; return(tr); } #if defined(DEBUG) || defined(VSTF) /* ** IsValidPCPATHLIST() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE BOOL IsValidPCPATHLIST(PCPATHLIST pcpl) { return(IS_VALID_READ_PTR(pcpl, CPATHLIST) && IS_VALID_HANDLE(pcpl->hpa, PTRARRAY) && IS_VALID_HANDLE(pcpl->hvl, VOLUMELIST) && IS_VALID_HANDLE(pcpl->hst, STRINGTABLE)); } /* ** IsValidPCPATH() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE BOOL IsValidPCPATH(PCPATH pcpath) { return(IS_VALID_READ_PTR(pcpath, CPATH) && IS_VALID_HANDLE(pcpath->hvol, VOLUME) && IS_VALID_HANDLE(pcpath->hsPathSuffix, STRING) && IsValidPathSuffix(GetString(pcpath->hsPathSuffix)) && IS_VALID_READ_PTR(pcpath->pplParent, CPATHLIST)); } #endif #if defined(DEBUG) /* ** IsValidPCPATHSEARCHINFO() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PRIVATE_CODE BOOL IsValidPCPATHSEARCHINFO(PCPATHSEARCHINFO pcpathsi) { return(IS_VALID_READ_PTR(pcpathsi, CPATHSEARCHINFO) && IS_VALID_HANDLE(pcpathsi->hvol, VOLUME) && IsValidPathSuffix(pcpathsi->pcszPathSuffix)); } #endif /****************************** Public Functions *****************************/ /* ** CreatePathList() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL CreatePathList(DWORD dwFlags, HWND hwndOwner, PHPATHLIST phpl) { BOOL bResult = FALSE; PPATHLIST ppl; 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(phpl, HPATHLIST)); if (AllocateMemory(sizeof(*ppl), &ppl)) { NEWPTRARRAY npa; /* Create pointer array of paths. */ npa.aicInitialPtrs = NUM_START_PATHS; npa.aicAllocGranularity = NUM_PATHS_TO_ADD; npa.dwFlags = NPA_FL_SORTED_ADD; if (CreatePtrArray(&npa, &(ppl->hpa))) { if (CreateVolumeList(dwFlags, hwndOwner, &(ppl->hvl))) { NEWSTRINGTABLE nszt; /* Create string table for path suffix strings. */ nszt.hbc = NUM_PATH_HASH_BUCKETS; if (CreateStringTable(&nszt, &(ppl->hst))) { *phpl = (HPATHLIST)ppl; bResult = TRUE; } else { DestroyVolumeList(ppl->hvl); CREATEPATHLIST_BAIL1: DestroyPtrArray(ppl->hpa); CREATEPATHLIST_BAIL2: FreeMemory(ppl); } } else goto CREATEPATHLIST_BAIL1; } else goto CREATEPATHLIST_BAIL2; } ASSERT(! bResult || IS_VALID_HANDLE(*phpl, PATHLIST)); return(bResult); } /* ** DestroyPathList() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE void DestroyPathList(HPATHLIST hpl) { ARRAYINDEX aicPtrs; ARRAYINDEX ai; ASSERT(IS_VALID_HANDLE(hpl, PATHLIST)); /* First free all paths in array. */ aicPtrs = GetPtrCount(((PCPATHLIST)hpl)->hpa); for (ai = 0; ai < aicPtrs; ai++) DestroyPath(GetPtr(((PCPATHLIST)hpl)->hpa, ai)); /* Now wipe out the array. */ DestroyPtrArray(((PCPATHLIST)hpl)->hpa); ASSERT(! GetVolumeCount(((PCPATHLIST)hpl)->hvl)); DestroyVolumeList(((PCPATHLIST)hpl)->hvl); ASSERT(! GetStringCount(((PCPATHLIST)hpl)->hst)); DestroyStringTable(((PCPATHLIST)hpl)->hst); FreeMemory((PPATHLIST)hpl); return; } /* ** InvalidatePathListInfo() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE void InvalidatePathListInfo(HPATHLIST hpl) { InvalidateVolumeListInfo(((PCPATHLIST)hpl)->hvl); return; } /* ** ClearPathListInfo() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE void ClearPathListInfo(HPATHLIST hpl) { ClearVolumeListInfo(((PCPATHLIST)hpl)->hvl); return; } /* ** AddPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE PATHRESULT AddPath(HPATHLIST hpl, LPCTSTR pcszPath, PHPATH phpath) { PATHRESULT pr; HVOLUME hvol; TCHAR rgchPathSuffix[MAX_PATH_LEN]; LPCTSTR pszPath; #ifdef UNICODE WCHAR szUnicode[MAX_PATH]; #endif ASSERT(IS_VALID_HANDLE(hpl, PATHLIST)); ASSERT(IS_VALID_STRING_PTR(pcszPath, CSTR)); ASSERT(IS_VALID_WRITE_PTR(phpath, HPATH)); // On NT, we want to convert a unicode string to an ANSI shortened path for // the sake of interop #if defined(UNICODE) { CHAR szAnsi[MAX_PATH]; szUnicode[0] = L'\0'; WideCharToMultiByte(CP_ACP, 0, pcszPath, -1, szAnsi, ARRAYSIZE(szAnsi), NULL, NULL); MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, szUnicode, ARRAYSIZE(szUnicode)); if (lstrcmp(szUnicode, pcszPath)) { // Cannot convert losslessly from Unicode -> Ansi, so get the short path lstrcpyn(szUnicode, pcszPath, ARRAYSIZE(szUnicode)); SheShortenPath(szUnicode, TRUE); pszPath = szUnicode; } else { // It will convert OK, so just use the original pszPath = pcszPath; } } #else pszPath = pcszPath; #endif pr = TranslateVOLUMERESULTToPATHRESULT( AddVolume(((PCPATHLIST)hpl)->hvl, pszPath, &hvol, rgchPathSuffix, ARRAYSIZE(rgchPathSuffix))); if (pr == PR_SUCCESS) { PPATH ppath; if (CreatePath((PPATHLIST)hpl, hvol, rgchPathSuffix, &ppath)) *phpath = (HPATH)ppath; else pr = PR_OUT_OF_MEMORY; DeleteVolume(hvol); } ASSERT(pr != PR_SUCCESS || IS_VALID_HANDLE(*phpath, PATH)); return(pr); } /* ** AddChildPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL AddChildPath(HPATHLIST hpl, HPATH hpathParent, LPCTSTR pcszSubPath, PHPATH phpathChild) { BOOL bResult; TCHAR rgchChildPathSuffix[MAX_PATH_LEN]; LPCTSTR pcszPathSuffix; LPTSTR pszPathSuffixEnd; PPATH ppathChild; ASSERT(IS_VALID_HANDLE(hpl, PATHLIST)); ASSERT(IS_VALID_HANDLE(hpathParent, PATH)); ASSERT(IS_VALID_STRING_PTR(pcszSubPath, CSTR)); ASSERT(IS_VALID_WRITE_PTR(phpathChild, HPATH)); ComposePath(rgchChildPathSuffix, GetString(((PCPATH)hpathParent)->hsPathSuffix), pcszSubPath, ARRAYSIZE(rgchChildPathSuffix)); pcszPathSuffix = rgchChildPathSuffix; if (IS_SLASH(*pcszPathSuffix)) pcszPathSuffix++; pszPathSuffixEnd = CharPrev(pcszPathSuffix, pcszPathSuffix + lstrlen(pcszPathSuffix)); if (IS_SLASH(*pszPathSuffixEnd)) *pszPathSuffixEnd = TEXT('\0'); ASSERT(IsValidPathSuffix(pcszPathSuffix)); bResult = CreatePath((PPATHLIST)hpl, ((PCPATH)hpathParent)->hvol, pcszPathSuffix, &ppathChild); if (bResult) *phpathChild = (HPATH)ppathChild; ASSERT(! bResult || IS_VALID_HANDLE(*phpathChild, PATH)); return(bResult); } /* ** DeletePath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE void DeletePath(HPATH hpath) { ASSERT(IS_VALID_HANDLE(hpath, PATH)); if (! UnlockPath((PPATH)hpath)) { UnlinkPath((PPATH)hpath); DestroyPath((PPATH)hpath); } return; } /* ** CopyPath() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL CopyPath(HPATH hpathSrc, HPATHLIST hplDest, PHPATH phpathCopy) { BOOL bResult; PPATH ppath; ASSERT(IS_VALID_HANDLE(hpathSrc, PATH)); ASSERT(IS_VALID_HANDLE(hplDest, PATHLIST)); ASSERT(IS_VALID_WRITE_PTR(phpathCopy, HPATH)); /* Is the destination path list the source path's path list? */ if (((PCPATH)hpathSrc)->pplParent == (PCPATHLIST)hplDest) { /* Yes. Use the source path. */ LockPath((PPATH)hpathSrc); ppath = (PPATH)hpathSrc; bResult = TRUE; } else bResult = CreatePath((PPATHLIST)hplDest, ((PCPATH)hpathSrc)->hvol, GetString(((PCPATH)hpathSrc)->hsPathSuffix), &ppath); if (bResult) *phpathCopy = (HPATH)ppath; ASSERT(! bResult || IS_VALID_HANDLE(*phpathCopy, PATH)); return(bResult); } /* ** GetPathString() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE void GetPathString(HPATH hpath, LPTSTR pszPathBuf, int cchMax) { ASSERT(IS_VALID_HANDLE(hpath, PATH)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszPathBuf, STR, cchMax)); GetPathRootString(hpath, pszPathBuf, cchMax); CatPath(pszPathBuf, GetString(((PPATH)hpath)->hsPathSuffix), cchMax); ASSERT(IsCanonicalPath(pszPathBuf)); return; } /* ** GetPathRootString() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE void GetPathRootString(HPATH hpath, LPTSTR pszPathRootBuf, int cchMax) { ASSERT(IS_VALID_HANDLE(hpath, PATH)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszPathRootBuf, STR, cchMax)); GetVolumeRootPath(((PPATH)hpath)->hvol, pszPathRootBuf, cchMax); ASSERT(IsCanonicalPath(pszPathRootBuf)); return; } /* ** GetPathSuffixString() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE void GetPathSuffixString(HPATH hpath, LPTSTR pszPathSuffixBuf) { ASSERT(IS_VALID_HANDLE(hpath, PATH)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszPathSuffixBuf, STR, MAX_PATH_LEN)); ASSERT(lstrlen(GetString(((PPATH)hpath)->hsPathSuffix)) < MAX_PATH_LEN); MyLStrCpyN(pszPathSuffixBuf, GetString(((PPATH)hpath)->hsPathSuffix), MAX_PATH_LEN); ASSERT(IsValidPathSuffix(pszPathSuffixBuf)); return; } /* ** AllocatePathString() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL AllocatePathString(HPATH hpath, LPTSTR *ppszPath) { TCHAR rgchPath[MAX_PATH_LEN]; ASSERT(IS_VALID_HANDLE(hpath, PATH)); ASSERT(IS_VALID_WRITE_PTR(ppszPath, LPTSTR)); GetPathString(hpath, rgchPath, ARRAYSIZE(rgchPath)); return(StringCopy(rgchPath, ppszPath)); } #ifdef DEBUG /* ** DebugGetPathString() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none ** ** N.b., DebugGetPathString() must be non-intrusive. */ PUBLIC_CODE LPCTSTR DebugGetPathString(HPATH hpath) { /* Allow 4 debug paths. */ static TCHAR SrgrgchPaths[][MAX_PATH_LEN] = { TEXT(""), TEXT(""), TEXT(""), TEXT("") }; static UINT SuiPath = 0; LPTSTR pszPath; ASSERT(IS_VALID_HANDLE(hpath, PATH)); pszPath = SrgrgchPaths[SuiPath]; DebugGetVolumeRootPath(((PPATH)hpath)->hvol, pszPath, ARRAYSIZE(SrgrchPaths[SuiPath])); CatPath(pszPath, GetString(((PPATH)hpath)->hsPathSuffix), ARRAYSIZE(SrgrchPaths[SuiPath])); SuiPath++; SuiPath %= ARRAY_ELEMENTS(SrgrgchPaths); return(pszPath); } /* ** GetPathCount() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE ULONG GetPathCount(HPATHLIST hpl) { ASSERT(IS_VALID_HANDLE(hpl, PATHLIST)); return(GetPtrCount(((PCPATHLIST)hpl)->hpa)); } #endif /* ** IsPathVolumeAvailable() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL IsPathVolumeAvailable(HPATH hpath) { ASSERT(IS_VALID_HANDLE(hpath, PATH)); return(IsVolumeAvailable(((PCPATH)hpath)->hvol)); } /* ** GetPathVolumeID() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE HVOLUMEID GetPathVolumeID(HPATH hpath) { ASSERT(IS_VALID_HANDLE(hpath, PATH)); return((HVOLUMEID)hpath); } /* ** MyIsPathOnVolume() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none ** ** MyIsPathOnVolume() will fail for a new root path alias for a volume. E.g., ** if the same net resource is connected to both X: and Y:, MyIsPathOnVolume() ** will only return TRUE for the drive root path that the net resource was ** connected to through the given HVOLUME. */ PUBLIC_CODE BOOL MyIsPathOnVolume(LPCTSTR pcszPath, HPATH hpath) { BOOL bResult; TCHAR rgchVolumeRootPath[MAX_PATH_LEN]; ASSERT(IsFullPath(pcszPath)); ASSERT(IS_VALID_HANDLE(hpath, PATH)); rgchVolumeRootPath[0] = TEXT('\0'); if (IsVolumeAvailable(((PPATH)hpath)->hvol)) { GetVolumeRootPath(((PPATH)hpath)->hvol, rgchVolumeRootPath, ARRAYSIZE(rgchVolumeRootPath)); bResult = (MyLStrCmpNI(pcszPath, rgchVolumeRootPath, lstrlen(rgchVolumeRootPath)) == CR_EQUAL); } else { TRACE_OUT((TEXT("MyIsPathOnVolume(): Failing on unavailable volume %s."), DebugGetVolumeRootPath(((PPATH)hpath)->hvol, rgchVolumeRootPath, ARRAYSIZE(rgchVolumeRootPath)))); bResult = FALSE; } return(bResult); } /* ** ComparePaths() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none ** ** PATHs are compared by: ** 1) volume ** 2) path suffix */ PUBLIC_CODE COMPARISONRESULT ComparePaths(HPATH hpath1, HPATH hpath2) { COMPARISONRESULT cr; ASSERT(IS_VALID_HANDLE(hpath1, PATH)); ASSERT(IS_VALID_HANDLE(hpath2, PATH)); /* This comparison works across path lists. */ cr = ComparePathVolumes(hpath1, hpath2); if (cr == CR_EQUAL) cr = ComparePathStringsByHandle(((PCPATH)hpath1)->hsPathSuffix, ((PCPATH)hpath2)->hsPathSuffix); return(cr); } /* ** ComparePathVolumes() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE COMPARISONRESULT ComparePathVolumes(HPATH hpath1, HPATH hpath2) { ASSERT(IS_VALID_HANDLE(hpath1, PATH)); ASSERT(IS_VALID_HANDLE(hpath2, PATH)); return(CompareVolumes(((PCPATH)hpath1)->hvol, ((PCPATH)hpath2)->hvol)); } /* ** IsPathPrefix() ** ** Determines whether or not one path is a prefix of another. ** ** Arguments: hpathChild - whole path (longer or same length) ** hpathParent - prefix path to test (shorter or same length) ** ** Returns: TRUE if the second path is a prefix of the first path. FALSE ** if not. ** ** Side Effects: none ** ** Read 'IsPathPrefix(A, B)' as 'Is A in B's subtree?'. */ PUBLIC_CODE BOOL IsPathPrefix(HPATH hpathChild, HPATH hpathParent) { BOOL bResult; ASSERT(IS_VALID_HANDLE(hpathParent, PATH)); ASSERT(IS_VALID_HANDLE(hpathChild, PATH)); if (ComparePathVolumes(hpathParent, hpathChild) == CR_EQUAL) { TCHAR rgchParentSuffix[MAX_PATH_LEN]; TCHAR rgchChildSuffix[MAX_PATH_LEN]; int nParentSuffixLen; int nChildSuffixLen; /* Ignore path roots when comparing path strings. */ GetPathSuffixString(hpathParent, rgchParentSuffix); GetPathSuffixString(hpathChild, rgchChildSuffix); /* Only root paths should have no path suffix off the root. */ nParentSuffixLen = lstrlen(rgchParentSuffix); nChildSuffixLen = lstrlen(rgchChildSuffix); /* * The parent path is a path prefix of the child path iff: * 1) The parent's path suffix string is shorter than or the same * length as the child's path suffix string. * 2) The two path suffix strings match through the length of the * parent's path suffix string. * 3) The prefix of the child's path suffix string is followed * immediately by a null terminator or a path separator. */ bResult = (nChildSuffixLen >= nParentSuffixLen && MyLStrCmpNI(rgchParentSuffix, rgchChildSuffix, nParentSuffixLen) == CR_EQUAL && (nChildSuffixLen == nParentSuffixLen || /* same paths */ ! nParentSuffixLen || /* root parent */ IS_SLASH(rgchChildSuffix[nParentSuffixLen]))); /* non-root parent */ } else bResult = FALSE; return(bResult); } /* ** SubtreesIntersect() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none ** ** N.b., two subtrees cannot both intersect a third subtree unless they ** intersect each other. */ PUBLIC_CODE BOOL SubtreesIntersect(HPATH hpath1, HPATH hpath2) { ASSERT(IS_VALID_HANDLE(hpath1, PATH)); ASSERT(IS_VALID_HANDLE(hpath2, PATH)); return(IsPathPrefix(hpath1, hpath2) || IsPathPrefix(hpath2, hpath1)); } /* ** FindEndOfRootSpec() ** ** Finds the end of the root specification in a path string. ** ** Arguments: pcszPath - path to examine for root specification ** hpath - handle to PATH that path string was generated from ** ** Returns: pointer to first character after end of root specification ** ** Side Effects: none ** ** Examples: ** ** input path output string ** ---------- ------------- ** c:\ ** c:\foo foo ** c:\foo\bar foo\bar ** \\pyrex\user\ ** \\pyrex\user\foo foo ** \\pyrex\user\foo\bar foo\bar */ PUBLIC_CODE LPTSTR FindEndOfRootSpec(LPCTSTR pcszFullPath, HPATH hpath) { LPCTSTR pcsz; UINT ucchPathLen; UINT ucchSuffixLen; ASSERT(IsCanonicalPath(pcszFullPath)); ASSERT(IS_VALID_HANDLE(hpath, PATH)); ucchPathLen = lstrlen(pcszFullPath); ucchSuffixLen = lstrlen(GetString(((PCPATH)hpath)->hsPathSuffix)); pcsz = pcszFullPath + ucchPathLen; if (ucchPathLen > ucchSuffixLen) pcsz -= ucchSuffixLen; else /* Assume path is root path. */ ERROR_OUT((TEXT("FindEndOfRootSpec(): Path suffix %s is longer than full path %s."), GetString(((PCPATH)hpath)->hsPathSuffix), pcszFullPath)); ASSERT(IsValidPathSuffix(pcsz)); return((LPTSTR)pcsz); } /* ** FindPathSuffix() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE LPTSTR FindChildPathSuffix(HPATH hpathParent, HPATH hpathChild, LPTSTR pszChildSuffixBuf) { LPCTSTR pcszChildSuffix; TCHAR rgchParentSuffix[MAX_PATH_LEN]; ASSERT(IS_VALID_HANDLE(hpathParent, PATH)); ASSERT(IS_VALID_HANDLE(hpathChild, PATH)); ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszChildSuffixBuf, STR, MAX_PATH_LEN)); ASSERT(IsPathPrefix(hpathChild, hpathParent)); GetPathSuffixString(hpathParent, rgchParentSuffix); GetPathSuffixString(hpathChild, pszChildSuffixBuf); ASSERT(lstrlen(rgchParentSuffix) <= lstrlen(pszChildSuffixBuf)); pcszChildSuffix = pszChildSuffixBuf + lstrlen(rgchParentSuffix); if (IS_SLASH(*pcszChildSuffix)) pcszChildSuffix++; ASSERT(IsValidPathSuffix(pcszChildSuffix)); return((LPTSTR)pcszChildSuffix); } /* ** ComparePointers() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE COMPARISONRESULT ComparePointers(PCVOID pcv1, PCVOID pcv2) { COMPARISONRESULT cr; /* pcv1 and pcv2 may be any value. */ if (pcv1 < pcv2) cr = CR_FIRST_SMALLER; else if (pcv1 > pcv2) cr = CR_FIRST_LARGER; else cr = CR_EQUAL; return(cr); } /* ** TWINRESULTFromLastError() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE TWINRESULT TWINRESULTFromLastError(TWINRESULT tr) { switch (GetLastError()) { case ERROR_OUTOFMEMORY: tr = TR_OUT_OF_MEMORY; break; default: break; } return(tr); } /* ** WritePathList() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE TWINRESULT WritePathList(HCACHEDFILE hcf, HPATHLIST hpl) { TWINRESULT tr; ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE)); ASSERT(IS_VALID_HANDLE(hpl, PATHLIST)); tr = WriteVolumeList(hcf, ((PCPATHLIST)hpl)->hvl); if (tr == TR_SUCCESS) { tr = WriteStringTable(hcf, ((PCPATHLIST)hpl)->hst); if (tr == TR_SUCCESS) { DWORD dwcbDBPathListHeaderOffset; tr = TR_BRIEFCASE_WRITE_FAILED; /* Save initial file position. */ dwcbDBPathListHeaderOffset = GetCachedFilePointerPosition(hcf); if (dwcbDBPathListHeaderOffset != INVALID_SEEK_POSITION) { DBPATHLISTHEADER dbplh; /* Leave space for path list header. */ ZeroMemory(&dbplh, sizeof(dbplh)); if (WriteToCachedFile(hcf, (PCVOID)&dbplh, sizeof(dbplh), NULL)) { ARRAYINDEX aicPtrs; ARRAYINDEX ai; LONG lcPaths = 0; tr = TR_SUCCESS; aicPtrs = GetPtrCount(((PCPATHLIST)hpl)->hpa); /* Write all paths. */ for (ai = 0; ai < aicPtrs; ai++) { PPATH ppath; ppath = GetPtr(((PCPATHLIST)hpl)->hpa, ai); /* * As a sanity check, don't save any path with a lock count * of 0. A 0 lock count implies that the path has not been * referenced since it was restored from the database, or * something is broken. */ if (ppath->ulcLock > 0) { tr = WritePath(hcf, ppath); if (tr == TR_SUCCESS) { ASSERT(lcPaths < LONG_MAX); lcPaths++; } else break; } else ERROR_OUT((TEXT("WritePathList(): PATH for path %s has 0 lock count and will not be written."), DebugGetPathString((HPATH)ppath))); } /* Save path list header. */ if (tr == TR_SUCCESS) { dbplh.lcPaths = lcPaths; tr = WriteDBSegmentHeader(hcf, dwcbDBPathListHeaderOffset, &dbplh, sizeof(dbplh)); TRACE_OUT((TEXT("WritePathList(): Wrote %ld paths."), dbplh.lcPaths)); } } } } } return(tr); } /* ** ReadPathList() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE TWINRESULT ReadPathList(HCACHEDFILE hcf, HPATHLIST hpl, PHHANDLETRANS phht) { TWINRESULT tr; HHANDLETRANS hhtVolumes; ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE)); ASSERT(IS_VALID_HANDLE(hpl, PATHLIST)); ASSERT(IS_VALID_WRITE_PTR(phht, HHANDLETRANS)); tr = ReadVolumeList(hcf, ((PCPATHLIST)hpl)->hvl, &hhtVolumes); if (tr == TR_SUCCESS) { HHANDLETRANS hhtStrings; tr = ReadStringTable(hcf, ((PCPATHLIST)hpl)->hst, &hhtStrings); if (tr == TR_SUCCESS) { DBPATHLISTHEADER dbplh; DWORD dwcbRead; tr = TR_CORRUPT_BRIEFCASE; if (ReadFromCachedFile(hcf, &dbplh, sizeof(dbplh), &dwcbRead) && dwcbRead == sizeof(dbplh)) { HHANDLETRANS hht; if (CreateHandleTranslator(dbplh.lcPaths, &hht)) { LONG l; tr = TR_SUCCESS; TRACE_OUT((TEXT("ReadPathList(): Reading %ld paths."), dbplh.lcPaths)); for (l = 0; l < dbplh.lcPaths; l++) { tr = ReadPath(hcf, (PPATHLIST)hpl, hhtVolumes, hhtStrings, hht); if (tr != TR_SUCCESS) break; } if (tr == TR_SUCCESS) { PrepareForHandleTranslation(hht); *phht = hht; ASSERT(IS_VALID_HANDLE(hpl, PATHLIST)); ASSERT(IS_VALID_HANDLE(*phht, HANDLETRANS)); } else DestroyHandleTranslator(hht); } else tr = TR_OUT_OF_MEMORY; } DestroyHandleTranslator(hhtStrings); } DestroyHandleTranslator(hhtVolumes); } return(tr); } /* ** IsValidHPATH() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL IsValidHPATH(HPATH hp) { return(IS_VALID_STRUCT_PTR((PCPATH)hp, CPATH)); } /* ** IsValidHVOLUMEID() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL IsValidHVOLUMEID(HVOLUMEID hvid) { return(IS_VALID_HANDLE((HPATH)hvid, PATH)); } #if defined(DEBUG) || defined(VSTF) /* ** IsValidHPATHLIST() ** ** ** ** Arguments: ** ** Returns: ** ** Side Effects: none */ PUBLIC_CODE BOOL IsValidHPATHLIST(HPATHLIST hpl) { return(IS_VALID_STRUCT_PTR((PCPATHLIST)hpl, CPATHLIST)); } #endif /***************************** Exported Functions ****************************/ /****************************************************************************** @doc SYNCENGAPI @api TWINRESULT | IsPathOnVolume | Determines whether or not a given path is on a given volume. @parm PCSTR | pcszPath | A pointer to a string indicating the path to be checked. @parm HVOLUMEID | hvid | A handle to a volume ID. @parm PBOOL | pbOnVolume | A pointer to a BOOL to be filled in with TRUE if the given path is on the given volume, or FALSE if not. *pbOnVolume is only valid if TR_SUCCESS is returned. @rdesc If the volume check was successful, TR_SUCCESS is returned. Otherwise, the volume check was not successful, and the return value indicates the error that occurred. ******************************************************************************/ SYNCENGAPI TWINRESULT WINAPI IsPathOnVolume(LPCTSTR pcszPath, HVOLUMEID hvid, PBOOL pbOnVolume) { TWINRESULT tr; if (BeginExclusiveBriefcaseAccess()) { DebugEntry(IsPathOnVolume); #ifdef EXPV /* Verify parameters. */ if (IS_VALID_STRING_PTR(pcszPath, CSTR) && IS_VALID_HANDLE(hvid, VOLUMEID) && IS_VALID_WRITE_PTR(pbOnVolume, BOOL)) #endif { TCHAR rgchFullPath[MAX_PATH_LEN]; LPTSTR pszFileName; DWORD dwPathLen; dwPathLen = GetFullPathName(pcszPath, ARRAYSIZE(rgchFullPath), rgchFullPath, &pszFileName); if (dwPathLen > 0 && dwPathLen < ARRAYSIZE(rgchFullPath)) { *pbOnVolume = MyIsPathOnVolume(rgchFullPath, (HPATH)hvid); tr = TR_SUCCESS; } else { ASSERT(! dwPathLen); tr = TR_INVALID_PARAMETER; } } #ifdef EXPV else tr = TR_INVALID_PARAMETER; #endif DebugExitTWINRESULT(IsPathOnVolume, tr); EndExclusiveBriefcaseAccess(); } else tr = TR_REENTERED; return(tr); } /****************************************************************************** @doc SYNCENGAPI @api TWINRESULT | GetVolumeDescription | Retrieves some descriptive information for a volume, if that information is available. @parm HVOLUMEID | hvid | A handle to a volume ID. @parm PVOLUMEDESC | pvoldesc | A pointer to a VOLUMEDESC to be filled in with information describing the volume. The ulSize field of the VOLUMEDESC structure should be filled in with sizeof(VOLUMEDESC) before calling GetVolumeDescription(). @rdesc If the volume was described successfully, TR_SUCCESS is returned. Otherwise, the volume was not described successfully, and the return value indicates the error that occurred. ******************************************************************************/ SYNCENGAPI TWINRESULT WINAPI GetVolumeDescription(HVOLUMEID hvid, PVOLUMEDESC pvoldesc) { TWINRESULT tr; if (BeginExclusiveBriefcaseAccess()) { DebugEntry(GetVolumeDescription); #ifdef EXPV /* Verify parameters. */ if (IS_VALID_HANDLE(hvid, VOLUMEID) && IS_VALID_WRITE_PTR(pvoldesc, VOLUMEDESC) && EVAL(pvoldesc->ulSize == sizeof(*pvoldesc))) #endif { DescribeVolume(((PCPATH)hvid)->hvol, pvoldesc); tr = TR_SUCCESS; } #ifdef EXPV else tr = TR_INVALID_PARAMETER; #endif DebugExitTWINRESULT(GetVolumeDescription, tr); EndExclusiveBriefcaseAccess(); } else tr = TR_REENTERED; return(tr); }