Index: llvm/trunk/include/llvm/Support/FileSystem.h =================================================================== --- llvm/trunk/include/llvm/Support/FileSystem.h +++ llvm/trunk/include/llvm/Support/FileSystem.h @@ -678,15 +678,20 @@ /// with F_Excl. F_Append = 2, + /// F_NoTrunc - When opening a file, if it already exists don't truncate + /// the file contents. F_Append implies F_NoTrunc, but F_Append seeks to + /// the end of the file, which F_NoTrunc doesn't. + F_NoTrunc = 4, + /// The file should be opened in text mode on platforms that make this /// distinction. - F_Text = 4, + F_Text = 8, /// Open the file for read and write. - F_RW = 8, + F_RW = 16, /// Delete the file on close. Only makes a difference on windows. - F_Delete = 16 + F_Delete = 32 }; /// @brief Create a uniquely named file. Index: llvm/trunk/include/llvm/Support/MemoryBuffer.h =================================================================== --- llvm/trunk/include/llvm/Support/MemoryBuffer.h +++ llvm/trunk/include/llvm/Support/MemoryBuffer.h @@ -20,6 +20,7 @@ #include "llvm/ADT/Twine.h" #include "llvm/Support/CBindingWrapping.h" #include "llvm/Support/ErrorOr.h" +#include "llvm/Support/FileSystem.h" #include #include #include @@ -49,7 +50,8 @@ void init(const char *BufStart, const char *BufEnd, bool RequiresNullTerminator); - static constexpr bool Writable = false; + static constexpr sys::fs::mapped_file_region::mapmode Mapmode = + sys::fs::mapped_file_region::readonly; public: MemoryBuffer(const MemoryBuffer &) = delete; @@ -148,15 +150,16 @@ MemoryBufferRef getMemBufferRef() const; }; -/// This class is an extension of MemoryBuffer, which allows writing to the -/// underlying contents. It only supports creation methods that are guaranteed -/// to produce a writable buffer. For example, mapping a file read-only is not -/// supported. +/// This class is an extension of MemoryBuffer, which allows copy-on-write +/// access to the underlying contents. It only supports creation methods that +/// are guaranteed to produce a writable buffer. For example, mapping a file +/// read-only is not supported. class WritableMemoryBuffer : public MemoryBuffer { protected: WritableMemoryBuffer() = default; - static constexpr bool Writable = true; + static constexpr sys::fs::mapped_file_region::mapmode Mapmode = + sys::fs::mapped_file_region::priv; public: using MemoryBuffer::getBuffer; @@ -209,6 +212,54 @@ using MemoryBuffer::getSTDIN; }; +/// This class is an extension of MemoryBuffer, which allows write access to +/// the underlying contents and committing those changes to the original source. +/// It only supports creation methods that are guaranteed to produce a writable +/// buffer. For example, mapping a file read-only is not supported. +class WriteThroughMemoryBuffer : public MemoryBuffer { +protected: + WriteThroughMemoryBuffer() = default; + + static constexpr sys::fs::mapped_file_region::mapmode Mapmode = + sys::fs::mapped_file_region::readwrite; + +public: + using MemoryBuffer::getBuffer; + using MemoryBuffer::getBufferEnd; + using MemoryBuffer::getBufferStart; + + // const_cast is well-defined here, because the underlying buffer is + // guaranteed to have been initialized with a mutable buffer. + char *getBufferStart() { + return const_cast(MemoryBuffer::getBufferStart()); + } + char *getBufferEnd() { + return const_cast(MemoryBuffer::getBufferEnd()); + } + MutableArrayRef getBuffer() { + return {getBufferStart(), getBufferEnd()}; + } + + static ErrorOr> + getFile(const Twine &Filename, int64_t FileSize = -1); + + /// Map a subrange of the specified file as a ReadWriteMemoryBuffer. + static ErrorOr> + getFileSlice(const Twine &Filename, uint64_t MapSize, uint64_t Offset); + +private: + // Hide these base class factory function so one can't write + // WritableMemoryBuffer::getXXX() + // and be surprised that he got a read-only Buffer. + using MemoryBuffer::getFileAsStream; + using MemoryBuffer::getFileOrSTDIN; + using MemoryBuffer::getMemBuffer; + using MemoryBuffer::getMemBufferCopy; + using MemoryBuffer::getOpenFile; + using MemoryBuffer::getOpenFileSlice; + using MemoryBuffer::getSTDIN; +}; + class MemoryBufferRef { StringRef Buffer; StringRef Identifier; Index: llvm/trunk/lib/Support/MemoryBuffer.cpp =================================================================== --- llvm/trunk/lib/Support/MemoryBuffer.cpp +++ llvm/trunk/lib/Support/MemoryBuffer.cpp @@ -184,10 +184,8 @@ public: MemoryBufferMMapFile(bool RequiresNullTerminator, int FD, uint64_t Len, uint64_t Offset, std::error_code &EC) - : MFR(FD, - MB::Writable ? sys::fs::mapped_file_region::priv - : sys::fs::mapped_file_region::readonly, - getLegalMapSize(Len, Offset), getLegalMapOffset(Offset), EC) { + : MFR(FD, MB::Mapmode, getLegalMapSize(Len, Offset), + getLegalMapOffset(Offset), EC) { if (!EC) { const char *Start = getStart(Len, Offset); MemoryBuffer::init(Start, Start + Len, RequiresNullTerminator); @@ -361,6 +359,59 @@ return true; } +static ErrorOr> +getReadWriteFile(const Twine &Filename, int64_t FileSize, uint64_t MapSize, + uint64_t Offset) { + int FD; + std::error_code EC = sys::fs::openFileForWrite( + Filename, FD, sys::fs::F_RW | sys::fs::F_NoTrunc); + + if (EC) + return EC; + + // Default is to map the full file. + if (MapSize == uint64_t(-1)) { + // 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. + if (FileSize == uint64_t(-1)) { + sys::fs::file_status Status; + std::error_code EC = sys::fs::status(FD, Status); + if (EC) + return EC; + + // If this not a file or a block device (e.g. it's a named pipe + // or character device), we can't mmap it, so error out. + sys::fs::file_type Type = Status.type(); + if (Type != sys::fs::file_type::regular_file && + Type != sys::fs::file_type::block_file) + return make_error_code(errc::invalid_argument); + + FileSize = Status.getSize(); + } + MapSize = FileSize; + } + + std::unique_ptr Result( + new (NamedBufferAlloc(Filename)) + MemoryBufferMMapFile(false, FD, MapSize, + Offset, EC)); + if (EC) + return EC; + return std::move(Result); +} + +ErrorOr> +WriteThroughMemoryBuffer::getFile(const Twine &Filename, int64_t FileSize) { + return getReadWriteFile(Filename, FileSize, FileSize, 0); +} + +/// Map a subrange of the specified file as a WritableMemoryBuffer. +ErrorOr> +WriteThroughMemoryBuffer::getFileSlice(const Twine &Filename, uint64_t MapSize, + uint64_t Offset) { + return getReadWriteFile(Filename, -1, MapSize, Offset); +} + template static ErrorOr> getOpenFileImpl(int FD, const Twine &Filename, uint64_t FileSize, Index: llvm/trunk/lib/Support/Unix/Path.inc =================================================================== --- llvm/trunk/lib/Support/Unix/Path.inc +++ llvm/trunk/lib/Support/Unix/Path.inc @@ -792,7 +792,7 @@ if (Flags & F_Append) OpenFlags |= O_APPEND; - else + else if (!(Flags & F_NoTrunc)) OpenFlags |= O_TRUNC; if (Flags & F_Excl) Index: llvm/trunk/lib/Support/Windows/Path.inc =================================================================== --- llvm/trunk/lib/Support/Windows/Path.inc +++ llvm/trunk/lib/Support/Windows/Path.inc @@ -1101,7 +1101,7 @@ DWORD CreationDisposition; if (Flags & F_Excl) CreationDisposition = CREATE_NEW; - else if (Flags & F_Append) + else if ((Flags & F_Append) || (Flags & F_NoTrunc)) CreationDisposition = OPEN_ALWAYS; else CreationDisposition = CREATE_ALWAYS; Index: llvm/trunk/unittests/Support/MemoryBufferTest.cpp =================================================================== --- llvm/trunk/unittests/Support/MemoryBufferTest.cpp +++ llvm/trunk/unittests/Support/MemoryBufferTest.cpp @@ -260,4 +260,33 @@ for (size_t i = 0; i < MB.getBufferSize(); i += 0x10) EXPECT_EQ("0123456789abcdef", MB.getBuffer().substr(i, 0x10)) << "i: " << i; } + +TEST_F(MemoryBufferTest, writeThroughFile) { + // Create a file initialized with some data + int FD; + SmallString<64> TestPath; + sys::fs::createTemporaryFile("MemoryBufferTest_WriteThrough", "temp", FD, + TestPath); + FileRemover Cleanup(TestPath); + raw_fd_ostream OF(FD, true); + OF << "0123456789abcdef"; + OF.close(); + { + auto MBOrError = WriteThroughMemoryBuffer::getFile(TestPath); + ASSERT_FALSE(MBOrError.getError()); + // Write some data. It should be mapped readwrite, so that upon completion + // the original file contents are modified. + WriteThroughMemoryBuffer &MB = **MBOrError; + ASSERT_EQ(16, MB.getBufferSize()); + char *Start = MB.getBufferStart(); + ASSERT_EQ(MB.getBufferEnd(), MB.getBufferStart() + MB.getBufferSize()); + ::memset(Start, 'x', MB.getBufferSize()); + } + + auto MBOrError = MemoryBuffer::getFile(TestPath); + ASSERT_FALSE(MBOrError.getError()); + auto &MB = **MBOrError; + ASSERT_EQ(16, MB.getBufferSize()); + EXPECT_EQ("xxxxxxxxxxxxxxxx", MB.getBuffer()); +} }