Files
swift-mirror/stdlib/public/runtime/Paths.cpp

636 lines
16 KiB
C++

//===--- 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 <TargetConditionals.h>
#endif
#if !defined(_WIN32) || defined(__CYGWIN__)
#if __has_include(<sys/stat.h>)
# include <sys/stat.h>
# 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 <dlfcn.h>
#endif
#if __has_include(<mach-o/dyld_priv.h>)
#include <mach-o/dyld_priv.h>
#define APPLE_OS_SYSTEM 1
#else
#define APPLE_OS_SYSTEM 0
#endif
#else // defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <psapi.h>
#endif // defined(_WIN32)
#ifdef __linux__
// Needed for 'readlink'.
#include <unistd.h>
#endif
#include <cerrno>
#include <cstdlib>
#include <cstring>
#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 <platform> or <platform>/<arch> 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<char *>(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;
// <rootPath>/libexec/swift/<platformName>
if (char *result = _swift_tryAuxExePath(name,
rootPath,
"libexec", "swift",
platformName, nullptr)) {
return result;
}
// <rootPath>/libexec/swift/<platformName>/<arch>
if (char *result = _swift_tryAuxExePath(name,
rootPath,
"libexec", "swift",
platformName,
archName, nullptr)) {
return result;
}
// <rootPath>/libexec/swift
if (char *result = _swift_tryAuxExePath(name,
rootPath,
"libexec", "swift",
nullptr)) {
return result;
}
// <rootPath>/libexec/swift/<arch>
if (char *result = _swift_tryAuxExePath(name,
rootPath,
"libexec", "swift",
archName, nullptr)) {
return result;
}
// <rootPath>/bin
if (char *result = _swift_tryAuxExePath(name,
rootPath,
"bin", nullptr)) {
return result;
}
// <rootPath>/bin/<arch>
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)-1);
if (len > 0 ) {
pathBuf[len] = '\0';
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