1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless requied by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18 #define LOG_TAG "BowTest"
19
20 #include <fstream>
21 #include <string>
22
23 #include <assert.h>
24 #include <errno.h>
25 #include <inttypes.h>
26 #include <linux/fs.h>
27 #include <linux/loop.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33
34 #include <android-base/stringprintf.h>
35 #include <android-base/unique_fd.h>
36 #include <gtest/gtest.h>
37 #include <libdm/dm.h>
38 #include <utils/Log.h>
39
40 namespace android {
41
42 using base::unique_fd;
43 using namespace dm;
44
blockCheckpointsSupported()45 bool blockCheckpointsSupported() {
46 static bool supported = false;
47 static bool evaluated = false;
48
49 if (evaluated) return supported;
50
51 pid_t pid = fork();
52 EXPECT_NE(pid, -1);
53
54 if (pid == 0) {
55 static const char* args[] = {"/system/bin/vdc", "checkpoint",
56 "supportsBlockCheckpoint"};
57 EXPECT_NE(execv(args[0], const_cast<char* const*>(args)), -1);
58 }
59
60 int status;
61 EXPECT_NE(waitpid(pid, &status, 0), -1);
62
63 supported = status == 1;
64 evaluated = true;
65 return supported;
66 }
67
68 template <void (*Prepare)(std::string)>
69 class LoopbackTestFixture : public ::testing::Test {
70 protected:
SetUp()71 void SetUp() {
72 Prepare(loop_file_);
73
74 // Get free loop device name
75 unique_fd cfd(open("/dev/loop-control", O_RDWR));
76 ASSERT_NE(cfd.get(), -1);
77 int i = ioctl(cfd, 0x4C82); // LOOP_CTL_GET_FREE
78 ASSERT_GE(i, 0);
79 loop_device_ = std::string("/dev/block/loop") + std::to_string(i);
80
81 // Associate loop device with file
82 unique_fd lfd(open(loop_device_.c_str(), O_RDWR));
83 ASSERT_NE(lfd.get(), -1);
84 unique_fd ffd(open(loop_file_.c_str(), O_RDWR));
85 ASSERT_NE(ffd.get(), -1);
86 ASSERT_EQ(ioctl(lfd.get(), LOOP_SET_FD, ffd.get()), 0);
87 }
88
TearDown()89 void TearDown() {
90 unique_fd lfd(open(loop_device_.c_str(), O_RDWR));
91 EXPECT_NE(lfd.get(), -1);
92 EXPECT_EQ(ioctl(lfd, LOOP_CLR_FD, 0), 0);
93 EXPECT_EQ(remove(loop_file_.c_str()), 0);
94 }
95
96 const static std::string loop_file_;
97
98 public:
99 const static size_t sector_size_ = 512;
100 const static size_t loop_size_ = 4096 * sector_size_;
101 std::string loop_device_;
102 };
103
104 template <void (*Prepare)(std::string)>
105 const std::string LoopbackTestFixture<Prepare>::loop_file_ =
106 "/data/local/tmp/bow_loop";
107
PrepareBowDefault(std::string)108 void PrepareBowDefault(std::string) {}
109
110 template <void (*PrepareLoop)(std::string),
111 void (*PrepareBow)(std::string) = PrepareBowDefault>
112 class BowTestFixture : public LoopbackTestFixture<PrepareLoop> {
GetTableStatus()113 std::string GetTableStatus() {
114 std::vector<DeviceMapper::TargetInfo> targets;
115 DeviceMapper& dm = DeviceMapper::Instance();
116 EXPECT_TRUE(dm.GetTableInfo("bow1", &targets));
117 EXPECT_EQ(targets.size(), 1);
118 return targets[0].data;
119 }
120
121 bool torn_down_;
122
123 protected:
SetUp()124 void SetUp() {
125 if (!blockCheckpointsSupported()) return;
126
127 LoopbackTestFixture<PrepareLoop>::SetUp();
128 PrepareBow(loop_device_);
129
130 torn_down_ = false;
131
132 DmTable table;
133 table.AddTarget(std::make_unique<DmTargetBow>(0, loop_size_ / 512,
134 loop_device_.c_str()));
135
136 DeviceMapper& dm = DeviceMapper::Instance();
137 ASSERT_TRUE(dm.CreateDevice("bow1", table));
138 ASSERT_TRUE(dm.GetDmDevicePathByName("bow1", &bow_device_));
139 }
140
TearDown()141 void TearDown() {
142 if (!blockCheckpointsSupported()) return;
143 BowTearDown();
144 LoopbackTestFixture<PrepareLoop>::TearDown();
145 }
146
TornDown() const147 bool TornDown() const { return torn_down_; }
148
149 public:
150 using LoopbackTestFixture<PrepareLoop>::loop_size_;
151 using LoopbackTestFixture<PrepareLoop>::sector_size_;
152 using LoopbackTestFixture<PrepareLoop>::loop_device_;
153
BowTearDown()154 void BowTearDown() {
155 if (torn_down_) return;
156 torn_down_ = true;
157 EXPECT_TRUE(DeviceMapper::Instance().DeleteDevice("bow1"));
158 }
159
SetState(int i)160 void SetState(int i) {
161 std::string state_file = "/sys" + bow_device_.substr(4) + "/bow/state";
162 std::ofstream(state_file) << i;
163
164 int j;
165 std::ifstream(state_file) >> j;
166 EXPECT_EQ(i, j);
167 }
168
169 enum SectorTypes {
170 INVALID,
171 SECTOR0,
172 SECTOR0_CURRENT,
173 UNCHANGED,
174 BACKUP,
175 FREE,
176 CHANGED,
177 TOP
178 };
179
180 struct TableEntry {
181 SectorTypes type;
182 uint64_t offset;
183
operator ==android::BowTestFixture::TableEntry184 bool operator==(const TableEntry& te) const {
185 return type == te.type && offset == te.offset;
186 }
187 };
188
GetTable()189 std::vector<TableEntry> GetTable() {
190 std::string status = GetTableStatus();
191 std::istringstream i(status);
192 std::vector<TableEntry> table;
193 while (true) {
194 TableEntry te = {};
195 std::string s;
196 i >> s >> te.offset;
197 if (!i) {
198 EXPECT_EQ(s, "");
199 break;
200 }
201
202 if (s == "Sector0:")
203 te.type = SECTOR0;
204 else if (s == "Sector0_current:")
205 te.type = SECTOR0_CURRENT;
206 else if (s == "Unchanged:")
207 te.type = UNCHANGED;
208 else if (s == "Backup:")
209 te.type = BACKUP;
210 else if (s == "Free:")
211 te.type = FREE;
212 else if (s == "Changed:")
213 te.type = CHANGED;
214 else if (s == "Top:")
215 te.type = TOP;
216 else
217 ADD_FAILURE();
218
219 te.offset /= sector_size_ / 512;
220
221 table.push_back(te);
222 }
223 return table;
224 }
225
226 std::string bow_device_;
227 };
228
PrepareFile(std::string loop_file)229 void PrepareFile(std::string loop_file) {
230 auto const& sector_size_ = LoopbackTestFixture<PrepareFile>::sector_size_;
231 auto const& loop_size_ = LoopbackTestFixture<PrepareFile>::loop_size_;
232
233 unique_fd fd(
234 open(loop_file.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR));
235 ASSERT_NE(fd.get(), -1);
236 for (int i = 0; i < loop_size_ / sector_size_; ++i) {
237 char buffer[sector_size_] = {};
238 snprintf(buffer, sizeof(buffer), "Sector %d", i);
239 write(fd.get(), buffer, sizeof(buffer));
240 }
241 }
242
243 class FileBowTestFixture : public BowTestFixture<&PrepareFile> {
SetUp()244 void SetUp() {
245 if (!blockCheckpointsSupported()) return;
246 BowTestFixture<&PrepareFile>::SetUp();
247 fd_ = unique_fd(open(bow_device_.c_str(), O_RDWR));
248 ASSERT_NE(fd_.get(), -1);
249 }
250
TearDown()251 void TearDown() {
252 fd_ = unique_fd();
253 BowTestFixture<&PrepareFile>::TearDown();
254 }
255
256 unique_fd fd_;
257
258 public:
Discard(uint64_t offset,uint64_t length)259 void Discard(uint64_t offset, uint64_t length) {
260 uint64_t range[2] = {offset * sector_size_, length * sector_size_};
261 EXPECT_EQ(ioctl(fd_.get(), BLKDISCARD, range), 0);
262 }
263
Write(SectorTypes type)264 int Write(SectorTypes type) {
265 for (auto i : GetTable())
266 if (i.type == type) {
267 EXPECT_NE(lseek(fd_, i.offset * sector_size_, SEEK_SET), -1);
268 EXPECT_EQ(write(fd_, "Changed", 8), 8);
269 return i.offset;
270 }
271 EXPECT_TRUE(false);
272 return -1;
273 }
274
FindChanged(std::vector<TableEntry> const & free,int expected_changed)275 void FindChanged(std::vector<TableEntry> const& free, int expected_changed) {
276 unique_fd fd;
277 if (TornDown())
278 fd = unique_fd(open(loop_device_.c_str(), O_RDONLY));
279 else
280 fd = unique_fd(open(bow_device_.c_str(), O_RDONLY));
281 EXPECT_NE(fd.get(), -1);
282 if (fd.get() == -1) return;
283
284 int changed = -1;
285 for (int i = 0; i < loop_size_ / sector_size_; ++i) {
286 if (i != expected_changed) {
287 auto type = SECTOR0;
288 for (auto j : free)
289 if (j.offset > i)
290 break;
291 else
292 type = j.type;
293 if (type == FREE) continue;
294 }
295
296 char buffer[sector_size_];
297 EXPECT_NE(lseek(fd.get(), i * sector_size_, SEEK_SET), -1);
298 EXPECT_EQ(read(fd.get(), buffer, sizeof(buffer)), sizeof(buffer));
299 if (strcmp(buffer, "Changed") == 0) {
300 EXPECT_EQ(changed, -1);
301 changed = i;
302 } else {
303 std::string expected = "Sector " + std::to_string(i);
304 EXPECT_STREQ(buffer, expected.c_str());
305 }
306 }
307 EXPECT_EQ(changed, expected_changed);
308 }
309
DumpSector0()310 void DumpSector0() {
311 char buffer[sector_size_];
312 lseek(fd_.get(), 0, SEEK_SET);
313 read(fd_.get(), buffer, sizeof(buffer));
314 for (int i = 0; i < sector_size_ * 2; ++i) {
315 std::cout << std::hex << std::setw(2) << std::setfill('0')
316 << (int)buffer[i];
317 if (i % 32 == 31)
318 std::cout << std::endl;
319 else
320 std::cout << " ";
321 }
322 }
323
WriteSubmit(SectorTypes type)324 void WriteSubmit(SectorTypes type) {
325 if (!blockCheckpointsSupported()) return;
326 Discard(1, 1);
327 Discard(3, 2);
328 auto free = GetTable();
329
330 SetState(1);
331 auto changed = Write(type);
332
333 SetState(2);
334 FindChanged(free, changed);
335 }
336
WriteRestore(SectorTypes type)337 void WriteRestore(SectorTypes type) {
338 if (!blockCheckpointsSupported()) return;
339 Discard(1, 1);
340 Discard(3, 2);
341 auto free = GetTable();
342 SetState(1);
343 Write(type);
344 BowTearDown();
345 system(std::string("vdc checkpoint restoreCheckpoint " + loop_device_)
346 .c_str());
347 FindChanged(free, -1);
348 }
349 };
350
TEST_F(FileBowTestFixture,discardVisible)351 TEST_F(FileBowTestFixture, discardVisible) {
352 if (!blockCheckpointsSupported()) return;
353 Discard(8, 1);
354 Discard(16, 1);
355 Discard(12, 1);
356 Discard(4, 1);
357
358 std::vector<TableEntry> table = {
359 {UNCHANGED, 0}, {FREE, 4},
360 {UNCHANGED, 5}, {FREE, 8},
361 {UNCHANGED, 9}, {FREE, 12},
362 {UNCHANGED, 13}, {FREE, 16},
363 {UNCHANGED, 17}, {TOP, loop_size_ / sector_size_},
364 };
365
366 EXPECT_EQ(GetTable(), table);
367 }
368
TEST_F(FileBowTestFixture,writeSector0Submit)369 TEST_F(FileBowTestFixture, writeSector0Submit) {
370 SCOPED_TRACE("write submit SECTOR0");
371 WriteSubmit(SECTOR0);
372 }
373
TEST_F(FileBowTestFixture,writeSector0Revert)374 TEST_F(FileBowTestFixture, writeSector0Revert) {
375 SCOPED_TRACE("write restore SECTOR0");
376 WriteRestore(SECTOR0);
377 }
378
TEST_F(FileBowTestFixture,writeSector0_CurrentSubmit)379 TEST_F(FileBowTestFixture, writeSector0_CurrentSubmit) {
380 SCOPED_TRACE("write submit SECTOR0_CURRENT");
381 WriteSubmit(SECTOR0_CURRENT);
382 }
383
TEST_F(FileBowTestFixture,writeSector0_CurrentRevert)384 TEST_F(FileBowTestFixture, writeSector0_CurrentRevert) {
385 SCOPED_TRACE("write restore SECTOR0_CURRENT");
386 WriteRestore(SECTOR0_CURRENT);
387 }
388
TEST_F(FileBowTestFixture,writeUnchangedSubmit)389 TEST_F(FileBowTestFixture, writeUnchangedSubmit) {
390 SCOPED_TRACE("write submit UNCHANGED");
391 WriteSubmit(UNCHANGED);
392 }
393
TEST_F(FileBowTestFixture,writeUnchangedRevert)394 TEST_F(FileBowTestFixture, writeUnchangedRevert) {
395 SCOPED_TRACE("write restore UNCHANGED");
396 WriteRestore(UNCHANGED);
397 }
398
TEST_F(FileBowTestFixture,writeBackupSubmit)399 TEST_F(FileBowTestFixture, writeBackupSubmit) {
400 SCOPED_TRACE("write submit BACKUP");
401 WriteSubmit(BACKUP);
402 }
403
TEST_F(FileBowTestFixture,writeBackupRevert)404 TEST_F(FileBowTestFixture, writeBackupRevert) {
405 SCOPED_TRACE("write restore BACKUP");
406 WriteRestore(BACKUP);
407 }
408
TEST_F(FileBowTestFixture,writeFreeSubmit)409 TEST_F(FileBowTestFixture, writeFreeSubmit) {
410 SCOPED_TRACE("write submit FREE");
411 WriteSubmit(FREE);
412 }
413
TEST_F(FileBowTestFixture,writeFreeRevert)414 TEST_F(FileBowTestFixture, writeFreeRevert) {
415 SCOPED_TRACE("write restore FREE");
416 WriteRestore(FREE);
417 }
418
419 /* There are no changed sectors at start, so these can't work as is
420 TEST_F(BowTestFixture, writeChangedSubmit) {
421 SCOPED_TRACE("write submit CHANGED");
422 WriteSubmit(CHANGED);
423 }
424
425 TEST_F(BowTestFixture, writeChangedRevert) {
426 SCOPED_TRACE("write restore CHANGED");
427 WriteRestore(CHANGED);
428 }
429 */
430
PrepareFileSystem(std::string loop_file)431 void PrepareFileSystem(std::string loop_file) {
432 EXPECT_EQ(
433 system((std::string("dd if=/dev/zero bs=512 count=4096 of=") + loop_file)
434 .c_str()),
435 0);
436 }
437
438 #define MOUNT_POINT "/data/local/tmp/mount"
439
SetupFileSystem(std::string loop_device)440 void SetupFileSystem(std::string loop_device) {
441 EXPECT_EQ(system((std::string("mke2fs ") + loop_device).c_str()), 0);
442 EXPECT_EQ(system("mkdir " MOUNT_POINT), 0);
443 EXPECT_EQ(
444 system((std::string("mount ") + loop_device + " " MOUNT_POINT).c_str()),
445 0);
446 EXPECT_EQ(system("echo Original > " MOUNT_POINT "/file"), 0);
447 EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
448 EXPECT_EQ(system("rmdir " MOUNT_POINT), 0);
449 }
450
Trim()451 void Trim() {
452 unique_fd fd(open(MOUNT_POINT, O_RDONLY));
453 EXPECT_NE(fd.get(), -1);
454 struct fstrim_range range = {};
455 range.len = ULLONG_MAX;
456 EXPECT_EQ(ioctl(fd, FITRIM, &range), 0);
457 }
458
459 typedef BowTestFixture<&PrepareFileSystem, &SetupFileSystem>
460 FileSystemBowTestFixture;
461
TEST_F(FileSystemBowTestFixture,filesystemSubmit)462 TEST_F(FileSystemBowTestFixture, filesystemSubmit) {
463 if (!blockCheckpointsSupported()) return;
464 EXPECT_EQ(system("mkdir " MOUNT_POINT), 0);
465 EXPECT_EQ(
466 system((std::string("mount ") + bow_device_ + " " MOUNT_POINT).c_str()),
467 0);
468 Trim();
469 SetState(1);
470 EXPECT_EQ(system("echo Changed > " MOUNT_POINT "/file"), 0);
471 SetState(2);
472 EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
473 BowTearDown();
474 EXPECT_EQ(
475 system((std::string("mount ") + loop_device_ + " " MOUNT_POINT).c_str()),
476 0);
477 std::string contents;
478 std::ifstream(MOUNT_POINT "/file") >> contents;
479 EXPECT_EQ(contents, std::string("Changed"));
480 EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
481 EXPECT_EQ(system("rmdir " MOUNT_POINT), 0);
482 }
483
TEST_F(FileSystemBowTestFixture,filesystemRevert)484 TEST_F(FileSystemBowTestFixture, filesystemRevert) {
485 if (!blockCheckpointsSupported()) return;
486 EXPECT_EQ(system("mkdir " MOUNT_POINT), 0);
487 EXPECT_EQ(
488 system((std::string("mount ") + bow_device_ + " " MOUNT_POINT).c_str()),
489 0);
490 Trim();
491 SetState(1);
492 EXPECT_EQ(system("echo Changed > " MOUNT_POINT "/file"), 0);
493 EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
494 BowTearDown();
495 system((std::string("vdc checkpoint restoreCheckpoint ") + loop_device_)
496 .c_str());
497 EXPECT_EQ(
498 system((std::string("mount ") + loop_device_ + " " MOUNT_POINT).c_str()),
499 0);
500 std::string contents;
501 std::ifstream(MOUNT_POINT "/file") >> contents;
502 EXPECT_EQ(contents, std::string("Original"));
503 EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
504 EXPECT_EQ(system("rmdir " MOUNT_POINT), 0);
505 }
506
507 } // namespace android
508