| /* |
| * Copyright (C) 2010 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 "ueventd.h" |
| |
| #include <android/api-level.h> |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <thread> |
| |
| #include <android-base/chrono_utils.h> |
| #include <android-base/logging.h> |
| #include <android-base/properties.h> |
| #include <fstab/fstab.h> |
| |
| #include "coldboot.h" |
| #include "devices.h" |
| #include "firmware_handler.h" |
| #include "modalias_handler.h" |
| #include "selabel.h" |
| #include "selinux.h" |
| #include "uevent_dependency_graph.h" |
| #include "uevent_handler.h" |
| #include "uevent_listener.h" |
| #include "ueventd_parser.h" |
| #include "util.h" |
| |
| // At a high level, ueventd listens for uevent messages generated by the kernel through a netlink |
| // socket. When ueventd receives such a message it handles it by taking appropriate actions, |
| // which can typically be creating a device node in /dev, setting file permissions, setting selinux |
| // labels, etc. |
| // Ueventd also handles loading of firmware that the kernel requests, and creates symlinks for block |
| // and character devices. |
| |
| // When ueventd starts, it regenerates uevents for all currently registered devices by traversing |
| // /sys and writing 'add' to each 'uevent' file that it finds. This causes the kernel to generate |
| // and resend uevent messages for all of the currently registered devices. This is done, because |
| // ueventd would not have been running when these devices were registered and therefore was unable |
| // to receive their uevent messages and handle them appropriately. This process is known as |
| // 'cold boot'. |
| |
| // 'init' currently waits synchronously on the cold boot process of ueventd before it continues |
| // its boot process. For this reason, cold boot should be as quick as possible. One way to achieve |
| // a speed up here is to parallelize the handling of ueventd messages, which consume the bulk of the |
| // time during cold boot. |
| |
| // Handling of uevent messages has two unique properties: |
| // 1) Messages can be handled in isolation when they do not depend on another. A device event |
| // depends on another when the device is the same, parent, or child of another device which |
| // should be handled first by another event. e.g. An add event must be handled first before |
| // removal. A device foo must be added before foo/bar. |
| // 2) The cold boot is unlikely to have events that depend on another in a critical manner. |
| // Therefore, ueventd handles uevent message in parallel. We provide two options for the |
| // parallel handling: |
| // 1) ueventd forks 'n' separate uevent handler subprocesses and has each of them to handle the |
| // uevents in the queue based on a starting offset (their process number) and a stride (the total |
| // number of processes). |
| // 2) Coldboot threadpool: ueventd uses a threadpool to handle uevents. |
| // The first option is the default option. The second option can be enabled by a feature flag |
| // RELEASE_UEVENTD_COLDBOOT_THREADPOOL. The second option provides the better performance since it |
| // distributes the uevent handling workload evenly across all the threads, while the first option |
| // only assigns equal numbers of uevents to each process and does not take the actual workload of |
| // each of them into consideration. |
| |
| // One other important caveat during the boot process is the handling of SELinux restorecon. |
| // Since many devices have child devices, calling selinux_android_restorecon() recursively for each |
| // device when its uevent is handled, results in multiple restorecon operations being done on a |
| // given file. It is more efficient to simply do restorecon recursively on /sys during cold boot, |
| // than to do restorecon on each device as its uevent is handled. This only applies to cold boot; |
| // once that has completed, restorecon is done for each device as its uevent is handled. |
| |
| // With all of the above considered, the cold boot process has the below steps: |
| // 1) ueventd regenerates uevents by doing the /sys traversal and listens to the netlink socket for |
| // the generated uevents. It writes these uevents into a queue. |
| // |
| // 2 (When threadpool is disabled)) ueventd forks 'n' separate uevent handler subprocesses and has |
| // each of them to handle the uevents in the queue based on a starting offset (their process |
| // number) and a stride (the total number of processes). Note that no IPC happens at this point |
| // and only const functions from DeviceHandler should be called from this context. |
| |
| // 2 (When threadpool is enabled)) ueventd uses a threadpool to handle uevents. The threadpool |
| // has 'n' threads and each thread is responsible for handling available uevents in the queue. |
| // |
| // 3) If enable_parallel_restorecon is false, the main thread of ueventd calls |
| // selinux_android_restorecon() recursively on /sys in parallel to the subprocesses or threadpool |
| // handling the uevents. |
| // |
| // 4) Once the restorecon operation finishes, the main thread calls waitpid() to wait for all |
| // subprocess handlers to complete and exit. Once this happens, it marks coldboot as having |
| // completed. |
| // |
| // At this point, ueventd is single threaded, poll()'s and then handles any future uevents. |
| |
| // Lastly, it should be noted that uevents that occur during the coldboot process are handled |
| // without issue after the coldboot process completes. This is because the uevent listener is |
| // paused while the uevent handler and restorecon actions take place. Once coldboot completes, |
| // the uevent listener resumes in polling mode and will handle the uevents that occurred during |
| // coldboot. |
| |
| namespace android { |
| namespace init { |
| |
| static constexpr bool kLogUeventDuration = false; |
| |
| static UeventdConfiguration GetConfiguration() { |
| if (IsMicrodroid()) { |
| return ParseConfig({"/system/etc/ueventd.rc", "/vendor/etc/ueventd.rc"}); |
| } |
| |
| return ParseConfig({"/system/etc/ueventd.rc"}); |
| } |
| |
| void main_loop(const UeventListener& uevent_listener, |
| const std::vector<std::shared_ptr<UeventHandler>>& uevent_handlers) { |
| uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) { |
| android::base::Timer t; |
| for (auto& uevent_handler : uevent_handlers) { |
| uevent_handler->HandleUevent(uevent); |
| } |
| if (kLogUeventDuration) { |
| LOG(INFO) << uevent << " took " << t.duration().count() << "ms"; |
| } |
| return ListenerAction::kContinue; |
| }); |
| } |
| |
| void parallel_main_loop(const UeventListener& uevent_listener, |
| const std::vector<std::shared_ptr<UeventHandler>>& uevent_handlers, |
| size_t num_threads) { |
| LOG(INFO) << "parallel main loop is enabled with " << num_threads << " threads"; |
| |
| std::vector<std::thread> threads; |
| UeventDependencyGraph graph; |
| |
| for (unsigned int i = 0; i < num_threads; i++) { |
| threads.emplace_back([&graph, &uevent_handlers] { |
| while (true) { |
| auto uevent = graph.WaitDependencyFreeEvent(); |
| android::base::Timer t; |
| for (auto& uevent_handler : uevent_handlers) { |
| uevent_handler->HandleUevent(uevent); |
| } |
| if (kLogUeventDuration) { |
| LOG(INFO) << uevent << " took " << t.duration().count() << "ms"; |
| } |
| graph.MarkEventCompleted(uevent.seqnum); |
| } |
| }); |
| } |
| |
| uevent_listener.Poll([&graph](const Uevent& uevent) { |
| graph.Add(uevent); |
| return ListenerAction::kContinue; |
| }); |
| } |
| |
| int ueventd_main(int argc, char** argv) { |
| /* |
| * init sets the umask to 077 for forked processes. We need to |
| * create files with exact permissions, without modification by |
| * the umask. |
| */ |
| umask(000); |
| |
| android::base::InitLogging(argv, &android::base::KernelLogger); |
| |
| LOG(INFO) << "ueventd started!"; |
| |
| SelinuxSetupKernelLogging(); |
| SelabelInitialize(); |
| |
| std::vector<std::shared_ptr<UeventHandler>> uevent_handlers; |
| |
| auto ueventd_configuration = GetConfiguration(); |
| |
| UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size); |
| |
| // Right after making DeviceHandler, replay all events looking for which |
| // block device has the boot partition. This lets us make symlinks |
| // for all of the other partitions on the same disk. Note that by the time |
| // we get here we know that the boot partition has already shown up (if |
| // we're looking for it) so just regenerating events is enough to know |
| // we'll see it. |
| std::shared_ptr<DeviceHandler> device_handler = std::make_shared<DeviceHandler>( |
| std::move(ueventd_configuration.dev_permissions), |
| std::move(ueventd_configuration.sysfs_permissions), |
| std::move(ueventd_configuration.drivers), std::move(ueventd_configuration.subsystems), |
| android::fs_mgr::GetBootDevices(), android::fs_mgr::GetBootPartUuid(), true); |
| uevent_listener.RegenerateUevents([&](const Uevent& uevent) -> ListenerAction { |
| bool uuid_check_done = device_handler->CheckUeventForBootPartUuid(uevent); |
| return uuid_check_done ? ListenerAction::kStop : ListenerAction::kContinue; |
| }); |
| |
| if (ueventd_configuration.enable_modalias_handling) { |
| std::vector<std::string> base_paths = {"/odm/lib/modules", "/vendor/lib/modules"}; |
| uevent_handlers.emplace_back(std::make_shared<ModaliasHandler>(base_paths)); |
| } |
| uevent_handlers.emplace_back(std::move(device_handler)); |
| uevent_handlers.emplace_back(std::make_shared<FirmwareHandler>( |
| std::move(ueventd_configuration.firmware_directories), |
| std::move(ueventd_configuration.external_firmware_handlers), |
| /*serial_handler_after_cold_boot=*/false)); |
| |
| if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) { |
| ColdBoot cold_boot(uevent_listener, uevent_handlers, |
| ueventd_configuration.enable_parallel_restorecon, |
| ueventd_configuration.parallel_restorecon_dirs); |
| cold_boot.Run(); |
| } |
| |
| for (auto& uevent_handler : uevent_handlers) { |
| uevent_handler->ColdbootDone(); |
| } |
| |
| // We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now. |
| signal(SIGCHLD, SIG_IGN); |
| // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN |
| // for SIGCHLD above. |
| while (waitpid(-1, nullptr, WNOHANG) > 0) { |
| } |
| |
| // Restore prio before main loop |
| setpriority(PRIO_PROCESS, 0, 0); |
| |
| if (ueventd_configuration.enable_parallel_ueventd_main_loop) { |
| size_t num_threads = |
| std::thread::hardware_concurrency() != 0 ? std::thread::hardware_concurrency() : 4; |
| if (ueventd_configuration.parallel_main_loop_max_workers.has_value()) { |
| num_threads = std::min(num_threads, |
| ueventd_configuration.parallel_main_loop_max_workers.value()); |
| } |
| parallel_main_loop(uevent_listener, uevent_handlers, num_threads); |
| } else { |
| main_loop(uevent_listener, uevent_handlers); |
| } |
| |
| LOG(ERROR) << "main loop exited unexpectedly"; |
| return EXIT_FAILURE; |
| } |
| |
| } // namespace init |
| } // namespace android |