llvm.org GIT mirror llvm / 8224610
Add a flag to FileOutputBuffer that allows modification. FileOutputBuffer creates a temp file and on commit atomically renames the temp file to the destination file. Sometimes we want to modify an existing file in place, but still have the atomicity guarantee. To do this we can initialize the contents of the temp file from the destination file (if it exists), that way the resulting FileOutputBuffer can have only selective bytes modified. Committing will then atomically replace the destination file as desired. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@335902 91177308-0d34-0410-b5e6-96231b3b80d8 Zachary Turner 1 year, 3 months ago
5 changed file(s) with 127 addition(s) and 25 deletion(s). Raw diff Collapse all Expand all
2929 /// not committed, the file will be deleted in the FileOutputBuffer destructor.
3030 class FileOutputBuffer {
3131 public:
32 enum {
33 F_executable = 1 /// set the 'x' bit on the resulting file
32 enum {
33 /// set the 'x' bit on the resulting file
34 F_executable = 1,
35
36 /// the contents of the new file are initialized from the file that exists
37 /// at the location (if present). This allows in-place modification of an
38 /// existing file.
39 F_modify = 2
3440 };
3541
3642 /// Factory method to create an OutputBuffer object which manages a read/write
3743 /// buffer of the specified size. When committed, the buffer will be written
3844 /// to the file at the specified path.
45 ///
46 /// When F_modify is specified and \p FilePath refers to an existing on-disk
47 /// file \p Size may be set to -1, in which case the entire file is used.
48 /// Otherwise, the file shrinks or grows as necessary based on the value of
49 /// \p Size. It is an error to specify F_modify and Size=-1 if \p FilePath
50 /// does not exist.
3951 static Expected>
4052 create(StringRef FilePath, size_t Size, unsigned Flags = 0);
4153
393393 /// @param To The path to copy to. This is created.
394394 std::error_code copy_file(const Twine &From, const Twine &To);
395395
396 /// Copy the contents of \a From to \a To.
397 ///
398 /// @param From The path to copy from.
399 /// @param To The open file descriptor of the destinatino file.
400 std::error_code copy_file(const Twine &From, int ToFD);
401
396402 /// Resize path to size. File is resized as if by POSIX truncate().
397403 ///
398404 /// @param FD Input file descriptor.
109109 }
110110
111111 static Expected>
112 createOnDiskBuffer(StringRef Path, size_t Size, unsigned Mode) {
112 createOnDiskBuffer(StringRef Path, size_t Size, bool InitExisting,
113 unsigned Mode) {
113114 Expected FileOrErr =
114115 fs::TempFile::create(Path + ".tmp%%%%%%%", Mode);
115116 if (!FileOrErr)
116117 return FileOrErr.takeError();
117118 fs::TempFile File = std::move(*FileOrErr);
118119
120 if (InitExisting) {
121 if (auto EC = sys::fs::copy_file(Path, File.FD))
122 return errorCodeToError(EC);
123 } else {
119124 #ifndef _WIN32
120 // On Windows, CreateFileMapping (the mmap function on Windows)
121 // automatically extends the underlying file. We don't need to
122 // extend the file beforehand. _chsize (ftruncate on Windows) is
123 // pretty slow just like it writes specified amount of bytes,
124 // so we should avoid calling that function.
125 if (auto EC = fs::resize_file(File.FD, Size)) {
126 consumeError(File.discard());
127 return errorCodeToError(EC);
125 // On Windows, CreateFileMapping (the mmap function on Windows)
126 // automatically extends the underlying file. We don't need to
127 // extend the file beforehand. _chsize (ftruncate on Windows) is
128 // pretty slow just like it writes specified amount of bytes,
129 // so we should avoid calling that function.
130 if (auto EC = fs::resize_file(File.FD, Size)) {
131 consumeError(File.discard());
132 return errorCodeToError(EC);
133 }
134 #endif
128135 }
129 #endif
130136
131137 // Mmap it.
132138 std::error_code EC;
150156 fs::file_status Stat;
151157 fs::status(Path, Stat);
152158
159 if ((Flags & F_modify) && Size == size_t(-1)) {
160 if (Stat.type() == fs::file_type::regular_file)
161 Size = Stat.getSize();
162 else if (Stat.type() == fs::file_type::file_not_found)
163 return errorCodeToError(errc::no_such_file_or_directory);
164 else
165 return errorCodeToError(errc::invalid_argument);
166 }
167
153168 // Usually, we want to create OnDiskBuffer to create a temporary file in
154169 // the same directory as the destination file and atomically replaces it
155170 // by rename(2).
164179 case fs::file_type::regular_file:
165180 case fs::file_type::file_not_found:
166181 case fs::file_type::status_error:
167 return createOnDiskBuffer(Path, Size, Mode);
182 return createOnDiskBuffer(Path, Size, !!(Flags & F_modify), Mode);
168183 default:
169184 return createInMemoryBuffer(Path, Size, Mode);
170185 }
926926 return create_directory(P, IgnoreExisting, Perms);
927927 }
928928
929 std::error_code copy_file(const Twine &From, const Twine &To) {
930 int ReadFD, WriteFD;
931 if (std::error_code EC = openFileForRead(From, ReadFD, OF_None))
932 return EC;
933 if (std::error_code EC =
934 openFileForWrite(To, WriteFD, CD_CreateAlways, OF_None)) {
935 close(ReadFD);
936 return EC;
937 }
938
929 static std::error_code copy_file_internal(int ReadFD, int WriteFD) {
939930 const size_t BufSize = 4096;
940931 char *Buf = new char[BufSize];
941932 int BytesRead = 0, BytesWritten = 0;
952943 if (BytesWritten < 0)
953944 break;
954945 }
955 close(ReadFD);
956 close(WriteFD);
957946 delete[] Buf;
958947
959948 if (BytesRead < 0 || BytesWritten < 0)
960949 return std::error_code(errno, std::generic_category());
961950 return std::error_code();
951 }
952
953 std::error_code copy_file(const Twine &From, const Twine &To) {
954 int ReadFD, WriteFD;
955 if (std::error_code EC = openFileForRead(From, ReadFD, OF_None))
956 return EC;
957 if (std::error_code EC =
958 openFileForWrite(To, WriteFD, CD_CreateAlways, OF_None)) {
959 close(ReadFD);
960 return EC;
961 }
962
963 std::error_code EC = copy_file_internal(ReadFD, WriteFD);
964
965 close(ReadFD);
966 close(WriteFD);
967
968 return EC;
969 }
970
971 std::error_code copy_file(const Twine &From, int ToFD) {
972 int ReadFD;
973 if (std::error_code EC = openFileForRead(From, ReadFD, OF_None))
974 return EC;
975
976 std::error_code EC = copy_file_internal(ReadFD, ToFD);
977
978 close(ReadFD);
979
980 return EC;
962981 }
963982
964983 ErrorOr md5_contents(int FD) {
1010 #include "llvm/Support/Errc.h"
1111 #include "llvm/Support/ErrorHandling.h"
1212 #include "llvm/Support/FileSystem.h"
13 #include "llvm/Support/MemoryBuffer.h"
1314 #include "llvm/Support/Path.h"
1415 #include "llvm/Support/raw_ostream.h"
1516 #include "gtest/gtest.h"
120121 // Clean up.
121122 ASSERT_NO_ERROR(fs::remove(TestDirectory.str()));
122123 }
124
125 TEST(FileOutputBuffer, TestModify) {
126 // Create unique temporary directory for these tests
127 SmallString<128> TestDirectory;
128 {
129 ASSERT_NO_ERROR(
130 fs::createUniqueDirectory("FileOutputBuffer-modify", TestDirectory));
131 }
132
133 SmallString<128> File1(TestDirectory);
134 File1.append("/file");
135 // First write some data.
136 {
137 Expected> BufferOrErr =
138 FileOutputBuffer::create(File1, 10);
139 ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
140 std::unique_ptr &Buffer = *BufferOrErr;
141 memcpy(Buffer->getBufferStart(), "AAAAAAAAAA", 10);
142 ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
143 }
144
145 // Then re-open the file for modify and change only some bytes.
146 {
147 Expected> BufferOrErr =
148 FileOutputBuffer::create(File1, size_t(-1), FileOutputBuffer::F_modify);
149 ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
150 std::unique_ptr &Buffer = *BufferOrErr;
151 ASSERT_EQ(10, Buffer->getBufferSize());
152 uint8_t *Data = Buffer->getBufferStart();
153 Data[0] = 'X';
154 Data[9] = 'X';
155 ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
156 }
157
158 // Finally, re-open the file for read and verify that it has the modified
159 // contents.
160 {
161 ErrorOr> BufferOrErr = MemoryBuffer::getFile(File1);
162 ASSERT_NO_ERROR(BufferOrErr.getError());
163 std::unique_ptr Buffer = std::move(*BufferOrErr);
164 ASSERT_EQ(10, Buffer->getBufferSize());
165 EXPECT_EQ(StringRef("XAAAAAAAAX"), Buffer->getBuffer());
166 }
167
168 // Clean up.
169 ASSERT_NO_ERROR(fs::remove(File1));
170 ASSERT_NO_ERROR(fs::remove(TestDirectory));
171 }
172
123173 } // anonymous namespace