mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
12 KiB
503 lines
12 KiB
//
|
|
// Copyright (c) Microsoft Corporation 1993-1995
|
|
//
|
|
// path.c
|
|
//
|
|
// This files contains path whacking functions. Some of this code
|
|
// was taken from the shell and the briefcase engine.
|
|
//
|
|
// This is meant to be used in conjunction with common.h and common.c.
|
|
//
|
|
// History:
|
|
// 01-31-94 ScottH Moved from shellext.c
|
|
// 04-28-95 ScottH Transferred and expanded from Briefcase code
|
|
//
|
|
|
|
#include "proj.h"
|
|
|
|
#ifndef NOPATH
|
|
|
|
#pragma data_seg(DATASEG_READONLY)
|
|
|
|
const TCHAR c_szColonSlash[] = ":\\";
|
|
|
|
#pragma data_seg()
|
|
|
|
#define DBL_BSLASH(sz) (*(WORD *)(sz) == 0x5C5C) // check for double backslash '\\'
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Check is a path is a root.
|
|
|
|
Returns: TRUE for "\" "X:\" "\\foo\asdf" "\\foo\"
|
|
FALSE for anything else
|
|
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC WPPathIsRoot(LPCTSTR pPath)
|
|
{
|
|
if (!IsDBCSLeadByte(*pPath))
|
|
{
|
|
if (!lstrcmpi(pPath + 1, c_szColonSlash)) // "X:\" case
|
|
return TRUE;
|
|
}
|
|
|
|
if ((*pPath == '\\') && (*(pPath + 1) == 0)) // "\" case
|
|
return TRUE;
|
|
|
|
if (DBL_BSLASH(pPath)) // smells like UNC name
|
|
{
|
|
LPCTSTR p;
|
|
int cBackslashes = 0;
|
|
|
|
for (p = pPath + 2; *p; p = AnsiNext(p))
|
|
{
|
|
if (*p == '\\' && (++cBackslashes > 1))
|
|
return FALSE; /* not a bare UNC name, therefore not a root dir */
|
|
}
|
|
return TRUE; /* end of string with only 1 more backslash */
|
|
/* must be a bare UNC, which looks like a root dir */
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Returns TRUE if the given string is a UNC path.
|
|
|
|
Returns: see above
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC WPPathIsUNC(LPCTSTR pszPath)
|
|
{
|
|
return DBL_BSLASH(pszPath);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
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 WPRemoveBackslash(
|
|
LPTSTR lpszPath)
|
|
{
|
|
int len = lstrlen(lpszPath)-1;
|
|
if (IsDBCSLeadByte(*AnsiPrev(lpszPath,lpszPath+len+1)))
|
|
len--;
|
|
|
|
if (!WPPathIsRoot(lpszPath) && lpszPath[len] == '\\')
|
|
lpszPath[len] = '\0';
|
|
|
|
return lpszPath + len;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: copies the path without the extension into the buffer
|
|
|
|
Returns: new path
|
|
Cond: --
|
|
*/
|
|
LPTSTR PUBLIC WPRemoveExt(
|
|
LPCTSTR pszPath,
|
|
LPTSTR pszBuf)
|
|
{
|
|
LPTSTR psz;
|
|
LPTSTR pszMark = NULL;
|
|
|
|
ASSERT(pszPath);
|
|
ASSERT(pszBuf);
|
|
|
|
psz = pszBuf;
|
|
while (*pszPath)
|
|
{
|
|
*psz = *pszPath;
|
|
pszPath = AnsiNext(pszPath);
|
|
if ('.' == *psz)
|
|
pszMark = psz;
|
|
else if ('\\' == *psz)
|
|
pszMark = NULL;
|
|
psz = AnsiNext(psz);
|
|
}
|
|
*psz = '\0';
|
|
|
|
if (pszMark)
|
|
*pszMark = '\0';
|
|
|
|
return pszBuf;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Convert a file spec to make it look a bit better
|
|
if it is all upper case chars.
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
BOOL PRIVATE MakeComponentPretty(LPTSTR lpPath)
|
|
{
|
|
LPTSTR lp;
|
|
|
|
// check for all uppercase
|
|
for (lp = lpPath; *lp; lp = AnsiNext(lp)) {
|
|
if ((*lp >= 'a') && (*lp <= 'z'))
|
|
return FALSE; // this is a LFN, dont mess with it
|
|
}
|
|
|
|
AnsiLower(lpPath);
|
|
AnsiUpperBuff(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 WPFindNextComponentI(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 = AnsiChr(lpszPath, '\\');
|
|
// 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 ('\\' == *(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 WPMakePresentable(
|
|
LPTSTR pszPath)
|
|
{
|
|
LPTSTR pszComp; // pointers to begining and
|
|
LPTSTR pszEnd; // end of path component
|
|
LPTSTR pch;
|
|
int cComponent = 0;
|
|
BOOL bUNCPath;
|
|
TCHAR ch;
|
|
|
|
bUNCPath = WPPathIsUNC(pszPath);
|
|
|
|
pszComp = pszPath;
|
|
while (pszEnd = WPFindNextComponentI(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 = AnsiNext(pszComp);
|
|
if (':' == *pch)
|
|
{
|
|
// Simply capitalize the drive-portion of the path
|
|
//
|
|
AnsiUpper(pszComp);
|
|
}
|
|
else if (bUNCPath && cComponent++ < 3)
|
|
{
|
|
// Network server or share name
|
|
// BUGBUG: handle LFN network names
|
|
//
|
|
AnsiUpper(pszComp);
|
|
MakeComponentPretty(pszComp);
|
|
}
|
|
else
|
|
{
|
|
// Normal path component
|
|
//
|
|
MakeComponentPretty(pszComp);
|
|
}
|
|
|
|
*pszEnd = ch;
|
|
pszComp = pszEnd;
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Returns TRUE if the combined path of pszFolder and
|
|
pszName is greater than MAX_PATH.
|
|
|
|
Returns: see above
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC WPPathsTooLong(
|
|
LPCTSTR pszFolder,
|
|
LPCTSTR pszName)
|
|
{
|
|
// +1 for possible '\' between the two path components
|
|
return CbFromCch(lstrlen(pszFolder) + lstrlen(pszName) + 1) >= MAX_PATH;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Fully qualifies a path
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC WPCanonicalize(
|
|
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);
|
|
|
|
WPMakePresentable(pszBuf);
|
|
|
|
ASSERT(lstrlen(pszBuf) < MAX_PATH);
|
|
}
|
|
|
|
|
|
// Returns a pointer to the last component of a path string.
|
|
//
|
|
// in:
|
|
// path name, either fully qualified or not
|
|
//
|
|
// returns:
|
|
// pointer into the path where the path is. if none is found
|
|
// returns a poiter to the start of the path
|
|
//
|
|
// c:\foo\bar -> bar
|
|
// c:\foo -> foo
|
|
// c:\foo\ -> c:\foo\ (REVIEW: is this case busted?)
|
|
// c:\ -> c:\ (REVIEW: this case is strange)
|
|
// c: -> c:
|
|
// foo -> foo
|
|
LPTSTR PUBLIC WPFindFileName(LPCTSTR pPath)
|
|
{
|
|
LPCTSTR pT;
|
|
|
|
for (pT = pPath; *pPath; pPath = AnsiNext(pPath))
|
|
{
|
|
if ((pPath[0] == '\\' || pPath[0] == ':') && pPath[1] && (pPath[1] != '\\'))
|
|
pT = pPath + 1;
|
|
}
|
|
|
|
return (LPTSTR)pT; // const -> non const
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Returns TRUE if the file/directory exists.
|
|
|
|
Returns: see above
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC WPPathExists(
|
|
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 WPFindEndOfRoot(
|
|
LPCTSTR pszPath)
|
|
{
|
|
LPCTSTR psz;
|
|
|
|
ASSERT(pszPath);
|
|
|
|
if (':' == pszPath[1])
|
|
{
|
|
if ('\\' == pszPath[2])
|
|
psz = &pszPath[3];
|
|
else
|
|
psz = &pszPath[2];
|
|
}
|
|
else if (WPPathIsUNC(pszPath))
|
|
{
|
|
psz = WPFindNextComponentI(pszPath); // hop double-slash
|
|
psz = WPFindNextComponentI(psz); // hop server name
|
|
if (psz)
|
|
psz = WPFindNextComponentI(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 WPPathIsPrefix(
|
|
LPCTSTR lpcszPath1, // whole path (longer or same length)
|
|
LPCTSTR lpcszPath2) // prefix path (shorter or same length)
|
|
{
|
|
BOOL bIsPrefix = FALSE;
|
|
int nLen1;
|
|
int nLen2;
|
|
|
|
ASSERT(lpcszPath1);
|
|
ASSERT(lpcszPath2);
|
|
|
|
nLen1 = lstrlen(lpcszPath1);
|
|
nLen2 = lstrlen(lpcszPath2);
|
|
|
|
/* 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(lpcszPath1, lpcszPath2, nLen2))
|
|
{
|
|
/*
|
|
* Yes. Is the prefix of the longer string followed immediately by
|
|
* a null terminator or a path separator?
|
|
*/
|
|
|
|
if (! lpcszPath1[nLen2] ||
|
|
lpcszPath1[nLen2] == '\\' ||
|
|
*(CharPrev(lpcszPath2, lpcszPath2 + lstrlen(lpcszPath2))) == '\\')
|
|
/* Yes. */
|
|
bIsPrefix = TRUE;
|
|
}
|
|
}
|
|
|
|
return(bIsPrefix);
|
|
}
|
|
|
|
|
|
#ifdef WANT_SHELL_SUPPORT
|
|
/*----------------------------------------------------------
|
|
Purpose: Gets the displayable filename of the path. The filename
|
|
is placed in the provided buffer.
|
|
|
|
Returns: pointer to buffer
|
|
Cond: --
|
|
*/
|
|
LPTSTR PUBLIC WPGetDisplayName(
|
|
LPCTSTR pszPath,
|
|
LPTSTR pszBuf)
|
|
{
|
|
SHFILEINFO sfi;
|
|
|
|
if (SHGetFileInfo(pszPath, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME))
|
|
lstrcpy(pszBuf, sfi.szDisplayName);
|
|
else
|
|
lstrcpy(pszBuf, WPFindFileName(pszPath));
|
|
|
|
return pszBuf;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Sends a notify message to the shell regarding a file-status
|
|
change.
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC WPNotifyShell(
|
|
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);
|
|
}
|
|
}
|
|
#endif // WANT_SHELL_SUPPORT
|
|
|
|
#endif // NOPATH
|
|
|