NT4/private/windows/rover/filesync/syncui/path.c
2020-09-30 17:12:29 +02:00

744 lines
19 KiB
C

//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation 1993-1994
//
// File: path.c
//
// This files contains the path whacking code.
//
// History:
// 01-31-94 ScottH Moved from shellext.c
//
//---------------------------------------------------------------------------
///////////////////////////////////////////////////// INCLUDES
#include "brfprv.h" // common headers
#include "res.h"
/*----------------------------------------------------------
Purpose: Removes the trailing backslash from a path.
A:\ --> A:\
C:\foo\ --> C:\foo
\\Pyrex\User\ --> \\Pyrex\User
Returns: pointer to NULL that replaced the backslash or
the pointer to the last character if it isn't
a backslash
Cond: pimped this code from the shell
*/
LPTSTR PUBLIC MyPathRemoveBackslash(
LPTSTR lpszPath)
{
int len = lstrlen(lpszPath)-1;
if (IsDBCSLeadByte(*CharPrev(lpszPath,lpszPath+len+1)))
len--;
if (!PathIsRoot(lpszPath) && lpszPath[len] == TEXT('\\'))
lpszPath[len] = TEXT('\0');
return lpszPath + len;
}
#ifdef NOTUSED
/*----------------------------------------------------------
Purpose: copies the path without the extension into the buffer
Returns: new path
Cond: --
*/
LPTSTR PUBLIC PathRemoveExt(
LPCTSTR pszPath,
LPTSTR pszBuf)
{
LPTSTR psz;
LPTSTR pszMark = NULL;
ASSERT(pszPath);
ASSERT(pszBuf);
psz = pszBuf;
while (*pszPath)
{
*psz = *pszPath;
pszPath = CharNext(pszPath);
if (TEXT('.') == *psz)
pszMark = psz;
else if (TEXT('\\') == *psz)
pszMark = NULL;
psz = CharNext(psz);
}
*psz = TEXT('\0');
if (pszMark)
*pszMark = TEXT('\0');
return pszBuf;
}
#endif
/*----------------------------------------------------------
Purpose: Convert a file spec to make it look a bit better
if it is all upper case chars.
Returns: --
Cond: --
*/
BOOL PRIVATE PathMakeComponentPretty(LPTSTR lpPath)
{
LPTSTR lp;
// REVIEW: INTL need to deal with lower case chars in (>127) range?
// check for all uppercase
for (lp = lpPath; *lp; lp = CharNext(lp)) {
if ((*lp >= TEXT('a')) && (*lp <= TEXT('z')))
return FALSE; // this is a LFN, dont mess with it
}
CharLower(lpPath);
CharUpperBuff(lpPath, 1);
return TRUE; // did the conversion
}
//---------------------------------------------------------------------------
// Given a pointer to a point in a path - return a ptr the start of the
// next path component. Path components are delimted by slashes or the
// null at the end.
// There's special handling for UNC names.
// This returns NULL if you pass in a pointer to a NULL ie if you're about
// to go off the end of the path.
LPTSTR PUBLIC PathFindNextComponentI(LPCTSTR lpszPath)
{
LPTSTR lpszLastSlash;
// Are we at the end of a path.
if (!*lpszPath)
{
// Yep, quit.
return NULL;
}
// Find the next slash.
// REVIEW UNDONE - can slashes be quoted?
lpszLastSlash = StrChr(lpszPath, TEXT('\\'));
// Is there a slash?
if (!lpszLastSlash)
{
// No - Return a ptr to the NULL.
return (LPTSTR) (lpszPath+lstrlen(lpszPath));
}
else
{
// Is it a UNC style name?
if (TEXT('\\') == *(lpszLastSlash+1))
{
// Yep, skip over the second slash.
return lpszLastSlash+2;
}
else
{
// Nope. just skip over one slash.
return lpszLastSlash+1;
}
}
}
/*----------------------------------------------------------
Purpose: Takes the path and makes it presentable.
The rules are:
If the LFN name is simply the short name (all caps),
then convert to lowercase with first letter capitalized
Returns: --
Cond: --
*/
void PUBLIC PathMakePresentable(
LPTSTR pszPath)
{
LPTSTR pszComp; // pointers to begining and
LPTSTR pszEnd; // end of path component
LPTSTR pch;
int cComponent = 0;
BOOL bUNCPath;
TCHAR ch;
bUNCPath = PathIsUNC(pszPath);
pszComp = pszPath;
while (pszEnd = PathFindNextComponentI(pszComp))
{
// pszEnd may be pointing to the right of the backslash
// beyond the path component, so back up one
//
ch = *pszEnd;
*pszEnd = 0; // temporary null
// pszComp points to the path component
//
pch = CharNext(pszComp);
if (TEXT(':') == *pch)
{
// Simply capitalize the drive-portion of the path
//
CharUpper(pszComp);
}
else if (bUNCPath && cComponent++ < 3)
{
// Network server or share name
// BUGBUG: handle LFN network names
//
CharUpper(pszComp);
PathMakeComponentPretty(pszComp);
}
else
{
// Normal path component
//
PathMakeComponentPretty(pszComp);
}
*pszEnd = ch;
pszComp = pszEnd;
}
}
#ifdef NOTUSED
/*----------------------------------------------------------
Purpose: Takes the path and pretties up each component of
the path.
The rules are:
Use the LFN name of the component
If the LFN name is simply the short name (all caps),
then convert to lowercase with first letter capitalized
Returns: --
Cond: --
*/
void PRIVATE PathGetCompleteLFN(
LPCTSTR pszPath,
LPTSTR pszLong,
int cbLong)
{
TCHAR sz[MAX_PATH];
TCHAR szPath[MAX_PATH+1];
LPTSTR pszComp; // pointers to begining and end of path component
LPTSTR pszEnd;
int cbPath;
int cb;
BOOL bAtEnd = FALSE;
int cComponent = 0;
BOOL bUNCPath;
TCHAR ch;
// BUGBUG: this is broken for double-byte characters for sure
// For each component in string, get the LFN and add it to
// the pszLong buffer.
//
cbPath = lstrlen(pszPath) * sizeof(TCHAR);
ASSERT(cbPath+1 <= sizeof(szPath));
lstrcpy(szPath, pszPath);
bUNCPath = PathIsUNC(szPath);
*pszLong = NULL_CHAR;
cb = 0;
pszComp = szPath;
while (pszEnd = PathFindNextComponentI(pszComp))
{
// pszEnd may be pointing to the right of the backslash beyond the
// path component, so back up one
//
if (0 == *pszEnd)
bAtEnd = TRUE;
else
{
if (!bUNCPath || cComponent > 0)
pszEnd--; // not the server or share portions of a UNC path
ch = *pszEnd;
*pszEnd = 0; // temporary null
}
// pszComp points to the path component now
//
if (TEXT(':') == *(pszEnd-1) || TEXT(':') == *(pszEnd-2))
{
// Simply capitalize the drive-portion of the path
//
CharUpper(szPath);
}
else if (bUNCPath && cComponent++ < 3)
{
// Network server or share name
// BUGBUG: handle LFN network names
//
CharUpper(pszComp);
PathMakeComponentPretty(pszComp);
}
else
{
int ib;
// Try to get the LFN
//
*sz = NULL_CHAR;
PathGetLongName(szPath, sz, ARRAYSIZE(sz));
// If an LFN does not exist, keep the path component
// as it is. (Sometimes the path component can be
// something like "Link to Foo.txt")
//
if (*sz)
{
// Make pszComp point to the same offset in sz now
// (the components in each are the same offsets)
//
ib = pszComp - (LPTSTR)szPath;
pszComp = &sz[ib];
}
PathMakeComponentPretty(pszComp);
}
// Save new LFN-ized component to buffer
//
cb += lstrlen(pszComp) * sizeof(TCHAR);
if (cbLong <= cb)
break; // reached end of pszLong buffer
lstrcat(pszLong, pszComp);
if (!bAtEnd)
{
PathAddBackslash(pszLong);
*pszEnd = ch;
if (bUNCPath && 1 == cComponent)
pszComp = pszEnd; // pointing to share portion of path
else
pszComp = pszEnd+1; // Move component pointer to next part
}
else
pszComp = pszEnd;
}
}
#endif
/*----------------------------------------------------------
Purpose: Returns TRUE if the combined path of pszFolder and
pszName is greater than MAX_PATH.
Returns: see above
Cond: --
*/
BOOL PUBLIC PathsTooLong(
LPCTSTR pszFolder,
LPCTSTR pszName)
{
// +1 for possible '\' between the two path components
return lstrlen(pszFolder) + lstrlen(pszName) + 1 >= MAX_PATH;
}
/*----------------------------------------------------------
Purpose: Fully qualifies a path
Returns: --
Cond: --
*/
void PUBLIC PathCanonicalize(
LPCTSTR pszPath,
LPTSTR pszBuf) // Must be sizeof(MAX_PATH)
{
DWORD dwcPathLen;
dwcPathLen = GetFullPathName(pszPath, MAX_PATH, pszBuf, NULL);
if (! dwcPathLen || dwcPathLen >= MAX_PATH)
lstrcpy(pszBuf, pszPath);
// If pszBuf won't cover losslessly to ANSI, use the short name instead
#if defined(UNICODE)
{
CHAR szAnsi[MAX_PATH];
WCHAR szUnicode[MAX_PATH];
szUnicode[0] = L'\0';
WideCharToMultiByte(CP_ACP, 0, pszBuf, -1, szAnsi, ARRAYSIZE(szAnsi), NULL, NULL);
MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, szUnicode, ARRAYSIZE(szUnicode));
if (lstrcmp(szUnicode, pszBuf))
{
// Cannot convert losslessly from Unicode -> Ansi, so get the short path
lstrcpy(szUnicode, pszBuf);
SheShortenPath(szUnicode, TRUE);
lstrcpy(pszBuf, szUnicode);
}
}
#endif
PathMakePresentable(pszBuf);
ASSERT(lstrlen(pszBuf) < MAX_PATH);
}
/*----------------------------------------------------------
Purpose: Gets the displayable filename of the path. The filename
is placed in the provided buffer.
Returns: pointer to buffer
Cond: --
*/
LPTSTR PUBLIC PathGetDisplayName(
LPCTSTR pszPath,
LPTSTR pszBuf)
{
SHFILEINFO sfi;
if (SHGetFileInfo(pszPath, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME))
lstrcpy(pszBuf, sfi.szDisplayName);
else
lstrcpy(pszBuf, PathFindFileName(pszPath));
return pszBuf;
}
/*----------------------------------------------------------
Purpose: Checks if the attributes of the path. If it is a
directory and has the system bit set, and if the brfcase.dat
file exists in the directory, then return TRUE.
Worst case: performs two GetFileAttributes.
Returns: see above
Cond: --
*/
BOOL PUBLIC PathCheckForBriefcase(
LPCTSTR pszPath,
DWORD dwAttrib) // if -1, then function gets the attributes
{
ASSERT(pszPath);
if (0xFFFFFFFF == dwAttrib)
{
dwAttrib = GetFileAttributes(pszPath);
if (0xFFFFFFFF == dwAttrib)
return FALSE;
}
if (IsFlagSet(dwAttrib, FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY) ||
IsFlagSet(dwAttrib, FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM))
{
TCHAR szT[MAX_PATH];
LPCTSTR pszDBName;
// Check for the existence of the brfcase.dat file.
//
if (IsLFNDrive(pszPath))
pszDBName = g_szDBName;
else
pszDBName = g_szDBNameShort;
if (PathsTooLong(pszPath, pszDBName))
return FALSE;
else
{
PathCombine(szT, pszPath, pszDBName);
return PathExists(szT);
}
}
return FALSE;
}
/*----------------------------------------------------------
Purpose: Returns TRUE if the path is to a briefcase root.
This function may hit the file-system to achieve
its goal.
Worst case: performs two GetFileAttributes.
Returns: TRUE if the path refers to a briefcase root.
Cond: --
*/
BOOL PUBLIC PathIsBriefcase(
LPCTSTR pszPath)
{
UINT uRet;
ASSERT(pszPath);
// We perform our search by first looking in our cache
// of known briefcase paths (CPATH). If we don't find
// anything, then we proceed to iterate thru each
// component of the path, checking for these two things:
//
// 1) A directory with the system attribute
// 2) The existence of a brfcase.dat file in the directory.
//
uRet = CPATH_GetLocality(pszPath, NULL);
if (PL_FALSE == uRet)
{
uRet = PathCheckForBriefcase(pszPath, (DWORD)-1) ? PL_ROOT : PL_FALSE;
if (PL_ROOT == uRet)
{
int atom;
// Add this path to the briefcase path cache.
//
atom = Atom_Add(pszPath);
if (ATOM_ERR != atom)
CPATH_Replace(atom);
}
}
return PL_ROOT == uRet;
}
/*----------------------------------------------------------
Purpose: Gets the locality of the path, relative to any
briefcase. If PL_ROOT or PL_INSIDE is returned,
pszBuf will contain the path to the root of the
briefcase.
This function may hit the file-system to achieve
its goal.
Worst case: performs 2*n GetFileAttributes, where
n is the number of components in pszPath.
Returns: Path locality (PL_FALSE, PL_ROOT, PL_INSIDE)
Cond: --
*/
UINT PUBLIC PathGetLocality(
LPCTSTR pszPath,
LPTSTR pszBuf) // Buffer for root path
{
UINT uRet;
ASSERT(pszPath);
ASSERT(pszBuf);
*pszBuf = NULL_CHAR;
// pszPath may be:
// 1) a path to the briefcase folder itself
// 2) a path to a file or folder beneath the briefcase
// 3) a path to something unrelated to a briefcase
// We perform our search by first looking in our cache
// of known briefcase paths (CPATH). If we don't find
// anything, then we proceed to iterate thru each
// component of the path, checking for these two things:
//
// 1) A directory with the system attribute
// 2) The existence of a brfcase.dat file in the directory.
//
uRet = CPATH_GetLocality(pszPath, pszBuf);
if (PL_FALSE == uRet)
{
int cnt = 0;
lstrcpy(pszBuf, pszPath);
do
{
if (PathCheckForBriefcase(pszBuf, (DWORD)-1))
{
int atom;
uRet = cnt > 0 ? PL_INSIDE : PL_ROOT;
// Add this briefcase path to our cache
//
atom = Atom_Add(pszBuf);
if (ATOM_ERR != atom)
CPATH_Replace(atom);
break; // Done
}
cnt++;
} while (PathRemoveFileSpec(pszBuf));
if (PL_FALSE == uRet)
*pszBuf = NULL_CHAR;
}
return uRet;
}
/*----------------------------------------------------------
Purpose: Returns TRUE if the file/directory exists.
Returns: see above
Cond: --
*/
BOOL PUBLIC PathExists(
LPCTSTR pszPath)
{
return GetFileAttributes(pszPath) != 0xFFFFFFFF;
}
/*----------------------------------------------------------
Purpose: Finds the end of the root specification in a path.
input path output string
---------- -------------
c: <empty string>
c:\ <empty string>
c:\foo foo
c:\foo\bar foo\bar
\\pyrex\user <empty string>
\\pyrex\user\ <empty string>
\\pyrex\user\foo foo
\\pyrex\user\foo\bar foo\bar
Returns: pointer to first character after end of root spec.
Cond: --
*/
LPCTSTR PUBLIC PathFindEndOfRoot(
LPCTSTR pszPath)
{
LPCTSTR psz;
ASSERT(pszPath);
if (TEXT(':') == pszPath[1])
{
if (TEXT('\\') == pszPath[2])
psz = &pszPath[3];
else
psz = &pszPath[2];
}
else if (PathIsUNC(pszPath))
{
psz = PathFindNextComponentI(pszPath); // hop double-slash
psz = PathFindNextComponentI(psz); // hop server name
if (psz)
psz = PathFindNextComponentI(psz); // hop share name
if (!psz)
{
ASSERT(0); // There is no share name
psz = pszPath;
}
}
else
{
ASSERT(0);
psz = pszPath;
}
return psz;
}
/*----------------------------------------------------------
Purpose: Determines whether or not one path is a prefix of another.
Stole this from DavidDi's twincore.
Returns: TRUE if second path is a prefix of the first path
Cond: --
*/
BOOL PUBLIC PathIsPrefix(
LPCTSTR lpcszInPath1, // whole path (longer or same length)
LPCTSTR lpcszInPath2) // prefix path (shorter or same length)
{
BOOL bIsPrefix = FALSE;
int nLen1;
int nLen2;
TCHAR szPath1[MAX_PATH];
TCHAR szPath2[MAX_PATH];
ASSERT(lpcszPath1);
ASSERT(lpcszPath2);
// We could wind up comparing a short path to an LFN path here,
// yielding erronerous results, so convert both to short paths
// to ensure we're comparing apples to apples.
lstrcpyn(szPath1, lpcszInPath1, MAX_PATH);
SheShortenPath(szPath1, TRUE);
lstrcpyn(szPath2, lpcszInPath2, MAX_PATH);
SheShortenPath(szPath2, TRUE);
nLen1 = lstrlen(szPath1);
nLen2 = lstrlen(szPath2);
/* Is the prefix string shorter or the same length? */
if (nLen1 >= nLen2)
{
/*
* Yes. Do the two strings match through the length of the prefix
* string?
*/
if (! lstrnicmp(szPath1, szPath2, nLen2))
{
/*
* Yes. Is the prefix of the longer string followed immediately by
* a null terminator or a path separator?
*/
if (! szPath1[nLen2] ||
szPath1[nLen2] == TEXT('\\') ||
*(CharPrev(szPath2, szPath2 + lstrlen(szPath2))) == TEXT('\\'))
/* Yes. */
bIsPrefix = TRUE;
}
}
return(bIsPrefix);
}
/*----------------------------------------------------------
Purpose: Sends a notify message to the shell regarding a file-status
change.
Returns: --
Cond: --
*/
void PUBLIC PathNotifyShell(
LPCTSTR pszPath,
NOTIFYSHELLEVENT nse,
BOOL bDoNow) // TRUE: force the event to be processed right away
{
#pragma data_seg(DATASEG_READONLY)
static LONG const rgShEvents[] =
{ SHCNE_CREATE, SHCNE_MKDIR, SHCNE_UPDATEITEM, SHCNE_UPDATEDIR };
#pragma data_seg()
ASSERT(pszPath);
ASSERT(nse < ARRAYSIZE(rgShEvents));
SHChangeNotify(rgShEvents[nse], SHCNF_PATH, pszPath, NULL);
if (bDoNow)
{
SHChangeNotify(0, SHCNF_FLUSHNOWAIT, NULL, NULL);
}
}