| // 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 <chrono> | |
| #include <string_view> | |
| #include <vector> | |
| #include <android-base/chrono_utils.h> | |
| #include <android-base/logging.h> | |
| #include <android-base/strings.h> | |
| #include <fs_mgr.h> | |
| #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<DeviceHandler>( | |
| std::vector<Permissions>{}, std::vector<SysfsPermissions>{}, std::vector<Subsystem>{}, | |
| std::vector<Subsystem>{}, 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<std::string>* 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<std::string> 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 |