From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/tier1/pathmatch.cpp | 1812 ++++++++++++++++++++++---------------------- 1 file changed, 906 insertions(+), 906 deletions(-) (limited to 'mp/src/tier1/pathmatch.cpp') diff --git a/mp/src/tier1/pathmatch.cpp b/mp/src/tier1/pathmatch.cpp index af07d70f..c29c6ed3 100644 --- a/mp/src/tier1/pathmatch.cpp +++ b/mp/src/tier1/pathmatch.cpp @@ -1,906 +1,906 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: Utility to interrogate and modify the data in the OSX IPC Server -// -// $NoKeywords: $ -//============================================================================= -// README:README -// -// This file implements the --wrap for ld on linux that lets file i/o api's -// behave as if it were running on a case insensitive file system. Unfortunately, -// this is needed by both steam2 and steam3. It was decided to check the source -// into both locations, otherwise someone would find the .o and have no idea -// where to go for the source if it was in the 'other' tree. Also, because this -// needs to be linked into every elf binary, the .o is checked in for Steam3 so that it is -// always available. In Steam2 it sits with the PosixWin32.cpp implementation and gets -// compiled along side of it through the make system. If you are reading this in Steam3, -// you will probably want to actually make your changes in steam2 and do a baseless merge -// to the steam3 copy. -// -// HOWTO: Add a new function. Add the function with _WRAP to the makefiles as noted below. -// Add the implementation to pathmatch.cpp - probably mimicking the existing functions. -// Build steam2 and copy to matching steam3/client. Take the pathmatch.o from steam 2 -// and check it in to steam3 (in the location noted below). Full rebuild (re-link really) -// of steam3. Test steam and check in. -// -// If you are looking at updating this file, please update the following as needed: -// -// STEAM2.../Projects/GazelleProto/Client/Engine/obj/RELEASE_NORMAL/libsteam_linux/Common/Misc/pathmatch.o -// This is where steam2 builds the pathmatch.o out to. -// -// STEAM2.../Projects/GazelleProto/Makefile.shlib.base - contains _WRAP references -// STEAM2.../Projects/Common/Misc/pathmatch.cpp - Where the source is checked in, keep in sync with: -// STEAM3.../src/common/pathmatch.cpp - should be identical to previous file, but discoverable in steam3. -// STEAM3.../src/lib/linux32/release/pathmatch.o - steam3 checked in version -// STEAM3.../src/devtools/makefile_base_posix.mak - look for the _WRAP references - -#ifdef LINUX - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Enable to do pathmatch caching. Beware: this code isn't threadsafe. -// #define DO_PATHMATCH_CACHE - -#ifdef UTF8_PATHMATCH -#define strcasecmp utf8casecmp -#endif - -static bool s_bShowDiag; -#define DEBUG_MSG( ... ) if ( s_bShowDiag ) fprintf( stderr, ##__VA_ARGS__ ) -#define DEBUG_BREAK() __asm__ __volatile__ ( "int $3" ) -#define _COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} - -#define WRAP( fn, ret, ... ) \ - ret __real_##fn(__VA_ARGS__); \ - ret __wrap_##fn(__VA_ARGS__) - -#define CALL( fn ) __real_##fn - -// Needed by pathmatch code -extern "C" int __real_access(const char *pathname, int mode); -extern "C" DIR *__real_opendir(const char *name); - - -// UTF-8 work from PhysicsFS: http://icculus.org/physfs/ -// Even if it wasn't under the zlib license, Ryan wrote all this code originally. - -#define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF -#define UNICODE_BOGUS_CHAR_CODEPOINT '?' - -inline __attribute__ ((always_inline)) static uint32_t utf8codepoint(const char **_str) -{ - const char *str = *_str; - uint32_t retval = 0; - uint32_t octet = (uint32_t) ((uint8_t) *str); - uint32_t octet2, octet3, octet4; - - if (octet == 0) // null terminator, end of string. - return 0; - - else if (octet < 128) // one octet char: 0 to 127 - { - (*_str)++; // skip to next possible start of codepoint. - return octet; - } - - else if ((octet > 127) && (octet < 192)) // bad (starts with 10xxxxxx). - { - // Apparently each of these is supposed to be flagged as a bogus - // char, instead of just resyncing to the next valid codepoint. - (*_str)++; // skip to next possible start of codepoint. - return UNICODE_BOGUS_CHAR_VALUE; - } - - else if (octet < 224) // two octets - { - octet -= (128+64); - octet2 = (uint32_t) ((uint8_t) *(++str)); - if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - *_str += 2; // skip to next possible start of codepoint. - retval = ((octet << 6) | (octet2 - 128)); - if ((retval >= 0x80) && (retval <= 0x7FF)) - return retval; - } - - else if (octet < 240) // three octets - { - octet -= (128+64+32); - octet2 = (uint32_t) ((uint8_t) *(++str)); - if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet3 = (uint32_t) ((uint8_t) *(++str)); - if ((octet3 & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - *_str += 3; // skip to next possible start of codepoint. - retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) ); - - // There are seven "UTF-16 surrogates" that are illegal in UTF-8. - switch (retval) - { - case 0xD800: - case 0xDB7F: - case 0xDB80: - case 0xDBFF: - case 0xDC00: - case 0xDF80: - case 0xDFFF: - return UNICODE_BOGUS_CHAR_VALUE; - } - - // 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. - if ((retval >= 0x800) && (retval <= 0xFFFD)) - return retval; - } - - else if (octet < 248) // four octets - { - octet -= (128+64+32+16); - octet2 = (uint32_t) ((uint8_t) *(++str)); - if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet3 = (uint32_t) ((uint8_t) *(++str)); - if ((octet3 & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet4 = (uint32_t) ((uint8_t) *(++str)); - if ((octet4 & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - *_str += 4; // skip to next possible start of codepoint. - retval = ( ((octet << 18)) | ((octet2 - 128) << 12) | - ((octet3 - 128) << 6) | ((octet4 - 128)) ); - if ((retval >= 0x10000) && (retval <= 0x10FFFF)) - return retval; - } - - // Five and six octet sequences became illegal in rfc3629. - // We throw the codepoint away, but parse them to make sure we move - // ahead the right number of bytes and don't overflow the buffer. - - else if (octet < 252) // five octets - { - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - *_str += 5; // skip to next possible start of codepoint. - return UNICODE_BOGUS_CHAR_VALUE; - } - - else // six octets - { - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - octet = (uint32_t) ((uint8_t) *(++str)); - if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? - return UNICODE_BOGUS_CHAR_VALUE; - - *_str += 6; // skip to next possible start of codepoint. - return UNICODE_BOGUS_CHAR_VALUE; - } - - return UNICODE_BOGUS_CHAR_VALUE; -} - -typedef struct CaseFoldMapping -{ - uint32_t from; - uint32_t to0; - uint32_t to1; - uint32_t to2; -} CaseFoldMapping; - -typedef struct CaseFoldHashBucket -{ - const uint8_t count; - const CaseFoldMapping *list; -} CaseFoldHashBucket; - -#include "pathmatch_casefolding.h" - -inline __attribute__ ((always_inline)) static void locate_case_fold_mapping(const uint32_t from, uint32_t *to) -{ - const uint8_t hashed = ((from ^ (from >> 8)) & 0xFF); - const CaseFoldHashBucket *bucket = &case_fold_hash[hashed]; - const CaseFoldMapping *mapping = bucket->list; - uint32_t i; - - for (i = 0; i < bucket->count; i++, mapping++) - { - if (mapping->from == from) - { - to[0] = mapping->to0; - to[1] = mapping->to1; - to[2] = mapping->to2; - return; - } - } - - // Not found...there's no remapping for this codepoint. - to[0] = from; - to[1] = 0; - to[2] = 0; -} - -inline __attribute__ ((always_inline)) static uint32_t *fold_utf8(const char *str) -{ - uint32_t *retval = new uint32_t[(strlen(str) * 3) + 1]; - uint32_t *dst = retval; - while (*str) - { - const char ch = *str; - if (ch & 0x80) // high bit set? UTF-8 sequence! - { - uint32_t fold[3]; - locate_case_fold_mapping(utf8codepoint(&str), fold); - *(dst++) = fold[0]; - if (fold[1]) - { - *(dst++) = fold[1]; - if (fold[2]) - *(dst++) = fold[2]; - } - } - else // simple ASCII test. - { - *(dst++) = (uint32_t) (((ch >= 'A') && (ch <= 'Z')) ? ch + 32 : ch); - str++; - } - } - *dst = 0; - return retval; -} - -inline __attribute__ ((always_inline)) static int utf8casecmp_loop(const uint32_t *folded1, const uint32_t *folded2) -{ - while (true) - { - const uint32_t ch1 = *(folded1++); - const uint32_t ch2 = *(folded2++); - if (ch1 < ch2) - return -1; - else if (ch1 > ch2) - return 1; - else if (ch1 == 0) - return 0; // complete match. - } -} - -static int utf8casecmp(const char *str1, const char *str2) -{ - uint32_t *folded1 = fold_utf8(str1); - uint32_t *folded2 = fold_utf8(str2); - const int retval = utf8casecmp_loop(folded1, folded2); - delete[] folded1; - delete[] folded2; - return retval; -} - -// Simple object to help make sure a DIR* from opendir -// gets closed when it goes out of scope. -class CDirPtr -{ -public: - CDirPtr() { m_pDir = NULL; } - CDirPtr( DIR *pDir ) : m_pDir(pDir) {} - ~CDirPtr() { Close(); } - - void operator=(DIR *pDir) { Close(); m_pDir = pDir; } - - operator DIR *() { return m_pDir; } - operator bool() { return m_pDir != NULL; } -private: - - void Close() { if ( m_pDir ) closedir( m_pDir ); } - - DIR *m_pDir; -}; - -// Object used to temporarily slice a path into a smaller componentent -// and then repair it when going out of scope. Typically used as an unnamed -// temp object that is a parameter to a function. -class CDirTrimmer -{ -public: - CDirTrimmer( char * pPath, size_t nTrimIdx ) - { - m_pPath = pPath; - m_idx = nTrimIdx; - m_c = m_pPath[nTrimIdx]; - m_pPath[nTrimIdx] = '\0'; - } - ~CDirTrimmer() { m_pPath[m_idx] = m_c; } - - operator const char *() { return m_pPath; } - -private: - size_t m_idx; - char *m_pPath; - char m_c; -}; - - -enum PathMod_t -{ - kPathUnchanged, - kPathLowered, - kPathChanged, - kPathFailed, -}; - -static bool Descend( char *pPath, size_t nStartIdx, bool bAllowBasenameMismatch, size_t nLevel = 0 ) -{ - DEBUG_MSG( "(%zu) Descend: %s, (%s), %s\n", nLevel, pPath, pPath+nStartIdx, bAllowBasenameMismatch ? "true" : "false " ); - // We assume up through nStartIdx is valid and matching - size_t nNextSlash = nStartIdx+1; - - // path might be a dir - if ( pPath[nNextSlash] == '\0' ) - { - return true; - } - - bool bIsDir = false; // is the new component a directory for certain? - while ( pPath[nNextSlash] != '\0' && pPath[nNextSlash] != '/' ) - { - nNextSlash++; - } - - // Modify the pPath string - if ( pPath[nNextSlash] == '/' ) - bIsDir = true; - - // See if we have an immediate match - if ( __real_access( CDirTrimmer(pPath, nNextSlash), F_OK ) == 0 ) - { - if ( !bIsDir ) - return true; - - bool bRet = Descend( pPath, nNextSlash, bAllowBasenameMismatch, nLevel+1 ); - if ( bRet ) - return true; - } - - // Start enumerating dirents - CDirPtr spDir; - if ( nStartIdx ) - { - // we have a path - spDir = __real_opendir( CDirTrimmer( pPath, nStartIdx ) ); - nStartIdx++; - } - else - { - // we either start at root or cwd - const char *pRoot = "."; - if ( *pPath == '/' ) - { - pRoot = "/"; - nStartIdx++; - } - spDir = __real_opendir( pRoot ); - } - - errno = 0; - struct dirent *pEntry = spDir ? readdir( spDir ) : NULL; - char *pszComponent = pPath + nStartIdx; - size_t cbComponent = nNextSlash - nStartIdx; - while ( pEntry ) - { - DEBUG_MSG( "\t(%zu) comparing %s with %s\n", nLevel, pEntry->d_name, (const char *)CDirTrimmer(pszComponent, cbComponent) ); - - // the candidate must match the target, but not be a case-identical match (we would - // have looked there in the short-circuit code above, so don't look again) - bool bMatches = ( strcasecmp( CDirTrimmer(pszComponent, cbComponent), pEntry->d_name ) == 0 && - strcmp( CDirTrimmer(pszComponent, cbComponent), pEntry->d_name ) != 0 ); - - if ( bMatches ) - { - char *pSrc = pEntry->d_name; - char *pDst = &pPath[nStartIdx]; - // found a match; copy it in. - while ( *pSrc && (*pSrc != '/') ) - { - *pDst++ = *pSrc++; - } - - if ( !bIsDir ) - return true; - - if ( Descend( pPath, nNextSlash, bAllowBasenameMismatch, nLevel+1 ) ) - return true; - - // If descend fails, try more directories - } - pEntry = readdir( spDir ); - } - - if ( bIsDir ) - { - DEBUG_MSG( "(%zu) readdir failed to find '%s' in '%s'\n", nLevel, (const char *)CDirTrimmer(pszComponent, cbComponent), (const char *)CDirTrimmer( pPath, nStartIdx ) ); - } - - // Sometimes it's ok for the filename portion to not match - // since we might be opening for write. Note that if - // the filename matches case insensitive, that will be - // preferred over preserving the input name - if ( !bIsDir && bAllowBasenameMismatch ) - return true; - - return false; -} - -#ifdef DO_PATHMATCH_CACHE -typedef std::map > resultCache_t; -typedef std::map >::iterator resultCacheItr_t; -static resultCache_t resultCache; -static const int k_cMaxCacheLifetimeSeconds = 2; -#endif // DO_PATHMATCH_CACHE - -PathMod_t pathmatch( const char *pszIn, char **ppszOut, bool bAllowBasenameMismatch, char *pszOutBuf, size_t OutBufLen ) -{ - // Path matching can be very expensive, and the cost is unpredictable because it - // depends on how many files are in directories on a user's machine. Therefore - // it should be disabled whenever possible, and only enabled in environments (such - // as running with loose files such as out of Perforce) where it is needed. - static const char *s_pszPathMatchEnabled = getenv("ENABLE_PATHMATCH"); - if ( !s_pszPathMatchEnabled ) - return kPathUnchanged; - - static const char *s_pszDbgPathMatch = getenv("DBG_PATHMATCH"); - - s_bShowDiag = ( s_pszDbgPathMatch != NULL ); - - *ppszOut = NULL; - - if ( __real_access( pszIn, F_OK ) == 0 ) - return kPathUnchanged; - -#ifdef DO_PATHMATCH_CACHE - resultCacheItr_t cachedResult = resultCache.find( pszIn ); - if ( cachedResult != resultCache.end() ) - { - unsigned int age = time( NULL ) - cachedResult->second.second; - const char *pszResult = cachedResult->second.first.c_str(); - if ( pszResult[0] != '\0' || age <= k_cMaxCacheLifetimeSeconds ) - { - if ( pszResult[0] != '\0' ) - { - *ppszOut = strdup( pszResult ); - DEBUG_MSG( "Cached '%s' -> '%s'\n", pszIn, *ppszOut ); - return kPathChanged; - } - else - { - DEBUG_MSG( "Cached '%s' -> kPathFailed\n", pszIn ); - return kPathFailed; - } - } - else if ( age <= k_cMaxCacheLifetimeSeconds ) - { - DEBUG_MSG( "Rechecking '%s' - cache is %u seconds old\n", pszIn, age ); - } - } -#endif // DO_PATHMATCH_CACHE - - char *pPath; - if( strlen( pszIn ) >= OutBufLen ) - { - pPath = strdup( pszIn ); - } - else - { - strncpy( pszOutBuf, pszIn, OutBufLen ); - pPath = pszOutBuf; - } - - if ( pPath ) - { - // I believe this code is broken. I'm guessing someone wanted to avoid lowercasing - // the path before the steam directory - but it's actually skipping lowercasing - // whenever steam is found anywhere - including the filename. For example, - // /home/mikesart/valvesrc/console/l4d2/game/left4dead2_dlc1/particles/steam_fx.pcf - // winds up only having the "steam_fx.pcf" portion lowercased. -#ifdef NEVER - // optimization, if the path contained steam somewhere - // assume the path up through the component with 'steam' in - // is valid (because we almost certainly obtained it - // progamatically - char *p = strcasestr( pPath, "steam" ); - if ( p ) - { - while ( p > pPath ) - { - if ( p[-1] == '/' ) - break; - p--; - } - - if ( ( p == pPath+1 ) && ( *pPath != '/' ) ) - p = pPath; - } - else - { - p = pPath; - } -#else - char *p = pPath; -#endif - - // Try the lower casing of the remaining path - char *pBasename = p; - while ( *p ) - { - if ( *p == '/' ) - pBasename = p+1; - - *p = tolower(*p); - p++; - } - if ( __real_access( pPath, F_OK ) == 0 ) - { - *ppszOut = pPath; - DEBUG_MSG( "Lowered '%s' -> '%s'\n", pszIn, pPath ); - return kPathLowered; - } - - // path didn't match lowered successfully, restore the basename - // if bAllowBasenameMismatch was true - if ( bAllowBasenameMismatch ) - { - const char *pSrc = pszIn + (pBasename - pPath); - while ( *pBasename ) - { - *pBasename++ = *pSrc++; - } - } - - if ( s_pszDbgPathMatch && strcasestr( s_pszDbgPathMatch, pszIn ) ) - { - DEBUG_MSG( "Breaking '%s' in '%s'\n", pszIn, s_pszDbgPathMatch ); - DEBUG_BREAK(); - } - - bool bSuccess = Descend( pPath, 0, bAllowBasenameMismatch ); - if ( bSuccess ) - { - *ppszOut = pPath; - DEBUG_MSG( "Matched '%s' -> '%s'\n", pszIn, pPath ); - } - else - { - DEBUG_MSG( "Unmatched %s\n", pszIn ); - } - -#ifndef DO_PATHMATCH_CACHE - return bSuccess ? kPathChanged : kPathFailed; -#else - time_t now = time(NULL); - if ( bSuccess ) - { - resultCache[ pszIn ] = std::make_pair( *ppszOut, now ); - return kPathChanged; - } - else - { - resultCache[ pszIn ] = std::make_pair( "", now ); - return kPathFailed; - } -#endif - } - return kPathFailed; -} - -// Wrapper object that manages the 'typical' usage cases of pathmatch() -class CWrap -{ -public: - CWrap( const char *pSuppliedPath, bool bAllowMismatchedBasename ) - : m_pSuppliedPath( pSuppliedPath ), m_pBestMatch( NULL ) - { - m_eResult = pathmatch( m_pSuppliedPath, &m_pBestMatch, bAllowMismatchedBasename, m_BestMatchBuf, sizeof( m_BestMatchBuf ) ); - if ( m_pBestMatch == NULL ) - { - m_pBestMatch = const_cast( m_pSuppliedPath ); - } - } - - ~CWrap() - { - if ( ( m_pBestMatch != m_pSuppliedPath ) && ( m_pBestMatch != m_BestMatchBuf ) ) - free( m_pBestMatch ); - } - - const char *GetBest() const { return m_pBestMatch; } - const char *GetOriginal() const { return m_pSuppliedPath; } - PathMod_t GetMatchResult() const { return m_eResult; } - - operator const char*() { return GetBest(); } - -private: - const char *m_pSuppliedPath; - char *m_pBestMatch; - char m_BestMatchBuf[ 512 ]; - PathMod_t m_eResult; -}; - -#ifdef MAIN_TEST -void usage() -{ - puts("pathmatch [options] "); - //puts("options:"); - //puts("\t"); - - exit(-1); -} - -void test( const char *pszFile, bool bAllowBasenameMismatch ) -{ - char *pNewPath; - char NewPathBuf[ 512 ]; - PathMod_t nStat = pathmatch( pszFile, &pNewPath, bAllowBasenameMismatch, NewPathBuf, sizeof( NewPathBuf ) ); - - printf("AllowMismatchedBasename: %s\n", bAllowBasenameMismatch ? "true" : "false" ); - printf("Path Was: "); - switch ( nStat ) - { - case kPathUnchanged: - puts("kPathUnchanged"); - break; - case kPathLowered: - puts("kPathLowered"); - break; - case kPathChanged: - puts("kPathChanged"); - break; - case kPathFailed: - puts("kPathFailed"); - break; - } - - printf(" Path In: %s\n", pszFile ); - printf("Path Out: %s\n", nStat == kPathUnchanged ? pszFile : pNewPath ); - - if ( pNewPath ) - free( pNewPath ); -} - -int -main(int argc, char **argv) -{ - if ( argc <= 1 || argc > 2 ) - usage(); - - test( argv[1], false ); - test( argv[1], true ); - - return 0; -} -#endif - -extern "C" { - - WRAP(freopen, FILE *, const char *path, const char *mode, FILE *stream) - { - // if mode does not have w, a, or +, it's open for read. - bool bAllowBasenameMismatch = strpbrk( mode, "wa+" ) != NULL; - CWrap mpath( path, bAllowBasenameMismatch ); - - return CALL(freopen)( mpath, mode, stream ); - } - - WRAP(fopen, FILE *, const char *path, const char *mode) - { - // if mode does not have w, a, or +, it's open for read. - bool bAllowBasenameMismatch = strpbrk( mode, "wa+" ) != NULL; - CWrap mpath( path, bAllowBasenameMismatch ); - - return CALL(fopen)( mpath, mode ); - } - - - WRAP(fopen64, FILE *, const char *path, const char *mode) - { - // if mode does not have w, a, or +, it's open for read. - bool bAllowBasenameMismatch = strpbrk( mode, "wa+" ) != NULL; - CWrap mpath( path, bAllowBasenameMismatch ); - - return CALL(fopen64)( mpath, mode ); - } - - WRAP(open, int, const char *pathname, int flags, mode_t mode) - { - bool bAllowBasenameMismatch = ((flags & (O_WRONLY | O_RDWR)) != 0); - CWrap mpath( pathname, bAllowBasenameMismatch ); - return CALL(open)( mpath, flags, mode ); - } - - WRAP(open64, int, const char *pathname, int flags, mode_t mode) - { - bool bAllowBasenameMismatch = ((flags & (O_WRONLY | O_RDWR)) != 0); - CWrap mpath( pathname, bAllowBasenameMismatch ); - return CALL(open64)( mpath, flags, mode ); - } - - int __wrap_creat(const char *pathname, mode_t mode) - { - return __wrap_open( pathname, O_CREAT|O_WRONLY|O_TRUNC, mode ); - } - - int __wrap_access(const char *pathname, int mode) - { - return __real_access( CWrap( pathname, false ), mode ); - } - - WRAP(stat, int, const char *path, struct stat *buf) - { - return CALL(stat)( CWrap( path, false ), buf ); - } - - WRAP(lstat, int, const char *path, struct stat *buf) - { - return CALL(lstat)( CWrap( path, false ), buf ); - } - - WRAP(scandir, int, const char *dirp, struct dirent ***namelist, - int (*filter)(const struct dirent *), - int (*compar)(const struct dirent **, const struct dirent **)) - { - return CALL(scandir)( CWrap( dirp, false ), namelist, filter, compar ); - } - - WRAP(opendir, DIR*, const char *name) - { - return CALL(opendir)( CWrap( name, false ) ); - } - - WRAP(__xstat, int, int __ver, __const char *__filename, struct stat *__stat_buf) - { - return CALL(__xstat)( __ver, CWrap( __filename, false), __stat_buf ); - } - - WRAP(__lxstat, int, int __ver, __const char *__filename, struct stat *__stat_buf) - { - return CALL(__lxstat)( __ver, CWrap( __filename, false), __stat_buf ); - } - - WRAP(__xstat64, int, int __ver, __const char *__filename, struct stat *__stat_buf) - { - return CALL(__xstat64)( __ver, CWrap( __filename, false), __stat_buf ); - } - - WRAP(__lxstat64, int, int __ver, __const char *__filename, struct stat *__stat_buf) - { - return CALL(__lxstat64)( __ver, CWrap( __filename, false), __stat_buf ); - } - - WRAP(chmod, int, const char *path, mode_t mode) - { - return CALL(chmod)( CWrap( path, false), mode ); - } - - WRAP(chown, int, const char *path, uid_t owner, gid_t group) - { - return CALL(chown)( CWrap( path, false), owner, group ); - } - - WRAP(lchown, int, const char *path, uid_t owner, gid_t group) - { - return CALL(lchown)( CWrap( path, false), owner, group ); - } - - WRAP(symlink, int, const char *oldpath, const char *newpath) - { - return CALL(symlink)( CWrap( oldpath, false), CWrap( newpath, true ) ); - } - - WRAP(link, int, const char *oldpath, const char *newpath) - { - return CALL(link)( CWrap( oldpath, false), CWrap( newpath, true ) ); - } - - WRAP(mknod, int, const char *pathname, mode_t mode, dev_t dev) - { - return CALL(mknod)( CWrap( pathname, true), mode, dev ); - } - - WRAP(mount, int, const char *source, const char *target, - const char *filesystemtype, unsigned long mountflags, - const void *data) - { - return CALL(mount)( CWrap( source, false ), CWrap( target, false ), filesystemtype, mountflags, data ); - } - - WRAP(unlink, int, const char *pathname) - { - return CALL(unlink)( CWrap( pathname, false ) ); - } - - WRAP(mkfifo, int, const char *pathname, mode_t mode) - { - return CALL(mkfifo)( CWrap( pathname, true ), mode ); - } - - WRAP(rename, int, const char *oldpath, const char *newpath) - { - return CALL(rename)( CWrap( oldpath, false), CWrap( newpath, true ) ); - } - - WRAP(utime, int, const char *filename, const struct utimbuf *times) - { - return CALL(utime)( CWrap( filename, false), times ); - } - - WRAP(utimes, int, const char *filename, const struct timeval times[2]) - { - return CALL(utimes)( CWrap( filename, false), times ); - } - - WRAP(realpath, char *, const char *path, char *resolved_path) - { - return CALL(realpath)( CWrap( path, true ), resolved_path ); - } - - WRAP(mkdir, int, const char *pathname, mode_t mode) - { - return CALL(mkdir)( CWrap( pathname, true ), mode ); - } - - WRAP(rmdir, char *, const char *pathname) - { - return CALL(rmdir)( CWrap( pathname, false ) ); - } - -}; - -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Utility to interrogate and modify the data in the OSX IPC Server +// +// $NoKeywords: $ +//============================================================================= +// README:README +// +// This file implements the --wrap for ld on linux that lets file i/o api's +// behave as if it were running on a case insensitive file system. Unfortunately, +// this is needed by both steam2 and steam3. It was decided to check the source +// into both locations, otherwise someone would find the .o and have no idea +// where to go for the source if it was in the 'other' tree. Also, because this +// needs to be linked into every elf binary, the .o is checked in for Steam3 so that it is +// always available. In Steam2 it sits with the PosixWin32.cpp implementation and gets +// compiled along side of it through the make system. If you are reading this in Steam3, +// you will probably want to actually make your changes in steam2 and do a baseless merge +// to the steam3 copy. +// +// HOWTO: Add a new function. Add the function with _WRAP to the makefiles as noted below. +// Add the implementation to pathmatch.cpp - probably mimicking the existing functions. +// Build steam2 and copy to matching steam3/client. Take the pathmatch.o from steam 2 +// and check it in to steam3 (in the location noted below). Full rebuild (re-link really) +// of steam3. Test steam and check in. +// +// If you are looking at updating this file, please update the following as needed: +// +// STEAM2.../Projects/GazelleProto/Client/Engine/obj/RELEASE_NORMAL/libsteam_linux/Common/Misc/pathmatch.o +// This is where steam2 builds the pathmatch.o out to. +// +// STEAM2.../Projects/GazelleProto/Makefile.shlib.base - contains _WRAP references +// STEAM2.../Projects/Common/Misc/pathmatch.cpp - Where the source is checked in, keep in sync with: +// STEAM3.../src/common/pathmatch.cpp - should be identical to previous file, but discoverable in steam3. +// STEAM3.../src/lib/linux32/release/pathmatch.o - steam3 checked in version +// STEAM3.../src/devtools/makefile_base_posix.mak - look for the _WRAP references + +#ifdef LINUX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Enable to do pathmatch caching. Beware: this code isn't threadsafe. +// #define DO_PATHMATCH_CACHE + +#ifdef UTF8_PATHMATCH +#define strcasecmp utf8casecmp +#endif + +static bool s_bShowDiag; +#define DEBUG_MSG( ... ) if ( s_bShowDiag ) fprintf( stderr, ##__VA_ARGS__ ) +#define DEBUG_BREAK() __asm__ __volatile__ ( "int $3" ) +#define _COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} + +#define WRAP( fn, ret, ... ) \ + ret __real_##fn(__VA_ARGS__); \ + ret __wrap_##fn(__VA_ARGS__) + +#define CALL( fn ) __real_##fn + +// Needed by pathmatch code +extern "C" int __real_access(const char *pathname, int mode); +extern "C" DIR *__real_opendir(const char *name); + + +// UTF-8 work from PhysicsFS: http://icculus.org/physfs/ +// Even if it wasn't under the zlib license, Ryan wrote all this code originally. + +#define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF +#define UNICODE_BOGUS_CHAR_CODEPOINT '?' + +inline __attribute__ ((always_inline)) static uint32_t utf8codepoint(const char **_str) +{ + const char *str = *_str; + uint32_t retval = 0; + uint32_t octet = (uint32_t) ((uint8_t) *str); + uint32_t octet2, octet3, octet4; + + if (octet == 0) // null terminator, end of string. + return 0; + + else if (octet < 128) // one octet char: 0 to 127 + { + (*_str)++; // skip to next possible start of codepoint. + return octet; + } + + else if ((octet > 127) && (octet < 192)) // bad (starts with 10xxxxxx). + { + // Apparently each of these is supposed to be flagged as a bogus + // char, instead of just resyncing to the next valid codepoint. + (*_str)++; // skip to next possible start of codepoint. + return UNICODE_BOGUS_CHAR_VALUE; + } + + else if (octet < 224) // two octets + { + octet -= (128+64); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 2; // skip to next possible start of codepoint. + retval = ((octet << 6) | (octet2 - 128)); + if ((retval >= 0x80) && (retval <= 0x7FF)) + return retval; + } + + else if (octet < 240) // three octets + { + octet -= (128+64+32); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (uint32_t) ((uint8_t) *(++str)); + if ((octet3 & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 3; // skip to next possible start of codepoint. + retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) ); + + // There are seven "UTF-16 surrogates" that are illegal in UTF-8. + switch (retval) + { + case 0xD800: + case 0xDB7F: + case 0xDB80: + case 0xDBFF: + case 0xDC00: + case 0xDF80: + case 0xDFFF: + return UNICODE_BOGUS_CHAR_VALUE; + } + + // 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. + if ((retval >= 0x800) && (retval <= 0xFFFD)) + return retval; + } + + else if (octet < 248) // four octets + { + octet -= (128+64+32+16); + octet2 = (uint32_t) ((uint8_t) *(++str)); + if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet3 = (uint32_t) ((uint8_t) *(++str)); + if ((octet3 & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet4 = (uint32_t) ((uint8_t) *(++str)); + if ((octet4 & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 4; // skip to next possible start of codepoint. + retval = ( ((octet << 18)) | ((octet2 - 128) << 12) | + ((octet3 - 128) << 6) | ((octet4 - 128)) ); + if ((retval >= 0x10000) && (retval <= 0x10FFFF)) + return retval; + } + + // Five and six octet sequences became illegal in rfc3629. + // We throw the codepoint away, but parse them to make sure we move + // ahead the right number of bytes and don't overflow the buffer. + + else if (octet < 252) // five octets + { + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 5; // skip to next possible start of codepoint. + return UNICODE_BOGUS_CHAR_VALUE; + } + + else // six octets + { + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + octet = (uint32_t) ((uint8_t) *(++str)); + if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx? + return UNICODE_BOGUS_CHAR_VALUE; + + *_str += 6; // skip to next possible start of codepoint. + return UNICODE_BOGUS_CHAR_VALUE; + } + + return UNICODE_BOGUS_CHAR_VALUE; +} + +typedef struct CaseFoldMapping +{ + uint32_t from; + uint32_t to0; + uint32_t to1; + uint32_t to2; +} CaseFoldMapping; + +typedef struct CaseFoldHashBucket +{ + const uint8_t count; + const CaseFoldMapping *list; +} CaseFoldHashBucket; + +#include "pathmatch_casefolding.h" + +inline __attribute__ ((always_inline)) static void locate_case_fold_mapping(const uint32_t from, uint32_t *to) +{ + const uint8_t hashed = ((from ^ (from >> 8)) & 0xFF); + const CaseFoldHashBucket *bucket = &case_fold_hash[hashed]; + const CaseFoldMapping *mapping = bucket->list; + uint32_t i; + + for (i = 0; i < bucket->count; i++, mapping++) + { + if (mapping->from == from) + { + to[0] = mapping->to0; + to[1] = mapping->to1; + to[2] = mapping->to2; + return; + } + } + + // Not found...there's no remapping for this codepoint. + to[0] = from; + to[1] = 0; + to[2] = 0; +} + +inline __attribute__ ((always_inline)) static uint32_t *fold_utf8(const char *str) +{ + uint32_t *retval = new uint32_t[(strlen(str) * 3) + 1]; + uint32_t *dst = retval; + while (*str) + { + const char ch = *str; + if (ch & 0x80) // high bit set? UTF-8 sequence! + { + uint32_t fold[3]; + locate_case_fold_mapping(utf8codepoint(&str), fold); + *(dst++) = fold[0]; + if (fold[1]) + { + *(dst++) = fold[1]; + if (fold[2]) + *(dst++) = fold[2]; + } + } + else // simple ASCII test. + { + *(dst++) = (uint32_t) (((ch >= 'A') && (ch <= 'Z')) ? ch + 32 : ch); + str++; + } + } + *dst = 0; + return retval; +} + +inline __attribute__ ((always_inline)) static int utf8casecmp_loop(const uint32_t *folded1, const uint32_t *folded2) +{ + while (true) + { + const uint32_t ch1 = *(folded1++); + const uint32_t ch2 = *(folded2++); + if (ch1 < ch2) + return -1; + else if (ch1 > ch2) + return 1; + else if (ch1 == 0) + return 0; // complete match. + } +} + +static int utf8casecmp(const char *str1, const char *str2) +{ + uint32_t *folded1 = fold_utf8(str1); + uint32_t *folded2 = fold_utf8(str2); + const int retval = utf8casecmp_loop(folded1, folded2); + delete[] folded1; + delete[] folded2; + return retval; +} + +// Simple object to help make sure a DIR* from opendir +// gets closed when it goes out of scope. +class CDirPtr +{ +public: + CDirPtr() { m_pDir = NULL; } + CDirPtr( DIR *pDir ) : m_pDir(pDir) {} + ~CDirPtr() { Close(); } + + void operator=(DIR *pDir) { Close(); m_pDir = pDir; } + + operator DIR *() { return m_pDir; } + operator bool() { return m_pDir != NULL; } +private: + + void Close() { if ( m_pDir ) closedir( m_pDir ); } + + DIR *m_pDir; +}; + +// Object used to temporarily slice a path into a smaller componentent +// and then repair it when going out of scope. Typically used as an unnamed +// temp object that is a parameter to a function. +class CDirTrimmer +{ +public: + CDirTrimmer( char * pPath, size_t nTrimIdx ) + { + m_pPath = pPath; + m_idx = nTrimIdx; + m_c = m_pPath[nTrimIdx]; + m_pPath[nTrimIdx] = '\0'; + } + ~CDirTrimmer() { m_pPath[m_idx] = m_c; } + + operator const char *() { return m_pPath; } + +private: + size_t m_idx; + char *m_pPath; + char m_c; +}; + + +enum PathMod_t +{ + kPathUnchanged, + kPathLowered, + kPathChanged, + kPathFailed, +}; + +static bool Descend( char *pPath, size_t nStartIdx, bool bAllowBasenameMismatch, size_t nLevel = 0 ) +{ + DEBUG_MSG( "(%zu) Descend: %s, (%s), %s\n", nLevel, pPath, pPath+nStartIdx, bAllowBasenameMismatch ? "true" : "false " ); + // We assume up through nStartIdx is valid and matching + size_t nNextSlash = nStartIdx+1; + + // path might be a dir + if ( pPath[nNextSlash] == '\0' ) + { + return true; + } + + bool bIsDir = false; // is the new component a directory for certain? + while ( pPath[nNextSlash] != '\0' && pPath[nNextSlash] != '/' ) + { + nNextSlash++; + } + + // Modify the pPath string + if ( pPath[nNextSlash] == '/' ) + bIsDir = true; + + // See if we have an immediate match + if ( __real_access( CDirTrimmer(pPath, nNextSlash), F_OK ) == 0 ) + { + if ( !bIsDir ) + return true; + + bool bRet = Descend( pPath, nNextSlash, bAllowBasenameMismatch, nLevel+1 ); + if ( bRet ) + return true; + } + + // Start enumerating dirents + CDirPtr spDir; + if ( nStartIdx ) + { + // we have a path + spDir = __real_opendir( CDirTrimmer( pPath, nStartIdx ) ); + nStartIdx++; + } + else + { + // we either start at root or cwd + const char *pRoot = "."; + if ( *pPath == '/' ) + { + pRoot = "/"; + nStartIdx++; + } + spDir = __real_opendir( pRoot ); + } + + errno = 0; + struct dirent *pEntry = spDir ? readdir( spDir ) : NULL; + char *pszComponent = pPath + nStartIdx; + size_t cbComponent = nNextSlash - nStartIdx; + while ( pEntry ) + { + DEBUG_MSG( "\t(%zu) comparing %s with %s\n", nLevel, pEntry->d_name, (const char *)CDirTrimmer(pszComponent, cbComponent) ); + + // the candidate must match the target, but not be a case-identical match (we would + // have looked there in the short-circuit code above, so don't look again) + bool bMatches = ( strcasecmp( CDirTrimmer(pszComponent, cbComponent), pEntry->d_name ) == 0 && + strcmp( CDirTrimmer(pszComponent, cbComponent), pEntry->d_name ) != 0 ); + + if ( bMatches ) + { + char *pSrc = pEntry->d_name; + char *pDst = &pPath[nStartIdx]; + // found a match; copy it in. + while ( *pSrc && (*pSrc != '/') ) + { + *pDst++ = *pSrc++; + } + + if ( !bIsDir ) + return true; + + if ( Descend( pPath, nNextSlash, bAllowBasenameMismatch, nLevel+1 ) ) + return true; + + // If descend fails, try more directories + } + pEntry = readdir( spDir ); + } + + if ( bIsDir ) + { + DEBUG_MSG( "(%zu) readdir failed to find '%s' in '%s'\n", nLevel, (const char *)CDirTrimmer(pszComponent, cbComponent), (const char *)CDirTrimmer( pPath, nStartIdx ) ); + } + + // Sometimes it's ok for the filename portion to not match + // since we might be opening for write. Note that if + // the filename matches case insensitive, that will be + // preferred over preserving the input name + if ( !bIsDir && bAllowBasenameMismatch ) + return true; + + return false; +} + +#ifdef DO_PATHMATCH_CACHE +typedef std::map > resultCache_t; +typedef std::map >::iterator resultCacheItr_t; +static resultCache_t resultCache; +static const int k_cMaxCacheLifetimeSeconds = 2; +#endif // DO_PATHMATCH_CACHE + +PathMod_t pathmatch( const char *pszIn, char **ppszOut, bool bAllowBasenameMismatch, char *pszOutBuf, size_t OutBufLen ) +{ + // Path matching can be very expensive, and the cost is unpredictable because it + // depends on how many files are in directories on a user's machine. Therefore + // it should be disabled whenever possible, and only enabled in environments (such + // as running with loose files such as out of Perforce) where it is needed. + static const char *s_pszPathMatchEnabled = getenv("ENABLE_PATHMATCH"); + if ( !s_pszPathMatchEnabled ) + return kPathUnchanged; + + static const char *s_pszDbgPathMatch = getenv("DBG_PATHMATCH"); + + s_bShowDiag = ( s_pszDbgPathMatch != NULL ); + + *ppszOut = NULL; + + if ( __real_access( pszIn, F_OK ) == 0 ) + return kPathUnchanged; + +#ifdef DO_PATHMATCH_CACHE + resultCacheItr_t cachedResult = resultCache.find( pszIn ); + if ( cachedResult != resultCache.end() ) + { + unsigned int age = time( NULL ) - cachedResult->second.second; + const char *pszResult = cachedResult->second.first.c_str(); + if ( pszResult[0] != '\0' || age <= k_cMaxCacheLifetimeSeconds ) + { + if ( pszResult[0] != '\0' ) + { + *ppszOut = strdup( pszResult ); + DEBUG_MSG( "Cached '%s' -> '%s'\n", pszIn, *ppszOut ); + return kPathChanged; + } + else + { + DEBUG_MSG( "Cached '%s' -> kPathFailed\n", pszIn ); + return kPathFailed; + } + } + else if ( age <= k_cMaxCacheLifetimeSeconds ) + { + DEBUG_MSG( "Rechecking '%s' - cache is %u seconds old\n", pszIn, age ); + } + } +#endif // DO_PATHMATCH_CACHE + + char *pPath; + if( strlen( pszIn ) >= OutBufLen ) + { + pPath = strdup( pszIn ); + } + else + { + strncpy( pszOutBuf, pszIn, OutBufLen ); + pPath = pszOutBuf; + } + + if ( pPath ) + { + // I believe this code is broken. I'm guessing someone wanted to avoid lowercasing + // the path before the steam directory - but it's actually skipping lowercasing + // whenever steam is found anywhere - including the filename. For example, + // /home/mikesart/valvesrc/console/l4d2/game/left4dead2_dlc1/particles/steam_fx.pcf + // winds up only having the "steam_fx.pcf" portion lowercased. +#ifdef NEVER + // optimization, if the path contained steam somewhere + // assume the path up through the component with 'steam' in + // is valid (because we almost certainly obtained it + // progamatically + char *p = strcasestr( pPath, "steam" ); + if ( p ) + { + while ( p > pPath ) + { + if ( p[-1] == '/' ) + break; + p--; + } + + if ( ( p == pPath+1 ) && ( *pPath != '/' ) ) + p = pPath; + } + else + { + p = pPath; + } +#else + char *p = pPath; +#endif + + // Try the lower casing of the remaining path + char *pBasename = p; + while ( *p ) + { + if ( *p == '/' ) + pBasename = p+1; + + *p = tolower(*p); + p++; + } + if ( __real_access( pPath, F_OK ) == 0 ) + { + *ppszOut = pPath; + DEBUG_MSG( "Lowered '%s' -> '%s'\n", pszIn, pPath ); + return kPathLowered; + } + + // path didn't match lowered successfully, restore the basename + // if bAllowBasenameMismatch was true + if ( bAllowBasenameMismatch ) + { + const char *pSrc = pszIn + (pBasename - pPath); + while ( *pBasename ) + { + *pBasename++ = *pSrc++; + } + } + + if ( s_pszDbgPathMatch && strcasestr( s_pszDbgPathMatch, pszIn ) ) + { + DEBUG_MSG( "Breaking '%s' in '%s'\n", pszIn, s_pszDbgPathMatch ); + DEBUG_BREAK(); + } + + bool bSuccess = Descend( pPath, 0, bAllowBasenameMismatch ); + if ( bSuccess ) + { + *ppszOut = pPath; + DEBUG_MSG( "Matched '%s' -> '%s'\n", pszIn, pPath ); + } + else + { + DEBUG_MSG( "Unmatched %s\n", pszIn ); + } + +#ifndef DO_PATHMATCH_CACHE + return bSuccess ? kPathChanged : kPathFailed; +#else + time_t now = time(NULL); + if ( bSuccess ) + { + resultCache[ pszIn ] = std::make_pair( *ppszOut, now ); + return kPathChanged; + } + else + { + resultCache[ pszIn ] = std::make_pair( "", now ); + return kPathFailed; + } +#endif + } + return kPathFailed; +} + +// Wrapper object that manages the 'typical' usage cases of pathmatch() +class CWrap +{ +public: + CWrap( const char *pSuppliedPath, bool bAllowMismatchedBasename ) + : m_pSuppliedPath( pSuppliedPath ), m_pBestMatch( NULL ) + { + m_eResult = pathmatch( m_pSuppliedPath, &m_pBestMatch, bAllowMismatchedBasename, m_BestMatchBuf, sizeof( m_BestMatchBuf ) ); + if ( m_pBestMatch == NULL ) + { + m_pBestMatch = const_cast( m_pSuppliedPath ); + } + } + + ~CWrap() + { + if ( ( m_pBestMatch != m_pSuppliedPath ) && ( m_pBestMatch != m_BestMatchBuf ) ) + free( m_pBestMatch ); + } + + const char *GetBest() const { return m_pBestMatch; } + const char *GetOriginal() const { return m_pSuppliedPath; } + PathMod_t GetMatchResult() const { return m_eResult; } + + operator const char*() { return GetBest(); } + +private: + const char *m_pSuppliedPath; + char *m_pBestMatch; + char m_BestMatchBuf[ 512 ]; + PathMod_t m_eResult; +}; + +#ifdef MAIN_TEST +void usage() +{ + puts("pathmatch [options] "); + //puts("options:"); + //puts("\t"); + + exit(-1); +} + +void test( const char *pszFile, bool bAllowBasenameMismatch ) +{ + char *pNewPath; + char NewPathBuf[ 512 ]; + PathMod_t nStat = pathmatch( pszFile, &pNewPath, bAllowBasenameMismatch, NewPathBuf, sizeof( NewPathBuf ) ); + + printf("AllowMismatchedBasename: %s\n", bAllowBasenameMismatch ? "true" : "false" ); + printf("Path Was: "); + switch ( nStat ) + { + case kPathUnchanged: + puts("kPathUnchanged"); + break; + case kPathLowered: + puts("kPathLowered"); + break; + case kPathChanged: + puts("kPathChanged"); + break; + case kPathFailed: + puts("kPathFailed"); + break; + } + + printf(" Path In: %s\n", pszFile ); + printf("Path Out: %s\n", nStat == kPathUnchanged ? pszFile : pNewPath ); + + if ( pNewPath ) + free( pNewPath ); +} + +int +main(int argc, char **argv) +{ + if ( argc <= 1 || argc > 2 ) + usage(); + + test( argv[1], false ); + test( argv[1], true ); + + return 0; +} +#endif + +extern "C" { + + WRAP(freopen, FILE *, const char *path, const char *mode, FILE *stream) + { + // if mode does not have w, a, or +, it's open for read. + bool bAllowBasenameMismatch = strpbrk( mode, "wa+" ) != NULL; + CWrap mpath( path, bAllowBasenameMismatch ); + + return CALL(freopen)( mpath, mode, stream ); + } + + WRAP(fopen, FILE *, const char *path, const char *mode) + { + // if mode does not have w, a, or +, it's open for read. + bool bAllowBasenameMismatch = strpbrk( mode, "wa+" ) != NULL; + CWrap mpath( path, bAllowBasenameMismatch ); + + return CALL(fopen)( mpath, mode ); + } + + + WRAP(fopen64, FILE *, const char *path, const char *mode) + { + // if mode does not have w, a, or +, it's open for read. + bool bAllowBasenameMismatch = strpbrk( mode, "wa+" ) != NULL; + CWrap mpath( path, bAllowBasenameMismatch ); + + return CALL(fopen64)( mpath, mode ); + } + + WRAP(open, int, const char *pathname, int flags, mode_t mode) + { + bool bAllowBasenameMismatch = ((flags & (O_WRONLY | O_RDWR)) != 0); + CWrap mpath( pathname, bAllowBasenameMismatch ); + return CALL(open)( mpath, flags, mode ); + } + + WRAP(open64, int, const char *pathname, int flags, mode_t mode) + { + bool bAllowBasenameMismatch = ((flags & (O_WRONLY | O_RDWR)) != 0); + CWrap mpath( pathname, bAllowBasenameMismatch ); + return CALL(open64)( mpath, flags, mode ); + } + + int __wrap_creat(const char *pathname, mode_t mode) + { + return __wrap_open( pathname, O_CREAT|O_WRONLY|O_TRUNC, mode ); + } + + int __wrap_access(const char *pathname, int mode) + { + return __real_access( CWrap( pathname, false ), mode ); + } + + WRAP(stat, int, const char *path, struct stat *buf) + { + return CALL(stat)( CWrap( path, false ), buf ); + } + + WRAP(lstat, int, const char *path, struct stat *buf) + { + return CALL(lstat)( CWrap( path, false ), buf ); + } + + WRAP(scandir, int, const char *dirp, struct dirent ***namelist, + int (*filter)(const struct dirent *), + int (*compar)(const struct dirent **, const struct dirent **)) + { + return CALL(scandir)( CWrap( dirp, false ), namelist, filter, compar ); + } + + WRAP(opendir, DIR*, const char *name) + { + return CALL(opendir)( CWrap( name, false ) ); + } + + WRAP(__xstat, int, int __ver, __const char *__filename, struct stat *__stat_buf) + { + return CALL(__xstat)( __ver, CWrap( __filename, false), __stat_buf ); + } + + WRAP(__lxstat, int, int __ver, __const char *__filename, struct stat *__stat_buf) + { + return CALL(__lxstat)( __ver, CWrap( __filename, false), __stat_buf ); + } + + WRAP(__xstat64, int, int __ver, __const char *__filename, struct stat *__stat_buf) + { + return CALL(__xstat64)( __ver, CWrap( __filename, false), __stat_buf ); + } + + WRAP(__lxstat64, int, int __ver, __const char *__filename, struct stat *__stat_buf) + { + return CALL(__lxstat64)( __ver, CWrap( __filename, false), __stat_buf ); + } + + WRAP(chmod, int, const char *path, mode_t mode) + { + return CALL(chmod)( CWrap( path, false), mode ); + } + + WRAP(chown, int, const char *path, uid_t owner, gid_t group) + { + return CALL(chown)( CWrap( path, false), owner, group ); + } + + WRAP(lchown, int, const char *path, uid_t owner, gid_t group) + { + return CALL(lchown)( CWrap( path, false), owner, group ); + } + + WRAP(symlink, int, const char *oldpath, const char *newpath) + { + return CALL(symlink)( CWrap( oldpath, false), CWrap( newpath, true ) ); + } + + WRAP(link, int, const char *oldpath, const char *newpath) + { + return CALL(link)( CWrap( oldpath, false), CWrap( newpath, true ) ); + } + + WRAP(mknod, int, const char *pathname, mode_t mode, dev_t dev) + { + return CALL(mknod)( CWrap( pathname, true), mode, dev ); + } + + WRAP(mount, int, const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const void *data) + { + return CALL(mount)( CWrap( source, false ), CWrap( target, false ), filesystemtype, mountflags, data ); + } + + WRAP(unlink, int, const char *pathname) + { + return CALL(unlink)( CWrap( pathname, false ) ); + } + + WRAP(mkfifo, int, const char *pathname, mode_t mode) + { + return CALL(mkfifo)( CWrap( pathname, true ), mode ); + } + + WRAP(rename, int, const char *oldpath, const char *newpath) + { + return CALL(rename)( CWrap( oldpath, false), CWrap( newpath, true ) ); + } + + WRAP(utime, int, const char *filename, const struct utimbuf *times) + { + return CALL(utime)( CWrap( filename, false), times ); + } + + WRAP(utimes, int, const char *filename, const struct timeval times[2]) + { + return CALL(utimes)( CWrap( filename, false), times ); + } + + WRAP(realpath, char *, const char *path, char *resolved_path) + { + return CALL(realpath)( CWrap( path, true ), resolved_path ); + } + + WRAP(mkdir, int, const char *pathname, mode_t mode) + { + return CALL(mkdir)( CWrap( pathname, true ), mode ); + } + + WRAP(rmdir, char *, const char *pathname) + { + return CALL(rmdir)( CWrap( pathname, false ) ); + } + +}; + +#endif -- cgit v1.2.3