/* Copyright 2017 The MathWorks, Inc. */ #ifndef CPPSHAREDLIB_CTF_PATH_HPP #define CPPSHAREDLIB_CTF_PATH_HPP #include "cppsharedlib_path_init.hpp" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #else #include #include #ifdef __APPLE__ #include #endif #endif #include #include #include namespace matlab { namespace cpplib { namespace detail { // An environment variable name must be ASCII, so it can be stored in an std::string. const std::string BASE_CTF_PATH_ENV_VAR_NAME = "CPPSHARED_BASE_CTF_PATH"; const std::u16string BASE_CTF_PATH_ENV_VAR_NAME_U{ BASE_CTF_PATH_ENV_VAR_NAME.cbegin(), BASE_CTF_PATH_ENV_VAR_NAME.cend()}; const char WINDOWS_PATH_SEP_CH = '\\'; const char COLON_CH = ':'; const char UNIX_PATH_SEP_CH = '/'; const char TILDE_CH = '~'; template inline void appendPathSepIfMissing(std::basic_string & strPath, CharT pathSep) { if (strPath.empty()) { strPath.append(1, pathSep); } else { CharT lastChar = strPath[strPath.length() - 1]; if (lastChar != static_cast(WINDOWS_PATH_SEP_CH) && lastChar != static_cast(UNIX_PATH_SEP_CH)) { strPath.append(1, pathSep); } } } template inline bool isAbsolute(const std::basic_string & strPath) { // The path to a CTF should always be greater than 2 characters. return (strPath.length() > 2 && (strPath[0] == static_cast(WINDOWS_PATH_SEP_CH) || strPath[0] == static_cast(UNIX_PATH_SEP_CH) || strPath[0] == static_cast(TILDE_CH) || strPath[1] == static_cast(COLON_CH))); } #ifdef _WIN32 const wchar_t WIN_PATH_SEP_W = L'\\'; const wchar_t * const PATH_SEPS_W = L"/\\"; const std::wstring BASE_CTF_PATH_ENV_VAR_NAME_W{BASE_CTF_PATH_ENV_VAR_NAME.cbegin(), BASE_CTF_PATH_ENV_VAR_NAME.cend()}; // In the names of the following functions, the suffix "W" can be read as referring // to either "Windows" or "wchar_t" (or "wstring"). inline std::wstring getPathToExecutableW() { // There's no guarantee that an ordinary path will fit within MAX_PATH. If it // doesn't fit, we iteratively double the buffer size up to 7 times (which // should be more than enough). static const size_t INITIAL_BUFFER_SIZE = MAX_PATH; static const size_t MAX_ITERATIONS = 7; std::wstring ret; DWORD bufferSize = INITIAL_BUFFER_SIZE; for (size_t iterations = 0; iterations < MAX_ITERATIONS; ++iterations) { ret.resize(bufferSize); DWORD charsReturned = GetModuleFileNameW(NULL, &ret[0], bufferSize); if (charsReturned < ret.length()) { ret.resize(charsReturned); return ret; } else { bufferSize *= 2; } } return L""; } // Path will end with a path separator (forward slash or backslash). // This differs from UNIX, where only forward slashes are treated // as path separators and backslashes are treated as literal characters. inline std::wstring getBaseCtfPathFromExecutableW() { std::wstring ret = getPathToExecutableW(); size_t lastPathSep = ret.find_last_of(PATH_SEPS_W); return ret.substr(0, lastPathSep+1); } inline std::wstring getBaseCtfPathFromEnvVarW() { return getValueFromEnvVarW(BASE_CTF_PATH_ENV_VAR_NAME_W.c_str()); } inline std::wstring getWorkingDirW() { DWORD bufferSize = GetCurrentDirectoryW(0, NULL); std::wstring strPath(bufferSize, wchar_t(0)); GetCurrentDirectoryW(bufferSize, &strPath[0]); return strPath.substr(0, bufferSize-1); } inline std::wstring getPathToCtfW(const std::wstring & ctfAsSpecifiedToInitFunc) { if (isAbsolute(ctfAsSpecifiedToInitFunc)) { if (fileExistsW(ctfAsSpecifiedToInitFunc)) { return ctfAsSpecifiedToInitFunc; } else { return std::wstring(); } } else { std::wstring strPath = getBaseCtfPathFromEnvVarW(); if (fileExistsInDirW(ctfAsSpecifiedToInitFunc, strPath)) { appendPathSepIfMissing(strPath, WIN_PATH_SEP_W); strPath += ctfAsSpecifiedToInitFunc; return strPath; } else { strPath = getWorkingDirW(); if (fileExistsInDirW(ctfAsSpecifiedToInitFunc, strPath)) { appendPathSepIfMissing(strPath, WIN_PATH_SEP_W); strPath += ctfAsSpecifiedToInitFunc; return strPath; } else { strPath = getBaseCtfPathFromExecutableW(); if (fileExistsInDirW(ctfAsSpecifiedToInitFunc, strPath)) { appendPathSepIfMissing(strPath, WIN_PATH_SEP_W); strPath += ctfAsSpecifiedToInitFunc; return strPath; } else { return std::wstring(); } } } } } inline std::u16string getWorkingDir() { std::wstring wstr = getWorkingDirW(); return std::u16string(wstr.cbegin(), wstr.cend()); } inline std::u16string getPathToCtf(const std::u16string & ctfAsSpecifiedToInitFunc) { std::wstring wstrCtfAsSpecifiedToInitFunc(ctfAsSpecifiedToInitFunc.cbegin(), ctfAsSpecifiedToInitFunc.cend()); const std::wstring wstrRet = getPathToCtfW(wstrCtfAsSpecifiedToInitFunc); return std::u16string(wstrRet.cbegin(), wstrRet.cend()); } #else // non-Windows code follows const char16_t UNIX_PATH_SEP_U = static_cast(UNIX_PATH_SEP_CH); inline std::u16string getWorkingDir() { char buf[FILENAME_MAX]; const char * workingDir = getcwd(buf, FILENAME_MAX); if (!workingDir) { return std::u16string(); } else { return std::u16string(buf, buf + strlen(buf)); } } inline std::u16string getBaseCtfPathFromEnvVar() { return getValueFromEnvVar(BASE_CTF_PATH_ENV_VAR_NAME_U); } inline std::u16string getPathToExecutable() { char pBuf[FILENAME_MAX]; #ifdef __linux__ int bytes = std::min(readlink("/proc/self/exe", pBuf, FILENAME_MAX), static_cast(FILENAME_MAX - 1)); if (bytes >= 0) { pBuf[bytes] = '\0'; } return std::u16string(pBuf, pBuf+bytes); #else uint32_t size = sizeof(pBuf); if (_NSGetExecutablePath(pBuf, &size) == 0) { // realpath allocates a buffer using malloc. Use a unique_ptr to ensure that it gets freed // even if convertUTF8StringToUTF16String() throws an exception. std::unique_ptr> ptrCanonical( realpath(pBuf, NULL), [](char * ptr){free(ptr);}); std::u16string ret = matlab::execution::convertUTF8StringToUTF16String(ptrCanonical.get()); return ret; } else { return std::u16string(); } #endif } // Path will end with a path separator (forward slash only). inline std::u16string getBaseCtfPathFromExecutable(const std::u16string & pathToExecutable) { size_t lastPathSep = pathToExecutable.find_last_of(UNIX_PATH_SEP_U); return pathToExecutable.substr(0, lastPathSep+1); } #ifdef __APPLE__ const std::u16string CONTENTS_MAC_OS = u".app/Contents/MacOS"; // Go up three directories. For example, if your executable is // generic_interface/foo_generic.app/Contents/MacOS/foo, look for the // last directory separator prior to ".app/Contents/MacOS" and // terminate the string there. inline std::u16string getPathOutsideBundle(const std::u16string & pathToExecutable) { size_t lastPathSep = pathToExecutable.rfind(CONTENTS_MAC_OS); if (lastPathSep != std::string::npos && lastPathSep != 0) { lastPathSep = pathToExecutable.rfind(UNIX_PATH_SEP_U, lastPathSep-1); if (lastPathSep != std::string::npos) { return pathToExecutable.substr(0, lastPathSep+1); } } return std::u16string(); } #endif // The code in this function is similar, but not identical, to the code // in getPathToCtfW(), which is called by the Windows-defined version of // getPathToCtf(). While it seems as though the code could be shared, in // reality, this would involve having to either (a) write template versions of // lower-level functions that are platform-specific anyway or (b) perform // unnecessary conversions between strings of different types. inline std::u16string getPathToCtf(const std::u16string & ctfAsSpecifiedToInitFunc) { if (isAbsolute(ctfAsSpecifiedToInitFunc)) { if (fileExists(ctfAsSpecifiedToInitFunc)) { return ctfAsSpecifiedToInitFunc; } else { return std::u16string(); } } else { std::u16string strPath = getBaseCtfPathFromEnvVar(); if (fileExistsInDir(ctfAsSpecifiedToInitFunc, strPath)) { appendPathSepIfMissing(strPath, UNIX_PATH_SEP_U); strPath += ctfAsSpecifiedToInitFunc; return strPath; } else { strPath = getWorkingDir(); if (fileExistsInDir(ctfAsSpecifiedToInitFunc, strPath)) { appendPathSepIfMissing(strPath, UNIX_PATH_SEP_U); strPath += ctfAsSpecifiedToInitFunc; return strPath; } else { std::u16string pathToExecutable = getPathToExecutable(); strPath = getBaseCtfPathFromExecutable(pathToExecutable); if (fileExistsInDir(ctfAsSpecifiedToInitFunc, strPath)) { appendPathSepIfMissing(strPath, UNIX_PATH_SEP_U); strPath += ctfAsSpecifiedToInitFunc; return strPath; } else { #ifdef __APPLE__ strPath = getPathOutsideBundle(pathToExecutable); if (fileExistsInDir(ctfAsSpecifiedToInitFunc, strPath)) { appendPathSepIfMissing(strPath, UNIX_PATH_SEP_U); strPath += ctfAsSpecifiedToInitFunc; return strPath; } #endif return std::u16string(); } } } } } #endif }}} // end of nested namespace #endif // CPPSHARED_CTF_PATH_HPP