Index: llvm/include/llvm/Support/FileSystem.h =================================================================== --- llvm/include/llvm/Support/FileSystem.h +++ llvm/include/llvm/Support/FileSystem.h @@ -464,6 +464,32 @@ return !equivalent(A, B, result) && result; } +/// @brief Is the file mounted on a local filesystem? +/// +/// @param path Input path. +/// @param result Set to true if \a path is on fixed media such as a hard disk, +/// false if it is not. +/// @returns errc::success if result has been successfully set, otherwise a +/// platform specific error_code. +std::error_code is_local(const Twine &path, bool &result); + +/// @brief Version of is_local accepting an open file descriptor. +std::error_code is_local(int FD, bool &result); + +/// @brief Simpler version of is_local for clients that don't need to +/// differentiate between an error and false. +inline bool is_local(const Twine &Path) { + bool Result; + return !is_local(Path, Result) && Result; +} + +/// @brief Simpler version of is_local accepting an open file descriptor for +/// clients that don't need to differentiate between an error and false. +inline bool is_local(int FD) { + bool Result; + return !is_local(FD, Result) && Result; +} + /// @brief Does status represent a directory? /// /// @param status A file_status previously returned from status. Index: llvm/lib/Support/MemoryBuffer.cpp =================================================================== --- llvm/lib/Support/MemoryBuffer.cpp +++ llvm/lib/Support/MemoryBuffer.cpp @@ -297,6 +297,16 @@ if (MapSize < 4 * 4096 || MapSize < (unsigned)PageSize) return false; + // Don't use mmap on files which are not on locally mounted file systems, as + // this can lead to instability if the filesystem is unmounted or a network + // connection is interrupted. If we read the entire contents from disk, we + // could still fail if the NFS goes down during the short window where the + // file contents are being read, but mmaps can be left open for very long + // periods of time, significantly increasing the likelihood of such an error + // occuring in practice. + if (!sys::fs::is_local(FD)) + return false; + if (!RequiresNullTerminator) return true; Index: llvm/lib/Support/Unix/Path.inc =================================================================== --- llvm/lib/Support/Unix/Path.inc +++ llvm/lib/Support/Unix/Path.inc @@ -65,23 +65,31 @@ #endif #include -#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__ANDROID__) +#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__FreeBSD__) && \ + !defined(__ANDROID__) && !defined(__linux__) #include #define STATVFS statvfs +#define FSTATVFS fstatvfs #define STATVFS_F_FRSIZE(vfs) vfs.f_frsize #else -#ifdef __OpenBSD__ +#if defined(__OpenBSD__) || defined(__FreeBSD__) #include #include -#elif defined(__ANDROID__) +#elif defined(__ANDROID__) || defined(__linux__) #include #else #include #endif #define STATVFS statfs +#define FSTATVFS fstatfs #define STATVFS_F_FRSIZE(vfs) static_cast(vfs.f_bsize) #endif +#if defined(__NetBSD__) +#define STATVFS_F_FLAG(vfs) (vfs).f_flag +#else +#define STATVFS_F_FLAG(vfs) (vfs).f_flags +#endif using namespace llvm; @@ -335,6 +343,40 @@ return std::error_code(); } +static bool is_local_impl(struct STATVFS &Vfs) { +#if defined(__linux__) + constexpr uint32_t CIFS_MAGIC_NUMBER = 0xFF534D42; + switch ((uint32_t)Vfs.f_type) { + case NFS_SUPER_MAGIC: + case SMB_SUPER_MAGIC: + case CIFS_MAGIC_NUMBER: + return false; + default: + return true; + } +#else + return !!(STATVFS_F_FLAG(Vfs) & MNT_LOCAL); +#endif +} + +std::error_code is_local(const Twine &Path, bool &Result) { + struct STATVFS Vfs; + if (::STATVFS(Path.str().c_str(), &Vfs)) + return std::error_code(errno, std::generic_category()); + + Result = is_local_impl(Vfs); + return std::error_code(); +} + +std::error_code is_local(int FD, bool &Result) { + struct STATVFS Vfs; + if (::FSTATVFS(FD, &Vfs)) + return std::error_code(errno, std::generic_category()); + + Result = is_local_impl(Vfs); + return std::error_code(); +} + std::error_code rename(const Twine &from, const Twine &to) { // Get arguments. SmallString<128> from_storage; @@ -491,6 +533,23 @@ int flags = (Mode == readwrite) ? MAP_SHARED : MAP_PRIVATE; int prot = (Mode == readonly) ? PROT_READ : (PROT_READ | PROT_WRITE); +#if defined(__APPLE__) +//---------------------------------------------------------------------- +// Newer versions of MacOSX have a flag that will allow us to read from +// binaries whose code signature is invalid without crashing by using +// the MAP_RESILIENT_CODESIGN flag. Also if a file from removable media +// is mapped we can avoid crashing and return zeroes to any pages we try +// to read if the media becomes unavailable by using the +// MAP_RESILIENT_MEDIA flag. +//---------------------------------------------------------------------- +#if defined(MAP_RESILIENT_CODESIGN) + flags |= MAP_RESILIENT_CODESIGN; +#endif +#if defined(MAP_RESILIENT_MEDIA) + flags |= MAP_RESILIENT_MEDIA; +#endif +#endif // #if defined (__APPLE__) + Mapping = ::mmap(nullptr, Size, prot, flags, FD, Offset); if (Mapping == MAP_FAILED) return std::error_code(errno, std::generic_category()); Index: llvm/lib/Support/Windows/Path.inc =================================================================== --- llvm/lib/Support/Windows/Path.inc +++ llvm/lib/Support/Windows/Path.inc @@ -277,6 +277,80 @@ return std::error_code(); } +static std::error_code is_local_internal(SmallVectorImpl &Path, + bool &Result) { + SmallVector VolumePath; + size_t Len = 128; + while (true) { + VolumePath.resize(Len); + BOOL Success = + ::GetVolumePathNameW(Path.data(), VolumePath.data(), VolumePath.size()); + + if (Success) + break; + + DWORD Err = ::GetLastError(); + if (Err != ERROR_INSUFFICIENT_BUFFER) + return mapWindowsError(Err); + + Len *= 2; + } + // If the output buffer has exactly enough space for the path name, but not + // the null terminator, it will leave the output unterminated. Push a null + // terminator onto the end to ensure that this never happens. + VolumePath.push_back(L'\0'); + VolumePath.set_size(wcslen(VolumePath.data())); + const wchar_t *P = VolumePath.data(); + + UINT Type = ::GetDriveTypeW(P); + switch (Type) { + case DRIVE_FIXED: + Result = true; + return std::error_code(); + case DRIVE_REMOTE: + case DRIVE_CDROM: + case DRIVE_RAMDISK: + case DRIVE_REMOVABLE: + Result = false; + return std::error_code(); + default: + return make_error_code(errc::no_such_file_or_directory); + } + llvm_unreachable("Unreachable!"); +} + +std::error_code is_local(const Twine &path, bool &result) { + if (!llvm::sys::fs::exists(path) || !llvm::sys::path::has_root_path(path)) + return make_error_code(errc::no_such_file_or_directory); + + SmallString<128> Storage; + StringRef P = path.toStringRef(Storage); + + // Convert to utf-16. + SmallVector WidePath; + if (std::error_code ec = widenPath(P, WidePath)) + return ec; + return is_local_internal(WidePath, result); +} + +std::error_code is_local(int FD, bool &Result) { + SmallVector FinalPath; + HANDLE Handle = reinterpret_cast(_get_osfhandle(FD)); + + size_t Len = 128; + do { + FinalPath.reserve(Len); + Len = ::GetFinalPathNameByHandleW(Handle, FinalPath.data(), + FinalPath.capacity() - 1, VOLUME_NAME_NT); + if (Len == 0) + return mapWindowsError(::GetLastError()); + } while (Len > FinalPath.capacity()); + + FinalPath.set_size(Len); + + return is_local_internal(FinalPath, Result); +} + std::error_code rename(const Twine &from, const Twine &to) { // Convert to utf-16. SmallVector wide_from; Index: llvm/unittests/Support/Path.cpp =================================================================== --- llvm/unittests/Support/Path.cpp +++ llvm/unittests/Support/Path.cpp @@ -1136,6 +1136,28 @@ ::close(FileDescriptor); } +TEST_F(FileSystemTest, is_local) { + SmallString<128> CurrentPath; + ASSERT_NO_ERROR(fs::current_path(CurrentPath)); + + bool Result; + ASSERT_NO_ERROR(fs::is_local(CurrentPath, Result)); + EXPECT_TRUE(Result); + EXPECT_TRUE(fs::is_local(CurrentPath)); + + int FD; + SmallString<64> TempPath; + ASSERT_NO_ERROR(fs::createTemporaryFile("prefix", "temp", FD, TempPath)); + FileRemover Cleanup(TempPath); + + // Make sure it exists. + ASSERT_TRUE(sys::fs::exists(Twine(TempPath))); + + ASSERT_NO_ERROR(fs::is_local(FD, Result)); + EXPECT_TRUE(Result); + EXPECT_TRUE(fs::is_local(FD)); +} + TEST_F(FileSystemTest, set_current_path) { SmallString<128> path;