| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <brillo/blkdev_utils/loop_device.h> |
| |
| #include <fcntl.h> |
| #include <linux/loop.h> |
| #include <linux/major.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| |
| namespace brillo { |
| |
| namespace { |
| |
| constexpr char kLoopControl[] = "/dev/loop-control"; |
| constexpr char kSysBlockPath[] = "/sys/block"; |
| // File containing device id in /sys/block/loopX/. |
| constexpr char kDeviceIdPath[] = "dev"; |
| constexpr char kLoopBackingFile[] = "loop/backing_file"; |
| constexpr int kLoopDeviceIoctlFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; |
| constexpr int kLoopControlIoctlFlags = O_RDONLY | O_NOFOLLOW | O_CLOEXEC; |
| // Arbitrary retry limit for attempting to attach a loop device. |
| constexpr int kMaxLoopDeviceAttachTries = 10; |
| |
| // ioctl runner for LoopDevice and LoopDeviceManager |
| int LoopDeviceIoctl(const base::FilePath& device, |
| int type, |
| uint64_t arg, |
| int open_flag) { |
| base::ScopedFD device_fd( |
| HANDLE_EINTR(open(device.value().c_str(), open_flag))); |
| |
| if (!device_fd.is_valid()) { |
| PLOG(ERROR) << "Unable to open loop device"; |
| return -EINVAL; |
| } |
| |
| int rc = ioctl(device_fd.get(), type, arg); |
| |
| if (rc < 0) |
| PLOG(ERROR) << "ioctl failed."; |
| |
| return rc; |
| } |
| |
| // Parse the device number for a valid /sys/block/loopX path |
| // or symlink to such a path. |
| // Returns -1 if invalid. |
| int GetDeviceNumber(const base::FilePath& sys_block_loopdev_path) { |
| std::string device_string; |
| int device_number = -1; |
| |
| base::FilePath device_file = sys_block_loopdev_path.Append(kDeviceIdPath); |
| |
| if (!base::ReadFileToString(device_file, &device_string)) |
| return -1; |
| |
| std::vector<std::string> device_ids = base::SplitString( |
| device_string, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| if (device_ids.size() != 2 || |
| device_ids[0] != base::NumberToString(LOOP_MAJOR)) |
| return -1; |
| |
| base::StringToInt(device_ids[1], &device_number); |
| return device_number; |
| } |
| |
| // For a validated loop device path, return the backing file path. |
| // Note that a pre-populated loop device path would return an empty |
| // backing file. |
| base::FilePath GetBackingFile(const base::FilePath& loopdev_path) { |
| // Backing file contains path to associated source for loop devices. |
| base::FilePath backing_file = loopdev_path.Append(kLoopBackingFile); |
| std::string backing_file_content; |
| // If the backing file doesn't exist, it's not an attached loop device. |
| if (!base::ReadFileToString(backing_file, &backing_file_content)) |
| return base::FilePath(); |
| base::FilePath backing_file_path( |
| base::TrimWhitespaceASCII(backing_file_content, base::TRIM_ALL)); |
| |
| return backing_file_path; |
| } |
| |
| base::FilePath CreateDevicePath(int device_number) { |
| return base::FilePath(base::StringPrintf("/dev/loop%d", device_number)); |
| } |
| |
| } // namespace |
| |
| LoopDevice::LoopDevice(int device_number, |
| const base::FilePath& backing_file, |
| const LoopIoctl& ioctl_runner) |
| : device_number_(device_number), |
| backing_file_(backing_file), |
| loop_ioctl_(ioctl_runner) {} |
| |
| bool LoopDevice::SetStatus(struct loop_info64 info) { |
| if (loop_ioctl_.Run(GetDevicePath(), LOOP_SET_STATUS64, |
| reinterpret_cast<uint64_t>(&info), |
| kLoopDeviceIoctlFlags) < 0) { |
| LOG(ERROR) << "ioctl(LOOP_SET_STATUS64) failed"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool LoopDevice::GetStatus(struct loop_info64* info) { |
| if (loop_ioctl_.Run(GetDevicePath(), LOOP_GET_STATUS64, |
| reinterpret_cast<uint64_t>(info), |
| kLoopDeviceIoctlFlags) < 0) { |
| LOG(ERROR) << "ioctl(LOOP_GET_STATUS64) failed"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool LoopDevice::SetName(const std::string& name) { |
| struct loop_info64 info; |
| |
| memset(&info, 0, sizeof(info)); |
| strncpy(reinterpret_cast<char*>(info.lo_file_name), name.c_str(), |
| LO_NAME_SIZE); |
| return SetStatus(info); |
| } |
| |
| bool LoopDevice::Detach() { |
| if (loop_ioctl_.Run(GetDevicePath(), LOOP_CLR_FD, 0, kLoopDeviceIoctlFlags) != |
| 0) { |
| LOG(ERROR) << "ioctl(LOOP_CLR_FD) failed"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| base::FilePath LoopDevice::GetDevicePath() { |
| return CreateDevicePath(device_number_); |
| } |
| |
| bool LoopDevice::IsValid() { |
| return device_number_ >= 0; |
| } |
| |
| LoopDeviceManager::LoopDeviceManager() |
| : loop_ioctl_(base::Bind(&LoopDeviceIoctl)) {} |
| |
| LoopDeviceManager::LoopDeviceManager(LoopIoctl ioctl_runner) |
| : loop_ioctl_(ioctl_runner) {} |
| |
| std::unique_ptr<LoopDevice> LoopDeviceManager::AttachDeviceToFile( |
| const base::FilePath& backing_file) { |
| int device_number = -1; |
| int retries = kMaxLoopDeviceAttachTries; |
| |
| while (retries >= 0) { |
| device_number = |
| loop_ioctl_.Run(base::FilePath(kLoopControl), LOOP_CTL_GET_FREE, 0, |
| kLoopControlIoctlFlags); |
| |
| if (device_number < 0) { |
| LOG(ERROR) << "ioctl(LOOP_CTL_GET_FREE) failed"; |
| return CreateLoopDevice(-1, base::FilePath()); |
| } |
| |
| base::ScopedFD backing_file_fd( |
| HANDLE_EINTR(open(backing_file.value().c_str(), O_RDWR))); |
| |
| if (!backing_file_fd.is_valid()) { |
| LOG(ERROR) << "Failed to open backing file."; |
| return CreateLoopDevice(-1, base::FilePath()); |
| } |
| |
| base::FilePath device_path = CreateDevicePath(device_number); |
| |
| if (loop_ioctl_.Run(device_path, LOOP_SET_FD, backing_file_fd.get(), |
| kLoopDeviceIoctlFlags) == 0) |
| break; |
| |
| if (errno != EBUSY) { |
| LOG(ERROR) << "ioctl(LOOP_SET_FD) failed"; |
| return CreateLoopDevice(-1, base::FilePath()); |
| } |
| |
| // Other users could have set the file descriptor between get a valid |
| // loop device number and using it. Continue to loop until the device is |
| // created. |
| retries--; |
| } |
| |
| if (retries < 0) { |
| LOG(ERROR) << "Failed to set up loop device after " |
| << kMaxLoopDeviceAttachTries << " retries."; |
| return CreateLoopDevice(-1, base::FilePath()); |
| } |
| |
| // Set direct I/O mode for the backing file for the loop device, if supported. |
| if (loop_ioctl_.Run(CreateDevicePath(device_number), LOOP_SET_DIRECT_IO, 1, |
| kLoopDeviceIoctlFlags) != 0) |
| PLOG(WARNING) << "Direct I/O mode is not supported."; |
| |
| // All steps of setting up the loop device succeeded. |
| return CreateLoopDevice(device_number, backing_file); |
| } |
| |
| std::vector<std::unique_ptr<LoopDevice>> |
| LoopDeviceManager::GetAttachedDevices() { |
| return SearchLoopDevicePaths(); |
| } |
| |
| std::unique_ptr<LoopDevice> LoopDeviceManager::GetAttachedDeviceByNumber( |
| int device_number) { |
| auto devices = SearchLoopDevicePaths(device_number); |
| |
| if (devices.empty()) |
| return CreateLoopDevice(-1, base::FilePath()); |
| |
| return std::move(devices[0]); |
| } |
| |
| std::unique_ptr<LoopDevice> LoopDeviceManager::GetAttachedDeviceByName( |
| const std::string& name) { |
| std::vector<std::unique_ptr<LoopDevice>> devices = GetAttachedDevices(); |
| |
| for (auto& attached_device : devices) { |
| struct loop_info64 device_info; |
| |
| if (!attached_device->GetStatus(&device_info)) { |
| LOG(ERROR) << "GetStatus failed"; |
| continue; |
| } |
| |
| if (strcmp(reinterpret_cast<char*>(device_info.lo_file_name), |
| name.c_str()) == 0) |
| return std::move(attached_device); |
| } |
| |
| return CreateLoopDevice(-1, base::FilePath()); |
| } |
| |
| // virtual |
| std::vector<std::unique_ptr<LoopDevice>> |
| LoopDeviceManager::SearchLoopDevicePaths(int device_number) { |
| std::vector<std::unique_ptr<LoopDevice>> devices; |
| base::FilePath rootdir(kSysBlockPath); |
| |
| if (device_number != -1) { |
| auto loopdev_path = |
| rootdir.Append(base::StringPrintf("loop%d", device_number)); |
| if (base::PathExists(loopdev_path)) |
| devices.push_back( |
| CreateLoopDevice(device_number, GetBackingFile(loopdev_path))); |
| } else { |
| // Read /sys/block to discover all loop devices. |
| base::FileEnumerator loopdev_enum( |
| rootdir, false /*recursive*/, |
| base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS, |
| "loop*"); |
| |
| for (auto loopdev = loopdev_enum.Next(); !loopdev.empty(); |
| loopdev = loopdev_enum.Next()) { |
| int dev_number = GetDeviceNumber(loopdev); |
| if (dev_number != -1) |
| devices.push_back( |
| CreateLoopDevice(dev_number, GetBackingFile(loopdev))); |
| } |
| } |
| return devices; |
| } |
| |
| std::unique_ptr<LoopDevice> LoopDeviceManager::CreateLoopDevice( |
| int device_number, const base::FilePath& backing_file) { |
| return std::make_unique<LoopDevice>(device_number, backing_file, loop_ioctl_); |
| } |
| |
| } // namespace brillo |