/* * 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); }