// Copyright (C) 2020 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 #include #include #include #include #include #include #include "block_dev_initializer.h" namespace android { namespace init { using android::base::Timer; using namespace std::chrono_literals; BlockDevInitializer::BlockDevInitializer() : uevent_listener_(16 * 1024 * 1024) { auto boot_devices = android::fs_mgr::GetBootDevices(); device_handler_ = std::make_unique( std::vector{}, std::vector{}, std::vector{}, std::vector{}, std::move(boot_devices), android::fs_mgr::GetBootPartUuid(), false); } // If boot_part_uuid is specified, use it to set boot_devices // // When `androidboot.boot_part_uuid` is specified then that's the partition UUID // of the kernel. Look for that partition and then set `boot_devices` to be // exactly one item: the block device containing that partition. // // NOTE that `boot_part_uuid` is only specified on newer devices. Older devices // specified `boot_devices` directly. bool BlockDevInitializer::InitBootDevicesFromPartUuid() { bool uuid_check_done = false; auto boot_part_callback = [&, this](const Uevent& uevent) -> ListenerAction { uuid_check_done = device_handler_->CheckUeventForBootPartUuid(uevent); return uuid_check_done ? ListenerAction::kStop : ListenerAction::kContinue; }; // Re-run already arrived uevents looking for the boot partition UUID. // // NOTE: If we're not using the boot partition UUID to find the boot // device then the first uevent we analyze will cause us to stop looking // and set `uuid_check_done`. This will shortcut all of the UUID logic. // Replaying one uevent is not expected to be slow. uevent_listener_.RegenerateUevents(boot_part_callback); // If we're not done looking, poll for uevents for longer if (!uuid_check_done) { Timer t; uevent_listener_.Poll(boot_part_callback, 10s); LOG(INFO) << "Wait for boot partition returned after " << t; } // Give a nicer error message if we were expecting to find the kernel boot // partition but didn't. Later code would fail too but the message there // is a bit further from the root cause of the problem. if (!uuid_check_done) { LOG(ERROR) << __PRETTY_FUNCTION__ << ": boot partition not found after polling timeout."; return false; } return true; } bool BlockDevInitializer::InitDeviceMapper() { return InitMiscDevice("device-mapper"); } bool BlockDevInitializer::InitDmUser(const std::string& name) { return InitMiscDevice("dm-user!" + name); } bool BlockDevInitializer::InitMiscDevice(const std::string& name) { const std::string dm_path = "/devices/virtual/misc/" + name; bool found = false; auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) { if (uevent.path == dm_path) { device_handler_->HandleUevent(uevent); found = true; return ListenerAction::kStop; } return ListenerAction::kContinue; }; uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback); if (!found) { LOG(INFO) << name << " device not found in /sys, waiting for its uevent"; Timer t; uevent_listener_.Poll(dm_callback, 10s); LOG(INFO) << "Wait for " << name << " returned after " << t; } if (!found) { LOG(ERROR) << name << " device not found after polling timeout"; return false; } return true; } ListenerAction BlockDevInitializer::HandleUevent(const Uevent& uevent, std::set* devices) { // Ignore everything that is not a block device. if (uevent.subsystem != "block") { return ListenerAction::kContinue; } auto name = uevent.partition_name; if (name.empty()) { size_t base_idx = uevent.path.rfind('/'); if (base_idx == std::string::npos) { return ListenerAction::kContinue; } name = uevent.path.substr(base_idx + 1); } auto iter = devices->find(name); if (iter == devices->end()) { auto partition_name = DeviceHandler::GetPartitionNameForDevice(uevent.device_name); if (!partition_name.empty()) { iter = devices->find(partition_name); } if (iter == devices->end()) { return ListenerAction::kContinue; } } LOG(VERBOSE) << __PRETTY_FUNCTION__ << ": found partition: " << name; // Remove the partition from the list of partitions we're waiting for. // // Partitions that we're waiting for here are expected to be on the boot // device, so only remove from the list if they're on the boot device. // This prevents us from being confused if there are multiple disks (some // perhaps connected via USB) that have matching partition names. // // ...but... // // Some products (especialy emulators) don't seem to set up boot_devices // or possibly not all the partitions that we need to wait for are on the // specified boot device. Thus, only require partitions to be on the boot // device in "strict" mode, which should be used on newer systems. if (device_handler_->IsBootDevice(uevent) || !device_handler_->IsBootDeviceStrict()) { devices->erase(iter); } device_handler_->HandleUevent(uevent); return devices->empty() ? ListenerAction::kStop : ListenerAction::kContinue; } // Wait for partitions that are expected to be on the "boot device" to initialize. // // Wait (for up to 10 seconds) for partitions passed in `devices` to show up. // All block devices found while waiting will be initialized, which includes // creating symlinks for them in /dev/block. Once all `devices` are found we'll // return success (true). If any devices aren't found we'll return failure // (false). As devices are found they will be removed from `devices`. // // The contents of `devices` is the names of the partitions. This can be: // - The `partition_name` reported by a uevent, or the final component in the // `path` reported by a uevent if the `partition_name` is blank. // - The result of DeviceHandler::GetPartitionNameForDevice() on the // `device_name` reported by a uevent. // // NOTE: on newer systems partitions _must_ be on the "boot device". See // comments inside HandleUevent(). bool BlockDevInitializer::InitDevices(std::set devices) { auto uevent_callback = [&, this](const Uevent& uevent) -> ListenerAction { return HandleUevent(uevent, &devices); }; uevent_listener_.RegenerateUevents(uevent_callback); // UeventCallback() will remove found partitions from |devices|. So if it // isn't empty here, it means some partitions are not found. if (!devices.empty()) { LOG(INFO) << __PRETTY_FUNCTION__ << ": partition(s) not found in /sys, waiting for their uevent(s): " << android::base::Join(devices, ", "); Timer t; uevent_listener_.Poll(uevent_callback, 10s); LOG(INFO) << "Wait for partitions returned after " << t; } if (!devices.empty()) { LOG(ERROR) << __PRETTY_FUNCTION__ << ": partition(s) not found after polling timeout: " << android::base::Join(devices, ", "); return false; } return true; } // Creates "/dev/block/dm-XX" for dm nodes by running coldboot on /sys/block/dm-XX. bool BlockDevInitializer::InitDmDevice(const std::string& device) { const std::string device_name(basename(device.c_str())); const std::string syspath = "/sys/block/" + device_name; return InitDevice(syspath, device_name); } bool BlockDevInitializer::InitPlatformDevice(const std::string& dev_name) { return InitDevice("/sys/devices/platform", dev_name); } bool BlockDevInitializer::InitHvcDevice(const std::string& dev_name) { return InitDevice("/sys/devices/virtual/tty", dev_name); } bool BlockDevInitializer::InitDevice(const std::string& syspath, const std::string& device_name) { bool found = false; auto uevent_callback = [&device_name, this, &found](const Uevent& uevent) { if (uevent.device_name == device_name) { LOG(VERBOSE) << "Creating device : " << device_name; device_handler_->HandleUevent(uevent); found = true; return ListenerAction::kStop; } return ListenerAction::kContinue; }; uevent_listener_.RegenerateUeventsForPath(syspath, uevent_callback); if (!found) { LOG(INFO) << "device '" << device_name << "' not found in /sys, waiting for its uevent"; Timer t; uevent_listener_.Poll(uevent_callback, 10s); LOG(INFO) << "wait for device '" << device_name << "' returned after " << t; } if (!found) { LOG(ERROR) << "device '" << device_name << "' not found after polling timeout"; return false; } return true; } } // namespace init } // namespace android