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/include/llvm/Support/MemoryBuffer.h =================================================================== --- llvm/include/llvm/Support/MemoryBuffer.h +++ llvm/include/llvm/Support/MemoryBuffer.h @@ -69,12 +69,12 @@ /// means that the client knows that the file exists and that it has the /// specified size. /// - /// \param IsVolatileSize Set to true to indicate that the file size may be - /// changing, e.g. when libclang tries to parse while the user is - /// editing/updating the file. + /// \param IsVolatile Set to true to indicate that the contents of the file + /// can change outside the user's control, e.g. when libclang tries to parse + /// while the user is editing/updating the file or if the file is on an NFS. static ErrorOr> getFile(const Twine &Filename, int64_t FileSize = -1, - bool RequiresNullTerminator = true, bool IsVolatileSize = false); + bool RequiresNullTerminator = true, bool IsVolatile = false); /// Read all of the specified file into a MemoryBuffer as a stream /// (i.e. until EOF reached). This is useful for special files that @@ -87,17 +87,17 @@ /// Since this is in the middle of a file, the buffer is not null terminated. static ErrorOr> getOpenFileSlice(int FD, const Twine &Filename, uint64_t MapSize, - int64_t Offset); + int64_t Offset, bool IsVolatile = false); /// Given an already-open file descriptor, read the file and return a /// MemoryBuffer. /// - /// \param IsVolatileSize Set to true to indicate that the file size may be - /// changing, e.g. when libclang tries to parse while the user is - /// editing/updating the file. + /// \param IsVolatile Set to true to indicate that the contents of the file + /// can change outside the user's control, e.g. when libclang tries to parse + /// while the user is editing/updating the file or if the file is on an NFS. static ErrorOr> getOpenFile(int FD, const Twine &Filename, uint64_t FileSize, - bool RequiresNullTerminator = true, bool IsVolatileSize = false); + bool RequiresNullTerminator = true, bool IsVolatile = false); /// Open the specified memory range as a MemoryBuffer. Note that InputData /// must be null terminated if RequiresNullTerminator is true. @@ -136,7 +136,7 @@ /// Map a subrange of the specified file as a MemoryBuffer. static ErrorOr> - getFileSlice(const Twine &Filename, uint64_t MapSize, uint64_t Offset); + getFileSlice(const Twine &Filename, uint64_t MapSize, uint64_t Offset, bool IsVolatile = false); //===--------------------------------------------------------------------===// // Provided for performance analysis. Index: llvm/lib/Support/MemoryBuffer.cpp =================================================================== --- llvm/lib/Support/MemoryBuffer.cpp +++ llvm/lib/Support/MemoryBuffer.cpp @@ -103,7 +103,7 @@ static ErrorOr> getFileAux(const Twine &Filename, int64_t FileSize, uint64_t MapSize, - uint64_t Offset, bool RequiresNullTerminator, bool IsVolatileSize); + uint64_t Offset, bool RequiresNullTerminator, bool IsVolatile); std::unique_ptr MemoryBuffer::getMemBuffer(StringRef InputData, StringRef BufferName, @@ -178,8 +178,8 @@ ErrorOr> MemoryBuffer::getFileSlice(const Twine &FilePath, uint64_t MapSize, - uint64_t Offset) { - return getFileAux(FilePath, -1, MapSize, Offset, false, false); + uint64_t Offset, bool IsVolatile) { + return getFileAux(FilePath, -1, MapSize, Offset, false, IsVolatile); } @@ -254,19 +254,19 @@ ErrorOr> MemoryBuffer::getFile(const Twine &Filename, int64_t FileSize, - bool RequiresNullTerminator, bool IsVolatileSize) { + bool RequiresNullTerminator, bool IsVolatile) { return getFileAux(Filename, FileSize, FileSize, 0, - RequiresNullTerminator, IsVolatileSize); + RequiresNullTerminator, IsVolatile); } static ErrorOr> getOpenFileImpl(int FD, const Twine &Filename, uint64_t FileSize, uint64_t MapSize, int64_t Offset, bool RequiresNullTerminator, - bool IsVolatileSize); + bool IsVolatile); static ErrorOr> getFileAux(const Twine &Filename, int64_t FileSize, uint64_t MapSize, - uint64_t Offset, bool RequiresNullTerminator, bool IsVolatileSize) { + uint64_t Offset, bool RequiresNullTerminator, bool IsVolatile) { int FD; std::error_code EC = sys::fs::openFileForRead(Filename, FD); if (EC) @@ -274,7 +274,7 @@ ErrorOr> Ret = getOpenFileImpl(FD, Filename, FileSize, MapSize, Offset, - RequiresNullTerminator, IsVolatileSize); + RequiresNullTerminator, IsVolatile); close(FD); return Ret; } @@ -285,11 +285,11 @@ off_t Offset, bool RequiresNullTerminator, int PageSize, - bool IsVolatileSize) { + bool IsVolatile) { // mmap may leave the buffer without null terminator if the file size changed // by the time the last page is mapped in, so avoid it if the file size is // likely to change. - if (IsVolatileSize) + if (IsVolatile) return false; // We don't use mmap for small files because this can severely fragment our @@ -300,7 +300,6 @@ if (!RequiresNullTerminator) return true; - // If we don't know the file size, use fstat to find out. fstat on an open // file descriptor is cheaper than stat on a random path. // FIXME: this chunk of code is duplicated, but it avoids a fstat when @@ -338,7 +337,7 @@ static ErrorOr> getOpenFileImpl(int FD, const Twine &Filename, uint64_t FileSize, uint64_t MapSize, int64_t Offset, bool RequiresNullTerminator, - bool IsVolatileSize) { + bool IsVolatile) { static int PageSize = sys::Process::getPageSize(); // Default is to map the full file. @@ -365,7 +364,7 @@ } if (shouldUseMmap(FD, FileSize, MapSize, Offset, RequiresNullTerminator, - PageSize, IsVolatileSize)) { + PageSize, IsVolatile)) { std::error_code EC; std::unique_ptr Result( new (NamedBufferAlloc(Filename)) @@ -415,17 +414,16 @@ ErrorOr> MemoryBuffer::getOpenFile(int FD, const Twine &Filename, uint64_t FileSize, - bool RequiresNullTerminator, bool IsVolatileSize) { + bool RequiresNullTerminator, bool IsVolatile) { return getOpenFileImpl(FD, Filename, FileSize, FileSize, 0, - RequiresNullTerminator, IsVolatileSize); + RequiresNullTerminator, IsVolatile); } ErrorOr> MemoryBuffer::getOpenFileSlice(int FD, const Twine &Filename, uint64_t MapSize, - int64_t Offset) { + int64_t Offset, bool IsVolatile) { assert(MapSize != uint64_t(-1)); - return getOpenFileImpl(FD, Filename, -1, MapSize, Offset, false, - /*IsVolatileSize*/ false); + return getOpenFileImpl(FD, Filename, -1, MapSize, Offset, false, IsVolatile); } ErrorOr> MemoryBuffer::getSTDIN() { 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;