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.
918 lines
25 KiB
918 lines
25 KiB
//========= 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 <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdint.h> |
|
#include <strings.h> |
|
#include <unistd.h> |
|
#include <getopt.h> |
|
#include <errno.h> |
|
#include <signal.h> |
|
#include <ctype.h> |
|
#include <dirent.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <sys/time.h> |
|
#include <sys/mount.h> |
|
#include <fcntl.h> |
|
#include <utime.h> |
|
#include <map> |
|
#include <string> |
|
#include <time.h> |
|
|
|
|
|
// 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__ ) |
|
|
|
#ifdef POSIX |
|
#include <signal.h> |
|
#define DEBUG_BREAK() raise(SIGINT) |
|
#elif !defined (__arm__) |
|
#define DEBUG_BREAK() __asm__ __volatile__ ( "int $3" ) |
|
#else |
|
#define DEBUG_BREAK() |
|
#endif |
|
|
|
#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. |
|
} |
|
} |
|
|
|
#ifdef UTF8_PATHMATCH |
|
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; |
|
} |
|
#endif |
|
|
|
// 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<std::string, std::pair<std::string, time_t> > resultCache_t; |
|
typedef std::map<std::string, std::pair<std::string, time_t> >::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<char*>( 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] <path>"); |
|
//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
|
|
|