| /* | |
| * 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 "libdm/dm.h" | |
| #include <sys/ioctl.h> | |
| #include <sys/sysmacros.h> | |
| #include <sys/types.h> | |
| #include <android-base/logging.h> | |
| #include <android-base/macros.h> | |
| namespace android { | |
| namespace dm { | |
| DeviceMapper::DeviceMapper() : fd_(-1) { | |
| fd_ = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC)); | |
| if (fd_ < 0) { | |
| PLOG(ERROR) << "Failed to open device-mapper"; | |
| } | |
| } | |
| DeviceMapper& DeviceMapper::Instance() { | |
| static DeviceMapper instance; | |
| return instance; | |
| } | |
| // Creates a new device mapper device | |
| bool DeviceMapper::CreateDevice(const std::string& name) { | |
| if (name.empty()) { | |
| LOG(ERROR) << "Unnamed device mapper device creation is not supported"; | |
| return false; | |
| } | |
| if (name.size() >= DM_NAME_LEN) { | |
| LOG(ERROR) << "[" << name << "] is too long to be device mapper name"; | |
| return false; | |
| } | |
| struct dm_ioctl io; | |
| InitIo(&io, name); | |
| if (ioctl(fd_, DM_DEV_CREATE, &io)) { | |
| PLOG(ERROR) << "DM_DEV_CREATE failed for [" << name << "]"; | |
| return false; | |
| } | |
| // Check to make sure the newly created device doesn't already have targets | |
| // added or opened by someone | |
| CHECK(io.target_count == 0) << "Unexpected targets for newly created [" << name << "] device"; | |
| CHECK(io.open_count == 0) << "Unexpected opens for newly created [" << name << "] device"; | |
| // Creates a new device mapper device with the name passed in | |
| return true; | |
| } | |
| bool DeviceMapper::DeleteDevice(const std::string& name) { | |
| if (name.empty()) { | |
| LOG(ERROR) << "Unnamed device mapper device creation is not supported"; | |
| return false; | |
| } | |
| if (name.size() >= DM_NAME_LEN) { | |
| LOG(ERROR) << "[" << name << "] is too long to be device mapper name"; | |
| return false; | |
| } | |
| struct dm_ioctl io; | |
| InitIo(&io, name); | |
| if (ioctl(fd_, DM_DEV_REMOVE, &io)) { | |
| PLOG(ERROR) << "DM_DEV_REMOVE failed for [" << name << "]"; | |
| return false; | |
| } | |
| // Check to make sure appropriate uevent is generated so ueventd will | |
| // do the right thing and remove the corresponding device node and symlinks. | |
| CHECK(io.flags & DM_UEVENT_GENERATED_FLAG) | |
| << "Didn't generate uevent for [" << name << "] removal"; | |
| return true; | |
| } | |
| const std::unique_ptr<DmTable> DeviceMapper::table(const std::string& /* name */) const { | |
| // TODO(b/110035986): Return the table, as read from the kernel instead | |
| return nullptr; | |
| } | |
| DmDeviceState DeviceMapper::GetState(const std::string& name) const { | |
| struct dm_ioctl io; | |
| InitIo(&io, name); | |
| if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) { | |
| return DmDeviceState::INVALID; | |
| } | |
| if ((io.flags & DM_ACTIVE_PRESENT_FLAG) && !(io.flags & DM_SUSPEND_FLAG)) { | |
| return DmDeviceState::ACTIVE; | |
| } | |
| return DmDeviceState::SUSPENDED; | |
| } | |
| bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table) { | |
| if (!CreateDevice(name)) { | |
| return false; | |
| } | |
| if (!LoadTableAndActivate(name, table)) { | |
| DeleteDevice(name); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool DeviceMapper::LoadTableAndActivate(const std::string& name, const DmTable& table) { | |
| std::string ioctl_buffer(sizeof(struct dm_ioctl), 0); | |
| ioctl_buffer += table.Serialize(); | |
| struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(&ioctl_buffer[0]); | |
| InitIo(io, name); | |
| io->data_size = ioctl_buffer.size(); | |
| io->data_start = sizeof(struct dm_ioctl); | |
| io->target_count = static_cast<uint32_t>(table.num_targets()); | |
| if (table.readonly()) { | |
| io->flags |= DM_READONLY_FLAG; | |
| } | |
| if (ioctl(fd_, DM_TABLE_LOAD, io)) { | |
| PLOG(ERROR) << "DM_TABLE_LOAD failed"; | |
| return false; | |
| } | |
| InitIo(io, name); | |
| if (ioctl(fd_, DM_DEV_SUSPEND, io)) { | |
| PLOG(ERROR) << "DM_TABLE_SUSPEND resume failed"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Reads all the available device mapper targets and their corresponding | |
| // versions from the kernel and returns in a vector | |
| bool DeviceMapper::GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets) { | |
| targets->clear(); | |
| // calculate the space needed to read a maximum of kMaxPossibleDmTargets | |
| uint32_t payload_size = sizeof(struct dm_target_versions); | |
| payload_size += DM_MAX_TYPE_NAME; | |
| // device mapper wants every target spec to be aligned at 8-byte boundary | |
| payload_size = DM_ALIGN(payload_size); | |
| payload_size *= kMaxPossibleDmTargets; | |
| uint32_t data_size = sizeof(struct dm_ioctl) + payload_size; | |
| auto buffer = std::unique_ptr<void, void (*)(void*)>(calloc(1, data_size), free); | |
| if (buffer == nullptr) { | |
| LOG(ERROR) << "failed to allocate memory"; | |
| return false; | |
| } | |
| // Sets appropriate data size and data_start to make sure we tell kernel | |
| // about the total size of the buffer we are passing and where to start | |
| // writing the list of targets. | |
| struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer.get()); | |
| InitIo(io); | |
| io->data_size = data_size; | |
| io->data_start = sizeof(*io); | |
| if (ioctl(fd_, DM_LIST_VERSIONS, io)) { | |
| PLOG(ERROR) << "DM_LIST_VERSIONS failed"; | |
| return false; | |
| } | |
| // If the provided buffer wasn't enough to list all targets, note that | |
| // any data beyond sizeof(*io) must not be read in this case | |
| if (io->flags & DM_BUFFER_FULL_FLAG) { | |
| LOG(INFO) << data_size << " is not enough memory to list all dm targets"; | |
| return false; | |
| } | |
| // if there are no targets registered, return success with empty vector | |
| if (io->data_size == sizeof(*io)) { | |
| return true; | |
| } | |
| // Parse each target and list the name and version | |
| // TODO(b/110035986): Templatize this | |
| uint32_t next = sizeof(*io); | |
| data_size = io->data_size - next; | |
| struct dm_target_versions* vers = | |
| reinterpret_cast<struct dm_target_versions*>(static_cast<char*>(buffer.get()) + next); | |
| while (next && data_size) { | |
| targets->emplace_back(vers); | |
| if (vers->next == 0) { | |
| break; | |
| } | |
| next += vers->next; | |
| data_size -= vers->next; | |
| vers = reinterpret_cast<struct dm_target_versions*>(static_cast<char*>(buffer.get()) + | |
| next); | |
| } | |
| return true; | |
| } | |
| bool DeviceMapper::GetAvailableDevices(std::vector<DmBlockDevice>* devices) { | |
| devices->clear(); | |
| // calculate the space needed to read a maximum of 256 targets, each with | |
| // name with maximum length of 16 bytes | |
| uint32_t payload_size = sizeof(struct dm_name_list); | |
| // 128-bytes for the name | |
| payload_size += DM_NAME_LEN; | |
| // dm wants every device spec to be aligned at 8-byte boundary | |
| payload_size = DM_ALIGN(payload_size); | |
| payload_size *= kMaxPossibleDmDevices; | |
| uint32_t data_size = sizeof(struct dm_ioctl) + payload_size; | |
| auto buffer = std::unique_ptr<void, void (*)(void*)>(calloc(1, data_size), free); | |
| if (buffer == nullptr) { | |
| LOG(ERROR) << "failed to allocate memory"; | |
| return false; | |
| } | |
| // Sets appropriate data size and data_start to make sure we tell kernel | |
| // about the total size of the buffer we are passing and where to start | |
| // writing the list of targets. | |
| struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer.get()); | |
| InitIo(io); | |
| io->data_size = data_size; | |
| io->data_start = sizeof(*io); | |
| if (ioctl(fd_, DM_LIST_DEVICES, io)) { | |
| PLOG(ERROR) << "DM_LIST_DEVICES failed"; | |
| return false; | |
| } | |
| // If the provided buffer wasn't enough to list all devices any data | |
| // beyond sizeof(*io) must not be read. | |
| if (io->flags & DM_BUFFER_FULL_FLAG) { | |
| LOG(INFO) << data_size << " is not enough memory to list all dm devices"; | |
| return false; | |
| } | |
| // if there are no devices created yet, return success with empty vector | |
| if (io->data_size == sizeof(*io)) { | |
| return true; | |
| } | |
| // Parse each device and add a new DmBlockDevice to the vector | |
| // created from the kernel data. | |
| uint32_t next = sizeof(*io); | |
| data_size = io->data_size - next; | |
| struct dm_name_list* dm_dev = | |
| reinterpret_cast<struct dm_name_list*>(static_cast<char*>(buffer.get()) + next); | |
| while (next && data_size) { | |
| devices->emplace_back((dm_dev)); | |
| if (dm_dev->next == 0) { | |
| break; | |
| } | |
| next += dm_dev->next; | |
| data_size -= dm_dev->next; | |
| dm_dev = reinterpret_cast<struct dm_name_list*>(static_cast<char*>(buffer.get()) + next); | |
| } | |
| return true; | |
| } | |
| // Accepts a device mapper device name (like system_a, vendor_b etc) and | |
| // returns the path to it's device node (or symlink to the device node) | |
| bool DeviceMapper::GetDmDevicePathByName(const std::string& name, std::string* path) { | |
| struct dm_ioctl io; | |
| InitIo(&io, name); | |
| if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) { | |
| PLOG(WARNING) << "DM_DEV_STATUS failed for " << name; | |
| return false; | |
| } | |
| uint32_t dev_num = minor(io.dev); | |
| *path = "/dev/block/dm-" + std::to_string(dev_num); | |
| return true; | |
| } | |
| bool DeviceMapper::GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) { | |
| return GetTable(name, 0, table); | |
| } | |
| bool DeviceMapper::GetTableInfo(const std::string& name, std::vector<TargetInfo>* table) { | |
| return GetTable(name, DM_STATUS_TABLE_FLAG, table); | |
| } | |
| // private methods of DeviceMapper | |
| bool DeviceMapper::GetTable(const std::string& name, uint32_t flags, | |
| std::vector<TargetInfo>* table) { | |
| std::vector<char> buffer; | |
| struct dm_ioctl* io = nullptr; | |
| for (buffer.resize(4096);; buffer.resize(buffer.size() * 2)) { | |
| io = reinterpret_cast<struct dm_ioctl*>(&buffer[0]); | |
| InitIo(io, name); | |
| io->data_size = buffer.size(); | |
| io->data_start = sizeof(*io); | |
| io->flags = flags; | |
| if (ioctl(fd_, DM_TABLE_STATUS, io) < 0) { | |
| PLOG(ERROR) << "DM_TABLE_STATUS failed for " << name; | |
| return false; | |
| } | |
| if (!(io->flags & DM_BUFFER_FULL_FLAG)) break; | |
| } | |
| uint32_t cursor = io->data_start; | |
| uint32_t data_end = std::min(io->data_size, uint32_t(buffer.size())); | |
| for (uint32_t i = 0; i < io->target_count; i++) { | |
| if (cursor + sizeof(struct dm_target_spec) > data_end) { | |
| break; | |
| } | |
| // After each dm_target_spec is a status string. spec->next is an | |
| // offset from |io->data_start|, and we clamp it to the size of our | |
| // buffer. | |
| struct dm_target_spec* spec = reinterpret_cast<struct dm_target_spec*>(&buffer[cursor]); | |
| uint32_t data_offset = cursor + sizeof(dm_target_spec); | |
| uint32_t next_cursor = std::min(io->data_start + spec->next, data_end); | |
| std::string data; | |
| if (next_cursor > data_offset) { | |
| // Note: we use c_str() to eliminate any extra trailing 0s. | |
| data = std::string(&buffer[data_offset], next_cursor - data_offset).c_str(); | |
| } | |
| table->emplace_back(*spec, data); | |
| cursor = next_cursor; | |
| } | |
| return true; | |
| } | |
| void DeviceMapper::InitIo(struct dm_ioctl* io, const std::string& name) const { | |
| CHECK(io != nullptr) << "nullptr passed to dm_ioctl initialization"; | |
| memset(io, 0, sizeof(*io)); | |
| io->version[0] = DM_VERSION0; | |
| io->version[1] = DM_VERSION1; | |
| io->version[2] = DM_VERSION2; | |
| io->data_size = sizeof(*io); | |
| io->data_start = 0; | |
| if (!name.empty()) { | |
| snprintf(io->name, sizeof(io->name), "%s", name.c_str()); | |
| } | |
| } | |
| } // namespace dm | |
| } // namespace android |