Index: llvm/include/llvm/Support/FileSystem.h =================================================================== --- llvm/include/llvm/Support/FileSystem.h +++ llvm/include/llvm/Support/FileSystem.h @@ -383,13 +383,13 @@ /// @param To The path to copy to. This is created. std::error_code copy_file(const Twine &From, const Twine &To); -/// @brief Resize path to size. File is resized as if by POSIX truncate(). +/// @brief Preallocate disk blocks to a given file. /// /// @param FD Input file descriptor. /// @param Size Size to resize to. /// @returns errc::success if \a path has been resized to \a size, otherwise a /// platform-specific error_code. -std::error_code resize_file(int FD, uint64_t Size); +std::error_code allocate_file(int FD, uint64_t Size); /// @brief Compute an MD5 hash of a file's contents. /// Index: llvm/lib/Support/FileOutputBuffer.cpp =================================================================== --- llvm/lib/Support/FileOutputBuffer.cpp +++ llvm/lib/Support/FileOutputBuffer.cpp @@ -108,7 +108,7 @@ return llvm::make_unique(Path, MB, Mode); } -static Expected> +static Expected> createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) { // Create new file in same directory but with random name. SmallString<128> TempPath; @@ -119,13 +119,24 @@ sys::RemoveFileOnSignal(TempPath); #ifndef LLVM_ON_WIN32 - // On Windows, CreateFileMapping (the mmap function on Windows) - // automatically extends the underlying file. We don't need to - // extend the file beforehand. _chsize (ftruncate on Windows) is - // pretty slow just like it writes specified amount of bytes, - // so we should avoid calling that function. - if (auto EC = fs::resize_file(FD, Size)) + // A signal (usually a SIGBUS) is raised when a disk becomes full while + // writing to a sparse file using mmap. There's no portable nor reliable + // way to handle such error condition. In order to prevent that error, we + // extend a file and then preallocate disk blocks now. + // + // Not all operating systems nor filesystems support block preallocation. + // If it is not supported, we use an in-memory buffer instead so that the + // disk full error can be caught on commit(). + // + // On Windows, we don't need to do this because CreateFileMapping (the + // mmap function on Windows) automatically extends an undeflying file. + // Pre-extending a file is just a waste of time. + if (auto EC = fs::allocate_file(FD, Size)) { + fs::remove(TempPath); + if (EC == errc::function_not_supported) + return createInMemoryBuffer(Path, Size, Mode); return errorCodeToError(EC); + } #endif // Mmap it. Index: llvm/lib/Support/Unix/Path.inc =================================================================== --- llvm/lib/Support/Unix/Path.inc +++ llvm/lib/Support/Unix/Path.inc @@ -421,21 +421,31 @@ return std::error_code(); } -std::error_code resize_file(int FD, uint64_t Size) { -#if defined(HAVE_POSIX_FALLOCATE) - // If we have posix_fallocate use it. Unlike ftruncate it always allocates - // space, so we get an error if the disk is full. +// Preallocate disk blocks. +std::error_code allocate_file(int FD, uint64_t Size) { +#ifdef HAVE_FALLOCATE + if (fallocate(FD, 0, 0, Size) == -1) + return std::error_code(errno, std::generic_category()); + return std::error_code(); +#endif + +#ifdef HAVE_POSIX_FALLOCATE if (int Err = ::posix_fallocate(FD, 0, Size)) { if (Err != EINVAL && Err != EOPNOTSUPP) return std::error_code(Err, std::generic_category()); + return make_error_code(errc::function_not_supported); } + return std::error_code(); #endif - // Use ftruncate as a fallback. It may or may not allocate space. At least on - // OS X with HFS+ it does. - if (::ftruncate(FD, Size) == -1) - return std::error_code(errno, std::generic_category()); +#ifdef __APPLE__ + fstore_t Store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, Size}; + if (fcntl(fd, F_PREALLOCATE, &Store) == -1) + return std::error_code(errno, std::generic_category()); return std::error_code(); +#endif + + return make_error_code(errc::function_not_supported); } static int convertAccessMode(AccessMode Mode) { Index: llvm/lib/Support/Windows/Path.inc =================================================================== --- llvm/lib/Support/Windows/Path.inc +++ llvm/lib/Support/Windows/Path.inc @@ -500,7 +500,7 @@ return errc::permission_denied; } -std::error_code resize_file(int FD, uint64_t Size) { +std::error_code allocate_file(int FD, uint64_t Size) { #ifdef HAVE__CHSIZE_S errno_t error = ::_chsize_s(FD, Size); #else Index: llvm/unittests/Support/Path.cpp =================================================================== --- llvm/unittests/Support/Path.cpp +++ llvm/unittests/Support/Path.cpp @@ -966,14 +966,16 @@ } #endif -TEST_F(FileSystemTest, Resize) { +TEST_F(FileSystemTest, Allocate) { int FD; SmallString<64> TempPath; ASSERT_NO_ERROR(fs::createTemporaryFile("prefix", "temp", FD, TempPath)); - ASSERT_NO_ERROR(fs::resize_file(FD, 123)); - fs::file_status Status; - ASSERT_NO_ERROR(fs::status(FD, Status)); - ASSERT_EQ(Status.getSize(), 123U); + std::error_code EC = fs::allocate_file(FD, 123); + if (!EC) { + fs::file_status Status; + ASSERT_NO_ERROR(fs::status(FD, Status)); + ASSERT_EQ(Status.getSize(), 123U); + } ::close(FD); ASSERT_NO_ERROR(fs::remove(TempPath)); } @@ -999,7 +1001,10 @@ ASSERT_NO_ERROR( fs::createTemporaryFile("prefix", "temp", FileDescriptor, TempPath)); unsigned Size = 4096; - ASSERT_NO_ERROR(fs::resize_file(FileDescriptor, Size)); + if (fs::allocate_file(FileDescriptor, Size)) { + ASSERT_NO_ERROR(fs::remove(TempPath)); + return; + } // Map in temp file and add some content std::error_code EC;