| /* | |
| * Copyright (C) 2018 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <fcntl.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/types.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #include <chrono> | |
| #include <ctime> | |
| #include <map> | |
| #include <thread> | |
| #include <android-base/file.h> | |
| #include <android-base/unique_fd.h> | |
| #include <gtest/gtest.h> | |
| #include <libdm/dm.h> | |
| #include <libdm/loop_control.h> | |
| #include "test_util.h" | |
| using namespace std; | |
| using namespace android::dm; | |
| using unique_fd = android::base::unique_fd; | |
| TEST(libdm, HasMinimumTargets) { | |
| DeviceMapper& dm = DeviceMapper::Instance(); | |
| vector<DmTargetTypeInfo> targets; | |
| ASSERT_TRUE(dm.GetAvailableTargets(&targets)); | |
| map<string, DmTargetTypeInfo> by_name; | |
| for (const auto& target : targets) { | |
| by_name[target.name()] = target; | |
| } | |
| auto iter = by_name.find("linear"); | |
| EXPECT_NE(iter, by_name.end()); | |
| } | |
| // Helper to ensure that device mapper devices are released. | |
| class TempDevice { | |
| public: | |
| TempDevice(const std::string& name, const DmTable& table) | |
| : dm_(DeviceMapper::Instance()), name_(name), valid_(false) { | |
| valid_ = dm_.CreateDevice(name, table); | |
| } | |
| TempDevice(TempDevice&& other) noexcept | |
| : dm_(other.dm_), name_(other.name_), valid_(other.valid_) { | |
| other.valid_ = false; | |
| } | |
| ~TempDevice() { | |
| if (valid_) { | |
| dm_.DeleteDevice(name_); | |
| } | |
| } | |
| bool Destroy() { | |
| if (!valid_) { | |
| return false; | |
| } | |
| valid_ = false; | |
| return dm_.DeleteDevice(name_); | |
| } | |
| bool WaitForUdev() const { | |
| auto start_time = std::chrono::steady_clock::now(); | |
| while (true) { | |
| if (!access(path().c_str(), F_OK)) { | |
| return true; | |
| } | |
| if (errno != ENOENT) { | |
| return false; | |
| } | |
| std::this_thread::sleep_for(50ms); | |
| std::chrono::duration elapsed = std::chrono::steady_clock::now() - start_time; | |
| if (elapsed >= 5s) { | |
| return false; | |
| } | |
| } | |
| } | |
| std::string path() const { | |
| std::string device_path; | |
| if (!dm_.GetDmDevicePathByName(name_, &device_path)) { | |
| return ""; | |
| } | |
| return device_path; | |
| } | |
| const std::string& name() const { return name_; } | |
| bool valid() const { return valid_; } | |
| TempDevice(const TempDevice&) = delete; | |
| TempDevice& operator=(const TempDevice&) = delete; | |
| TempDevice& operator=(TempDevice&& other) noexcept { | |
| name_ = other.name_; | |
| valid_ = other.valid_; | |
| other.valid_ = false; | |
| return *this; | |
| } | |
| private: | |
| DeviceMapper& dm_; | |
| std::string name_; | |
| bool valid_; | |
| }; | |
| TEST(libdm, DmLinear) { | |
| unique_fd tmp1(CreateTempFile("file_1", 4096)); | |
| ASSERT_GE(tmp1, 0); | |
| unique_fd tmp2(CreateTempFile("file_2", 4096)); | |
| ASSERT_GE(tmp2, 0); | |
| // Create two different files. These will back two separate loop devices. | |
| const char message1[] = "Hello! This is sector 1."; | |
| const char message2[] = "Goodbye. This is sector 2."; | |
| ASSERT_TRUE(android::base::WriteFully(tmp1, message1, sizeof(message1))); | |
| ASSERT_TRUE(android::base::WriteFully(tmp2, message2, sizeof(message2))); | |
| LoopDevice loop_a(tmp1); | |
| ASSERT_TRUE(loop_a.valid()); | |
| LoopDevice loop_b(tmp2); | |
| ASSERT_TRUE(loop_b.valid()); | |
| // Define a 2-sector device, with each sector mapping to the first sector | |
| // of one of our loop devices. | |
| DmTable table; | |
| ASSERT_TRUE(table.AddTarget(make_unique<DmTargetLinear>(0, 1, loop_a.device(), 0))); | |
| ASSERT_TRUE(table.AddTarget(make_unique<DmTargetLinear>(1, 1, loop_b.device(), 0))); | |
| ASSERT_TRUE(table.valid()); | |
| TempDevice dev("libdm-test-dm-linear", table); | |
| ASSERT_TRUE(dev.valid()); | |
| ASSERT_FALSE(dev.path().empty()); | |
| ASSERT_TRUE(dev.WaitForUdev()); | |
| // Note: a scope is needed to ensure that there are no open descriptors | |
| // when we go to close the device. | |
| { | |
| unique_fd dev_fd(open(dev.path().c_str(), O_RDWR)); | |
| ASSERT_GE(dev_fd, 0); | |
| // Test that each sector of our device is correctly mapped to each loop | |
| // device. | |
| char sector[512]; | |
| ASSERT_TRUE(android::base::ReadFully(dev_fd, sector, sizeof(sector))); | |
| ASSERT_EQ(strncmp(sector, message1, sizeof(message1)), 0); | |
| ASSERT_TRUE(android::base::ReadFully(dev_fd, sector, sizeof(sector))); | |
| ASSERT_EQ(strncmp(sector, message2, sizeof(message2)), 0); | |
| } | |
| // Test GetTableStatus. | |
| DeviceMapper& dm = DeviceMapper::Instance(); | |
| vector<DeviceMapper::TargetInfo> targets; | |
| ASSERT_TRUE(dm.GetTableStatus(dev.name(), &targets)); | |
| ASSERT_EQ(targets.size(), 2); | |
| EXPECT_EQ(strcmp(targets[0].spec.target_type, "linear"), 0); | |
| EXPECT_TRUE(targets[0].data.empty()); | |
| EXPECT_EQ(targets[0].spec.sector_start, 0); | |
| EXPECT_EQ(targets[0].spec.length, 1); | |
| EXPECT_EQ(strcmp(targets[1].spec.target_type, "linear"), 0); | |
| EXPECT_TRUE(targets[1].data.empty()); | |
| EXPECT_EQ(targets[1].spec.sector_start, 1); | |
| EXPECT_EQ(targets[1].spec.length, 1); | |
| // Normally the TestDevice destructor would delete this, but at least one | |
| // test should ensure that device deletion works. | |
| ASSERT_TRUE(dev.Destroy()); | |
| } | |
| TEST(libdm, DmVerityArgsAvb2) { | |
| std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a"; | |
| std::string algorithm = "sha1"; | |
| std::string digest = "4be7e823b8c40f7bd5c8ccd5123f0722c5baca21"; | |
| std::string salt = "cc99f81ecb9484220a003b0719ee59dcf9be7e5d"; | |
| DmTargetVerity target(0, 10000, 1, device, device, 4096, 4096, 125961, 125961, algorithm, | |
| digest, salt); | |
| target.UseFec(device, 2, 126955, 126955); | |
| target.SetVerityMode("restart_on_corruption"); | |
| target.IgnoreZeroBlocks(); | |
| // Verity table from a walleye build. | |
| std::string expected = | |
| "1 /dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a " | |
| "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a 4096 4096 125961 125961 sha1 " | |
| "4be7e823b8c40f7bd5c8ccd5123f0722c5baca21 cc99f81ecb9484220a003b0719ee59dcf9be7e5d 10 " | |
| "use_fec_from_device /dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a fec_roots " | |
| "2 fec_blocks 126955 fec_start 126955 restart_on_corruption ignore_zero_blocks"; | |
| EXPECT_EQ(target.GetParameterString(), expected); | |
| } |