1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/files/memory_mapped_file.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <utility>
11
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "testing/platform_test.h"
16
17 namespace base {
18
19 namespace {
20
21 // Create a temporary buffer and fill it with a watermark sequence.
CreateTestBuffer(size_t size,size_t offset)22 std::unique_ptr<uint8_t[]> CreateTestBuffer(size_t size, size_t offset) {
23 std::unique_ptr<uint8_t[]> buf(new uint8_t[size]);
24 for (size_t i = 0; i < size; ++i)
25 buf.get()[i] = static_cast<uint8_t>((offset + i) % 253);
26 return buf;
27 }
28
29 // Check that the watermark sequence is consistent with the |offset| provided.
CheckBufferContents(span<const uint8_t> bytes,size_t offset)30 bool CheckBufferContents(span<const uint8_t> bytes, size_t offset) {
31 std::unique_ptr<uint8_t[]> test_data(CreateTestBuffer(bytes.size(), offset));
32 return memcmp(test_data.get(), bytes.data(), bytes.size()) == 0;
33 }
34
35 class MemoryMappedFileTest : public PlatformTest {
36 protected:
SetUp()37 void SetUp() override {
38 PlatformTest::SetUp();
39 CreateTemporaryFile(&temp_file_path_);
40 }
41
TearDown()42 void TearDown() override { EXPECT_TRUE(DeleteFile(temp_file_path_)); }
43
CreateTemporaryTestFile(size_t size)44 void CreateTemporaryTestFile(size_t size) {
45 File file(temp_file_path_,
46 File::FLAG_CREATE_ALWAYS | File::FLAG_READ | File::FLAG_WRITE);
47 EXPECT_TRUE(file.IsValid());
48
49 std::unique_ptr<uint8_t[]> test_data(CreateTestBuffer(size, 0));
50 size_t bytes_written =
51 file.Write(0, reinterpret_cast<char*>(test_data.get()), size);
52 EXPECT_EQ(size, bytes_written);
53 file.Close();
54 }
55
temp_file_path() const56 const FilePath temp_file_path() const { return temp_file_path_; }
57
58 private:
59 FilePath temp_file_path_;
60 };
61
TEST_F(MemoryMappedFileTest,MapWholeFileByPath)62 TEST_F(MemoryMappedFileTest, MapWholeFileByPath) {
63 const size_t kFileSize = 68 * 1024;
64 CreateTemporaryTestFile(kFileSize);
65 MemoryMappedFile map;
66 ASSERT_TRUE(map.Initialize(temp_file_path()));
67 ASSERT_EQ(kFileSize, map.length());
68 ASSERT_TRUE(map.data() != nullptr);
69 EXPECT_TRUE(map.IsValid());
70 ASSERT_TRUE(CheckBufferContents(map.bytes(), 0));
71 }
72
TEST_F(MemoryMappedFileTest,MapWholeFileByFD)73 TEST_F(MemoryMappedFileTest, MapWholeFileByFD) {
74 const size_t kFileSize = 68 * 1024;
75 CreateTemporaryTestFile(kFileSize);
76 MemoryMappedFile map;
77 ASSERT_TRUE(map.Initialize(
78 File(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ)));
79 ASSERT_EQ(kFileSize, map.length());
80 ASSERT_TRUE(map.data() != nullptr);
81 EXPECT_TRUE(map.IsValid());
82 ASSERT_TRUE(CheckBufferContents(map.bytes(), 0));
83 }
84
TEST_F(MemoryMappedFileTest,MapSmallFile)85 TEST_F(MemoryMappedFileTest, MapSmallFile) {
86 const size_t kFileSize = 127;
87 CreateTemporaryTestFile(kFileSize);
88 MemoryMappedFile map;
89 ASSERT_TRUE(map.Initialize(temp_file_path()));
90 ASSERT_EQ(kFileSize, map.length());
91 ASSERT_TRUE(map.data() != nullptr);
92 EXPECT_TRUE(map.IsValid());
93 ASSERT_TRUE(CheckBufferContents(map.bytes(), 0));
94 }
95
TEST_F(MemoryMappedFileTest,MapWholeFileUsingRegion)96 TEST_F(MemoryMappedFileTest, MapWholeFileUsingRegion) {
97 const size_t kFileSize = 157 * 1024;
98 CreateTemporaryTestFile(kFileSize);
99 MemoryMappedFile map;
100
101 File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
102 ASSERT_TRUE(
103 map.Initialize(std::move(file), MemoryMappedFile::Region::kWholeFile));
104 ASSERT_EQ(kFileSize, map.length());
105 ASSERT_TRUE(map.data() != nullptr);
106 EXPECT_TRUE(map.IsValid());
107 ASSERT_TRUE(CheckBufferContents(map.bytes(), 0));
108 }
109
TEST_F(MemoryMappedFileTest,MapPartialRegionAtBeginning)110 TEST_F(MemoryMappedFileTest, MapPartialRegionAtBeginning) {
111 const size_t kFileSize = 157 * 1024;
112 const size_t kPartialSize = 4 * 1024 + 32;
113 CreateTemporaryTestFile(kFileSize);
114 MemoryMappedFile map;
115
116 File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
117 MemoryMappedFile::Region region = {0, kPartialSize};
118 ASSERT_TRUE(map.Initialize(std::move(file), region));
119 ASSERT_EQ(kPartialSize, map.length());
120 ASSERT_TRUE(map.data() != nullptr);
121 EXPECT_TRUE(map.IsValid());
122 ASSERT_TRUE(CheckBufferContents(map.bytes().first(kPartialSize), 0));
123 }
124
TEST_F(MemoryMappedFileTest,MapPartialRegionAtEnd)125 TEST_F(MemoryMappedFileTest, MapPartialRegionAtEnd) {
126 const size_t kFileSize = 157 * 1024;
127 const size_t kPartialSize = 5 * 1024 - 32;
128 const size_t kOffset = kFileSize - kPartialSize;
129 CreateTemporaryTestFile(kFileSize);
130 MemoryMappedFile map;
131
132 File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
133 MemoryMappedFile::Region region = {kOffset, kPartialSize};
134 ASSERT_TRUE(map.Initialize(std::move(file), region));
135 ASSERT_EQ(kPartialSize, map.length());
136 ASSERT_TRUE(map.data() != nullptr);
137 EXPECT_TRUE(map.IsValid());
138 ASSERT_TRUE(CheckBufferContents(map.bytes().first(kPartialSize), kOffset));
139 }
140
TEST_F(MemoryMappedFileTest,MapSmallPartialRegionInTheMiddle)141 TEST_F(MemoryMappedFileTest, MapSmallPartialRegionInTheMiddle) {
142 const size_t kFileSize = 157 * 1024;
143 const size_t kOffset = 1024 * 5 + 32;
144 const size_t kPartialSize = 8;
145
146 CreateTemporaryTestFile(kFileSize);
147 MemoryMappedFile map;
148
149 File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
150 MemoryMappedFile::Region region = {kOffset, kPartialSize};
151 ASSERT_TRUE(map.Initialize(std::move(file), region));
152 ASSERT_EQ(kPartialSize, map.length());
153 ASSERT_TRUE(map.data() != nullptr);
154 EXPECT_TRUE(map.IsValid());
155 ASSERT_TRUE(CheckBufferContents(map.bytes().first(kPartialSize), kOffset));
156 }
157
TEST_F(MemoryMappedFileTest,MapLargePartialRegionInTheMiddle)158 TEST_F(MemoryMappedFileTest, MapLargePartialRegionInTheMiddle) {
159 const size_t kFileSize = 157 * 1024;
160 const size_t kOffset = 1024 * 5 + 32;
161 const size_t kPartialSize = 16 * 1024 - 32;
162
163 CreateTemporaryTestFile(kFileSize);
164 MemoryMappedFile map;
165
166 File file(temp_file_path(), File::FLAG_OPEN | File::FLAG_READ);
167 MemoryMappedFile::Region region = {kOffset, kPartialSize};
168 ASSERT_TRUE(map.Initialize(std::move(file), region));
169 ASSERT_EQ(kPartialSize, map.length());
170 ASSERT_TRUE(map.data() != nullptr);
171 EXPECT_TRUE(map.IsValid());
172 ASSERT_TRUE(CheckBufferContents(map.bytes().first(kPartialSize), kOffset));
173 }
174
TEST_F(MemoryMappedFileTest,WriteableFile)175 TEST_F(MemoryMappedFileTest, WriteableFile) {
176 const size_t kFileSize = 127;
177 CreateTemporaryTestFile(kFileSize);
178
179 {
180 MemoryMappedFile map;
181 ASSERT_TRUE(map.Initialize(temp_file_path(), MemoryMappedFile::READ_WRITE));
182 ASSERT_EQ(kFileSize, map.length());
183 ASSERT_TRUE(map.data() != nullptr);
184 EXPECT_TRUE(map.IsValid());
185 ASSERT_TRUE(CheckBufferContents(map.bytes(), 0));
186
187 span<uint8_t> bytes = map.mutable_bytes();
188 bytes[0] = 'B';
189 bytes[1] = 'a';
190 bytes[2] = 'r';
191 bytes[kFileSize - 1] = '!';
192 EXPECT_FALSE(CheckBufferContents(map.bytes(), 0));
193 EXPECT_TRUE(
194 CheckBufferContents(map.bytes().first(kFileSize - 1).subspan(3), 3));
195 }
196
197 int64_t file_size;
198 ASSERT_TRUE(GetFileSize(temp_file_path(), &file_size));
199 EXPECT_EQ(static_cast<int64_t>(kFileSize), file_size);
200
201 std::string contents;
202 ASSERT_TRUE(ReadFileToString(temp_file_path(), &contents));
203 EXPECT_EQ("Bar", contents.substr(0, 3));
204 EXPECT_EQ("!", contents.substr(kFileSize - 1, 1));
205 }
206
TEST_F(MemoryMappedFileTest,CopyOnWrite)207 TEST_F(MemoryMappedFileTest, CopyOnWrite) {
208 const size_t kFileSize = 127;
209 CreateTemporaryTestFile(kFileSize);
210
211 {
212 MemoryMappedFile map;
213 ASSERT_TRUE(
214 map.Initialize(temp_file_path(), MemoryMappedFile::READ_WRITE_COPY));
215 ASSERT_EQ(kFileSize, map.length());
216 ASSERT_TRUE(map.data() != nullptr);
217 EXPECT_TRUE(map.IsValid());
218 ASSERT_TRUE(CheckBufferContents(map.bytes(), 0));
219
220 span<uint8_t> bytes = map.mutable_bytes();
221 bytes[0] = 'B';
222 bytes[1] = 'a';
223 bytes[2] = 'r';
224 bytes[kFileSize - 1] = '!';
225 EXPECT_FALSE(CheckBufferContents(map.bytes(), 0));
226 EXPECT_TRUE(
227 CheckBufferContents(map.bytes().first(kFileSize - 1).subspan(3), 3));
228 }
229
230 int64_t file_size;
231 ASSERT_TRUE(GetFileSize(temp_file_path(), &file_size));
232 EXPECT_EQ(static_cast<int64_t>(kFileSize), file_size);
233
234 // Although the buffer has been modified in memory, the file is unchanged.
235 std::string contents;
236 ASSERT_TRUE(ReadFileToString(temp_file_path(), &contents));
237 EXPECT_TRUE(CheckBufferContents(as_bytes(span(contents)), 0));
238 }
239
TEST_F(MemoryMappedFileTest,ExtendableFile)240 TEST_F(MemoryMappedFileTest, ExtendableFile) {
241 const size_t kFileSize = 127;
242 const size_t kFileExtend = 100;
243 CreateTemporaryTestFile(kFileSize);
244
245 {
246 File file(temp_file_path(),
247 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
248 MemoryMappedFile::Region region = {0, kFileSize + kFileExtend};
249 MemoryMappedFile map;
250 ASSERT_TRUE(map.Initialize(std::move(file), region,
251 MemoryMappedFile::READ_WRITE_EXTEND));
252 EXPECT_EQ(kFileSize + kFileExtend, map.length());
253 ASSERT_TRUE(map.data() != nullptr);
254 EXPECT_TRUE(map.IsValid());
255 ASSERT_TRUE(CheckBufferContents(map.bytes().first(kFileSize), 0));
256
257 span<uint8_t> bytes = map.mutable_bytes();
258 EXPECT_EQ(0, bytes[kFileSize + 0]);
259 EXPECT_EQ(0, bytes[kFileSize + 1]);
260 EXPECT_EQ(0, bytes[kFileSize + 2]);
261 bytes[kFileSize + 0] = 'B';
262 bytes[kFileSize + 1] = 'A';
263 bytes[kFileSize + 2] = 'Z';
264 EXPECT_TRUE(CheckBufferContents(map.bytes().first(kFileSize), 0));
265 }
266
267 int64_t file_size;
268 ASSERT_TRUE(GetFileSize(temp_file_path(), &file_size));
269 EXPECT_LE(static_cast<int64_t>(kFileSize + 3), file_size);
270 EXPECT_GE(static_cast<int64_t>(kFileSize + kFileExtend), file_size);
271
272 std::string contents;
273 ASSERT_TRUE(ReadFileToString(temp_file_path(), &contents));
274 EXPECT_EQ("BAZ", contents.substr(kFileSize, 3));
275 }
276
277 } // namespace
278
279 } // namespace base
280