//===--- Paths.cpp - Swift Runtime path utility functions -------*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2022 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Functions that obtain paths that might be useful within the runtime. // //===----------------------------------------------------------------------===// #include "swift/Runtime/Config.h" #include "swift/Runtime/EnvironmentVariables.h" #include "swift/Runtime/Debug.h" #include "swift/Runtime/Paths.h" #include "swift/Runtime/Win32.h" #include "swift/Threading/Once.h" #ifdef __APPLE__ #include #endif #if !defined(_WIN32) || defined(__CYGWIN__) #if __has_include() # include # ifdef S_IFMT # define HAS_STAT 1 # else # define HAS_STAT 0 # endif #else # define HAS_STAT 0 #endif #if SWIFT_STDLIB_HAS_DLADDR #include #endif #if __has_include() #include #define APPLE_OS_SYSTEM 1 #else #define APPLE_OS_SYSTEM 0 #endif #else // defined(_WIN32) #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include #endif // defined(_WIN32) #ifdef __linux__ // Needed for 'readlink'. #include #endif #include #include #include #if !SWIFT_STDLIB_HAS_FILESYSTEM SWIFT_RUNTIME_EXPORT const char * swift_getRuntimeLibraryPath() { return nullptr; } SWIFT_RUNTIME_EXPORT const char * swift_getRootPath() { return nullptr; } SWIFT_RUNTIME_EXPORT char * swift_copyAuxiliaryExecutablePath(const char *name) { return nullptr; } #else // SWIFT_STDLIB_HAS_FILESYSTEM namespace { swift::once_t runtimePathToken; const char *runtimePath; swift::once_t rootPathToken; const char *rootPath; void _swift_initRuntimePath(void *); void _swift_initRootPath(void *); const char *_swift_getDefaultRootPath(); char *_swift_getAuxExePathIn(const char *path, const char *name); char *_swift_tryAuxExePath(const char *name, const char *path, ...); bool _swift_isPathSep(char ch) { #ifdef _WIN32 return ch == '/' || ch == '\\'; #else return ch == '/'; #endif } bool _swift_exists(const char *path); #if !defined(_WIN32) || defined(__CYGWIN__) #define PATHSEP_STR "/" #define PATHSEP_CHR '/' #else #define PATHSEP_STR "\\" #define PATHSEP_CHR '\\' #endif } SWIFT_RUNTIME_EXPORT const char * swift_getRuntimeLibraryPath() { swift::once(runtimePathToken, _swift_initRuntimePath, nullptr); return runtimePath; } SWIFT_RUNTIME_EXPORT const char * swift_getRootPath() { swift::once(rootPathToken, _swift_initRootPath, nullptr); return rootPath; } namespace { bool _swift_lookingAtLibSwift(const char *ptr, const char *base) { // /some/path/to/some/thing/lib/swift/libswiftCore.dylib // ^ ^ // | +---- ptr // +-------------- ptr - 10 return (ptr - base >= 10 && _swift_isPathSep(ptr[-10]) && std::strncmp(ptr - 9, "lib", 3) == 0 && _swift_isPathSep(ptr[-6]) && std::strncmp(ptr - 5, "swift", 5) == 0); } bool _swift_lookingAtBin(const char *ptr, const char *base) { // C:\some\path\to\some\thing\bin\libswiftCore.dylib // ^ ^ // | +---- ptr // +-------- ptr - 4 return (ptr - base > 4 && _swift_isPathSep(ptr[-4]) && std::strncmp(ptr - 3, "bin", 3) == 0); } const char * _swift_getDefaultRootPath() { const char *runtimePath = swift_getRuntimeLibraryPath(); if (!runtimePath) return nullptr; size_t runtimePathLen = std::strlen(runtimePath); // Scan backwards until we find a path separator const char *ptr = runtimePath + runtimePathLen; while (ptr > runtimePath && !_swift_isPathSep(*--ptr)); if (_swift_lookingAtLibSwift(ptr, runtimePath)) { // /some/path/to/some/thing/lib/swift/libswiftCore.dylib // ^ ^ // | +---- ptr // +-------------- ptr - 10 ptr -= 10; } else { // We *might* be in a or / directory, so scan // backwards for that too bool found = false; const char *platform = ptr; for (unsigned n = 0; n < 2; ++n) { while (platform > runtimePath && !_swift_isPathSep(*--platform)); if (_swift_lookingAtLibSwift(platform, runtimePath)) { // When we get here, we have: // // /some/path/to/some/thing/lib/swift/macosx/libswiftCore.dylib // ^ ^ ^ // | | +---- ptr // | +----------- platform // +--------------------- platform - 10 ptr = platform - 10; found = true; break; } } if (!found) { // We *might* also be in a bin directory, for instance on Windows, so // check if we should remove that also. if (_swift_lookingAtBin(ptr, runtimePath)) { // C:\some\path\to\some\thing\bin\libswiftCore.dylib // ^ ^ // | +---- ptr // +-------- ptr - 4 ptr -= 4; } } } // If the result is empty, return "./" or ".\\" if (ptr == runtimePath) { return "." PATHSEP_STR; } // Duplicate the string up to and including ptr size_t len = ptr - runtimePath + 1; char *thePath = (char *)malloc(len + 1); std::memcpy(thePath, runtimePath, len); thePath[len] = 0; return thePath; } // Join paths together char * _swift_joinPathsV(const char *path, va_list val) { va_list val2; size_t baseLen = 0; size_t totalLen = 0; const char *pathSeg; va_copy(val2, val); baseLen = std::strlen(path); while (baseLen && _swift_isPathSep(path[baseLen - 1])) --baseLen; if (!baseLen) totalLen = 1; else totalLen = baseLen; while ((pathSeg = va_arg(val2, const char *))) { size_t len = std::strlen(pathSeg); while (len && _swift_isPathSep(pathSeg[len - 1])) --len; if (len) totalLen += 1 + len; } char *buffer = static_cast(std::malloc(totalLen + 1)); char *ptr = buffer; if (!baseLen) *ptr++ = PATHSEP_CHR; else { std::memcpy(ptr, path, baseLen); ptr += baseLen; } while ((pathSeg = va_arg(val, const char *))) { size_t len = std::strlen(pathSeg); while (len && _swift_isPathSep(pathSeg[len - 1])) --len; if (len) { *ptr++ = PATHSEP_CHR; std::memcpy(ptr, pathSeg, len); ptr += len; } } buffer[totalLen] = 0; return buffer; } char * _swift_joinPaths(const char *path, ...) { va_list val; char *result; va_start(val, path); result = _swift_joinPathsV(path, val); va_end(val); return result; } void _swift_initRootPath(void *) { // SWIFT_ROOT overrides the path returned by this function const char *swiftRoot = swift::runtime::environment::SWIFT_ROOT(); if (!swiftRoot || !*swiftRoot) { rootPath = _swift_getDefaultRootPath(); return; } size_t len = std::strlen(swiftRoot); // Ensure that there's a trailing slash if (_swift_isPathSep(swiftRoot[len - 1])) { rootPath = swiftRoot; } else { char *thePath = (char *)malloc(len + 2); std::memcpy(thePath, swiftRoot, len); thePath[len] = PATHSEP_CHR; thePath[len + 1] = 0; rootPath = thePath; } } #if _WIN32 /// Map an NT-style filename to a Win32 filename. /// /// We can't use GetFinalPathNameByHandle() because there's no way to obtain /// a handle (at least, not without using the internal NtCreateFile() API, which /// we aren't supposed to be using). Additionally, that function would resolve /// symlinks, which we don't want to do here. /// /// As a result, we use the approach demonstrated here: /// /// https://learn.microsoft.com/en-us/windows/win32/memory/obtaining-a-file-name-from-a-file-handle /// /// @param pszFilename The NT-style filename to convert. /// /// @result A string, allocated using std::malloc(), containing the Win32-style /// filename. LPWSTR _swift_win32NameFromNTName(LPWSTR pszFilename) { DWORD dwLen = GetLogicalDriveStringsW(0, NULL); if (!dwLen) return NULL; LPWSTR lpDriveStrings = (LPWSTR)std::malloc(dwLen * sizeof(WCHAR)); if (!lpDriveStrings) return NULL; DWORD dwRet = GetLogicalDriveStringsW(dwLen, lpDriveStrings); if (!dwRet) return NULL; LPWSTR pszDrive = lpDriveStrings; while (*pszDrive) { size_t len = wcslen(pszDrive); if (len && pszDrive[len - 1] == '\\') pszDrive[len - 1] = 0; WCHAR ntPath[4096]; dwRet = QueryDosDeviceW(pszDrive, ntPath, 4096); if (dwRet) { size_t ntLen = wcslen(ntPath); if (_wcsnicmp(pszFilename, ntPath, ntLen) == 0 && pszFilename[ntLen] == '\\') { size_t fnLen = wcslen(pszFilename); size_t driveLen = wcslen(pszDrive); size_t pathLen = fnLen - ntLen; size_t newLen = driveLen + pathLen + 1; LPWSTR pszWin32Name = (LPWSTR)std::malloc(newLen * sizeof(WCHAR)); if (!pszWin32Name) { std::free(lpDriveStrings); return NULL; } LPWSTR ptr = pszWin32Name; memcpy(ptr, pszDrive, driveLen * sizeof(WCHAR)); ptr += driveLen; memcpy(ptr, pszFilename + ntLen, pathLen * sizeof(WCHAR)); ptr += pathLen; *ptr = 0; std::free(lpDriveStrings); return pszWin32Name; } } pszDrive += len + 1; } std::free(lpDriveStrings); return _wcsdup(pszFilename); } #endif } // namespace SWIFT_RUNTIME_EXPORT char * swift_copyAuxiliaryExecutablePath(const char *name) { const char *rootPath = swift_getRootPath(); if (!rootPath) return nullptr; const char *platformName = SWIFT_LIB_SUBDIR; const char *archName = SWIFT_ARCH; // /libexec/swift/ if (char *result = _swift_tryAuxExePath(name, rootPath, "libexec", "swift", platformName, nullptr)) { return result; } // /libexec/swift// if (char *result = _swift_tryAuxExePath(name, rootPath, "libexec", "swift", platformName, archName, nullptr)) { return result; } // /libexec/swift if (char *result = _swift_tryAuxExePath(name, rootPath, "libexec", "swift", nullptr)) { return result; } // /libexec/swift/ if (char *result = _swift_tryAuxExePath(name, rootPath, "libexec", "swift", archName, nullptr)) { return result; } // /bin if (char *result = _swift_tryAuxExePath(name, rootPath, "bin", nullptr)) { return result; } // /bin/ if (char *result = _swift_tryAuxExePath(name, rootPath, "bin", archName, nullptr)) { return result; } // Otherwise, look in the root itself return _swift_tryAuxExePath(name, rootPath, nullptr); } namespace { char * _swift_getAuxExePathIn(const char *path, const char *name) { #ifdef _WIN32 size_t nameLen = std::strlen(name); char *nameWithSuffix = nullptr; if (nameLen > 4 && strcmp(name + nameLen - 4, ".exe") != 0) { nameWithSuffix = (char *)std::malloc(nameLen + 4 + 1); std::memcpy(nameWithSuffix, name, nameLen); std::memcpy(nameWithSuffix + nameLen, ".exe", 4 + 1); name = nameWithSuffix; } #endif char *fullPath = _swift_joinPaths(path, name, nullptr); #ifdef _WIN32 if (nameWithSuffix) std::free(nameWithSuffix); #endif return fullPath; } char * _swift_tryAuxExePath(const char *name, const char *path, ...) { va_list val; char *fullPath; va_start(val, path); fullPath = _swift_joinPathsV(path, val); va_end(val); if (_swift_exists(fullPath)) { char *result = _swift_getAuxExePathIn(fullPath, name); if (_swift_exists(result)) { std::free(fullPath); return result; } std::free(result); } std::free(fullPath); return nullptr; } #if !defined(_WIN32) || defined(__CYGWIN__) void _swift_initRuntimePath(void *) { #if APPLE_OS_SYSTEM const char *path = dyld_image_path_containing_address((const void *)_swift_initRuntimePath); // No need to ::strdup() this, as the return value is guaranteed to remain // valid as long as the library is loaded. runtimePath = path; #elif SWIFT_STDLIB_HAS_DLADDR Dl_info dli; int ret = ::dladdr((void *)_swift_initRuntimePath, &dli); if (!ret) { #ifdef __linux__ // If we don't find anything, try reading /proc/self/exe as a fallback; // this is needed with Musl when statically linking because in that case // dladdr() does nothing. char pathBuf[4096]; ssize_t len = readlink("/proc/self/exe", pathBuf, sizeof(pathBuf)); if (len > 0 && len < sizeof(pathBuf)) { runtimePath = ::strdup(pathBuf); return; } #endif swift::fatalError(/* flags = */ 0, "Unable to obtain Swift runtime path\n"); } runtimePath = ::strdup(dli.dli_fname); #else runtimePath = nullptr; #endif } #else void _swift_initRuntimePath(void *) { const DWORD dwBufSize = 4096; LPWSTR lpFilename = (LPWSTR)std::malloc(dwBufSize * sizeof(WCHAR)); // Again, we can't use GetFinalPathNameByHandle for the reasons given // above. DWORD dwRet = GetMappedFileNameW(GetCurrentProcess(), (void *)_swift_initRuntimePath, lpFilename, dwBufSize); if (!dwRet) { swift::fatalError(/* flags = */ 0, "Unable to obtain Swift runtime path\n"); } // GetMappedFileNameW() returns an NT-style path, not a Win32 path; that is, // it starts with \Device\DeviceName rather than a drive letter. LPWSTR lpWin32Filename = _swift_win32NameFromNTName(lpFilename); if (!lpWin32Filename) { swift::fatalError(/* flags = */ 0, "Unable to obtain Win32 path for Swift runtime\n"); } std::free(lpFilename); runtimePath = _swift_win32_copyUTF8FromWide(lpWin32Filename); if (!runtimePath) { swift::fatalError(/* flags = */ 0, "Unable to convert Swift runtime path to UTF-8: %lx, %d\n", ::GetLastError(), errno); } std::free(lpWin32Filename); } #endif /// Return true if a file exists at path. /// /// On Windows, path will be in UTF-8 so can't be passed to _stat() or any /// of the ANSI functions. /// /// @param path The path to check /// /// @result true iff there is a file at @a path bool _swift_exists(const char *path) { #if !defined(_WIN32) # if !HAS_STAT return false; # else struct stat st; return stat(path, &st) == 0; # endif #else wchar_t *wszPath = _swift_win32_copyWideFromUTF8(path); bool result = GetFileAttributesW(wszPath) != INVALID_FILE_ATTRIBUTES; free(wszPath); return result; #endif // defined(_WIN32) } } #endif // SWIFT_STDLIB_HAS_FILESYSTEM