//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1998 - 2000. // // File: FunyPath.hxx // // Contents: Implementation of ``Funny'' Path for long pathnames // // Classes: CFunnyPath // CLowerFunnyPath // CLowerFunnyStack // // Notes: This class takes in a fully qualified path. The path can be in // two forms - C:\dir\... or \\machine\share\... // EXCEPTION: To accomodate for scope restrictions, this class // will also allow paths of \dir... format // This path is converted to the "funny" form. The normal path // takes the form of \\?\C:\dir\... and remote path takes the form // of \\?\UNC\machine\share\... These funny paths can used by in // functions like CreateFile to open paths > MAX_PATH // // History: 08-May-98 vikasman Created // //---------------------------------------------------------------------------- #pragma once #include "lcase.hxx" const WCHAR _FUNNY_PATH[] = L"\\\\?\\"; const WCHAR _UNC_FUNNY_PATH[] = L"\\\\?\\UN"; const _FUNNY_PATH_LENGTH = ( ( sizeof( _FUNNY_PATH ) / sizeof( WCHAR ) ) - 1 ); const _UNC_FUNNY_PATH_LENGTH = ( ( sizeof( _UNC_FUNNY_PATH ) / sizeof( WCHAR ) ) - 1 ); //+--------------------------------------------------------------------------- // // Class: CFunnyPath // // Purpose: A path name preceeded by "\\?\" to allow file operations // when the length of the path is > MAX_PATH. // // History: 08-May-98 vikasman Created // //---------------------------------------------------------------------------- class CFunnyPath { public: // used by SetState function to set the state to funny/actual enum PathState { FUNNY_PATH_STATE, ACTUAL_PATH_STATE }; // // Constructors, Destructors, Operators... // CFunnyPath( BOOL fRemote = FALSE ) : _fRemote( fRemote ), _ccActualBuf( 0 ) { if ( _fRemote ) { _xBuf.SetSize( _UNC_FUNNY_PATH_LENGTH + 1 ); _xBuf[_UNC_FUNNY_PATH_LENGTH] = 0; } else { _xBuf.SetSize( _FUNNY_PATH_LENGTH + 1 ); _xBuf[_FUNNY_PATH_LENGTH] = 0; } } CFunnyPath( const WCHAR * wcsPath, unsigned cc = 0 ) : _fRemote( FALSE ), _ccActualBuf( 0 ) { SetPath( wcsPath, cc ); } CFunnyPath( CFunnyPath const & src ) { *this = src; } CFunnyPath & operator =( CFunnyPath const & src ) { _xBuf = src._xBuf; _fRemote = src._fRemote; _ccActualBuf = src._ccActualBuf; return *this; } // // Sets the path "wcsPath" // virtual void SetPath( const WCHAR * wcsPath, unsigned cc = 0 ) { Win4Assert( wcsPath ); _ccActualBuf = ( 0 == cc ? wcslen( wcsPath ) : cc ) ; if ( 0 == _ccActualBuf ) { InitBlank(); return; } Win4Assert( _ccActualBuf > 1 ); // at least 2 characters WCHAR * pwcActualPath; BOOL fInvalidPath = FALSE; if ( L'\\' != wcsPath[0] && L':' != wcsPath[1] ) // Looks like we don't have a valid path, but still // we will continue. It's not CFunnyPath's problem // We will treat it as a normal path { ciDebugOut(( DEB_ITRACE, "CFunnyPath::SetPath - The path (%ws) is not valid.\n", wcsPath )); fInvalidPath = TRUE; } if ( L':' == wcsPath[1] || fInvalidPath ) { // normal or invalid path _fRemote = FALSE; _xBuf.SetSize( _ccActualBuf + _FUNNY_PATH_LENGTH + 1 ); _xBuf.SetBuf( _FUNNY_PATH, _FUNNY_PATH_LENGTH ); pwcActualPath = &_xBuf[_FUNNY_PATH_LENGTH]; } else { // remote path _fRemote = TRUE; _xBuf.SetSize( _ccActualBuf + _UNC_FUNNY_PATH_LENGTH + 1 ); _xBuf.SetBuf( _UNC_FUNNY_PATH, _UNC_FUNNY_PATH_LENGTH ); pwcActualPath = &_xBuf[_UNC_FUNNY_PATH_LENGTH]; } RtlCopyMemory( pwcActualPath, wcsPath, _ccActualBuf * sizeof( WCHAR ) ); pwcActualPath[_ccActualBuf] = 0; } // // Appends the path "wcsPath" // virtual void AppendPath( const WCHAR * wcsPath, unsigned cc = 0 ) { AssertValid(); Win4Assert( wcsPath ); if ( 0 == cc ) { cc = wcslen( wcsPath ); } unsigned ccTotalSizeNeeded = cc + _ccActualBuf + 1; WCHAR * pwcAppendAt; if ( _fRemote ) { _xBuf.SetSize( ccTotalSizeNeeded + _UNC_FUNNY_PATH_LENGTH ); pwcAppendAt = &_xBuf[_UNC_FUNNY_PATH_LENGTH + _ccActualBuf]; } else { _xBuf.SetSize( ccTotalSizeNeeded + _FUNNY_PATH_LENGTH ); pwcAppendAt = &_xBuf[_FUNNY_PATH_LENGTH + _ccActualBuf]; } RtlCopyMemory( pwcAppendAt, wcsPath, cc * sizeof( WCHAR ) ); pwcAppendAt[cc] = 0; _ccActualBuf += cc; } // // Returns the path either as funny or unc_funny which can be used in // functions like CreateFile. Do not keep and use the pointer from // GetPath after calling GetActualPath. // Reason: Both these functions(even though they are const) may // modify the internal buffer before returning data // const WCHAR * GetPath() const { AssertValid(); if ( _fRemote ) { // need to replace \ with C ((CFunnyPath*)this)->_xBuf[_UNC_FUNNY_PATH_LENGTH] = L'C'; } return _xBuf.Get(); } // // Returns the "actual path". Do not keep and use the pointer from // GetActualPath after calling GetPath. Reason: Both these functions // (even though they are const) may modify the internal buffer before // returning data // const WCHAR * GetActualPath() const { return _GetActualPath(); } // // Sets the state of the path to actual/funny // void SetState( const PathState state ) { if ( _fRemote ) { _xBuf[_UNC_FUNNY_PATH_LENGTH] = ( FUNNY_PATH_STATE == state ? L'C' : L'\\' ); } } // // Returns the actual length of characters // unsigned GetActualLength() const { return _ccActualBuf; } // // Returns the total length // unsigned GetLength() const { return ( _ccActualBuf + (_fRemote ? _UNC_FUNNY_PATH_LENGTH : _FUNNY_PATH_LENGTH) ); } // // Appends back slash // BOOL AppendBackSlash() { WCHAR * pwcsActualPath = _GetActualPath(); if ( 0 == _ccActualBuf || L'\\' == pwcsActualPath[_ccActualBuf - 1] ) { return FALSE; } AppendPath( L"\\", 1 ); return TRUE; } // // Removes back slash // BOOL RemoveBackSlash() { WCHAR * pwcsActualPath = _GetActualPath(); if ( 0 == _ccActualBuf || L'\\' != pwcsActualPath[_ccActualBuf - 1] ) { return FALSE; } pwcsActualPath[--_ccActualBuf] = 0; return TRUE; } // // Truncates the buf to ccNewLength (this is the "actual char.", excluding // funny path prefix, length) // void Truncate( unsigned ccNewLength ) { if ( ccNewLength < _ccActualBuf ) { WCHAR * pwcActualPath = _GetActualPath(); pwcActualPath[ _ccActualBuf = ccNewLength ] = 0; } } BOOL IsRemote () const { return _fRemote; } // // returns TRUE if any component in the path looks like the short version // of a long file name. // BOOL IsShortPath () const; static BOOL IsShortName( WCHAR const * const pwszName ); // // Returns the count of all the characters in xBuf // unsigned Count() const { return _xBuf.Count(); } // // Set the size of _xBuf // void SetSize( unsigned cc ) { _xBuf.SetSize( cc ); } // // Get the internal buffer // const WCHAR * GetBuffer() const { return _xBuf.Get(); } // // This is a dangerous function as it exposes the internal buffer // WCHAR * GetBuffer() { return _xBuf.Get(); } // This path initializes the internal buffer with empty actual path void InitBlank( BOOL fRemote = FALSE ) { _fRemote = fRemote; if ( _fRemote ) { _xBuf.SetBuf( _UNC_FUNNY_PATH, _UNC_FUNNY_PATH_LENGTH + 1 ); } else { _xBuf.SetBuf( _FUNNY_PATH, _FUNNY_PATH_LENGTH + 1 ); } _ccActualBuf = 0; } #if CIDBG==1 // // Asserts that we are in good state // void AssertValid() const { const WCHAR * pwcsFunny = _xBuf.Get(); Win4Assert( pwcsFunny && L'\\' == pwcsFunny[0] && L'\\' == pwcsFunny[1] && L'?' == pwcsFunny[2] && L'\\' == pwcsFunny[3] ); Win4Assert( L':' == pwcsFunny[5] || L'N' == pwcsFunny[5] || L'n' == pwcsFunny[5] ); } #else void AssertValid() const {} #endif // // Some static utility functions // enum FunnyUNC { NOT_FUNNY, // not funny FUNNY_ONLY, // of type \\?\c:\dir\... FUNNY_UNC // of type \\?\unc\... }; static inline BOOL IsFunnyPath( WCHAR const * pwcsPath ) { return ( pwcsPath && L'\\' == pwcsPath[0] && L'\\' == pwcsPath[1] && L'?' == pwcsPath[2] && L'\\' == pwcsPath[3] ); } static inline FunnyUNC IsFunnyUNCPath( WCHAR const * pwcsPath ) { // The 6th char here can also be as '\\' (instead of being c/C) as // this class modifies that charatcter to be a '\\'. return ( FALSE == IsFunnyPath( pwcsPath ) ? NOT_FUNNY : ( ( (L'U' == pwcsPath[4] || L'u' == pwcsPath[4]) && (L'N' == pwcsPath[5] || L'n' == pwcsPath[5]) && (L'C' == pwcsPath[6] || L'c' == pwcsPath[6] || L'\\' == pwcsPath[6]) && (L'\\' == pwcsPath[7] ) ) ? FUNNY_UNC : FUNNY_ONLY ) ); } protected: WCHAR * _GetActualPath() const { WCHAR * pwcsActualPath; if ( _fRemote ) { pwcsActualPath = &(((CFunnyPath*)this)->_xBuf[_UNC_FUNNY_PATH_LENGTH]); // need to replace C with "\" *pwcsActualPath = L'\\'; } else { pwcsActualPath = &(((CFunnyPath*)this)->_xBuf[_FUNNY_PATH_LENGTH]); } return pwcsActualPath; } BOOL _fRemote; // Is the path remote ? unsigned _ccActualBuf; // number of actual characters in _xBuf // (excluding _FUNNY_PATH/_UNC_FUNNY_PATH) XGrowable _xBuf; }; //+--------------------------------------------------------------------------- // // Method: CFunnyPath::IsShortName, static public // // Arguments: [pwszName] -- name to be checked (only a single path component // is checked at a time) // // Returns: TRUE if file is potentially a short (8.3) name for // a file with a long name. // // History: 01-Sep-1998 AlanW Created // //---------------------------------------------------------------------------- inline BOOL CFunnyPath::IsShortName( WCHAR const * const pwszName ) { // // First, see if this is possibly a short name (has ~ in file name part). // BOOL fTwiddle = FALSE; unsigned cDot = 0; for ( unsigned cchFN = 0; cchFN < 13; cchFN++ ) { if ( pwszName[cchFN] == L'~' && cchFN >= 1 ) fTwiddle = TRUE; else if ( pwszName[cchFN] == L'.' ) { if (cchFN == 0 || cchFN > 8) return FALSE; // short names can't have '.' at beginning cDot++; } else if ( pwszName[cchFN] == L'\0' || pwszName[cchFN] == L'\\' ) break; cchFN++; } if (fTwiddle) { // short names can't have more than 1 '.' // max filename size if no extension is 8 if (cDot >= 2 || cDot == 0 && cchFN > 8) return FALSE; // check for min (e.g., EXT~1 for .ext) and max lengths if (cchFN >= 5 && cchFN <= 12) return TRUE; } return FALSE; } //+--------------------------------------------------------------------------- // // Method: CFunnyPath::IsShortPath, public // // Arguments: - none - // // Returns: TRUE if the path name potentially contains a short (8.3) name // for a file with a long name. // // History: 15-Sep-1998 AlanW Created // //---------------------------------------------------------------------------- inline BOOL CFunnyPath::IsShortPath() const { // // Check to see if the input path name contains an 8.3 short name // WCHAR * pwszTilde = wcschr( GetActualPath(), L'~' ); if (pwszTilde) { WCHAR * pwszComponent; for ( pwszComponent = wcschr( GetActualPath(), L'\\' ); pwszComponent; pwszComponent = wcschr( pwszComponent, L'\\' ) ) { pwszComponent++; pwszTilde = wcschr( pwszComponent, L'~' ); if ( 0 == pwszTilde || pwszTilde - pwszComponent > 13) continue; if (IsShortName( pwszComponent )) return TRUE; } } return FALSE; } //+--------------------------------------------------------------------------- // // Class: CFunnyPath // // Purpose: A path name preceeded by "\\?\" to allow file operations when // the length of the path is > MAX_PATH. Keeps the FunnyPath in // lower case. // // History: 08-May-98 vikasman Created // //---------------------------------------------------------------------------- class CLowerFunnyPath : public CFunnyPath { public: CLowerFunnyPath( BOOL fRemote = FALSE ) : CFunnyPath( fRemote ) { } CLowerFunnyPath( const CLowerFunnyPath & src ) { *this = src; } CLowerFunnyPath( const WCHAR * wcsPath, unsigned cc = 0, BOOL fNoConvert = FALSE ) : CFunnyPath() { SetPath( wcsPath, cc, fNoConvert ); } CLowerFunnyPath & operator =( CLowerFunnyPath const & src ) { CFunnyPath::operator=( (CFunnyPath&)src ); return *this; } virtual void SetPath( const WCHAR * wcsPath, unsigned cc = 0, BOOL fNoConvert = FALSE ); virtual void AppendPath( const WCHAR * wcsPath, unsigned cc = 0, BOOL fNoConvert = FALSE ); BOOL ConvertToLongName(); private: // // Converts to wcsPath of length cc to lower case and puts it in // _xBuf starting from ccStart index (actual) // void ToLower( const WCHAR * wcsPath, unsigned cc, unsigned ccStart = 0 ); }; inline void CLowerFunnyPath::SetPath( const WCHAR * wcsPath, unsigned cc, BOOL fNoConvert ) { if ( fNoConvert ) { // already lower case CFunnyPath::SetPath( wcsPath, cc ); AssertLowerCase( GetActualPath(), GetActualLength() ); return; } Win4Assert( wcsPath ); cc = ( 0 == cc ? wcslen( wcsPath ) : cc ) ; if ( 0 == cc ) { InitBlank(); return; } BOOL fInvalidPath = FALSE; if ( L'\\' != wcsPath[0] && L':' != wcsPath[1] ) { // Looks like we don't have a valid path, but still // we will continue. It's not CFunnyPath's problem // We will treat it as a normal path ciDebugOut(( DEB_ITRACE, "CLowerFunnyPath::SetPath - path (%ws) is not valid.\n", wcsPath )); fInvalidPath = TRUE; } if ( L':' == wcsPath[1] || fInvalidPath ) { // normal or invalid path _fRemote = FALSE; _xBuf.SetBuf( _FUNNY_PATH, _FUNNY_PATH_LENGTH + 1 ); } else { // remote path _fRemote = TRUE; _xBuf.SetBuf( _UNC_FUNNY_PATH, _UNC_FUNNY_PATH_LENGTH + 1 ); } ToLower( wcsPath, cc ); } inline void CLowerFunnyPath::AppendPath( const WCHAR * wcsPath, unsigned cc, BOOL fNoConvert ) { if ( fNoConvert ) { // already lower case CFunnyPath::AppendPath( wcsPath, cc ); AssertLowerCase( GetActualPath(), GetActualLength() ); return; } AssertValid(); Win4Assert( wcsPath ); if ( 0 == cc ) { cc = wcslen( wcsPath ); } ToLower( wcsPath, cc, _ccActualBuf ); } inline void CLowerFunnyPath::ToLower( const WCHAR * wcsPath, unsigned cc, unsigned ccStart ) { WCHAR * pwcDestActualPath; unsigned ccDestActualCount; Win4Assert( wcsPath ); if ( !cc ) { return; } #if CIDBG == 1 // Variable to try lowercasing again due to some random // failures with LCMapStringW BOOL fTryAgain = TRUE; #endif while ( TRUE ) { if ( _fRemote ) { pwcDestActualPath = &_xBuf[_UNC_FUNNY_PATH_LENGTH] + ccStart; ccDestActualCount = _xBuf.Count() - (_UNC_FUNNY_PATH_LENGTH + ccStart); } else { pwcDestActualPath = &_xBuf[_FUNNY_PATH_LENGTH] + ccStart; ccDestActualCount = _xBuf.Count() - (_FUNNY_PATH_LENGTH + ccStart); } if ( ccDestActualCount < 2 ) { // Need to allocate more memory _xBuf.SetSize( _xBuf.Count() * 2 ); continue; } unsigned cchLen = LCMapStringW ( LOCALE_NEUTRAL, LCMAP_LOWERCASE, wcsPath, cc, pwcDestActualPath, ccDestActualCount - 1 ); if ( 0 == cchLen ) { if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { _xBuf.SetSize( _xBuf.Count() * 2 ); } else { ciDebugOut(( DEB_ERROR, "Error 0x%x lowercasing path\n", GetLastError() )); #if CIDBG == 1 if ( fTryAgain ) { fTryAgain = FALSE; Win4Assert(( !"neutral lowercase failed. Trying again..." )); continue; } #endif Win4Assert(( !"neutral lowercase failed" )); THROW( CException() ); } } else { Win4Assert( cchLen < ccDestActualCount ); pwcDestActualPath[ cchLen ] = 0; _ccActualBuf = ccStart + cchLen; break; } } } //+--------------------------------------------------------------------------- // // Member: CLowerFunnyPath::ConvertToLongName, public // // Synopsis: Converts file path name components to long names. // // Returns: TRUE if conversion was successful. // // Notes: GetLongPathNameW will fail with remote funny paths because // it will try to call FindFirstFileW on the machine name and // share name. // // History: 06-Jan-98 KyleP Created // //---------------------------------------------------------------------------- inline BOOL CLowerFunnyPath::ConvertToLongName() { XGrowable wcsTemp; WCHAR * pwcsShortName; unsigned ccFunnyChars; if (IsRemote()) { pwcsShortName = _GetActualPath(); ccFunnyChars = 0; } else { pwcsShortName = (WCHAR *)GetPath(); ccFunnyChars = _FUNNY_PATH_LENGTH; } DWORD ccOut = GetLongPathNameW( pwcsShortName, // Short name wcsTemp.Get(), // Long name wcsTemp.Count() - 1 ); if ( ccOut > wcsTemp.Count() - 1 ) { // Need to grow the buffer wcsTemp.SetSize( ccOut + 1 ); ccOut = GetLongPathNameW( pwcsShortName, // Short name wcsTemp.Get(), // Long name wcsTemp.Count() - 1 ); Win4Assert( ccOut <= wcsTemp.Count() - 1 ); } if ( 0 == ccOut ) { ciDebugOut(( DEB_WARN, "GetLongPathName( %ws ) returned %d\n", pwcsShortName, GetLastError() )); return FALSE; } SetPath( wcsTemp.Get() + ccFunnyChars, ccOut - ccFunnyChars ); return TRUE; } // // Stack class for FunnyPath // DECL_DYNSTACK( _CLowerFunnyStack, CLowerFunnyPath ) class CLowerFunnyStack : protected _CLowerFunnyStack { public: BOOL Pop( XPtr & xLowerFunnyPath ) { if (Count() == 0) { return FALSE; } xLowerFunnyPath.Set( _CLowerFunnyStack::Pop() ); return TRUE; } void Push( const CLowerFunnyPath & funnyElem ) { XPtr xLowerFunnyPath( new CLowerFunnyPath(funnyElem) ); _CLowerFunnyStack::Push( xLowerFunnyPath.GetPointer() ); xLowerFunnyPath.Acquire(); } };